Medius - BindingsTypes & errors

Types & errors

Every enum, dataclass, and exception the package exposes

Reference for the values the API takes and returns. Field meanings live with each command, so this page links to the Library types and Native API. Raw HID id meanings (keycodes, button slots, Consumer usages) are on Usage IDs.

Every enum subclasses enum.IntEnum. A member is its wire byte: int(Button.LEFT) == 0, and anywhere an enum is accepted a bare int works too, for a raw HID id, an endpoint address, or an interface number with no named member.

Injection enums

Button · Action

See the injection model for what each Action means; button slots on Usage IDs.

Button
MemberValue
LEFT0
RIGHT1
MIDDLE2
SIDE13
SIDE24
Action
MemberValueMeaning
SOFT_RELEASE0release unless the user is physically holding it
PRESS1hold down
FORCE_RELEASE2release even against a physical hold

Lock & blanket enums

Direction · BearingMode · LockTargetKind · Blanket

See Lock for what a direction and a blanket class mean, and Catch for the third reading a direction has on a traffic subscription.

Direction

One enum with three readings, picked by what it is attached to: an axis, a usage, or a CatchFilter naming one of the byte-oriented catch classes.

MemberValueAliasesOn an axis or wheelOn a button or keyOn a traffic-class filter
BOTH0-both signs; on a scale, a full pass to the relative pairpress and releaseboth directions
POSITIVE1PRESS · IN+x / +y / wheel-up onlythe press edgeIN, device to PC
NEGATIVE2RELEASE · OUT-x / -y / wheel-down onlythe release edgeOUT, PC to device
WITH3-the sign the box is injectingrefusedno meaning
AGAINST4-the sign opposing itrefusedno meaning

The aliases are the same values under names that read at the call site: Direction.PRESS is Direction.POSITIVE. WITH and AGAINST are measured against the bearing rather than a fixed sign; .is_relative tells them apart.

Only an axis has a bearing, so WITH or AGAINST on a lock anywhere else raises RelativeDirectionError. A media usage has no edges: an edge named on one goes out as BOTH, which is what Locks reports it as.

BearingMode

How the box reads the direction it is injecting, which is what WITH and AGAINST resolve by. Set with dev.set_bearing(window_ms, mode).

MemberValueMeaning
PER_AXIS0each axis compares its own sign against its own bearing, independently; the default
VECTOR1the delta is projected onto the injected direction, and the relative scale weighs only the part along it; one relative scale, the lower of X's and Y's, governs the whole aim, and the fixed-sign scales still reach what the projection leaves on each axis

What Locks reports back under VECTOR is there.

LockTargetKind
MemberValue
X0
Y1
WHEEL2
USAGE3

Built for you by LockTarget.x/y/wheel/usage (and the button/key/media shortcuts); you rarely name it directly.

Blanket
MemberValueClassWhat direction picks
AIM0the X and Y cursor axesa sign, on each axis
WHEEL1the wheela sign
BUTTONS2every mouse buttonan edge, on each button
KEYS3every keyboard key and modifieran edge: POSITIVE blocks presses, NEGATIVE releases, BOTH both
MEDIA4every media usagenothing; media has no edges

