Skip to content

HIDAPI ​

📥 下载此API文档:hid-api.md

PIO 口切换为 Device 模式后,PIO-USB-C 口变为 generic HID 设备。上位机通过 HID OUT 端点发送 64B 命令帧控制设备,通过 HID IN 端点接收设备上报的事件。


命令帧格式 ​

[0x55][0xAA][LEN][CMD][PAYLOAD ...]
字段字节含义
Header2固定 0x55 0xAA,不符则丢弃整帧
LEN1其后字节数 = 1(CMD) + payload 长度,故 payload 长度 = LEN − 1
CMD1命令码(见下表)
PAYLOADLEN−1命令数据
  • 整帧长度 = 3 + LEN,单包 64B 内可容纳(LEN ≤ 61,payload ≤ 60)。
  • 多字节字段统一小端 (LE),与 HID 报告 / RP2350 原生字节序一致,可直接 memcpy。
  • 校验:header 错 / LEN 越界 / payload 被截断 → 丢弃,不上报。

命令一览 ​

CMD名称说明
0xFF直接触屏输出绕过映射引擎,直接发送触摸事件
0xFE鼠标报文标准 8B HID 鼠标报告
0xFD键盘报文标准 8B HID 键盘报告
0xFCcore_input 调度直接调用映射引擎接口
0xFBvmouse 状态输出虚拟鼠标状态转发到网络
0xFA自定义事件发送文本字符串到 Lua on_custom_event(str)
0xF8DS5 裸报文直通DualSense 原始输入报文(裁尾)注入映射引擎

CMD 0xFF — 直接触屏输出 ​

payload 10 字节。直接调 touch_queue_push,绕过映射引擎。

55 AA 0B FF [action:u8] [id:u8] [x:i32 LE] [y:i32 LE]
偏移字段类型说明
0actionu81=DOWN / 2=MOVE / 3=UP
1idu8触点 ID
2–5xi32 LEX 坐标 (0..0x7FFFFFFE)
6–9yi32 LEY 坐标 (0..0x7FFFFFFE)

tip 由 action 推导(DOWN/MOVE → 按下,UP → 抬起),contact_count=1。


CMD 0xFE — 鼠标报文 ​

payload 8 字节,标准 mouse_report_x8。复用 HID 鼠标解析:按键边沿 → core_input_mouse_button;x/y/wheel → core_input_mouse_move。

55 AA 09 FE [report_id:u8] [buttons:u8] [x:i16 LE] [y:i16 LE] [wheel:i8] [reserved:u8]
偏移字段类型说明
0report_idu8device 模式忽略,填 0
1buttonsu8bit0 左 / bit1 右 / bit2 中 / bit3 前进 / bit4 后退
2–3xi16 LEX 位移
4–5yi16 LEY 位移
6wheeli8滚轮
7reservedu80

CMD 0xFD — 键盘报文 ​

payload 8 字节,标准 keyboard_report_x8。复用 HID 键盘解析:修饰键边沿与普通键按下/释放统一走 core_input_keyboard(修饰键为 0xE0-0xE7 标准 HID 键码)。

55 AA 09 FD [modifiers:u8] [reserved:u8] [keys[6]:u8×6]
偏移字段类型说明
0modifiersu8bit0 LCtrl … bit7 RGUI
1reservedu80
2–7keys[6]u8×6当前按下的键码

CMD 0xFC — core_input 调度 ​

payload[0] = subcmd,之后为对应参数。

subcmd调用函数payload(subcmd 之后)LEN
0xFFcore_input_mouse_movedx:i32 + dy:i32 + wheel:i32 (12B)14 (0x0E)
0xFEcore_input_mouse_buttonbutton:u8 + down:u8 (2B)4
0xFCcore_input_keyboardkeycode:u8 + down:u8 (2B)4
0xFBcore_input_orientationorientation:u8 (1B)3
0xF9core_input_gamepad_abs / core_input_gamepad_btnls_x:i16 + ls_y:i16 + rs_x:i16 + rs_y:i16 + lt:i16 + rt:i16 + buttons:u32 (16B)18 (0x12)

