import json import pyarrow as pa import pyarrow.parquet as parquet import pytest from lerobot_robot_ufactory.scripts import uf_lerobot_replay as replay_module from lerobot_robot_ufactory.scripts.uf_lerobot_replay import ( JOINT_STATE_NAMES, ReplayEpisode, STATE_NAMES, load_replay_episode, replay_episode, state_to_robot_action, ) def _write_dataset(tmp_path, states, episode_indices=None, frame_indices=None, names=None): dataset_root = tmp_path / "dataset" (dataset_root / "data" / "chunk-000").mkdir(parents=True) (dataset_root / "meta").mkdir() episode_indices = episode_indices or [0] * len(states) frame_indices = frame_indices or list(range(len(states))) names = names or list(STATE_NAMES) info = { "fps": 30, "features": { "observation.state": { "dtype": "float32", "names": names, "shape": [len(names)], } }, } (dataset_root / "meta" / "info.json").write_text( json.dumps(info), encoding="utf-8" ) table = pa.table( { "observation.state": states, "episode_index": episode_indices, "frame_index": frame_indices, } ) parquet.write_table(table, dataset_root / "data" / "chunk-000" / "file-000.parquet") return dataset_root def test_load_replay_episode_sorts_and_maps_absolute_states(tmp_path): state_a = [0.1, -0.2, 0.3, -0.4, 0.5, -0.6, 0.7, 1.00375] state_b = [0.2, -0.3, 0.4, -0.5, 0.6, -0.7, 0.8, 0.4] dataset_root = _write_dataset( tmp_path, states=[state_b, state_a, [9.0] * 8], episode_indices=[0, 0, 1], frame_indices=[1, 0, 0], ) episode = load_replay_episode(dataset_root, episode_index=0) assert episode.fps == 30 assert episode.frame_indices == (0, 1) assert episode.states == (tuple(state_a), tuple(state_b)) assert state_to_robot_action(state_a) == dict(zip(STATE_NAMES, state_a, strict=True)) def test_replay_sends_absolute_values_without_delta_accumulation(monkeypatch): class FakeRobot: def __init__(self): self.actions = [] def send_action(self, action): self.actions.append(action.copy()) monkeypatch.setattr(replay_module, "precise_sleep", lambda _: None) state_a = (0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8) state_b = (0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 0.9) episode = ReplayEpisode( fps=30, episode_index=0, frame_indices=(0, 1), states=(state_a, state_b), ) robot = FakeRobot() replay_episode(robot, episode, show_progress=False) assert robot.actions == [ dict(zip(STATE_NAMES, state_a, strict=True)), dict(zip(STATE_NAMES, state_b, strict=True)), ] assert robot.actions[1]["J1.pos"] == state_b[0] def test_ufactory_robot_sends_absolute_radian_joint_targets(monkeypatch): from lerobot_robot_ufactory.robots.uf_robot import uf_robot as uf_robot_module from lerobot_robot_ufactory.robots.uf_robot.uf_robot_config import UFRobotConfig class FakeArm: error_code = 0 mode = 6 def __init__(self): self.calls = [] def set_mode(self, mode): self.calls.append(("set_mode", mode)) self.mode = mode return 0 def set_state(self, state): self.calls.append(("set_state", state)) return 0 def set_servo_angle(self, **kwargs): self.calls.append(("set_servo_angle", kwargs)) return 0 monkeypatch.setattr(uf_robot_module.time, "sleep", lambda _: None) robot = uf_robot_module.UFRobot( UFRobotConfig( id="replay-test", robot_dof=7, control_space="joint", gripper_type=0, cameras={}, ) ) arm = FakeArm() robot.real_arm = arm robot._is_connected = True target = state_to_robot_action((0.1, -0.2, 0.3, -0.4, 0.5, -0.6, 0.7, 0.8)) robot.send_action(target) servo_calls = [kwargs for name, kwargs in arm.calls if name == "set_servo_angle"] assert servo_calls == [ { "angle": [0.1, -0.2, 0.3, -0.4, 0.5, -0.6, 0.7], "speed": 0.2, "is_radian": True, "wait": True, } ] def test_reset_to_initial_enables_robot_before_motion(monkeypatch): from lerobot_robot_ufactory.robots.uf_robot import uf_robot as uf_robot_module from lerobot_robot_ufactory.robots.uf_robot.uf_robot_config import UFRobotConfig class FakeArm: mode = 0 state = 0 error_code = 0 warn_code = 0 def __init__(self): self.calls = [] def motion_enable(self, enable=True): self.calls.append(("motion_enable", enable)) return 0 def clean_error(self): self.calls.append(("clean_error",)) return 0 def set_mode(self, mode): self.calls.append(("set_mode", mode)) self.mode = mode return 0 def set_state(self, state): self.calls.append(("set_state", state)) self.state = state return 0 def set_servo_angle(self, **kwargs): self.calls.append(("set_servo_angle", kwargs)) return 0 monkeypatch.setattr(uf_robot_module.time, "sleep", lambda _: None) robot = uf_robot_module.UFRobot( UFRobotConfig( id="replay-reset-test", robot_dof=7, control_space="joint", gripper_type=0, cameras={}, ) ) arm = FakeArm() robot.real_arm = arm robot._is_connected = True robot._initial_point = [0.0] * 7 robot.configure = lambda: None robot.reset_to_initial() assert [name for name, *_ in arm.calls] == [ "motion_enable", "clean_error", "set_mode", "set_state", "set_servo_angle", ] assert arm.calls[0] == ("motion_enable", True) def test_ufactory_robot_replay_path_sends_absolute_servo_j_targets(): from lerobot_robot_ufactory.robots.uf_robot import uf_robot as uf_robot_module from lerobot_robot_ufactory.robots.uf_robot.uf_robot_config import UFRobotConfig class FakeArm: error_code = 0 mode = 1 state = 0 warn_code = 0 def __init__(self): self.calls = [] def set_servo_angle_j(self, angles, **kwargs): self.calls.append(("set_servo_angle_j", angles, kwargs)) return 0 robot = uf_robot_module.UFRobot( UFRobotConfig( id="replay-servoj-test", robot_dof=7, control_space="joint", joint_command_mode=1, gripper_type=0, cameras={}, ) ) arm = FakeArm() robot.real_arm = arm robot._is_connected = True target = state_to_robot_action((0.1, -0.2, 0.3, -0.4, 0.5, -0.6, 0.7, 0.8)) robot.send_action(target) assert arm.calls == [ ( "set_servo_angle_j", [0.1, -0.2, 0.3, -0.4, 0.5, -0.6, 0.7], {"speed": 0.2, "is_radian": True}, ) ] def test_load_replay_episode_rejects_wrong_state_schema(tmp_path): states = [[0.0] * 8] dataset_root = _write_dataset(tmp_path, states, names=list(JOINT_STATE_NAMES) + ["wrong"]) with pytest.raises(ValueError, match="absolute fields"): load_replay_episode(dataset_root) def test_load_replay_episode_rejects_non_contiguous_frames(tmp_path): dataset_root = _write_dataset(tmp_path, [[0.0] * 8, [1.0] * 8], frame_indices=[0, 2]) with pytest.raises(ValueError, match="contiguous"): load_replay_episode(dataset_root) def test_state_to_robot_action_rejects_non_finite_values(): state = [0.0] * 7 + [float("nan")] with pytest.raises(ValueError, match="non-finite"): state_to_robot_action(state)