feat: implement multidistance rssi->distance model parameter estimation

This commit is contained in:
2026-05-21 18:31:07 +02:00
parent 7b02a37abe
commit bacf56156b
9 changed files with 438 additions and 78 deletions
@@ -108,21 +108,39 @@ defmodule Localiser.Web.Controllers.SensorController do
unauthorized: {"Unauthorized", "application/json", Schemas.Error}
]
operation :calibration_start,
summary: "Begin RSSI calibration",
operation :calibration_begin,
summary: "Enter calibration mode (between-stages)",
parameters: [id: [in: :path, type: :integer, required: true]],
request_body: {"Calibration params", "application/json", Schemas.CalibrationStartParams, required: true},
responses: [
ok: {"Calibration started", "application/json", Schemas.CalibrationStatus},
bad_request: {"Missing reference_distance", "application/json", Schemas.Error},
ok: {"Calibration mode entered", "application/json", Schemas.CalibrationBeginResponse},
unauthorized: {"Unauthorized", "application/json", Schemas.Error}
]
operation :calibration_stop,
summary: "Abort active calibration",
operation :calibration_stage_start,
summary: "Start collecting samples for a specific distance",
parameters: [id: [in: :path, type: :integer, required: true]],
request_body: {"Stage params", "application/json", Schemas.CalibrationStageParams, required: true},
responses: [
ok: {"Stage started", "application/json", Schemas.CalibrationStageResponse},
bad_request: {"Missing or invalid distance", "application/json", Schemas.Error},
unprocessable_entity: {"Stage already active", "application/json", Schemas.Error},
unauthorized: {"Unauthorized", "application/json", Schemas.Error}
]
operation :calibration_finish,
summary: "Run regression over completed stages and save calibration",
parameters: [id: [in: :path, type: :integer, required: true]],
responses: [
ok: {"Calibration aborted", "application/json", Schemas.CalibrationStatus},
ok: {"Calibration saved", "application/json", Schemas.CalibrationFinishResponse},
unprocessable_entity: {"Insufficient stages or stage active", "application/json", Schemas.Error},
unauthorized: {"Unauthorized", "application/json", Schemas.Error}
]
operation :calibration_cancel,
summary: "Abort calibration, discard all stages",
parameters: [id: [in: :path, type: :integer, required: true]],
responses: [
ok: {"Calibration cancelled", "application/json", Schemas.CalibrationStatus},
unauthorized: {"Unauthorized", "application/json", Schemas.Error}
]
@@ -252,24 +270,77 @@ defmodule Localiser.Web.Controllers.SensorController do
json(conn, %{status: "ok"})
end
def calibration_start(conn, %{"id" => id, "reference_distance" => ref_dist}) do
def calibration_begin(conn, %{"id" => id}) do
sensor = Sensors.get_sensor!(id)
:ok = SensorServer.begin_calibration(sensor.sensor_id, ref_dist)
json(conn, %{status: "calibrating"})
:ok = SensorServer.begin_calibration_mode(sensor.sensor_id)
json(conn, %{status: "calibration_mode", samples_needed: calibration_samples_needed()})
end
def calibration_start(conn, _params) do
def calibration_stage_start(conn, %{"id" => id, "distance" => distance})
when is_number(distance) and distance > 0 do
sensor = Sensors.get_sensor!(id)
case SensorServer.start_stage(sensor.sensor_id, distance * 1.0) do
{:ok, samples_needed} ->
json(conn, %{status: "stage_active", distance: distance, samples_needed: samples_needed})
{:error, :already_active} ->
conn
|> put_status(:unprocessable_entity)
|> json(%{error: "a stage is already active"})
{:error, :not_in_calibration_mode} ->
conn
|> put_status(:unprocessable_entity)
|> json(%{error: "sensor is not in calibration mode"})
end
end
def calibration_stage_start(conn, _params) do
conn
|> put_status(:bad_request)
|> json(%{error: "reference_distance is required"})
|> json(%{error: "distance is required and must be a positive number"})
end
def calibration_stop(conn, %{"id" => id}) do
def calibration_finish(conn, %{"id" => id}) do
sensor = Sensors.get_sensor!(id)
case SensorServer.finish_calibration(sensor.sensor_id) do
{:ok, %{rssi_ref: rssi_ref, path_loss_exp: path_loss_exp}} ->
json(conn, %{status: "idle", rssi_ref: rssi_ref, path_loss_exp: path_loss_exp})
{:error, :insufficient_stages} ->
conn
|> put_status(:unprocessable_entity)
|> json(%{error: "at least 2 completed stages required"})
{:error, :stage_active} ->
conn
|> put_status(:unprocessable_entity)
|> json(%{error: "cannot finish while a stage is active"})
{:error, :not_in_calibration_mode} ->
conn
|> put_status(:unprocessable_entity)
|> json(%{error: "sensor is not in calibration mode"})
{:error, reason} ->
conn
|> put_status(:unprocessable_entity)
|> json(%{error: inspect(reason)})
end
end
def calibration_cancel(conn, %{"id" => id}) do
sensor = Sensors.get_sensor!(id)
:ok = SensorServer.abort_calibration(sensor.sensor_id)
json(conn, %{status: "idle"})
end
defp calibration_samples_needed do
Application.get_env(:localiser, :calibration_samples, 30)
end
defp render_sensor(sensor) do
%{
id: sensor.id,