query_versionFirmware identity, round-trip

fn query_version(&self) -> Result<Version>

Blocks

Returns a Version. The box's name is its name field.

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

Device::find() already runs a version query in the handshake. query_version re-reads it.

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

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_infoClone'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.

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

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.

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

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

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);
}
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_locksActive 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.

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

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

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

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

EXAMPLE
let fw = device.firmware_info()?;
println!("device {}", fw.device);
match fw.host {
    Some(h) => println!("host {h}"),
    None => println!("host chip not replying"),
}

Async queriesSame 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 async

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

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