Files
wlpro-robot-ctl/rc.sh
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

94 lines
3.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# 通过常驻守护进程注入动作(只写状态文件,不自己开连接)
#
# 前提:robot-daemon.py 正在运行,保持唯一长连接。
#
# 用法:
# bash rc.sh go -40 800 # 后退 40% 速度 0.8 秒
# bash rc.sh go 40 800 # 前进
# bash rc.sh strafe 40 800 # 右转(负值左转)
# bash rc.sh joy 30 -40 800 # 任意摇杆组合
# bash rc.sh jump # 跳一下
# bash rc.sh stop # 立即归零
# bash rc.sh status # 看当前注入状态 + 守护进程是否活着
# bash rc.sh hold # 持续注入零帧(确保静止,默认 5 秒)
set -u
D="$(cd "$(dirname "$0")" && pwd)"
STATE="${RC_STATE:-$D/daemon-state.json}"
PIDF="$D/daemon.pid"
die() { echo "[rc] $*" >&2; exit 1; }
alive() {
[ -f "$PIDF" ] || return 1
local pid; pid="$(cat "$PIDF" 2>/dev/null || true)"
[ -n "$pid" ] || return 1
kill -0 "$pid" 2>/dev/null || return 1
return 0
}
write_state() { # joy_x joy_y dir seconds
local jx="$1" jy="$2" dr="$3" secs="$4"
local until
if [ "${secs:-0}" = "0" ]; then
until=0
else
until="$(python3 -c "import time;print(f'{time.time()+float($secs):.3f}')")"
fi
python3 - "$STATE" "$jx" "$jy" "$dr" "$until" <<'PY'
import json, sys, os, tempfile
path, jx, jy, dr, until = sys.argv[1], int(sys.argv[2]), int(sys.argv[3]), sys.argv[4], float(sys.argv[5])
data = {"joy_x": jx, "joy_y": jy, "dir": dr, "until": until}
d = os.path.dirname(os.path.abspath(path))
fd, tmp = tempfile.mkstemp(dir=d)
with os.fdopen(fd, "w") as f:
json.dump(data, f)
os.replace(tmp, path)
PY
}
CMD="${1:-}"; shift || true
alive || die "守护进程未运行。先启动:setsid nohup python3 $D/robot-daemon.py > $D/daemon.log 2>&1 &"
# 平衡开关硬锁:拒绝关闭
case "$CMD" in
stable|balance)
die "拒绝:主动保持平衡不允许关闭(用户明确指令)。"
;;
esac
case "$CMD" in
go)
Y="${1:-50}"; MS="${2:-800}"
write_state 0 "$Y" stop "$(python3 -c "print(float($MS)/1000)")"
echo "[rc] 摇杆 y=$Y 持续 ${MS}ms" ;;
strafe)
X="${1:-50}"; MS="${2:-800}"
write_state "$X" 0 stop "$(python3 -c "print(float($MS)/1000)")"
echo "[rc] 摇杆 x=$X 持续 ${MS}ms" ;;
joy)
X="${1:-0}"; Y="${2:-0}"; MS="${3:-800}"
write_state "$X" "$Y" stop "$(python3 -c "print(float($MS)/1000)")"
echo "[rc] 摇杆 x=$X y=$Y 持续 ${MS}ms" ;;
jump)
MS="${1:-300}"
write_state 0 0 jump "$(python3 -c "print(float($MS)/1000)")"
echo "[rc] 跳 ${MS}ms" ;;
stop)
write_state 0 0 stop 0
echo "[rc] 已归零" ;;
hold)
MS="${1:-5000}"
write_state 0 0 stop "$(python3 -c "print(float($MS)/1000)")"
echo "[rc] 保持零帧 ${MS}ms" ;;
status)
echo "[rc] 守护进程 PID: $(cat "$PIDF")"
echo "[rc] 状态文件: $STATE"
cat "$STATE" 2>/dev/null || echo "(无)"
echo
echo "[rc] 最近日志:"; tail -6 "$D/daemon.log" 2>/dev/null ;;
*)
sed -n '2,16p' "$0" | sed 's/^# \{0,1\}//'; exit 1 ;;
esac