Base URL: http://<host>:<port> (default port auto-assigned starting at 8080)
Service discovery: dns-sd -B _roadie._tcp (mDNS/Bonjour)
Index page with links to all endpoints.
Live video feed with full HID control. Click/touch the stream to send mouse or touch events to the target. Keyboard events are captured automatically (except when interacting with the toolbar). Includes audio toggle, quality/FPS/resolution settings, and mouse/touch input mode toggle. Input mode persists across page refreshes via localStorage.
Device info and JPEG quality adjustment UI.
Interactive HID test page with mouse/touch trackpad, keyboard input, and key combo controls. Supports Mouse mode (pointer + scroll wheel) and Touch mode (multi-touch digitizer with pinch-to-zoom). Trackpad overlays the auto-cropped MJPEG stream and auto-adjusts aspect ratio to match the target's video signal. Coordinates are remapped to account for crop offset so touches align with visible content. Input mode persists via localStorage (shared with /view). Communicates with the target via WebSocket (/api/hid/ws).
MJPEG stream (auto-cropped to detected content area).
Response: multipart/x-mixed-replace with image/jpeg frames.
Single JPEG frame (auto-cropped).
Response: image/jpeg
MJPEG stream (uncropped, full capture resolution).
Response: multipart/x-mixed-replace with image/jpeg frames.
Single JPEG frame (uncropped).
Response: image/jpeg
WebSocket endpoint for live PCM audio.
Protocol:
- Server sends audio parameters as the first text message:
{"sampleRate": 48000, "channels": 2, "format": "f32-planar"} - Server streams PCM audio as binary messages (little-endian float32).
Returns 503 if no audio device is active.
Get current settings.
Response:
{"quality": 80, "fps": 30, "width": 1920, "height": 1080}Update settings. All fields are optional β only provided fields are changed. Changing fps, width, or height triggers a capture restart.
Request:
{"quality": 60, "fps": 15, "width": 1280, "height": 720}| Field | Range | Description |
|---|---|---|
quality |
30-95 | JPEG compression quality |
fps |
10-30 | Capture framerate |
width |
Capture width (must be set with height) |
|
height |
Capture height (must be set with width) |
Response:
{"quality": 60, "fps": 15, "width": 1280, "height": 720}Service status and device info.
Response:
{
"status": "ok",
"source_type": "hardware",
"device": "USB Video",
"resolution": "1920x1080",
"fps": 30,
"quality": 80,
"crop": {"x": 0, "y": 140, "width": 1920, "height": 800},
"audio": {"sampleRate": 48000, "channels": 2}
}| Field | Description |
|---|---|
status |
ok, disconnected, connecting, or no_signal |
source_type |
hardware or http |
crop |
Present only when active crop differs from full frame |
audio |
Present only when audio capture is active |
Reset the USB capture device (unbind/rebind). Forces HDMI re-negotiation β useful when the signal is glitchy after initial connection. The stream will briefly disconnect and auto-recover.
Response:
{"status": "ok"}Requires Linux and a udev rule (installed by make setup). Returns 503 if capture is not available.
Reset the HID board. The relay sends a reset command over UART; the HID board reboots.
Response:
{"status": "ok"}Reset the relay board. The board reboots immediately, dropping the USB serial connection. The server auto-reconnects within a few seconds.
Response:
{"status": "ok"}All HID endpoints require the relay board to be connected via USB serial. Returns 503 if HID is not available.
Connection status of the relay board.
Response:
{"status": "connected"}Status values: connected, disconnected, connecting, unavailable.
Type text on the target device. Text longer than 29 characters is automatically chunked.
Request:
{"text": "hello world"}Response:
{"status": "ok"}Send a keyboard key event.
Request:
{"keycode": 4, "action": "press"}| Field | Description |
|---|---|
keycode |
USB HID keycode (see table below) |
action |
press, release, or click (press + release) |
Response:
{"status": "ok"}Move mouse to absolute position.
Request:
{"x": 16383, "y": 16383}Coordinates are absolute in the range 0-32767:
(0, 0)= top-left(16383, 16383)= center(32767, 32767)= bottom-right
Response:
{"status": "ok"}Mouse button click, press, or release.
Request:
{"buttons": 1, "action": "click"}| Field | Description |
|---|---|
buttons |
Button mask: 1 = left, 2 = right, 4 = middle. Default: 1 |
action |
click (default), press, or release |
Response:
{"status": "ok"}Scroll the mouse wheel.
Request:
{"amount": 5}| Field | Description |
|---|---|
amount |
Scroll amount: positive = down, negative = up. Range: -127 to 127 |
Response:
{"status": "ok"}Send a multi-touch digitizer report (up to 2 simultaneous contacts). The HID board presents as a touchscreen to the target device, enabling native touch gestures like tap, drag, scroll, and pinch-to-zoom.
Request:
{"contacts": [
{"id": 0, "tip": true, "x": 16383, "y": 16383},
{"id": 1, "tip": true, "x": 20000, "y": 20000}
]}| Field | Description |
|---|---|
contacts |
Array of 0-2 touch contacts |
contacts[].id |
Contact identifier: 0 or 1 |
contacts[].tip |
true = finger down, false = finger lifted |
contacts[].x |
Absolute X position (0-32767) |
contacts[].y |
Absolute Y position (0-32767) |
Send an empty contacts array [] to lift all fingers.
Response:
{"status": "ok"}Gesture examples:
| Gesture | Sequence |
|---|---|
| Tap | Send contact tip=true, then tip=false |
| Drag | Send contact tip=true, update x/y over time, then tip=false |
| Scroll | Two contacts (id 0+1), move both in same vertical direction |
| Pinch zoom | Two contacts, move apart (zoom in) or together (zoom out) |
WebSocket for real-time HID control. Accepts the same JSON command format used by the relay board protocol. Preferred for mouse movement and touch (lower latency than REST).
Messages (client to server):
{"cmd": "mouse_move", "x": 16383, "y": 16383}
{"cmd": "mouse_click", "buttons": 1}
{"cmd": "mouse_press", "buttons": 1}
{"cmd": "mouse_release", "buttons": 1}
{"cmd": "mouse_scroll", "amount": 5}
{"cmd": "key_press", "keycode": 4}
{"cmd": "key_release", "keycode": 4}
{"cmd": "type", "text": "hello"}
{"cmd": "touch", "contacts": [{"id": 0, "tip": true, "x": 16383, "y": 16383}]}No server-to-client messages. Connection auto-reconnects on the /test page.
A WebDriver BiDi-flavored WebSocket endpoint. This allows BiDi-speaking automation tools (like Vibium) to control a physical device through Roadie using the same protocol they use for browsers.
Roadie implements a minimal subset of BiDi β only the methods that map to hardware KVM capabilities.
Protocol: JSON over WebSocket. Client sends {"id": <int>, "method": "<method>", "params": {...}}. Server responds with {"type": "success", "id": <int>, "result": {...}} or {"type": "error", "id": <int>, "error": "<code>", "message": "<detail>"}.
Supported methods:
| Method | Description |
|---|---|
session.status |
Check if Roadie is ready (capture + HID connected) |
session.new |
Create a session (one at a time) |
session.end |
End the session, release all held input |
browsingContext.getTree |
Returns a single context ("screen") representing the target display |
browsingContext.captureScreenshot |
Returns a base64-encoded JPEG of the current frame |
roadie:screen.getViewport |
Returns the current viewport (cropped image) width and height |
input.performActions |
Execute keyboard, mouse, and touch actions |
input.releaseActions |
Release all held keys, buttons, and touches |
Example session (using websocat):
websocat ws://localhost:8080/session{"id": 1, "method": "session.new", "params": {"capabilities": {}}}
{"id": 2, "method": "roadie:screen.getViewport", "params": {}}
{"id": 3, "method": "browsingContext.captureScreenshot", "params": {"context": "screen"}}
{"id": 4, "method": "input.performActions", "params": {"context": "screen", "actions": [{"type": "pointer", "id": "mouse", "actions": [{"type": "pointerMove", "x": 243, "y": 530}, {"type": "pointerDown", "button": 0}, {"type": "pointerUp", "button": 0}]}]}}
{"id": 5, "method": "input.performActions", "params": {"context": "screen", "actions": [{"type": "pointer", "id": "finger0", "parameters": {"pointerType": "touch"}, "actions": [{"type": "pointerMove", "x": 243, "y": 530}, {"type": "pointerDown", "button": 0}, {"type": "pointerUp", "button": 0}]}]}}
{"id": 6, "method": "session.end", "params": {}}Example: draw a checkbox with touch (using websocat):
Draws a square then a checkmark whose final stroke extends past the box.
websocat ws://localhost:8080/session{"id": 1, "method": "session.new", "params": {"capabilities": {}}}
{"id": 2, "method": "input.performActions", "params": {"context": "screen", "actions": [{"type": "pointer", "id": "finger0", "parameters": {"pointerType": "touch"}, "actions": [{"type": "pointerMove", "x": 143, "y": 430}, {"type": "pointerDown", "button": 0}, {"type": "pointerMove", "x": 343, "y": 430, "duration": 250}, {"type": "pointerMove", "x": 343, "y": 630, "duration": 250}, {"type": "pointerMove", "x": 143, "y": 630, "duration": 250}, {"type": "pointerMove", "x": 143, "y": 430, "duration": 250}, {"type": "pointerUp", "button": 0}, {"type": "pointerMove", "x": 170, "y": 510}, {"type": "pointerDown", "button": 0}, {"type": "pointerMove", "x": 230, "y": 580, "duration": 200}, {"type": "pointerMove", "x": 370, "y": 380, "duration": 350}, {"type": "pointerUp", "button": 0}]}]}}
{"id": 3, "method": "session.end", "params": {}}Viewport size: session.new returns roadie:viewport: {width, height} in capabilities, and browsingContext.captureScreenshot includes it in the result. This tells you the coordinate space for input.performActions β pixel coordinates are relative to this viewport (the cropped screenshot). Roadie translates them to the 0β32767 HID absolute coordinate range internally.
Touch input: Use pointer actions with "parameters": {"pointerType": "touch"}. Up to 2 simultaneous touch contacts are supported.
Key input: Key values follow the WebDriver key mapping β printable characters as literals, special keys as Unicode Private Use Area values (e.g., "\uE007" for Enter, "\uE008" for Shift).
Session limit: Only one BiDi session can be active at a time. The session is automatically cleaned up when the WebSocket disconnects.
Common keycodes for use with /api/hid/key and the WebSocket key_press/key_release commands.
| Key | Code | Key | Code | Key | Code |
|---|---|---|---|---|---|
| A | 4 | J | 13 | S | 22 |
| B | 5 | K | 14 | T | 23 |
| C | 6 | L | 15 | U | 24 |
| D | 7 | M | 16 | V | 25 |
| E | 8 | N | 17 | W | 26 |
| F | 9 | O | 18 | X | 27 |
| G | 10 | P | 19 | Y | 28 |
| H | 11 | Q | 20 | Z | 29 |
| I | 12 | R | 21 |
| Key | Code | Key | Code |
|---|---|---|---|
| 1 | 30 | 6 | 35 |
| 2 | 31 | 7 | 36 |
| 3 | 32 | 8 | 37 |
| 4 | 33 | 9 | 38 |
| 5 | 34 | 0 | 39 |
| Key | Code | Key | Code |
|---|---|---|---|
| Enter | 40 | Delete | 76 |
| Escape | 41 | End | 77 |
| Backspace | 42 | Page Down | 78 |
| Tab | 43 | Right Arrow | 79 |
| Space | 44 | Left Arrow | 80 |
| Caps Lock | 57 | Down Arrow | 81 |
| Print Screen | 70 | Up Arrow | 82 |
| Insert | 73 | Num Lock | 83 |
| Home | 74 | ||
| Page Up | 75 |
| Key | Code | Key | Code | Key | Code |
|---|---|---|---|---|---|
| F1 | 58 | F5 | 62 | F9 | 66 |
| F2 | 59 | F6 | 63 | F10 | 67 |
| F3 | 60 | F7 | 64 | F11 | 68 |
| F4 | 61 | F8 | 65 | F12 | 69 |
| Key | Code | Key | Code |
|---|---|---|---|
| Left Ctrl | 224 | Right Ctrl | 228 |
| Left Shift | 225 | Right Shift | 229 |
| Left Alt | 226 | Right Alt | 230 |
| Left GUI (Win/Cmd) | 227 | Right GUI | 231 |
Send modifier key presses before the target key, then release in reverse order:
Ctrl+C: press 224, press 6, release 6, release 224
Ctrl+V: press 224, press 25, release 25, release 224
Alt+Tab: press 226, press 43, release 43, release 226
Ctrl+Alt+Del: press 224, press 226, press 76, release 76, release 226, release 224
| Flag | Default | Description |
|---|---|---|
--device |
(auto-detect) | Video device name substring |
--source |
HTTP MJPEG source URL (mutually exclusive with --device) | |
--port |
(auto, from 8080) | HTTP server port |
--width |
1920 | Capture width |
--height |
1080 | Capture height |
--fps |
30 | Capture framerate |
--quality |
80 | JPEG quality (30-95) |
--name |
roadie | mDNS service name |
--list-devices |
List video and audio devices, then exit |