MAKCU API
KM Host Protocol — v3.9 · Comprehensive command reference for the MAKCU ecosystem.
Last updated 2026-10-02 · V4.073 official API compatibility update · Firmware version & capability matrix →
Legacy connection compatibility · V4.073
V27/V29 are not V4.027/V4.029. Use the USB2 CH343 serial port, 8N1, no flow control; probe 4000000 and 115200 separately and close competing tools. Baud commands cannot work at a mismatched rate.
Probe with km.version() plus a line ending. V27/V29 echo the queried line; current firmware returns km.MAKCU and a prompt. Parse the result line and consume the prompt instead of matching the whole reply. Enable streams only after discovery.
V4.073 adds km.baud() and km.baud(115200/4000000/0), one argument at a time; zero selects 115200. Binary 0xA4 returns four-byte little-endian applied baud. Reopen and verify at the new rate; the saved boot rate is unchanged. Earlier versions lack these queries.
Existing version/echo replies are preserved. km.serial, km.info, km.help and km.catch_* remain unsupported and must not be mandatory handshake commands. Software tests pass; third-party applications and physical serial adapters remain unverified.
Firmware V4 (V4.0xx): differences from this page
This page documents the V3.x firmware (V30 and the AIO-tool era). Units updated with MAKCU Toolkit (formerly the diagnostic tool) run V4.0xx firmware, which has a smaller km.* text command set; everything else goes through the binary API and the web app (app.makcu.com).
Available on V4: version, device, echo, buttons, left, right, middle, side1, side2, move(x,y), wheel, move_mask, wheel_mask, left_mask … side2_mask, down, up, init, press, string, isdown, multidown, multiup, multipress, mask, remap, keys, stream, controller, controller_state, controller_mask, controller_physical; from V4.026 also click, moveto, getpos, screen, silent, lock_*. move_now / moving are not "available since V4.028": the V4.030 line (V4.030/031/033) has move_now, moving, flick and ms1/ms2; the V4.032/V4.035 endpoint line has none of them; the V4.036 default line restores only move_now; moving and ms1/ms2 return in V4.039, and flick has not returned to the default line.
No V4 text equivalent: axis, bypass, catch_*, disable, fault, help, hs, info, invert_x/y, keyboard, led, log, mo, mouse, pan, reboot, release, remap_button, serial, swap_xy, tilt, turbo. To block physical input use move_mask / wheel_mask / *_mask / mask.
Reply format (V4.018+): a query answers result\r\n>>> , e.g. km.version() → km.MAKCU, as on V3.x; with km.echo(1) the command is repeated first. Button queries (V4.020+): km.left() etc. answer 0 none / 1 physical / 2 injected / 3 both. The text km.buttons stream differs by firmware: V30 sends a 0x53 event frame; V4.024/026/032/035 send a bare 1-byte mask per change; the V4.030 line and V4.036+ send mode 1/2 = 6B 6D 2E ("km.") + mask + 0D 0A (CRLF), and mode 3 = a bare byte. Clients that scan for the "km." prefix miss the bare-byte builds.
Controllers (V4 only): stick +Y is down for every family; V4.008–V4.023 also flipped the Y axis of generic HID controllers (e.g. SCUF Envision) by mistake, fixed in V4.024. Polling rate: the MAKCU runs full-speed USB, so any mouse or 8K receiver passes through at up to 1000 Hz.
V3 mouse commands (V4.026+): km.lock_* (the same state as the *_mask commands), km.click, km.screen, km.getpos, km.moveto and km.silent. The pointer position is tracked on the km.screen virtual screen (default 1920×1080, centred when set) from every mouse report sent to the PC and clamped at the edges; Windows pointer acceleration makes it drift, as on V3.x. km.moveto accepts the V3 segment and control-point arguments without using them; km.silent moves to the point, left-clicks and moves back by the same distance (the V4 reading of the V3 description). Mouse injection interpolate: spreads km.move over several reports. V4.036 defaults to 50% (about 4 ms up to 25%, 17 ms at 50%, 42 ms at 80% at 1000 Hz); from V4.039 the default is 0 (sent in the next report, V3 timing). km.move_now (next-report send) is available from the V4.036 default line; km.moving() (answers 1 while injected movement is still being sent) exists only on the V4.030 line and from V4.039, not on V4.032–V4.038. From V4.028: controllers through MAKCU no longer expose USB audio (speaker, headset jack, microphone, USB haptics); rumble and adaptive triggers are unaffected.
MAKXD firmware (0.2.0+): behaves like MAKCU V4 here: queries answer result\r\n>>> and repeat the command only with km.echo(1) (earlier MAKXD firmware always repeated it); km.left()…km.side2() answer 0–3 and text km.buttons(1) streams one mask byte per change; stick +Y is down on every controller family, including the axis mask modes (earlier MAKXD passed Xbox-protocol/XInput Y through, positive up); mouse/keyboard-to-controller translation is off by default, also on devices upgraded without saved settings; third-party Xbox-protocol (GIP) and XInput pads are recognised by their interface, and generic HID controllers take their right stick and triggers from the report descriptor, with any 16-bit range. MAKXD version numbers: up to V24 they are Vn; from 0.1.0 major.minor.patch, reported as major×10000 + minor×100 + patch (0.2.0 is 200), so numeric comparisons with the old releases keep working.
From V4.036 (binary): adds 0x55 PHYS_BUTTONS (read the physical mouse-button mask), 0x56 INJECT_SNAPSHOT (read a 40-byte injected-state snapshot), 0x67 move_now (send a move in the next report) and 0x34 mouse-history read (signal analysis) — read-only / injection queries for a host bridge and diagnostics.
Do not mix the two binary protocols: the "V2 API (Binary)" on this page is the legacy AIO/MAKXD binary protocol; the V4 firmware binary interface is MAK_API (the DE AD | LEN | CMD | PAYLOAD frame), a different opcode space — hitting V4 with a legacy opcode fails. The full per-opcode payload/reply tables, the stability promise and per-firmware availability are in the firmware version & capability matrix.
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.