Medius - Rust LibraryStructs

Structs

Values the box reports back

Plain value types you get back from queries and discovery. Their fields are public.

Version

Firmware identity and box id

Firmware identity from query_version(). Display prints fw M.m.p and omits proto_ver; read it from the field.

FieldTypeMeaning
proto_veru8Wire-protocol version the firmware speaks (5 here).
fw_majoru8Firmware major version.
fw_minoru8Firmware minor version.
fw_patchu8Firmware patch version.
mac[u8; 6]The device chip's base MAC, a stable per-box id.
nameStringThe box's human-readable name (a synthesized default when unset), set via set_name.
MethodReturnsMeaning
mac_hex()StringThe MAC as 12 lowercase hex digits, the id used by open_by_id.
EXAMPLE
use medius::Version;

let v = Version { proto_ver: 5, fw_major: 3, fw_minor: 2, fw_patch: 0, mac: [0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc], name: "Loki".into() };
assert_eq!(v.to_string(), "fw 3.2.0"); // Display omits proto_ver
assert_eq!(v.mac_hex(), "123456789abc");
println!("{v} (protocol {}, box {}, name {})", v.proto_ver, v.mac_hex(), v.name);

Health

Box readiness flags

Box readiness from query_health(), one bool per bit. from_flags(u8) and to_flags() convert the byte.

FieldTypeTrue when
link_upboolThe link to the host chip is up.
mouse_attachedboolA real mouse is plugged in.
clone_configuredboolThe PC has set up the cloned mouse.
injection_activeboolThe box is holding at least one injected button or move.
rate_confidentboolThe native-rate estimator window is full, so Rate is trustworthy.
lock_onboolAt least one input is off a full pass, whether locked or merely scaled.
catch_onboolThe catch table holds at least one CatchFilter, whatever class it addresses.
kbd_attachedboolA keyboard is attached on the host chip, cloned and injectable.
EXAMPLE
use medius::Health;

let h = Health::from_flags(0b0000_0011); // link_up | mouse_attached
assert!(h.link_up && h.mouse_attached);
assert!(!h.clone_configured);
assert_eq!(h.to_flags(), 0b0000_0011); // round-trips to the same byte

DeviceInfo

The cloned device's USB identity, kind, and product

USB identity from device_info(). Every field is zero/empty when nothing is cloned. Display prints VVVV:PPPP product.

FieldTypeMeaning
vidu16USB vendor id (idVendor).
pidu16USB product id (idProduct).
bcd_deviceu16Device release (bcdDevice).
bcd_usbu16USB version (bcdUSB), e.g. 0x0200.
has_serialboolThe clone serves a serial string.
has_bosboolThe clone serves a BOS descriptor.
kindDeviceKindThe device's primary kind, from its Boot-interface protocol.
productStringThe product string the device serves (empty when it serves none).
EXAMPLE
use medius::{DeviceInfo, DeviceKind};

let d = DeviceInfo {
    vid: 0x046D, pid: 0xC08B, bcd_device: 0, bcd_usb: 0x0201,
    has_serial: true, has_bos: true, kind: DeviceKind::Mouse, product: "G502".into(),
};
assert_eq!(d.to_string(), "046D:C08B G502"); // Display is VVVV:PPPP product

Caps

The whole device, mouse and keyboard

Everything one caps() query returns. has_mouse() / has_keyboard() tell you which are bound; is_composite() is true when the device has more than one HID interface.

FieldTypeMeaning
mouseMouseCapsThe mouse half (all-zero when no mouse is bound).
keyboardKbdCapsThe keyboard half (all-zero when no keyboard is bound).
mouse_change_drivenboolAlways false: mouse motion is continuous, so its Rate has a learned cadence.
kbd_change_drivenboolTrue when a keyboard is bound: it reports only on a key change, so its rate has no continuous cadence.
EXAMPLE
let caps = device.caps()?;
if caps.has_keyboard() && caps.keyboard.has_consumer {
    // media injection is real on this board
}
println!("{} mouse buttons", caps.mouse.n_buttons);

MouseCaps

What the cloned mouse can do

Semantic capabilities from caps(). Every field is zero when no relative-axis mouse interface is bound. is_composite() is true when n_hid > 1.

