From 208e863a4d08a0646459087f688df4bcf4b67c9a Mon Sep 17 00:00:00 2001 From: LengedZhao <867178694@qq.com> Date: Sat, 8 Aug 2026 17:44:51 +0800 Subject: [PATCH] fix: remove robot-side 250ms network disarm --- docs/天工3.0本地同构臂遥操迁移部署指南.md | 3 +- tg3_local_teleop/README.md | 14 ++--- tg3_local_teleop/config.toml | 5 +- tg3_local_teleop/test_session_gate.py | 63 +++++++++++++++++++++++ tg3_local_teleop/tg3_local_teleop.py | 9 ---- tg3_omnisocket_transport/README.md | 2 +- 6 files changed, 75 insertions(+), 21 deletions(-) diff --git a/docs/天工3.0本地同构臂遥操迁移部署指南.md b/docs/天工3.0本地同构臂遥操迁移部署指南.md index 3dfea2a..ad4e718 100644 --- a/docs/天工3.0本地同构臂遥操迁移部署指南.md +++ b/docs/天工3.0本地同构臂遥操迁移部署指南.md @@ -408,7 +408,8 @@ omnisocket_server = "203.0.113.10:15000" 立即零速停止。再次按 C/推杆也应立即响应,不再等待 3 秒; 12. 保持左 Z,把右摇杆小幅横推,确认机器人原地转向;松开任一输入应立即清零角速度; 13. 再次长按 Z+C 停止,观察双臂限速回到新机器人保存的 Home; -14. 测试 Hub 短暂断线:应在 0.25 秒后停止发布,恢复后先确认未武装,再重新长按启动; +14. 测试 Hub 短暂断线:短于 2 秒不应解除会话;持续约 2 秒无帧会重建机器人接收进程, + 恢复后先确认未武装,再重新长按启动; 15. 记录最终 Peer ID、Hub、SSH 地址、Home、行走限速、手部端点和单食指姿态到该机器人的设备档案。 迁移时确认目标机提供 `/hric/robot/cmd_vel`(`geometry_msgs/msg/TwistStamped`),并保持 diff --git a/tg3_local_teleop/README.md b/tg3_local_teleop/README.md index 0645455..f82167e 100644 --- a/tg3_local_teleop/README.md +++ b/tg3_local_teleop/README.md @@ -6,7 +6,7 @@ TS1P 同构臂 -> 工控机 xTELE tcp://127.0.0.1:5003 -> OmniSocket KCP Hub 175.178.116.187:14049 - -> tg3_local_teleop(直接 OmniSocket Session;HBWALK / 错误 / 限位 / 失联门控) + -> tg3_local_teleop(直接 OmniSocket Session;HBWALK / 错误 / 限位门控) -> /encoder_identical_joint(14 维 JointState) -> 厂家 freq_change_tg3_node(同步、滤波、200 Hz) -> /arm/cmd @@ -58,7 +58,7 @@ TS1P 同构臂 只有以下条件全部满足才允许开始:机器人 `current_state` 和 `child_state` 均为 `HBWALK`、状态为 `running`;同构臂数据新鲜;左右 CAN/伺服无错误;采样频率正常; 启动瞬间的 14 个目标均在天工 3.0 文档限位内;没有检测到云端第二路同构臂命令。 -自定义关节目标限位仅对每个新 `session_id` 的 START 检查一次,采集/跟随过程中不再检查;失联、电机错误、 +自定义关节目标限位仅对每个新 `session_id` 的 START 检查一次,采集/跟随过程中不再检查;电机错误、 HBWALK 状态等运行时保护仍持续检查。厂家 `freq_change_tg3_node` 原有的姿态同步、 关节限位和碰撞保护保持不变。为启用二次开放文档规定的 HBWALK topic 行走,本机 `xmigcs/config/dex_config.yaml` 的 HBWALK 速度许可已由全零改为当前 NAVIGATE 的上限; @@ -71,7 +71,7 @@ HBWALK 状态等运行时保护仍持续检查。厂家 `freq_change_tg3_node` 灵巧手仅在同一次长按武装后发布;以机器人真实手指位置为起点,并按每秒最多 400 个 位置单位平滑跟随。BrainCo 状态 `0`(空闲)、`1`(运动)、`2`(接触/堵转或到限位) -和 `3`(持续力)均为厂家定义的正常运行状态;仅状态失联、未知/非法状态、同构臂失联 +和 `3`(持续力)均为厂家定义的正常运行状态;仅状态失联、未知/非法状态 或检测到另一命令源时,双臂和双手会一起解除武装并停止发布。停止遥操后灵巧手保持 最后目标,双臂按原逻辑限速回 Home。BrainCo 驱动原有的电流、堵转和碰撞保护不作修改。 匹配的 STOP、安全解除武装或服务退出都会清除右 B 指向手势的逻辑状态,但不会在 @@ -130,10 +130,10 @@ systemctl --user stop tg3-local-teleop.service 接收代理。若需回退到机器人与工控机的局域网直连,可把 `network.transport` 改成 `zmq`;保留的 `iarm_endpoint` 为 `tcp://192.168.5.14:5003`。 -遥操活动期间公网输入超过 `0.25 s` 未更新会立即解除武装并停止发布,同一会话不能自动 -重新武装;活动会话超过 `2 s` 仍无帧时才重建机器人 OmniSocket 进程。未 START 或收到 -STOP 后没有业务帧是正常待机,不触发反复重启。只有匹配的操作员 STOP 自动回 Home; -意外断网只停控,不自动产生回位运动。 +机器人侧不再以 `0.25 s` 公网输入间隔解除武装。活动会话超过 `2 s` 仍无帧时重建机器人 +OmniSocket 进程;在此之前桥会保持最后一帧目标。未 START 或收到 STOP 后没有业务帧是 +正常待机,不触发反复重启。只有匹配的操作员 STOP 自动回 Home;意外断网不自动产生 +回位运动。 ## 完整重启顺序 diff --git a/tg3_local_teleop/config.toml b/tg3_local_teleop/config.toml index 86dc95b..5c725f2 100644 --- a/tg3_local_teleop/config.toml +++ b/tg3_local_teleop/config.toml @@ -11,14 +11,13 @@ omnisocket_max_packet_age_ms = 300.0 # Active traffic resets this timer and is never interrupted by this refresh. omnisocket_idle_session_refresh_s = 30.0 # The native session can remain blocked on an old connection after the public -# Hub disappears. Publication stops at source_timeout_s; after this longer -# interval the process exits and systemd creates a completely fresh session. +# Hub disappears. An active session that receives no business frame for this +# interval exits so systemd creates a completely fresh session. omnisocket_restart_after_stale_s = 2.0 # Retained only as a direct-LAN fallback when transport is changed to "zmq". iarm_endpoint = "tcp://192.168.5.14:5003" expected_iarm_id = "IArm009027FA8190" expected_iarm_type = "TS1P" -source_timeout_s = 0.25 minimum_arm_frequency_hz = 30.0 [ros] diff --git a/tg3_local_teleop/test_session_gate.py b/tg3_local_teleop/test_session_gate.py index dcae4c0..917db5f 100644 --- a/tg3_local_teleop/test_session_gate.py +++ b/tg3_local_teleop/test_session_gate.py @@ -164,6 +164,69 @@ class RobotSessionGateTest(unittest.TestCase): valid["tg3_transport"]["protocol_version"] = 1 self.assertIsNone(LocalTeleopBridge._teleop_session_info(valid)) + def test_network_sample_age_is_not_a_runtime_disarm_gate(self) -> None: + bridge = LocalTeleopBridge.__new__(LocalTeleopBridge) + bridge.cfg = { + "network": { + "expected_iarm_id": "IArm-test", + "expected_iarm_type": "TS1P", + "minimum_arm_frequency_hz": 30.0, + }, + "robot": { + "arm_state_timeout_s": 0.25, + "state_timeout_s": 1.0, + "required_state": "HBWALK", + "required_status": "running", + }, + "control": {}, + } + bridge.hands_enabled = False + bridge.robot_arm_at = 100.0 + bridge.robot_arm_errors = [0] * 14 + bridge.rl_state_at = 100.0 + bridge.rl_state = { + "current_state": "HBWALK", + "child_state": "HBWALK", + "status": "running", + } + bridge.foreign_source_seen = False + bridge.foreign_hand_source_seen = False + old_network_sample = ArmSnapshot( + { + "isomorphic_arm_id": "IArm-test", + "isomorphic_arm_type": "TS1P", + "servo_error": {"left": [0] * 7, "right": [0] * 7}, + "joycan_error": [0, 0], + "freq": {"left": 80.0, "right": 80.0}, + }, + received_at=10.0, + ) + + reasons = bridge._safety_reasons( + 100.0, + old_network_sample, + "", + check_iarm_target=False, + check_hands=False, + check_hand_feedback=False, + ) + self.assertEqual(reasons, []) + + def test_two_second_omnisocket_restart_watchdog_remains(self) -> None: + bridge = LocalTeleopBridge.__new__(LocalTeleopBridge) + bridge.cfg = { + "network": { + "transport": "omnisocket", + "omnisocket_restart_after_stale_s": 2.0, + } + } + active = sample("f" * 32, 1, "active") + self.assertIsNone(bridge._source_restart_reason(11.999, active)) + self.assertIn( + "OmniSocket input stale", + bridge._source_restart_reason(12.0, active), + ) + def test_locomotion_reacts_immediately_after_repress(self) -> None: bridge = LocalTeleopBridge.__new__(LocalTeleopBridge) bridge.locomotion_cfg = { diff --git a/tg3_local_teleop/tg3_local_teleop.py b/tg3_local_teleop/tg3_local_teleop.py index 940a950..04b9577 100755 --- a/tg3_local_teleop/tg3_local_teleop.py +++ b/tg3_local_teleop/tg3_local_teleop.py @@ -799,7 +799,6 @@ class LocalTeleopBridge(Node): # A server outage can leave the native OmniSocket recv call stuck in # an apparently connected session. Raising out of the ROS loop lets # systemd replace the whole process (and native session) cleanly. - # The normal runtime gate above has already stopped publication. self.get_logger().error(restart_reason) raise RuntimeError(restart_reason) @@ -1342,9 +1341,6 @@ class LocalTeleopBridge(Node): if check_iarm and sample is None: reasons.append(source_error or "no isomorphic-arm data") elif check_iarm: - age = now - sample.received_at - if age > float(net_cfg["source_timeout_s"]): - reasons.append(f"isomorphic-arm data stale ({age:.3f}s)") data = sample.data expected_id = str(net_cfg.get("expected_iarm_id", "")) expected_type = str(net_cfg.get("expected_iarm_type", "")) @@ -1565,11 +1561,6 @@ class LocalTeleopBridge(Node): "omnisocket_restart_after_stale_s" ), "iarm_connected": sample is not None, - "iarm_stream_fresh": ( - sample is not None - and now - sample.received_at - <= float(self.cfg["network"]["source_timeout_s"]) - ), "iarm_age_s": None if sample is None else round(now - sample.received_at, 4), "iarm_id": None if sample is None else sample.data.get("isomorphic_arm_id"), "iarm_frequency_hz": None if sample is None else sample.data.get("freq"), diff --git a/tg3_omnisocket_transport/README.md b/tg3_omnisocket_transport/README.md index 5f7ca3e..ea2646f 100644 --- a/tg3_omnisocket_transport/README.md +++ b/tg3_omnisocket_transport/README.md @@ -68,7 +68,7 @@ xTELE 帧;连续长按左 Z + 右 C 3 秒后才生成新的 128-bit `session_i 活动会话都不会自动重试或自动恢复。 `tg3_local_teleop` 内部直接拒绝非预期发送端、 乱序、格式错误以及相对本次连接最低包龄额外排队超过 300 ms 的数据;相对包龄会消除 -两端系统时钟的固定偏差。遥操桥仍有 250 ms 输入失联保护。 +两端系统时钟的固定偏差。机器人侧不再以 250 ms 公网输入间隔解除遥操。 机器人直接在 `tg3_local_teleop` 中接收 OmniSocket 数据,不再安装或运行单独的 `tg3-omnisocket-receiver.service`,也不再经过机器人本机 ZMQ 接收代理。