move gello control frequecy args to teleop

This commit is contained in:
ChenYuhan 2026-08-18 08:58:20 +08:00
parent b2875851ae
commit 70b30fe307
12 changed files with 74 additions and 9 deletions

View File

@ -72,6 +72,7 @@ Predefined configs are provided under `config/`:
- `teleop.joint_ids` / `teleop.joint_signs` — per-arm servo mapping and direction - `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.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.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.root` / `dataset.repo_id` — where the dataset is stored
- `dataset.single_task` — task description saved with each frame - `dataset.single_task` — task description saved with each frame
- `dataset.fps` / `episode_time_s` / `reset_time_s` — recording timing - `dataset.fps` / `episode_time_s` / `reset_time_s` — recording timing

View File

@ -72,6 +72,7 @@ ls /dev/serial/by-id/
- `teleop.joint_ids` / `teleop.joint_signs` — 各型号机械臂的舵机映射与方向 - `teleop.joint_ids` / `teleop.joint_signs` — 各型号机械臂的舵机映射与方向
- `teleop.start_joints` — GELLO 校准参考值(角度),应与 xArm SDK 初始点一致 - `teleop.start_joints` — GELLO 校准参考值(角度),应与 xArm SDK 初始点一致
- `teleop.gripper_id` — GELLO 夹爪舵机 ID`8``-1` 表示无夹爪) - `teleop.gripper_id` — GELLO 夹爪舵机 ID`8``-1` 表示无夹爪)
- `teleop.realtime_control_fps` — GELLO 到 xArm 的独立实时控制频率,与 `dataset.fps` 分开
- `dataset.root` / `dataset.repo_id` — 数据集保存位置 - `dataset.root` / `dataset.repo_id` — 数据集保存位置
- `dataset.single_task` — 随每一帧保存的任务描述 - `dataset.single_task` — 随每一帧保存的任务描述
- `dataset.fps` / `episode_time_s` / `reset_time_s` — 录制时序参数 - `dataset.fps` / `episode_time_s` / `reset_time_s` — 录制时序参数

View File

@ -10,6 +10,7 @@ robot:
# make sure to edit with your correct configurations! # make sure to edit with your correct configurations!
teleop: teleop:
type: uf::gello_teleop type: uf::gello_teleop
realtime_control_fps: 60
port: "/dev/serial/by-id/usb-FTDI_USB__-__Serial_Converter_FTAJZYC7-if00-port0" port: "/dev/serial/by-id/usb-FTDI_USB__-__Serial_Converter_FTAJZYC7-if00-port0"
joint_ids: [1, 2, 4, 6, 7] joint_ids: [1, 2, 4, 6, 7]
joint_signs: [1, 1, -1, 1, 1] joint_signs: [1, 1, -1, 1, 1]

View File

@ -10,6 +10,7 @@ robot:
# make sure to edit with your correct configurations! # make sure to edit with your correct configurations!
teleop: teleop:
type: uf::gello_teleop type: uf::gello_teleop
realtime_control_fps: 60
port: "/dev/serial/by-id/usb-FTDI_USB__-__Serial_Converter_FTAJZYC7-if00-port0" port: "/dev/serial/by-id/usb-FTDI_USB__-__Serial_Converter_FTAJZYC7-if00-port0"
joint_ids: [1, 2, 4, 5, 6, 7] joint_ids: [1, 2, 4, 5, 6, 7]
joint_signs: [1, 1, -1, 1, 1, 1] joint_signs: [1, 1, -1, 1, 1, 1]

View File

@ -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

View File

@ -13,10 +13,9 @@ robot:
gripper_speed: 1500 gripper_speed: 1500
# Use the high-frequency servo interface for lower-latency GELLO tracking. # Use the high-frequency servo interface for lower-latency GELLO tracking.
joint_command_mode: 1 joint_command_mode: 1
realtime_control_fps: 60
max_joint_velocity: 120 max_joint_velocity: 120
# TCP z floor in the xArm base coordinate system (mm). # 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. # CPU-local FK/Jacobian projection keeps ServoJ free of synchronous SDK queries.
tcp_z_guard_backend: "local_projection" tcp_z_guard_backend: "local_projection"
tcp_z_soft_margin_mm: 5.0 tcp_z_soft_margin_mm: 5.0
@ -43,6 +42,8 @@ robot:
teleop: teleop:
type: uf::gello_teleop type: uf::gello_teleop
id: "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" port: "/dev/serial/by-id/usb-FTDI_USB__-__Serial_Converter_FTB9HYVD-if00-port0"
joint_ids: [1, 2, 3, 4, 5, 6, 7] joint_ids: [1, 2, 3, 4, 5, 6, 7]
joint_signs: [1, 1, 1, 1, 1, 1, 1] joint_signs: [1, 1, 1, 1, 1, 1, 1]