FieldTypeMeaning
n_buttonsu8Buttons the mouse report carries.
has_xboolThe report carries an X axis.
has_yboolThe report carries a Y axis.
has_wheelboolThe report carries a wheel.
has_report_idboolThe mouse report sits behind a HID report ID.
n_hidu8Cloned HID interfaces; >1 = composite.
EXAMPLE
use medius::MouseCaps;

let c = MouseCaps { n_buttons: 5, has_x: true, has_y: true, has_wheel: true, has_report_id: false, n_hid: 1 };
assert!(!c.is_composite()); // single HID interface

Rate

The native report rate the box tracks

Live rate from query_rate(). native_hz() converts the period to a frequency, returning None while native_period_us is still 0. On a change-driven input, poll_period_us is the only rate there is.

FieldTypeMeaning
native_period_usu16Realised native report period in µs; 0 = not learned, or change-driven.
poll_period_usu16Cloned inject-endpoint poll period in µs.
confidentboolThe estimator window is full and the value is trustworthy.
change_drivenboolThe active input is event-driven (keyboard / media), so there is no continuous cadence.
EXAMPLE
use medius::Rate;

let r = Rate { native_period_us: 1000, poll_period_us: 1000, confident: true, change_driven: false };
assert_eq!(r.native_hz(), Some(1000.0));

Stats

Delivery and telemetry counters

Delivery counters from query_stats(). A nonzero tx_drops or tx_wedges means delivery degraded under load. The narrowed fields saturate instead of wrapping.

FieldTypeMeaning
inject_emitsu32Pure-injection reports emitted.
tx_dropsu16Reports dropped on TX-queue overflow (should stay 0).
tx_mergesu16Backed-up reports merged instead of queued.
tx_maxdepthu8Deepest the TX queue has reached.
tx_wedgesu8Wedged-endpoint recoveries.
wakeupsu16Remote-wakeups issued.
reset_countu16USB bus resets seen.
config_countu16SET_CONFIGURATION events (re-enumerations).

Locks

The active input scales

The active set from query_locks(), a list of LockEntry across every class, one per direction that is not passing untouched. See the native LOCKS reply for the wire format.

MethodReturnsMeaning
entries()&[LockEntry]Every weighed direction, one entry each, across specific targets and whole-class blankets.
scale_of(target, dir)u8Percent of the physical value kept there; 100 when nothing weighs it, and where entries overlap it reports the lowest. Both reports the lowest across every direction, which is not the figure a delta meets: it picks up one from each pair, multiplied.
is_locked(target, dir)boolWhether it is blocked outright. A direction merely weighed is not locked. Both asks about the two fixed signs; ask for a relative one by name.
from_entries(Vec<LockEntry>)LocksBuild one from entries, for tests and the MockBox.
READBACK
CaseWhat the list holds
A blanket key lockOne entry per blocked edge, never Both.
A media lock, blanket or specificDirection Both, always. Media has no edges.
A relative direction under BearingMode::VectorThe effective scale, the lower of X's and Y's, on both axes.
96 entries reachedThe rest is absent, with nothing marking it. See the native LOCKS budget.
EXAMPLE
use medius::{Axis, Button, Direction};

let locks = device.query_locks()?;
if locks.is_locked(Axis::X, Direction::Positive) {
    // physical +X is zeroed
}
if locks.is_locked(Button::Left, Direction::Negative) {
    // a left-click is latched down: the release edge is blocked
}
// how much of a delta opposing the injection survives
println!("{}%", locks.scale_of(Axis::X, Direction::Against));

LockEntry

One entry in a Locks list
struct LockEntry { scope: LockScope, direction: Direction, scale: u8 }

One weighed direction in a Locks list. Entries mirror the LOCK frame field for field, so what comes back is what you would send to reproduce it.

FieldTypeMeaning
scopeLockScopeA specific axis or usage, or a whole-class blanket.
directionDirectionWhich direction of it this entry weighs.
scaleu8Percent of the physical value kept. A momentary usage carries one bit, so the box stores the block or pass it renders and this never reads between them.

is_block() is scale == 0: blocked outright rather than weighed.

Bearing

