Medius - Native APIRequests

Requests

Query the box for state

QUERY 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 state

QUERY asks for one piece of state, named by its what byte. Opcode 0x05.

QUERY 0x05 · payload 1 byte (2 for OPTIONS)

Returns RESP

PAYLOAD
OffsetFieldTypeNotes
0whatu8which state to read (see below)
1idu8only for what = 9 (OPTIONS): which option to read; omitted otherwise
SELECTORS
whatReadsReply
0The firmware version.VERSION
1The box's health.HEALTH
2The cloned device's USB identity, kind, and product.DEVICE_INFO
3The whole device's capabilities (mouse + keyboard).CAPS
4The native report rate.RATE
5Delivery and telemetry counters.STATS
6The active input locks.LOCKS
7The active catch subscription table, plus its drop counts and the cross-chip clock estimate.CATCH
8reserved-
9A persistent box option, by id.OPTIONS
10The buffered-clip ring depth, playback state, and config.CLIP
11Both chips' firmware versions, the slot each runs, and what is staged.FIRMWARE
EFFECT

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.

EXAMPLE

what = 0 (read the version):

+--------+--------+--------+--------+--------+--------+
| A5     | 05     | 00     | 01 00  | 00     | lo hi  |
+--------+--------+--------+--------+--------+--------+
| SOF    | TYPE   | SEQ    | LEN    | what   | CRC16  |
+--------+--------+--------+--------+--------+--------+

RESP

The box's reply

RESP is the box's reply to a QUERY. Opcode 0x06.

RESP 0x06 · box → PC

Reply

PAYLOAD
OffsetFieldTypeNotes
0whatu8echoes the request's selector
1..datavariesthe layout for the requested what (see the selectors)
EFFECT

Exactly one RESP per QUERY. Its SEQ matches the request's and what echoes the selector.

VERSION

RESP payload, what = 0

The 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

PAYLOAD
OffsetFieldTypeNotes
0whatu80x00
1proto_veru8protocol version, expected 5
2fw_majoru8firmware major
3fw_minoru8firmware minor
4fw_patchu8firmware patch
5macu8[6]the device chip's base MAC; the stable per-box id, rendered as 12 lowercase hex digits
11..nameasciithe 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)
EFFECT

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.

EXAMPLE

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 = 1

The RESP payload when what = 1: a single flags byte, each bit an independent status.

QUERY what = 1 · RESP 2 bytes

Returns RESP

PAYLOAD
OffsetFieldTypeNotes
0whatu80x01
1flagsu8the status bits below
FLAGS
BitMaskSet when
b00x01the link to the host chip is up
b10x02a real mouse is attached
b20x04the PC has set up the cloned mouse
b30x08injection is active
b40x10RATE_CONFIDENT: the native-rate estimator window is full, so the RATE value is trustworthy
b50x20LOCK_ON: at least one input is off a full pass under LOCK, blocked or merely weighed
b60x40CATCH_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
b70x80KBD_ATT: a keyboard is attached on the host chip, cloned and injectable
EFFECT

The first three bits set means the box is ready for input to reach the PC. Library binding: query_health.

EXAMPLE

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 = 2

The 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

PAYLOAD
OffsetFieldTypeNotes
0whatu80x02
1vidu16idVendor, little-endian
3pidu16idProduct, little-endian
5bcd_deviceu16bcdDevice, the device release
7bcd_usbu16bcdUSB, e.g. 0x0200 or 0x0201
9flagsu8the bits below
10primary_kindu8the cloned device's kind, from its Boot-interface protocol (see below)
11..productUTF-8the product string, filling the rest of the payload; may be empty
FLAGS
BitMaskSet when
b00x01HAS_SERIAL: the clone serves a serial string
b10x02HAS_BOS: the clone serves a BOS descriptor
PRIMARY_KIND
ValueKind
0unknown
1keyboard
2mouse
EFFECT

A vid of 0 means nothing is attached yet. Library binding: device_info.

EXAMPLE

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 = 3

The 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

PAYLOAD
OffsetFieldTypeNotes
0whatu80x03
1n_buttonsu8buttons the mouse report carries
2axis_flagsu8mouse axes, the bits below
3n_hidu8cloned HID interfaces; >1 = composite
4n_keysu8keycode-array slots, or 0xFF for NKRO; 0 = no keyboard
5kbd_flagsu8keyboard, the bits below
6change_drivenu8per class: b0 mouse (continuous, 0), b1 keyboard/media (change-driven, 1 when bound)
AXIS_FLAGS
BitMaskSet when
b00x01X: the report carries an X axis
b10x02Y: the report carries a Y axis
b20x04WHEEL: the report carries a wheel
b30x08REPORT_ID: the mouse report sits behind a HID report ID
KBD_FLAGS
BitMaskSet when
b00x01NKRO: the keyboard reports an NKRO bitmap
b10x02CONSUMER: a Consumer collection is present, so media keys are injectable
b20x04SYSTEM: a system-control collection is present (passthrough-only)
b30x08REPORT_ID: the keyboard report sits behind a HID report ID
EFFECT