View File

@ -20,7 +20,6 @@
robot: robot:
control_space: "joint" control_space: "joint"
joint_command_mode: 1 joint_command_mode: 1
realtime_control_fps: 60
min_tcp_z_mm: 70.0 min_tcp_z_mm: 70.0
tcp_z_guard_backend: "local_projection" tcp_z_guard_backend: "local_projection"
@ -31,6 +30,9 @@ robot:
gripper_command_threshold: 0.01 gripper_command_threshold: 0.01
gripper_speed: 1500 gripper_speed: 1500
teleop:
realtime_control_fps: 60
dataset: dataset:
fps: 30 fps: 30
``` ```

View File

@ -26,7 +26,6 @@ class UFRobotConfig(RobotConfig):
manual_gripper_speed: float = 0.5 # normalized gripper position per second in manual mode 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 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 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. # start_joints and start_tcp_pose are intentionally disabled.
# Reset uses the xArm SDK initial_point instead of configuration poses. # Reset uses the xArm SDK initial_point instead of configuration poses.
max_joint_velocity: int = 90 # °/s, only effective in joint control mode 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") 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): 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") 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): 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") raise ValueError("min_tcp_z_mm must be finite when provided")
if ( if (

View File

@ -380,7 +380,7 @@ def record_loop(
teleop=teleop, teleop=teleop,
teleop_action_processor=teleop_action_processor, teleop_action_processor=teleop_action_processor,
robot_action_processor=robot_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, initial_observation=last_robot_cmd,
) )
realtime_controller.start() realtime_controller.start()

View File

@ -288,6 +288,7 @@ def teleop_loop(cfg: TeleopConfig):
experiment_start_t = None experiment_start_t = None
previous_command_t = None previous_command_t = None
realtime_controller = None realtime_controller = None
realtime_control_fps = int(teleop.config.realtime_control_fps)
def start_realtime_controller(): def start_realtime_controller():
nonlocal realtime_controller nonlocal realtime_controller
@ -303,7 +304,7 @@ def teleop_loop(cfg: TeleopConfig):
teleop, teleop,
teleop_action_processor, teleop_action_processor,
robot_action_processor, robot_action_processor,
cfg.fps, realtime_control_fps,
obs, obs,
) )
realtime_controller.start() realtime_controller.start()
@ -410,7 +411,9 @@ def teleop_loop(cfg: TeleopConfig):
print("\n********** Teleop Control Loop Exit **********") print("\n********** Teleop Control Loop Exit **********")
stop_realtime_controller() stop_realtime_controller()
if latency_samples: 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}") print(f"Guard latency timing log: {output_path}")
cleanup_connections() cleanup_connections()
atexit.unregister(cleanup_connections) atexit.unregister(cleanup_connections)

View File

@ -8,6 +8,8 @@ from lerobot.teleoperators import TeleoperatorConfig
@TeleoperatorConfig.register_subclass("uf::gello_teleop") @TeleoperatorConfig.register_subclass("uf::gello_teleop")
@dataclass @dataclass
class GelloTeleopConfig(TeleoperatorConfig): 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 to connect to the gello dummy arm
port: str = "/dev/serial/by-id/usb-FTDI_USB__-__Serial_Converter_FTAJZYC7-if00-port0" 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): def __post_init__(self):
self.id = 'gello_teleop' if self.id is None else self.id 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): if len(self.joint_ids) != len(self.joint_signs):
raise ValueError("joint_ids and joint_signs must have the same length") raise ValueError("joint_ids and joint_signs must have the same length")
if len(self.joint_ids) != len(self.start_joints): if len(self.joint_ids) != len(self.start_joints):

View File

@ -2,6 +2,9 @@ import time
import pytest import pytest
from lerobot_robot_ufactory.teleoperators.gello_teleop.gello_teleop_config import (
GelloTeleopConfig,
)
from lerobot_robot_ufactory.utils.realtime_teleop import RealtimeTeleopController from lerobot_robot_ufactory.utils.realtime_teleop import RealtimeTeleopController
@ -27,6 +30,15 @@ def identity_action_processor(value):
return value[0] 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(): def test_realtime_controller_sends_without_waiting_for_observation_owner():
robot = FakeRobot() robot = FakeRobot()
controller = RealtimeTeleopController( controller = RealtimeTeleopController(