# 协议逆向笔记 记录 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:///` | | 控制通道 | `ws://:81/` | | WiFi 设置 | `http:///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` 说明数据根本没到达设备。