defmodule Localiser.MQTT.Router do @moduledoc """ Module responsible for routing incoming MQTT messages to the appropriate handlers. """ use GenServer require Logger alias Localiser.Domain.Sensors def start_link(opts) do GenServer.start_link(__MODULE__, opts, name: __MODULE__) end def route(topic, payload) do GenServer.cast(__MODULE__, {:route, topic, payload}) end # GenServer Callbacks @impl true def init(_opts) do {:ok, %{}} end @impl true def handle_cast({:route, topic, payload}, state) do case parse_topic(topic) do {:rssi, sensor_id} -> handle_rssi(sensor_id, payload) {:announce, sensor_id} -> handle_announce(sensor_id) {:error, :invalid_topic} -> Logger.debug("[MQTT.Router] Received message with invalid topic: #{topic}") end {:noreply, state} end # Private helper functions defp parse_topic(topic) do # Example topic: "localiser/sensor/device123/rssi" case String.split(topic, "/") do ["localiser", "sensor", sensor_id, "rssi"] -> {:rssi, sensor_id} ["localiser", "sensor", sensor_id, "announce"] -> {:announce, sensor_id} _ -> {:error, :invalid_topic} end end defp handle_rssi(sensor_id, payload) do with {:ok, %{"tag_id" => tag_id, "rssi" => rssi}} <- Jason.decode(payload) do reading = %{ sensor_id: sensor_id, tag_id: tag_id, rssi: rssi, timestamp: DateTime.utc_now() } Localiser.MQTT.Telemetry.count_reading() Localiser.RSSI.Buffer.push(reading) else {:error, reason} -> Logger.error("[MQTT.Router] Bad payload from #{sensor_id}: #{inspect(reason)}") Localiser.MQTT.Telemetry.count_error() end end defp handle_announce(sensor_id) do case Sensors.upsert_announced(sensor_id) do {:ok, _sensor} -> Logger.info("[MQTT.Router] Sensor announced: #{sensor_id}") {:error, reason} -> Logger.error("[MQTT.Router] Failed to register announced sensor #{sensor_id}: #{inspect(reason)}") end end end