These are ABI-local ordinals (matching the crate's Blanket order), not the clip auto-lock scope bits.

Keycode enums

Key · MediaKey

Named subsets of the HID usage tables. The full list of ids and what they do is on Usage IDs (keys) and Usage IDs (media). Any call that takes a Key or MediaKey also accepts a raw int usage.

Key
MembersValues
AZ4 to 29
N1N9, N030 to 39
ENTER ESCAPE BACKSPACE TAB SPACE40 to 44
CAPS_LOCK57
F1F1258 to 69
INSERT HOME PAGE_UP DELETE END PAGE_DOWN73 to 78
RIGHT LEFT DOWN UP (arrows)79 to 82
LEFT_CTRL LEFT_SHIFT LEFT_ALT LEFT_GUI224 to 227
RIGHT_CTRL RIGHT_SHIFT RIGHT_ALT RIGHT_GUI228 to 231
MediaKey
MemberValue
PLAY176
PAUSE177
NEXT_TRACK181
PREV_TRACK182
STOP183
PLAY_PAUSE205
MUTE226
VOLUME_UP233
VOLUME_DOWN234

LED & admin enums

LedTarget · LedMode · RebootTarget

See LED and Admin.

LedTarget
MemberValue
DEVICE0
HOST1
BOTH2
LedMode
MemberValue
AUTO0
OFF1
SOLID2
BLINK3
RebootTarget
MemberValue
DEVICE_DOWNLOAD0
HOST_DOWNLOAD1
DEVICE_RUN2
HOST_RUN3

Emit pace

EmitMode · EmitPace

Passed to dev.set_emit_pace(). See Options.

EmitMode
MemberValue
LEARNED0
INTERVAL1
FIXED2
EmitPace

A frozen dataclass carrying mode and hz. Build it with EmitPace.learned(), EmitPace.interval(), or EmitPace.fixed(hz) (the rate snaps to 1000/n and caps at 1 kHz).

Clip

ClipState · Edge · ClipAction · ClipTrigger · ClipSettings · ClipStatus

The buffered-clip types. Concept on Clip.

ClipState
MemberValueMeaning
IDLE0No clip playing.
PLAYING1Draining the ring, one entry per native frame.
PAUSED2Halted mid-clip; the cursor and any held input are retained.
FAULTED3An append was dropped or the ring overflowed; clear to recover.
UpdateTarget
MemberValueMeaning
DEVICE0The PC-facing chip, written directly over the control port.
HOST1The chip that reads the real device, relayed over the inter-chip link.
ImageState
MemberValueMeaning
NEW0Selected but not yet booted.
PENDING_VERIFY1Booted and on probation; the window rollback lives in.
VALID2Confirmed by the image itself.
INVALID3The image asked to be rolled back.
ABORTED4Booted once and never confirmed.
UNKNOWN0xFFNo entry for this slot.
Edge
MemberValueFires on
BOTH0either edge of the trigger usage
PRESS1the physical press edge
RELEASE2the physical release edge

Which edge of a ClipTrigger runs its action.

ClipAction
MemberValueRuns
START0rewind and play
STOP1stop and release
PAUSE2halt mid-clip
RESUME3continue from the pause
RESTART4force a rewind and play
TOGGLE5play if idle/paused, stop if playing

The action a bound trigger runs on the box, matching the clip.start/stop/pause/resume/restart/toggle methods.

ClipTrigger

A dataclass binding a physical usage's edge to a clip action, passed to clip.bind(). The box runs the action itself with no host round-trip.

FieldTypeMeaning
onUsagethe trigger usage (button, key, or media)
edgeEdgewhich edge fires the action
actionClipActionwhat the box runs
consumeboolsuppress the physical edge so it does not reach the PC (default False)

Construct it directly, e.g. ClipTrigger(Usage.button(Button.SIDE1), Edge.PRESS, ClipAction.TOGGLE, consume=True).

ClipSettings (clip.query_config())
FieldTypeMeaning
autolockList[Blanket]the input groups auto-locked while the clip plays
loopboolplayback loops at the clip end (retained mode only)
retainboolthe loaded clip is retained so it can rewind and replay
finalizedboola retained clip's end is fixed, ready to replay and loop
rideboolthe clip's motion waits for a real move under movement riding
triggersList[ClipTrigger]the bound trigger set (up to 8)
ClipStatus (clip.query_status())
Field / methodTypeMeaning
stateClipStatethe lifecycle state
free / totalintring bytes free (pace top-ups off this) / retained clip size in bytes (streaming: buffered-but-undrained)
playedintbytes played from the clip start (retained progress; ~0 while streaming)
ticksintcontent frames emitted since the last start (gap runs excluded)
underruns / overruns / seq_gapsintempty-ring / ring-full / dropped-append counts
heldList[Usage]the held-usage snapshot: the buttons, keys, and media the clip is holding down (one shape, like a UsageSnapshot)
is_held(usage)booltest one Usage in held

Stream enums

CatchClass · TrafficClass · Axis · CatchFilter · Capture · CatchEventKind · ClockDomain · BusEventKind · LogLevel

See Catch and Logs & counters; consuming events is on Streams.

CatchClass

The address class a CatchFilter names. It is the same address vocabulary lock uses, with members 0 to 3 being the lock classes unchanged, extended with the byte-oriented traffic the box carries. id is class-specific.

MemberValueid meansAs a blanket
BUTTON0a Button slotevery button
KEY1a HID keyboard usageevery key and modifier
MEDIA2a 16-bit Consumer usageevery media usage
AXIS3an Axisevery axis
HID_IN4an interface numberevery HID interface
HID_OUT5an endpoint addressevery interrupt-OUT endpoint
VENDOR_INTERRUPT6an endpoint addressevery vendor interrupt endpoint
VENDOR_BULK7an endpoint addressevery vendor bulk endpoint
CONTROL8an endpoint number (0 = EP0)every control endpoint
EMIT9an endpoint addressevery emitting endpoint
BUS10unusedthe bus lifecycle

There is no every-class member. The wildcard is CatchFilter.everything(), whose catch_class reads None. cls.is_input() is true for 0 to 3, cls.is_traffic() for the rest.

The input classes are tapped before lock suppression and injection, so an input you have locked still reports here. EMIT is the opposite end, what the clone put on the wire afterwards.

TrafficClass

The byte-oriented half of the address space, values 4 to 10 under the same names as CatchClass.

MembersValues
HID_IN HID_OUT4, 5
VENDOR_INTERRUPT VENDOR_BULK6, 7
CONTROL EMIT BUS8, 9, 10

It is what CatchFilter.traffic and traffic_class take, so an input class cannot reach a traffic constructor at all.

Axis
MemberValue
X0
Y1
WHEEL2

One relative axis, for CatchFilter.watch_axis(axis). The values are the wire axis ids a catch or lock entry carries.

CatchFilter

One subscription entry: a class, an id inside it, a direction, and how many bytes to keep per event. Pass one or an iterable to dev.catch_events() or dev.input_events(). The instance methods return a new filter rather than mutating in place.

CatchFilter.watch(usage)               -> CatchFilter         # a Usage, or a Button/Key/MediaKey
CatchFilter.watch_axis(axis)           -> CatchFilter         # one Axis
CatchFilter.watch_class(input_class)   -> CatchFilter         # every usage in one Class
CatchFilter.watch_axes()               -> CatchFilter         # X, Y and the wheel
CatchFilter.all_input()                -> List[CatchFilter]   # all four input classes
CatchFilter.traffic(traffic_class, id) -> CatchFilter         # one endpoint, interface, or EP number
CatchFilter.traffic_class(tc)          -> CatchFilter         # every id in one TrafficClass
CatchFilter.everything()               -> CatchFilter         # every class, every id, one entry

  .with_direction(direction)           -> CatchFilter   # a Direction, default BOTH
  .with_capture(n)                     -> CatchFilter   # bytes kept per event, default 0 = all
  .on_press() / .on_release()          -> CatchFilter   # one edge of an input filter
  .inbound() / .outbound()             -> CatchFilter   # one flow of a traffic filter
  .same_address(other)                 -> bool          # same table entry, whatever the capture

CatchFilter.traffic(TrafficClass.VENDOR_INTERRUPT, 0x83).with_capture(16)
PropertyTypeMeaning
catch_classOptional[CatchClass]the address class, or None for the every-class wildcard
idOptional[int]the class-specific id, or None for the every-id wildcard. An id of 0 is a real address, not a wildcard.
directionDirectionfor an input class, the press/release edge, exactly as for a lock; for a traffic class, the transfer flow, where POSITIVE is IN (device to PC) and NEGATIVE is OUT (PC to device).
captureintbytes captured per event; 0 = the whole packet

Matching is most-specific-first: an exact (class, id) is matched before a class blanket, that before everything(), and a named direction before BOTH. The winning entry supplies the capture.

same_address is true across two filters that differ only in capture, and false once one is narrowed to a direction.

The arguments are checked here, before they reach ctypes. with_direction, watch_axis, watch_class, traffic, and traffic_class want a member of their enum, and with_capture a byte; anything else is a ValueError naming the argument.

The box's table holds 32 entries. catch_events() raises CatchTableFullError when the union of every subscription in this process exceeds it. What the box itself refuses (a class this firmware does not know) raises nothing: compare CatchState.entries from dev.query_catch() against what you sent.

Capture
Capture.WHOLE      # 0, keep the whole packet
Capture.first(n)   # keep the first n bytes; first(0) is WHOLE

What with_capture takes. Traffic classes only: an input class carries no packet, so naming one with a capture raises CaptureNotApplicableError.

A ceiling request, not a guarantee. The box holds one entry per address and cuts once, so another subscriber naming that address more widely raises yours too.

CatchEventKind
MemberValueCatchEvent.payload typeFed by
MOTION0MotionEventCatchClass.AXIS
USAGES1UsageSnapshotBUTTON / KEY / MEDIA
TRAFFIC2TrafficEventevery class from HID_IN to BUS
ClockDomain

Which of the box's two chips stamped an event's ts_us. The two ESP32-S3s boot independently, so nothing relates their timers.

MemberValueStampedCovers
HOST_CHIP0in USB interrupt context, when the real device's transfer completedmotion, usages, HID_IN, and IN transfers on VENDOR_INTERRUPT / VENDOR_BULK
DEVICE_CHIP1at the tap on the clone sideHID_OUT, every OUT transfer, and CONTROL / EMIT / BUS

A stamp is only meaningful against another from the same domain. Both clocks are box-local, wrap every ~71.6 minutes, and restart at zero when that chip reboots, so a value below the previous one is a wrap, a reboot, or a domain change.

To put stamps on this machine's clock, feed them to a Timeline; to cross the two domains, apply the offset in ClockEstimate and respect its error bound.

BusEventKind

What a CatchClass.BUS event describes. These also drive Health bits and Stats counters; catching them adds a timestamped ordering.

MemberValuePayload fields
RESET0-
SUSPEND1-
RESUME2-
CONFIGURED3configuration
DECONFIGURED4-
SET_INTERFACE5interface, alt
DEVICE_ATTACHED6-
DEVICE_DETACHED7-
CLONE_UP8-
CLONE_DOWN9-

BusEvent is the decoded dataclass TrafficEvent.bus_event() returns: a kind plus configuration, interface and alt, each 0 for the kinds carrying none.

LogLevel
MemberValue
ERROR0
WARN1
INFO2
DEBUG3
VERBOSE4

Wire enums

MotionKind · MoveTiming · PendingMotion · Class · FrameType

Mostly internal. MotionKind and Class tag the structs the Usage and Motion builders produce; FrameType names a wire frame for MockBox.saw() and RecordedFrame.type. Frame semantics are on Frames and Library frames.

MotionKind
MemberValue
CURSOR0
WHEEL1
MoveTiming
MemberValueMeaning
RIDE0wait for a real cursor move to carry the delta (the default)
NOW1emit on the box's own clock, whatever movement riding is set to
PendingMotion
MemberValueMeaning
KEEP0leave motion held for a ride alone (the default)
FLUSH1emit it now, ignoring the ride window
DISCARD2drop it
Class
MemberValue
BUTTON0
KEY1
MEDIA2
FrameType
MemberValueMemberValue
MOVE1LOCK10
INJECT3CATCH11
RESET4MOTION_EVENT12
QUERY5USAGE_EVENT15
RESP6OPTION17
REBOOT_DL7CLIP_APPEND18
LOG8CLIP_CTRL19
LED9CLIP_SET20
CLIP_TRIGGER21
TRAFFIC_EVENT22

Parameter builders

Usage · Motion · LockTarget

Small classes that wrap a native struct. Build them with their class methods and pass the result to the matching call. Never construct one field by field.

Usage
Usage.button(button) -> Usage      # build
Usage.key(key)       -> Usage
Usage.media(media)   -> Usage

usage.kind           -> Class      # read one back
usage.id             -> int

An injection target for dev.inject(input, action), and what a InputEvent and a UsageSnapshot hand back. It compares by value, hashes, and reprs as Usage(kind=BUTTON, id=0).

EXAMPLE
# Naming a button off the stream: id is the Button value, kind says which class it is.
if ev.usage is not None and ev.usage.kind is Class.BUTTON:
    print(Button(ev.usage.id).name)

# Or compare whole usages.
if ev.usage == Usage.button(Button.SIDE1):
    print("side button")
Motion
Motion.cursor(dx, dy) -> Motion
Motion.wheel(delta)   -> Motion

A relative axis drive for dev.move_axis(motion, timing, pending). See Move.

LockTarget
LockTarget.x()            -> LockTarget
LockTarget.y()            -> LockTarget
LockTarget.wheel()        -> LockTarget
LockTarget.usage(usage)   -> LockTarget
LockTarget.button(button) -> LockTarget
LockTarget.key(key)       -> LockTarget
LockTarget.media(media)   -> LockTarget

An axis or usage to lock for dev.lock(target, direction); the button/key/media shortcuts wrap usage(). See Lock.

Device enums

DeviceKind

The cloned device's kind, on DeviceInfo.kind, and what Device.find_mouse_box() / find_keyboard_box() select on. See DeviceKind.

DeviceKind
MemberValue
UNKNOWN0
KEYBOARD1
MOUSE2

Identity & capability types

Version · Health · DeviceInfo · Caps

Dataclasses returned by the queries. Canonical field docs: Library structs.

Version (query_version())
Field / propertyTypeMeaning
proto_verintcontrol-protocol version
fw_majorintfirmware major
fw_minorintfirmware minor
fw_patchintfirmware patch
macbytesthe device chip's base MAC (6 bytes), a stable per-box id
mac_hexstrthe MAC as 12 lowercase hex digits
namestrthe box's human-readable name (a synthesized default when unset), set with set_name
Health (query_health())
FieldType
link_upbool
mouse_attachedbool
clone_configuredbool
injection_activebool
rate_confidentbool
lock_onbool
catch_onbool
kbd_attachedbool
DeviceInfo (device_info())
FieldTypeMeaning
vidintUSB vendor id
pidintUSB product id
bcd_deviceintdevice release (BCD)
bcd_usbintUSB spec (BCD)
has_serialboolexposes a serial string
has_bosboolexposes a BOS descriptor
kindDeviceKindthe device's primary kind (Boot-interface protocol)
productstrthe product string (empty when none)
Caps (caps())
Field / methodTypeMeaning
mouseMouseCapsmouse capabilities
keyboardKbdCapskeyboard capabilities
mouse_change_drivenboolmouse reports only on change
kbd_change_drivenboolkeyboard reports only on change
has_mouse()boola mouse interface is present
has_keyboard()boola keyboard interface is present
is_composite()boolthe clone has more than one HID interface (n_hid > 1)
MouseCaps
FieldTypeMeaning
n_buttonsintbutton count
has_xboolX axis present
has_yboolY axis present
has_wheelboolwheel present
has_report_idboolreports carry a report id
n_hidintHID interface count
KbdCaps
FieldTypeMeaning
n_keysintrollover key count
nkrobooln-key rollover
has_consumerboolConsumer (media) page
has_systemboolSystem-control page
has_report_idboolreports carry a report id

State & telemetry types

Rate · Stats · Locks · CatchState · CatchEntry · ClockEstimate · ImperfectStatus · Counters · PortInfo

More query results, plus PortInfo from find_ports(). Canonical field docs: Library structs.

ChipFirmware
FieldTypeMeaning
major, minor, patchintthe version this chip is running
slotintwhich app slot it booted, 0 or 1
stateImageStateconfirmed, on probation, or rolled back
FirmwareInfo (firmware_info())
FieldTypeMeaning
deviceChipFirmwarethe PC-facing chip
hostChipFirmware | NoneNone when the host chip has not answered over the link
slot_sizeintusable 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
Rate (query_rate())
Field / methodTypeMeaning
native_period_usintmouse report period, µs
poll_period_usintpoll period, µs
confidentboolestimate is settled
change_drivenboolreports only on change
native_hz()float | Nonerate in Hz, or None if unknown
Stats (query_stats())
FieldTypeMeaning
inject_emitsintinjected reports emitted
tx_dropsintdropped TX frames
tx_mergesintcoalesced TX frames
tx_maxdepthintpeak TX queue depth
tx_wedgesintTX stalls
wakeupsintscheduler wakeups
reset_countintresets seen
config_countintclone configures
Locks (query_locks())
Field / methodTypeMeaning
entriesList[LockEntry]one LockEntry per weighed direction
scale_of(target, direction)intpercent of the physical value kept there, 100 when nothing weighs it; 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, direction)boolwhether it is blocked outright; a direction merely weighed is not locked. Also true when a whole-class blanket covers it. BOTH asks about the two fixed signs only, so name WITH or AGAINST to ask about one of those
Readback caseWhat entries holds
a blanket key lockone entry per blocked edge, never BOTH
a media lock, blanket or specificBOTH, always
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
SCALE CONSTANTS
Module constantValueMeaning
LOCK_SCALE_BLOCK0keep none of the physical value
LOCK_SCALE_PASS100keep all of it, untouched
LOCK_SCALE_MAX2552.55x, the ceiling
LockEntry
FieldTypeMeaning
targetLockTargetwhat is weighed (an axis or a usage)
is_blanketboola whole-class entry, where target names only the class
directionDirectionwhich direction of the target this entry weighs
scaleintpercent of the physical value kept; a usage carries one bit, so the box stores the block or pass it renders and this never reads between them
is_blockboolscale == 0: blocked outright rather than weighed
Bearing (query_bearing())