down 字段:0 = 释放,非 0 = 按下。


CMD 0xFB — vmouse 状态输出 ​

payload 9 字节,vmouse_t 内存二进制。由 Pico 直接转发到网络侧(TCP vmouse 通道 + UDP cursor),不做触摸转换。

55 AA 0A FB [x:i32 LE] [y:i32 LE] [state:u8]
偏移字段类型说明
0–3xi32 LE光标 X(屏幕像素坐标)
4–7yi32 LE光标 Y
8stateu8bit0=show / bit1=down

上位机 struct.pack("<iiB", x, y, state)。LEN = 1(CMD) + 9 = 10 (0x0A)。


CMD 0xFC 子命令 0xF9 — 手柄报文 ​

payload 16 字节。将上位机采集的标准手柄状态注入映射引擎,直接调用 core_input_gamepad_abs + core_input_gamepad_btn。

55 AA 12 FC F9 [ls_x:i16 LE] [ls_y:i16 LE] [rs_x:i16 LE] [rs_y:i16 LE] [lt:i16 LE] [rt:i16 LE] [buttons:u32 LE]
偏移字段类型说明
0–1ls_xi16 LE左摇杆 X 轴(-32767..32767,左负右正)
2–3ls_yi16 LE左摇杆 Y 轴(-32767..32767,上负下正)
4–5rs_xi16 LE右摇杆 X 轴
6–7rs_yi16 LE右摇杆 Y 轴
8–9lti16 LE左扳机(0..32767,模拟值)
10–11rti16 LE右扳机(0..32767,模拟值)
12–15buttonsu32 LE按键 bitmap(20 个按键,bit 位含义见下方映射表)

摇杆值范围对应 int16 满量程,由 Web 端 Gamepad API 的 axes[i](-1..+1)缩放得到。

摇杆映射行为:

  • 左摇杆 → WASD 轮盘映射(与 WASD 按键不共存)。圆形死区判定(默认 6.25%),摇杆推力的幅度与角度决定屏幕上轮盘触点的位置。幅度控制半径(推到底=疾跑距离),方向精确跟随。
  • 右摇杆 → 映射开启时控制视角移动(极坐标曲线+积分);映射关闭时移动虚拟鼠标指针。支持可配置的死区、曲线和灵敏度。
  • LT/RT → 当前版本暂存值,不做主动映射。RT 在 vmouse 模式下有特殊行为(自动转为鼠标左键)。

按键 bitmap 位映射(与固件 pio_gamepad_btn_map 一致,共 20 位):

bit(十进制位号)按键码(十六进制)按键名
00x10十字键 上
10x11十字键 下
20x12十字键 左
30x13十字键 右
40x0ASTART
50x09SELECT
60x0BLS(左摇杆按下)
70x0CRS(右摇杆按下)
80x05LB / L1
90x06RB / R1
100x0DHOME / Guide
110x0EMISC
120x01A / Cross
130x02B / Circle
140x03X / Square
150x04Y / Triangle
160x07LT / L2(数字边沿)
170x08RT / R2(数字边沿)
180x14EXTRA_1
190x15EXTRA_2

按键采用边沿检测:上一帧到当前帧的差分触发 core_input_gamepad_btn,与键盘/鼠标边沿逻辑一致。由映射引擎处理按键→映射查询;若有 Lua 脚本声明了 on_gamepad_btn,则 Lua 先拦截。

手柄参数配置(持久化存储):

手柄的摇杆死区、曲线、灵敏度等参数可通过 HTTP API 配置。「预览」只写设备内存、 立即生效;「保存」才写入 flash —— 反复试参数不会磨损 flash,也不会因为写 flash 冻结总线而打断 USB:

