Requests
Query the box for stateQUERY is the only command that gets a reply, a RESP. The what selector picks the state: version, health, device info, capabilities, rate, stats, locks, the catch subscription, an option, or the buffered 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 table, plus its drop counts and the cross-chip clock estimate. | CATCH |
8 | reserved | - |
9 | A persistent box option, by id. | OPTIONS |
10 | The buffered-clip ring depth, playback state, and config. | CLIP |
11 | Both chips' firmware versions, the slot each runs, and what is staged. | FIRMWARE |
The box replies with a RESP carrying the same what and the requested data. Every other command 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_bearing, query_emit_pace, and the clip status query, and firmware_info.
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) |
Exactly one RESP per QUERY. Its SEQ matches the request's and what echoes the selector.
VERSION
RESP payload, what = 0The RESP payload when what = 0: the protocol version, the box's own firmware version, its base mac, then a length-delimited ASCII name tail. The tail is additive, so an older box sends an empty one.
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 5 |
| 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 identifies the same box across replugs and port renumbering. Library binding: query_version.
Firmware 3.2.0, protocol 5, MAC 123456789abc, name "Loki":
+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+ | A5 | 06 | 00 | 0F 00 | 00 | 05 | 03 | 02 | 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 is off a full pass under LOCK, blocked or merely weighed |
| b6 | 0x40 | CATCH_ON: the CATCH subscription table is non-empty, so events are streaming. It says nothing about what is subscribed; read QUERY(CATCH) for the table |
| 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, which the control PC cannot see any other way. 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.
An INJECT for a usage the device lacks reaches no report field. 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. Which field to read depends on 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, so these counters are the only delivery feedback there is.
A nonzero tx_drops or tx_wedges means delivery slipped under load. 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. 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 weighed by LOCK, one entry per direction that is not passing untouched. An empty list (n = 0) means everything passes.
QUERY what = 6 · RESP 2 + 5n bytes
Returns RESP
| Offset | Field | Type | Notes |
|---|---|---|---|
| 0 | what | u8 | 0x06 |
| 1 | n | u8 | number of entries that follow, up to 96 |
| + | class | u8 | per entry: 0=button 1=key 2=media 3=axis (as LOCK) |
| + | id | u16 | the weighed field's id, or 0xFFFF for a whole-class blanket, little-endian |
| + | direction | u8 | which direction of it, as LOCK |
| + | scale | u8 | percent of the physical value kept, 0-255 (as LOCK); 0 = blocked, above 100 amplifies |
Entries mirror the LOCK frame field for field, so what comes back is what you would send to reproduce it.
| State | Reports as |
|---|---|
| An axis or button weighed on a direction | One entry under its own class and id, per direction. |
| A target passing on every direction | Absent. |
| A blanket button or axis lock | One entry per member, under its own id; never 0xFFFF. |
| A blanket key lock | One entry per blocked edge, id = 0xFFFF, direction 1 and/or 2; never 0. |
| A media lock, blanket or specific | Direction 0, always. Media has no edges. |
| A relative direction in vector mode | The effective scale, the lower of X's and Y's, on both axes. |
| Any momentary usage | scale of 0 or 100 only; the box stores the block or pass it renders, not the number sent. |
| A relative direction with no bearing live | Its stored scale, unchanged. A lapsed window, or an OPTION(BEARING) window of 0, stops with and against weighing without clearing them, so an entry can report 40 while that axis passes untouched. |
| Order | Source | Most it spends |
|---|---|---|
| 1 | Mouse axes and buttons | 22: 3 axes x 4 directions, plus 5 buttons x 2 edges. A button has no relative pair, so 10 of the table's 32 slots are out of reach. |
| 2 | The blanket key lock | 2, one per blocked edge |
| 3 | The blanket media lock | 1 |
| 4 | Specific media usages | 8, the whole media-lock table |
| 5 | Specific keys | the rest of the 96, so at least 63, one per blocked edge |
Rows 1 to 4 are capped by the box's own tables and spend 33 between them, so they are always in the reply. A keyboard usage is the one unbounded class: 252 of them, two edges each.
Truncation can only land on row 5: n stops short and the reply has nowhere to say so. Count the key edges you asked for against what came back.
Library binding: query_locks.
One entry: the wheel's negative (scroll-down) sign blocked (class = 3 axis, id = 2 wheel, direction = 2, scale = 0):
+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+ | A5 | 06 | 00 | 07 00 | 06 | 01 | 03 | 02 00 | 02 | 00 | lo hi | +--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+ | SOF | TYPE | SEQ | LEN | what | n | class | id | dir | scale | CRC16 | +--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+
CATCH
RESP payload, what = 7The RESP payload when what = 7: the active CATCH subscription. A fixed scalar header, then the table, shaped like RESP(LOCKS). An empty table means nothing is subscribed, which mirrors the CATCH_ON health bit.
QUERY what = 7 · RESP 19 + 7n bytes
Returns RESP
| Offset | Field | Type | Notes |
|---|---|---|---|
| 0 | what | u8 | 0x07 |
| 1 | flags | u8 | b0 = the table is full and an entry was refused |
| 2 | dropped | u32 | box-wide events that could not be queued, little-endian |
| 6 | clk_offset_us | i32 | the host chip's clock minus the device chip's, measured (below), little-endian |
| 10 | clk_rate_ppb | i32 | relative drift between the two chips in parts per billion; INT32_MIN = no fit made, little-endian |
| 14 | clk_delay_us | u16 | best measured round trip; the offset's error bound is half this, little-endian |
| 16 | clk_age_ms | u16 | age of the exchange the offset rests on; 0xFFFF = no estimate, little-endian |
| 18 | n | u8 | number of entries following |
| + | class | u8 | per entry: the address class |
| + | id | u16 | the entry's id, or 0xFFFF for a class blanket, little-endian |
| + | dir | u8 | the entry's direction |
| + | snaplen | u8 | bytes captured per event, 0 = whole packet |
| + | dropped | u16 | events this entry could not queue, little-endian |
The box-wide dropped counts every lost event; the per-entry one attributes them to a subscription.
CATCH is fire-and-forget, so this reply is the only way to see that an entry landed. A refused entry is simply absent from the list; flags b0 tells you the 32-entry table was the reason. Library binding: query_catch.
The two ESP32-S3s boot independently, so nothing relates their timers, and events carry stamps from both (see the clk byte).
The box measures the difference with a four-timestamp exchange across the inter-chip link, stamped as each frame reaches the wire rather than when it is queued.
device chip host chip
t1 ------- request -------------------> t2
|
t4 <---------------- reply ------------ t3
offset = ((t2 - t1) + (t3 - t4)) / 2
delay = (t4 - t1) - (t3 - t2) -> the error bound is delay / 2| Field | What it buys you |
|---|---|
clk_delay_us | the round trip of the best exchange in the window, so the offset is good to about half of it. A caller that needs a hard bound has one. |
clk_rate_ppb | lets you extrapolate between exchanges rather than trusting a stale offset, which two independent crystals make stale at up to 20 µs per second. INT32_MIN means no fit has been made, distinct from a fitted 0, which means the crystals are matched. |
clk_age_ms | The age of the exchange the offset actually rests on, not of the newest one. The offset comes from the least-delayed exchange in the window, which is often older. 0xFFFF distinguishes "no estimate yet" from "the offset happens to be zero", which both otherwise report as an offset of 0. |
Applying the offset is optional; the clk byte on each event stays authoritative.
One entry, every mouse button, both edges, whole packet (class = 0, id = 0xFFFF, dir = 0, snaplen = 0), with no drops and no clock estimate taken yet:
+--------+--------+--------+--------+--------+--------+--------------+ | A5 | 06 | 00 | 1A 00 | 07 | 00 | 00 00 00 00 | +--------+--------+--------+--------+--------+--------+--------------+ | SOF | TYPE | SEQ | LEN | what | flags | dropped | +--------+--------+--------+--------+--------+--------+--------------+ +-------------+-------------+--------+--------+--------+ | 00 00 00 00 | 00 00 00 00 | 00 00 | FF FF | 01 | +-------------+-------------+--------+--------+--------+ | clk_offset | clk_rate |clk_dly | clk_age| n | +-------------+-------------+--------+--------+--------+ +--------+--------+--------+--------+--------+--------+ | 00 | FF FF | 00 | 00 | 00 00 | lo hi | +--------+--------+--------+--------+--------+--------+ | class | id | dir |snaplen | 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 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 BEARING setting (id 4): the window and how the box reads it.
| Offset | Field | Notes |
|---|---|---|
| 2 | window | u16, little-endian; the bearing window in ms, 0 = off |
| 4 | mode | 0 per axis, 1 vector |
Library binding: query_bearing.
The current EMIT pacing (id 2).
| 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 |
| 7 | force_hz | u16, little-endian; the requested wire rate, 0 = off |
| 9 | advertised_hz | u16, little-endian; what the clone's input endpoints advertise now, forced or native, 0 = no clone |
| 11 | force_active | 1 when a forced interval is in the served descriptor |
A force_hz set while IMPERFECT is off reads back with force_active 0 and advertised_hz still the device's own.
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. A fixed prefix, then the clip's held-usage snapshot (the same class-tagged list a USAGE_EVENT carries), then the config tail.
Read free before a CLIP_APPEND to avoid an overrun, and state to see a fault or that playback finished.
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, b3 ride |
| + | 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 = lock the trigger usage while it stays active |
| 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 |
| b3 | 0x08 | RIDE: the clip's motion waits to ride a native report instead of emitting on the box's own clock |
The held snapshot lists the usages the clip is currently forcing down, one class-tagged entry each (3 bytes). The config tail mirrors what CLIP_SET and CLIP_TRIGGER set.
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 | +--------+--------+--------+--------+--------+
FIRMWARE
RESP payload, what = 11The RESP payload when what = 11: both chips' versions, the slot each is running, and what is staged. The only place the host chip's version appears, because VERSION reports the device chip alone and its name tail is delimited by the frame LEN, so nothing can follow it.
Read it before an UPDATE: slot_size is the largest image a chip will take, and a chip whose state is pending-verify refuses to open one.
QUERY what = 11 · RESP 17 bytes
Returns RESP
| Offset | Field | Type | Notes |
|---|---|---|---|
| 0 | what | u8 | 11 |
| 1 | dev_major | u8 | |
| 2 | dev_minor | u8 | |
| 3 | dev_patch | u8 | |
| 4 | dev_slot | u8 | 0 = ota_0, 1 = ota_1 |
| 5 | dev_state | u8 | image state, below |
| 6 | host_present | u8 | 1 when the host chip has answered over the link |
| 7 | host_major | u8 | 0 when host_present is 0 |
| 8 | host_minor | u8 | |
| 9 | host_patch | u8 | |
| 10 | host_slot | u8 | 0xFF when host_present is 0 |
| 11 | host_state | u8 | |
| 12 | slot_size | u32 | usable bytes in a spare slot, little-endian; the same on both chips |
| 16 | staged | u8 | bit 0 device staged, bit 1 host staged |
| Value | State | Means |
|---|---|---|
0 | new | selected but not yet booted |
1 | pending-verify | booted, on probation; this is the window rollback lives in |
2 | valid | confirmed by the image itself |
3 | invalid | the image asked to be rolled back |
4 | aborted | booted once and never confirmed |
0xFF | unknown | no entry for this slot |
Library binding: firmware_info.
Both chips on 3.2.0, device on ota_1, host on ota_0, nothing staged:
+--------+--------+--------+--------+--------+-------------+--------+ | A5 | 06 | 01 | 11 00 | 0B | 03 02 00 01 | ... | +--------+--------+--------+--------+--------+-------------+--------+ | SOF | TYPE | SEQ | LEN | what | device | host | +--------+--------+--------+--------+--------+-------------+--------+ +--------+-------------+--------+--------+ | 02 | 00 00 0F 00 | 00 | lo hi | +--------+-------------+--------+--------+ | state | slot_size | staged | CRC16 | +--------+-------------+--------+--------+