33
44from lekiwi_lerobot .utils import record_loop
55from lekiwi_teleoperate .teleoperate .arm import ArmTeleop
6+ from lerobot .cameras .configs import CameraConfig
67from lerobot .datasets .lerobot_dataset import LeRobotDataset
78from lerobot .datasets .utils import hw_to_dataset_features
89from lerobot .robots .lekiwi .config_lekiwi import LeKiwiClientConfig
1112 KeyboardTeleop ,
1213 KeyboardTeleopConfig ,
1314)
15+ from lerobot .teleoperators .so101_leader import SO101Leader , SO101LeaderConfig
1416from lerobot .utils .constants import ACTION , OBS_STR
1517from lerobot .utils .control_utils import (
1618 init_keyboard_listener ,
@@ -32,6 +34,13 @@ def main() -> None:
3234 default = "INFO" ,
3335 help = "Set the logging level (default: INFO). Case-insensitive." ,
3436 )
37+ parser .add_argument (
38+ "-i" ,
39+ "--ip" ,
40+ type = str ,
41+ default = "127.0.0.1" ,
42+ help = "IP address of the robot (default: 127.0.0.1)." ,
43+ )
3544 parser .add_argument (
3645 "-r" ,
3746 "--repo-id" ,
@@ -59,6 +68,18 @@ def main() -> None:
5968 dest = "visualize" ,
6069 help = "Disable Rerun visualization during recording." ,
6170 )
71+ parser .add_argument (
72+ "-la" ,
73+ "--leader-arm" ,
74+ action = "store_true" ,
75+ help = "Use the leader arm for teleoperation (default: False)." ,
76+ )
77+ parser .add_argument (
78+ "--leader-arm-port" ,
79+ type = str ,
80+ default = "/dev/ttyACM0" ,
81+ help = "Serial port for the leader arm (default: /dev/ttyACM0)." ,
82+ )
6283
6384 args = parser .parse_args ()
6485 if args .repo_id is None :
@@ -72,13 +93,28 @@ def main() -> None:
7293 level = log_level , format = "%(asctime)s | %(levelname)-8s | %(message)s" , datefmt = "%Y-%m-%d %H:%M:%S"
7394 )
7495
96+ # Camera config should match the one used in the robot config
97+ # when starting the robot host or simulation.
98+ #
99+ # Based on: --robot.cameras="{ front: {type: opencv, index_or_path: /dev/video0, width: 640, height: 480, fps: 30},
100+ # wrist: {type: opencv, index_or_path: /dev/video2, width: 640, height: 480, fps: 30}}"
101+ camera_config : dict [str , CameraConfig ] = {
102+ "front" : CameraConfig (width = 640 , height = 480 , fps = 30 ),
103+ "wrist" : CameraConfig (width = 640 , height = 480 , fps = 30 ),
104+ }
105+
75106 # Create the robot and teleoperator configurations
76- robot_config = LeKiwiClientConfig (remote_ip = "127.0.0.1" , id = "lekiwi" )
107+ robot_config = LeKiwiClientConfig (remote_ip = args . ip , id = "lekiwi" , cameras = camera_config )
77108 keyboard_config = KeyboardTeleopConfig ()
109+ if args .leader_arm :
110+ teleop_arm_config = SO101LeaderConfig (port = args .leader_arm_port , id = "lekiwi_leader_arm" )
78111
79112 robot = LeKiwiClient (robot_config )
80113 keyboard = KeyboardTeleop (keyboard_config )
81- arm_keyboard_handler = ArmTeleop ()
114+ if args .leader_arm :
115+ leader_arm = SO101Leader (teleop_arm_config )
116+ else :
117+ arm_keyboard_handler = ArmTeleop ()
82118 # Configure the dataset features
83119 action_features = hw_to_dataset_features (robot .action_features , ACTION )
84120 obs_features = hw_to_dataset_features (robot .observation_features , OBS_STR )
@@ -102,6 +138,8 @@ def main() -> None:
102138 # - Sim robot: this script running on LeKiwi sim: `uv run lekiwi_sim --robot.id=my_awesome_kiwi`
103139 robot .connect ()
104140 keyboard .connect ()
141+ if args .leader_arm :
142+ leader_arm .connect ()
105143
106144 if args .visualize :
107145 logging .info ("Initializing Rerun for visualization." )
@@ -113,10 +151,13 @@ def main() -> None:
113151
114152 if not robot .is_connected or not keyboard .is_connected :
115153 raise ValueError ("Robot or keyboard is not connected!" )
154+ if args .leader_arm and not leader_arm .is_connected :
155+ raise ValueError ("Leader arm is not connected!" )
116156 logging .info ("Robot and keyboard are connected." )
117157 recorded_episodes = 0
118158 while recorded_episodes < args .episodes and not events ["stop_recording" ]:
119- arm_keyboard_handler = ArmTeleop ()
159+ if not args .leader_arm :
160+ arm_keyboard_handler = ArmTeleop ()
120161 logging .info (f"Recording episode { recorded_episodes } " )
121162 # Run the record loop
122163 record_loop (
@@ -125,7 +166,7 @@ def main() -> None:
125166 fps = FPS ,
126167 dataset = dataset ,
127168 keyboard_handler = keyboard ,
128- arm_keyboard_handler = arm_keyboard_handler ,
169+ arm_keyboard_handler = leader_arm if args . leader_arm else arm_keyboard_handler ,
129170 control_time_s = EPISODE_TIME_SEC ,
130171 single_task = args .task ,
131172 display_data = args .visualize ,
@@ -140,7 +181,7 @@ def main() -> None:
140181 fps = FPS ,
141182 dataset = None , # Don't record during reset phase
142183 keyboard_handler = keyboard ,
143- arm_keyboard_handler = arm_keyboard_handler ,
184+ arm_keyboard_handler = leader_arm if args . leader_arm else arm_keyboard_handler ,
144185 control_time_s = RESET_TIME_SEC ,
145186 single_task = args .task ,
146187 display_data = args .visualize ,
@@ -163,6 +204,8 @@ def main() -> None:
163204
164205 robot .disconnect ()
165206 keyboard .disconnect ()
207+ if args .leader_arm :
208+ leader_arm .disconnect ()
166209 listener .stop ()
167210
168211
0 commit comments