接口方法说明
/gamepad/paramsGET返回当前生效参数 blob(122 字节,application/octet-stream)
/gamepad/paramsPOST上传参数 blob 即时生效 并持久化(=保存)
/gamepad/previewPOST上传参数 blob 只写内存即时生效,不碰 flash(=预览)
/gamepad/exitPOST放弃预览:从 flash 重读覆盖内存并重新应用(不写 flash);随后 GET /gamepad/params 即得到恢复后的值
/gamepad/resetPOST恢复默认参数并删除持久化 key

开机、保存、恢复默认、放弃预览之后,"生效值"与 flash 内容一致;只有预览期间两者不同。 预览不跨重启,也不跨弹窗重新打开(面板打开时会先 /gamepad/exit 拉回 flash 值)。

面板上的「预览 / 放弃 / 保存」管整张弹窗:它们会同时作用于 gamepad_args 与 gyro_cal 两个存储项(各走各自的端点)。/gamepad/exit 自身只碰手柄参数—— 它内部会重置视角/鼠标的积分累加器,但与陀螺仪校准和遥测流没有任何关系。

默认参数:左/右摇杆死区 6.25%,回报率 500Hz,视角/鼠标灵敏度 1.0,曲线为空。详细参数格式见固件源码 gamepad_args.h。

陀螺仪校准(设备级,不跟槽位走):

三轴零偏 / 死区 / 静止阈值 / 自动校准,见 映射配置 → 陀螺仪校准。HTTP 接口:

接口方法说明
/gyro/calGET返回校准 blob(24 字节,application/octet-stream)
/gyro/calPOST上传 blob 即时生效 并持久化(=保存)
/gyro/cal/previewPOST上传 blob 只写运行期内存即时生效,不碰 flash(=预览)
/gyro/cal/exitPOST放弃预览:从 flash 重读覆盖运行期值(不写 flash);随后 GET /gyro/cal 即得到恢复后的值
/gyro/calGET返回生效值(预览中=预览值,否则=flash 值)
/gyro/cal/resetPOST恢复默认参数并删除持久化 key
/gyro/cal/startPOST开始手动校准(立即返回,结果经 WebSocket 回读)
/gyro/cal/cancelPOST取消进行中的校准
/gyro/cal/monitorPOSTbody "0" / "1" 开关实时遥测流(不持久化)。面板的判据是「分组展开 且 窗口在前台」,设备不会自己开或关它
/gyro/cal/paramsPOSTbody [flags u8][still_thr u16 LE],只改运行期参数(自动校准开关 + 静止阈值),不写 flash
/gyro/cal/stateGET返回 32 字节运行期快照

GET /gyro/cal/state 与 WebSocket 遥测帧同布局(见 WebSocket API → 陀螺仪遥测),字段顺序:flags(1) cal_state(1) cal_progress(1) 保留(1) raw[3]i16 pp[3]u16 bias[3]i16 dead[3]u16 still_threshold u16 保留 u16。其中 pp[3] 是三轴抖动幅度(1 秒窗口内最大值减最小值;恒定零偏在此值里被抵消)。


CMD 0xFA — 自定义事件 ​

发送文本字符串到 Lua,触发 on_custom_event(str)。payload 为 UTF-8 文本,最大 60 字节(受 64B HID 报告限制,帧头占 4B)。

55 AA LEN FA [text: UTF-8 string]
偏移字段类型说明
0–(N−1)textu8×NUTF-8 字符串,LEN = 1 + N,N ≤ 60

收到后直接调用 lua_binder_on_custom_event(text),无需 enable_listen。

配合 on_custom_event + string.match 解码参数可实现任意复杂逻辑,配合 print() 可经 WebSocket 向主机回传结果。

lua
function on_custom_event(str)
    -- 示例:接收 "click 500 800" 并点击对应坐标
    local x, y = string.match(str, "^click (%d+) (%d+)$")
    if x and y then
        local id = touch_down(tonumber(x), tonumber(y))
        touch_up(id)
        print("clicked at " .. x .. "," .. y)  -- 回传到 WebSocket 日志
        return true
    end
    return false
