Quickstart
Plug in and send your first commandA Medius box sits inline between a USB device and a PC. The real device passes through, and your program sends input of its own (cursor and buttons for a mouse, keys and media for a keyboard) over a USB-serial link. Below: wire it, open the link, send one movement command. Every step is byte-exact firmware behavior.
The box talks in frames, small fixed-shape packets that each carry one command. Most are fire-and-forget: you send and move on with no reply. The exception is QUERY, which gets one answer back.
Wire it up
The safe 3-port layoutUSB1(clone) → game PCUSB2(control) → control PCUSB3(mouse) → real mouse
The clone copies the real mouse's USB identity, so the game PC sees the same device it would if the mouse were plugged in directly.
Never connect USB1 and USB3 to the same machine. The USB3 5V rail can't be pulled low in firmware, so wiring both to one machine back-feeds power and can force a shutdown and drain the battery. One game PC, one control PC, never the same machine.
Full port map and hazard detail on Hardware.
Open the link
Open at the fixed baud, speak binaryOpen /dev/ttyACM0 (Linux) or COMx (Windows) at 4,000,000 baud, 8N1, and speak binary immediately. There's no 115200 handshake and no baud-switch frame.
RESP is the box's reply to a question. To learn the protocol version:
- On first contact the box sends one
RESP(VERSION)on its own, withSEQ0, your ready signal. Or sendQUERY(VERSION)yourself. - Read
proto_verfrom theVERSIONpayload, the one-byte protocol version the firmware speaks. - Check
proto_ver == 3before trusting the commands here. This documents version3.
Serial framing detail on Transport; handshake and presence detail on Connection.
Send a MOVE
Relative cursor movementMOVE nudges the cursor on the PC. Its frame has the standard shape, laid out byte-by-byte below.
SEQis a number you pick and increment per frame. For aQUERYthe box echoes it in the matchingRESPso you can pair reply to request; here0.CRC16lets the box reject a corrupted frame. It is CRC16-CCITT (polynomial0x1021, initial value0xFFFF) overTYPE | SEQ | LEN | PAYLOAD, stored little-endian.
import struct
def crc16_ccitt(data):
crc = 0xFFFF
for b in data:
crc ^= b << 8
for _ in range(8):
crc = (crc << 1) ^ 0x1021 if crc & 0x8000 else crc << 1
crc &= 0xFFFF
return crc
def encode(type, seq, payload):
head = bytes([type, seq]) + struct.pack('<H', len(payload)) + payload
return bytes([0xA5]) + head + struct.pack('<H', crc16_ccitt(head))
frame = encode(0x01, 0, struct.pack('<Bhh', 0, 100, 0))
port.write(frame)MOVE has opcode 0x01. Its payload is a motion byte (0 = cursor) then two signed 16-bit deltas, dx then dy. +x is right, +y is down. The example moves 100 right, 0 down.
That builds the bytes A5 01 00 05 00 00 64 00 00 00 <crc>: start byte, opcode 0x01, sequence, length, the motion/dx/dy payload, then the checksum. The byte-by-byte breakdown is on MOVE, the frame format on Frame Format.
Confirm
Check the chain before relying on injectionInjection is the input your program sends on top of the real mouse's passthrough. Confirm the chain (real mouse → box → PC) is live before you rely on it: send QUERY(HEALTH), read the flags byte from the RESP, and check these bits are set.
| Flag | Mask | Means |
|---|---|---|
LINK_UP | 0x01 | The link to the host chip is up. |
MOUSE_ATTACHED | 0x02 | A mouse is on USB3. |
CLONE_CONFIGURED | 0x04 | The game PC has enumerated the clone. |
A flag is set when (flags & mask) is non-zero. With all three set, your MOVE reaches the game PC. The full byte is on HEALTH.
Skip the framing
For a ready-made client, cargo add medius. See the library.