Requests
Unlike the fire-and-forget calls, queries block. Each sends one QUERY frame and waits for one RESP frame back. They are query_version, query_health, device_info, caps, query_rate, query_stats, query_locks, query_catch, and firmware_info, plus query_status and query_config on the clip handle.
query_version Firmware identity, round-trip
fn query_version(&self) -> Result<Version>
Blocks
Returns a Version. The box's name is its name field.
use medius::Device;
let device = Device::find()?; // or Device::open("/dev/ttyACM0")?
let v = device.query_version()?;
println!("{v}"); // fw 3.4.6
println!("proto {}", v.proto_ver); // proto 9
println!("name {}", v.name); // LokiDevice::find() already runs a version query in the handshake. query_version re-reads it.
query_health Status bits of the device-box-PC path
fn query_health(&self) -> Result<Health>
Blocks
Returns a Health, eleven booleans from one u16 flags word. link_up, mouse_attached, and clone_configured must all be true for injected input to be emitted.
use medius::Device;
let device = Device::find()?;
let h = device.query_health()?;
if h.link_up && h.mouse_attached && h.clone_configured {
// chain is live, safe to inject
} else {
eprintln!("not ready: {h:?}");
}device_info Clone's USB identity, kind, product
fn device_info(&self) -> Result<DeviceInfo>
Blocks
Returns a DeviceInfo: the vid, pid, USB version, a DeviceKind, and the product string the box read from the real device. With nothing cloned, every field is zero or empty. Display prints VVVV:PPPP product.
use medius::{Device, DeviceKind};
let device = Device::find()?;
let d = device.device_info()?;
if d.vid == 0 {
eprintln!("nothing cloned yet");
} else {
println!("{d}"); // 046D:C08B G502
println!("usb {:#06x}", d.bcd_usb);
println!("kind={} serial={} bos={}", d.kind, d.has_serial, d.has_bos);
if d.kind == DeviceKind::Mouse {
// the clone is a mouse
}
}capsFeature-detect the clone
fn caps(&self) -> Result<Caps>
Blocks
Returns a Caps: a mouse half, a keyboard half, and the per-class change-driven flags. An absent class reads all-zero. has_mouse() and has_keyboard() say which are bound. An inject for a usage the clone lacks is dropped with no error, so check here first.
use medius::Device;
let device = Device::find()?;
let caps = device.caps()?;
println!("{} buttons", caps.mouse.n_buttons);
if caps.mouse.has_wheel {
device.wheel(1)?;
}
if caps.has_keyboard() && caps.keyboard.has_consumer {
device.press(medius::MediaKey::MUTE)?;
}query_rate Live native report rate
fn query_rate(&self) -> Result<Rate>
Blocks
Returns a Rate. native_hz() converts the period to hertz. It returns None while native_period_us is 0 (not learned yet). confident is true once the estimator window is full.
use medius::Device;
let device = Device::find()?;
let r = device.query_rate()?;
match r.native_hz() {
Some(hz) if r.confident => println!("{hz:.0} Hz"),
Some(hz) => println!("{hz:.0} Hz (still settling)"),
None => println!("rate not learned yet"),
}query_stats Delivery counters
fn query_stats(&self) -> Result<Stats>
Blocks
Returns a Stats. Nonzero tx_drops or tx_wedges means native input was lost on the way to the PC. Nonzero link_rx_drops or host_rx_drops means it was lost between the box's two chips.
relay_drops counts relayed traffic and commands that went no further (none of it native input). The narrowed counters saturate instead of wrapping. link_rx_drops, host_rx_drops and relay_drops are full width. session moves each time the box releases some or all of what a host set. The library watches it for session recovery.
use medius::Device;
let device = Device::find()?;
let s = device.query_stats()?;
println!("{} emits", s.inject_emits);
if s.tx_drops > 0 || s.tx_wedges > 0 {
eprintln!("delivery degraded: {} drops, {} wedges", s.tx_drops, s.tx_wedges);
}
if s.link_rx_drops > 0 || s.host_rx_drops > 0 {
eprintln!("input lost on the inter-chip link: {} into the device chip, {} into the host chip",
s.link_rx_drops, s.host_rx_drops);
}
if s.relay_drops > 0 {
println!("{} relayed packets shed under load", s.relay_drops);
}query_locks Active input scales
fn query_locks(&self) -> Result<Locks>
Blocks
Returns a Locks: every direction scale currently weighs. scale_of(target, direction) reads the percentage in effect, and is_locked(target, direction) reports a full block. Locks lists what a blanket, a media usage, and a vector-mode relative direction report.
use medius::{Device, Axis, Direction};
let device = Device::find()?;
let locks = device.query_locks()?;
if locks.is_locked(Axis::X, Direction::Both) {
println!("horizontal motion is blocked");
}
println!("opposing the injection at {}%", locks.scale_of(Axis::X, Direction::Against));query_catch Active catch subscription
fn query_catch(&self) -> Result<CatchState>
Blocks
Returns a CatchState: the live subscription table as a list of CatchEntry, the table_full flag, the box-wide dropped count, and a ClockEstimate relating the two chips' timers.
catch_events gets no reply, so the only way to confirm each filter was accepted is to compare the entries (the class / id / direction / capture the box accepted) with the filters sent.
A missing filter was refused. table_full says whether the cause was a full 32-entry table or a malformed filter.
A lost event counts against every entry it matched. Drops on an entry mean the subscription is too broad for the link.
use medius::{Capture, CatchFilter, Class, Device, TrafficClass};
let device = Device::find()?;
// Bind the stream: dropping it unsubscribes, emptying the table.
let _events = device.catch_events([
CatchFilter::watch_class(Class::Key),
CatchFilter::traffic(TrafficClass::VendorBulk, 0x02).with_capture(Capture::First(16)),
])?;
let c = device.query_catch()?;
if c.table_full {
eprintln!("the 32-entry table is full: some filters were refused");
}
for e in &c.entries {
let f = e.filter;
println!("{:?} {:?} capture={:?} dropped={}", f.class(), f.id(), f.capture(), e.dropped);
}
println!("{} dropped box-wide", c.dropped);
if let Some(age) = c.clock.age {
println!("clocks differ by {} us (+/- {}, {age:?} old)", c.clock.offset_us, c.clock.delay_us / 2);
}query_status (clip) Clip ring depth, progress, playback state
fn query_status(&self) -> Result<ClipStatus>
Blocks
On the ClipHandle from device.clip(), not Device. Returns a ClipStatus: state (including Faulted), ring free, retained played/total, the playback counters, and the held usages. Backs QUERY(CLIP).
use medius::Device;
let device = Device::find()?;
let clip = device.clip();
let s = clip.query_status()?;
if s.state == medius::ClipState::Faulted { clip.clear()?; }
println!("{} free, {} played", s.free, s.played);query_config (clip) Clip config readback
fn query_config(&self) -> Result<ClipSettings>
Blocks
Also on ClipHandle. Reads the config view of the same QUERY(CLIP) frame as query_status. Returns a ClipSettings: auto-lock, loop, retain, ride, the finalized flag, and both trigger sets (each packet trigger with its hits). Every setting round-trips.
use medius::Device;
let device = Device::find()?;
let cfg = device.clip().query_config()?;
println!("{} input triggers, {} packet triggers, loop={}",
cfg.triggers.len(), cfg.packet_triggers.len(), cfg.loop_);firmware_info Both chips' versions, slots, staged images
fn firmware_info(&self) -> Result<FirmwareInfo>
Blocks
Returns a FirmwareInfo (backs QUERY(FIRMWARE)). The other firmware calls are on Update.
It's the only call that reports the host chip's version. query_version reports the device chip alone.
let fw = device.firmware_info()?;
println!("device {}", fw.device);
match fw.host {
Some(h) => println!("host {h}"),
None => println!("host chip not replying"),
}Async queries Same queries on AsyncDevice
async fn query_version(&self) -> Result<Version>
async fn query_health(&self) -> Result<Health>
async fn device_info(&self) -> Result<DeviceInfo>
async fn caps(&self) -> Result<Caps>
async fn query_rate(&self) -> Result<Rate>
async fn query_stats(&self) -> Result<Stats>
async fn query_locks(&self) -> Result<Locks>
async fn query_catch(&self) -> Result<CatchState>
async fn query_imperfect(&self) -> Result<ImperfectStatus>
async fn query_movement_riding(&self) -> Result<Option<Duration>>
async fn query_bearing(&self) -> Result<Bearing>
async fn query_emit_pace(&self) -> Result<EmitPaceStatus>
async fn query_status(&self) -> Result<ClipStatus>
async fn query_config(&self) -> Result<ClipSettings>
async fn firmware_info(&self) -> Result<FirmwareInfo>
Blocks
cargo add medius --features asyncWith the async feature, Device::into_async() yields an AsyncDevice whose queries are futures. Other methods stay synchronous. Any executor drives them (no tokio needed), such as futures::executor::block_on.
use futures::executor::block_on;
use medius::Device;
let device = Device::find()?.into_async();
let v = block_on(device.query_version())?;
let h = block_on(device.query_health())?;
println!("{v} link_up={}", h.link_up);