Requests
Query the box for stateQUERY is the only command that gets a reply, a RESP. Pick what to read with the what selector: the firmware version, the box's health, the cloned device info, device capabilities, rate, delivery stats, the active input locks, the catch subscription, a persistent box option, or the buffered input clip.
QUERY
Ask the box for stateQUERY asks for one piece of state, named by its what byte. Opcode 0x05.
QUERY 0x05 · payload 1 byte (2 for OPTIONS)
Returns RESP
| Offset | Field | Type | Notes |
|---|---|---|---|
| 0 | what | u8 | which state to read (see below) |
| 1 | id | u8 | only for what = 9 (OPTIONS): which option to read; omitted otherwise |
what | Reads | Reply |
|---|---|---|
0 | The firmware version. | VERSION |
1 | The box's health. | HEALTH |
2 | The cloned device's USB identity, kind, and product. | DEVICE_INFO |
3 | The whole device's capabilities (mouse + keyboard). | CAPS |
4 | The native report rate. | RATE |
5 | Delivery and telemetry counters. | STATS |
6 | The active input locks. | LOCKS |
7 | The active catch subscription. | CATCH |
8 | reserved | - |
9 | A persistent box option, by id. | OPTIONS |
10 | The buffered-clip ring depth, playback state, and config. | CLIP |
The box replies with a RESP carrying the same what and the requested data, one per selector in the table above. QUERY is the only command that gets a reply; everything else is fire-and-forget. Library bindings: query_version, query_health, device_info, caps, query_rate, query_stats, query_locks, query_catch, query_imperfect, query_movement_riding, query_emit_pace, and the clip status query.
what = 0 (read the version):
+--------+--------+--------+--------+--------+--------+ | A5 | 05 | 00 | 01 00 | 00 | lo hi | +--------+--------+--------+--------+--------+--------+ | SOF | TYPE | SEQ | LEN | what | CRC16 | +--------+--------+--------+--------+--------+--------+
RESP
The box's replyRESP is the box's reply to a QUERY. Opcode 0x06.
RESP 0x06 · box → PC
Reply
| Offset | Field | Type | Notes |
|---|---|---|---|
| 0 | what | u8 | echoes the request's selector |
| 1.. | data | varies | the layout for the requested what (see the selectors) |
You get exactly one RESP per QUERY. Its SEQ matches the request's and what echoes the selector, so you can pair a reply with its request and tell which kind it is. Each selector's payload is laid out below, with an example frame.
VERSION
RESP payload, what = 0The RESP payload when what = 0. proto_ver is the protocol version (this documentation describes 3); the box reports its own firmware version, then its base mac, a stable per-box id, then a length-delimited ASCII name tail (a synthesized default when unset). The name is additive, so proto_ver stays 3: an older box just sends an empty tail.
QUERY what = 0 · RESP 11-byte header + name
Returns RESP
| Offset | Field | Type | Notes |
|---|---|---|---|
| 0 | what | u8 | 0x00 |
| 1 | proto_ver | u8 | protocol version, expected 3 |
| 2 | fw_major | u8 | firmware major |
| 3 | fw_minor | u8 | firmware minor |
| 4 | fw_patch | u8 | firmware patch |
| 5 | mac | u8[6] | the device chip's base MAC; the stable per-box id, rendered as 12 lowercase hex digits |
| 11.. | name | ascii | the box's human-readable name, filling the rest of the payload (the frame LEN delimits it); a synthesized Medius-XXXX default when unset, set via OPTION(NAME) |
The box also sends this unprompted at startup, as a ready signal. The mac stays fixed for a box, so it identifies the same box across replugs and port renumbering; the name is its readable label. Library binding: query_version.
Firmware 3.0.0, protocol 3, MAC 123456789abc, name "Loki":
+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+ | A5 | 06 | 00 | 0F 00 | 00 | 03 | 03 | 00 | 00 | ... | +--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+ | SOF | TYPE | SEQ | LEN | what | proto | major | minor | patch | ... | +--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+ | ... | 12 34 56 78 9A BC | 4C 6F 6B 69 | lo hi | +--------+--------------------+--------------+--------+ | ... | mac (6 bytes) | "Loki" | CRC16 | +--------+--------------------+--------------+--------+
HEALTH
RESP payload, what = 1The RESP payload when what = 1: a single flags byte, each bit an independent status.
QUERY what = 1 · RESP 2 bytes
Returns RESP
| Offset | Field | Type | Notes |
|---|---|---|---|
| 0 | what | u8 | 0x01 |
| 1 | flags | u8 | the status bits below |
| Bit | Mask | Set when |
|---|---|---|
| b0 | 0x01 | the link to the host chip is up |
| b1 | 0x02 | a real mouse is attached |
| b2 | 0x04 | the PC has set up the cloned mouse |
| b3 | 0x08 | injection is active |
| b4 | 0x10 | RATE_CONFIDENT: the native-rate estimator window is full, so the RATE value is trustworthy |
| b5 | 0x20 | LOCK_ON: at least one input LOCK is active |
| b6 | 0x40 | CATCH_ON: a CATCH subscription is active, physical-input events are streaming |
| b7 | 0x80 | KBD_ATT: a keyboard is attached on the host chip, cloned and injectable |
The first three bits set means the box is ready for input to reach the PC. Library binding: query_health.
Ready, with link, mouse, and clone all up (flags = 0x07):
+--------+--------+--------+--------+--------+--------+--------+ | A5 | 06 | 00 | 02 00 | 01 | 07 | lo hi | +--------+--------+--------+--------+--------+--------+--------+ | SOF | TYPE | SEQ | LEN | what | flags | CRC16 | +--------+--------+--------+--------+--------+--------+--------+
DEVICE_INFO
RESP payload, what = 2The RESP payload when what = 2: the USB identity, kind, and product string the box read from the real device. The clone shows up on the game PC, not here, so this is the only way the control PC can read it. The header is fixed at 11 bytes, then a length-delimited UTF-8 product tail (which may be empty). Every field is zero when nothing is attached.
QUERY what = 2 · RESP 11-byte header + product
Returns RESP
| Offset | Field | Type | Notes |
|---|---|---|---|
| 0 | what | u8 | 0x02 |
| 1 | vid | u16 | idVendor, little-endian |
| 3 | pid | u16 | idProduct, little-endian |
| 5 | bcd_device | u16 | bcdDevice, the device release |
| 7 | bcd_usb | u16 | bcdUSB, e.g. 0x0200 or 0x0201 |
| 9 | flags | u8 | the bits below |
| 10 | primary_kind | u8 | the cloned device's kind, from its Boot-interface protocol (see below) |
| 11.. | product | UTF-8 | the product string, filling the rest of the payload; may be empty |
| Bit | Mask | Set when |
|---|---|---|
| b0 | 0x01 | HAS_SERIAL: the clone serves a serial string |
| b1 | 0x02 | HAS_BOS: the clone serves a BOS descriptor |
| Value | Kind |
|---|---|
0 | unknown |
1 | keyboard |
2 | mouse |
A vid of 0 means nothing is attached yet. Library binding: device_info.
A Logitech G502 (046D:C08B), USB 2.01, serial and BOS served, kind mouse, product "G502":
+--------+--------+--------+--------+--------+--------+--------+--------+ | A5 | 06 | 00 | 0F 00 | 02 | 6D 04 | 8B C0 | ... | +--------+--------+--------+--------+--------+--------+--------+--------+ | SOF | TYPE | SEQ | LEN | what | vid | pid | ... | +--------+--------+--------+--------+--------+--------+--------+--------+ | ... | 10 01 | 01 02 | 03 | 02 | 47 35 30 32 | lo hi | +--------+--------+--------+--------+--------+--------------+--------+ | ... | bcdDev | bcdUSB | flags | kind | "G502" | CRC16 | +--------+--------+--------+--------+--------+--------------+--------+
CAPS
RESP payload, what = 3The RESP payload when what = 3: one summary of the whole cloned device, mouse and keyboard, read from its HID report descriptors. Counts and yes/no flags only, never raw HID field offsets. Use it to check before you act: an INJECT for a usage the device lacks is silently ignored, so the counts tell you what is real. A class that is not present reads all-zero.
QUERY what = 3 · RESP 7 bytes
Returns RESP
| Offset | Field | Type | Notes |
|---|---|---|---|
| 0 | what | u8 | 0x03 |
| 1 | n_buttons | u8 | buttons the mouse report carries |
| 2 | axis_flags | u8 | mouse axes, the bits below |
| 3 | n_hid | u8 | cloned HID interfaces; >1 = composite |
| 4 | n_keys | u8 | keycode-array slots, or 0xFF for NKRO; 0 = no keyboard |
| 5 | kbd_flags | u8 | keyboard, the bits below |
| 6 | change_driven | u8 | per class: b0 mouse (continuous, 0), b1 keyboard/media (change-driven, 1 when bound) |
| Bit | Mask | Set when |
|---|---|---|
| b0 | 0x01 | X: the report carries an X axis |
| b1 | 0x02 | Y: the report carries a Y axis |
| b2 | 0x04 | WHEEL: the report carries a wheel |
| b3 | 0x08 | REPORT_ID: the mouse report sits behind a HID report ID |
| Bit | Mask | Set when |
|---|---|---|
| b0 | 0x01 | NKRO: the keyboard reports an NKRO bitmap |
| b1 | 0x02 | CONSUMER: a Consumer collection is present, so media keys are injectable |
| b2 | 0x04 | SYSTEM: a system-control collection is present (passthrough-only) |
| b3 | 0x08 | REPORT_ID: the keyboard report sits behind a HID report ID |
Library binding: caps.
A 5-button mouse (X/Y/wheel, one interface) plus a 6-key Consumer keyboard (axis_flags = 0x07, kbd_flags = 0x02, keyboard change-driven):
+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+ | A5 | 06 | 00 | 07 00 | 03 | 05 | 07 | 01 | 06 | 02 | 02 | lo hi | +--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+ | SOF | TYPE | SEQ | LEN | what | n_btn | axis | n_hid | n_keys | kbdfl | chgdrv | CRC16 | +--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+
RATE
RESP payload, what = 4The RESP payload when what = 4: how fast the active input reports, plus the poll period the clone advertises. The answer is class-aware, so read the field that fits the input kind:
| Input kind | CHANGE_DRIVEN | Read | Gives |
|---|---|---|---|
| continuous (moving mouse) | 0 | native_period_us | Hz = 1e6 / period; reads 0 until learned |
| change-driven (keyboard, media) | 1 | poll_period_us | no steady cadence, so native_period_us is 0 |
QUERY what = 4 · RESP 6 bytes
Returns RESP
| Offset | Field | Type | Notes |
|---|---|---|---|
| 0 | what | u8 | 0x04 |
| 1 | native_period_us | u16 | realised native period in µs; 0 = not learned, or change-driven (see flags), Hz = 1e6/period |
| 3 | poll_period_us | u16 | cloned inject-endpoint bInterval poll period in µs; the honest figure for a change-driven input |
| 5 | flags | u8 | the bits below |
| Bit | Mask | Set when |
|---|---|---|
| b0 | 0x01 | CONFIDENT: the estimator window is full, same source as HEALTH RATE_CONFIDENT |
| b1 | 0x02 | CHANGE_DRIVEN: the active input is event-driven (keyboard / media), so there is no continuous cadence and native_period_us is 0 |
A 1 kHz mouse reads ~1000 µs once learned. Library binding: query_rate.
A 1 kHz mouse, 1000 µs poll, estimator confident (flags = 0x01):
+--------+--------+--------+--------+--------+--------+--------+--------+--------+ | A5 | 06 | 00 | 06 00 | 04 | E8 03 | E8 03 | 01 | lo hi | +--------+--------+--------+--------+--------+--------+--------+--------+--------+ | SOF | TYPE | SEQ | LEN | what | native | poll | flags | CRC16 | +--------+--------+--------+--------+--------+--------+--------+--------+--------+
STATS
RESP payload, what = 5The RESP payload when what = 5: counters the box keeps about whether your commands were delivered. Commands are fire-and-forget with no acknowledgement, so these counters are the only way to tell that everything you sent landed. A nonzero tx_drops or tx_wedges means delivery slipped under load. Wide counters clamp at their max instead of wrapping around.
QUERY what = 5 · RESP 17 bytes
Returns RESP
| Offset | Field | Type | Notes |
|---|---|---|---|
| 0 | what | u8 | 0x05 |
| 1 | inject_emits | u32 | pure-injection reports emitted, little-endian |
| 5 | tx_drops | u16 | reports dropped on TX-queue overflow; should stay 0 |
| 7 | tx_merges | u16 | backed-up reports merged instead of queued |
| 9 | tx_maxdepth | u8 | deepest the TX queue has reached |
| 10 | tx_wedges | u8 | wedged-endpoint recoveries |
| 11 | wakeups | u16 | remote-wakeups issued |
| 13 | reset_count | u16 | USB bus resets seen |
| 15 | config_count | u16 | SET_CONFIGURATION events (re-enumerations) |
inject_emits counts the no-halving / 1 kHz path. The rest are merge, queue, wakeup, reset, and reconfig counters. Library binding: query_stats.
4096 emits, no drops, no wedges:
+--------+--------+--------+--------+--------+--------------+ | A5 | 06 | 00 | 11 00 | 05 | 00 10 00 00 | +--------+--------+--------+--------+--------+--------------+ | SOF | TYPE | SEQ | LEN | what | inject_emits | +--------+--------+--------+--------+--------+--------------+ | 00 00 | 00 00 | 00 | 00 | 00 00 | 00 00 | 00 00 | lo hi | +--------+--------+--------+--------+--------+--------+--------+--------+ | drops | merges | maxdep | wedges | wakeup | resets | config | CRC16 | +--------+--------+--------+--------+--------+--------+--------+--------+
LOCKS
RESP payload, what = 6The RESP payload when what = 6: which physical inputs are currently locked by LOCK, as a variable list of entries, one per locked field across every class, so keyboard and media locks read the same as mouse ones. An empty list (n = 0) means nothing is locked.
QUERY what = 6 · RESP 2 + 4n bytes
Returns RESP
| Offset | Field | Type | Notes |
|---|---|---|---|
| 0 | what | u8 | 0x06 |
| 1 | n | u8 | number of lock entries that follow |
| + | class | u8 | per entry: 0=button 1=key 2=media 3=axis (as LOCK) |
| + | id | u16 | the locked field's id, or 0xFFFF for a whole-class blanket, little-endian |
| + | dirbits | u8 | which edges are locked, the bits below |
| Bit | Mask | Set when |
|---|---|---|
| b0 | 0x01 | the positive / press edge is locked |
| b1 | 0x02 | the negative / release edge is locked |
Read it to confirm a lock landed, or to mirror the box's lock state in a UI. Library binding: query_locks.
One entry: the wheel's negative (scroll-down) sign locked (class = 3 axis, id = 2 wheel, dirbits = 0x02):
+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+ | A5 | 06 | 00 | 06 00 | 06 | 01 | 03 | 02 00 | 02 | lo hi | +--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+ | SOF | TYPE | SEQ | LEN | what | n | class | id | dirbits| CRC16 | +--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+
CATCH
RESP payload, what = 7The RESP payload when what = 7: the active CATCH subscription mask, plus the box-side count of event frames dropped under back-pressure. A zero mask means nothing is subscribed. Mirrors the CATCH_ON health bit.
QUERY what = 7 · RESP 6 bytes
Returns RESP
| Offset | Field | Type | Notes |
|---|---|---|---|
| 0 | what | u8 | 0x07 |
| 1 | mask | u8 | subscribed event classes; bits Motion 0x01, Wheel 0x02, Buttons 0x04, Keys 0x08, Media 0x10 |
| 2 | dropped | u32 | events dropped box-side under back-pressure, little-endian |
Read it to confirm a subscription landed, or to check whether you're losing events. Library binding: query_catch.
Motion and buttons subscribed, no drops (mask = 0x05):
+--------+--------+--------+--------+--------+--------+--------------+--------+ | A5 | 06 | 00 | 06 00 | 07 | 05 | 00 00 00 00 | lo hi | +--------+--------+--------+--------+--------+--------+--------------+--------+ | SOF | TYPE | SEQ | LEN | what | mask | dropped | CRC16 | +--------+--------+--------+--------+--------+--------+--------------+--------+
OPTIONS
RESP payload, what = 9The RESP payload when what = 9: the value of one persistent box OPTION, echoing the queried id. The value is id-specific, so the query takes the option's id as a second byte and each option is read on its own. An unknown id gets no reply.
QUERY what = 9, id · RESP varies
Returns RESP
| Offset | Field | Type | Notes |
|---|---|---|---|
| 0 | what | u8 | 0x09 |
| 1 | id | u8 | which option this value is for |
| 2.. | value | varies | id-specific, mirroring the matching OPTION value |
The IMPERFECT opt-in (id 0) plus two derived clone-status bytes. Each is 0 or 1; a faithful clone reads all-zero.
| Offset | Field | Notes |
|---|---|---|
| 2 | allowed | the opt-in toggle; 1 = cloning an over-capacity device is allowed |
| 3 | over_capacity | the attached device needs an interrupt-IN endpoint the box can't service |
| 4 | clone_imperfect | the live clone is over-capacity and was cloned anyway, so one interface is dead |
Read it to tell why a clone is missing (over_capacity = 1, allowed = 0), or to confirm an imperfect clone is live (clone_imperfect = 1). Library binding: query_imperfect.
The current MOVE_RIDE window (id 1).
| Offset | Field | Notes |
|---|---|---|
| 2 | timeout | u16, little-endian; the ride window in ms, 0 = off |
Library binding: query_movement_riding.
The current EMIT pacing (id 2): the mode, the configured fixed rate, and the rate actually in effect.
| Offset | Field | Notes |
|---|---|---|
| 2 | mode | 0 learnt, 1 interval, 2 fixed |
| 3 | fixed_hz | u16, little-endian; the configured fixed rate |
| 5 | resolved_hz | u16, little-endian; the ceiling in effect, 0 = learnt/adaptive or no device yet |
Library binding: query_emit_pace.
Reading id = 0: opted in, an over-capacity device attached and cloned imperfectly:
+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+ | A5 | 06 | 00 | 05 00 | 09 | 00 | 01 | 01 | 01 | lo hi | +--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+ | SOF | TYPE | SEQ | LEN | what | id | allow | overcap| imperf | CRC16 | +--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+
CLIP
RESP payload, what = 10The RESP payload when what = 10: the buffered-clip ring depth, playback state, and full config, for host flow-control. A fixed 25-byte prefix, then the clip's held-usage snapshot (the same class-tagged list a USAGE_EVENT carries), then the config tail (autolock, flags, and the bound triggers). Read free before a CLIP_APPEND to avoid an overrun, and state to see a fault or that playback finished. Backs ClipHandle::query_status and query_config.
QUERY what = 10 · RESP 25-byte prefix + held usages + config
Returns RESP
| Offset | Field | Type | Notes |
|---|---|---|---|
| 0 | what | u8 | 10 |
| 1 | state | u8 | 0 idle / 1 playing / 2 paused / 3 faulted (recover with CLEAR) |
| 2 | free | u32 | ring bytes free; pace top-ups off this, little-endian |
| 6 | total | u32 | retained clip size in bytes (streaming: buffered-but-undrained bytes) |
| 10 | played | u32 | bytes played from the clip start (retained progress; ~0 while streaming) |
| 14 | ticks | u32 | content ticks emitted since start (diagnostic) |
| 18 | underruns | u16 | empty-ring episodes |
| 20 | overruns | u16 | appends dropped whole because the ring was full |
| 22 | seq_gaps | u16 | dropped CLIP_APPEND frames detected (SEQ gaps) |
| 24 | held_n | u8 | number of held usages that follow |
| + | class | u8 | per held usage: 0=button 1=key 2=media |
| + | id | u16 | the held usage's id (button id, HID keycode, or Consumer usage), little-endian |
| + | autolock | u8 | config: the CLIP_SET autolock bitmask (CLIP_LOCK_*) |
| + | flags | u8 | config: b0 loop, b1 retain, b2 finalized |
| + | n_trig | u8 | config: number of bound triggers that follow |
| + | class | u8 | per trigger: 0=button 1=key 2=media 0xFF=any |
| + | id | u16 | per trigger: the usage id, 0xFFFF=any, little-endian |
| + | edge | u8 | per trigger: 0 both / 1 press / 2 release |
| + | action | u8 | per trigger: the CLIP_CTRL op 0..5 (start/stop/pause/resume/restart/toggle) |
| + | consume | u8 | per trigger: 1 = swallow the triggering edge from the host |
| Bit | Mask | Set when |
|---|---|---|
| b0 | 0x01 | LOOP: playback restarts from the top on drain instead of stopping |
| b1 | 0x02 | RETAIN: the buffered content survives a stop instead of clearing |
| b2 | 0x04 | FINALIZED: the clip is sealed, no more CLIP_APPEND accepted |
The held snapshot lists the usages the clip is currently forcing down, one class-tagged entry each (3 bytes), so buttons, keys, and media are reported one way. The config tail mirrors what CLIP_SET and CLIP_TRIGGER set, so a UI can read back the whole clip in one query. Library bindings: query_status (ClipStatus) and query_config (ClipSettings).
Idle, empty ring, no held usages, no autolock, no triggers (state = 0, free = 1024):
+--------+--------+--------+--------+--------+--------+--------------+ | A5 | 06 | 00 | 1C 00 | 0A | 00 | 00 04 00 00 | +--------+--------+--------+--------+--------+--------+--------------+ | SOF | TYPE | SEQ | LEN | what | state | free | +--------+--------+--------+--------+--------+--------+--------------+ | 00 04 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 | 00 00 | 00 00 | +--------------+--------------+--------------+--------+--------+--------+ | total | played | ticks | undrun | ovrrun | seqgap | +--------------+--------------+--------------+--------+--------+--------+ | 00 | 00 | 00 | 00 | lo hi | +--------+--------+--------+--------+--------+ | held_n | autolk | flags | n_trig | CRC16 | +--------+--------+--------+--------+--------+