| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| """ |
| Script to find joint limits and end-effector bounds via teleoperation. |
| |
| Example: |
| |
| ```shell |
| lerobot-find-joint-limits \ |
| --robot.type=so100_follower \ |
| --robot.port=/dev/tty.usbmodem58760432981 \ |
| --robot.id=black \ |
| --teleop.type=so100_leader \ |
| --teleop.port=/dev/tty.usbmodem58760434471 \ |
| --teleop.id=blue \ |
| --urdf_path=<user>/SO-ARM100-main/Simulation/SO101/so101_new_calib.urdf \ |
| --target_frame_name=gripper \ |
| --teleop_time_s=30 \ |
| --warmup_time_s=5 \ |
| --control_loop_fps=30 |
| ``` |
| """ |
|
|
| import time |
| from dataclasses import dataclass |
|
|
| import draccus |
| import numpy as np |
|
|
| from lerobot.model import RobotKinematics |
| from lerobot.robots import ( |
| RobotConfig, |
| bi_openarm_follower, |
| bi_rebot_b601_follower, |
| bi_so_follower, |
| koch_follower, |
| make_robot_from_config, |
| omx_follower, |
| openarm_follower, |
| rebot_b601_follower, |
| so_follower, |
| ) |
| from lerobot.teleoperators import ( |
| TeleoperatorConfig, |
| bi_openarm_leader, |
| bi_openarm_mini, |
| bi_rebot_102_leader, |
| bi_so_leader, |
| gamepad, |
| koch_leader, |
| make_teleoperator_from_config, |
| omx_leader, |
| openarm_leader, |
| openarm_mini, |
| rebot_102_leader, |
| so_leader, |
| ) |
| from lerobot.utils.robot_utils import precise_sleep |
|
|
|
|
| @dataclass |
| class FindJointLimitsConfig: |
| teleop: TeleoperatorConfig |
| robot: RobotConfig |
|
|
| |
| |
| |
| urdf_path: str |
| target_frame_name: str = "gripper" |
|
|
| |
| teleop_time_s: float = 30 |
| |
| warmup_time_s: float = 5 |
| |
| control_loop_fps: int = 30 |
|
|
|
|
| @draccus.wrap() |
| def find_joint_and_ee_bounds(cfg: FindJointLimitsConfig): |
| teleop = make_teleoperator_from_config(cfg.teleop) |
| robot = make_robot_from_config(cfg.robot) |
|
|
| print(f"Connecting to robot: {cfg.robot.type}...") |
| teleop.connect() |
| robot.connect() |
| print("Devices connected.") |
|
|
| |
| try: |
| kinematics = RobotKinematics(cfg.urdf_path, cfg.target_frame_name) |
| except Exception as e: |
| print(f"Error initializing kinematics: {e}") |
| print("Ensure URDF path and target frame name are correct.") |
| robot.disconnect() |
| teleop.disconnect() |
| return |
|
|
| |
| max_pos = None |
| min_pos = None |
| max_ee = None |
| min_ee = None |
|
|
| start_t = time.perf_counter() |
| warmup_done = False |
|
|
| print("\n" + "=" * 40) |
| print(f" WARMUP PHASE ({cfg.warmup_time_s}s)") |
| print(" Move the robot freely to ensure control works.") |
| print(" Data is NOT being recorded yet.") |
| print("=" * 40 + "\n") |
|
|
| try: |
| while True: |
| t0 = time.perf_counter() |
|
|
| |
| action = teleop.get_action() |
| robot.send_action(action) |
|
|
| |
| observation = robot.get_observation() |
| joint_positions = np.array([observation[f"{key}.pos"] for key in robot.bus.motors]) |
|
|
| |
| |
| ee_pos = kinematics.forward_kinematics(joint_positions)[:3, 3] |
|
|
| current_time = time.perf_counter() |
| elapsed = current_time - start_t |
|
|
| |
| if elapsed < cfg.warmup_time_s: |
| |
| pass |
|
|
| else: |
| |
| if not warmup_done: |
| print("\n" + "=" * 40) |
| print(" RECORDING STARTED") |
| print(" Move robot to ALL joint limits.") |
| print(" Press Ctrl+C to stop early and save results.") |
| print("=" * 40 + "\n") |
|
|
| |
| max_pos = joint_positions.copy() |
| min_pos = joint_positions.copy() |
| max_ee = ee_pos.copy() |
| min_ee = ee_pos.copy() |
| warmup_done = True |
|
|
| |
| max_ee = np.maximum(max_ee, ee_pos) |
| min_ee = np.minimum(min_ee, ee_pos) |
| max_pos = np.maximum(max_pos, joint_positions) |
| min_pos = np.minimum(min_pos, joint_positions) |
|
|
| |
| recording_time = elapsed - cfg.warmup_time_s |
| remaining = cfg.teleop_time_s - recording_time |
|
|
| |
| if int(recording_time * 100) % 100 == 0: |
| print(f"Time remaining: {remaining:.1f}s", end="\r") |
|
|
| if recording_time > cfg.teleop_time_s: |
| print("\nTime limit reached.") |
| break |
|
|
| precise_sleep(max(1.0 / cfg.control_loop_fps - (time.perf_counter() - t0), 0.0)) |
|
|
| except KeyboardInterrupt: |
| print("\n\nInterrupted by user. Stopping safely...") |
|
|
| finally: |
| |
| print("\nDisconnecting devices...") |
| robot.disconnect() |
| teleop.disconnect() |
|
|
| |
| if max_pos is not None: |
| print("\n" + "=" * 40) |
| print("FINAL RESULTS") |
| print("=" * 40) |
|
|
| |
| r_max_ee = np.round(max_ee, 4).tolist() |
| r_min_ee = np.round(min_ee, 4).tolist() |
| r_max_pos = np.round(max_pos, 4).tolist() |
| r_min_pos = np.round(min_pos, 4).tolist() |
|
|
| print("\n# End Effector Bounds (x, y, z):") |
| print(f"max_ee = {r_max_ee}") |
| print(f"min_ee = {r_min_ee}") |
|
|
| print("\n# Joint Position Limits (radians):") |
| print(f"max_pos = {r_max_pos}") |
| print(f"min_pos = {r_min_pos}") |
|
|
| else: |
| print("No data recorded (exited during warmup).") |
|
|
|
|
| def main(): |
| find_joint_and_ee_bounds() |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|