Options
Persistent box settingsFive box settings, each set and read on its own. All persist in NVS and survive a reboot. See the native OPTION command for the wire contract.
| Option | Set | Read |
|---|---|---|
| imperfect clone | allow_imperfect_clones | query_imperfect |
| movement riding | set_movement_riding | query_movement_riding |
| bearing | set_bearing | query_bearing |
| emit-rate pacing | set_emit_pace | query_emit_pace |
| box name | set_name / clear_name | Version::name |
allow_imperfect_clones
Clone an over-capacity device anywayfn allow_imperfect_clones(&self, allow: bool) -> Result<()>
Fire-and-forget
By default the box refuses a device it can't clone faithfully. true clones an over-capacity device anyway, the rest faithful and the over-capacity interface dead. Changing the setting while such a device is attached reboots the box to re-clone; a normal device is unaffected.
| Parameter | Type | Description |
|---|---|---|
allow | bool | Clone an over-capacity device anyway, or stay faithful-only. |
use medius::Device;
let device = Device::find()?;
device.allow_imperfect_clones(true)?; // reboots + re-clones if an over-capacity device is attachedset_movement_riding
Inject motion only on a native movefn set_movement_riding(&self, window: Option<Duration>) -> Result<()>
Fire-and-forget
Some(window) turns riding on: injected cursor and wheel motion ride a native cursor-motion report seen within window; the box emits no synthetic motion frame. Motion unridden past the window is dropped, not dumped on the next move. None (the default) is off.
The window rounds to whole milliseconds, a non-zero Some is at least 1 ms, and it clamps to 65535 ms.
Pure idle injection, moving the cursor while the user holds still, stops working while riding is on, unless a move opts out with move_rel_now. Button, key and media injection are unaffected.
A change to this setting drops whatever motion was held for a ride, and clears the standing bearing with it, so every With / Against scale stops applying at that instant.
| Parameter | Type | Description |
|---|---|---|
window | Option<Duration> | Some with the ride window, or None to turn it off. |
use std::time::Duration;
use medius::Device;
let device = Device::find()?;
device.set_movement_riding(Some(Duration::from_millis(20)))?; // ride native moves
device.set_movement_riding(None)?; // back to gapless fillset_bearing
What With and Against are measured againstfn set_bearing(&self, window: Option<Duration>, mode: BearingMode) -> Result<()>
Fire-and-forget
Sets the bearing, the direction the box is injecting, which Direction::With and Direction::Against weigh against in scale. Each axis holds the direction of its last injected delta for window past the last one still owed, then has none and both relative directions stop applying.
| Parameter | Type | Description |
|---|---|---|
window | Option<Duration> | Some with the hold window, or None to turn the bearing off, leaving the relative directions inert whatever their scale. |
mode | BearingMode | PerAxis or Vector. |
Persisted in NVS, so a box that has been set boots at its own value. BEARING_WINDOW_DEFAULT (20 ms) in PerAxis is the factory one.
Vector weighs a report twice, and the second pass reads whatever the projection left standing on each axis, not what the device reported. Block Y negative while the injection runs diagonally and a purely horizontal flick can come out with its vertical share removed.
A change to either field drops the standing bearing and the box's banked fractions, which is a visible step while a relative scale is live.
use std::time::Duration;
use medius::{Axis, BearingMode, Device, Direction};
let device = Device::find()?;
device.set_bearing(Some(Duration::from_millis(20)), BearingMode::PerAxis)?;
device.scale(Axis::X, Direction::Against, 40)?; // motion opposing the injection, at 40%
device.set_bearing(None, BearingMode::PerAxis)?; // and off againset_emit_pace
Pick what paces injected motion, and what rate the clone runs atfn set_emit_pace(&self, pace: EmitPace, force_hz: Option<u16>) -> Result<()>
Fire-and-forget
Picks the emit-rate ceiling for injected motion. EmitPace::Learned (the default) paces injection to the rate the real mouse reports at. EmitPace::Interval paces to the cloned mouse's declared poll rate (itsbInterval). EmitPace::Fixed(hz) paces to a rate you set.
The 1 ms frame clock snaps a fixed rate to 1000/n Hz and caps it at 1 kHz. The pace raises the ceiling only: idle stays idle, and the box emits a frame solely when injection is pending.
force_hz writes one bInterval onto every HID interrupt-IN endpoint of the descriptor the clone serves, and polls the real device at that same interval, so a mouse that declares 125 Hz while able to deliver 1 kHz is not held to what it declared. It snaps to 1000/n Hz and floors at 10 ms on a low-speed clone (100 Hz).
A forced rate applies only with allow_imperfect_clones on, because the descriptor stops matching the real device. Changing the resolved interval re-clones the box, which drops the control port for a few seconds.
| Parameter | Type | Description |
|---|---|---|
pace | EmitPace | Learned, Interval, or Fixed(hz). |
force_hz | Option<u16> | The rate the clone advertises and the box polls the device at; None leaves the device's own. |
The two are independent, and both ride one command, so every call writes both.
use medius::{Device, EmitPace};
let device = Device::find()?;
device.set_emit_pace(EmitPace::Fixed(1000), None)?; // emit at a fixed 1 kHz
device.allow_imperfect_clones(true)?;
device.set_emit_pace(EmitPace::Learned, Some(1000))?; // a 1 kHz clone, human-paced injection
device.set_emit_pace(EmitPace::Learned, None)?; // back to the defaultsset_name
Give the box a human-readable namefn set_name(&self, name: &str) -> Result<()>
Fire-and-forget
Sets the box's name, the readable partner to its MAC. The firmware keeps the leading printable-ASCII run, capped at 32 bytes; an empty string clears it. Read it back off Version::name, not a query.
| Parameter | Type | Description |
|---|---|---|
name | &str | The new name, 1 to 32 printable ASCII characters. |
use medius::Device;
let device = Device::find()?;
device.set_name("Loki")?; // stored in NVS, reported on Version
let name = device.query_version()?.name; // read it back off Versionclear_name
Back to the synthesized defaultfn clear_name(&self) -> Result<()>
Fire-and-forget
Clears the custom name, reverting the box to a firmware-synthesized Medius-XXXX default derived from its MAC.
use medius::Device;
let device = Device::find()?;
device.clear_name()?; // back to "Medius-XXXX"query_imperfect
Read the imperfect-clone statefn query_imperfect(&self) -> Result<ImperfectStatus>
Blocks
Returns an ImperfectStatus: the opt-in toggle, whether the attached device is over-capacity, and whether the live clone went over-capacity anyway with one interface dead.
use medius::Device;
let device = Device::find()?;
let status = device.query_imperfect()?;
if status.over_capacity && !status.allowed {
// the device was refused; opt in to clone it imperfectly
device.allow_imperfect_clones(true)?;
}query_movement_riding
Read the ride windowfn query_movement_riding(&self) -> Result<Option<Duration>>
Blocks
Returns the current ride window as a Duration, or None when movement riding is off.
use medius::Device;
let device = Device::find()?;
match device.query_movement_riding()? {
Some(window) => println!("riding, window {window:?}"),
None => println!("off"),
}query_bearing
Read the bearing window and geometryfn query_bearing(&self) -> Result<Bearing>
Blocks
Returns the configured Bearing: the window and how the box reads it.
use medius::Device;
let device = Device::find()?;
let bearing = device.query_bearing()?;
if bearing.is_live() {
println!("{:?} over {:?}", bearing.mode, bearing.window);
}query_emit_pace
Read the pacing mode and the rate the clone runs atfn query_emit_pace(&self) -> Result<EmitPaceStatus>
Blocks
Returns an EmitPaceStatus. advertised_hz is what the clone advertises now: the device's own rate while nothing is forced, the forced rate once something is. The reply carries no record of what the device declared before a force was applied.
use medius::{Device, EmitPace};
let device = Device::find()?;
let status = device.query_emit_pace()?;
if let EmitPace::Fixed(hz) = status.mode {
println!("fixed {hz} Hz, emitting at {} Hz", status.resolved_hz);
}
println!("the clone advertises {} Hz", status.advertised_hz);On AsyncDevice
setters fire, queries awaitAsyncDevice keeps the setters fire-and-forget (no await) and makes query_imperfect, query_movement_riding, query_bearing, and query_emit_pace futures, like the other queries.
use std::time::Duration;
use medius::Device;
let device = Device::find()?.into_async();
device.set_movement_riding(Some(Duration::from_millis(20)))?; // sync, no await
let window = device.query_movement_riding().await?; // awaits