Requests
Asking the box a question and waiting for the answerUnlike fire-and-forget, the queries are blocking: a question frame out, one answer frame back. They are query_version, query_health, device_info, caps, query_rate, query_stats, query_locks, query_catch, each covered below. The clip handle adds query_status and query_config, also here.
query_version
Firmware identity, round-tripfn query_version(&self) -> Result<Version>
Blocks
Returns a Version. The box's name rides on it, in the name field.
use medius::Device;
let device = Device::find()?; // or Device::open("/dev/ttyACM0")?
let v = device.query_version()?;
println!("{v}"); // fw 3.0.0
println!("proto {}", v.proto_ver); // proto 3
println!("name {}", v.name); // LokiDevice::find() already runs a version query during the handshake; calling query_version again just re-reads it.
query_health
Is the mouse-to-box-to-PC chain livefn query_health(&self) -> Result<Health>
Blocks
Returns a Health, eight booleans from one status byte. link_up, mouse_attached, and clone_configured must all be true before injection has anywhere to land.
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
USB identity, kind, and product of the clonefn device_info(&self) -> Result<DeviceInfo>
Blocks
Returns a DeviceInfo: the vid, pid, USB version, a DeviceKind, and the product string the box read off the real device. The clone sits on the game PC's bus, so this is the only way to see it from the control link. Every field is zero/empty when nothing is cloned. 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
}
}caps
Feature-detect the whole devicefn caps(&self) -> Result<Caps>
Blocks
One query describes the whole cloned device. Returns a Caps with a mouse half and a keyboard half, plus the per-class change-driven flags. Use it for feature detection: an inject for a usage the device lacks is a silent no-op, so the counts tell you what is real. A class that is not present reads all-zero; has_mouse() and has_keyboard() say which are bound.
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
Read the live native report ratefn query_rate(&self) -> Result<Rate>
Blocks
Returns a Rate. native_hz() converts the period to a frequency, returning None while native_period_us is still 0 (not learned yet). confident is true once the estimator window is full and the value is trustworthy.
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
Read the delivery countersfn query_stats(&self) -> Result<Stats>
Blocks
Returns a Stats. inject_emits counts pure-injection reports emitted; a nonzero tx_drops or tx_wedges is the signal that delivery degraded under load. The narrowed counters saturate, so a maxed field clamps instead of wrapping.
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);
}query_locks
Read the active input locksfn query_locks(&self) -> Result<Locks>
Blocks
Returns a Locks, the list of inputs currently blocked by lock. entries() walks them and is_locked(target, direction) answers whether one particular lock is set. Read it to confirm a lock landed, or to mirror the box's lock state in a UI.
use medius::{Device, Axis, LockDirection};
let device = Device::find()?;
let locks = device.query_locks()?;
if locks.is_locked(Axis::X, LockDirection::Both) {
println!("horizontal motion is frozen");
}query_catch
Read the active catch subscriptionfn query_catch(&self) -> Result<CatchState>
Blocks
Returns a CatchState: the mask currently streaming via catch_events, plus dropped, the box-side count of events shed under back-pressure. Read it after subscribing to confirm the mask took, or to reflect the live catch mask in your own UI.
use medius::Device;
let device = Device::find()?;
let c = device.query_catch()?;
if !c.mask.is_empty() {
println!("catching {:?}, {} dropped", c.mask, c.dropped);
}query_status (clip)
Read the buffered-clip ring depth, progress, and playback statefn query_status(&self) -> Result<ClipStatus>
Blocks
On the ClipHandle from device.clip(), not Device itself. Returns a ClipStatus: state, ring free, retained played/total, the drain counters, and the held usages. Pace clip top-ups off free, and watch state for a Faulted re-sync or for playback reaching Idle. 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)
Read the whole clip config backfn query_config(&self) -> Result<ClipSettings>
Blocks
The config view of the same QUERY(CLIP) frame query_status reads, also on the ClipHandle. Returns a ClipSettings with the auto-lock, loop, retain, finalized flag, and triggers you set; nothing is write-only, every setting round-trips.
use medius::Device;
let device = Device::find()?;
let cfg = device.clip().query_config()?;
println!("{} triggers, loop={}", cfg.triggers.len(), cfg.loop_);Async queries
The same queries on AsyncDeviceasync 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>
Blocks
cargo add medius --features asyncWith the async feature, Device::into_async() yields an AsyncDevice whose queries are futures; other methods stay synchronous. The crate is runtime-agnostic (no tokio), so drive a future with anything, 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);