diff --git a/lib/localiser/domain/schema/sensor.ex b/lib/localiser/domain/schema/sensor.ex index 2c67468..d0ef267 100644 --- a/lib/localiser/domain/schema/sensor.ex +++ b/lib/localiser/domain/schema/sensor.ex @@ -8,6 +8,7 @@ defmodule Localiser.Domain.Schema.Sensor do schema "sensors" do field :sensor_id, :string field :confirmed, :boolean, default: false + field :firmware_version, :string field :x, :float field :y, :float field :floor_x, :float, virtual: true @@ -22,7 +23,7 @@ defmodule Localiser.Domain.Schema.Sensor do @doc false def changeset(sensor, attrs) do sensor - |> cast(attrs, [:sensor_id, :confirmed, :room_id, :x, :y]) + |> cast(attrs, [:sensor_id, :confirmed, :firmware_version, :room_id, :x, :y]) |> validate_required([:sensor_id]) |> unique_constraint(:sensor_id) |> assoc_constraint(:room) diff --git a/lib/localiser/domain/sensors.ex b/lib/localiser/domain/sensors.ex index de1d831..b0d5c7c 100644 --- a/lib/localiser/domain/sensors.ex +++ b/lib/localiser/domain/sensors.ex @@ -59,9 +59,29 @@ defmodule Localiser.Domain.Sensors do {:ok, sensor} end + @version_timeout_ms 5_000 + def get_version(%Sensor{} = sensor) do - Localiser.MQTT.Connection.publish(cmd_topic(sensor), Jason.encode!(%{action: "version"})) - {:ok, sensor} + :ok = Phoenix.PubSub.subscribe(Localiser.PubSub, "sensors") + + case Localiser.MQTT.Connection.publish(cmd_topic(sensor), Jason.encode!(%{action: "version"})) do + :ok -> + result = + receive do + {:sensor_announced, %Sensor{sensor_id: sid, firmware_version: v}} + when sid == sensor.sensor_id and not is_nil(v) -> + {:ok, v} + after + @version_timeout_ms -> {:error, :timeout} + end + + Phoenix.PubSub.unsubscribe(Localiser.PubSub, "sensors") + result + + error -> + Phoenix.PubSub.unsubscribe(Localiser.PubSub, "sensors") + error + end end def send_ota_update(%Sensor{} = sensor, url, version) do @@ -169,12 +189,22 @@ defmodule Localiser.Domain.Sensors do # Called when an ESP board self-announces on MQTT. Inserts a new unplaced sensor # record, or marks it confirmed if the sensor_id already exists. - def upsert_announced(sensor_id) do + def upsert_announced(sensor_id, firmware_version \\ nil) do + attrs = %{sensor_id: sensor_id, confirmed: true} + attrs = if firmware_version, do: Map.put(attrs, :firmware_version, firmware_version), else: attrs + + on_conflict = + if firmware_version do + [set: [confirmed: true, firmware_version: firmware_version, updated_at: DateTime.utc_now()]] + else + [set: [confirmed: true, updated_at: DateTime.utc_now()]] + end + result = %Sensor{} - |> Sensor.changeset(%{sensor_id: sensor_id, confirmed: true}) + |> Sensor.changeset(attrs) |> Repo.insert( - on_conflict: [set: [confirmed: true, updated_at: DateTime.utc_now()]], + on_conflict: on_conflict, conflict_target: :sensor_id, returning: true ) diff --git a/lib/localiser/mqtt/router.ex b/lib/localiser/mqtt/router.ex index 5d1fd0d..0112e59 100644 --- a/lib/localiser/mqtt/router.ex +++ b/lib/localiser/mqtt/router.ex @@ -30,7 +30,7 @@ defmodule Localiser.MQTT.Router do handle_rssi(sensor_id, payload) {:announce, sensor_id} -> - handle_announce(sensor_id) + handle_announce(sensor_id, payload) {:error, :invalid_topic} -> Logger.debug("[MQTT.Router] Received message with invalid topic: #{topic}") @@ -70,8 +70,14 @@ defmodule Localiser.MQTT.Router do end end - defp handle_announce(sensor_id) do - case Sensors.upsert_announced(sensor_id) do + defp handle_announce(sensor_id, payload) do + version = + case Jason.decode(payload) do + {:ok, %{"version" => v}} when is_binary(v) -> v + _ -> nil + end + + case Sensors.upsert_announced(sensor_id, version) do {:ok, _sensor} -> Logger.info("[MQTT.Router] Sensor announced: #{sensor_id}") diff --git a/lib/localiser/web/controllers/sensor_controller.ex b/lib/localiser/web/controllers/sensor_controller.ex index cb3a24d..be1e652 100644 --- a/lib/localiser/web/controllers/sensor_controller.ex +++ b/lib/localiser/web/controllers/sensor_controller.ex @@ -241,8 +241,8 @@ defmodule Localiser.Web.Controllers.SensorController do def get_version(conn, %{"id" => id}) do sensor = Sensors.get_sensor!(id) - {:ok, _} = Sensors.get_version(sensor) - json(conn, %{status: "ok"}) + {:ok, version} = Sensors.get_version(sensor) + json(conn, %{status: "ok", version: version}) end def reconfigure(conn, %{"id" => id} = params) do diff --git a/priv/repo/migrations/20260520192900_add_firmware_version_to_sensors.exs b/priv/repo/migrations/20260520192900_add_firmware_version_to_sensors.exs new file mode 100644 index 0000000..8f771dc --- /dev/null +++ b/priv/repo/migrations/20260520192900_add_firmware_version_to_sensors.exs @@ -0,0 +1,9 @@ +defmodule Localiser.Repo.Migrations.AddFirmwareVersionToSensors do + use Ecto.Migration + + def change do + alter table(:sensors) do + add :firmware_version, :string + end + end +end