Exclude log record in data collection

This commit is contained in:
Saberlve 2026-08-17 15:06:02 +00:00
parent 172bec5fe6
commit 99546165ec
17 changed files with 117 additions and 63 deletions

View File

@ -84,6 +84,7 @@ Predefined configs are provided under `config/`:
- `robot.teach_sensitivity` — teaching sensitivity, valid range 15
- `robot.manual_gripper_speed` — gripper velocity in normalized position per second (default `0.5`)
- `robot.observe_joint_vel` — record joint velocities in observations (`false` by default)
- `robot.enable_logs` — enable optional per-cycle timing/diagnostic logs (`false` by default)
- `robot.cameras.camera` — Intel RealSense camera (`serial_number_or_name`, resolution, fps)
- `dataset.root` / `dataset.repo_id` / `single_task` / `fps` / `episode_time_s` / `reset_time_s` / `num_episodes` — dataset settings
@ -134,6 +135,7 @@ records the loop period, GELLO read, safety guard, ServoJ, and complete
```bash
uv run uf-robot-teleop \
--config_path config/gello/xarm7_gello_record_config.yaml \
--robot.enable_logs=true \
--fps 60 \
--guard_latency_experiment=true \
--experiment_duration_s 60

View File

@ -84,6 +84,7 @@ ls /dev/serial/by-id/
- `robot.teach_sensitivity` — 示教灵敏度,有效范围 15
- `robot.manual_gripper_speed` — 夹爪速度(每秒归一化位置变化,默认 `0.5`
- `robot.observe_joint_vel` — 是否在观测中记录关节速度(默认 `false`
- `robot.enable_logs` — 是否启用每帧耗时和诊断日志(默认 `false`
- `robot.cameras.camera` — Intel RealSense 相机配置(`serial_number_or_name`、分辨率、fps
- `dataset.root` / `dataset.repo_id` / `single_task` / `fps` / `episode_time_s` / `reset_time_s` / `num_episodes` — 数据集配置
@ -124,6 +125,7 @@ GELLO 读取、安全检测、ServoJ 和完整 `send_action` 耗时:
```bash
uv run uf-robot-teleop \
--config_path config/gello/xarm7_gello_record_config.yaml \
--robot.enable_logs=true \
--fps 60 \
--guard_latency_experiment=true \
--experiment_duration_s 60

View File

@ -5,6 +5,7 @@ robot:
control_space: "joint"
robot_ip: "192.168.1.245"
gripper_type: 1
enable_logs: false
# make sure to edit with your correct configurations!
teleop:

View File

@ -5,6 +5,7 @@ robot:
control_space: "joint"
robot_ip: "192.168.1.245"
gripper_type: 1
enable_logs: false
# make sure to edit with your correct configurations!
teleop:

View File

@ -5,6 +5,7 @@ robot:
control_space: "cartesian"
robot_ip: "192.168.1.245"
gripper_type: 1
enable_logs: false
max_linear_velocity: 200
min_tcp_z_mm: 50
gripper_error_log_path: "logs/xarm7_gripper_errors.log"

View File

@ -5,6 +5,7 @@ robot:
control_space: "joint"
robot_ip: "192.168.1.245"
gripper_type: 1
enable_logs: false
# Up to 60 Hz goal updates; the first command after idle is immediate.
# Keeping the gripper below its 5000 maximum speed avoids C19 in testing.
gripper_command_interval_s: 0.0166667

View File

@ -7,6 +7,7 @@ robot:
robot_ip: "192.168.1.245"
# Gripper type: 1 is the xArm gripper.
gripper_type: 1
enable_logs: false
manual_mode: true
# Normalized gripper position change per second while holding C/O.
manual_gripper_speed: 0.5

View File

@ -5,6 +5,7 @@ robot:
control_space: "cartesian"
robot_ip: "192.168.1.85"
gripper_type: 10
enable_logs: false
max_linear_velocity: 200
cameras:
fisheye:

View File