What With and Against are measured against
struct Bearing { window: Option<Duration>, mode: BearingMode }

The configured bearing from query_bearing(). See the native bearing for what it does.

FieldTypeMeaning
windowOption<Duration>How long an axis holds the direction of its last injected delta. None is off, so With and Against are inert whatever their scale.
modeBearingModePerAxis: each axis reads its own sign. Vector: the physical delta is projected onto the injected direction, and the relative scale weighs only the part along it.

is_live() is whether a bearing is held at all.

CatchFilter

One subscription entry: what to catch, and how much of it
struct CatchFilter { /* private */ }

One entry in the table you hand to catch_events or input_events. Built with a constructor rather than by hand.

ConstructorAddresses
CatchFilter::watch(usage)One Usage: a button, a key, or a media usage, the same argument lock takes.
CatchFilter::watch_axis(axis)One Axis.
CatchFilter::watch_class(class)Every usage in one Class.
CatchFilter::watch_axes()Every axis.
CatchFilter::all_input()All four input classes, as a [CatchFilter; 4].
CatchFilter::traffic(class, id)One id in a TrafficClass: an endpoint, an interface, an endpoint number.
CatchFilter::traffic_class(class)Every id in one traffic class, a blanket.
CatchFilter::everything()Every class, every id, both directions.
MODIFIERS AND ACCESSORS
MethodReturnsMeaning
.on_press() / .on_release()CatchFilterOne edge, on the momentary classes.
.inbound() / .outbound()CatchFilterOne flow, on the traffic classes: IN is device to PC.
.with_direction(dir)CatchFilterThe Direction directly; defaults to Both.
.with_capture(cap)CatchFilterA Capture; defaults to Whole. Traffic classes only.
.class()Option<CatchClass>The class, or None for the wildcard.
.id()Option<u16>The class-specific id, or None for a blanket.
.direction() / .capture()Direction / CaptureWhat the filter was narrowed to.
.same_address(other)boolWhether both name the same box table entry, whatever their captures.
MATCHING IS MOST-SPECIFIC-FIRST

An exact (class, id) is matched before a class blanket, that before the wildcard, and a named direction before Both. That entry supplies the capture:

catch_events([
    CatchFilter::everything().with_capture(Capture::First(16)),   // everything, 16 bytes
    CatchFilter::traffic(TrafficClass::VendorInterrupt, 0x83),    // except 0x83, in full
])

  packet on 0x83  ->  traffic(VendorInterrupt, 0x83)  resolves  ->  whole packet
  packet on 0x84  ->  everything()                    resolves  ->  First(16)

Capture is not part of a filter's address, so two filters naming one entry at different lengths are one box entry at the wider of the two. same_address is that comparison; == compares everything.

CAPACITY AND REFUSALS

The table holds 32 entries, and a subscription that would exceed it is refused before anything is sent. See Error. So is an empty subscription, and a capture on an input class.

EXAMPLE
use medius::{CatchFilter, Class, TrafficClass};

// Press edges of every key, plus one control endpoint, plus bus context.
let stream = device.catch_events([
    CatchFilter::watch_class(Class::Key).on_press(),
    CatchFilter::traffic(TrafficClass::Control, 0),
    CatchFilter::traffic_class(TrafficClass::Bus),
])?;

// Dropping the stream clears the whole table.
drop(stream);

MotionEvent

One physical relative-axis event

The payload of a CatchEvent::Motion, read off an EventStream. The real hand motion at the merge point, before lock suppression or injection, so a locked or injected axis still reports the true delta.

FieldTypeMeaning
ts_usu32When the device's report arrived, in box microseconds. See Catch timestamps for what the clock means.
clockClockDomainWhich chip's timer ts_us came from. Always HostChip here: physical motion is stamped on the host chip as the real device's transfer completes.
dxi16X movement this report (right positive).
dyi16Y movement this report (down positive).
dzi16Wheel movement this report (up positive).
EXAMPLE
use medius::{CatchEvent, CatchFilter};

let stream = device.catch_events([CatchFilter::watch_axes()])?;
if let CatchEvent::Motion(m) = stream.recv()? {
    println!("at {} us ({:?}): moved {} {}, wheel {}", m.ts_us, m.clock, m.dx, m.dy, m.dz);
}