What Direction.WITH and Direction.AGAINST are measured against; see the native bearing.

Field / propertyTypeMeaning
window_msOptional[int]how long an axis holds the direction of its last injected delta; None is off, leaving both relative directions inert
modeBearingModehow the bearing is read
is_liveboolwhether a bearing is held at all

Module constant BEARING_WINDOW_DEFAULT_MS (20) is the factory window. A box that has been set boots at its own value.

CatchState (query_catch())

The live subscription table read back from the box. Since catch_events() gets no reply, this is the only way to see which filters the box holds.

FieldTypeMeaning
table_fullboolan entry was refused because the 32-entry table was full
droppedintbox-wide events that could not be queued
clockClockEstimatethe measured relationship between the two chips' clocks
entriesList[CatchEntry]one entry per live subscription, up to 32
CatchEntry

One row of the box's table: the CatchFilter you sent, with a drop count attached.

FieldTypeMeaning
filterCatchFilterthe entry as the box stored it; a class blanket stays one entry, never expanded per id
droppedintevents this entry could not queue
ClockEstimate (CatchState.clock)
Field / memberTypeMeaning
offset_usintthe host chip's clock minus the device chip's, in µs (signed)
rate_ppbOptional[int]relative drift between the two chips, parts per billion (signed), or None when the box fitted no rate
delay_usintthe best measured round trip in the window; the offset is good to about half of it
age_msOptional[int]age of the estimate, or None when there is no estimate yet
error_bound_usinthalf delay_us: the bound on how wrong offset_us can be
to_host_domain(device_us)Optional[int]a device-chip stamp on the host chip's timeline, or None when there is no estimate to apply

