Skip to content

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 value

Changing 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.