UsageSnapshot

One physical held-usage snapshot

The payload of a CatchEvent::Usages: every held Usage of one class (buttons, keys, or media, all one shape), captured before injection. Diff successive snapshots for press/release edges, or test one with is_held; a dropped frame self-corrects on the next.

FieldTypeMeaning
ts_usu32When the device's report arrived, in box microseconds. See Catch timestamps for what the clock means.
clockClockDomainWhich chip's timer ts_us came from. Always HostChip: a held-usage snapshot is taken where the real device's report lands.
usagesVec<Usage>The currently-held usages, all of one class per event.
MethodReturnsMeaning
is_held(usage)boolWhether usage is held; takes any impl Into<Usage>.
class()Option<Class>The class of this snapshot, from its first usage, or None when empty.
EXAMPLE
use medius::{Button, CatchEvent, CatchFilter, Class};

let stream = device.catch_events([CatchFilter::watch_class(Class::Button)])?;
if let CatchEvent::Usages(s) = stream.recv()? {
    if s.is_held(Button::Left) {
        println!("left button held");
    }
}

InputEvent

One decoded input edge, and when it happened
struct InputEvent { ts_us: u32, clock: ClockDomain, input: Input }

What input_events yields. The Input is the edge, decoded from the held-usage snapshots the box sends.

FieldTypeMeaning
ts_usu32The report's arrival stamp, in the stamping chip's microseconds.
clockClockDomainAlways HostChip for physical input.
inputInputWhat happened.
EXAMPLE
use medius::{CatchFilter, Input};

for ev in device.input_events(CatchFilter::all_input())? {
    if let Input::Press(u) = ev.input {
        println!("{u:?} down at {}", ev.ts_us);
    }
}

TrafficEvent

Bytes off one pipe, with what was cut

The payload of a CatchEvent::Traffic: one packet, one completed control transaction, or one bus event.

FieldTypeMeaning
ts_usu32When the transfer completed, in the microseconds of the chip named by clock.
clockClockDomainWhich chip stamped it. Varies by class and direction here, unlike the two input events.
classCatchClassWhich address space the event came from.
idu16The endpoint address, endpoint number, or interface number inside that class.
directionDirectionPositive = IN (device to PC), Negative = OUT (PC to device).
flagsu8Class-specific, see below; 0 for the classes that define none.
true_lenu16The packet's length before the Capture cut it.
bytesVec<u8>What was actually captured, at most the entry's capture length.
MethodReturnsMeaning
truncated()boolWhether the capture or the frame ceiling cut this packet: bytes.len() < true_len.

One event frame carries at most 180 bytes, so Capture::Whole still truncates a longer packet, and still says so.

FLAGS BY CLASS
Classflags
VendorBulkb0 = end of transfer, b1 = zero-length packet.
ControlHow the proxied transfer ended: 0 completed, 0xFD the device STALLed, 0xFE it NAKed until the transfer timed out.
BusThe BusEvent kind.
everything else0.
CONTROL IS PER TRANSACTION

bytes is the 8-byte SETUP packet then the data stage, and direction says which way that data went. Requests answered from the box's descriptor cache still raise events.

EXAMPLE
use medius::{Capture, CatchEvent, CatchFilter, TrafficClass};

let stream = device.catch_events([CatchFilter::traffic(TrafficClass::VendorInterrupt, 0x83)
    .with_capture(Capture::First(16))])?;
if let CatchEvent::Traffic(t) = stream.recv()? {
    println!("ep 0x{:02X} {:?}: {:02X?}", t.id, t.direction, t.bytes);
    if t.truncated() {
        println!("  cut: {} of {} bytes", t.bytes.len(), t.true_len);
    }
}

Key

A HID keyboard keycode

A newtype over a HID keyboard/keypad usage. It converts Into<Usage>, so you pass one straight to inject or press. Modifiers are the usages 0xE0-0xE7.

ItemReturnsMeaning
Key::A .. Key::LEFT_SHIFTKeyNamed consts for common keycodes and modifiers.
new(u8)KeyWrap any raw HID keycode.
usage()u8The raw keycode byte.
EXAMPLE
use medius::Key;