Two independent crystals make an offset stale at up to 20 µs per second, so extrapolate with rate_ppb rather than trusting it.

Applying the offset is optional: each event's clock stays authoritative.

ImperfectStatus (query_imperfect())
FieldTypeMeaning
allowedboolimperfect clones opted in
over_capacityboolmouse exceeds clone capacity
clone_imperfectboolthe live clone is imperfect

See Options.

EmitPaceStatus (query_emit_pace())
FieldTypeMeaning
modeEmitPacethe selected mode
resolved_hzintthe ceiling in effect; 0 = learned/adaptive or no device yet
force_hzint | Nonethe forced wire rate requested; None leaves the device's own
advertised_hzintwhat 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

See Options.

Counters (counters())
FieldTypeMeaning
frames_txinthost-side frames sent
frames_rxinthost-side frames received
crc_dropsintframes dropped on CRC
reconnectsintlink reconnects
PortInfo (find_ports())
FieldTypeMeaning
pathstrserial path, e.g. /dev/ttyACM0 or COM3
vidintUSB vendor id
pidintUSB product id
serialOptional[str]the CH343 adapter's serial, when it serves one

Pass path to Device.open(path). Canonical: PortInfo.

BoxInfo (list_boxes())
Field / propertyTypeMeaning
portPortInfothe box's control port
versionVersionits firmware version, with the box MAC and name
deviceDeviceInfothe device it clones
idstrthe box identity (the MAC hex)
serialOptional[str]the CH343 serial

