208 lines
6.8 KiB
Python
208 lines
6.8 KiB
Python
import sys
|
|
import argparse
|
|
import logging
|
|
import time
|
|
from pathlib import Path
|
|
from dataclasses import asdict, dataclass
|
|
from pprint import pformat
|
|
import lerobot_robot_ufactory # patch
|
|
from lerobot.scripts.lerobot_record import register_third_party_plugins
|
|
from lerobot.processor import (
|
|
make_default_processors,
|
|
)
|
|
from lerobot.robots import ( # noqa: F401
|
|
RobotConfig,
|
|
make_robot_from_config,
|
|
)
|
|
from lerobot.teleoperators import ( # noqa: F401
|
|
TeleoperatorConfig,
|
|
make_teleoperator_from_config,
|
|
)
|
|
from lerobot.utils.import_utils import register_third_party_plugins
|
|
from lerobot.utils.robot_utils import precise_sleep
|
|
from lerobot.utils.utils import (
|
|
init_logging,
|
|
)
|
|
from lerobot_robot_ufactory.configs import parser
|
|
from lerobot_robot_ufactory.utils.utils import is_headless, init_keyboard_listener
|
|
from lerobot_robot_ufactory.teleoperators.base_teleop import UFBaseTeleop
|
|
|
|
|
|
@dataclass
|
|
class TeleopConfig:
|
|
robot: RobotConfig
|
|
teleop: TeleoperatorConfig
|
|
fps: int = 30
|
|
|
|
def __post_init__(self):
|
|
if hasattr(self.robot, 'robots'):
|
|
for _, robot in self.robot.robots.items():
|
|
robot.cameras = {}
|
|
else:
|
|
self.robot.cameras = {}
|
|
|
|
|
|
def teleop_loop(cfg: TeleopConfig):
|
|
init_logging()
|
|
logging.info(pformat(asdict(cfg)))
|
|
|
|
teleop = make_teleoperator_from_config(cfg.teleop)
|
|
if hasattr(cfg.robot, "teleop"):
|
|
cfg.robot.teleop = teleop
|
|
robot = make_robot_from_config(cfg.robot)
|
|
|
|
teleop_action_processor, robot_action_processor, robot_observation_processor = make_default_processors()
|
|
|
|
robot.connect()
|
|
teleop.connect()
|
|
|
|
sleep_time_s = 1 / cfg.fps
|
|
|
|
is_evt = not is_headless()
|
|
is_uf_teleop = isinstance(teleop, UFBaseTeleop)
|
|
|
|
def reset_uf_control():
|
|
if is_uf_teleop:
|
|
# Stop teleop output before handing control to the xArm reset motion.
|
|
teleop.set_teleop_enabled(False)
|
|
reset = getattr(robot, "reset_to_initial", None)
|
|
if reset is None:
|
|
reset = robot.configure
|
|
reset()
|
|
if is_uf_teleop:
|
|
obs = robot.get_observation()
|
|
teleop.reset_to_robot_observation(obs)
|
|
teleop.set_teleop_enabled(True, obs)
|
|
|
|
is_reset = is_uf_teleop
|
|
is_paused = True
|
|
events = {"exit": False}
|
|
listener = None
|
|
key_dict = {}
|
|
|
|
if is_evt:
|
|
from pynput import keyboard
|
|
|
|
key_dict = {
|
|
keyboard.Key.esc: 0, # exit
|
|
keyboard.Key.left: 0, # reset and pause
|
|
keyboard.Key.space: 0, # start/pause
|
|
keyboard.Key.enter: 0, # help
|
|
}
|
|
|
|
def on_press(key):
|
|
if key_dict.get(key, 1) == 0:
|
|
try:
|
|
if key == keyboard.Key.esc:
|
|
events["exit"] = True
|
|
print("\nEscape key pressed. Stopping ...")
|
|
except Exception as e:
|
|
print(f"Error handling key press: {e}")
|
|
if key in key_dict:
|
|
key_dict[key] = True
|
|
|
|
def on_release(key):
|
|
try:
|
|
if key == keyboard.Key.enter:
|
|
if is_paused:
|
|
if is_reset:
|
|
print('⌨ [ESC] Exit [Space] Reset / Start [←] Reset')
|
|
else:
|
|
print('⌨ [ESC] Exit [Space] Start [←] Reset')
|
|
else:
|
|
print('⌨ [ESC] Exit [Space] Pause [←] Pause / Reset')
|
|
except Exception as e:
|
|
print(f"Error handling key release: {e}")
|
|
if key in key_dict:
|
|
key_dict[key] = False
|
|
|
|
listener, events = init_keyboard_listener(events=events, on_press=on_press, on_release=on_release)
|
|
print("\n********** Teleop Control Loop Start **********")
|
|
if is_uf_teleop:
|
|
print('⌨ [ESC] Exit [Space] Reset / Start [←] Reset')
|
|
else:
|
|
print('⌨ [ESC] Exit [Space] Start [←] Reset')
|
|
else:
|
|
input('⌨ Press Enter to start teleop >>> ')
|
|
if is_uf_teleop:
|
|
reset_uf_control()
|
|
is_paused = False
|
|
is_reset = False
|
|
print("\n********** Teleop Control Loop Start **********")
|
|
|
|
key_space_pressed = False
|
|
key_left_pressed = False
|
|
|
|
while not events["exit"]:
|
|
start_loop_t = time.perf_counter()
|
|
|
|
if is_evt:
|
|
if key_dict[keyboard.Key.left] and not key_left_pressed:
|
|
key_left_pressed = True
|
|
is_reset = True
|
|
if not is_paused:
|
|
is_paused = True
|
|
if is_uf_teleop:
|
|
teleop.set_teleop_enabled(False)
|
|
print('⌨ [ESC] Exit [Space] Reset / Start [←] Reset')
|
|
elif not key_dict[keyboard.Key.left] and key_left_pressed:
|
|
key_left_pressed = False
|
|
|
|
if key_dict[keyboard.Key.space] and not key_space_pressed:
|
|
key_space_pressed = True
|
|
is_paused = not is_paused
|
|
if is_paused:
|
|
if is_uf_teleop:
|
|
teleop.set_teleop_enabled(False)
|
|
# print('========== Teleop is paused ==========')
|
|
print('⌨ [ESC] Exit [Space] Start [←] Reset')
|
|
else:
|
|
if is_reset:
|
|
reset_uf_control()
|
|
is_reset = False
|
|
# print('========== Teleop is start ==========')
|
|
elif is_uf_teleop:
|
|
obs = robot.get_observation()
|
|
teleop.set_teleop_enabled(True, obs)
|
|
print('⌨ [ESC] Exit [Space] Pause [←] Reset')
|
|
continue
|
|
elif not key_dict[keyboard.Key.space] and key_space_pressed:
|
|
key_space_pressed = False
|
|
|
|
if is_reset or is_paused:
|
|
continue
|
|
|
|
# Get robot observation
|
|
obs = robot.get_observation()
|
|
|
|
act = teleop.get_action()
|
|
act_processed_teleop = teleop_action_processor((act, obs))
|
|
|
|
robot_action_to_send = robot_action_processor((act_processed_teleop, obs))
|
|
robot.send_action(robot_action_to_send)
|
|
|
|
dt_s = time.perf_counter() - start_loop_t
|
|
precise_sleep(sleep_time_s - dt_s)
|
|
|
|
print("\n********** Teleop Control Loop Exit **********")
|
|
robot.disconnect()
|
|
teleop.disconnect()
|
|
if is_evt and listener is not None:
|
|
listener.stop()
|
|
|
|
@parser.wrap()
|
|
def get_cfg(cfg: TeleopConfig) -> TeleopConfig:
|
|
return cfg
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='configuration args')
|
|
args, unknown = parser.parse_known_args()
|
|
sys.argv = [sys.argv[0]] + unknown
|
|
register_third_party_plugins()
|
|
cfg = get_cfg()
|
|
teleop_loop(cfg)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|