Protocol
everything-imu speaks two UDP protocols: the SlimeVR tracker protocol (bridge → server) and the eimu remote-hub protocol (phone → desktop).
SlimeVR wire format
Section titled “SlimeVR wire format”UDP, big-endian. Every datagram begins with a 12-byte header:
[u32 BE tag][u64 BE seq]. The tag is the packet ID; the sequence number is
per-tracker and monotonically increasing, and the server uses it to drop
out-of-order packets.
Packet IDs (tracker → server)
Section titled “Packet IDs (tracker → server)”| ID | Name | Notes |
|---|---|---|
| 0 | Heartbeat | Trailing 1-byte tracker id (always 0) |
| 1 | Rotation | Deprecated - use ID 17 on modern servers |
| 3 | Handshake | Initial connect (protocol version 19) |
| 4 | Acceleration | Linear acceleration in m/s² (not g) |
| 5 | Magnetometer | Mag readings (µT) when 9-DOF fusion is active |
| 10 | Ping/Pong | Server challenge, tracker echoes |
| 12 | Battery level | [f32 voltage][f32 level 0..1] |
| 15 | Sensor info | Tracker description (one per logical tracker) |
| 17 | Rotation data | Modern rotation - quaternion in (X, Y, Z, W) order |
| 21 | Calibration action | User reset events (yaw / full / mounting) |
| 22 | Feature flags | Bidirectional (see below) |
| 100 | BUNDLE | Batched datagram, gated by the server’s feature flags |
Key rules
Section titled “Key rules”- Acceleration is m/s², not g. Multiply g by
9.80665. Note the tracker id is the last byte of an accel packet (vs the first in a rotation packet). - Rotation quaternion byte order is
(X, Y, Z, W), i.e.(i, j, k, w). - Sensor status: send
1(OK) in packet 15 to flip the dashboard tracker green. Thesensor_configbitmask reports magnetometer state -0x0003enabled,0x0002supported-but-disabled,0x0000unsupported.
BUNDLE auto-fallback
Section titled “BUNDLE auto-fallback”BUNDLE (packet 100) is only usable if the server’s feature-flags reply advertises
PROTOCOL_BUNDLE_SUPPORT. On servers without it, type-100 datagrams are silently
dropped, so the bridge falls back to sending rotation (17) then acceleration
(4) as separate datagrams - rotation first, because accel-first produced a
visible one-frame jitter. The handful of samples that arrive before the
feature-flags reply take the fallback path automatically, so no data is lost.
Wire-compat invariant
Section titled “Wire-compat invariant”Any change to the encoder must pass byte-for-byte reference tests against known-good captures. SlimeVR-Server silently rejects malformed packets, and silent failures are extremely costly to debug.
eimu remote-hub protocol
Section titled “eimu remote-hub protocol”A second, separate UDP protocol. The mobile app acts as a hub: it forwards its own IMU and any BLE controllers it owns to the desktop bridge, which runs fusion and registers each forwarded device as a normal tracker.
Envelope
Section titled “Envelope”[4 bytes] magic "EIMU" (45 49 4D 55)[1 byte] version = 1[1 byte] message type[ ... ] payload, LITTLE-endian (opposite of the SlimeVR protocol)The hub binds 0.0.0.0:9320 by default. Datagrams with a wrong magic or version
are dropped.
Message types
Section titled “Message types”| ID | Name | Direction | Purpose |
|---|---|---|---|
0x01 |
HELLO | phone → desktop | Announce the hub (uuid + name, opt. clock) |
0x02 |
HELLO_ACK | desktop → phone | Ack, echo the clock for RTT |
0x03 |
ANNOUNCE | phone → desktop | Register a forwarded device (a handle) |
0x04 |
REMOVE | phone → desktop | Drop a forwarded device |
0x05 |
IMU | phone → desktop | Sample burst for a handle |
0x06 |
BATTERY | phone → desktop | Battery fraction + charging |
0x07 |
BUTTON | phone → desktop | Reset request (yaw / full) |
0x08 |
RUMBLE | desktop → phone | Rumble intensity for a handle |
0x09 |
IMU2 | phone → desktop | Like IMU, plus a wrapping seq for loss |
handle is a u16 chosen by the phone; each (hub, handle) pair maps to one
tracker on the desktop. An IMU sample burst carries, per sample: an 8-byte µs
timestamp, gyro (3× f32, rad/s), accel (3× f32, m/s²), a has_mag byte, and mag
(3× f32, µT) that is always on the wire but ignored when has_mag is 0.