let a = Key::A;            // 0x04
let custom = Key::new(0x04);
assert_eq!(a.usage(), custom.usage());

MediaKey

A 16-bit Consumer usage

A newtype over a 16-bit Consumer usage. It converts Into<Usage>, so you pass one straight to inject or press.

ItemReturnsMeaning
MediaKey::VOLUME_UP .. MediaKey::MUTEMediaKeyNamed consts for common media usages.
new(u16)MediaKeyWrap any raw Consumer usage.
usage()u16The raw Consumer usage.
EXAMPLE
use medius::MediaKey;

let vol_up = MediaKey::VOLUME_UP;   // 0x00E9
let custom = MediaKey::new(0xE9);
assert_eq!(vol_up.usage(), custom.usage());

KbdCaps

What the cloned keyboard can do

Semantic capabilities from caps(). Every field is zero when no keyboard is bound. has_consumer gates media injection.

FieldTypeMeaning
n_keysu8Keycode-array slots, or 0xFF for an NKRO bitmap.
nkroboolThe keyboard reports an NKRO bitmap.
has_consumerboolA Consumer collection is present (media keys injectable).
has_systemboolA system-control collection is present (passthrough-only).
has_report_idboolThe keyboard report sits behind a HID report ID.

CatchState

The live subscription table, read back
struct CatchState { table_full: bool, dropped: u32, clock: ClockEstimate, entries: Vec<CatchEntry> }

What query_catch() returns. Subscribing has no reply of its own, so this is the only view of what the box accepted.

FieldTypeMeaning
table_fullboolAt least one entry was refused because the 32-slot table was full.
droppedu32Box-wide events shed under back-pressure, across every entry.
clockClockEstimateThe measured relationship between the host chip's and device chip's timers.
entriesVec<CatchEntry>The live subscription table, one entry per accepted CatchFilter. Empty = catching nothing.

An entry absent from entries was refused; table_full says the reason was capacity rather than a malformed filter.

EXAMPLE
let c = device.query_catch()?;
if c.entries.is_empty() {
    println!("catching nothing");
}
if c.table_full {
    eprintln!("some filters were refused: the 32-entry table is full");
}
for e in &c.entries {
    println!("{:?} {:?} {:?} {:?} dropped={}",
             e.filter.class(), e.filter.id(), e.filter.direction(), e.filter.capture(), e.dropped);
}
println!("{} dropped box-wide", c.dropped);

CatchEntry

One accepted subscription, and what it lost
struct CatchEntry { filter: CatchFilter, dropped: u16 }

One row of the box's subscription table in a CatchState: the CatchFilter the box accepted, echoed back. A blanket comes back as one entry, not one row per id.

FieldTypeMeaning
filterCatchFilterThe subscription as the box holds it; read it with class(), id(), direction(), capture().
droppedu16Events this entry could not queue.

The box-wide count on CatchState says you are losing events; this one says which. Vendor bulk drops first, by design.

EXAMPLE
let c = device.query_catch()?;
for e in c.entries.iter().filter(|e| e.dropped > 0) {
    eprintln!("{:?} {:?} lost {} events", e.filter.class(), e.filter.id(), e.dropped);
}

ClockEstimate

How the two chips' timers relate
struct ClockEstimate { offset_us: i32, rate_ppb: Option<i32>, delay_us: u16, age: Option<Duration> }

The clock field of a CatchState, and the only thing that puts stamps from both clock domains on one timeline.

The box measures the difference with a four-timestamp exchange across the inter-chip link, stamping each frame as it reaches the wire rather than when it is queued. The two crystals drift by up to 20 µs per second.

FieldTypeMeaning
offset_usi32The host chip's clock minus the device chip's, in microseconds. Add it to a device-domain stamp to read it on the host's timeline, subtract it to go the other way.
rate_ppbOption<i32>How fast the two are drifting apart, in parts per billion, or None when the box has fitted no rate. Not the same as a fitted 0: on a link too busy for enough clean exchanges no fit is made at all, which is when assuming no drift costs the most.
delay_usu16The best round trip measured in the window; the offset is good to about half of it.
ageOption<Duration>How long ago the exchange ran. None = no estimate yet.

