Payload rules
A rule is a declarative bit layout that turns raw frames into named metrics and hardware events. One rule is active per fleet; the interpreter and the generated C header share the exact same semantics.
The spec
{
"fields": [
{"name": "heap_free", "offset_bits": 0, "length_bits": 16},
{"name": "batt_mv", "offset_bits": 16, "length_bits": 16},
{"name": "temp_c", "offset_bits": 32, "length_bits": 8,
"type": "int", "scale": 0.5},
{"name": "cpu_pct", "offset_bits": 40, "length_bits": 7}
],
"events": [
{"name": "watchdog_reset", "offset_bits": 47, "length_bits": 1}
]
}Bit semantics
Bit 0 is the most-significant bit of byte 0; fields read MSB-first. Little-endian is available for byte-aligned fields. type: int applies two's-complement; scale multiplies the raw integer into the stored metric value. Sub-byte fields are first-class — pack seven metrics into six bytes and spend your radio budget on batteries.
Device-side packing
Download the generated C header for any rule — frame-size constant, per-field offsets, and static inline setters that mirror the decoder bit-for-bit:
GET /v1/rules/{rule_id}/header → frame-v1.h
uint8_t buf[VS_FRAME_BYTES] = {0};
vs_set_heap_free(buf, xPortGetFreeHeapSize());
vs_set_temp_c(buf, raw_temp); // raw pre-scale valueChanging a rule
Activating a new rule triggers nothing destructive: stored raw frames can be replayed through it, rewriting history with the corrected decoding.
The decoder is the product's core differentiator — if a layout can't be expressed, tell us and we'll extend the spec rather than have you pre-process on-device.