IMU UDP Protocol and Timing Model¶
This page is the English public documentation for the root PROTOCOL.md. It
defines the current binary UDP attitude packets and the timing fields used by
the host-side motion pipeline. All multi-byte packet fields use little-endian
encoding.
UDP Channels¶
Channel |
Port |
Direction |
Purpose |
|---|---|---|---|
Data |
4211 |
device to host |
Binary attitude packets and text status |
Discovery |
4212 |
device broadcast |
|
Discovery beacons use:
VQF_DISC,node=<MAC suffix>,id=<optional device ID>,ip=<IP>,tcp=4210,udp=4211,ver=<firmware version>
The device also announces _vqf-imu._tcp by mDNS, with host name
vqf-imu-<node_id>.local.
Rotation v1¶
type = 0x01, exactly 12 bytes:
Offset |
Type |
Field |
|---|---|---|
0 |
|
|
1 |
|
|
2 |
|
|
4 |
|
quaternion |
Python layout: "<BBH4h". Decode each Q15 value as value / 32767.0.
seq is only meaningful within one device. It cannot order samples from
different IMUs.
Rotation v2¶
type = 0x03, exactly 20 bytes:
Offset |
Type |
Field |
|---|---|---|
0 |
|
|
1 |
|
|
2 |
|
|
4 |
|
|
12 |
|
quaternion |
Python layout: "<BBHQ4h".
sensor_time_us is the device monotonic microsecond clock since boot. The
firmware must capture it at IMU sampling or VQF attitude update time, never at
UDP send time. It is not Unix time and is not comparable across devices before
clock synchronization.
The current firmware emits v2 0x03. The host remains compatible with legacy
v1 0x01 firmware, but v1 nodes cannot participate in common-timeline solving.
Text Status Packet¶
type = 0x02, at least 4 bytes:
Offset |
Type |
Field |
|---|---|---|
0 |
|
|
1 |
|
Reserved |
2 |
|
Packet sequence number |
4 |
UTF-8 bytes |
Null-terminated text payload |
The device uses this packet for low-rate messages such as $CAL,..., $PWR,...
and $DIAG,....
Host Timing Fields¶
Field |
Meaning |
|---|---|
|
Host monotonic time recorded immediately after |
|
Host-monotonic sample time after a valid clock mapping, otherwise |
|
Future monotonically increasing common mocap frame identifier. |
|
Future common target time used to solve one body frame. |
|
Future host monotonic time when body solving completes. |
|
True after at least four valid samples spanning at least three seconds. |
|
RMS residual of the accepted linear clock fit, otherwise |
|
Age of the oldest valid sample in a solved common frame, otherwise |
Wall-clock Unix time is for logs and UI only. Real-time ordering, liveness, and packet-rate calculations use the host monotonic clock.
TCP Commands¶
The host opens a TCP connection to port 4210 and sends newline-terminated text commands:
Command |
Purpose |
|---|---|
|
Start magnetometer calibration collection |
|
Stop calibration collection |
|
Erase stored calibration parameters |
|
Request power off |
|
Store a custom device ID |
|
Query the custom device ID |
|
Clock-sync request; TCP replies directly with |
TCP Clock Synchronization¶
The host synchronizes only nodes that emit v2 attitude packets. Every 10 seconds, it runs four immediate exchanges per node:
The host records
t0_host_nsbefore sending$CMD,SYNC,<nonce>.The device TCP receive task records
t1_device_usafter receiving the full line andt2_device_usimmediately before replying.The host records
t3_host_nsafter receiving$SYNC,<nonce>,<t1_us>,<t2_us>.The host fits
host_monotonic_ns ~= scale * sensor_time_us + offset.
Samples with RTT above 50 ms, or materially worse than the current best RTT,
are discarded. offset handles different boot times; scale compensates for
oscillator error and long-term drift. Before the mapping is valid, the system
shows each node’s latest pose but does not treat nodes as sampled at one instant.
Common Mocap Timeline¶
Body solving never simply combines the most recently received attitude from each node. For synchronized v2 nodes, the host keeps a short pose history and selects:
t_target = min(latest_sample_time[node]) - safety_margin
The default safety_margin is 20 ms. Every required role (chest, upper arm,
forearm, and palm) must provide samples before and after t_target with a gap
no greater than 80 ms. The host uses shortest-path SLERP to obtain each
attitude at that common instant. Missing, unsynchronized, insufficient, or
stale data invalidates the common frame; old and new poses are never silently
mixed.
In MocapState, frame_time_ns is t_target; node_samples retains the
bracketing sequence numbers, times, interpolation fraction, and sample age.
time_sync_error_us is the largest mapping error among nodes used in the
frame, while max_sample_age_ms is the oldest leading-sample age.
OTA¶
The OTA HTTP service listens on port 8080:
Request |
Purpose |
|---|---|
|
Upload a full firmware binary with |
|
Query OTA/device information |
Use a raw binary request body. Multipart form uploads are not supported.