error_bound_us() halves delay_us for you; drift_us_over(age) extrapolates rate_ppb, returning 0 for a None rate.

EXAMPLE
let clock = device.query_catch()?.clock;
match clock.age {
    None => println!("no cross-chip estimate yet: compare stamps within one domain only"),
    Some(age) => {
        let offset_now = clock.offset_us as i64 + clock.drift_us_over(age);
        println!("offset {offset_now} us, +/- {} us", clock.error_bound_us());
    }
}

Timeline

Box stamps on this machine's clock
struct Timeline { /* private */ }

A catch stamp is microseconds on a chip that booted before this process did: it wraps every ~71.6 minutes, restarts at zero on reboot, and has no relation to any clock here. Feed every event in as it arrives, in order.

&event is anything implementing Timestamped: an InputEvent, a CatchEvent, or one of the three frame structs. The decoded and raw paths share one timeline.

MethodReturnsMeaning
observe(&event)StampedPlace an event on this machine's clock, taking the arrival as now.
observe_at(&event, now)StampedThe same with the arrival supplied, for replaying a capture.
observe_stamp(ts_us, domain, now)StampedThe same from a stamp and domain held on their own.
box_us(&event)u64The stamp unwrapped past the rollover, monotonic within its domain.
reset(domain)()Forget one domain's rollover count and floor, for a chip that rebooted.
samples(domain)u64Events observed for a domain; the floor is a minimum over these.

Each domain is tracked separately, so both chips' stamps land on one comparable timeline.

The mapping keeps a per-domain minimum of (elapsed here minus elapsed on the box), not an average. It improves as it runs and never steps backwards.

EXAMPLE
use medius::{CatchFilter, Timeline};

let mut input = device.input_events(CatchFilter::all_input())?;
let mut time = Timeline::new();
for ev in input.by_ref().take(20) {
    println!("{:?} at {:?}", ev.input, time.observe(&ev).host);
}

Stamped

One event placed on this machine's clock
struct Stamped { host: Instant, box_us: u64, excess: Duration }
FieldTypeMeaning
hostInstantWhen the event happened, on this machine's monotonic clock.
box_usu64The event's own stamp, unwrapped past the 32-bit rollover.
excessDurationHow much later than the measured floor this event reached you. Jitter, not latency: the constant part of the delay is unknowable from here.

ImperfectStatus

The imperfect-clone state

The imperfect-clone state from query_imperfect().

FieldTypeMeaning
allowedboolThe opt-in toggle; cloning an over-capacity device is allowed.
over_capacityboolThe attached device needs an interrupt-IN endpoint the box can't service.
clone_imperfectboolThe live clone is over-capacity and was cloned anyway, so one interface is dead.

EmitPaceStatus

The emit-rate pacing state and the rate the clone runs at

The emit-rate pacing state from query_emit_pace().

FieldTypeMeaning
modeEmitPaceThe selected mode; Fixed carries the requested rate.
resolved_hzu16The ceiling in effect (Hz); 0 = learnt/adaptive, or no device yet in Interval.
force_hzOption<u16>The forced wire rate requested; None leaves the device's own.
advertised_hzu16What the clone's input endpoints advertise now, forced or native; 0 = no clone.
force_activeboolWhether a forced interval is written into the descriptor being served.

LogLine

One line from the LOG stream

One line from the box's LOG stream, read off a LogStream.

FieldTypeMeaning
levelLogLevelSeverity tag.
textStringThe decoded message.

PortInfo

A discovered serial port

A serial port that looks like a Medius box, from find_medius(). serial is the CH343 adapter's serial string, part of the box identity.

FieldTypeMeaning
pathStringSerial port path.
vidu16USB vendor id (0x1A86).
pidu16USB product id (0x55D3).
serialOption<String>The CH343 adapter's serial string, when it serves one.

CountersSnapshot

Link statistics snapshot

Four running link totals from counters().

FieldTypeMeaning
frames_txu64Frames sent to the box.
frames_rxu64Frames received from the box.
crc_dropsu64Inbound frames dropped on a bad checksum.
reconnectsu64Times the library reopened the port.

