Medius - Native APIOption

Option

Set a persistent box option by id

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

OptionidValueDoesDefault
IMPERFECT0[allow u8]Clone an over-capacity device anywayoff
MOVE_RIDE1[timeout u16 LE]Inject motion only on a real moveoff
EMIT2[mode u8][rate_hz u16 LE]Pick what paces injected motionlearnt
NAME3[name ascii 0..32]Give the box a human-readable nameMedius-XXXX

OPTION

One generic, persistent option

OPTION 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

PAYLOAD
OffsetFieldTypeNotes
0idu8which option
1..valuevariesid-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 anyway
id 0 · [allow u8]
ALLOW
ValueEffect
0Faithful-only: refuse a device the box can't clone exactly (default)
1Clone 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.

EXAMPLE

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 move
id 1 · [timeout u16 LE] ms
TIMEOUT
ValueEffect
0Off: injection emits via the frame clock (default)
N msInjected 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.

EXAMPLE

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 motion
id 2 · [mode u8][rate_hz u16 LE]
MODE
modeNamerate_hzEmit paced to
0Learnt (default)n/aThe rate the real mouse actually reports at
1Intervaln/aThe cloned mouse's declared poll rate (its bInterval)
2Fixedtarget Hzrate_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.

EXAMPLE

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 name
id 3 · [name ascii 1..32] (0 bytes = clear)
VALUE
BytesEffect
1..32 printable ASCIISets 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.

EXAMPLE

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