Medius - Rust LibraryRequests

Requests

Asking the box a question and waiting for the answer

Unlike 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-trip
fn query_version(&self) -> Result<Version>

Blocks

Returns a Version. The box's name rides on it, in the name field.

EXAMPLE
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);           // Loki

Device::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 live
fn 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.

EXAMPLE
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 clone
fn 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.

EXAMPLE
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 device
fn 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.

EXAMPLE
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 rate
fn 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.

EXAMPLE
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 counters
fn 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.

EXAMPLE
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 locks
fn 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.

EXAMPLE
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 subscription
fn 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.

EXAMPLE
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 state
fn 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).

EXAMPLE
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 back
fn 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.

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

Blocks

cargo add medius --features async

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

EXAMPLE
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);