Sensor telemetry, alarms, battery status and GNSS data all travel in one compact record format, carried over any transport.
TLV stands for Type–Length–Value. Each record is a one-byte type, a one-byte length, then exactly that many bytes of payload:
┌──────┬─────┬───────────────────────┐
│ type │ len │ value — len bytes │
└──────┴─────┴───────────────────────┘
0x09 4 mv, percent, flags ← a battery record
The length is what makes the format extensible. A decoder that does not recognise a type does not need to understand it — it advances by len bytes and carries on parsing, so a newer device is always safe against an older reader. Adding a record type never breaks a deployed backend.
The format is also transport-agnostic by design — the identical bytes ride UWB sensor-data frames (0x25) or BLE extended advertising, so a decoder does not care how a record arrived.
This page describes the envelope. What the individual sensors measure and how to interpret the numbers is on IMU, Barometer and Gas Sensors. GNSS also rides this envelope as record 0x07, but is documented under Device Protocols because it produces a position rather than context.
| Type | Payload | Bytes |
|---|---|---|
0x01 | IMU raw — N × {accel[3], gyro[3]} int16 | 12 / sample |
0x02 | IMU aggregate | 24 |
0x03 | Baro raw | 6 / sample |
0x04 | Baro aggregate | 18 |
0x05 | Temperature {source, reserved, temp_dc} | 4 |
0x06 | Origin EUI | 8 |
0x07 | GNSS data | 20 |
0x08 | Alarm | 1 |
0x09 | Battery | 4 |
A record's length is its compatibility contract. Decoders verify len == sizeof(record) and silently skip a mismatch. Existing records are never resized — a new capability gets a new type.
percentpercent = 255 is not a single condition. It has two causes, and bit 3 tells them apart:
| bit 3 | percent | Meaning |
|---|---|---|
| set | 255 | No cell fitted — running on external supply. mv is 0. This is normal, not a fault. |
| clear | 255 | Voltage outside a plausible cell range — a scaling or sense fault, not a flat cell. |
| clear | 0–100 | Normal reading. |
A consumer that treats every 255 as a fault will raise an alarm on every mains- or PoE-powered device on the site. Check bit 3 first.
An absent battery record is a fourth state again: firmware too old to report one. That is why a batteryless device still emits the record and sets bit 3 rather than staying silent — "this unit has no battery" and "this unit cannot tell me about its battery" call for opposite responses, and a missing record cannot express the difference.
mv and percentThe percentage is derived from a discharge curve that is chemistry-specific and load-dependent. Shipping the raw terminal voltage alongside it means a consumer can apply its own curve, and a curve that turns out to be wrong can be re-derived from field data rather than mis-reporting invisibly. Where the distinction matters, prefer mv.
| Bit | Set when |
|---|---|
| 0 | the charger is actively charging |
| 1 | an external supply is present |
| 2 | charge sensing is not wired on this device — bits 0 and 1 carry no information |
| 3 | no cell fitted; external supply only |
Bit 3 is configured rather than detected. With a linear charger an empty battery terminal sits at the regulation voltage — the same reading as a fully charged cell on charge — so no measurement separates the two. The device is told.
A value of 255 therefore distinguishes a fault in the measurement path from a genuinely discharged cell.