Library binding: caps.

EXAMPLE

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 = 4

The 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 kindCHANGE_DRIVENReadGives
continuous (moving mouse)0native_period_usHz = 1e6 / period; reads 0 until learned
change-driven (keyboard, media)1poll_period_usno steady cadence, so native_period_us is 0
QUERY what = 4 · RESP 6 bytes

Returns RESP

PAYLOAD
OffsetFieldTypeNotes
0whatu80x04
1native_period_usu16realised native period in µs; 0 = not learned, or change-driven (see flags), Hz = 1e6/period
3poll_period_usu16cloned inject-endpoint bInterval poll period in µs; the honest figure for a change-driven input
5flagsu8the bits below
FLAGS
BitMaskSet when
b00x01CONFIDENT: the estimator window is full, same source as HEALTH RATE_CONFIDENT
b10x02CHANGE_DRIVEN: the active input is event-driven (keyboard / media), so there is no continuous cadence and native_period_us is 0
EFFECT

A 1 kHz mouse reads ~1000 µs once learned. Library binding: query_rate.

EXAMPLE

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 = 5

The 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

PAYLOAD
OffsetFieldTypeNotes
0whatu80x05
1inject_emitsu32pure-injection reports emitted, little-endian
5tx_dropsu16reports dropped on TX-queue overflow; should stay 0
7tx_mergesu16backed-up reports merged instead of queued
9tx_maxdepthu8deepest the TX queue has reached
10tx_wedgesu8wedged-endpoint recoveries
11wakeupsu16remote-wakeups issued
13reset_countu16USB bus resets seen
15config_countu16SET_CONFIGURATION events (re-enumerations)
EFFECT

inject_emits counts the no-halving / 1 kHz path. Library binding: query_stats.

EXAMPLE

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 = 6

The 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

PAYLOAD
OffsetFieldTypeNotes
0whatu80x06
1nu8number of entries that follow, up to 96
+classu8per entry: 0=button 1=key 2=media 3=axis (as LOCK)
+idu16the weighed field's id, or 0xFFFF for a whole-class blanket, little-endian
+directionu8which direction of it, as LOCK
+scaleu8percent of the physical value kept, 0-255 (as LOCK); 0 = blocked, above 100 amplifies
READBACK

Entries mirror the LOCK frame field for field, so what comes back is what you would send to reproduce it.

StateReports as
An axis or button weighed on a directionOne entry under its own class and id, per direction.
A target passing on every directionAbsent.
A blanket button or axis lockOne entry per member, under its own id; never 0xFFFF.
A blanket key lockOne entry per blocked edge, id = 0xFFFF, direction 1 and/or 2; never 0.
A media lock, blanket or specificDirection 0, always. Media has no edges.
A relative direction in vector modeThe effective scale, the lower of X's and Y's, on both axes.
Any momentary usagescale of 0 or 100 only; the box stores the block or pass it renders, not the number sent.
A relative direction with no bearing liveIts 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.
BUDGET
OrderSourceMost it spends
1Mouse axes and buttons22: 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.
2The blanket key lock2, one per blocked edge
3The blanket media lock1
4Specific media usages8, the whole media-lock table
5Specific keysthe 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.

EFFECT

Library binding: query_locks.

EXAMPLE

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 = 7

The 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

PAYLOAD
OffsetFieldTypeNotes
0whatu80x07
1flagsu8b0 = the table is full and an entry was refused
2droppedu32box-wide events that could not be queued, little-endian
6clk_offset_usi32the host chip's clock minus the device chip's, measured (below), little-endian
10clk_rate_ppbi32relative drift between the two chips in parts per billion; INT32_MIN = no fit made, little-endian
14clk_delay_usu16best measured round trip; the offset's error bound is half this, little-endian
16clk_age_msu16age of the exchange the offset rests on; 0xFFFF = no estimate, little-endian
18nu8number of entries following
+classu8per entry: the address class
+idu16the entry's id, or 0xFFFF for a class blanket, little-endian
+diru8the entry's direction
+snaplenu8bytes captured per event, 0 = whole packet
+droppedu16events this entry could not queue, little-endian

