Files
TG3/docs/天工3.0本地同构臂遥操迁移部署指南.md

1134 lines
44 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.
# 天工 3.0 本地同构臂遥操迁移部署指南
适用范围:天工 3.0(HBWALK)+ TS1P 同构臂。当前灵巧手实现适用于机器人实际配置为
BrainCo Revo2 的情况。
## 1. 迁移前先区分四种地址
| 地址/标识 | 示例 | IP 变化时是否修改运行配置 |
|---|---|---|
| 新机器人 SSH 地址 | `nvidia@192.168.41.2` | 只影响安装、维护命令;公网 OmniSocket 运行时不使用这个地址 |
| EAI SSH 地址/局域网 IP | `eai` / `192.168.5.14` | 公网模式只影响运维;第 6 节本地 OmniSocket Hub 模式下,机器人必须连接该局域网 IP |
| OmniSocket Hub 地址 | `175.178.116.187:14049` | 公网模式两端使用公网地址;本地模式 EAI sender 用回环地址、机器人用 EAI 局域网地址 |
| OmniSocket Peer ID | `tg3-...-iarm/robot` | 每套链路必须成对匹配;换机器人时建议使用新的机器人唯一 ID |
当前公网模式下,机器人从 Wi-Fi 换到有线、DHCP 地址变化或 EAI 局域网地址变化,通常都
不需要修改运行配置,只需保证两端能主动访问 Hub 的 UDP 端口。新的 SSH 地址需要更新到
运维命令或设备清单中。
`config.toml` 中保留的 `iarm_endpoint` 只在 `transport="zmq"` 时使用;当前
`transport="omnisocket"` 时它被忽略。不要因为机器人或 EAI IP 变化而修改这个字段。
## 2. 每次迁移必须确认或重做的项目
### 2.1 必须使用唯一、成对匹配的 Peer ID
推荐命名:
```text
EAI 发送端:tg3-<机器人编号>-iarm
机器人接收端:tg3-<机器人编号>-robot
```
三处必须满足:
```text
EAI --peer-id == 机器人 omnisocket_expected_sender
EAI --target-peer == 机器人 omnisocket_peer_id
公网模式:EAI --server == 机器人 omnisocket_server
```
第 6 节本地 Hub 模式是地址表示的例外:EAI 用 `127.0.0.1:14049`,机器人用
`<EAI局域网IP>:14049`,两者必须指向同一个 EAI 本机 Hub 进程。
同一台 EAI 同一时刻只应控制一台机器人。现有发送服务只有一个 `--target-peer`,迁移到
另一台机器人后要修改目标 Peer 并重启发送服务,不要用一套 TS1P 同时向多台机器人发指令。
### 2.2 新机器人必须重新保存 Home
不要把当前机器人的 `home.joint_goal_rad` 直接用于另一台机器人。即使型号相同,机械零位、
装配偏差和期望停放姿态也可能不同。
在新机器人上:
1. 使用厂家认可的方法把双臂移动到希望保存的 Home;不要强行扳动上电电机;
2. 确认本地遥操为 `armed=false`;
3. 读取实测位置:
```bash
python3 -c 'import json; p="/home/nvidia/tg3_local_teleop/status.json"; print(json.load(open(p))["robot_arm_position_rad"])'
```
4. 将输出的 14 个弧度值原样写入新机器人
`/home/nvidia/tg3_local_teleop/config.toml` 的 `[home].joint_goal_rad`;
5. 重启服务后,先检查状态,再在净空和急停可用的条件下测试限速 Home。
### 2.3 必须确认灵巧手型号
```bash
grep -E 'left_hand_type|right_hand_type' /home/nvidia/data/param/hand_driver.yaml
```
只有左右都显示 `brainco` 时,才能直接使用当前 `[hands]` 配置。如果是 `inspire` 或其他
型号,不要启动灵巧手控制;消息类型、Topic 和关节映射不同,需要单独适配。
BrainCo 新手初次部署时应在默认打开状态记录:
```bash
python3 -c 'import json; p="/home/nvidia/tg3_local_teleop/status.json"; d=json.load(open(p)); print(d["robot_hand_positions"])'
```
当前打开端点约为 `[400,400,50,50,50,50]`。若新机器明显不同,需要按
`normalized=(position-1)/999` 重新计算 `[hands].open_normalized`。闭合端点也应从小幅、
低速测试开始确认,不能直接假设所有手的机械校准完全相同。
### 2.4 确认机器人软件接口
至少确认以下 Topic/消息仍存在:
```text
/hric/robot/rl_state
/freq_change/arm_status
/encoder_identical_joint
/left_hand/set_motor_multi
/right_hand/set_motor_multi
/left_hand/motor_status
/right_hand/motor_status
/hric/robot/cmd_vel
```
若新机器人不是天工 3.0、SDK 版本接口有变化、不是 14 维双臂或不是 BrainCo Revo2,先停止
部署并适配,不要仅靠修改 IP 强行运行。
## 3. 场景 A:保留当前 EAI,只换机器人
这是推荐迁移方式。EAI 上的 xTELE、OmniSocketGo 和发送脚本不需要重新安装。
### 3.1 准备变量
以下变量只用于本次终端命令,不写入服务:
```bash
export TG3_NEW_ROBOT_IP=192.168.41.XX
export TG3_NEW_ROBOT_PEER=tg3-<新机器人编号>-robot
export TG3_IARM_PEER=tg3-<新机器人编号>-iarm
export TG3_HUB_ADDR=175.178.116.187:14049
```
不要使用旧机器人和新机器人相同的 Robot Peer ID。如果 EAI Peer ID 也随机器人编号更换,
机器人 `omnisocket_expected_sender` 必须同步更新。
### 3.2 在新机器人原生编译 OmniSocket Python 扩展
开发机上的 OmniSocket 扩展是 x86_64,而天工 3.0 机器人是 aarch64,不能复制已编译的
`.so`。传输源码时排除已有二进制,在机器人上重新编译:
```bash
ssh nvidia@"${TG3_NEW_ROBOT_IP}" 'mkdir -p /home/nvidia/OmniSocketGo'
rsync -av \
--exclude=.git \
--exclude=bin \
--exclude=python/build \
--exclude='python/omnisocket/_omnisocket*.so' \
/home/ps/Desktop/OmniSocketGo/ \
nvidia@"${TG3_NEW_ROBOT_IP}":/home/nvidia/OmniSocketGo/
ssh nvidia@"${TG3_NEW_ROBOT_IP}" \
'cd /home/nvidia/OmniSocketGo && make python-ext'
```
验证扩展架构和导入:
```bash
ssh nvidia@"${TG3_NEW_ROBOT_IP}" \
'uname -m; find /home/nvidia/OmniSocketGo/python -name "_omnisocket*.so" -print'
ssh nvidia@"${TG3_NEW_ROBOT_IP}" \
'PYTHONPATH=/home/nvidia/OmniSocketGo/python python3 -c "from omnisocket import Session; print(Session)"'
```
预期机器人架构为 `aarch64`,扩展文件名包含 `aarch64` 和机器人实际 Python 版本。
### 3.3 部署机器人桥,但暂不启动
```bash
rsync -av \
--exclude=__pycache__ \
--exclude=status.json \
/home/ps/Downloads/TG3/tg3_local_teleop/ \
nvidia@"${TG3_NEW_ROBOT_IP}":/home/nvidia/tg3_local_teleop/
ssh nvidia@"${TG3_NEW_ROBOT_IP}" \
'mkdir -p /home/nvidia/.config/systemd/user && cp /home/nvidia/tg3_local_teleop/tg3-local-teleop.service /home/nvidia/.config/systemd/user/'
```
此时不要立即长按启动。先编辑新机器人:
```bash
ssh nvidia@"${TG3_NEW_ROBOT_IP}"
nano /home/nvidia/tg3_local_teleop/config.toml
```
必须检查的字段:
```toml
[network]
transport = "omnisocket"
omnisocket_server = "175.178.116.187:14049" # TG3_HUB_ADDR
omnisocket_peer_id = "tg3-<新机器人编号>-robot" # TG3_NEW_ROBOT_PEER
omnisocket_expected_sender = "tg3-<新机器人编号>-iarm" # TG3_IARM_PEER
expected_iarm_id = "IArm009027FA8190" # 若仍用当前 TS1P,可保持
expected_iarm_type = "TS1P"
[home]
joint_goal_rad = [ ...新机器人实测的 14 个 Home 值... ]
```
还要检查:
- `[hands]` 是否与新机器人的真实手型相符;
- `[hands].right_b_point_pose_normalized` 是否适用于新 BrainCo 手的校准;首次只在净空、
急停可用且低速限制生效时测试;
- `[control].joint_lower_rad/joint_upper_rad` 是否仍适用于同型号和当前 SDK;
- `run.sh`、service 内的用户名和路径是否仍为 `/home/nvidia`;
- ROS 安装路径是否仍有 `/opt/ros/jazzy`、`/home/nvidia/xos` 或
`/opt/robot_tele_server/install`。
### 3.4 修改 EAI 的目标 Peer
编辑:
```bash
ssh eai
nano /home/eai/.config/systemd/user/tg3-omnisocket-sender.service
```
修改 `ExecStart` 中:
```text
--server <TG3_HUB_ADDR>
--peer-id <TG3_IARM_PEER>
--target-peer <TG3_NEW_ROBOT_PEER>
```
不要修改:
```text
--zmq-endpoint tcp://127.0.0.1:5003
--cmd-zmq-endpoint tcp://127.0.0.1:5001
--cmd-max-age-s 0.25
```
应用 EAI 配置:
```bash
systemctl --user daemon-reload
systemctl --user restart tg3-omnisocket-sender.service
systemctl --user status tg3-omnisocket-sender.service --no-pager
cat /home/eai/tg3_omnisocket_transport/status.json
```
### 3.5 启动新机器人监测服务
```bash
ssh nvidia@"${TG3_NEW_ROBOT_IP}" \
'systemctl --user daemon-reload && systemctl --user enable --now tg3-local-teleop.service'
```
先观察,不要长按武装:
```bash
ssh nvidia@"${TG3_NEW_ROBOT_IP}" \
'sleep 5; cat /home/nvidia/tg3_local_teleop/status.json'
```
必须看到:
```text
mode = active-capable
armed = false
returning_home = false
operator_session_state = inactive(尚未有首帧时也可为空闲)
iarm_transport_status.connected = true
foreign_source_seen = false
foreign_hand_source_seen = false
```
待机时 EAI 的 `frames_received` 应持续增长,但 `frames_sent/bytes_sent` 不增长;机器人
`iarm_age_s` 为空或逐渐变旧属于预期。身份、频率和扳机原始值先在 EAI 本机检查,完整
启动门控在 START 首帧到达机器人后执行。
然后再保存新 Home、核对手部打开位,并按第 8 节进行现场验收。
## 4. 场景 B:EAI 工控机也更换
新 EAI 除场景 A 的机器人步骤外,还要完成以下内容。
### 4.1 原厂 xTELE 必须先独立正常
先按 TS1P 厂家方法完成串口、CAN、关节方向、偏置和 Home 标定,确认 xTELE 能在 EAI
本机持续发布:
```text
tcp://127.0.0.1:5003 原始关节、按键和诊断帧
tcp://127.0.0.1:5001 xTELE 处理后的控制帧(组合手势需要)
```
若 5003 正常而 5001 不存在,双臂、Z+C 和摇杆仍能收到原始数据,但飞书指南中的手势组合键
不会生成 BrainCo 六维目标。迁移时必须同时检查两个端口。
不要把旧 EAI 的串口设备路径和关节偏置盲目复制到不同硬件。只有同一套 TS1P 搬到新
工控机且串口设备一致时,才可参考旧配置。
### 4.2 在新 EAI 原生编译 OmniSocket 扩展
新 EAI 当前通常是 `x86_64`,但仍应在目标机本地构建,以匹配其 Python 版本:
```bash
rsync -av \
--exclude=.git \
--exclude=bin \
--exclude=python/build \
--exclude='python/omnisocket/_omnisocket*.so' \
/home/ps/Desktop/OmniSocketGo/ \
<新EAI用户>@<新EAI地址>:/home/<新EAI用户>/OmniSocketGo/
ssh <新EAI用户>@<新EAI地址> \
'cd /home/<新EAI用户>/OmniSocketGo && make python-ext'
```
将当前 EAI 的自有发送目录复制到新 EAI,并保持原厂目录不变:
```text
/home/eai/tg3_omnisocket_transport/
omnisocket_xtele_sender.py
tg3-omnisocket-sender.service
tg3-omnisocket-hub.service # 仅局域网本地 Hub 模式需要
README.md
```
在新 EAI 创建对应用户服务,修改所有 `/home/eai` 为新用户真实目录,并设置:
```text
Environment=PYTHONPATH=/home/<新EAI用户>/OmniSocketGo/python
--server <Hub IP:Port>
--peer-id <EAI Peer ID>
--target-peer <机器人 Peer ID>
--zmq-endpoint tcp://127.0.0.1:5003
--cmd-zmq-endpoint tcp://127.0.0.1:5001
--cmd-max-age-s 0.25
--source-timeout-s 0.25
--start-stop-hold-s 3.0
--combo-release-s 0.5
--start-marker-frames 500
--max-feedback-age-ms 500
--max-pending-frames 100
Restart=always
```
如果希望用户级服务在没有图形登录时也随开机运行,需要由管理员为实际账号开启 linger:
```bash
sudo loginctl enable-linger <新EAI用户>
```
机器人用户服务同理,是否需要 linger 取决于机器人系统是否会自动创建 `nvidia` 用户会话。
## 5. Hub IP 或端口变化时修改哪里
假设新 Hub 为 `203.0.113.10:15000`:
### EAI
修改:
```text
/home/eai/.config/systemd/user/tg3-omnisocket-sender.service
```
把:
```text
--server 175.178.116.187:14049
```
改为:
```text
--server 203.0.113.10:15000
```
### 机器人
修改:
```text
/home/nvidia/tg3_local_teleop/config.toml
```
把:
```toml
omnisocket_server = "175.178.116.187:14049"
```
改为:
```toml
omnisocket_server = "203.0.113.10:15000"
```
### Hub/防火墙
- KCP Hub 应监听 `0.0.0.0:15000`;
- 云安全组和主机防火墙应允许对应 UDP 端口;
- 不要只开放同端口的 TCP;当前跨公网控制使用 UDP/KCP。
修改后按顺序重启:
1. Hub;
2. EAI `tg3-omnisocket-sender.service`;
3. 机器人接收端。2 秒业务帧看门狗只在活动会话内生效,待机不会反复重启。
## 6. 场景 C:局域网本地 OmniSocket Hub(推荐)
这里的“本地链路”仍使用现有 OmniSocket sender 和机器人 receiver,只把公网 Hub
替换成运行在 EAI 工控机上的局域网 KCP Hub:
```text
TS1P
-> EAI xTELE 127.0.0.1:5003 + 127.0.0.1:5001
-> tg3-omnisocket-sender(合并双手目标、Z+C session 门控)
-> EAI 本机 kcpserver Hub,127.0.0.1:14049
-> 同一可信局域网 UDP/KCP
-> 机器人从 <EAI局域网IP>:14049 接收
-> tg3_local_teleop,transport="omnisocket"
```
公网服务器可以完全关闭。该方案保留 5001 六维手势合并、Peer 路由、session ID、
START/ACTIVE/STOP、晚包过滤和现有断线处理,比第 7 节纯 ZMQ 回退更完整。
### 6.1 本地与公网配置映射
当前现场局域网地址:
```text
EAI Wi-Fi: 192.168.5.14
机器人 Nvidia wlan0:192.168.5.15
本地 Hub UDP 端口: 14049
```
两端填写的地址不同,但实际指向同一个 EAI Hub:
| 配置位置 | 公网模式 | 本地 Hub 模式 |
|---|---|---|
| EAI sender `--server` | `175.178.116.187:14049` | `127.0.0.1:14049` |
| 机器人 `omnisocket_server` | `175.178.116.187:14049` | `192.168.5.14:14049` |
| 机器人 `network.transport` | `omnisocket` | `omnisocket`,不要改成 `zmq` |
| EAI/机器人 Peer ID | 当前成对 ID | 完全保持不变 |
| 5003+5001 合并和右 B 抑制 | 保留 | 保留 |
EAI sender 使用回环地址,避免 EAI 自己向 Hub 的数据绕行 Wi-Fi;机器人使用 EAI 的真实
局域网地址。Hub 必须监听 `0.0.0.0:14049` 才能同时接收回环和局域网客户端。若把 Hub
只绑定到 `192.168.5.14:14049`,EAI sender 也必须改用该地址,不能再写
`127.0.0.1:14049`。
### 6.2 在 EAI 准备完整 OmniSocketGo 和 kcpserver
本地 Hub 必须是独立的 `kcpserver -mode hub` 进程。Python `omnisocket.Session` 只有
客户端 API,现有 sender 进程不能兼任 Hub;也不要使用 `-mode relay`,relay 仍需要另一个
远端 Hub。
固定使用已验证提交:
```text
de3f5c96779dbe1571c10feb22fc7f2331b6b222
```
当前 EAI 的 `/home/eai/OmniSocketGo` 是为了 Python 扩展裁剪过的副本,缺少 `cmd/` 和
`bin/kcpserver`,不能直接启动 Hub。应直接从上述提交导出干净源码,避免把本机
OmniSocketGo 工作区中未提交的实验改动带到 EAI:
```bash
git -C /home/ps/Desktop/OmniSocketGo \
archive de3f5c96779dbe1571c10feb22fc7f2331b6b222 \
| ssh eai 'mkdir -p /home/eai/OmniSocketGo && tar -x -C /home/eai/OmniSocketGo'
ssh eai 'cd /home/eai/OmniSocketGo && make bin/kcpserver'
ssh eai '/home/eai/OmniSocketGo/bin/kcpserver --help'
```
如果执行命令的开发机没有该 Git 仓库,先从项目根 README 中的 Gitea 地址 clone,
然后用 `git archive <固定提交>` 导出;不要复制其他平台已编译的 `.so` 或 `bin/`。
新 EAI 还需按第 4 节执行 `make python-ext`。当前 EAI 已具备 gcc、make 和 build-essential;
`kcpserver` 本身只依赖系统 libc,不需要 Go 运行时或 FFmpeg。
### 6.3 为本地 Hub 创建用户服务
项目已提供模板:
```text
tg3_omnisocket_transport/tg3-omnisocket-hub.service
```
把它安装到 EAI:
```text
/home/eai/.config/systemd/user/tg3-omnisocket-hub.service
```
用户名不是 `eai` 时同步修改模板中的路径。模板内容:
```ini
[Unit]
Description=TG3 local OmniSocket KCP hub
[Service]
Type=simple
WorkingDirectory=/home/eai/OmniSocketGo
ExecStart=/home/eai/OmniSocketGo/bin/kcpserver -mode hub -listen 0.0.0.0:14049
Restart=always
RestartSec=1
KillSignal=SIGTERM
TimeoutStopSec=5
[Install]
WantedBy=default.target
```
在部署机的 TG3 项目根目录执行,直接把模板安装到 EAI 用户服务目录:
```bash
ssh eai 'mkdir -p /home/eai/.config/systemd/user'
scp tg3_omnisocket_transport/tg3-omnisocket-hub.service \
eai:/home/eai/.config/systemd/user/tg3-omnisocket-hub.service
```
默认不要开启 latency、packet-debug 或 session-stats 日志,避免长期高频写盘。然后登录 EAI,
在 `eai` 用户会话中加载并启动:
```bash
systemctl --user daemon-reload
systemctl --user enable --now tg3-omnisocket-hub.service
systemctl --user --no-pager status tg3-omnisocket-hub.service
ss -lunp | grep ':14049'
```
应看到 UDP `0.0.0.0:14049`。若希望 EAI 未图形登录也能自动运行用户服务,需要管理员执行:
```bash
sudo loginctl enable-linger eai
```
### 6.4 Wi-Fi、有线和防火墙
机器人必须能访问 EAI 的本地 Hub:
```bash
# 在机器人执行
ping -c 3 192.168.5.14
```
当前 Wi-Fi 已确认机器人 `192.168.5.15` 能到达 EAI `192.168.5.14`,但无线 RTT 仍会有
波动。有线直连时应使用未被厂家占用的网卡建立独立静态网段,例如 EAI
`192.168.50.1/24`、机器人 `192.168.50.2/24`,机器人配置相应写成
`192.168.50.1:14049`。不要修改 Ubuntu `192.168.41.1` 与 Nvidia
`192.168.41.2` 之间的厂家内部链路。
KCP 使用 UDP,不是 TCP。不要开放 TCP 14049,也不需要让机器人访问 EAI 的 5001/5003;
这两个 xTELE 端口只由同机 sender 通过回环地址读取。若 EAI 启用 UFW,Wi-Fi 当前可限制为:
```bash
sudo ufw status verbose
sudo ufw allow in on wlp172s0 from 192.168.5.15 to any port 14049 proto udp
```
有线时把网卡名和机器人地址替换成现场值。还应检查现有 UFW/云网关规则,确认没有更宽的
UDP 14049 放行规则覆盖上述限制。OmniSocket Peer ID 不是密码或加密认证;Hub 只应监听隔离、
可信的控制网,不要做公网端口映射。
### 6.5 从公网安全切换到本地 Hub
1. 先用 Z+C 3 秒正常 STOP,等待自动 Home 完成,在机器人确认 `armed=false`、
`returning_home=false`;
2. 确认 EAI 本地 Hub 已经 `active` 且 UDP 14049 正在监听;
3. 停止 EAI sender,防止修改期间创建会话:
```bash
systemctl --user stop tg3-omnisocket-sender.service
```
4. 在 EAI 直接修改 sender 地址:
```bash
sed -i -E 's#--server [^ ]+#--server 127.0.0.1:14049#' \
/home/eai/.config/systemd/user/tg3-omnisocket-sender.service
grep -oE -- '--server [^ ]+' \
/home/eai/.config/systemd/user/tg3-omnisocket-sender.service
```
`--peer-id`、`--target-peer`、5003/5001、所有超时和按键参数都保持不变。
5. 在机器人直接修改 Hub 地址(有线时替换为实际 EAI 地址):
```bash
sed -i 's#^omnisocket_server = ".*"$#omnisocket_server = "192.168.5.14:14049"#' \
/home/nvidia/tg3_local_teleop/config.toml
grep -nE '^(transport|omnisocket_server) *=' \
/home/nvidia/tg3_local_teleop/config.toml
```
Peer ID、`expected_iarm_id`、Home、手部、行走和厂家保护都不要改。`iarm_endpoint` 在
OmniSocket 模式不使用;检查输出应为 `transport = "omnisocket"`。
6. 按“本地 Hub → 机器人 receiver → EAI sender”的顺序应用:
```bash
# EAI:Hub 已在运行
systemctl --user is-active tg3-omnisocket-hub.service
# 机器人
systemctl --user restart tg3-local-teleop.service
# EAI
systemctl --user daemon-reload
systemctl --user restart tg3-omnisocket-sender.service
```
服务启动只进入待机,不会自动武装;仍需新的 Z+C 3 秒 START。
### 6.6 切换后验证
EAI Hub:
```bash
systemctl --user is-active tg3-omnisocket-hub.service
ss -lunp | grep ':14049'
```
机器人待机状态应看到:
```text
armed = false
iarm_transport = omnisocket
iarm_endpoint = omnisocket://192.168.5.14:14049/tg3-009027fa8190-robot
iarm_transport_status.connected = true
iarm_transport_status.registered = true
foreign_source_seen = false
foreign_hand_source_seen = false
```
EAI sender 待机时仍持续读取本机 5003/5001,但应为:
```text
teleop_active = false
application_data_sending = false
connected = 0
frames_sent 不增长
```
然后按第 8 节从小幅双臂、扳机、右 B、其他 5001 手势到行走逐项验收。由于 sender 与
机器人仍走原来的 OmniSocket 协议,操作按键和数据格式不变。
### 6.7 本地 Hub 的断线与重启行为
- EAI sender 活动期间仍检查 KCP feedback、发送队列和本机 5003 时间戳;异常会作废
session,必须松开后重新 Z+C;
- 机器人仍拒绝错误 Peer、乱序和相对基线额外排队超过 300 ms 的包;
- 按现场要求删除的 0.25 秒输入陈旧解除不会恢复;活动会话连续约 2 秒收不到帧时,机器人
OmniSocket 看门狗重启我们自己的桥并停止发布;
- 意外断网不会自动 Home,只有收到匹配的操作员 STOP 才自动回 Home;
- 本地 Hub 重启后,机器人在空闲时最多可能等待 30 秒刷新注册。最确定的恢复顺序是先
重启 Hub,再重启 `tg3-local-teleop.service`,确认 registered 后重新长按 START;
- 无线本地链路去掉了公网排队,但 Wi-Fi 本身仍可能抖动;正式运行优先使用隔离有线网。
### 6.8 恢复公网 Hub
先正常 STOP、完成 Home,并确认公网 Hub 已启动。停止 EAI sender,然后分别执行:
```bash
# EAI
sed -i -E 's#--server [^ ]+#--server 175.178.116.187:14049#' \
/home/eai/.config/systemd/user/tg3-omnisocket-sender.service
# 机器人
sed -i 's#^omnisocket_server = ".*"$#omnisocket_server = "175.178.116.187:14049"#' \
/home/nvidia/tg3_local_teleop/config.toml
```
然后先重启机器人 receiver,再重启 EAI sender:
```bash
# 机器人
systemctl --user restart tg3-local-teleop.service
# EAI
systemctl --user daemon-reload
systemctl --user restart tg3-omnisocket-sender.service
systemctl --user disable --now tg3-omnisocket-hub.service
```
本地 Hub 文件可以保留。确认机器人已注册公网 Hub 且 `armed=false` 后,才做新的 Z+C 3 秒
START。
## 7. 备选:完全不经过 Hub 的局域网 ZMQ 直连(当前仅 monitor-only)
这一路径不启动公网或本地 OmniSocket Hub,也不经过
`tg3-omnisocket-sender.service`:
```text
TS1P
-> EAI xTELE tcp://0.0.0.0:5003(原始 JSON)
-> 同一可信局域网内的 TCP/ZMQ
-> 机器人 tg3_local_teleop,network.transport="zmq"
-> /encoder_identical_joint、BrainCo 手和 /hric/robot/cmd_vel
```
这里的“本地”是机器人直接订阅 EAI 的局域网地址,不是把
`tcp://127.0.0.1:5003` 原样写到机器人。机器人配置中的 `127.0.0.1` 会指向机器人自身,
无法访问 EAI。
### 7.1 适用条件和网络检查
- EAI 与机器人必须在同一可信 Wi-Fi、有线路由可达网段,或使用两端静态地址的直连网线;
- EAI 地址应固定,至少配置 DHCP 保留;地址变化后必须同步修改机器人
`network.iarm_endpoint`;
- 只需机器人访问 EAI 的 TCP `5003`。不要把 5003 暴露到公网,防火墙应只允许机器人地址;
- xTELE 必须监听非回环地址。当前 EAI 实测为 `0.0.0.0:5003` 和
`0.0.0.0:5001`;新工控机仍需重新检查;
- ZMQ/TCP 直连没有 OmniSocket 的 Peer ID、包龄过滤或身份认证,只应在隔离、可信的控制网
使用。
在 EAI 检查地址和监听:
```bash
ip -br -4 address
ss -lntp | grep -E ':(5001|5003)\b'
```
若 5003 只显示 `127.0.0.1:5003`,机器人不能直接连接。应按厂家 xTELE 配置修改监听地址,
不要用公网端口转发代替局域网隔离。
从机器人检查到 EAI 的路由;把示例 IP 换成实际 EAI 局域网 IP:
```bash
ping -c 3 192.168.5.14
python3 -c 'import socket; s=socket.create_connection(("192.168.5.14",5003),2); s.close(); print("TCP 5003 OK")'
```
上面的 TCP 检查只证明端口可达。还应在机器人实际订阅一帧 ZMQ JSON:
```bash
python3 - <<'PY'
import json
import zmq
context = zmq.Context()
socket = context.socket(zmq.SUB)
socket.setsockopt(zmq.SUBSCRIBE, b"")
socket.connect("tcp://192.168.5.14:5003")
if socket.poll(3000) == 0:
raise SystemExit("3 秒内未收到 xTELE 5003 数据")
data = json.loads(socket.recv())
print(data.get("isomorphic_arm_id"), data.get("isomorphic_arm_type"), data.get("freq"))
socket.close()
context.term()
PY
```
Wi-Fi 和有线的配置方法相同,`iarm_endpoint` 始终填写“机器人能够访问到的 EAI 地址”。
不要因为使用机器人内部 SSH 地址 `192.168.41.2`,就把它误写成 EAI 地址。
当前现场 Wi-Fi 已验证为 EAI `192.168.5.14`、机器人 Nvidia `wlan0`
`192.168.5.15`。若改用直连网线,应选择双方确认未被厂家系统占用的物理网卡,配置一个
独立静态网段,例如 EAI `192.168.50.1/24`、机器人 `192.168.50.2/24`,随后把
`iarm_endpoint` 改为 `tcp://192.168.50.1:5003`。网卡名必须现场确认;不要改动机器人
Ubuntu `192.168.41.1` 与 Nvidia `192.168.41.2` 之间的厂家内部链路。
### 7.2 切换前停止公网链路
先用正常 Z+C 3 秒 STOP 结束当前遥操,等待自动 Home 完成,并在机器人确认:
```bash
python3 -c 'import json; d=json.load(open("/home/nvidia/tg3_local_teleop/status.json")); print({k:d.get(k) for k in ("armed","returning_home","home_status")})'
```
必须看到 `armed=false` 且 `returning_home=false`。然后在 EAI 停止并禁用公网 sender:
```bash
systemctl --user disable --now tg3-omnisocket-sender.service
systemctl --user is-active tg3-omnisocket-sender.service
```
第二条应输出 `inactive`。不要删除 sender、OmniSocketGo 或原来的 service;保留它们用于
回切公网模式。纯 ZMQ 模式下 EAI 的 sender `status.json` 不再是运行状态依据。
### 7.3 修改机器人配置
在机器人编辑:
```text
/home/nvidia/tg3_local_teleop/config.toml
```
只修改以下字段:
```toml
[network]
transport = "zmq"
iarm_endpoint = "tcp://192.168.5.14:5003" # 改成实际 EAI 局域网 IP
```
`omnisocket_server`、`omnisocket_peer_id` 和 `omnisocket_expected_sender` 可以保留,ZMQ
模式不会读取它们,回切公网时仍可继续使用。`expected_iarm_id` 和
`expected_iarm_type` 仍会检查,换 TS1P 时必须同步修改真实设备 ID。
当前版本在纯 ZMQ 断流时会保留最后一帧。`locomotion.enabled=false` 只能先排除最危险的
持续速度输出,仍不足以让双臂和手安全投用;配置阶段先设置:
```toml
[locomotion]
enabled = false
```
这会禁用我们桥发布的 HBWALK 速度。完成第 7.5 节所述本地断流保护之前,双臂、手和
行走都只能做无发布监测,不能长按 Z+C 武装。
不要立即启动带 `--allow-publish` 的 systemd 服务。先停止并暂时禁用我们自己的机器人桥,
然后以前台、无 `--allow-publish` 的监测模式运行 30 秒:
```bash
systemctl --user disable --now tg3-local-teleop.service
cd /home/nvidia/tg3_local_teleop
./run.sh --duration 30
/home/nvidia/tg3_local_teleop/status.sh
```
`run.sh` 只有显式收到 `--allow-publish` 才能发布;上述命令应显示
`mode=monitor-only`。如果 systemd 服务仍为 `active`,先停掉,避免同时启动第二个桥实例。
状态至少应满足:
```text
iarm_transport = zmq
iarm_endpoint = tcp://<EAI局域网IP>:5003
iarm_transport_status.frames_accepted 持续增长
iarm_id = 当前 TS1P ID
iarm_frequency_hz 左右均高于 30 Hz
armed = false
foreign_source_seen = false
mode = monitor-only
```
不要只看 `iarm_transport_status.connected`:当前 ZMQ 接收器收到过首帧后会保持该布尔值,
判断链路是否仍在更新要连续观察 `frames_accepted` 和 `iarm_age_s`。
服务重启和检查期间必须保持 Z+C 完全松开。ZMQ 回退门控在机器人端计时,若服务启动时
已经按住 Z+C,连续 3 秒后也可能被当成新的启动请求。
### 7.4 直连模式下协议字段与功能差异
下表说明数据和代码路径是否存在,不代表当前版本已经允许运动。第 7.5 节的 ZMQ 断流门控
完成前,所有运动项都保持 monitor-only。
| 功能 | 纯 ZMQ 直连的数据/代码情况 |
|---|---|
| 双臂 14 关节、启动限位和 ROS 状态门控 | 字段与代码路径保留;当前禁止武装 |
| 左右扳机 BrainCo 手控 | 5003 原始 `hand.position` 存在;当前禁止武装 |
| 右 B 长按 1 秒单食指 | 5003 原始 B 键存在;当前禁止武装 |
| Z+C 长按 3 秒启动/停止、STOP 后限速 Home | 改由机器人本地计时;断流可锁存按键,当前禁止武装 |
| C + 左摇杆行走、Z + 右摇杆转向 | 代码路径存在;当前必须 `enabled=false` |
| xTELE 5001 处理后的其他六维组合手势 | 不保留;机器人只订阅 5003 |
| OmniSocket session ID、Peer 校验、300 ms 晚包过滤 | 不使用 |
| 公网 Hub、EAI OmniSocket sender | 不需要 |
若必须完整保留 5001 组合手势,不能只把机器人指向 5003。需要另行实现一个 EAI 本地
5003+5001 合并代理,或在局域网内运行 OmniSocket Hub;后者不经过公网,但仍属于本地
Hub 模式,不是本节的“完全无 Hub”直连。
### 7.5 当前版本的断流风险
此前按现场要求删除了机器人桥“输入陈旧超过 0.25 秒立即解除遥操”的自定义门控;而
`omnisocket_restart_after_stale_s=2.0` 只在 `transport="omnisocket"` 时生效。
因此纯 ZMQ 模式断网后:
- 最后一帧双臂和手目标仍会以桥的周期重复发布;
- 如果行走仍启用且最后一帧是非零速度组合,非零 `/hric/robot/cmd_vel` 也会持续发布;
- 如果最后一帧仍按住 Z+C,本地 3 秒计时会在没有新数据时继续,可能在断网后武装或停止;
- 网络恢复后会直接继续当前 armed 会话,不要求重新 Z+C;
- `connected=true` 不能证明数据仍然新鲜。
所以当前纯直连配置只能按第 7.3 节以 monitor-only 验证链路。正式启用前必须实现仅作用于
`transport="zmq"` 的断流门控,至少满足:
1. 陈旧样本不得继续 Z+C 或右 B 计时;
2. 已武装时陈旧输入必须立即发布零速、解除双臂/手发布并锁定为需要重新松开、长按;
3. 网络恢复不得自动恢复原 armed 会话;
4. 阈值应作为 ZMQ 专用配置经现场验证,不能把已删除的通用 0.25 秒门控悄悄加回来;
5. 完成断网、拔线、xTELE 停止和恢复的无运动测试后,才能重新启用
`tg3-local-teleop.service --allow-publish`,行走还需单独验证零速帧。
这不是厂家默认保护的变化;厂家关节限位、电机、碰撞和手部保护仍保持原样。
### 7.6 从纯直连恢复公网 OmniSocket
先正常 STOP、完成 Home 并保持 Z+C 松开,然后把机器人恢复为:
```toml
[network]
transport = "omnisocket"
[locomotion]
enabled = true # 仅在现场仍需要并已确认安全时恢复
```
确认保留的 `omnisocket_server`、双方 Peer ID 仍匹配。按“先机器人接收端、后 EAI
sender”的顺序恢复:
```bash
# 机器人
systemctl --user enable --now tg3-local-teleop.service
# EAI
systemctl --user enable --now tg3-omnisocket-sender.service
```
机器人先确认 `armed=false`、`iarm_transport=omnisocket`;EAI 待机时仍只读本地
5003/5001,不会在新的 Z+C 3 秒 START 前发送业务数据。
## 8. 新机器人首次现场验收顺序
全程确认防护、活动空间、急停和 HBWALK 状态。
纯 ZMQ 当前版本若尚未完成第 7.5 节断流门控,只执行下面第 1~4 步的 monitor-only 检查,
不得执行第 5 步及后续任何武装或运动测试;完成并验证断流门控后才能继续。
1. OmniSocket 模式:服务启动后保持未武装,在 EAI sender 状态中确认本地接收计数增长而
公网发送计数不增长;纯 ZMQ 模式:sender 已停用,应在机器人 monitor-only 状态中确认
`frames_accepted` 持续增长、`iarm_age_s` 接近 0,不读取旧的 EAI sender
`status.json`;
2. 不武装时分别扣左右扳机,在 EAI 本地数据中确认左右值独立从 0 到 1;
3. 确认左右手物理默认打开位和 `robot_hand_states` 正常;
4. 把 TS1P 双臂摆到与机器人当前位置尽量接近的安全姿态;
5. 松开两个扳机,长按左 Z + 右 C 3 秒启动;
6. 先做小幅单关节跟随,再逐渐扩大动作;
7. 分别小幅扣左右扳机,验证双手方向、范围和限速;
8. 保持右摇杆回中,连续长按右 B 1 秒,确认右手以 `400 units/s` 限速进入厂商
“单食指”姿态;松开至少 0.5 秒后再次长按 1 秒,确认退出并恢复正常右手跟随;
9. 仅在 OmniSocket/5001 合并模式下:先停止并确认 `armed=false`,按飞书指南逐个选择
其他手势组合键;检查 EAI
`status.json` 的 `command_frames_accepted` 增长,并在 EAI 本机 5001 抓帧确认处理后的
`hand.position` 为六维数组;待机不会构包,`command_hand_merges` 此时不应增长;
10. 仅在 OmniSocket/5001 合并模式下:再次武装后检查 `command_hand_merges` 增长和机器人
`iarm_hand_position` 为六维,
再只做小幅动作,逐个验证所需的其他组合手势方向和限速;
11. 仅在 `[locomotion].enabled=true` 且相应链路断流保护已经验证后:保持右 C,把左摇杆
小幅向前推出死区,确认下一个 50 Hz 周期立即响应;松 C,确认
立即零速停止。再次按 C/推杆也应立即响应,不再等待 3 秒;
12. 在同一行走前提下,保持左 Z,把右摇杆小幅横推,确认机器人原地转向;松开任一输入
应立即清零角速度;
13. 再次长按 Z+C 停止,观察双臂限速回到新机器人保存的 Home;
14. 仅在 OmniSocket 模式、机器人已静止且急停可用时测试 Hub 断线:EAI sender 的 KCP
反馈超时约为 500 ms,触发后会作废 session;机器人若持续约 2 秒收不到活动帧,会另行
重建我们的接收桥。不应期待断线后透明续控;恢复后先确认未武装,稳定松开 Z+C 后再重新长按
启动。纯 ZMQ 模式在完成本地断流门控前禁止做运动中的断链测试;
15. 记录 SSH 地址、Home、行走限速、手部端点和单食指姿态到设备档案;OmniSocket 模式
另记 Peer ID/Hub,纯 ZMQ 模式改记 EAI 固定局域网 IP、TCP 5003 防火墙范围和断流门控参数。
迁移时确认目标机提供 `/hric/robot/cmd_vel`(`geometry_msgs/msg/TwistStamped`),并保持
`locomotion.command_topic` 与目标固件一致。行走限速、死区、曲线和方向符号均在机器人
`config.toml` 的 `[locomotion]` 中调整。当前天工 3.0 按二次开放文档的半身行走 Topic
范围配置:`max_forward_m_s=1.0`、`max_reverse_m_s=0.8`、
`max_angular_rad_s=0.8`;迁移到不同型号或固件时应重新核对其官方 Topic 范围。
## 9. 常见问题
### 机器人 SSH IP 变了,需要改 `config.toml` 吗?
公网 OmniSocket 模式不需要。只修改 SSH 命令中的地址。运行时机器人主动连接 Hub。
### EAI IP 变了,需要改 `tcp://127.0.0.1:5003/5001` 吗?
公网 OmniSocket sender 不需要修改:`127.0.0.1` 永远表示 EAI 本机,和网卡地址无关。
第 6 节本地 OmniSocket Hub 模式下,EAI sender 仍使用 `127.0.0.1:14049`,但机器人
`omnisocket_server` 使用 EAI 局域网地址;EAI 地址变化时只改机器人这一项并重启桥。
第 7 节纯 ZMQ 直连同理需要修改机器人 `iarm_endpoint`。
### Hub 没启动时已经长按了 Z+C,服务器恢复后怎么办?
本次长按只做一次后台连接检测。Hub 未启动、机器人 Peer 不存在或首帧发送失败时,
`teleop_start_pending` 会回到 `false`;Hub 离线时没有业务帧发出,Peer/发送错误时机器人
不会收到控制数据。旧请求不会在服务器稍后恢复
时自动重试。稳定松开 Z+C 至少 0.5 秒,待 Hub 与机器人接收端就绪后重新长按 3 秒即可。
后台检测期间 sender 仍持续读取 5003,因此松键不会因连接等待而漏掉。活动会话断网同样
按故障关闭处理,必须稳定松开后重新长按。
### 只改机器人 Peer,不改 EAI 可以吗?
不可以。EAI 的 `--target-peer` 必须等于机器人的 `omnisocket_peer_id`,机器人
`omnisocket_expected_sender` 必须等于 EAI 的 `--peer-id`。
### 可以直接复制当前机器人的整个 OmniSocketGo 目录吗?
只有目标机器架构和 Python ABI 完全一致时才可能复用。更稳妥的方式是复制源码并在目标
机器执行 `make python-ext`。EAI 的 x86_64 `.so` 绝对不能用于 aarch64 机器人。
### 可以复用当前 Home 吗?
不建议。Home 是现场机器人真实反馈值,不是型号固定常数。每台机器人都应重新保存。
### 新机器人是因时手,可以直接改 Topic 吗?
不可以。当前桥导入并发布 BrainCo 消息,因时手是不同消息类型和 13 自由度映射,需要
单独实现适配后再部署。
## 10. 迁移时真正需要修改的最小清单
同一 EAI、同一 TS1P、同一 Hub、换一台同配置 BrainCo 天工 3.0 时,最少修改:
1. EAI service 的 `--target-peer`;
2. 若更换 EAI Peer ID,同时修改 EAI `--peer-id` 和机器人
`omnisocket_expected_sender`;
3. 机器人 `omnisocket_peer_id`;
4. 新机器人 `[home].joint_goal_rad`;
5. 核对并按实测修正 `[hands].open_normalized`;
6. SSH 命令中的新机器人 IP。
Hub IP/端口变化时,额外同时修改 EAI `--server` 和机器人 `omnisocket_server`。
从公网切换为第 6 节本地 OmniSocket Hub 时,最少需要:
1. 在 EAI 部署固定提交的完整 OmniSocketGo 并编译 `bin/kcpserver`;
2. 启用 `tg3-omnisocket-hub.service`,监听 UDP `0.0.0.0:14049`;
3. 把 EAI sender `--server` 改为 `127.0.0.1:14049`;
4. 保持机器人 `transport="omnisocket"`,把 `omnisocket_server` 改为
`<EAI局域网IP>:14049`;
5. Peer ID、5003/5001、Home、手部与行走配置全部保持不变;
6. 防火墙只允许机器人局域网地址访问 EAI UDP 14049。
完全不经过 Hub、切换为第 7 节纯 ZMQ 直连时,最少修改机器人
`network.transport="zmq"`、`network.iarm_endpoint="tcp://<EAI局域网IP>:5003"`,并在
当前没有直连断流门控的版本中设置 `locomotion.enabled=false`,同时停用 EAI 的
`tg3-omnisocket-sender.service`,并仅以机器人 monitor-only 模式验证;完成第 7.5 节的
ZMQ 专用断流门控前不得启用运动发布。
## 11. 数采部署与迁移
数采是本项目新增功能,不调用 `/bag_record/control/notify`,也不停止或修改 Ubuntu 厂家
`record_bag_node`。Nvidia 独立录制,PS 本机负责把完成数据同步到:
```text
/home/ps/Desktop/TG3_TS1P_OmniSocket_Teleop/Data_Get
```
### 11.1 机器人 Nvidia
部署 `tg3_local_teleop/` 全目录,至少必须包含:
```text
tg3_local_teleop.py
data_collection.py
data_recorder_protocol.py
data_recorder_node.py
delete_ready_episode.py
config.toml
run.sh
run_data_recorder.sh
wait_ros_ready.sh
tg3-local-teleop.service
tg3-data-recorder.service
ros2_py/
```
目标路径固定为 `/home/nvidia/tg3_local_teleop`。先按原项目步骤构建 `ros2_py`,再安装:
```bash
mkdir -p ~/.config/systemd/user
cp /home/nvidia/tg3_local_teleop/tg3-data-recorder.service \
/home/nvidia/tg3_local_teleop/tg3-local-teleop.service \
~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now tg3-data-recorder.service
systemctl --user restart tg3-local-teleop.service
```
录制脚本必须依次加载 `/opt/ros/jazzy`、`/home/nvidia/xos` 和
`/opt/robot_tele_server/install`,但**不能**再加载本项目 `ros2_py/install`。后者是桥接
Python 进程专用的最小 `ArmStatus` 绑定,会遮蔽厂家完整 `ros2_bridge_msgs` C++ 库,导致
rosbag 加载 `RobotState/ArmCtrl` 时出现 undefined symbol。`run.sh` 仍需项目 overlay;
`run_data_recorder.sh` 只用厂家完整消息 overlay。
还需 Python 3 的 PyYAML(Ubuntu 包 `python3-yaml`)解析 rosbag metadata。
迁移时在 `config.toml [data_collection]` 核对 `base_directory`、100 GiB 余量、30 分钟
上限、明确 topic 白名单和 required topics。不要改成 `ros2 bag record -a`,点云需另行
估算带宽和磁盘后再加入。当前白名单已经包含头/腰 Orbbec 的 JPEG RGB、无损 PNG 深度、
CameraInfo、Metadata 和 `/tf_static`。头、腰相机都是 optional:服务已启动时 rosbag 自动
发现并采集;未启动时不影响核心 episode 开始或进入 READY。收尾后 manifest 会分别把
两台相机标成 `absent`、`healthy`、`partial` 或 `low_rate`,后两种附带质量警告但不阻塞
核心数据保存。
需要采集某台相机的完整 episode 时,先确认相机服务和发布者;不需要相机时可保持服务
停止,L3 数采仍可正常启动:
```bash
systemctl is-active orbbec_waist.service orbbec_head.service
sudo systemctl enable --now orbbec_waist.service
# 同时需要头部 RGB-D 时:
sudo systemctl enable --now orbbec_head.service
ros2 topic info /ob_camera_waist/color/image_raw/compressed
ros2 topic info /ob_camera_waist/depth/image_raw/compressedDepth
```
希望采集腰部图像时,两条腰部话题应显示 `Publisher count: 1`;头部同理。原始
1280x720 RGB+深度约
`8.3 GB/min/相机`,所以默认不重复录 raw,而是录标准 image_transport 压缩流;现场
单相机约 `1.3–1.5 GB/min`,实际随画面变化。头部和腰部都运行时应按约两倍预留空间与
同步时间。`minimum_free_gib=100` 是停止下限,不是整条 episode 的容量预算;双相机
长录制前至少预留“100 GiB + 预计数据量”,并在短录包中先确认实际带宽。
### 11.2 PS 本机
项目需包含 `tg3_data_collection/` 和 `Data_Get/`。确认到新 Nvidia 的免密 SSH 后安装:
```bash
ssh -o BatchMode=yes nvidia@192.168.41.2 true
mkdir -p ~/.config/systemd/user
cp tg3_data_collection/tg3-data-get-sync.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now tg3-data-get-sync.service
```
机器人 SSH IP 改变时,只改同步 unit;最简命令(替换两个地址)是:
```bash
sed -i 's/nvidia@旧IP/nvidia@新IP/g' \
~/.config/systemd/user/tg3-data-get-sync.service
systemctl --user daemon-reload
systemctl --user restart tg3-data-get-sync.service
```
这不修改 OmniSocket Hub、Peer ID 或机器人 `config.toml`。若 PS 用户名/项目路径改变,
同时修改 unit 的 `WorkingDirectory`、脚本路径和 `--destination`。
### 11.3 操作与验收
1. 保持 L3 松开,长按 Z+C 3 秒正常开启遥操;
2. 左摇杆按下(L3)连续 1 秒开始数采;该键是 `button_joystick.left`,不是 X/Y/Z;
3. 松开 L3 至少 0.5 秒,再长按 1 秒结束;或正常 Z+C 结束遥操自动收尾;
4. 查看 Nvidia `/tg3/data_collection/status`,必须先到 `ready`,不能从 `active/` 取数据;
recorder 会要求核心 required topics 有实时发布者且最终消息计数大于零;相机质量看
manifest 的 `optional_topic_groups`,相机未启动显示 `absent`,不会阻塞;
5. 查看本机 `Data_Get/sync_status.json`,最终目录必须有 `READY`、`manifest.json`、
`VERIFIED`、`bag/metadata.yaml` 和非空 `*.mcap`;
6. 执行 `ros2 bag info <episode>/bag` 并确认所需 topic 有消息;
7. 断开 PS 网络再采一条,确认 Nvidia 暂存 `ready/`;恢复网络后应续传,完成 SHA-256、
fsync 和 `VERIFIED` 后,只删除机器人上 manifest 完全匹配的该 episode。
每个新会话先要求 L3 稳定松开 0.5 秒,同一次持续按压只切换一次。数采故障不得解除
遥操或延迟 STOP/Home;桥心跳中断超过 3 秒时 recorder 会自行 SIGINT 收尾。当前架构在
录制期间必须先写 Nvidia 的项目专用 `active/`,PS 离线时也会暂存在 `ready/`;它不是
长期副本。本机完成全文件验真并持久化 `VERIFIED` 后,固定根目录删除助手才会原子移走并
删除该 exact episode,不能触碰 `active/`、`failed/` 或根目录。`retain_failed_episodes=false`
时失败录包也不在机器人保留。EAI 只转发控制数据,不保存 MCAP 或相机 payload。