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/方向键无效
- 网页与脚本不可同时连接,会互相覆盖帧
This commit is contained in:
commit
6dd1fa7e24
11 files changed
+1971
No files matched your search
@@ -0,0 +1,93 @@
|
||||
#!/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
|
||||
Reference in new issue
Block a user