end

字符串超过 60 字节会被截断。长指令建议拆分为多条简短指令,或用协程 + tick 构成序列。


CMD 0xF8 — DS5 裸报文直通 ​

payload 40 字节。将 DualSense(DS5)HID 输入报文的前 40 字节原样转发给设备,固件补零还原成完整 64B 报文后走与 USB Host 口实体手柄完全相同的解析链(hidkit ds5_parse → core_input_gamepad_abs/btn/motion),摇杆/按键/陀螺仪/加速度计全部进映射引擎,语义与插在 Host 口的 DS5 一致。

55 AA 29 F8 [body 0..39 of dualsense_input_report]
偏移字段类型说明
0–3ls_x/ls_y/rs_x/rs_yu8×4摇杆,中值 128
4–5lt/rtu8×2模拟扳机,0..255
6seq_numberu8报文序号(可用于丢帧检测)
7–10buttonsu32 LE按键位域(DS5 原生布局,bit0–3 为十字键枚举)
11–14reserved4原样转发(占位不裁)
15–20gyro_x/y/zi16 LE×3陀螺仪,±32767 = ±2000 dps
21–26accel_x/y/zi16 LE×3加速度计,±32767 = ±4 g
27–30sensor_timestampu32 LE传感器时间戳
31reserved1原样转发
32–39touch_point ×24B×2触摸板触点(保留,后续适配)

裁切规则:只裁尾部。DS5 USB 输入报文 64B(含 Report ID 0x01),偏移 40 起的 reserved3[12] + status + reserved4[10] 共 23 字节为保留段,裁掉后 40B 载荷 + 4B 帧头 = 44B,恰在 64B HID 报文限制内。偏移 0–39 的字节(含中间保留占位)必须原样转发,不做重排/压缩。

蓝牙报文:DS5 蓝牙输入报文为 78B(Report ID 0x31),布局与 USB 相同但整体后移 1 字节;同样取 body 0–39 转发即可。

断连约定:全零 payload(40 字节全 0)= 上位机断开。真 DS5 报文里摇杆中值恒为 0x80,全零 body 物理上不会出现。固件收到后释放该通道的手柄状态(按住的键补发松开、摇杆/陀螺仪归零)。上位机正常断开时应主动补发一帧全零;未补发(如页面崩溃)则固件侧最后状态残留。

命令仅普通固件(PIO Device 模式)支持;DS5 蓝牙变体无 PIO 口,不响应 0xF8。


IN 端点事件 ​

设备通过 IN 端点主动上报事件,由事件队列驱动。

[0x55][0xAA][LEN][EVT][DATA ...]
EVT名称DATA触发条件
0x01方向变化orientation:u8 (0..3)屏幕方向改变时

WebHID 控制工具 ​

为了方便调试,提供了一个浏览器端的 WebHID 控制工具(PIO Device Sender),无需安装任何软件即可:

  • 连接设备 — 通过 WebHID API 直接选择并连接 Pico HID 设备
  • 命令构造 — 可视化构造上述所有 CMD(触屏/鼠标/键盘/core_input/Lua 事件/手柄报文),一键发送
  • 实时反馈 — 显示 sendReport 往返延迟(RTT),接收设备 IN 端点上报事件
  • 独占模式 — 锁定设备,防止其他应用干扰
  • 手柄可视化 — 采集电脑手柄(Gamepad API),实时显示摇杆位置、扳机力度、按钮状态,并通过 CMD 0xFC 子命令 0xF9 转发
  • DS5 直连 — 通过 WebHID 直读 DualSense 原始报文(CMD 0xF8 转发),连接期间 Gamepad API 转发自动关断;支持摇杆/按键/陀螺仪,触摸板触点已随报文保留

使用前提:PIO 口切换为 Device 模式,Chrome / Edge 浏览器,PIO-USB-C 口连接电脑。

工具为纯前端页面,所有数据在浏览器本地处理,不会上传到任何服务器。