主题
WebSocket API
📥 下载此API文档:ws-api.md
设备在 80 端口提供 WebSocket 服务,用于双向通信。
- USB 直连(需要断开其他网络):
ws://192.168.73.1/ws - vPointer 转发:
ws://<手机IP>:8000/ws
发送自定义事件 → 设备
客户端发送二进制帧,触发 Lua 的 on_custom_event(str)。
帧格式
[0x01][UTF-8 text]| 偏移 | 字段 | 类型 | 说明 |
|---|---|---|---|
| 0 | CMD | u8 | 固定 0x01 |
| 1–128 | text | UTF-8 | 文本内容,最大 128 字节,超出截断 |
示例
浏览器
Web UI 内建输入框(PICO 配置 → 管理脚本 → 底部),直接输入文本发送。
命令行(websocat)
bash
echo -ne '\x01tap(540,1200)\n' | websocat ws://192.168.73.1/wsPython
python
import websocket
ws = websocket.create_connection("ws://192.168.73.1/ws")
ws.send_binary(b"\x01click 500 800")
ws.close()Lua 接收
lua
function on_custom_event(str)
print("收到: " .. str) -- 回显到日志
-- 解析指令并执行...
return true
endWebSocket 与 HID CMD 0xFA 都是触发
on_custom_event,区别:WS 最大 128 字节,HID 最大 60 字节。print()输出经 WS 回传到所有连接的客户端。
接收日志 ← 设备
设备主动推送文本帧,包含两类日志:
| 来源 | 说明 |
|---|---|
print() / warn() | Lua 脚本中调用,输出调试信息 |
| 系统日志 | 固件运行状态、错误提示等 |
客户端只需监听 WebSocket 的 onmessage 事件即可收到。
帧格式为二进制帧:首字节是命令字。
0x01= 日志文本(正文 UTF-8,可能含嵌入换行);0x02= 陀螺仪遥测(见下一节);0x03= 光标位置帧(见下下节)。客户端应先看首字节再决定怎么解析—— 只当文本处理的旧客户端遇到0x02/0x03应直接忽略。
示例
浏览器
js
const ws = new WebSocket("ws://192.168.73.1/ws");
ws.onmessage = async (e) => {
const bytes = new Uint8Array(e.data instanceof Blob ? await e.data.arrayBuffer() : e.data);
if (bytes[0] !== 0x01) return; // 0x02 = 陀螺仪遥测,此处忽略
console.log("[Pico]", new TextDecoder().decode(bytes.slice(1)));
};Python
python
import websocket
def on_message(ws, message):
print("[Pico]", message)
ws = websocket.WebSocketApp("ws://192.168.73.1/ws", on_message=on_message)
ws.run_forever()命令行
bash
websocat ws://192.168.73.1/ws
# 直接打印所有日志陀螺仪遥测帧 (0x02) ← 设备
在「手柄参数 → 陀螺仪校准」分组展开、且窗口在前台(即开启监视流)或手动校准进行中时, 设备以 20 Hz 推送三轴角速度与校准状态。帧为二进制,首字节 0x02,其后 32 字节固定布局(小端):
| 偏移 | 字段 | 类型 | 说明 |
|---|---|---|---|
| 0 | CMD | u8 | 固定 0x02 |
| 1 | flags | u8 | bit0 自动校准开|bit1 监视流开|bit2 当前静止|bit3 校准进行中|bit4 六轴在线 |
| 2 | cal_state | u8 | 0 未校准|1 等待静止|2 校准中|3 完成|4 失败(移动)|5 失败(无六轴) |
| 3 | cal_progress | u8 | 0~100(仅状态 2 有意义) |
| 4 | 保留 | u8 | |
| 5–10 | raw[3] | i16 ×3 | 最近一帧角速度(±32767 = ±2000 dps) |
| 11–16 | pp[3] | u16 ×3 | 最近 1 秒窗口内三轴抖动幅度(raw;= 最大值 − 最小值,恒定零偏在此值里被抵消) |
| 17–22 | bias[3] | i16 ×3 | 当前生效三轴零偏(raw) |
| 23–28 | dead[3] | u16 ×3 | 当前生效三轴死区(raw) |
| 29–30 | still_threshold | u16 | 当前静止阈值(raw) |
| 31–32 | 保留 | u16 |
三轴顺序恒为 X(俯仰)/ Y(偏航)/ Z(横滚)。
监视流的开关完全由面板决定(展开且有焦点才开),设备不会自己开或关它——包括 手动校准跑完的时候:那时
cal_state会停在 3 / 4 / 5,只要监视流还在推,客户端下一帧 就读得到;面板收起/失焦/关弹窗时流才停。因此客户端应把"收到状态 3/4/5"当作本次校准结束。 若在没有监视流时也要查状态,可用GET /gyro/cal/state(同布局,不含首字节 CMD)。
光标位置帧 (0x03) ← 设备
「参数配置 → 光标显示方式」选**融合器光标(6)**时,设备把虚拟光标位置经 WebSocket 推送 (面板 #cursor 绿幕页即为此设计:在 OBS 添加浏览器源填 http://<设备IP>/#cursor, 加「色键」滤镜抠掉绿色,即可把指针叠加到直播画面)。
- 触发:脏驱动——位置或状态(显示/按下)每次变化推一帧,无固定频率(与 vPointer app 的 TCP 推流同一条链路,最快约 1 kHz)。其它光标方式下不推送。
- 坐标系:
0 .. 0x7FFFFFFE归一化,已含 contain 缩放(外接显示器黑边)、不含旋转 ——左上角为原点,与 vPointer app 的光标同一口径。前端换算:px = x / 0x7FFFFFFE * 窗口宽、py = y / 0x7FFFFFFE * 窗口高,页面任意尺寸自适应。 - 帧为二进制,共 10 字节(小端):
| 偏移 | 字段 | 类型 | 说明 |
|---|---|---|---|
| 0 | CMD | u8 | 固定 0x03 |
| 1–4 | x | i32 | 0..0x7FFFFFFE(已含 contain 缩放,未旋转) |
| 5–8 | y | i32 | 同上 |
| 9 | state | u8 | bit0 = 光标显示中|bit1 = 按下中(点击本身仍由触屏接口产生真实触摸,与 vPointer app 模式相同;此位只供前端做按下缩小等视觉反馈) |
示例
浏览器
js
const ws = new WebSocket("ws://192.168.73.1/ws");
ws.binaryType = "arraybuffer";
ws.onmessage = (e) => {
const b = new Uint8Array(e.data);
if (b[0] !== 0x03) return; // 0x01 日志 / 0x02 陀螺,忽略
const v = new DataView(b.buffer);
const x = v.getInt32(1, true), y = v.getInt32(5, true);
const show = !!(b[9] & 1), down = !!(b[9] & 2);
cursor.style.transform =
`translate(${x / 0x7FFFFFFE * innerWidth}px, ${y / 0x7FFFFFFE * innerHeight}px)`;
cursor.style.display = show ? "block" : "none";
};命令行观察原始帧
websocat -b ws://192.168.73.1/ws | xxd