Pass id or serial to Device.open_by_id(id). Canonical: BoxInfo.

Event & log types

Yielded by the streams

Payloads from streams. dev.catch_events() yields CatchEvent, dev.input_events() yields InputEvent, and dev.logs() yields LogLine. What catch reports lives on Catch.

CatchEvent
Field / memberTypeMeaning
kindCatchEventKindwhich payload is set
payloadMotionEvent | UsageSnapshot | TrafficEventthe decoded event
ts_usintWhen the event happened, in box microseconds: the report's arrival for input, the tap firing for traffic. Box-local and wrapping every ~71.6 minutes, so compare stamps only against each other. See Catch timestamps.
clockClockDomainwhich chip stamped ts_us. Two stamps are directly comparable only when this matches; across domains, apply CatchState.clock.
motionMotionEvent | Nonepayload when kind == MOTION
usagesUsageSnapshot | Nonepayload when kind == USAGES
trafficTrafficEvent | Nonepayload when kind == TRAFFIC
MotionEvent
FieldTypeMeaning
dxintX delta
dyintY delta
dzintwheel delta

The stamp and its domain stay on the CatchEvent around it.

UsageSnapshot
Field / methodTypeMeaning
usagesList[Usage]every held Usage (button, key, or media; modifiers are key usages 0xE0 to 0xE7)
clsClassthe one class this snapshot covers, from the frame header
directionDirectionthe edge that produced it
is_held(usage)booltest a Usage in the snapshot

