API index
Every Python call, linked to what it doesThe full Device surface, grouped. What each call does lives in the Rust Library and Native API. Types and enums are on Types & errors; streams on Streams.
Most calls are fire-and-forget. They return once the frame is queued. The query calls, plus Device.open / find, block for the box's reply. Any call raises a MediusError on failure.
Connecting & lifecycle
Open, share, and release the linkSee Connection and Lifecycle.
| Call | Does |
|---|---|
Device.open(path) | Open a serial path and handshake. |
Device.find() | Open the first box found, or raise NotFoundError. |
dev.clone() | Another handle to the same link; the connection is shared. |
dev.close() | Free the handle. Called automatically by a with block and on GC. |
with Device.find() as dev: | Context manager that closes the link on block exit. |
Discovery
Enumerate boxes and open one by identityPick a box out of several by a stable identity (device MAC or CH343 serial), or by the kind of device it clones. See Discovery.
| Call | Does |
|---|---|
medius.list_boxes(cap=16) | Enumerate every connected box as a BoxInfo (opens, handshakes, and reads each one's version + device info). |
Device.open_by_id(id) | Open the box whose identity matches id (device MAC hex or CH343 serial) and handshake. |
Device.find_mouse_box() | Open the first box whose clone is a mouse. |
Device.find_keyboard_box() | Open the first box whose clone is a keyboard. |
Movement
Relative cursor and wheelSee Move. +x right, +y down.
| Call | Does |
|---|---|
dev.move_rel(dx, dy) | Nudge the cursor by a signed 16-bit delta. |
dev.wheel(delta) | Scroll the wheel. |
dev.move_rel_now(dx, dy) | The same, bypassing movement riding. |
dev.wheel_now(delta) | Scroll, bypassing movement riding. |
dev.flush_motion() | Emit the motion riding is holding, now. |
dev.discard_motion() | Drop the motion riding is holding. |
dev.move_axis(motion, timing, pending) | Drive one axis from a Motion.cursor(dx, dy) or Motion.wheel(delta), with a MoveTiming and a PendingMotion. |
Inject
Press and release any usage: button, key, or mediaSee Inject and the injection model (press / soft-release / force-release). One usage vocabulary drives every verb; build a Usage with Usage.button / key / media. Ids are on Usage IDs.
| Call | Does |
|---|---|
dev.inject(input, action) | Apply an Action to a built Usage (button, key, or media usage). |
dev.press(input) | Hold a usage down (Action.PRESS). |
dev.soft_release(input) | Release, unless the user is physically holding it. |
dev.force_release(input) | Release even against a physical hold. |
Every verb takes a Usage, so a button, key, and media usage inject the same way: dev.press(Usage.button(Button.LEFT)), dev.press(Usage.key(Key.W)), dev.press(Usage.media(MediaKey.MUTE)).
Locks
Weigh the user's own inputSee Lock. Build axis/usage targets with LockTarget.x/y/wheel/usage (or the button/key/media shortcuts); a Direction picks a direction, and scale is the percent of the physical value kept (LOCK_SCALE_BLOCK 0, LOCK_SCALE_PASS 100, LOCK_SCALE_MAX 255).
| Call | Does |
|---|---|
dev.scale(target, direction, scale) | Keep scale percent of an axis or usage (e.g. LockTarget.x(), LockTarget.key(Key.W)). |
dev.scale_all(what, direction, scale) | The same over a Blanket class (buttons, keys, media, aim, wheel). |
dev.lock(target, direction) | Block an axis or usage: scale 0. |
dev.unlock(target, direction) | Back to passing untouched: scale 100. |
dev.lock_all(what, direction) / unlock_all | Blanket block / release a whole class. |
LED, admin & options
Status light, resets, persistent settings| Call | Does |
|---|---|
dev.led(target, mode, level) | Drive the status LED. See LED. |
dev.reset() | Clear all overrides. See Admin. |
dev.reapply() | Re-send the active settings. |
dev.reconnect() | Force a reconnect to the mouse. |
dev.reboot(target) | Reboot a chip to run or download mode. |
dev.allow_imperfect_clones(allow) | Opt in to cloning over-capacity devices. See Options. |
dev.set_movement_riding(window_ms) | Set the riding window in ms, or None to turn it off. |
dev.set_bearing(window_ms, mode) | Set what Direction.WITH / AGAINST are measured against; None turns it off. mode is a BearingMode. |
dev.set_emit_pace(pace, force_hz=None) | Pick what paces injected motion (EmitPace.learned() / .interval() / .fixed(hz)) and what rate the clone advertises (force_hz, None = the device's own). See Options. |
dev.set_name(name) | Set the box's human-readable name (1 to 32 printable ASCII). See Name. |
dev.clear_name() | Clear the name, back to the synthesized default. Read it back on Version.name. |
Queries
Read box state, each blocks for one replySee Requests. Each blocks for the box's reply and returns a dataclass documented on Types & errors.
| Call | Returns |
|---|---|
dev.query_version() | Version: protocol + firmware version. |
dev.query_health() | Health: link, mouse, clone, injection flags. |
dev.device_info() | DeviceInfo: the cloned device's USB identity, kind, and product. |
dev.caps() | Caps: mouse/keyboard capabilities. |
dev.query_rate() | Rate: native report rate and poll period. |
dev.query_stats() | Stats: box-side telemetry. |
dev.query_locks() | Locks: every weighed direction (.entries, .scale_of(...), .is_locked(...)). |
dev.query_bearing() | Bearing: the bearing window and geometry. |
dev.query_catch() | CatchState: the live filter table (.entries, .table_full), drop counts, and the two chips' ClockEstimate. |
dev.query_imperfect() | ImperfectStatus: imperfect-clone state. |
dev.query_movement_riding() | int ms, or None when off. |
dev.query_emit_pace() | EmitPaceStatus: pacing mode, rate in effect, and the rate the clone advertises. |
dev.firmware_info() | FirmwareInfo: both chips' versions, slots, and what is staged. |
dev.counters() | Counters: host-side wire counters. |
Firmware update
Write either chip over the open connectionSee Firmware update. Staging blocks for the whole transfer and calls progress(sent, total) as windows are acknowledged; a refusal raises UpdateError.
| Call | Does |
|---|---|
dev.stage_firmware(target, image, progress=None) | Write one image into that chip's spare slot, without booting it. target is an UpdateTarget. |
dev.activate_firmware() | Commit everything staged and boot into it, host chip first. |
dev.abort_update(target) | Throw a staged or in-flight transfer away. |
dev.update_firmware(target, image, progress=None) | Stage one image and activate it in a single call. |
Streams
Subscribe to live input and logsConsuming events is covered on Streams; the catch feature itself on Catch and Logs & counters.
| Call | Returns |
|---|---|
dev.catch_events(filters) | EventStream of the subscribed traffic: input, raw HID, vendor endpoints, control transactions, bus events. |
dev.input_events(filters) | InputStream of decoded press and release edges, and motion. Every filter must name an input class and cover both edges. |
dev.logs() | LogStream of device log lines. |
filters takes one CatchFilter or an iterable of them, each naming a CatchClass and an id inside it, with an optional Direction and Capture.
The box's own refusals get no reply, so check what it actually holds with dev.query_catch().
Buffered clip playback
Preload a per-frame stream, box-clockedBuild a stream with ClipBuilder, then drive it with the ClipHandle from dev.clip(). Concept on Clip.
| Call | Appends |
|---|---|
ClipBuilder() / .clear() | A new builder (chainable); reset for reuse. |
.gap(frames) | A gap run (0 = no-op). |
.move(dx, dy) / .wheel(dz) | A cursor / wheel motion frame. |
.press(usage) / .release(usage) / .force_release(usage) | A one-edge press / soft-release / force-release frame; usage is a Usage (button, key, or media). |
.edge(usage, action) | A one-edge frame for any Usage with an explicit Action (default press). |
.frame(dx, dy, wheel, edges) | A motion delta plus up to 8 Usage / Action edges on one frame. |
from medius import Action, Button, ClipBuilder, Usage
b = ClipBuilder()
# move (+10, -4) AND press Left on the same frame
b.frame(10, -4, 0, [(Usage.button(Button.LEFT), Action.PRESS)])| Call | Effect |
|---|---|
dev.clip() | A ClipHandle (owns the append-seq counter). |
clip.append(builder) | Append the builder's entries to the ring. |
clip.set_autolock(blankets) | Set the auto-lock scope: a list of Blanket classes locked while the clip plays. |
clip.set_loop(on) / clip.set_retain(on) | Loop the ring on completion; retain entries after playback instead of flushing. |
clip.set_ride(on) | Make the clip's motion wait for a real move under movement riding (off = the box's own clock, the default). |
clip.finalize() | Fix a retained clip's end so it can replay and loop. |
clip.bind(trigger) | Bind a ClipTrigger: a physical Usage + Edge fires a ClipAction (up to 8). |
clip.unbind(usage, edge) / clip.clear_triggers() | Remove one bound trigger by usage + edge; drop all triggers. |
clip.start() / clip.stop() | Begin playback; stop and flush the ring, releasing the auto-lock. |
clip.pause() / clip.resume() | Halt playback in place; carry on from where it paused. |
clip.restart() / clip.toggle() | Replay from the first frame; start if idle else stop. |
clip.clear() | Drop the ring's entries. |
clip.query_status() | ClipStatus: ring depth, playback state, held usages, counters. |
clip.query_config() | ClipSettings: auto-lock, loop, retain, finalized, bound triggers. |
Module functions
Top-level helpers on medius| Call | Does |
|---|---|
medius.find_ports(cap=16) | List present medius ports as PortInfo (now including the CH343 serial). |
medius.list_boxes(cap=16) | Enumerate every connected box as a BoxInfo. See Discovery. |
medius.default_query_timeout_ms() | The default query reply wait (1000 ms). |
medius.default_keepalive_cadence_ms() | The default keepalive interval (500 ms). |
medius.abi_version() | The C ABI version the library exposes. |
medius.version_string() | The library version string. |