WebSocket HID protocol, REST API endpoints, and UART wire format.
The browser connects to the KVMD WebSocket endpoint to send keyboard and mouse events. Messages are JSON objects dispatched to the UART HID bridge.
ws://<host>:8080/ws
// Key press
{"type": "key", "code": "KeyA", "state": true}
// Key release
{"type": "key", "code": "KeyA", "state": false}
// Paste text as keystrokes
{"type": "paste", "text": "hello world"} // Relative mouse move
{"type": "mouse_move", "dx": 10, "dy": -5}
// Mouse button (button: 0=left, 1=right, 2=middle)
{"type": "mouse_button", "button": 0, "state": true}
// Scroll wheel
{"type": "mouse_scroll", "dy": -3} /api/atx // Response
{"power": true, "hdd": false} /api/atx/power /api/atx/power-long /api/atx/reset /api/media // Response
{"images": ["alpine.iso", "ubuntu.iso"], "mounted": "alpine.iso"} /api/media/upload file field./api/media/download {"preset": "alpine"}/api/media/mount {"image": "alpine.iso"}/api/media/eject /rtc/{path}
Binary protocol between the Duo S (KVMD) and ESP32-S3 (HID bridge) over a 115200 baud UART link. The Python reference implementation is in software/sim/protocol.py; the Rust mirror is in firmware/esp32-hid-rs/kvm-protocol/.
┌──────────┬──────────┬───────────────┬──────────┐ │ type: u8 │ len: u8 │ payload: [u8] │ csum: u8 │ └──────────┴──────────┴───────────────┴──────────┘ Checksum = sum of all preceding bytes, masked to 8 bits (& 0xFF)
| Code | Name | Payload | Description |
|---|---|---|---|
| 0x01 | KEY_DOWN | [modifier:u8][keycode:u8] | Press a key |
| 0x02 | KEY_UP | [modifier:u8][keycode:u8] | Release a key |
| 0x03 | MOUSE_MOVE | [dx:i16 LE][dy:i16 LE] | Relative mouse movement |
| 0x04 | MOUSE_BUTTON | [button:u8][state:u8] | Mouse button press/release |
| 0x05 | MOUSE_SCROLL | [dy:i8] | Scroll wheel delta |
| 0x06 | RESET | (none) | Reset HID state |
# KEY_DOWN: press 'A' (keycode 0x04, no modifier) # type=0x01, len=0x02, payload=[0x00, 0x04], csum=0x07 [01] [02] [00 04] [07] # MOUSE_MOVE: dx=10, dy=-5 (little-endian i16) # type=0x03, len=0x04, payload=[0x0A 0x00 0xFB 0xFF], csum=0x0B [03] [04] [0A 00 FB FF] [0B] # RESET: no payload # type=0x06, len=0x00, csum=0x06 [06] [00] [06]
| Bit | Modifier | Bit | Modifier |
|---|---|---|---|
| 0x01 | Left Ctrl | 0x10 | Right Ctrl |
| 0x02 | Left Shift | 0x20 | Right Shift |
| 0x04 | Left Alt | 0x40 | Right Alt |
| 0x08 | Left GUI | 0x80 | Right GUI |