Threading and CloneShare one connection across threads

Device is Send + Sync and clones cheaply (an Arc inside). One connection serves any number of threads, and every clone shares the open port.

EXAMPLE
use std::thread;

let worker = device.clone();
let handle = thread::spawn(move || {
    worker.move_rel(10, 0)
});
handle.join().unwrap()?;

Concurrent queriesjoin and cloning across tasks

AsyncDevice clones the same way across tasks. futures::future::join awaits queries together.

EXAMPLE
let (v, h) = futures::future::join(
    device.query_version(),
    device.query_health(),
).await;
let v = v?;
let h = h?;
println!("{v}, link_up={}", h.link_up);

Only the queries need .await. Mutators stay synchronous on either handle.

Keepalive and holdsHolds past the silence window

If no frame arrives for the silence window, the box clears every injected input and pending move.

A Device keeps overrides past the window. While anything is held, its background thread (medius-keepalive) sends QUERY(STATS) every DEFAULT_KEEPALIVE_CADENCE (500 ms). Each tick also re-sends held catch subscriptions, rewrite rules and transforms.

StateBehaviour
Override heldKeepalive thread runs. The reply's session counter shows whether the box released the session.
Clip heldKeepalive thread runs while a clip is loaded (from append to clear), a clip setting is off its default, or a trigger of either kind is bound.
IdleNo QUERY(STATS) ticks. A device-chip restart still starts a recovery: QUERY(CAPS) every 50 ms (every 500 ms after the first 5 s) until the box has a clone, then one QUERY(STATS) and one QUERY(CLIP).
EXAMPLE
device.press(Button::LEFT)?;

// The keepalive thread sends QUERY(STATS), so the hold outlasts
// the 1000 ms silence window.
std::thread::sleep(std::time::Duration::from_secs(5));

device.reset()?;

An override is an input the box holds down or up, set by press or force_release. The library keeps a copy. After a dropped link, reapply and reconnect restore it. After a device-chip restart or a released session, the library restores it itself.

Releasing the deviceDrop the handle; no close() call

Dropping the last Arc-backed handle runs its Drop and tears the connection down.

StepEffect
SignalSets the stop flag the reader and keepalive threads watch.
JoinWaits for both threads to exit.
CloseReleases the serial port as the transport drops.

To return the box to passthrough at once, call reset before drop.

EXAMPLE
device.reset()?;   // passthrough now, not after the silence window
drop(device);      // tears down threads and closes the port