Appearance
Legacy Sensor MQTT Specification
This document specifies the MQTT message format for legacy (non-BLE) MQTT-enabled sensors. Devices that implement this spec are discovered by the server and registered as sensor assets, feeding the same storage and alerting pipeline as BLE sensor assets.
The format is intentionally minimal: it carries only fields the system consumes. Parsing is handled by MqttSensorDataSchema in lib/src/schemas.ts.
Transport
| Item | Value |
|---|---|
| Protocol | MQTT 3.1.1 or later |
| Topic | safecall/sensor/{DMAC} (e.g. safecall/sensor/A1B2C3D4E5F6) |
| QoS | 0 |
| Retain | false |
| Credentials | Per-deployment broker username/password (same broker as server_mqtt_url) |
The server subscribes to # and routes on the payload msg field, not the topic. The topic above is still normative for devices — it keeps the broker organized and allows per-device ACLs (restrict each device to publishing on its own topic).
Payload
A single flat JSON object, UTF-8 encoded, maximum 256 bytes. No envelope, no batching — one reading per message.
| Field | Type | Required | Description |
|---|---|---|---|
msg | string | yes | Message discriminator. Must be exactly "sData". |
v | integer | yes | Spec version. Must be 1. |
dmac | string | yes | Device MAC address: 12 uppercase hex characters, no separators (^[0-9A-F]{12}$). |
temp | number | yes | Box or primary temperature in °C. At most 2 decimal places recommended. |
hum | number or "nan" | no | Relative humidity in % (0–100). Send "nan" or omit the field when no humidity value is available. |
temp2 | number | no | Evaporator or secondary temperature in °C. |
defrost | number | no | Defrost status, normally 0 or 1. The server stores the raw numeric value. |
doorswitch | number | no | Door switch status, normally 0 or 1. The server stores the raw numeric value. |
fridge_id | number | no | Vendor fridge identifier (typically 1–8). Used as import metadata and, when in 1..8, to derive a virtual asset MAC (see below). |
firdge_id | number | no | Vendor typo accepted for compatibility; normalized internally to fridge_id. |
Messages with missing required fields, a malformed dmac, or an unknown msg/v value are silently dropped. Unknown extra fields are ignored by the server; devices should not send them.
Shared ethernet MAC / virtual asset identity
Some fridge controllers publish multiple sData messages with the same physical dmac (one ethernet NIC) and different fridge_id values. The server keeps beacons.mac UNIQUE by rewriting identity at ingest:
- When
fridge_idis an integer in 1..8, the asset MAC becomesFE+ first 9 hex digits of physicaldmac+fridge_idas one hex digit.
Example: physical70B8F66C3C33, fridge6→FE70B8F66C36. - Devices keep publishing the physical
dmacon the wire; only SafeCall remaps server-side (discovery, import,sensor_data, alerts). - The original ethernet address is stored on the asset as
beacons.physical_mac(shown read-only on the Assets edit form). - Import Assets sets
typetosensorwhen discovery hasfridge_id. - Debug Device matches subscriptions against either the physical wire
dmacor the derived virtual MAC. fridge_idoutside1..8(or absent) leavesdmacunchanged.- BLE
adv1/adv4/adv8paths are never rewritten.
If a shared physical MAC was already imported before this mapping, delete or re-import so assets use the virtual MACs. Existing virtual-MAC assets missing physical_mac are backfilled on the next sData reading.
Example
json
{"msg":"sData","v":1,"dmac":"A1B2C3D4E5F6","temp":22.5,"hum":45.2}About 65 bytes on the wire.
Fridge example
json
{"temp":18.8,"temp2":18.8,"defrost":0,"doorswitch":0,"msg":"sData","v":1,"dmac":"70B8F66C3C33","firdge_id":6,"hum":"nan"}This message is discovered and stored under virtual MAC FE70B8F66C36 (import default name still Fridge 6).
Publish Cadence
Publish one message every 60 seconds (recommended default). The server throttles persistence per beacon via its update_interval setting, so publishing more frequently only improves alert latency, not stored resolution.
Server Processing
Device ──sData──▶ MQTT Broker ──▶ lib/src/mqtt.ts (parse + validate)
│
▼
server/src/mqtt.ts (dispatch "sdata")
├── devices_mqtt_process()
│ → virtual MAC if fridge_id 1–8
│ → discovery cache → device_beacons
└── events_process_mqtt_message()
→ virtual MAC if fridge_id 1–8
→ sensor_data storage (temperature, optional humidity/fridge metrics)
→ range alerts (ntfy "sensors" topic)- Discovery — every valid message updates the in-memory device cache immediately;
device_beaconsis updated periodically from that cache. Admin Import Beacons reads both sources, so a new device can be imported as soon as MQTT is received. - Storage — temperature and any available humidity/fridge metrics are written to the sensor database, at most once per
update_intervalseconds. - Alerts — readings are checked against the beacon's
temperature_range/humidity_range(after applying the optional coefficients) on every message; out-of-range values trigger a notification. - No positioning — these devices publish directly (no gateway, no RSSI), so they never participate in RTLS tracking and have no gateway sightings.
Registration
Sensor data is only stored and alerted on for active, registered assets:
- Power the device and let it publish at least one valid message.
- In the admin UI, open Assets and use Import Assets — the device is listed by its MAC from discovery (live MQTT cache plus
device_beacons). - Edit the imported asset: set type to
sensor, mark it active, and configureupdate_interval,temperature_range/humidity_range, and coefficients as needed.
Until registered, messages still update discovery (device_beacons) but nothing is stored.
Versioning
- Any backwards-incompatible change to this format bumps
v; the server validatesvstrictly per version. - Field meanings and units must never change within a version.
- New optional fields can be added within version
1when old devices remain valid and field meanings do not change.
See Also
- MQTT Integration — full server-side MQTT pipeline
- Schemas — Valibot schema catalog