PrimeBot 启元 Q1 & T1 技能整活工作台
ROBOT RUNNER · 现场真机指挥官

真机一键侦察、抓动作表与点火器源码精讲

告别手敲长串 ssh/scp!一条命令搞定真机体检、拉取机上 YAML 动作表、传包与抓取硬件日志

😜 调皮课代表大白话划重点(30 秒读懂核心)
到了现场面对真机紧张手抖?别怕,robot_run.py 就是你的特种部队侦察兵!第一遍运行(不加 --run):它只做安全侦察——连 SSH、看 58080 端口任务、把机器人肚子里的真实 YAML 动作表抓回 Doc/reports/robot_probe_*.txt,再把包传到 /tmp/;等你看清周围 1 米没人后,加上 --run 跑第二遍:它才正式启动技能并自动把硬件动作日志捞出来贴在你脸上!

🔍 核心机关与修改指南

🕵️ 先侦察后开火 两阶段安全设计

默认不带 --run 只探路传包不触发电机动作,严格配合现场「先核对动作表与 1 米安全净空」的铁律。

📜 自动归档探测报告 robot_probe_*.txt

每次运行都会在 Doc/reports/ 下生成带时间戳的完整探测报告,机上真实 YAML 动作表和运行结果全留痕。

🔍 自动捞取硬件账本 task_engine.log

加 --run 跑完后自动执行 grep "Animation task成功",一眼看穿电机到底动没动!

💻 完整源码与配置内容(共 102 行 · 支持行号定位与一键复制)

📄 共 102 行
1#!/usr/bin/env python3
2"""真机 SSH 环境探测、机上 YAML 动作表拉取、技能包上传与一键运行脚本(纯 Python 跨平台版)。"""
3
4import argparse
5import datetime
6import shutil
7import subprocess
8import sys
9from pathlib import Path
10
11TOOLS_ROOT = Path(__file__).resolve().parent.parent
12WORKSPACE_ROOT = TOOLS_ROOT.parent
13DEPLOY_SCRIPT = TOOLS_ROOT / "primehub_dev_kit/deploy_test.py"
14DIST_ROOT = TOOLS_ROOT / "dist"
15REPORTS_DIR = WORKSPACE_ROOT / "Doc/reports"
16
17
18def run_remote(target: str, cmd: str) -> str:
19 res = subprocess.run(
20 ["ssh", target, cmd],
21 capture_output=True,
22 text=True,
23 encoding="utf-8",
24 errors="replace",
25 )
26 if res.returncode != 0:
27 raise RuntimeError(f"远程命令执行失败:{cmd}\n{res.stdout}\n{res.stderr}")
28 return (res.stdout + res.stderr).strip()
29
30
31def main() -> int:
32 parser = argparse.ArgumentParser(description="启元 Q1 / T1 真机探测、传包与技能运行工具")
33 parser.add_argument("--model", required=True, choices=["q1", "t1"], help="机器人型号:q1 或 t1")
34 parser.add_argument("--ip", required=True, help="机器人局域网 IP,例如 192.168.112.222")
35 parser.add_argument(
36 "--skill",
37 default="universal_skill",
38 choices=["universal_skill", "energy_station"],
39 help="要上传/运行的技能名称(默认 universal_skill)",
40 )
41 parser.add_argument("--run", action="store_true", help="上传后立即在真机上启动技能并抓取硬件日志")
42 args = parser.parse_args()
43
44 for cmd in ("ssh", "scp"):
45 if not shutil.which(cmd):
46 raise SystemExit(f"找不到 {cmd} 命令,请先确认系统已安装 OpenSSH 客户端。")
47
48 pkg_path = DIST_ROOT / f"{args.skill}_{args.model}.rpk"
49 if not pkg_path.exists():
50 raise SystemExit(f"找不到技能包 {pkg_path},请先运行 python Tools/scripts/build.py 构建。")
51
52 REPORTS_DIR.mkdir(parents=True, exist_ok=True)
53 ts = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
54 report_path = REPORTS_DIR / f"robot_probe_{args.model}_{ts}.txt"
55 target = f"run@{args.ip}"
56 remote_pkg = f"/tmp/{args.skill}_{args.model}.rpk"
57
58 lines = [
59 f"# 真机探测记录 {datetime.datetime.now().isoformat(timespec='seconds')}",
60 f"型号: {args.model.upper()}",
61 f"机器人: {args.ip}",
62 f"技能包: {pkg_path.name}",
63 ]
64
65 def record(title: str, cmd: str) -> None:
66 print(f"== {title} ==")
67 out = run_remote(target, cmd)
68 lines.extend([f"\n## {title}", f"> {cmd}", out])
69
70 record("连接探测", "echo ROBOT_SSH_OK")
71 record(
72 "运行环境与残留任务",
73 "python3 --version; curl -fsS --max-time 5 http://127.0.0.1:58080/packages; echo; "
74 "curl -fsS --max-time 5 http://127.0.0.1:58080/tasks/running",
75 )
76
77 if args.model == "q1":
78 record("Q1 upper_body 机上动作表", "cat /robot/software/task_engine/bin/cfg/motions/q1/upper_body.yaml")
79 else:
80 record("T1 机上动作表", "cat /robot/software/task_engine/bin/cfg/motions/t1/*.yaml")
81
82 print(f"== 上传调试工具与 {pkg_path.name} ==")
83 subprocess.run(["scp", str(DEPLOY_SCRIPT), f"{target}:/tmp/deploy_test.py"], check=True)
84 subprocess.run(["scp", str(pkg_path), f"{target}:{remote_pkg}"], check=True)
85 lines.extend(["\n## 已上传文件", "deploy_test.py -> /tmp/deploy_test.py", f"{pkg_path.name} -> {remote_pkg}"])
86
87 if args.run:
88 record("真机技能运行结果", f"python3 /tmp/deploy_test.py {remote_pkg} 120")
89 record(
90 "硬件动作日志核验",
91 'grep "Animation task成功" /robot/persist/log/task_engine/task_engine.log | tail -12',
92 )
93 else:
94 print("探测与传包已完成。核对动作表与安全环境后,追加 --run 即可启动真机运行。")
95
96 report_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
97 print(f"探测日志已归档:{report_path}")
98 return 0
99
100
101if __name__ == "__main__":
102 raise SystemExit(main())