Skip to content

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]
偏移字段类型说明
0CMDu8固定 0x01
1–128textUTF-8文本内容,最大 128 字节,超出截断

示例 ​

浏览器

Web UI 内建输入框(PICO 配置 → 管理脚本 → 底部),直接输入文本发送。

命令行(websocat)

bash
echo -ne '\x01tap(540,1200)\n' | websocat ws://192.168.73.1/ws

Python

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
end

WebSocket 与 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 字节固定布局(小端):

偏移字段类型说明
0CMDu8固定 0x02
1flagsu8bit0 自动校准开|bit1 监视流开|bit2 当前静止|bit3 校准进行中|bit4 六轴在线
2cal_stateu80 未校准|1 等待静止|2 校准中|3 完成|4 失败(移动)|5 失败(无六轴)
3cal_progressu80~100(仅状态 2 有意义)
4保留u8
5–10raw[3]i16 ×3最近一帧角速度(±32767 = ±2000 dps)
11–16pp[3]u16 ×3最近 1 秒窗口内三轴抖动幅度(raw;= 最大值 − 最小值,恒定零偏在此值里被抵消)
17–22bias[3]i16 ×3当前生效三轴零偏(raw)
23–28dead[3]u16 ×3当前生效三轴死区(raw)
29–30still_thresholdu16当前静止阈值(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 字节(小端):
偏移字段类型说明
0CMDu8固定 0x03
1–4xi320..0x7FFFFFFE(已含 contain 缩放,未旋转)
5–8yi32同上
9stateu8bit0 = 光标显示中|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