The box-wide dropped counts every lost event; the per-entry one attributes them to a subscription.

CONFIRMING 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 CLOCK FIELDS

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
FieldWhat it buys you
clk_delay_usthe 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_ppblets 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_msThe 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.

EXAMPLE

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 = 9

The 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

PAYLOAD
OffsetFieldTypeNotes
0whatu80x09
1idu8which option this value is for
2..valuevariesid-specific, mirroring the matching OPTION value
IMPERFECT VALUE

The IMPERFECT opt-in (id 0) plus two derived clone-status bytes. Each is 0 or 1; a faithful clone reads all-zero.

OffsetFieldNotes
2allowedthe opt-in toggle; 1 = cloning an over-capacity device is allowed
3over_capacitythe attached device needs an interrupt-IN endpoint the box can't service
4clone_imperfectthe 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.

MOVE_RIDE VALUE

The current MOVE_RIDE window (id 1).

OffsetFieldNotes
2timeoutu16, little-endian; the ride window in ms, 0 = off

Library binding: query_movement_riding.

BEARING VALUE

The current BEARING setting (id 4): the window and how the box reads it.

OffsetFieldNotes
2windowu16, little-endian; the bearing window in ms, 0 = off
4mode0 per axis, 1 vector

Library binding: query_bearing.

EMIT VALUE

The current EMIT pacing (id 2).

OffsetFieldNotes
2mode0 learnt, 1 interval, 2 fixed
3fixed_hzu16, little-endian; the configured fixed rate
5resolved_hzu16, little-endian; the ceiling in effect, 0 = learnt/adaptive or no device yet
7force_hzu16, little-endian; the requested wire rate, 0 = off
9advertised_hzu16, little-endian; what the clone's input endpoints advertise now, forced or native, 0 = no clone
11force_active1 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.

EXAMPLE

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 = 10

The 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

PAYLOAD
OffsetFieldTypeNotes
0whatu810
1stateu80 idle / 1 playing / 2 paused / 3 faulted (recover with CLEAR)
2freeu32ring bytes free; pace top-ups off this, little-endian
6totalu32retained clip size in bytes (streaming: buffered-but-undrained bytes)
10playedu32bytes played from the clip start (retained progress; ~0 while streaming)
14ticksu32content ticks emitted since start (diagnostic)
18underrunsu16empty-ring episodes
20overrunsu16appends dropped whole because the ring was full
22seq_gapsu16dropped CLIP_APPEND frames detected (SEQ gaps)
24held_nu8number of held usages that follow
+classu8per held usage: 0=button 1=key 2=media
+idu16the held usage's id (button id, HID keycode, or Consumer usage), little-endian
+autolocku8config: the CLIP_SET autolock bitmask (CLIP_LOCK_*)
+flagsu8config: b0 loop, b1 retain, b2 finalized, b3 ride
+n_trigu8config: number of bound triggers that follow
+classu8per trigger: 0=button 1=key 2=media 0xFF=any
+idu16per trigger: the usage id, 0xFFFF=any, little-endian
+edgeu8per trigger: 0 both / 1 press / 2 release
+actionu8per trigger: the CLIP_CTRL op 0..5 (start/stop/pause/resume/restart/toggle)
+consumeu8per trigger: 1 = lock the trigger usage while it stays active
FLAGS
BitMaskSet when
b00x01LOOP: playback restarts from the top on drain instead of stopping
b10x02RETAIN: the buffered content survives a stop instead of clearing
b20x04FINALIZED: the clip is sealed, no more CLIP_APPEND accepted
b30x08RIDE: the clip's motion waits to ride a native report instead of emitting on the box's own clock
EFFECT

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).

EXAMPLE

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 = 11

The 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

PAYLOAD
OffsetFieldTypeNotes
0whatu811
1dev_majoru8
2dev_minoru8
3dev_patchu8
4dev_slotu80 = ota_0, 1 = ota_1
5dev_stateu8image state, below
6host_presentu81 when the host chip has answered over the link
7host_majoru80 when host_present is 0
8host_minoru8
9host_patchu8
10host_slotu80xFF when host_present is 0
11host_stateu8
12slot_sizeu32usable bytes in a spare slot, little-endian; the same on both chips
16stagedu8bit 0 device staged, bit 1 host staged
STATE
ValueStateMeans
0newselected but not yet booted
1pending-verifybooted, on probation; this is the window rollback lives in
2validconfirmed by the image itself
3invalidthe image asked to be rolled back
4abortedbooted once and never confirmed
0xFFunknownno entry for this slot

Library binding: firmware_info.

EXAMPLE

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  |
+--------+-------------+--------+--------+