diff --git a/README.md b/README.md index 9554b47..caf3dc4 100755 --- a/README.md +++ b/README.md @@ -154,27 +154,27 @@ source install/setup.bash ros2 run xr_rm_bringup launcher_ui ``` -面板顶部的 `Mode` 分为五类: +面板顶部的 `Mode` 分为四类: -- `Simulation`:左臂 mock、右臂 mock、双臂 mock、sample UDP 发送、one-click mock demo、controller 位置/频率监控。 -- `Left Arm`:左臂网络 ping、左臂真机 launch、左手 sample UDP。 -- `Right Arm`:右臂网络 ping、右臂真机 launch、右手 sample UDP。 -- `Dual Arm`:左右臂 ping、双臂真机 launch、双手 sample UDP。 -- `Diagnostics`:`ros2 doctor --report` 和核心包的 `ros2 pkg prefix` 检查。 +- `Simulation`:双臂 mock、XRoboToolkit bridge、双手 sample UDP 和 controller 频率监控。 +- `MuJoCo`:双臂 Mock/真机 MuJoCo launch、XRoboToolkit bridge 和 controller 频率监控;真机命令会连接两台 RM75。 +- `Real Hardware`:左右臂网络 ping、左臂/右臂/双臂真机 launch、XRoboToolkit bridge 和左右夹爪开合。 +- `Diagnostics`:`ros2 doctor --report`、四个核心包的 `ros2 pkg prefix`、controller 位置/频率监控。 常用按钮: - `Run Selected`:运行当前选中的命令。双击列表项也可以运行。 -- `Check Env`:检查 ROS2 Humble、工作空间 build、终端、核心 ROS 包、睿尔曼 API2。 -- `Stop All`:结束由本工作空间启动的 launch、sample sender、topic monitor、相关 ROS 节点和终端窗口。 +- `Check Env`:检查 ROS2 Humble、工作空间 build、终端、四个核心 ROS 包、睿尔曼 API2。 +- `Stop All`:结束由本工作空间启动的 launch、sample sender、topic monitor、MuJoCo viewer、相关 ROS 节点和终端窗口。 + +`Stop All` 会保留现有 PC Service;点击启动器窗口 `X` 并确认退出时会额外停止 PC Service。两条清理路径都会停止 `dual_arm_simulator`,关闭 MuJoCo viewer。 每个模式都会附带基础监控入口: - `Open Controller Topic Monitor`:同时查看 `/xr/left_controller` 和 `/xr/right_controller`。 -- `Open Target Velocity Monitor`:同时查看 `/xr_rm/left_rm75/cmd_vel` 和 `/xr_rm/right_rm75/cmd_vel`;该话题表示目标位姿变化率,仅用于调试。 - `Open ROS Topic/Node List Monitor`:每秒刷新 `ros2 topic list` 和 `ros2 node list`。 -`Simulation` 模式还提供 `Open Controller Position Monitor` 和 `Open Controller Hz Monitor`,用于快速看手柄位置字段和接收频率。 +`Simulation` 和 `MuJoCo` 模式还提供 `Open Controller Hz Monitor`;`Diagnostics` 同时提供 controller 位置与频率监控。 分屏监控依赖 `x-terminal-emulator` 指向 Terminator。若提示不支持,可安装并切换: @@ -244,7 +244,7 @@ ros2 launch xr_rm_bringup arm_debug.launch.py arm:=both use_mock:=false ## Launch 入口说明 -`arm_debug.launch.py` 是当前唯一的遥操作 launch 主入口,`launcher_ui.py` 中的 mock、单臂真机和双臂真机按钮都调用它。 +`arm_debug.launch.py` 是当前唯一的遥操作 launch 主入口,`launcher_ui.py` 中的 Simulation、MuJoCo 和 Real Hardware launch 命令都调用它。 常用参数: @@ -274,6 +274,8 @@ ros2 launch xr_rm_bringup arm_debug.launch.py \ arm:=both use_mock:=false use_mujoco:=true ``` +桌面 UI 的 `MuJoCo` 模式分别提供上述 Mock 和真机命令,并明确标记会连接真机的 `Dual Arm MuJoCo Real Hardware Launch`。 + MuJoCo 只订阅当前关节状态,不参与控制,也不向真机下发指令: - `/xr_rm/left_rm75/joint_states`、`/xr_rm/right_rm75/joint_states`:当前适配器反馈;Mock 与真机模式均按控制周期约 `90 Hz` 发布。真机底层原始反馈周期仍为 `5 ms`(约 `200 Hz`),由遥操作节点按 `90 Hz` 采样发布。 @@ -344,7 +346,7 @@ ros2 topic pub --once /xr_rm/right_rm75/tool_enable std_msgs/msg/Bool "{data: tr ros2 topic pub --once /xr_rm/right_rm75/tool_enable std_msgs/msg/Bool "{data: false}" ``` -桌面 UI 的 `Left Arm` 和 `Right Arm` 模式里也有对应的 Tool Open/Close 命令项;`Dual Arm` 真机模式下可直接通过左右手柄 `trigger` 分别切换夹爪。 +桌面 UI 的 `Real Hardware` 模式提供 `Left/Right Gripper Open/Close` 命令项;运行双臂真机 launch 时也可直接通过左右手柄 `trigger` 分别切换夹爪。 ## UDP 数据格式 @@ -491,7 +493,7 @@ ros2 topic echo /xr/right_controller --field trigger 6. 小角度转动手柄,确认 `/xr_rm//target_pose` 姿态和 `/xr_rm//cmd_vel.twist.angular` 变化符合预期。 7. 点击对应 `trigger`,确认每次点击都会切换对应夹爪状态,松开 trigger 后状态保持且左右不串臂。 8. 确认松开 `grip` 后机械臂慢停,`/xr_rm//cmd_vel` 回到零;trigger 仍只影响夹爪,不影响机械臂运动门控。 -9. 左右臂都确认后,再进入双臂模式。 +9. 左右臂都确认后,再运行 `Dual Arm RealMan Launch`。 当前项目没有双臂碰撞检测/避障。双臂首次联调时,请让两个工作区在物理上分开,低速验证,不要让两臂末端互相靠近。 diff --git a/xr_rm_bringup/config/peripherals_rm75.yaml b/xr_rm_bringup/config/peripherals_rm75.yaml index 5439678..30f39c3 100644 --- a/xr_rm_bringup/config/peripherals_rm75.yaml +++ b/xr_rm_bringup/config/peripherals_rm75.yaml @@ -9,7 +9,7 @@ set_initial_tool_state: false tools_in_ee: scissor: # x, y, z, qx, qy, qz, qw - pose: [0.0, 0.0, 0.19, 0.0, 0.0, 0.0, 1.0] + pose: [0.0, 0.0, 0.165, 0.0, 0.0, 0.0, 1.0] # mass, center_x, center_y, center_z, reserved... load: [0.66, 0.0, 0.0, 0.06, 0.0, 0.0, 0.0] omnipic: @@ -24,6 +24,6 @@ tools_in_ee: arms: left: - scissorgripper: 2 + scissorgripper: 0 right: scissorgripper: 1 diff --git a/xr_rm_bringup/test/test_launcher_ui_cleanup.py b/xr_rm_bringup/test/test_launcher_ui_cleanup.py index 7a00dfc..146eb2d 100644 --- a/xr_rm_bringup/test/test_launcher_ui_cleanup.py +++ b/xr_rm_bringup/test/test_launcher_ui_cleanup.py @@ -13,6 +13,100 @@ assert SPEC.loader is not None SPEC.loader.exec_module(launcher_ui) +class LauncherCommandsTest(unittest.TestCase): + def test_modes_expose_the_confirmed_command_matrix(self) -> None: + expected_titles = { + "Simulation": [ + "Dual Arm Mock Launch", + "XRobotoolkit UDP Bridge (90 Hz)", + "Sample UDP Sender (Both Staggered, 60s)", + "Open Controller Hz Monitor", + "Open ROS Topic/Node List Monitor", + "Open Controller Topic Monitor", + ], + "MuJoCo": [ + "Dual Arm MuJoCo Mock Launch", + "Dual Arm MuJoCo Real Hardware Launch", + "XRobotoolkit UDP Bridge (90 Hz)", + "Open Controller Hz Monitor", + "Open ROS Topic/Node List Monitor", + "Open Controller Topic Monitor", + ], + "Real Hardware": [ + "Ping Left RM75", + "Ping Right RM75", + "Left Arm RealMan Launch", + "Right Arm RealMan Launch", + "Dual Arm RealMan Launch", + "XRobotoolkit UDP Bridge (90 Hz)", + "Left Gripper Open", + "Left Gripper Close", + "Right Gripper Open", + "Right Gripper Close", + "Open ROS Topic/Node List Monitor", + "Open Controller Topic Monitor", + ], + "Diagnostics": [ + "ROS Doctor Report", + "XR-RM Bringup Prefix", + "XR-RM Input Prefix", + "XR-RM Teleop Prefix", + "XR-RM MuJoCo Prefix", + "Open Controller Position Monitor", + "Open Controller Hz Monitor", + "Open ROS Topic/Node List Monitor", + "Open Controller Topic Monitor", + ], + } + + self.assertEqual(launcher_ui.MODES, list(expected_titles)) + for mode, titles in expected_titles.items(): + actual = [ + indexed_title.split(". ", 1)[1] + for indexed_title, _command in launcher_ui.build_commands_by_mode(mode) + ] + self.assertEqual(actual, titles) + + def test_mujoco_launches_distinguish_mock_and_real_hardware(self) -> None: + commands = dict(launcher_ui.build_commands_by_mode("MuJoCo")) + + self.assertIn( + "arm:=both use_mock:=true use_mujoco:=true", + commands["1. Dual Arm MuJoCo Mock Launch"], + ) + self.assertIn( + "arm:=both use_mock:=false use_mujoco:=true", + commands["2. Dual Arm MuJoCo Real Hardware Launch"], + ) + + def test_cmd_vel_monitor_is_completely_removed(self) -> None: + self.assertFalse(hasattr(launcher_ui, "CMD_VEL_MONITOR_ACTION")) + for mode in launcher_ui.MODES: + self.assertNotIn("cmd_vel", repr(launcher_ui.build_commands_by_mode(mode))) + + def test_environment_check_includes_mujoco_package(self) -> None: + app = object.__new__(launcher_ui.LauncherApp) + app.workspace_root = launcher_ui._find_workspace_root() + app.terminal_command = lambda _title, _script: ["terminal"] + app.x_terminal_target = lambda: "terminator" + checked_packages = [] + app._ros_package_available = lambda package: checked_packages.append(package) or True + app.show_text_dialog = lambda *_args: None + app.status = mock.Mock() + + with mock.patch.object( + launcher_ui.shutil, + "which", + return_value="/usr/bin/x-terminal-emulator", + ): + app.check_prerequisites() + + self.assertEqual( + checked_packages, + ["xr_rm_bringup", "xr_rm_input", "xr_rm_teleop", "xr_rm_mujoco"], + ) + + class LauncherCleanupTest(unittest.TestCase): def test_xrobotoolkit_cleanup_policy_only_stops_service_on_window_close(self) -> None: stop_all_patterns = set( @@ -89,6 +183,34 @@ class LauncherCleanupTest(unittest.TestCase): ], ) + def test_stop_all_and_window_close_stop_mujoco(self) -> None: + for stop_pc_service in (False, True): + app = object.__new__(launcher_ui.LauncherApp) + app.status = mock.Mock() + app.close_related_terminal_windows = lambda: 0 + + def fake_check_output(command, **_kwargs): + if command == ["pgrep", "-f", "dual_arm_simulator"]: + return "404\n" + raise subprocess.CalledProcessError(1, command) + + with ( + mock.patch.object( + launcher_ui.subprocess, + "check_output", + side_effect=fake_check_output, + ), + mock.patch.object(launcher_ui.os, "kill") as kill, + mock.patch.object(launcher_ui.time, "sleep"), + ): + app.stop_launched_processes( + confirm=False, + notify=False, + stop_pc_service=stop_pc_service, + ) + + kill.assert_called_once_with(404, signal.SIGTERM) + if __name__ == "__main__": unittest.main() diff --git a/xr_rm_bringup/tools/launcher_ui.py b/xr_rm_bringup/tools/launcher_ui.py index ffba4b9..0c50052 100755 --- a/xr_rm_bringup/tools/launcher_ui.py +++ b/xr_rm_bringup/tools/launcher_ui.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 """XR-RM 桌面调试启动器。 -提供 Tkinter 图形界面,按“仿真/左臂/右臂/双臂/诊断”组织常用 ROS2 -launch、sample_udp_sender、topic 监控和环境检查命令,降低现场调试时的命令输入成本。 +提供 Tkinter 图形界面,按“仿真/MuJoCo/真机/诊断”组织常用 ROS2 launch、 +sample_udp_sender、topic 监控和环境检查命令,降低现场调试时的命令输入成本。 """ from __future__ import annotations @@ -44,8 +44,6 @@ SAMPLE_SENDER_ARGS = ( TERMINAL_TITLE_PREFIX = "XR-RM Terminal - " TOPIC_MONITOR_TITLE = "XR-RM Topic Monitor" TOPIC_MONITOR_ACTION = "__xr_rm_topic_monitor__" -CMD_VEL_MONITOR_TITLE = "XR-RM Target Velocity Monitor" -CMD_VEL_MONITOR_ACTION = "__xr_rm_cmd_vel_monitor__" ROS_GRAPH_MONITOR_TITLE = "XR-RM ROS Graph Monitor" ROS_GRAPH_MONITOR_ACTION = "__xr_rm_ros_graph_monitor__" @@ -54,11 +52,6 @@ TOPIC_MONITORS = [ ("Right Controller", "/xr/right_controller"), ] -CMD_VEL_MONITORS = [ - ("Left Target Vel", "/xr_rm/left_rm75/cmd_vel"), - ("Right Target Vel", "/xr_rm/right_rm75/cmd_vel"), -] - CONTROLLER_POSITION_MONITOR_TITLE = "XR-RM Controller Position Monitor" CONTROLLER_POSITION_MONITOR_ACTION = "__xr_rm_controller_position_monitor__" CONTROLLER_HZ_MONITOR_TITLE = "XR-RM Controller Hz Monitor" @@ -81,9 +74,8 @@ ROS_GRAPH_MONITORS = [ MODES = [ "Simulation", - "Left Arm", - "Right Arm", - "Dual Arm", + "MuJoCo", + "Real Hardware", "Diagnostics", ] @@ -177,21 +169,6 @@ def _source_lines(workspace_root: Path) -> list[str]: return lines -def _one_click_mock(arm: str, hand: str) -> str: - sender_seconds = SAMPLE_SENDER_STAGGERED_SECONDS if hand == "both" else SAMPLE_SENDER_SECONDS - both_mode = "staggered" if hand == "both" else "synchronized" - return "\n".join([ - f"ros2 launch xr_rm_bringup arm_debug.launch.py arm:={arm} use_mock:=true &", - "launch_pid=$!", - "sleep 2", - _sample_udp_sender_command(hand, sender_seconds, both_mode), - "echo", - "echo 'Sample sender finished. The launch process is still running in this terminal.'", - "echo 'Press Ctrl-C here, or use the cleanup button in the launcher, to stop it.'", - "wait \"$launch_pid\"", - ]) - - def _diagnostic_commands() -> list[tuple[str, str]]: return [ ("Open ROS Topic/Node List Monitor", ROS_GRAPH_MONITOR_ACTION), @@ -202,14 +179,9 @@ def _topic_monitor_item() -> tuple[str, str]: return ("Open Controller Topic Monitor", TOPIC_MONITOR_ACTION) -def _cmd_vel_monitor_item() -> tuple[str, str]: - return ("Open Target Velocity Monitor", CMD_VEL_MONITOR_ACTION) - - def _is_topic_monitor_action(action: str) -> bool: return action in ( TOPIC_MONITOR_ACTION, - CMD_VEL_MONITOR_ACTION, CONTROLLER_POSITION_MONITOR_ACTION, CONTROLLER_HZ_MONITOR_ACTION, ) @@ -232,14 +204,6 @@ def _topic_monitor_spec(action: str) -> tuple[str, list[tuple[str, str]], str, s "xr_rm_controller_hz_monitor_", "controller hz topic", ) - if action == CMD_VEL_MONITOR_ACTION: - return ( - CMD_VEL_MONITOR_TITLE, - [(title, f"ros2 topic echo {topic}") for title, topic in CMD_VEL_MONITORS], - "xr_rm_cmd_vel_monitor", - "xr_rm_cmd_vel_monitor_", - "target velocity topic", - ) return ( TOPIC_MONITOR_TITLE, [(title, f"ros2 topic echo {topic}") for title, topic in TOPIC_MONITORS], @@ -249,16 +213,10 @@ def _topic_monitor_spec(action: str) -> tuple[str, list[tuple[str, str]], str, s ) -def _finalize_items( - items: list[tuple[str, str]], - one_click: tuple[str, str] | None = None, -) -> list[tuple[str, str]]: +def _finalize_items(items: list[tuple[str, str]]) -> list[tuple[str, str]]: final_items = items + _diagnostic_commands() + [ _topic_monitor_item(), - _cmd_vel_monitor_item(), ] - if one_click is not None: - final_items.append(one_click) return _with_index(final_items) @@ -266,25 +224,64 @@ def build_commands_by_mode(mode: str) -> list[tuple[str, str]]: # UI 列表只维护命令模板;真正执行时统一套上工作空间 source 和终端包装。 if mode == "Simulation": items = [ - ("Left Arm Mock Launch", "ros2 launch xr_rm_bringup arm_debug.launch.py arm:=left use_mock:=true"), - ("Right Arm Mock Launch", "ros2 launch xr_rm_bringup arm_debug.launch.py arm:=right use_mock:=true"), ("Dual Arm Mock Launch", "ros2 launch xr_rm_bringup arm_debug.launch.py arm:=both use_mock:=true"), ("XRobotoolkit UDP Bridge (90 Hz)", _xrobotoolkit_bridge_command()), - ( - "Sample UDP Sender (Left, 30s)", - _sample_udp_sender_command("left"), - ), - ( - "Sample UDP Sender (Right, 30s)", - _sample_udp_sender_command("right"), - ), ( "Sample UDP Sender (Both Staggered, 60s)", _sample_udp_sender_command("both", SAMPLE_SENDER_STAGGERED_SECONDS, "staggered"), ), - ("One-Click Left Mock Demo", _one_click_mock("left", "left")), - ("One-Click Right Mock Demo", _one_click_mock("right", "right")), - ("One-Click Dual Mock Demo", _one_click_mock("both", "both")), + ( + "Open Controller Hz Monitor", + CONTROLLER_HZ_MONITOR_ACTION, + ), + ] + elif mode == "MuJoCo": + items = [ + ( + "Dual Arm MuJoCo Mock Launch", + "ros2 launch xr_rm_bringup arm_debug.launch.py " + "arm:=both use_mock:=true use_mujoco:=true", + ), + ( + "Dual Arm MuJoCo Real Hardware Launch", + "ros2 launch xr_rm_bringup arm_debug.launch.py " + "arm:=both use_mock:=false use_mujoco:=true", + ), + ("XRobotoolkit UDP Bridge (90 Hz)", _xrobotoolkit_bridge_command()), + ( + "Open Controller Hz Monitor", + CONTROLLER_HZ_MONITOR_ACTION, + ), + ] + elif mode == "Real Hardware": + items = [ + ("Ping Left RM75", f"ping -c 4 {DEFAULT_LEFT_IP}"), + ("Ping Right RM75", f"ping -c 4 {DEFAULT_RIGHT_IP}"), + ( + "Left Arm RealMan Launch", + "ros2 launch xr_rm_bringup arm_debug.launch.py arm:=left use_mock:=false", + ), + ( + "Right Arm RealMan Launch", + "ros2 launch xr_rm_bringup arm_debug.launch.py arm:=right use_mock:=false", + ), + ( + "Dual Arm RealMan Launch", + "ros2 launch xr_rm_bringup arm_debug.launch.py arm:=both use_mock:=false", + ), + ("XRobotoolkit UDP Bridge (90 Hz)", _xrobotoolkit_bridge_command()), + ("Left Gripper Open", _tool_command("left", True)), + ("Left Gripper Close", _tool_command("left", False)), + ("Right Gripper Open", _tool_command("right", True)), + ("Right Gripper Close", _tool_command("right", False)), + ] + else: + items = [ + ("ROS Doctor Report", "ros2 doctor --report"), + ("XR-RM Bringup Prefix", "ros2 pkg prefix xr_rm_bringup"), + ("XR-RM Input Prefix", "ros2 pkg prefix xr_rm_input"), + ("XR-RM Teleop Prefix", "ros2 pkg prefix xr_rm_teleop"), + ("XR-RM MuJoCo Prefix", "ros2 pkg prefix xr_rm_mujoco"), ( "Open Controller Position Monitor", CONTROLLER_POSITION_MONITOR_ACTION, @@ -294,64 +291,7 @@ def build_commands_by_mode(mode: str) -> list[tuple[str, str]]: CONTROLLER_HZ_MONITOR_ACTION, ), ] - one_click = None - elif mode == "Left Arm": - items = [ - ("Ping Left RM75", f"ping -c 4 {DEFAULT_LEFT_IP}"), - ( - "Left Arm RealMan Launch", - "ros2 launch xr_rm_bringup arm_debug.launch.py arm:=left use_mock:=false", - ), - ("XRobotoolkit UDP Bridge (90 Hz)", _xrobotoolkit_bridge_command()), - ("Left Tool Open", _tool_command("left", True)), - ("Left Tool Close", _tool_command("left", False)), - ( - "Sample UDP Sender (Left, 30s)", - _sample_udp_sender_command("left"), - ), - ] - one_click = None - elif mode == "Right Arm": - items = [ - ("Ping Right RM75", f"ping -c 4 {DEFAULT_RIGHT_IP}"), - ( - "Right Arm RealMan Launch", - "ros2 launch xr_rm_bringup arm_debug.launch.py arm:=right use_mock:=false", - ), - ("XRobotoolkit UDP Bridge (90 Hz)", _xrobotoolkit_bridge_command()), - ("Right Tool Open", _tool_command("right", True)), - ("Right Tool Close", _tool_command("right", False)), - ( - "Sample UDP Sender (Right, 30s)", - _sample_udp_sender_command("right"), - ), - ] - one_click = None - elif mode == "Dual Arm": - items = [ - ("Ping Left RM75", f"ping -c 4 {DEFAULT_LEFT_IP}"), - ("Ping Right RM75", f"ping -c 4 {DEFAULT_RIGHT_IP}"), - ( - "Dual Arm RealMan Launch", - "ros2 launch xr_rm_bringup arm_debug.launch.py arm:=both use_mock:=false", - ), - ("XRobotoolkit UDP Bridge (90 Hz)", _xrobotoolkit_bridge_command()), - ( - "Sample UDP Sender (Both Staggered, 60s)", - _sample_udp_sender_command("both", SAMPLE_SENDER_STAGGERED_SECONDS, "staggered"), - ), - ] - one_click = None - else: - items = [ - ("ROS Doctor Report", "ros2 doctor --report"), - ("XR-RM Bringup Prefix", "ros2 pkg prefix xr_rm_bringup"), - ("XR-RM Input Prefix", "ros2 pkg prefix xr_rm_input"), - ("XR-RM Teleop Prefix", "ros2 pkg prefix xr_rm_teleop"), - ("XRobotoolkit UDP Bridge (90 Hz)", _xrobotoolkit_bridge_command()), - ] - one_click = None - return _finalize_items(items, one_click) + return _finalize_items(items) class LauncherApp: @@ -608,7 +548,7 @@ class LauncherApp: else: warnings.append("[WARN] Could not identify x-terminal-emulator target.") - for package in ("xr_rm_bringup", "xr_rm_input", "xr_rm_teleop"): + for package in ("xr_rm_bringup", "xr_rm_input", "xr_rm_teleop", "xr_rm_mujoco"): if self._ros_package_available(package): ok.append(f"[OK] ROS package available: {package}") else: @@ -1254,7 +1194,6 @@ class LauncherApp: title_patterns = ( TERMINAL_TITLE_PREFIX, TOPIC_MONITOR_TITLE, - CMD_VEL_MONITOR_TITLE, CONTROLLER_POSITION_MONITOR_TITLE, CONTROLLER_HZ_MONITOR_TITLE, ROS_GRAPH_MONITOR_TITLE, @@ -1326,24 +1265,21 @@ class LauncherApp: *_xrobotoolkit_cleanup_patterns(stop_pc_service=stop_pc_service), TERMINAL_TITLE_PREFIX, TOPIC_MONITOR_TITLE, - CMD_VEL_MONITOR_TITLE, ROS_GRAPH_MONITOR_TITLE, "xr_rm_topic_monitor_", - "xr_rm_cmd_vel_monitor_", "xr_rm_controller_position_monitor_", "xr_rm_controller_hz_monitor_", "xr_rm_ros_graph_monitor_", "udp_controller_receiver", "sample_udp_sender", "single_arm_velocity_teleop", + "dual_arm_simulator", "ros2 topic echo /xr/left_controller --field pose.position", "ros2 topic echo /xr/right_controller --field pose.position", "ros2 topic echo /xr/left_controller", "ros2 topic echo /xr/right_controller", "ros2 topic hz /xr/left_controller", "ros2 topic hz /xr/right_controller", - "ros2 topic echo /xr_rm/left_rm75/cmd_vel", - "ros2 topic echo /xr_rm/right_rm75/cmd_vel", "ros2 topic list", "ros2 node list", ]