Files
wlpro-robot-ctl/docs/PROTOCOL.md
T
deepseek Bot 6dd1fa7e24 release: v1.0 - WL-PRO 轮组机器人遥控脚本
反向实现设备 WebSocket 控制协议,提供单次命令与常驻长连接两种模式。

核心内容:
- robot-ctl.py     单次命令客户端,纯标准库无依赖
- robot-daemon.py  常驻守护进程,维持唯一长连接,
                   解决断开丢尾帧导致的速度残留
- rc.sh            向守护进程注入动作,内置 stable=1 硬锁
- docs/PROTOCOL.md 协议逆向与踩坑记录
- tests/fake-robot-ws.js 离线验证用假设备

实测标定 (50% 速度):
- 前进/后退 ~8 cm/s
- 左转/右转 ~35 deg/s

已知限制:
- 设备为 Arduino WebSocketsServer,纯单向,不回传状态
- 仅摇杆与跳跃有效,linear/angular/方向键无效
- 网页与脚本不可同时连接,会互相覆盖帧
2026-10-25 17:53:11 +08:00

182 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 协议逆向笔记
记录 WL-PRO 轮组机器人的通信协议,以及踩过的坑。内容全部来自实测,不是推测。
---
## 设备识别
WebSocket 握手响应头泄露了固件身份:
```
HTTP/1.1 101 Switching Protocols
Server: arduino-WebSocketsServer
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Version: 13
Sec-WebSocket-Accept: ...
```
结论:固件基于 **Arduino WebSocketsServer** 库(ESP32 / Arduino 平台)。
这个库的关键特性:**纯单向**。
- 握手完成后不发送任何数据
- 收到客户端帧后不返回任何 ack 或状态
- 因此**永远无法确认动作是否执行**,只能靠肉眼观察
---
## 端点
| 用途 | 地址 |
|---|---|
| 遥控页 | `http://<device>/` |
| 控制通道 | `ws://<device>:81/` |
| WiFi 设置 | `http://<device>/wifi` |
控制通道固定 **81 端口**,路径为 `/`。
---
## 帧格式
完整的 JSON 文本帧,每次发送必须包含全部字段:
```json
{
"dir": "stop",
"mode": "basic",
"roll": 0,
"height": 38,
"linear": 0,
"angular": 0,
"stable": 1,
"joy_x": 0,
"joy_y": 50
}
```
### 字段详解
| 字段 | 范围 | 有效 | 说明 |
|---|---|---|---|
| `joy_y` | -100 ~ 100 | ✅ | 纵向摇杆。正值前进,负值后退 |
| `joy_x` | -100 ~ 100 | ✅ | 横向摇杆。正值右转,负值左转 |
| `dir` | 字符串 | ✅ 仅 `jump` | `jump` 触发跳跃;其余值(含 `forward`/`back`/`left`/`right`)**无效** |
| `stable` | 0 / 1 | ✅ | 主动保持平衡开关。1 = 开启 |
| `height` | 32 ~ 85 | ✅ | 站高,单位 mm |
| `roll` | -30 ~ 30 | ❌ | 滑块存在但设备侧无响应 |
| `linear` | -200 ~ 200 | ❌ | 单位 mm/s,设备侧无响应 |
| `angular` | -100 ~ 100 | ❌ | 单位 °/s,设备侧无响应 |
| `mode` | 字符串 | — | 固定 `basic` |
**只有摇杆(`joy_x`/`joy_y`)、跳跃(`dir:"jump"`)、`stable`、`height` 真正生效。**
`roll` / `linear` / `angular` 三个滑块在网页上有 UI,但拖动了机器人没反应——用户确认过,不要浪费时间试。
---
## 为什么必须常驻连接
这是本项目最重要的一条经验。
### 现象
早期实现是「每次命令新建连接 → 推几百毫秒帧 → 断开」。结果:
```
17:32 执行 joy_y=-60 持续 600ms,命令已退出
17:36 用户报告「还在退」
```
**命令结束四分钟后,机器人还在移动。**
### 原因
设备对短连接的处理不可靠:客户端推完动作帧后立刻发 WebSocket close 帧,**归零帧可能随连接关闭一起丢失**,设备保留最后一次收到的速度值。
设备是纯单向的,不会告诉我们它记住了什么。
### 解法
保持**单条长连接永不主动断开**:
- 空闲时每 120ms 推一帧全零(`dir:"stop"`, `joy:0,0`),与网页空闲时的行为一致
- 动作通过状态文件注入,带到期时间,到期自动归零
- 断开前先补 12 帧零,再发 close
`robot-daemon.py` 实现的就是这个模式。
---
## 网页抢帧冲突
网页里这个定时器是隐形杀手:
```js
setInterval(function () {
send_data(); // dir 恒为 "stop",joy_x/joy_y 取摇杆当前位置
}, 150);
```
不动摇杆时摇杆自动回中,所以它**每 150ms 发一帧 `joy=0,0`**。
如果网页和脚本同时连着设备,两个独立 WS 客户端的帧会交错:
```
脚本: joy_y = -60
网页: joy_y = 0 ← 150ms 后
脚本: joy_y = -60
网页: joy_y = 0
```
设备收到的是来回跳变的矛盾指令,表现就是"几乎没动"或原地抖动。
**结论:操作前必须关掉网页标签页**(关闭,不是最小化)。
同理,网页的方向键是 `mousedown` 发一帧 `dir`、`mouseup` 发一帧 `stop`,中间还有 150ms 定时器插一帧 `stop`——**实际效果是 ≤150ms 的瞬间点动**,不是持续移动。
早期我把 `dir:"forward"` 连推 1 秒(7 帧),等于连下 7 次动作指令不给停止,姿态控制被打乱,机器人直接摔倒。
---
## 非线性响应
速度与 `joy` 值不是线性的:
| `joy` 绝对值 | 实测表现 |
|---|---|
| 20 | 几乎不动 |
| 30 | 前进 3 秒变化很小 |
| 50 | ≈ 8 cm/s(可用基准) |
| 80 | 明显更快 |
**低于约 50% 推进力不足**。标定表只在 50% 附近有效,换速度需重新标定。
---
## 远程访问
设备通常藏在局域网里。远程控制需要一条端口隧道:
- frpc 客户端在局域网机器上
- 代理类型必须选 **tcp**(`http`/`https` 是 frps 的虚拟主机模式,需要 `vhostHTTPPort`,通常没开)
- `localIP` 必须填**设备真实 IP**,不能填 `127.0.0.1`(那是客户端自己)
- `localPort` = 81,`remotePort` = 云服务器上已放行的端口
云服务器安全组必须放行 `remotePort`。被 DROP 的端口表现为连接超时(不是拒绝),很容易误判成网络问题。
---
## 调试技巧
判断隧道是否真的打通,看 frps 的**出向流量**:
```
in=579B / out=0B ← 后端没收到,或连错了对象
in=2155B / out=964B ← 通了
```
`in` 是我们发出的字节,`out` 是设备返回的字节。纯单向设备握手后不再说话,但**握手响应本身**会让 `out` 增长。`out=0` 说明数据根本没到达设备。