From 70b30fe307a4e8925d6e45a00e561a038ad387a5 Mon Sep 17 00:00:00 2001 From: ChenYuhan <2514158309@qq.com> Date: Tue, 18 Aug 2026 08:58:20 +0800 Subject: [PATCH] move gello control frequecy args to teleop --- README.md | 1 + README_ZH.md | 1 + config/gello/xarm5_gello_record_config.yaml | 1 + config/gello/xarm6_gello_record_config.yaml | 1 + .../xarm7_gello_cartesian_record_config.yaml | 42 +++++++++++++++++++ config/gello/xarm7_gello_record_config.yaml | 5 ++- docs/gello_xarm7_smooth_safe_recording_zh.md | 4 +- .../robots/uf_robot/uf_robot_config.py | 3 -- .../scripts/uf_lerobot_record.py | 2 +- .../scripts/uf_robot_teleop.py | 7 +++- .../gello_teleop/gello_teleop_config.py | 4 ++ tests/test_realtime_teleop.py | 12 ++++++ 12 files changed, 74 insertions(+), 9 deletions(-) create mode 100644 config/gello/xarm7_gello_cartesian_record_config.yaml diff --git a/README.md b/README.md index e40fc9e..ee49527 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ Predefined configs are provided under `config/`: - `teleop.joint_ids` / `teleop.joint_signs` — per-arm servo mapping and direction - `teleop.start_joints` — GELLO calibration reference, should match the xArm SDK initial point (degrees) - `teleop.gripper_id` — GELLO gripper servo ID (`8`; `-1` disables it) +- `teleop.realtime_control_fps` — independent GELLO-to-xArm command loop rate; it is separate from `dataset.fps` - `dataset.root` / `dataset.repo_id` — where the dataset is stored - `dataset.single_task` — task description saved with each frame - `dataset.fps` / `episode_time_s` / `reset_time_s` — recording timing diff --git a/README_ZH.md b/README_ZH.md index 61ba00e..5b67982 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -72,6 +72,7 @@ ls /dev/serial/by-id/ - `teleop.joint_ids` / `teleop.joint_signs` — 各型号机械臂的舵机映射与方向 - `teleop.start_joints` — GELLO 校准参考值(角度),应与 xArm SDK 初始点一致 - `teleop.gripper_id` — GELLO 夹爪舵机 ID(`8`;`-1` 表示无夹爪) +- `teleop.realtime_control_fps` — GELLO 到 xArm 的独立实时控制频率,与 `dataset.fps` 分开 - `dataset.root` / `dataset.repo_id` — 数据集保存位置 - `dataset.single_task` — 随每一帧保存的任务描述 - `dataset.fps` / `episode_time_s` / `reset_time_s` — 录制时序参数 diff --git a/config/gello/xarm5_gello_record_config.yaml b/config/gello/xarm5_gello_record_config.yaml index d16f971..2e31ebf 100644 --- a/config/gello/xarm5_gello_record_config.yaml +++ b/config/gello/xarm5_gello_record_config.yaml @@ -10,6 +10,7 @@ robot: # make sure to edit with your correct configurations! teleop: type: uf::gello_teleop + realtime_control_fps: 60 port: "/dev/serial/by-id/usb-FTDI_USB__-__Serial_Converter_FTAJZYC7-if00-port0" joint_ids: [1, 2, 4, 6, 7] joint_signs: [1, 1, -1, 1, 1] diff --git a/config/gello/xarm6_gello_record_config.yaml b/config/gello/xarm6_gello_record_config.yaml index 1792fe6..7eee0fb 100644 --- a/config/gello/xarm6_gello_record_config.yaml +++ b/config/gello/xarm6_gello_record_config.yaml @@ -10,6 +10,7 @@ robot: # make sure to edit with your correct configurations! teleop: type: uf::gello_teleop + realtime_control_fps: 60 port: "/dev/serial/by-id/usb-FTDI_USB__-__Serial_Converter_FTAJZYC7-if00-port0" joint_ids: [1, 2, 4, 5, 6, 7] joint_signs: [1, 1, -1, 1, 1, 1] diff --git a/config/gello/xarm7_gello_cartesian_record_config.yaml b/config/gello/xarm7_gello_cartesian_record_config.yaml new file mode 100644 index 0000000..48a7947 --- /dev/null +++ b/config/gello/xarm7_gello_cartesian_record_config.yaml @@ -0,0 +1,42 @@ +robot: + type: uf::robot + id: "uf_robot_cartesian" + robot_dof: 7 + control_space: "cartesian" + robot_ip: "192.168.1.245" + gripper_type: 1 + max_linear_velocity: 200 + min_tcp_z_mm: -2.0 + gripper_error_log_path: "logs/xarm7_gripper_errors.log" + cameras: + camera: + type: intelrealsense + serial_number_or_name: "242622070583" + width: 640 + height: 480 + fps: 30 + camera2: + type: intelrealsense + serial_number_or_name: "148522072685" + width: 640 + height: 480 + fps: 30 + +teleop: + type: uf::gello_teleop + id: "gello_teleop" + port: "/dev/serial/by-id/usb-FTDI_USB__-__Serial_Converter_FTB9HYVD-if00-port0" + joint_ids: [1, 2, 3, 4, 5, 6, 7] + joint_signs: [1, 1, 1, 1, 1, 1, 1] + gripper_id: 8 + gripper_open_deg: 198.28125 + gripper_close_deg: 155.75 + +dataset: + root: "datasets/xarm7_gello_cartesian_datas" + repo_id: "ufactory/xarm7_gello_cartesian_datas" + single_task: "Pick up the purple grape and drop into the box on the left." + fps: 60 + episode_time_s: 60 + reset_time_s: 20 + push_to_hub: False diff --git a/config/gello/xarm7_gello_record_config.yaml b/config/gello/xarm7_gello_record_config.yaml index c5bc9c7..ed056df 100644 --- a/config/gello/xarm7_gello_record_config.yaml +++ b/config/gello/xarm7_gello_record_config.yaml @@ -13,10 +13,9 @@ robot: gripper_speed: 1500 # Use the high-frequency servo interface for lower-latency GELLO tracking. joint_command_mode: 1 - realtime_control_fps: 60 max_joint_velocity: 120 # TCP z floor in the xArm base coordinate system (mm). - min_tcp_z_mm: 70.0 + min_tcp_z_mm: -2.0 # CPU-local FK/Jacobian projection keeps ServoJ free of synchronous SDK queries. tcp_z_guard_backend: "local_projection" tcp_z_soft_margin_mm: 5.0 @@ -43,6 +42,8 @@ robot: teleop: type: uf::gello_teleop id: "gello_teleop" + # Independent GELLO -> xArm command loop; dataset.fps remains the recording rate. + realtime_control_fps: 60 port: "/dev/serial/by-id/usb-FTDI_USB__-__Serial_Converter_FTB9HYVD-if00-port0" joint_ids: [1, 2, 3, 4, 5, 6, 7] joint_signs: [1, 1, 1, 1, 1, 1, 1] diff --git a/docs/gello_xarm7_smooth_safe_recording_zh.md b/docs/gello_xarm7_smooth_safe_recording_zh.md index 61b5ca7..b2c1da7 100644 --- a/docs/gello_xarm7_smooth_safe_recording_zh.md +++ b/docs/gello_xarm7_smooth_safe_recording_zh.md @@ -20,7 +20,6 @@ robot: control_space: "joint" joint_command_mode: 1 - realtime_control_fps: 60 min_tcp_z_mm: 70.0 tcp_z_guard_backend: "local_projection" @@ -31,6 +30,9 @@ robot: gripper_command_threshold: 0.01 gripper_speed: 1500 +teleop: + realtime_control_fps: 60 + dataset: fps: 30 ``` diff --git a/src/lerobot_robot_ufactory/robots/uf_robot/uf_robot_config.py b/src/lerobot_robot_ufactory/robots/uf_robot/uf_robot_config.py index c15758f..09a8eb2 100644 --- a/src/lerobot_robot_ufactory/robots/uf_robot/uf_robot_config.py +++ b/src/lerobot_robot_ufactory/robots/uf_robot/uf_robot_config.py @@ -26,7 +26,6 @@ class UFRobotConfig(RobotConfig): manual_gripper_speed: float = 0.5 # normalized gripper position per second in manual mode teach_sensitivity: int | None = None # xArm teaching sensitivity, valid range: 1-5 joint_command_mode: int = 6 # 1: servo-angle-j, 6: online trajectory planning - realtime_control_fps: int = 60 # independent of camera/dataset sampling fps # start_joints and start_tcp_pose are intentionally disabled. # Reset uses the xArm SDK initial_point instead of configuration poses. max_joint_velocity: int = 90 # °/s, only effective in joint control mode @@ -62,8 +61,6 @@ class UFRobotConfig(RobotConfig): raise ValueError("gripper_command_interval_s must be finite and non-negative") if self.control_space == "joint" and self.joint_command_mode not in (1, 6): raise ValueError("joint_command_mode must be 1 or 6 for joint control") - if self.realtime_control_fps <= 0: - raise ValueError("realtime_control_fps must be positive") if self.min_tcp_z_mm is not None and not math.isfinite(self.min_tcp_z_mm): raise ValueError("min_tcp_z_mm must be finite when provided") if ( diff --git a/src/lerobot_robot_ufactory/scripts/uf_lerobot_record.py b/src/lerobot_robot_ufactory/scripts/uf_lerobot_record.py index a9a29d5..72038fc 100644 --- a/src/lerobot_robot_ufactory/scripts/uf_lerobot_record.py +++ b/src/lerobot_robot_ufactory/scripts/uf_lerobot_record.py @@ -380,7 +380,7 @@ def record_loop( teleop=teleop, teleop_action_processor=teleop_action_processor, robot_action_processor=robot_action_processor, - fps=getattr(robot.config, "realtime_control_fps", fps), + fps=int(teleop.config.realtime_control_fps), initial_observation=last_robot_cmd, ) realtime_controller.start() diff --git a/src/lerobot_robot_ufactory/scripts/uf_robot_teleop.py b/src/lerobot_robot_ufactory/scripts/uf_robot_teleop.py index 2d68619..ad95bc6 100644 --- a/src/lerobot_robot_ufactory/scripts/uf_robot_teleop.py +++ b/src/lerobot_robot_ufactory/scripts/uf_robot_teleop.py @@ -288,6 +288,7 @@ def teleop_loop(cfg: TeleopConfig): experiment_start_t = None previous_command_t = None realtime_controller = None + realtime_control_fps = int(teleop.config.realtime_control_fps) def start_realtime_controller(): nonlocal realtime_controller @@ -303,7 +304,7 @@ def teleop_loop(cfg: TeleopConfig): teleop, teleop_action_processor, robot_action_processor, - cfg.fps, + realtime_control_fps, obs, ) realtime_controller.start() @@ -410,7 +411,9 @@ def teleop_loop(cfg: TeleopConfig): print("\n********** Teleop Control Loop Exit **********") stop_realtime_controller() if latency_samples: - output_path = _write_guard_latency_timings(latency_samples, cfg.timing_log_dir, cfg.fps) + output_path = _write_guard_latency_timings( + latency_samples, cfg.timing_log_dir, realtime_control_fps + ) print(f"Guard latency timing log: {output_path}") cleanup_connections() atexit.unregister(cleanup_connections) diff --git a/src/lerobot_robot_ufactory/teleoperators/gello_teleop/gello_teleop_config.py b/src/lerobot_robot_ufactory/teleoperators/gello_teleop/gello_teleop_config.py index 774f819..94a7714 100644 --- a/src/lerobot_robot_ufactory/teleoperators/gello_teleop/gello_teleop_config.py +++ b/src/lerobot_robot_ufactory/teleoperators/gello_teleop/gello_teleop_config.py @@ -8,6 +8,8 @@ from lerobot.teleoperators import TeleoperatorConfig @TeleoperatorConfig.register_subclass("uf::gello_teleop") @dataclass class GelloTeleopConfig(TeleoperatorConfig): + # Frequency of the independent GELLO -> xArm realtime control loop. + realtime_control_fps: int = 60 # Port to connect to the gello dummy arm port: str = "/dev/serial/by-id/usb-FTDI_USB__-__Serial_Converter_FTAJZYC7-if00-port0" @@ -27,6 +29,8 @@ class GelloTeleopConfig(TeleoperatorConfig): def __post_init__(self): self.id = 'gello_teleop' if self.id is None else self.id + if self.realtime_control_fps <= 0: + raise ValueError("realtime_control_fps must be positive") if len(self.joint_ids) != len(self.joint_signs): raise ValueError("joint_ids and joint_signs must have the same length") if len(self.joint_ids) != len(self.start_joints): diff --git a/tests/test_realtime_teleop.py b/tests/test_realtime_teleop.py index 97fa50f..79c0ee5 100644 --- a/tests/test_realtime_teleop.py +++ b/tests/test_realtime_teleop.py @@ -2,6 +2,9 @@ import time import pytest +from lerobot_robot_ufactory.teleoperators.gello_teleop.gello_teleop_config import ( + GelloTeleopConfig, +) from lerobot_robot_ufactory.utils.realtime_teleop import RealtimeTeleopController @@ -27,6 +30,15 @@ def identity_action_processor(value): return value[0] +def test_gello_realtime_control_fps_defaults_to_60(): + assert GelloTeleopConfig().realtime_control_fps == 60 + + +def test_gello_realtime_control_fps_must_be_positive(): + with pytest.raises(ValueError, match="realtime_control_fps"): + GelloTeleopConfig(realtime_control_fps=0) + + def test_realtime_controller_sends_without_waiting_for_observation_owner(): robot = FakeRobot() controller = RealtimeTeleopController(