LogStream

Receiver for the device LOG stream

Receives the box's LOG frames as LogLine values off a local channel, from device.logs(). No receive method touches the wire, so cloning shares the queue. The methods and an example are on Logs & counters.

ClipSettings

A clip's persistent config, read back

A clip's configuration from ClipHandle::query_config(). You set these with the handle setters (set_autolock, set_loop, set_retain, set_ride, finalize, bind); this is the readback.

FieldTypeMeaning
autolockVec<Blanket>The Blanket groups auto-locked while playing (clip-owned, released on stop); empty = no auto-lock.
loop_boolPlayback restarts from the top instead of stopping at the end.
retainboolThe buffered content survives a stop, so a restart replays it instead of needing a fresh append.
finalizedboolThe clip is sealed: no more appends, ready to replay as a fixed sequence.
rideboolThe clip's motion waits for a real move under movement riding; false (the default) plays it on the box's own clock.
triggersVec<ClipTrigger>The bound input triggers (up to 8), each firing a playback action on a physical edge.
EXAMPLE
let cfg = handle.query_config()?;
if cfg.loop_ && cfg.finalized {
    println!("sealed looping clip, {} triggers", cfg.triggers.len());
}

ClipTrigger

One input binding that drives a clip

One physical-input binding for a clip, handed to ClipHandle::bind. The box keeps up to 8, keyed by usage and edge.

Build one with the constructor, where consume defaults to false:

fn new(on: impl Into<Usage>, edge: Edge, action: ClipAction) -> ClipTrigger

No round-trip

FieldTypeMeaning
onUsageThe button, key, or media usage that fires the trigger.
edgeEdgeWhich edge fires it: Press, Release, or Both.
actionClipActionThe playback action to run (Start, Stop, Toggle, ...).
consumeboolLock the trigger usage while it is active, so its edge never reaches the PC; the .consume() builder sets it true.
EXAMPLE
use medius::{Button, ClipAction, ClipTrigger, Edge};

// Toggle the clip on a Side1 press, and suppress that press.
let trig = ClipTrigger::new(Button::Side1, Edge::Press, ClipAction::Toggle).consume();
handle.bind(trig)?;

ClipStatus

The buffered-clip ring and playback state

The clip ring depth and playback counters from ClipHandle::query_status(). Pace top-ups off free; a ClipState::Faulted state means re-sync (stop, then rebuild).

FieldTypeMeaning
stateClipStateThe lifecycle state (idle / playing / paused / faulted).
freeu32Free bytes in the ring, the headroom for the next append.
totalu32The retained clip size in bytes; while streaming, the buffered-but-undrained bytes.
playedu32Bytes played from the clip start (retained progress; ~0 while streaming).
ticksu32Content frames drained since the last start (gap runs are not counted).
underrunsu16Underrun episodes (the ring ran dry mid-playback).
overrunsu16Appends dropped because the ring was full.
seq_gapsu16Append-sequence gaps seen (a dropped append frame).
heldVec<Usage>The usages the clip is holding down, buttons, keys, and media in one list like a UsageSnapshot; test one with is_held(usage).

ChipFirmware

What one chip is running

One chip's half of firmware_info(). Display renders it as major.minor.patch.

FieldTypeMeaning
major, minor, patchu8The firmware version this chip is running.
slotu8Which app slot it booted: 0 or 1.
stateImageStateWhether that image is confirmed, on probation, or rolled back.

FirmwareInfo

Both chips, and what is staged

Returned by firmware_info(). any_pending() is true while either chip is still on probation, which is when an update is refused.

FieldTypeMeaning
deviceChipFirmwareThe PC-facing chip.
hostOption<ChipFirmware>None when the host chip has not answered over the inter-chip link.
slot_sizeu32Usable bytes in a spare slot; the same on both chips.
device_stagedboolAn image is written and waiting to be activated.
host_stagedboolThe same, for the host chip.

UpdateProgress

One acknowledged window

Handed to the closure passed to stage_firmware(), once per acknowledged window rather than once per chunk.

FieldTypeMeaning
targetUpdateTargetThe chip being written.
sentusizeBytes the box has acknowledged.
totalusizeBytes in the whole image.