Skip to content

Commit a0a7bc6

Browse files
fhunlethmnishiguchi
authored andcommitted
Make pure library and support >1 sensor
This moves the C algorithm code management out of a singleton GenServer since it maintains per-sensor state. This also makes this package a pure library and simplifies state handling in the port.
1 parent 179b8dc commit a0a7bc6

File tree

5 files changed

+73
-58
lines changed

5 files changed

+73
-58
lines changed

lib/sgp40.ex

+42-7
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,14 @@ defmodule SGP40 do
2929

3030
defmodule State do
3131
@moduledoc false
32-
defstruct [:humidity_rh, :last_measurement, :serial_id, :temperature_c, :transport]
32+
defstruct [
33+
:humidity_rh,
34+
:last_measurement,
35+
:serial_id,
36+
:temperature_c,
37+
:transport,
38+
:voc_index
39+
]
3340
end
3441

3542
@default_bus_name "i2c-1"
@@ -58,6 +65,24 @@ defmodule SGP40 do
5865
GenServer.call(server, :measure)
5966
end
6067

68+
@spec get_states(GenServer.server()) ::
69+
{:ok, SGP40.VocIndex.AlgorithmStates.t()} | {:error, any}
70+
def get_states(server) do
71+
GenServer.call(server, :get_states)
72+
end
73+
74+
@spec set_states(GenServer.server(), SGP40.VocIndex.AlgorithmStates.t()) ::
75+
{:ok, binary} | {:error, any}
76+
def set_states(server, args) do
77+
GenServer.call(server, {:set_states, args})
78+
end
79+
80+
@spec set_tuning_params(GenServer.server(), SGP40.VocIndex.AlgorithmTuningParams.t()) ::
81+
{:ok, binary} | {:error, any}
82+
def set_tuning_params(server, args) do
83+
GenServer.call(server, {:set_tuning_params, args})
84+
end
85+
6186
@doc """
6287
Update relative ambient humidity (RH %) and ambient temperature (degree C)
6388
for the humidity compensation.
@@ -82,13 +107,15 @@ defmodule SGP40 do
82107
case @transport_mod.open(bus_name: bus_name, bus_address: bus_address) do
83108
{:ok, transport} ->
84109
{:ok, serial_id} = SGP40.Comm.serial_id(transport)
110+
{:ok, voc_index} = SGP40.VocIndex.start_link()
85111

86112
state = %State{
87113
humidity_rh: humidity_rh,
88114
last_measurement: nil,
89115
serial_id: serial_id,
90116
temperature_c: temperature_c,
91-
transport: transport
117+
transport: transport,
118+
voc_index: voc_index
92119
}
93120

94121
{:ok, state, {:continue, :init_sensor}}
@@ -123,7 +150,7 @@ defmodule SGP40 do
123150
state.humidity_rh,
124151
state.temperature_c
125152
),
126-
{:ok, voc_index} <- SGP40.VocIndex.process(sraw) do
153+
{:ok, voc_index} <- SGP40.VocIndex.process(state.voc_index, sraw) do
127154
timestamp_ms = System.monotonic_time(:millisecond)
128155
measurement = %SGP40.Measurement{timestamp_ms: timestamp_ms, voc_index: voc_index}
129156

@@ -140,14 +167,22 @@ defmodule SGP40 do
140167
{:reply, {:ok, state.last_measurement}, state}
141168
end
142169

170+
def handle_call(:get_states, _from, state) do
171+
{:reply, SGP40.VocIndex.get_states(state.voc_index), state}
172+
end
173+
174+
def handle_call({:set_states, args}, _from, state) do
175+
{:reply, SGP40.VocIndex.set_states(state.voc_index, args), state}
176+
end
177+
178+
def handle_call({:set_tuning_params, args}, _from, state) do
179+
{:reply, SGP40.VocIndex.set_tuning_params(state.voc_index, args), state}
180+
end
181+
143182
@impl GenServer
144183
def handle_cast({:update_rht, humidity_rh, temperature_c}, state) do
145184
state = %{state | humidity_rh: humidity_rh, temperature_c: temperature_c}
146185

147186
{:noreply, state}
148187
end
149-
150-
defdelegate get_states, to: SGP40.VocIndex
151-
defdelegate set_states(args), to: SGP40.VocIndex
152-
defdelegate set_tuning_params(args), to: SGP40.VocIndex
153188
end

lib/sgp40/application.ex

-21
This file was deleted.

lib/sgp40/voc_index.ex

+18-24
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,27 @@ defmodule SGP40.VocIndex do
22
@moduledoc """
33
Process the raw output of the SGP40 sensor into the VOC Index.
44
"""
5+
use GenServer
56

67
alias SGP40.VocIndex.AlgorithmStates
78
alias SGP40.VocIndex.AlgorithmTuningParams
89

9-
use GenServer, restart: :permanent
10-
1110
require Logger
1211

1312
@doc """
14-
Initialize the VOC algorithm parameters. Call this once at the beginning or
15-
whenever the sensor stopped measurements.
13+
Initialize the VOC algorithm
1614
"""
1715
@spec start_link(keyword()) :: GenServer.on_start()
18-
def start_link(_args \\ []) do
19-
case GenServer.start_link(__MODULE__, nil, name: __MODULE__) do
20-
{:ok, pid} -> {:ok, pid}
21-
# Stop this process and let the supervisor restart so that we can
22-
# re-initialize the VOC algorithm.
23-
{:error, {:already_started, pid}} -> GenServer.stop(pid, :normal)
24-
end
16+
def start_link(args \\ []) do
17+
GenServer.start_link(__MODULE__, args)
2518
end
2619

2720
@doc """
2821
Calculate the VOC index value from the raw sensor value.
2922
"""
30-
@spec process(0..0xFFFF) :: {:ok, 1..500} | {:error, any}
31-
def process(sraw) do
32-
GenServer.call(__MODULE__, {:process, sraw})
23+
@spec process(GenServer.server(), 0..0xFFFF) :: {:ok, 1..500} | {:error, any}
24+
def process(server, sraw) do
25+
GenServer.call(server, {:process, sraw})
3326
end
3427

3528
@doc """
@@ -38,30 +31,31 @@ defmodule SGP40.VocIndex do
3831
skipping initial learning phase. This feature can only be used after at least
3932
3 hours of continuous operation.
4033
"""
41-
@spec get_states :: {:ok, AlgorithmStates.t()} | {:error, any}
42-
def get_states() do
43-
GenServer.call(__MODULE__, :get_states)
34+
@spec get_states(GenServer.server()) :: {:ok, AlgorithmStates.t()} | {:error, any}
35+
def get_states(server) do
36+
GenServer.call(server, :get_states)
4437
end
4538

4639
@doc """
4740
Set previously retrieved algorithm states to resume operation after a short
4841
interruption, skipping initial learning phase. This feature should not be
49-
used after inerruptions of more than 10 minutes. Call this once after
42+
used after interruptions of more than 10 minutes. Call this once after
5043
`start_link/1` and the optional `set_tuning_params/1`, if
5144
desired. Otherwise, the algorithm will start with initial learning phase.
5245
"""
53-
@spec set_states(AlgorithmStates.t()) :: {:ok, binary} | {:error, any}
54-
def set_states(args) do
55-
GenServer.call(__MODULE__, {:set_states, args})
46+
@spec set_states(GenServer.server(), AlgorithmStates.t()) :: {:ok, binary} | {:error, any}
47+
def set_states(server, args) do
48+
GenServer.call(server, {:set_states, args})
5649
end
5750

5851
@doc """
5952
Set parameters to customize the VOC algorithm. Call this once after
6053
`start_link/1`, if desired. Otherwise, the default values will be used.
6154
"""
62-
@spec set_tuning_params(AlgorithmTuningParams.t()) :: {:ok, binary} | {:error, any}
63-
def set_tuning_params(args) do
64-
GenServer.call(__MODULE__, {:set_tuning_params, args})
55+
@spec set_tuning_params(GenServer.server(), AlgorithmTuningParams.t()) ::
56+
{:ok, binary} | {:error, any}
57+
def set_tuning_params(server, args) do
58+
GenServer.call(server, {:set_tuning_params, args})
6559
end
6660

6761
@impl GenServer

mix.exs

+1-2
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ defmodule SGP40.MixProject do
2727
# Run "mix help compile.app" to learn about applications.
2828
def application do
2929
[
30-
extra_applications: [:logger],
31-
mod: {SGP40.Application, []}
30+
extra_applications: [:logger]
3231
]
3332
end
3433

test/sgp40/voc_index_test.exs

+12-4
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,42 @@ defmodule SGP40.VocIndexTestTest do
33
alias SGP40.VocIndex
44

55
test "process" do
6-
{:ok, voc_index} = VocIndex.process(123)
6+
pid = start_supervised!(SGP40.VocIndex)
7+
8+
{:ok, voc_index} = VocIndex.process(pid, 123)
79

810
assert is_integer(voc_index)
911
end
1012

1113
test "get_states" do
12-
{:ok, states} = VocIndex.get_states()
14+
pid = start_supervised!(SGP40.VocIndex)
15+
16+
{:ok, states} = VocIndex.get_states(pid)
1317

1418
assert %{mean: mean, std: std} = states
1519
assert is_integer(mean)
1620
assert is_integer(std)
1721
end
1822

1923
test "set_states" do
20-
assert {:ok, echo} = VocIndex.set_states(%{mean: 1, std: 2})
24+
pid = start_supervised!(SGP40.VocIndex)
25+
26+
assert {:ok, echo} = VocIndex.set_states(pid, %{mean: 1, std: 2})
2127

2228
assert echo =~ ~r/mean:\d*,std:\d*/
2329
end
2430

2531
test "set_tuning_params" do
32+
pid = start_supervised!(SGP40.VocIndex)
33+
2634
params = %{
2735
voc_index_offset: 1,
2836
learning_time_hours: 2,
2937
gating_max_duration_minutes: 3,
3038
std_initial: 4
3139
}
3240

33-
assert {:ok, echo} = VocIndex.set_tuning_params(params)
41+
assert {:ok, echo} = VocIndex.set_tuning_params(pid, params)
3442

3543
assert echo ==
3644
"voc_index_offset:1,learning_time_hours:2,gating_max_duration_minutes:3,std_initial:4"

0 commit comments

Comments
 (0)