Only held usages that resolve against your filters appear, and no event is emitted when none do, so a subscription to one button stays sparse even while the mouse reports at 1 kHz.

An empty snapshot still names its class: cls and direction come from the frame header, not the entries.

TrafficEvent

The payload for every byte-oriented CatchClass from HID_IN to BUS: one packet, one control transaction, or one bus event, with whatever the entry's capture let through.

Field / methodTypeMeaning
catch_classCatchClasswhich class produced it
idintendpoint address, interface number, or endpoint number, per the class
directionDirectionIN (device to PC) or OUT (PC to device)
flagsintclass-specific, see the table below
true_lenintthe packet's length before capture truncation
bytesbytesthe captured bytes, at most 180 of them
truncated()boollen(bytes) < true_len: bytes were cut
setup()Optional[bytes]the 8-byte setup packet of a CONTROL event; None for another class or a shorter capture
data()bytesthe data stage of a CONTROL event, the whole packet for any other class
control_status()Optional[ControlStatus]what the real device answered; None for any class but CONTROL
bus_event()Optional[BusEvent]the decoded lifecycle event; None for any class but BUS or an unknown kind
bulk_end_of_transfer() / bulk_zlp()boolthe two VENDOR_BULK framing bits, read off flags
FLAGS, BY CLASS
ClassflagsRead it with
VENDOR_BULKb0 end-of-transfer, b1 zero-length packetbulk_end_of_transfer(), bulk_zlp()
CONTROLthe real device's answer: 0 OK, 0xFD it STALLed, 0xFE it NAKed to timeoutcontrol_status()
BUSthe BusEventKind; the bytes hold its argumentsbus_event()
everything else0-

