Option
One command (opcode 0x11) sets every box-level toggle. An id byte picks the option, and the rest is its value. All persist in NVS, restore at boot, and are fire-and-forget. An unknown id is ignored.
| Option | id | Does | Factory default |
|---|---|---|---|
IMPERFECT | 0 | Clone a device the box can't clone faithfully, and admit the advanced control layer | off |
MOVE_RIDE | 1 | Inject motion only on a native motion report | off |
EMIT | 2 | Injection pace and wire rate | learnt, no forced rate |
NAME | 3 | Human-readable box name | Medius-XXXX |
BEARING | 4 | What with and against are measured against | 20 ms, per axis |
RENDER | 5 | Motion rendering texture | de-spiked, injected only |
SPREAD | 6 | Injected delta spread over time | one command interval |
OPTIONOne generic, persistent option
OPTION carries an id byte then an id-specific value. Opcode 0x11.
OPTION 0x11 · payload 1 + value bytes
Fire-and-forget
| Offset | Field | Type | Notes |
|---|---|---|---|
| 0 | id | u8 | option |
| 1.. | value | varies | id-specific, delimited by the frame LEN |
No reply. Read any value back with QUERY(OPTIONS, id).
IMPERFECTClone anyway, and admit the advanced control layer
id 0 · [allow u8]
| Value | Effect |
|---|---|
0 | Faithful-only: refuse a device the box can't clone faithfully (default) |
1 | Clone the device anyway (every interface the box can serve byte-faithful), and admit the gated commands below |
| Item | Opt-in off |
|---|---|
| a device the box can't clone faithfully | Refused: no clone appears. |
a forced rate, EMIT's force_hz | Not applied. |
REWRITE | Discarded, except the whole-table clear. Turning the opt-in off clears the table. |
PATCH | Stored, not served. APPLY is ignored. Turning the opt-in off presents a patched clone again without its set. |
TRANSFER | Replied with status 0xFC. |
RAW, and a clip's raw and transfer items | Discarded. Turning the opt-in off drops queued clip transfers. |
| a clip packet trigger that consumes | Refused. Turning the opt-in off removes the ones the box holds. |
A device the box can't clone faithfully has any of these: >4 IN endpoints live at once; >6 HID interfaces; high speed; configurations the box did not capture; a vendor bulk or isochronous endpoint; a setting carrying more endpoint descriptors than it declares (what fits is cloned, the rest left undriven); a report descriptor truncated or never captured.
The ESP32-S3 has four transmit FIFOs for IN endpoints other than endpoint 0. Those endpoints can be numbered up to 6, and a control endpoint above 0 counts as one.
With more live at once, the endpoints the box binds as mouse, keyboard and media keep a FIFO each. The rest share the remaining FIFOs, so a report on a shared one can go ≥1 poll late while every shared FIFO is in use.
If a device the box can't clone faithfully is attached, or a forced rate is pending, changing this option reboots the device chip to re-clone. Otherwise, the clone is presented again when the change alters the patch set it serves.
Read QUERY(OPTIONS, 0) (opt-in plus the over-capacity and imperfect-clone flags) · Library allow_imperfect_clones.
Opt in (allow = 1):
MOVE_RIDE Inject motion only on a native motion report
id 1 · [timeout u16 LE] ms
| Value | Effect |
|---|---|
0 | Off: injected motion is emitted on native reports and the box's reports alike (default) |
N ms | Injected cursor and wheel motion rides only a native motion report seen within N ms. No synthetic motion frame. Motion left unridden is dropped (never dumped on the next move) |
Injected motion keeps native report density.
While on, pure idle injection (moving the cursor while the real device is still) stops working: motion waits for a native motion report and is dropped if none comes.
Button, key, and media injection are unaffected. A move can opt out per command with the MOVE flags.
Changing the value drops any motion held for a ride and clears the standing bearing. Every with / against scale stops applying at that instant.
Read QUERY(OPTIONS, 1) · Library set_movement_riding.
Turn it on with a 20 ms window (timeout = 0x0014):
EMITInjection pace and wire rate
id 2 · [mode u8][rate_hz u16 LE][force_hz u16 LE]
Every OPTION(EMIT) writes all three fields. Resend the ones to keep.
| Value | Name | rate_hz | Emit paced to |
|---|---|---|---|
0 | Learnt (default) | n/a | The measured native report rate |
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 and caps at 1 kHz, so 1000, 500, 333 and 250 are exact (0 means 1000).
The pace is a ceiling. The box emits only while injection is pending.
| Value | Effect |
|---|---|
0 (default) | Serves the captured descriptor and polls the real device at the interval it declared |
| target Hz | Writes a power-of-two bInterval onto every HID interrupt-IN endpoint of the served descriptor: 1000/force_hz ms rounded to the nearest millisecond, then down to a power of two (600 Hz gives 500 Hz). Polls the real device at that same interval |
A forced rate applies only with IMPERFECT on, because the descriptor stops matching the real device. Changing the resolved interval re-clones the box: the device chip reboots, and queries time out for a few seconds.
Vendor interfaces, interrupt-OUT and isochronous endpoints keep the captured value. A low-speed clone cannot express an interval <10 ms, and only powers of two are offered. Its floor is therefore bInterval 16, and any request >62 Hz resolves to 62.
An unknown mode discards the whole command, force_hz included, with no reply.
Read QUERY(OPTIONS, 2) (the stored fields, the rate in effect, and what the clone advertises) · Library set_emit_pace.
A fixed 1 kHz ceiling with the wire rate forced to 1 kHz (mode = 2, rate_hz = 0x03E8, force_hz = 0x03E8):
NAMEHuman-readable box name
id 3 · [name ascii 1..32] (0 bytes = clear)
| Bytes | Effect |
|---|---|
1..32 printable ASCII | Sets the name to those bytes. |
0 (the id alone) | Clears the name, reverting to the MAC-derived Medius-XXXX default. |
The name pairs with the box MAC and persists in NVS with no reboot. Read it as the ASCII tail of RESP(VERSION) after the MAC, not through QUERY(OPTIONS).
Read it back on RESP(VERSION) · Library set_name.
Name the box "Loki" (id = 3, ascii 4C 6F 6B 69):
BEARINGWhat with and against are measured against
id 4 · [window u16 LE] ms [mode u8]
| Value | Effect |
|---|---|
0 | No bearing is ever held, so with and against are inert whatever their scale |
N ms | An axis keeps the direction of its last injected delta for N ms (default 20) |
| Value | Geometry |
|---|---|
0 | Per axis (default) |
1 | Vector |
2 or above | Unknown: the whole command is dropped, window included, with no reply |
A write that changes either field drops the standing bearing and the banked carry on every mouse interface. With a with / against scale live, the drop is a visible step in the emitted motion. Set the geometry before the scales, not between reports.
Read QUERY(OPTIONS, 4) · Library set_bearing.
A 20 ms window in vector mode (window = 0x0014, mode = 1):
RENDERMotion rendering texture
id 5 · [mode u8][full u8]
Every OPTION(RENDER) writes both fields. Resend the one to keep.
| Value | Name | Effect |
|---|---|---|
0 | Off | Renderer off. The box emits the paced fill. |
1 | Stock | The model's bit-exact triangular smoother. Its first report carries twice the steady delta. |
2 | De-spiked (default) | The same smoother, ramping up to the steady delta over 8 ms from the first report. |
3 | Unsmoothed | No smoother. The model receives the raw injection. |
one injected correction, drained over ~12 ms (| = a report, . = an idle ms) mode = 0 | | | | | | | | | | | | even fill at the paced rate mode > 0 | | . | . . | | . | . | the native on/off texture
| Aspect | mode = 0 | mode > 0 |
|---|---|---|
| Per-report delta | The accumulator, split to fit the field | Shaped by the model, summing to the same total |
| Model | None | ABCurves (MIT), fit live per device |
| Before a profile arms | Emits at once | Emits the paced fill, then switches over |
| Value | Effect |
|---|---|
0 (default) | Renders injected motion only. Native cursor delta is relayed byte for byte. |
1 | Renders both: native cursor delta is taken out of the relayed report and joins injection as one stream through the model. |
Rendering adds the smoother's group delay: 4 ms at mode = 1, 2.7 ms at mode = 2, none at mode = 3. With full at 1, native motion gets that delay plus 1 ms.
Per axis, a rendered report carries at most what the clone's cursor field holds (a bound never <127 counts). The model carries anything past that as debt into later reports.
The pace caps the rendered rate. The model's debt carries what the cap coalesces. Buttons, the wheel and every other field are relayed at native timing in both modes: the model renders cursor motion only.
A MOVE carrying any flag takes the plain paced path instead: NOW, FLUSH and DISCARD each fix exact timing, which the renderer otherwise sets. Clip cursor motion renders like a MOVE with no flag.
Under full, the rendered stream ignores MOVE_RIDE. Riding would hold native motion and drop what it never rides out. A clip's stream ignores it too.
The profile is built from native reports and never persisted. Every boot starts without one, and the profile arms from a window of native motion. full renders nothing while mode is 0, with no model in the path.
A mode above 3 or a full above 1 discards the whole command, with no reply.
Read QUERY(OPTIONS, 5) (both stored fields, and whether a profile has armed) · Library set_render.
De-spiked, rendering native motion too (mode = 2, full = 1):
SPREADInjected delta spread over time
id 6 · [percent u16 LE]
A host loop slower than the native report rate sends deltas worth several native reports. The percent is the share of the command interval over which the box releases each delta.
| Value | Effect |
|---|---|
0 | The whole delta is emitted on the next report. |
1..99 | Released across that share of the interval, with the rounding remainder at the end of the share. |
100 (default) | Released evenly across one whole command interval. |
101..65535 | Released over longer than the interval. Each command arrives on a remainder, so the box carries a standing backlog. |
a 250 Hz host on a 1000 Hz native rate: one MOVE of 8 every 4 ms, 12 ms of wire ms 0 1 2 3 4 5 6 7 8 9 10 11 percent = 0 8 . . . 8 . . . 8 . . . one report per command, 4× the delta percent = 100 2 2 2 2 2 2 2 2 2 2 2 2 a report every ms at the native magnitude
The box learns the interval as the most common gap between MOVE arrivals. A burst doesn't move it, and a rate change is followed. Until the box has an interval, and for commands more than ~32 ms apart, the whole delta is emitted on the next report regardless of percent.
| Aspect | percent = 0 | percent = 100 |
|---|---|---|
| Delivered total | Exact | Exact |
| Added latency | None | Half the interval on average (~4 ms at 125 Hz) |
| Reports per command | One | Up to one per report the box emits in the interval, and never more than the delta's count |
| Wheel motion | Not spread | Not spread: a detent is one unit |
A loop at the native report rate keeps each command whole. The command waits in a short queue and is emitted alone on one report, no later than the first report after its interval ends. A delta smaller than its interval's report count can't be split one count per report. It is emitted once, part way through.
A MOVE carrying any flag is not spread: NOW, FLUSH and DISCARD each fix exact timing. FLUSH and DISCARD also act on any still-waiting part of an earlier delta, so they mean the same at any percent.
SPREAD and RENDER compose independently. Under full rendering, only injected motion is spread. Native motion already arrives at the native report rate.
Read QUERY(OPTIONS, 6) (the percent, and the interval in effect) · Library set_spread.
One whole command interval (percent = 0x0064):