Calls & errors
The API index lists every call; Types & errors the value types. What each command does is in the Rust Library and Native API.
Fire-and-forget vs blocking Call-kind badges
| Badge | Meaning | Calls |
|---|---|---|
| Fire-and-forget | Queues a frame and returns at once; no reply is read (fire-and-forget). | move_rel, wheel, inject, press, soft_release, scale/lock/unlock, led, reset, reapply, set_movement_riding, set_bearing, set_emit_pace ... |
| Blocks | Sends, then waits for the box's reply (or times out). | Device.open / find (the handshake), every query_* / caps / counters, and a stream recv() |
When a Fire-and-forget call returns without raising, the frame was queued. That does not mean the box acted on it. Confirm with a Blocks query such as dev.query_health(). Default reply wait: medius.default_query_timeout_ms() (1000 ms).
ErrorsMediusError and its subclasses
A failed call raises. The base type is MediusError (an Exception subclass); each status code (a Status IntEnum) maps to a distinct subclass. Catch MediusError for all of them; full mapping on Types & errors.
class MediusError(Exception):
status: Status # the failure code
message: str # the last error text, may be ""
proto_ver: int # offending byte for BadProtoVerError, else 0
str(err) # "ERR_NOT_FOUND: no medius device found" (or only the name)from medius import Device, MediusError, DisconnectedError
try:
with Device.find() as dev:
dev.move_rel(10, 0)
print(dev.query_health())
except DisconnectedError:
print("the link dropped mid-session")
except MediusError as e:
print(e.status.name, "-", e.message) # e.g. ERR_NOT_FOUND - no medius device foundA dropped link raises DisconnectedError from a call and from recv(), but a stream iterator (for ev in dev.catch_events(CatchFilter.everything()):) ends without raising. Box-side telemetry behind these errors is on Diagnostics.
LifecycleHandle ownership and release
Connection sharing is on Connection and Lifecycle.
| Open with | Does |
|---|---|
Device.find() | First box found + handshake. Blocks |
Device.open(path) | One serial path + handshake. Blocks |
dev.clone() | Another handle to the same link. |
MockBox().open() | Open over an in-process mock box (needs the mock feature). |
Device.find() ──┐
├──▶ one USB-serial link (stays up while any handle is open)
dev.clone() ────┘
each handle is freed separately; the link closes with the last one| Route | Freed |
|---|---|
with Device.find() as dev: | When the context manager block exits (__exit__). Preferred. |
dev.close() | Immediately; a second call is a no-op. |
| garbage collection | Best-effort via __del__. Don't rely on timing. |
# preferred: the context manager closes on exit
with Device.find() as dev:
dev.move_rel(5, 5)
# or hand a clone to a worker thread and close each when done
dev = Device.find()
worker = dev.clone()
worker.close()
dev.close()EventStream, InputStream, LogStream, Timeline, and MockBox follow the same pattern: a with block, .close(), or GC. An open stream keeps the device open; see Streams.
Building targets Usage, Motion, LockTarget, CatchFilter for the generic verbs
The inject, move, lock, and catch calls take a target object built with a classmethod, not a bare value.
| Builder | Feeds | Result |
|---|---|---|
Usage.button(button) | dev.inject(input, action), dev.press(input)see Inject | a mouse-button usage |
Usage.key(key) | a keyboard-key usage (keycodes) | |
Usage.media(media) | a consumer/media usage (usages) | |
Motion.cursor(dx, dy) | dev.move_axis(motion, timing, pending)see Move | a relative cursor move |
Motion.wheel(delta) | a wheel turn | |
LockTarget.x() / y() / wheel() / pan() | dev.lock(target, direction) / unlocksee Lock | an axis lock target |
LockTarget.usage(usage) (or button/key/media) | a usage lock target | |
CatchFilter.watch(usage) / .watch_axis(axis) / .watch_class(cls) / .watch_axes() / .all_input() | dev.catch_events(filters), dev.input_events(filters)see Catch | one subscription entry on an input class, addressed as a lock is |
.traffic(tc, id) / .traffic_class(tc) / .everything() | one entry on a TrafficClass, or the wildcard across every class | |
.with_direction(direction) / .with_capture(n) / .on_press() / .inbound() | a narrowed copy of one |
from medius import (Device, Usage, Motion, LockTarget, CatchFilter, TrafficClass,
Button, Action, Direction)
with Device.find() as dev:
dev.inject(Usage.button(Button.LEFT), Action.PRESS) # generic inject
dev.move_axis(Motion.cursor(10, -4)) # generic move (Ride / Keep by default)
dev.lock(LockTarget.button(Button.LEFT), Direction.BOTH)
stream = dev.catch_events([ # generic subscribe
CatchFilter.watch_axes(),
CatchFilter.traffic(TrafficClass.VENDOR_INTERRUPT, 0x83).with_capture(16),
])action is an Action (PRESS / SOFT_RELEASE / FORCE_RELEASE); the injection model defines what each does.
Usage.button takes a Button; Usage.key/media accept a Key/MediaKey or a raw int.
A filter's direction is the same Direction: on an input class it picks the press or release edge (PRESS / RELEASE), on a traffic class the flow (IN / OUT).