A CONTROL event is one completed transaction, not one stage: bytes is [setup 8][data…] and direction says which way the data stage went. Requests the box serves from its own descriptor cache still produce an event.

ControlStatus
MemberValueMeaning
OK0the real device answered
STALLED1it STALLed the request
NAKED2it NAKed to timeout
OTHER3a status byte this build does not know; the raw byte stays on TrafficEvent.flags
InputEvent

One decoded input: a press edge, a release edge, or a motion report. Yielded by dev.input_events(), which diffs the box's held-usage snapshots so you do not have to.

Field / propertyTypeMeaning
kindInputKindwhich arm is populated
usageOptional[Usage]the usage this is an edge on; None for MOTION
dx / dy / dzintright / down / wheel-up deltas this report; 0 unless kind is MOTION
ts_usintthe box stamp, as on a CatchEvent
clockClockDomainwhich chip stamped it
is_press / is_releaseboolshorthand for the kind test
InputKind
MemberValuePopulates
PRESS0usage, a momentary usage going down
RELEASE1usage, the same coming up
MOTION2dx, dy, dz
Stamped

One event placed on this machine's clock by a Timeline.

FieldTypeMeaning
host_nsintwhen the event happened, on the same monotonic scale passed as now_ns
box_usintthe event's own stamp, unwrapped past the 32-bit rollover
excess_nsinthow much later than the measured floor this event arrived. Jitter, not latency.
LogLine
FieldTypeMeaning
levelLogLevelseverity
textstrthe log message
RecordedFrame (MockBox.recorded_frame(idx))
FieldTypeMeaning
typeFrameType | intframe type (raw int if unknown)
seqintframe sequence byte
payloadbytesraw frame payload