@ -5,6 +5,7 @@ robot:
control_space: "cartesian"
robot_ip: "192.168.1.127"
gripper_type: 0
enable_logs: false
cameras:
overhead:

View File

@ -12,6 +12,7 @@ robot:
control_space: "cartesian"
robot_ip: "192.168.1.29"
gripper_type: 2
enable_logs: false
max_linear_velocity: 200
cameras:
fisheye:
@ -28,6 +29,7 @@ robot:
control_space: "cartesian"
robot_ip: "192.168.1.83"
gripper_type: 2
enable_logs: false
max_linear_velocity: 200
cameras:
fisheye:

View File

@ -5,6 +5,7 @@ robot:
control_space: "cartesian"
robot_ip: "192.168.1.83"
gripper_type: 2
enable_logs: false
max_linear_velocity: 250
cameras:
fisheye:

View File

@ -55,6 +55,7 @@ GELLO get_action
```bash
uv run uf-robot-teleop \
--config_path config/gello/xarm7_gello_record_config.yaml \
--robot.enable_logs=true \
--fps 60 \
--experiment_1_baseline=true \
--experiment_duration_s 60

View File

@ -107,6 +107,7 @@ class UFRobot(Robot, Thread):
self._is_connected = False
self._is_calibrated =True
self.enable_logs = bool(getattr(config, "enable_logs", False))
self.logs = {}
self._cmd_cnt = 0
@ -539,7 +540,8 @@ class UFRobot(Robot, Thread):
local = self._local_kinematics.tcp_position(joints)
error_mm = float(np.linalg.norm(local - reported))
limit = float(self.config.local_kinematics_max_error_mm)
self.logs["local_kinematics_error_mm"] = error_mm
if getattr(self, "enable_logs", True):
self.logs["local_kinematics_error_mm"] = error_mm
if math.isfinite(error_mm) and error_mm <= limit:
self._local_model_fault_count = 0
return True
@ -820,9 +822,10 @@ class UFRobot(Robot, Thread):
def get_observation(self) -> dict[str, np.ndarray]:
obs_dict = {}
self._log_controller_error_if_changed("get_observation")
logs_enabled = bool(getattr(self, "enable_logs", True))
# Read Stretch state
before_read_t = time.perf_counter()
# Read robot state
before_read_t = time.perf_counter() if logs_enabled else None
if self._control_space == "joint":
code, states = self.real_arm.get_joint_states(is_radian=True, num=3)
pos_list = states[0].copy()
@ -867,12 +870,13 @@ class UFRobot(Robot, Thread):
self.real_arm.robotiq_get_status(number_of_registers=3)
grippos = self.real_arm.robotiq_status['gPO'] # 0..255
grippos_norm = self._gripper_param.get_gripper_norm(grippos) # 0=open, 1=closed
self.logs["read_pos_dt_s"] = time.perf_counter() - before_read_t
if logs_enabled:
self.logs["read_pos_dt_s"] = time.perf_counter() - before_read_t
obs_dict[f"{self.prefix}gripper.pos"] = grippos_norm
# Capture images from cameras
for cam_key, cam in self.cameras.items():
before_camread_t = time.perf_counter()
before_camread_t = time.perf_counter() if logs_enabled else None
frame = cam.async_read()
shape = frame.shape
if (self.camera_height > 0 and self.camera_height != shape[0]) or (self.camera_width > 0 and self.camera_width != shape[1]):
@ -881,7 +885,10 @@ class UFRobot(Robot, Thread):
import cv2
frame = cv2.resize(frame, (camera_height, camera_width), interpolation=cv2.INTER_AREA)
obs_dict[f"{self.prefix}{cam_key}"] = frame
self.logs[f"async_read_camera_{cam_key}_dt_s"] = time.perf_counter() - before_camread_t
if logs_enabled:
self.logs[f"async_read_camera_{cam_key}_dt_s"] = (
time.perf_counter() - before_camread_t
)
return obs_dict
@ -896,6 +903,7 @@ class UFRobot(Robot, Thread):
return self.get_observation()
if not self._rt_report_normal:
raise ConnectionError("RT Report for target robot NOT READY!")
logs_enabled = bool(getattr(self, "enable_logs", True))
with self._update_lock:
positions = self.rt_actual_joint_pos.copy()
velocities = self.rt_actual_joint_speed.copy()
@ -919,9 +927,9 @@ class UFRobot(Robot, Thread):
obs_dict[f"{self.prefix}gripper.pos"] = float(gripper)
for camera_key, camera in self.cameras.items():
before_camera_t = time.perf_counter()
before_camera_t = time.perf_counter() if logs_enabled else None
frame = camera.async_read()
after_camera_t = time.perf_counter()
after_camera_t = time.perf_counter() if logs_enabled else None
shape = frame.shape
if (
self.camera_height > 0
@ -935,20 +943,23 @@ class UFRobot(Robot, Thread):
height = self.camera_height if self.camera_height != 0 else shape[0]
frame = cv2.resize(frame, (width, height), interpolation=cv2.INTER_AREA)
obs_dict[f"{self.prefix}{camera_key}"] = frame
self.logs[f"async_read_camera_{camera_key}_dt_s"] = (
after_camera_t - before_camera_t
)
if not hasattr(self, "_last_realtime_camera_timings"):
self._last_realtime_camera_timings = {}
self._last_realtime_camera_timings[camera_key] = (
before_camera_t,
after_camera_t,
)
self._last_realtime_observation_end_monotonic_s = time.perf_counter()
if logs_enabled:
self.logs[f"async_read_camera_{camera_key}_dt_s"] = (
after_camera_t - before_camera_t
)
if not hasattr(self, "_last_realtime_camera_timings"):
self._last_realtime_camera_timings = {}
self._last_realtime_camera_timings[camera_key] = (
before_camera_t,
after_camera_t,
)
if logs_enabled:
self._last_realtime_observation_end_monotonic_s = time.perf_counter()
return obs_dict
def _send_gripper_action(self, gripper_norm: float) -> None:
gripper_norm = min(max(float(gripper_norm), 0.0), 1.0)
logs_enabled = bool(getattr(self, "enable_logs", True))
if (
self._last_gripper_command is not None
and abs(gripper_norm - self._last_gripper_command)
@ -964,7 +975,7 @@ class UFRobot(Robot, Thread):
if now - self._last_gripper_command_attempt_s < self.config.gripper_command_interval_s:
return
self._last_gripper_command_attempt_s = now
command_start_s = now
command_start_s = now if logs_enabled else None
if self._gripper_type == GripperType.xArmGripper:
grippos = self._gripper_param.get_grippos(gripper_norm)
@ -1005,15 +1016,23 @@ class UFRobot(Robot, Thread):
result = self.real_arm.getset_tgpio_modbus_data(modbus_datas)
code = result[0] if isinstance(result, (tuple, list)) else result
command_dt_ms = (time.perf_counter() - command_start_s) * 1000
command_dt_ms = (
(time.perf_counter() - command_start_s) * 1000
if logs_enabled
else None
)
if code not in (None, 0):
detail = f"target={gripper_norm:.6f}, pulse={grippos}"
if command_dt_ms is not None:
detail += f", dt_ms={command_dt_ms:.3f}"
self._log_gripper_error(
"send_gripper_action",
code,
f"target={gripper_norm:.6f}, pulse={grippos}, dt_ms={command_dt_ms:.3f}",
detail,
)
return
self._log_gripper_command(gripper_norm, grippos, command_dt_ms)
if logs_enabled:
self._log_gripper_command(gripper_norm, grippos, command_dt_ms)
self._last_gripper_command = gripper_norm
def _log_gripper_command(self, target: float, pulse: int, dt_ms: float) -> None:
@ -1139,10 +1158,8 @@ class UFRobot(Robot, Thread):
if self.config.no_action:
return action
before_write_t = time.perf_counter()
self.logs["safety_guard_dt_s"] = 0.0
self.logs["servo_j_dt_s"] = 0.0
self.logs["safety_guard_path"] = "not_run"
logs_enabled = bool(getattr(self, "enable_logs", True))
before_write_t = time.perf_counter() if logs_enabled else None
safe_action = dict(action)
if self._control_space == "joint":
# first sync with gello or other control device SLOWLY!
@ -1152,10 +1169,11 @@ class UFRobot(Robot, Thread):
cmd_list = [0]*(self._dof)
for i in range(self._dof):
cmd_list[i] = action[f"{self.prefix}J{i+1}.pos"]
guard_start_t = time.perf_counter()
guard_start_t = time.perf_counter() if logs_enabled else None
safe_cmd = self._guard_joint_target(cmd_list)
self.logs["safety_guard_dt_s"] = time.perf_counter() - guard_start_t
self.logs["safety_guard_path"] = self._last_guard_path
if logs_enabled:
self.logs["safety_guard_dt_s"] = time.perf_counter() - guard_start_t
self.logs["safety_guard_path"] = self._last_guard_path
if safe_cmd is None:
# Do not send an unverified arm target. Gripper handling below
# remains independent and can continue safely.
@ -1174,11 +1192,12 @@ class UFRobot(Robot, Thread):
code = self.real_arm.set_state(0)
self._check_motion_code("set_state(0)", code)
time.sleep(0.1)
servo_j_start_t = time.perf_counter()
servo_j_start_t = time.perf_counter() if logs_enabled else None
code = self.real_arm.set_servo_angle_j(
safe_cmd[:self._dof].tolist(), speed=jnt_spd, is_radian=True
)
self.logs["servo_j_dt_s"] = time.perf_counter() - servo_j_start_t
if logs_enabled:
self.logs["servo_j_dt_s"] = time.perf_counter() - servo_j_start_t
self._check_motion_code("set_servo_angle_j", code)
elif safe_cmd is not None:
# The legacy mode-6 path uses the absolute move_joint API.
@ -1222,7 +1241,8 @@ class UFRobot(Robot, Thread):
if self._gripper_type > GripperType.NoGripper:
self._send_gripper_action(safe_action[f"{self.prefix}gripper.pos"])
self.logs["write_pos_dt_s"] = time.perf_counter() - before_write_t
if logs_enabled:
self.logs["write_pos_dt_s"] = time.perf_counter() - before_write_t
return safe_action
def print_logs(self) -> None:

View File

@ -20,6 +20,7 @@ class UFRobotConfig(RobotConfig):
gripper_command_threshold: float = 0.01 # normalized change required before sending a new command
gripper_command_interval_s: float = 0.1 # minimum interval between tool RS485 goals
gripper_error_log_path: str | None = "logs/xarm_gripper_errors.log"
enable_logs: bool = False # optional per-cycle timing and diagnostic logs
observe_joint_vel: bool = False # only effective in joint control mode
manual_mode: bool = False # xArm joint teaching mode; records state and optional gripper actions
manual_gripper_speed: float = 0.5 # normalized gripper position per second in manual mode

View File

@ -102,6 +102,18 @@ def _manual_gripper_action_key(action_features):
return next((key for key in action_features if key.endswith("gripper.pos")), None)
def _diagnostic_logs_enabled(robot) -> bool:
"""Return whether optional per-cycle diagnostics are enabled for a robot."""
config = getattr(robot, "config", None)
if config is not None and hasattr(config, "enable_logs"):
return bool(config.enable_logs)
child_robots = getattr(robot, "robots", None)
if child_robots:
return any(_diagnostic_logs_enabled(child) for child in child_robots.values())
return False
def _manual_action_from_observation(observation, action_features, gripper_target=None):
"""Keep only robot action fields when mirroring manual-mode state."""
action = {key: value for key, value in observation.items() if key in action_features}
@ -353,6 +365,7 @@ def record_loop(
manual_gripper_action_key = _manual_gripper_action_key(robot.action_features)
realtime_controller = None
diagnostic_logs_enabled = _diagnostic_logs_enabled(robot)
sync_log_file = None
sync_log_writer = None
sync_frame_index = 0
@ -371,27 +384,29 @@ def record_loop(
initial_observation=last_robot_cmd,
)
realtime_controller.start()
sync_log_dir = Path("logs")
sync_log_dir.mkdir(parents=True, exist_ok=True)
sync_log_path = sync_log_dir / (
f"gello_record_sync_{time.strftime('%Y%m%d_%H%M%S')}_{time.time_ns() % 1_000_000:06d}.csv"
)
sync_log_file = sync_log_path.open("w", newline="", buffering=1)
sync_log_writer = csv.DictWriter(
sync_log_file,
fieldnames=[
"frame",
"state_sample_s",
"action_sent_s",
"action_age_ms",
"observation_end_s",
"state_to_observation_end_ms",
"camera_timings",
"frame_loop_ms",
],
)
sync_log_writer.writeheader()
logging.info("Realtime dataset synchronization log: %s", sync_log_path)
if diagnostic_logs_enabled:
sync_log_dir = Path("logs")
sync_log_dir.mkdir(parents=True, exist_ok=True)
sync_log_path = sync_log_dir / (
f"gello_record_sync_{time.strftime('%Y%m%d_%H%M%S')}_"
f"{time.time_ns() % 1_000_000:06d}.csv"
)
sync_log_file = sync_log_path.open("w", newline="", buffering=1)
sync_log_writer = csv.DictWriter(
sync_log_file,
fieldnames=[
"frame",
"state_sample_s",
"action_sent_s",
"action_age_ms",
"observation_end_s",
"state_to_observation_end_ms",
"camera_timings",
"frame_loop_ms",
],
)
sync_log_writer.writeheader()
logging.info("Realtime dataset synchronization log: %s", sync_log_path)
timestamp = 0
start_episode_t = time.perf_counter()
@ -405,13 +420,16 @@ def record_loop(
# Get robot observation
if realtime_controller is not None:
obs = robot.get_realtime_observation()
observation_monotonic_s = getattr(
robot, "_last_realtime_observation_monotonic_s", time.perf_counter()
)
observation_monotonic_s = getattr(robot, "_last_realtime_observation_monotonic_s", None)
if observation_monotonic_s is None:
observation_monotonic_s = time.perf_counter()
realtime_controller.update_observation(obs)
matched_action, matched_action_sent_s = realtime_controller.action_sample_at(
observation_monotonic_s
)
if diagnostic_logs_enabled:
matched_action, matched_action_sent_s = realtime_controller.action_sample_at(
observation_monotonic_s
)
else:
matched_action = realtime_controller.action_at(observation_monotonic_s)
else:
obs = robot.get_observation()

View File

@ -162,6 +162,10 @@ def teleop_loop(cfg: TeleopConfig):
if cfg.guard_latency_experiment:
_validate_guard_latency_config(cfg)
if hasattr(cfg.robot, "enable_logs") and not cfg.robot.enable_logs:
raise ValueError(
"Guard latency experiment requires robot.enable_logs=true"
)
logging.warning(
"Guard latency experiment enabled: measuring GELLO, safety guard, ServoJ, "
"and total send latency for %.1f active seconds",

View File

@ -1,6 +1,5 @@
#!/usr/bin/env python
import logging
import time
import numpy as np
from lerobot.utils.errors import DeviceAlreadyConnectedError, DeviceNotConnectedError
from ..base_teleop import UFBaseTeleop
@ -170,11 +169,8 @@ class GelloTeleop(UFBaseTeleop):
def get_action(self) -> dict[str, np.ndarray]:
if not self._teleop_enabled:
raise RuntimeError("Gello teleop is disabled")
start = time.perf_counter()
fake_obs = dict({"joint_state": np.array([0.0]*(self.dof+1))}) # for agent.act() argument, actually no use
action_array = self.gello_agent.act(fake_obs) # current gello joint pos as np.ndarray
dt_ms = (time.perf_counter() - start) * 1e3
logger.debug(f"{self} read action: {dt_ms:.1f}ms")
action = {}
for i in range(self.dof):