A six-axis accelerometer and gyroscope, reported either as raw samples or as metrics computed on the device — activity level, tilt, shock magnitude, dominant vibration frequency and a motion classification.
Both forms travel as Sensor TLV records: 0x01 raw, 0x02 aggregate. A device can emit either, and the choice is not simply a bandwidth trade.
Aggregation is not about smaller frames. It decouples the sample rate from the transmit rate, and that is the only way some of the metrics can exist at all:
0x01accel[3] int16 6 B
gyro[3] int16 6 B
12 B per sample
Units are the sensor's native LSBs, not physical units. The full-scale setting needed to convert them is reported once in the aggregate record rather than repeated on every sample — carrying it per sample would cost two bytes each time to say something that rarely changes.
A consumer receiving only raw records has no full-scale code and cannot convert to g or dps. Raw mode is intended alongside aggregates, not instead of them.
0x02| Field | Type | Unit | Meaning |
|---|---|---|---|
window_ms | uint16 | ms | aggregation window |
sample_count | uint16 | — | samples actually included |
gravity[3] | int16 ×3 | LSB | mean acceleration — gives tilt and attitude |
accel_rms | uint16 | milli-g | RMS of |a| − g — activity level |
accel_peak | uint16 | milli-g | max of |a| − g — shock magnitude |
gyro_rms | uint16 | milli-dps | rotational activity |
dom_freq_mhz | uint16 | milli-Hz | dominant vibration frequency |
dom_amp | uint16 | — | amplitude at that frequency |
accel_fs | uint8 | code | accelerometer full scale — converts raw LSBs |
gyro_fs | uint8 | code | gyroscope full scale |
motion_state | uint8 | enum | classification, see below |
event_flags | uint8 | bits | events since the last report |
Total 24 bytes, fixed regardless of how many samples went into it.
accel_rms and accel_peak answer different questions and both are needed: a machine can have high sustained vibration with no shocks, or sit quiet and take one impact. RMS describes the former, peak the latter, and an average of the two describes neither.
dom_freq_mhz is in milli-Hertz — 25 000 means 25 Hz. The resolution matters for rotating machinery, where a shift of a fraction of a hertz is the signal.
Classified on the device, so a consumer does not have to infer it from the numbers:
| Value | State |
|---|---|
0 | Static |
1 | Moving |
2 | Impact |
3 | Freefall |
One byte of sticky bits, cleared once reported — so an event occurring between reports cannot be missed by sampling at the wrong moment:
| Bit | Event |
|---|---|
| 0 | Impact |
| 1 | Freefall |
| 2 | Tilt |
| 3 | No motion |
Any event bit forces immediate transmission. The aggregation window is not waited out — a fall cannot sit in a buffer for ten seconds. A record arriving with event bits set will therefore have a shorterwindow_msand lowersample_countthan a scheduled one; that is the mechanism working, not a truncated frame.
Because the bits are sticky and cleared on report, event_flags describes the interval since the previous record rather than the instant of transmission. motion_state is the opposite — it is the state at the close of the window. The two can legitimately disagree: an impact bit set with motion_state = static means something struck a device that has since come to rest.
The record is stamped when the aggregation window closes, on the same clock as the ranging measurement — see Sensor TLV. The frame header carries beacon_seq and offset_ms; a beacon_seq of 0xFFFFFFFF marks an unsynced sample that must not be fused with a position.
vert_vel for fall detection.Imu object of a /Sensors message.