MAKCU API
KM Host Protocol — v3.9 · Comprehensive command reference for the MAKCU ecosystem.
Legacy API (ASCII)
km. and end withCRLF followed by the prompt>>> .\r\n>>> .- Command format: Start with . and end with ). The km. prefix is no longer required.
- Other values (such as km, \r\n, etc.) are ignored. Example: .move(1,1,)
- Optional binary frame: DE AD <lenLE:2> <ASCII or binary payload>
- Format: km.payload\r\n>>> (the km. prefix is retained for compatibility)
- Setters echo the input as ACK unless suppressed byecho(0).
- Some streaming replies carry binary after the km. prefix:km.mouse<8 bytes> andkm.buttons<1-byte mask>. Lines still terminate with CRLF and the prompt.
Mouse
Click Scheduling (SET)
km.click(1,3) >>>
Echo ACK.
turbo([button[,delay_ms]]) — GET/SET
Enable rapid-fire mode for mouse buttons. When the button is held down, it automatically triggers rapid press/release cycles at the specified interval. Multiple buttons can have turbo enabled simultaneously, each with its own delay setting.
- button: 1-5 (mouse buttons: 1=left, 2=right, 3=middle, 4=side1, 5=side2), 0=disable all turbo
- delay_ms: 1-5000ms (0=disable). If omitted, uses random 35-75ms. The delay is the time between each press/release toggle (e.g., 500ms = press for 500ms, release for 500ms, repeat)
- Delay is automatically rounded to the mouse endpoint's bInterval for USB synchronization
- turbo() - Returns only active turbo settings as (m1=200, m2=400) (only shows what's set, not zeros)
- turbo(button) - Sets turbo with random 35-75ms delay (auto-rounded to bInterval)
- turbo(button, delay_ms) - Sets turbo with specified delay (auto-rounded to bInterval)
- turbo(button, 0) - Disables turbo for that specific button
- turbo(0) - Disables all turbo
km.turbo(1, 500) >>>
km.turbo(2, 250) >>>
km.turbo(1) >>>
km.turbo() >>>
Mouse Movement
move(dx,dy[,segments[,cx1,cy1[,cx2,cy2]]]) — SET
dx,dy: relative move distance; segments: segment count (optional, default 1, max 512); cx1,cy1,cx2,cy2: cubic Bézier control points (optional)
km.move(10,-3) >>>
km.move(100,50,8,40,25,80,10) >>>
Echo ACK.
moveto(x,y[,segments[,cx1,cy1[,cx2,cy2]]]) — SET
Move pointer to absolute position. Internally calculates the needed x,y movement to reach the requested position on the screen. Parameters align with km.move().
x,y: absolute coordinates; segments: segment count (optional, default 1, max 512); cx1,cy1,cx2,cy2: cubic Bézier control points (optional)
km.moveto(640,360) >>>
km.moveto(100,50,8,40,25,80,10) >>>
Echo ACK.
wheel(delta) — SET
Windows does not allow multiple scroll steps in a single command. The delta value is clamped to ±1 step (positive for scroll up, negative for scroll down). Values greater than 1 are treated as 1, values less than -1 are treated as -1.
km.wheel(-1) >>>
km.wheel(1) >>>
Echo ACK. Note: km.wheel(-5) will only scroll down 1 step, km.wheel(5) will only scroll up 1 step.
pan([steps]) — GET/SET
km.pan(3) >>>
tilt([steps]) — GET/SET
km.tilt(2) >>>
getpos() — GET
km.getpos() >>> km.getpos(123,456) >>>
Returns current pointer position as (x,y).
silent(x,y) — SET
km.silent(400,300) >>>
Echo ACK.
Mouse Advanced
mo(buttons,x,y,wheel,pan,tilt) — SET
Send complete raw mouse frame (SET only, no GET). (0) clears all; x,y,wheel,pan,tilt are one-shots; button mask mirrors button states.
km.mo(1,10,5,0,0,0) >>>
lock_<target>(state) — GET/SET
Lock button or axis. The target is part of the command name, not a parameter. state: 1=lock, 0=unlock. Call with `()` to read lock state.
Buttons: ml/mm/mr/ms1/ms2
- ml - Left mouse button
- mm - Middle mouse button
- mr - Right mouse button
- ms1 - Side button 1
- ms2 - Side button 2
Axes: mx/my/mw/mx+/mx-/my+/my-/mw+/mw-
- mx/my/mw - Full lock (blocks all movement in that axis)
- mx+/my+/mw+ - Positive direction lock (blocks positive movement only)
- mx-/my-/mw- - Negative direction lock (blocks negative movement only)
km.lock_mx() >>> km.lock_mx(1) >>>
Returns: 1=locked, 0=unlocked
km.lock_mx(1) >>>
Lock all X-axis movement
km.lock_mx+(1) >>>
Lock only positive X movement (right)
km.lock_mx-(1) >>>
Lock only negative X movement (left)
km.lock_my(1) >>>
Lock all Y-axis movement
km.lock_mw(1) >>>
Lock all wheel movement
km.lock_mw+(1) >>>
Lock only positive wheel movement (scroll up)
km.lock_mw-(1) >>>
Lock only negative wheel movement (scroll down)
km.lock_ml(1) >>>
Lock left mouse button
catch_<target>(mode) — GET/SET
Enable catch on a locked button. The target is part of the command name, not a parameter. Note: Catch only works for buttons, not axes. Requires corresponding km.lock_<target> to be set first.
km.catch_ml() >>>
Use `()` to query catch state
km.catch_ml(1) >>>
Enable manual catch for left mouse button
km.catch_ml(0) >>>
Enable auto catch for left mouse button
Mouse Remap (Physical)
remap_axis([inv_x,inv_y,swap]) — GET/SET
Remap mouse axes (physical only). Set all three flags at once.
- () - Show current settings, e.g., (invert_x=0,invert_y=1,swap_xy=0)
- (0) - Reset all axis remaps
- (inv_x,inv_y,swap) - Set all three flags (0 or 1)
km.remap_axis() >>>
km.remap_axis(0,1,0) >>>
km.remap_axis(0) >>>
invert_x([state]) — GET/SET
km.invert_x(1) >>>
invert_y([state]) — GET/SET
km.invert_y(1) >>>
swap_xy([state]) — GET/SET
km.swap_xy(1) >>>
Keyboard Commands
down(key) — SET
km.down('shift')
>>> up(key) — SET
km.up("ctrl")
>>> press(key[,hold_ms[,rand_ms]]) — SET
- key: HID code (0-255) or quoted key name
- hold_ms: Optional hold duration in milliseconds. If omitted, uses random 35-75ms (value is logged)
- rand_ms: Optional randomization range added to hold_ms (0 = no randomization)
- Note: Duration is automatically rounded to the keyboard's bInterval for USB synchronization
km.press('a')
>>> Press 'a' with random 35-75ms hold (logged)
km.press('d', 50)
>>> Press 'd' with exactly 50ms hold
km.press('d', 50, 10)
>>> Press 'd' with 50ms base + random 0-10ms
string(text) — SET
Type an ASCII string using queued key presses with automatic timing. Automatically handles Shift for uppercase letters and symbols. Uses timed queue system (no timer slot limits).
- text: ASCII string to type (max 256 characters)
- Each character uses random 35-75ms hold time (internal timing, not logged)
- Inter-character delay: 10ms between characters
km.string("Hello")
>>> km.string("Test123!")
>>> km.string("hello")
>>> init() — SET
km.init() >>>
isdown(key) — GET
km.isdown("ctrl")
>>> km.isdown(1)
>>> Returns 1 if key is down, 0 if released.
disable([key1,key2,...] | [key,mode]) — GET/SET
Disable/enable keyboard keys to block them from being sent to the host.
- () - List all currently disabled keys in format (a,c,f,) (shows key names when available, HID codes otherwise)
- (key1,key2,...) - Disable multiple keys at once. Keys can be HID codes or quoted key names (e.g., 'a', 'f1', 'ctrl')
- (key,mode) - Enable or disable a single key. mode: 1=disable, 0=enable
km.disable() >>> km.disable(a,c,f,) >>>
km.disable('a','c','f')
>>> km.disable('f1','alt','win')
>>> km.disable('a', 0)
>>> km.disable('a', 1)
>>> km.disable(4,6,9) >>>
mask(key[,mode]) — GET/SET
km.mask('a',1)
>>> remap(source,target) — GET/SET
km.remap('a','b')
>>> km.remap('a',0)
>>> Complete Keyboard Key Reference
Complete keyboard key reference table. All keys support HID codes or string names. Single character letters are case-sensitive (lowercase types lowercase, uppercase automatically uses Shift to type uppercase). Multi-character special keys are case-insensitive.
| Key Names | HID | Normal Output | Shift Output |
|---|---|---|---|
| 'a' | 4 | a | A |
| 'b' | 5 | b | B |
| 'c' | 6 | c | C |
| 'd' | 7 | d | D |
| 'e' | 8 | e | E |
| 'f' | 9 | f | F |
| 'g' | 10 | g | G |
| 'h' | 11 | h | H |
| 'i' | 12 | i | I |
| 'j' | 13 | j | J |
| 'k' | 14 | k | K |
| 'l' | 15 | l | L |
| 'm' | 16 | m | M |
| 'n' | 17 | n | N |
| 'o' | 18 | o | O |
| 'p' | 19 | p | P |
| 'q' | 20 | q | Q |
| 'r' | 21 | r | R |
| 's' | 22 | s | S |
| 't' | 23 | t | T |
| 'u' | 24 | u | U |
| 'v' | 25 | v | V |
| 'w' | 26 | w | W |
| 'x' | 27 | x | X |
| 'y' | 28 | y | Y |
| 'z' | 29 | z | Z |
km.press('a') # types "a" (HID 4)
km.press('A') # types "A" (HID 4 + Shift)| Key Names | HID | Normal Output | Shift Output |
|---|---|---|---|
| '1' | 30 | 1 | ! |
| '2' | 31 | 2 | @ |
| '3' | 32 | 3 | # |
| '4' | 33 | 4 | $ |
| '5' | 34 | 5 | % |
| '6' | 35 | 6 | ^ |
| '7' | 36 | 7 | & |
| '8' | 37 | 8 | * |
| '9' | 38 | 9 | ( |
| '0' | 39 | 0 | ) |
km.press('1') # types "1" (HID 30)
km.down('shift')
km.press('1') # types "!" (HID 30 + Shift)
km.up('shift')| Key Names | HID | Normal Output | Shift Output |
|---|---|---|---|
| 'enter', 'return' | 40 | Enter | - |
| 'escape', 'esc' | 41 | Escape | - |
| 'backspace', 'back' | 42 | Backspace | - |
| 'tab' | 43 | Tab | - |
| 'space', 'spacebar' | 44 | - |
km.press('enter') # Enter key (HID 40)| Key Names | HID | Normal Output | Shift Output |
|---|---|---|---|
| 'minus', 'dash', 'hyphen' | 45 | - | _ |
| 'equals', 'equal' | 46 | = | + |
| 'leftbracket', 'lbracket', 'openbracket' | 47 | [ | { |
| 'rightbracket', 'rbracket', 'closebracket' | 48 | ] | } |
| 'backslash', 'bslash' | 49 | \ | | |
| 'nonus_hash' | 50 | # | - |
| 'semicolon', 'semi' | 51 | ; | : |
| 'quote', 'apostrophe', 'singlequote' | 52 | ' | " |
| 'grave', 'backtick', 'tilde' | 53 | ` | ~ |
| 'comma' | 54 | , | < |
| 'period', 'dot' | 55 | . | > |
| 'slash', 'forwardslash', 'fslash' | 56 | / | ? |
| 'capslock', 'caps' | 57 | Caps Lock | - |
km.press('minus') # types "-" (HID 45)
km.down('shift')
km.press('minus') # types "_" (HID 45 + Shift)
km.up('shift')| Key Names | HID | Normal Output | Shift Output |
|---|---|---|---|
| 'f1' | 58 | F1 | - |
| 'f2' | 59 | F2 | - |
| 'f3' | 60 | F3 | - |
| 'f4' | 61 | F4 | - |
| 'f5' | 62 | F5 | - |
| 'f6' | 63 | F6 | - |
| 'f7' | 64 | F7 | - |
| 'f8' | 65 | F8 | - |
| 'f9' | 66 | F9 | - |
| 'f10' | 67 | F10 | - |
| 'f11' | 68 | F11 | - |
| 'f12' | 69 | F12 | - |
km.press('f1') # F1 key (HID 58)| Key Names | HID | Normal Output | Shift Output |
|---|---|---|---|
| 'printscreen', 'prtsc', 'print' | 70 | Print Screen | - |
| 'scrolllock', 'scroll' | 71 | Scroll Lock | - |
| 'pause', 'break' | 72 | Pause/Break | - |
| 'insert', 'ins' | 73 | Insert | - |
| 'home' | 74 | Home | - |
| 'pageup', 'pgup' | 75 | Page Up | - |
| 'delete', 'del' | 76 | Delete | - |
| 'end' | 77 | End | - |
| 'pagedown', 'pgdown', 'pgdn' | 78 | Page Down | - |
| 'right', 'rightarrow' | 79 | Right Arrow | - |
| 'left', 'leftarrow' | 80 | Left Arrow | - |
| 'down', 'downarrow' | 81 | Down Arrow | - |
| 'up', 'uparrow' | 82 | Up Arrow | - |
| 'numlock', 'num' | 83 | Num Lock | - |
km.press('home') # Home key (HID 74)| Key Names | HID | Normal Output | Shift Output |
|---|---|---|---|
| 'kpdivide', 'npdivide' | 84 | / | - |
| 'kpmultiply', 'npmultiply' | 85 | * | - |
| 'kpminus', 'npminus' | 86 | - | - |
| 'kpplus', 'npplus' | 87 | + | - |
| 'kpenter', 'npenter' | 88 | Enter | - |
| 'kp1', 'np1' | 89 | 1 | - |
| 'kp2', 'np2' | 90 | 2 | - |
| 'kp3', 'np3' | 91 | 3 | - |
| 'kp4', 'np4' | 92 | 4 | - |
| 'kp5', 'np5' | 93 | 5 | - |
| 'kp6', 'np6' | 94 | 6 | - |
| 'kp7', 'np7' | 95 | 7 | - |
| 'kp8', 'np8' | 96 | 8 | - |
| 'kp9', 'np9' | 97 | 9 | - |
| 'kp0', 'np0' | 98 | 0 | - |
| 'kpperiod', 'kpdot', 'npperiod', 'npdot' | 99 | . | - |
km.press('kp1') # Numpad 1 (HID 89)| Key Names (aliases) | HID | Normal Output | Shift Output |
|---|---|---|---|
| 'leftctrl', 'lctrl', 'leftcontrol', 'lcontrol', 'ctrl', 'control' | 224 | Left Ctrl | - |
| 'leftshift', 'lshift', 'shift' | 225 | Left Shift | - |
| 'leftalt', 'lalt', 'alt' | 226 | Left Alt | - |
| 'leftgui', 'lgui', 'leftwin', 'lwin', 'gui', 'win', 'windows', 'super', 'meta', 'cmd', 'command' | 227 | Left GUI | - |
| 'rightctrl', 'rctrl', 'rightcontrol', 'rcontrol' | 228 | Right Ctrl | - |
| 'rightshift', 'rshift' | 229 | Right Shift | - |
| 'rightalt', 'ralt' | 230 | Right Alt | - |
| 'rightgui', 'rgui', 'rightwin', 'rwin', 'rightwindows' | 231 | Right GUI | - |
Note: Generic names ('ctrl', 'shift', 'alt', 'gui') default to the left variant
km.down('ctrl') # Left Ctrl (HID 224)
km.up('ctrl')Streaming
keyboard([mode[,period]]) — GET/SET
Stream keyboard keys with human-readable names. Mode 1=raw (physical input), 2=constructed frame (after remapping/masking); period clamped 1-1000 frames.
Output format: keyboard(raw,shift,'h') or keyboard(constructed,ctrl,shift,'a') - modifiers and keys shown as names (e.g., 'shift', 'ctrl', 'h', 'a') instead of HID numbers.
km.keyboard(2,50) >>>
km.keyboard(1,100) >>>
axis([mode[,period_ms]]) — GET/SET
Output format: raw(x,y,w) or mut(x,y,w) where w is the wheel delta.
km.axis(1,25) >>>
km.axis(2,10) >>>
mouse([mode[,period_ms]]) — GET/SET
km.mouse(2,25) >>>
km.mouse(1,10) >>>
Device emits 8-byte binary frame (x,y as int16):
km.mouse<8 bytes> >>>
Misc
help() — GET
km.help() >>>
info() — GET
km.info() >>>
version() — GET
km.version() >>>
device() — GET
km.device() >>>
fault() — GET
Returns stored parse fault information including ESP32 MAC address, failed endpoint address, interface number, failure reason, and raw HID descriptor bytes. Useful for debugging devices that fail to parse.
km.fault() >>>
reboot() — SET
km.reboot() >>>
serial([text]) — GET/SET
Used to change the serial number of an attached mouse or keyboard while connected to MAKCU. This change is persistent and remains even after future firmware changes. Note: MAKCU does not allow changing serial numbers for a device that does not contain one.
km.serial()
>>> km.serial("MAKCU001")
>>> Returns current serial number.
km.serial("MAKCU001")
>>> km.serial(0) >>>
Echo ACK. The change is persistent across firmware updates.
log([level]) — GET/SET
km.log(3) >>>
echo([enable]) — GET/SET
km.echo(1) >>>
baud([rate]) — GET/SET
km.baud(115200) >>>
km.baud(921600) >>>
Applies immediately; host must re-open serial at new speed.
bypass([mode]) — GET/SET
Blocks the mouse endpoint from being sent to USB 1, all other endpoints will pass without issues. Works without USB device attached.
- () - Query current state
- (0) - Off (restore USB write, disable telemetry)
- (1) - Mouse bypass (enables km.mouse(1,1) and disables USB write)
- (2) - Keyboard bypass (enables km.keyboard(1,1) and disables USB write)
km.bypass() >>> km.bypass(0) >>>
Warns (no mouse) or (no keyboard) if device not detected
km.bypass(1) >>>
km.bypass(2) >>>
km.bypass(0) >>>
hs([enable]) — GET/SET
km.hs(1) >>>
led([target[,mode[,times,delay_ms]]]) — GET/SET
Control LED and RGB state for both device and host sides. Supports query, control, and flash functionality.
- led() - Query device LED state (backward compatible)
- led(1) - Query device LED state
- led(2) - Query host LED state (via UART)
- Returns: (device,off), (device,on), (device,slow_blink), (device,fast_blink), (host,off), (host,on), etc.
- led(0) - Turn device LED off (backward compatible)
- led(1) - Turn device LED on (backward compatible, but conflicts with query - use led(1,1) for explicit control)
- led(1, 0) - Turn device LED off
- led(1, 1) - Turn device LED on
- led(2, 0) - Turn host LED off (via UART)
- led(2, 1) - Turn host LED on (via UART)
- Target: 1 = device LED, 2 = host LED (USB host side, controlled via UART)
- Mode: 0 = off, 1 = on
- led(1, times, delay_ms) - Flash device LED (e.g., led(1, 3, 200) = 3 flashes at 200ms)
- led(2, times, delay_ms) - Flash host LED (e.g., led(2, 5, 100) = 5 flashes at 100ms)
- Flash parameters: times = number of flashes (default 1), delay_ms = delay between flashes in milliseconds (default 100ms, max 5000ms)
km.led() >>> km.led(device,on) >>>
km.led(2) >>> km.led(host,off) >>>
km.led(1, 0) >>>
km.led(2, 1) >>>
km.led(1, 3, 200) >>>
km.led(2, 5, 100) >>>
release([timer_ms]) — GET/SET
Auto-release monitoring system. Continuously monitors independent lock, button, and key states. When the timer expires, it releases only the corresponding values that remain active (not all at once). This setting is persistently saved to storage and automatically enabled on startup/boot. `()` get status (0=disabled, else time ms); `(timer_ms)` set timer 500-300000ms (5 min), (0) disables.
km.release() >>>
km.release(5000) >>>
km.release(0) >>>
screen([W,H]) — GET/SET
km.screen(1920,1080) >>>
km.screen(2560,1440) >>>
Baud Rate Change (Legacy)
DE AD 05 00 A5 00 C2 01 00
Breakdown: DE AD | 05 00(len=5) | A5 (cmd) |00 C2 01 00 (115200 LE)
V2 API (Binary)
- Frame format: [0x50] [CMD] [LEN_LO] [LEN_HI] [PAYLOAD...]
- 0x50 - Frame start byte (fixed)
- CMD - Command byte (0x01-0xFF)
- LEN_LO / LEN_HI - Payload length in bytes (little-endian, 16-bit)
- PAYLOAD - Command-specific data (variable length)
- Setters: Return [0x50] [CMD] [LEN_LO] [LEN_HI] [status:u8] where 0x00 = OK (success) and 0x01 = ERR (error)
- Getters: Return [0x50] [CMD] [LEN_LO] [LEN_HI] [PAYLOAD...] with raw value bytes or structured data
- Streaming: Return raw HID frame bytes (no text formatting) as [0x50] [CMD] [LEN_LO] [LEN_HI] [PAYLOAD...]
- Data Types: Multi-byte values use little-endian byte order. Example: Query baud rate (command 0xB1, no payload) returns [0x50] [0xB1] [0x04] [0x00] [0x00] [0xC2] [0x01] [0x00] where 0x00C20100 = 115200 (little-endian)
- In subsequent command examples, we show only: [CMD] [PAYLOAD...]
Mouse
Click Scheduling (SET)
[0x04] [button:u8] [count:u8] [delay_ms:u8] Response: [0x04] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
turbo() (GET/SET)
[0x17] Response: [0x17] [btn1-5_delay:u16×5]
Returns 10 bytes: 5 delays (u16 each) for buttons 1-5.
[0x17] [button:u8] [delay_ms:u16] Response: [0x17] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
Mouse Movement
move(dx,dy[,segments[,cx1,cy1]]) — SET (0x0D)
[0x0D] [x:i16] [y:i16] [segments:u8] [cx1:i8] [cy1:i8] Response: [0x0D] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
moveto(x,y[,segments[,cx1,cy1[,cx2,cy2]]]) — SET (0x0E)
[0x0E] [x:i16] [y:i16] [segments:u8] [cx1:i16] [cy1:i16] [cx2:i16] [cy2:i16] Response: [0x0E] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
wheel(delta) — SET (0x18)
[0x18] [delta:i8] Response: [0x18] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
pan([steps]) — GET/SET (0x0F)
[0x0F] Response: [0x0F] [pending:i8]
[0x0F] [steps:i8] Response: [0x0F] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
swap_xy([enable]) — GET/SET (0x15)
[0x15] Response: [0x15] [enabled:u8]
Returns 0=disabled, 1=enabled.
[0x15] [enable:u8] Response: [0x15] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
tilt([steps]) — GET/SET (0x16)
[0x16] Response: [0x16] [pending:i8]
[0x16] [steps:i8] Response: [0x16] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
silent(x,y) — SET (0x14)
[0x14] [x:i16] [y:i16] Response: [0x14] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
Mouse Advanced
catch_<target>([mode]) — GET/SET (0x03)
[0x03] Response: [0x03] [mode:u8]
[0x03] [mode:u8] Response: [0x03] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
getpos() — GET (0x05)
[0x05] Response: [0x05] [x:i16] [y:i16]
Returns current pointer position as int16 coordinates.
invert_x([enable]) — GET/SET (0x06)
[0x06] Response: [0x06] [enabled:u8]
Returns 0=disabled, 1=enabled.
[0x06] [enable:u8] Response: [0x06] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
invert_y([enable]) — GET/SET (0x07)
[0x07] Response: [0x07] [enabled:u8]
Returns 0=disabled, 1=enabled.
[0x07] [enable:u8] Response: [0x07] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
lock_<target>([state]) — GET/SET (0x09)
[0x09] Response: [0x09] [locked:u8]
Returns 1=locked, 0=unlocked.
[0x09] [state:u8] Response: [0x09] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
mo(buttons,x,y,wheel,pan,tilt) — SET (0x0B)
[0x0B] [buttons:u8] [x:i16] [y:i16] [wheel:i8] [pan:i8] [tilt:i8] Response: [0x0B] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
remap_axis([inv_x,inv_y,swap]) — GET/SET (0x19)
- () - Query current settings
- (0) - Reset all axis remaps
- (inv_x,inv_y,swap) - Set all three flags (each 0 or 1)
[0x19] Response: [0x19] [inv_x:u8] [inv_y:u8] [swap:u8]
Returns 3 bytes: inv_x, inv_y, swap (each 0=disabled, 1=enabled).
[0x19] [inv_x:u8] [inv_y:u8] [swap:u8] Response: [0x19] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error. Sets all three flags atomically.
[0x19] [0x00] Response: [0x19] [status:u8]
Reset all axis remaps (sets all flags to 0).
Keyboard
disable([key1,key2,...] | [key,mode]) — GET/SET (0xA1)
[0xA1] Response: [0xA1] [disabled_keys:u8×N]
Returns list of disabled HID codes.
[0xA1] [key1:u8]...[keyN:u8] Response: [0xA1] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
down(key) — SET (0xA2)
[0xA2] [key:u8] Response: [0xA2] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
init() — SET (0xA3)
[0xA3] Response: [0xA3] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
isdown(key) — GET (0xA4)
[0xA4] [key:u8] Response: [0xA4] [is_down:u8]
Returns 0=up, 1=down.
mask(key[,mode]) — SET (0xA6)
[0xA6] [key:u8] [mode:u8] Response: [0xA6] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
press(key[,hold_ms[,rand_ms]]) — SET (0xA7)
[0xA7] [key:u8] [hold_ms:u8] [rand_ms:u8] Response: [0xA7] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
remap(source,target) — SET (0xA8)
[0xA8] [source:u8] [target:u8] Response: [0xA8] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
string(text) — SET (0xA9)
[0xA9] [text:u8×N] Response: [0xA9] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
up(key) — SET (0xAA)
[0xAA] [key:u8] Response: [0xAA] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
Streaming
axis([mode[,period_ms]]) — GET/SET (0x01)
[0x01] Response: [0x01] [mode:u8] [period:u8]
[0x01] [mode:u8] [period:u8] Response: [0x01] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
Device emits 6-byte binary frame when axes change:
[0x01] [dx:i16] [dy:i16] [wheel:i8]
mouse([mode[,period_ms]]) — GET/SET (0x0C)
[0x0C] Response: [0x0C] [mode:u8] [period:u8]
[0x0C] [mode:u8] [period:u8] Response: [0x0C] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
Device emits 8-byte binary frame when mouse data changes:
[0x0C] [buttons:u8] [dx:i16] [dy:i16] [wheel:i8] [pan:i8] [tilt:i8]
keyboard([mode[,period_ms]]) — GET/SET (0xA5)
[0xA5] Response: [0xA5] [mode:u8] [period:u8]
[0xA5] [mode:u8] [period:u8] Response: [0xA5] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
Device emits 15-byte binary frame when keyboard data changes:
[0xA5] [modifiers:u8] [keys:u8×14]
Modifiers byte: bit flags for ctrl, shift, alt, gui, etc. Keys array: up to 14 HID codes.
Misc
baud([rate]) — GET/SET (0xB1)
[0xB1] Response: [0xB1] [rate:u32]
Returns current baud rate as uint32 (little-endian).
[0xB1] [rate:u32] Response: [0xB1] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error. Change is persistent.
bypass([mode]) — GET/SET (0xB2)
[0xB2] Response: [0xB2] [mode:u8]
[0xB2] [mode:u8] Response: [0xB2] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
echo([enable]) — GET/SET (0xB4)
[0xB4] Response: [0xB4] [enabled:u8]
[0xB4] [enable:u8] Response: [0xB4] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error. When echo is disabled, most setter commands won't echo their input.
hs([enable]) — GET/SET (0xB7)
[0xB7] Response: [0xB7] [enabled:u8]
[0xB7] [enable:u8] Response: [0xB7] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error. Setting is persistent across reboots.
led([...]) — GET/SET (0xB9)
Query device: [0xB9] Query host: [0xB9] [0x02] Response: [0xB9] [target:u8] [state:u8]
State: 0=off, 1=on, 2=slow_blink, 3=fast_blink
Set: [0xB9] [target:u8] [mode:u8] Response: [0xB9] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
Flash: [0xB9] [target:u8] [times:u32] [delay_ms:u32] Response: [0xB9] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error. Controls both LED and RGB. Times: 1-255, delay_ms: 100-5000ms
log([level]) — GET/SET (0xBA)
[0xBA] Response: [0xBA] [level:u8]
[0xBA] [level:u8] Response: [0xBA] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
release([timer_ms]) — GET/SET (0xBC)
[0xBC] Response: [0xBC] [timer_ms:u32]
Returns 0 if disabled, else time in milliseconds.
[0xBC] [timer_ms:u32] Response: [0xBC] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
screen([W,H]) — GET/SET (0xBD)
[0xBD] Response: [0xBD] [width:i16] [height:i16]
[0xBD] [width:i16] [height:i16] Response: [0xBD] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error.
serial([text]) — GET/SET (0xBE)
[0xBE] Response: [0xBE] [serial:u8×N]
Returns current serial number as ASCII bytes.
[0xBE] [text:u8×N] Response: [0xBE] [status:u8]
Returns 0x00 (OK) on success, 0x01 (ERR) on error. The change is persistent across firmware updates.
device() — GET (0xB3)
[0xB3] Response: [0xB3] [type:u8]
Type: 0=none, 1=keyboard, 2=mouse
fault() — GET (0xB5)
[0xB5] Response: [0xB5] [parse_fault_t struct]
Returns parse_fault_t structure with MAC, endpoint, reason, and raw descriptor bytes.
info() — GET (0xB8)
[0xB8] Response: [0xB8] [field_count:u8] [fields...]
Returns structured binary data with system/device info key-value pairs. Device info cached after first fetch.
reboot() — SET (0xBB)
[0xBB] Response: [0xBB] [status:u8]
Always returns 0x00 (success). Device reboots after sending response.
version() — GET (0xBF)
[0xBF] Response: [0xBF] [version:u8×N]
Returns firmware version as ASCII bytes.