Only meaningful with the mock feature.

Errors

MediusError, its subclasses, and the Status codes

Every Blocks call (and any that fails on the wire) raises a MediusError or one of its subclasses. Catch the base class to catch them all. Canonical mapping: Library errors.

MediusError (Exception)
AttributeTypeMeaning
statusStatusthe failure code
messagestrthe box's last error text
proto_verintoffending version byte (bad-proto-version only)
from medius import Device, MediusError, NotFoundError

try:
    dev = Device.find()
except NotFoundError:
    ...                      # no box plugged in
except MediusError as e:     # any other failure
    print(e.status, e.message)
Subclass per Status
ExceptionRaised on
IoErrorERR_IO
NotFoundErrorERR_NOT_FOUND
NoReplyErrorERR_NO_REPLY
BadProtoVerErrorERR_BAD_PROTO_VER
QueryTimeoutErrorERR_QUERY_TIMEOUT
DisconnectedErrorERR_DISCONNECTED
FrameTooLongErrorERR_FRAME_TOO_LONG
UpdateErrorERR_UPDATE
InvalidArgErrorERR_INVALID_ARG
PanicErrorERR_PANIC
CatchTableFullErrorERR_CATCH_TABLE_FULL
EmptySubscriptionErrorERR_EMPTY_SUBSCRIPTION
CaptureNotApplicableErrorERR_CAPTURE_NOT_APPLICABLE
NotAnInputFilterErrorERR_NOT_AN_INPUT_FILTER
WildcardNotInputErrorERR_WILDCARD_NOT_INPUT
HalfEdgeInputFilterErrorERR_HALF_EDGE_INPUT_FILTER
ReservedIdErrorERR_RESERVED_ID
RelativeDirectionErrorERR_RELATIVE_DIRECTION

The last eight are argument refusals, raised before a frame reaches the box.

RefusalRaised on
CatchTableFullErrorthe union of every subscription in this process needs more than the box's 32 entries
EmptySubscriptionErrora subscription with no filters, which would never yield an event
CaptureNotApplicableErrora Capture on an input class, which carries no packet
NotAnInputFilterErrora traffic class passed to input_events, which cannot decode one
WildcardNotInputErrorCatchFilter.everything() passed to input_events; it covers traffic too
HalfEdgeInputFilterErroran input filter narrowed to one edge, which cannot be decoded into press and release
ReservedIdErroran exact id equal to the blanket sentinel, which would address the whole class instead
RelativeDirectionErrorDirection.WITH or AGAINST where only a fixed sign or edge can be addressed; they resolve against the bearing at emit time, after the call is made

DisconnectedError ends a stream iteration cleanly rather than propagating. OK and ERR_UNKNOWN have no dedicated subclass; ERR_UNKNOWN raises the base MediusError.

Status
MemberValueMemberValue
OK0ERR_DISCONNECTED6
ERR_IO1ERR_FRAME_TOO_LONG7
ERR_NOT_FOUND2ERR_UPDATE8
ERR_NO_REPLY3ERR_INVALID_ARG9
ERR_BAD_PROTO_VER4ERR_PANIC10
ERR_QUERY_TIMEOUT5ERR_UNKNOWN11
ERR_CATCH_TABLE_FULL12ERR_WILDCARD_NOT_INPUT16
ERR_EMPTY_SUBSCRIPTION13ERR_HALF_EDGE_INPUT_FILTER17
ERR_CAPTURE_NOT_APPLICABLE14ERR_RESERVED_ID18
ERR_NOT_AN_INPUT_FILTER15ERR_RELATIVE_DIRECTION19