defmodule Localiser.RSSI.Buffer do use GenServer alias Localiser.Localisation alias Localiser.Localisation.Tag.Filter, as: TagFilter alias Localiser.Localisation.Sensor.Server, as: SensorServer @flush_interval_ms 500 # reading :: %{sensor_id: String.t(), tag_id: String.t(), rssi: integer()} def push(reading) do GenServer.cast(__MODULE__, {:push, reading}) end def start_link(_args) do GenServer.start_link(__MODULE__, %{}, name: __MODULE__) end @impl true def init(state) do schedule_flush() {:ok, state} end @impl true def handle_cast({:push, %{tag_id: tag_id} = reading}, state) do {:noreply, Map.update(state, tag_id, [reading], &[reading | &1])} end @impl true def handle_info(:flush, state) do flush_batches(state) schedule_flush() {:noreply, %{}} end defp flush_batches(batches) do Enum.each(batches, fn {tag_id, readings} -> case Registry.lookup(Localiser.Registry, {:filter, tag_id}) do [{_pid, _}] -> measurements = Enum.flat_map(readings, &resolve_measurement/1) if measurements != [], do: TagFilter.ingest(tag_id, measurements) [] -> :ok end end) end # Resolves a raw RSSI reading to a measurement with sensor location and distance. # If the sensor is in calibration mode, feeds the reading to Sensor.Server instead # and returns [] so the sample is excluded from Tag.Filter measurements. # If the sensor server isn't running, returns []. defp resolve_measurement(%{sensor_id: sensor_id, rssi: rssi}) do case Registry.lookup(Localiser.Registry, {:sensor, sensor_id}) do [{_pid, _}] -> if SensorServer.calibrating?(sensor_id) do SensorServer.calibration_reading(sensor_id, rssi) [] else [SensorServer.measure(sensor_id, rssi)] end [] -> [] end end defp schedule_flush do Process.send_after(self(), :flush, @flush_interval_ms) end end