Option
Set a persistent box option by idOne command (opcode 0x11) sets every box-level toggle: an id byte picks the option, the rest is its value. All persist in NVS, restore at boot, and are fire-and-forget. An unknown id is ignored.
| Option | id | Value | Does | Default |
|---|---|---|---|---|
IMPERFECT | 0 | [allow u8] | Clone an over-capacity device anyway | off |
MOVE_RIDE | 1 | [timeout u16 LE] | Inject motion only on a real move | off |
EMIT | 2 | [mode u8][rate_hz u16 LE] | Pick what paces injected motion | learnt |
NAME | 3 | [name ascii 0..32] | Give the box a human-readable name | Medius-XXXX |
OPTION
One generic, persistent optionOPTION carries an id byte then an id-specific value, and the box persists the setting across a reboot. One opcode covers every persistent option; the id picks which. Opcode 0x11.
OPTION 0x11 · payload 1 + value bytes
Fire-and-forget
| Offset | Field | Type | Notes |
|---|---|---|---|
| 0 | id | u8 | which option |
| 1.. | value | varies | id-specific; the frame LEN delimits it, so a new option needs no new opcode |
No reply. Read any value back with QUERY(OPTIONS, id).
IMPERFECT
Clone an over-capacity device anywayid 0 · [allow u8]
| Value | Effect |
|---|---|
0 | Faithful-only: refuse a device the box can't clone exactly (default) |
1 | Clone it anyway: every other interface byte-faithful, the over-capacity one dead |
Some devices need more interrupt-IN endpoints than the box serves (the Wooting Two HE's analog stream wants a sixth, past the ESP32-S3's five). Changing this for an attached over-capacity device reboots the box to re-clone; a normal device is unaffected.
Read QUERY(OPTIONS, 0) (opt-in plus the over-capacity and imperfect-clone flags) · Library allow_imperfect_clones.
Opt in (allow = 1):
+--------+--------+--------+--------+--------+--------+--------+ | A5 | 11 | 00 | 02 00 | 00 | 01 | lo hi | +--------+--------+--------+--------+--------+--------+--------+ | SOF | TYPE | SEQ | LEN | id | allow | CRC16 | +--------+--------+--------+--------+--------+--------+--------+
MOVE_RIDE
Inject motion only on a real moveid 1 · [timeout u16 LE] ms
| Value | Effect |
|---|---|
0 | Off: injection emits via the frame clock (default) |
N ms | Injected cursor and wheel motion only rides a native move seen within N ms; no synthetic motion frame, and motion left unridden is dropped (never dumped on the next move) |
This keeps injected motion's report density identical to the real mouse's, erasing the density tell (a human aims at ~270-360 Hz, idle 60-70% of the time; gap-filling injection runs gapless near 990 Hz).
While on, pure idle injection (moving the cursor while the hand is still) stops working: motion waits for a native move and is dropped if none comes. Button, key, and media injection are unaffected.
Read QUERY(OPTIONS, 1) · Library set_movement_riding.
Turn it on with a 20 ms window (timeout = 0x0014):
+--------+--------+--------+--------+--------+--------+--------+ | A5 | 11 | 00 | 03 00 | 01 | 14 00 | lo hi | +--------+--------+--------+--------+--------+--------+--------+ | SOF | TYPE | SEQ | LEN | id | timeout| CRC16 | +--------+--------+--------+--------+--------+--------+--------+
EMIT
Pick what paces injected motionid 2 · [mode u8][rate_hz u16 LE]
mode | Name | rate_hz | Emit paced to |
|---|---|---|---|
0 | Learnt (default) | n/a | The rate the real mouse actually reports at |
1 | Interval | n/a | The cloned mouse's declared poll rate (its bInterval) |
2 | Fixed | target Hz | rate_hz, snapped to 1000/n |
Fixed snaps to 1000/n Hz on the 1 ms frame clock and caps at 1 kHz, so 1000, 500, 333, 250… are exact and 750 lands on 1000 (0 means 1000). Every mode raises the ceiling only: the box still emits a frame solely when injection is pending, so idle stays idle. Learnt keeps injection matched to the real mouse; the other modes are for a host that shapes its own report density and wants the box to stop re-pacing it.
Read QUERY(OPTIONS, 2) (mode plus the rate in effect) · Library set_emit_pace.
Fixed 1 kHz (mode = 2, rate_hz = 0x03E8):
+--------+--------+--------+--------+--------+--------+--------+--------+ | A5 | 11 | 00 | 04 00 | 02 | 02 | E8 03 | lo hi | +--------+--------+--------+--------+--------+--------+--------+--------+ | SOF | TYPE | SEQ | LEN | id | mode | rate_hz| CRC16 | +--------+--------+--------+--------+--------+--------+--------+--------+
NAME
Give the box a human-readable nameid 3 · [name ascii 1..32] (0 bytes = clear)
| Bytes | Effect |
|---|---|
1..32 printable ASCII | Sets the box's name to those bytes. |
0 (the id alone) | Clears the name, reverting to the synthesized Medius-XXXX default derived from the MAC. |
The name is the readable partner to the box MAC, persisted in NVS with no reboot. It rides on RESP(VERSION) as the ASCII tail after the MAC, so it's read there, not through QUERY(OPTIONS).
Read it back on RESP(VERSION) · Library set_name.
Name the box "Loki" (id = 3, ascii 4C 6F 6B 69):
+--------+--------+--------+--------+--------+--------------+--------+ | A5 | 11 | 00 | 05 00 | 03 | 4C 6F 6B 69 | lo hi | +--------+--------+--------+--------+--------+--------------+--------+ | SOF | TYPE | SEQ | LEN | id | name ascii | CRC16 | +--------+--------+--------+--------+--------+--------------+--------+