- added back degrees mode back to motor bus for IK and FK to work properly

- created new so100followerendeffector robot that inherits from so100follower but takes eef actions and transforms them to joint positions
- created teleop_gamepad device to use gamepad as a teleoperater
This commit is contained in:
Michel Aractingi
2025-05-20 19:11:50 +02:00
committed by AdilZouitine
parent b166296ba5
commit 05fcfca374
7 changed files with 72 additions and 4 deletions

View File

@@ -786,13 +786,19 @@ class MotorsBus(abc.ABC):
raise ValueError(f"Invalid calibration for motor '{motor}': min and max are equal.") raise ValueError(f"Invalid calibration for motor '{motor}': min and max are equal.")
bounded_val = min(max_, max(min_, val)) bounded_val = min(max_, max(min_, val))
# TODO(Steven): normalization can go boom if max_ == min_, we should add a check probably in record_ranges_of_motions
# (which probably indicates the user forgot to move a motor, most likely a gripper-like one)
if self.motors[motor].norm_mode is MotorNormMode.RANGE_M100_100: if self.motors[motor].norm_mode is MotorNormMode.RANGE_M100_100:
norm = (((bounded_val - min_) / (max_ - min_)) * 200) - 100 norm = (((bounded_val - min_) / (max_ - min_)) * 200) - 100
normalized_values[id_] = -norm if drive_mode else norm normalized_values[id_] = -norm if drive_mode else norm
elif self.motors[motor].norm_mode is MotorNormMode.RANGE_0_100: elif self.motors[motor].norm_mode is MotorNormMode.RANGE_0_100:
norm = ((bounded_val - min_) / (max_ - min_)) * 100 norm = ((bounded_val - min_) / (max_ - min_)) * 100
normalized_values[id_] = 100 - norm if drive_mode else norm normalized_values[id_] = 100 - norm if drive_mode else norm
else: elif self.motors[motor].norm_mode is MotorNormMode.DEGREE:
if drive_mode:
val *= -1
val += homing_offset
normalized_values[id_] = val / (self.model_resolution_table[self.motors[motor].model] // 2) * 180
# TODO(alibers): degree mode # TODO(alibers): degree mode
raise NotImplementedError raise NotImplementedError
@@ -819,6 +825,12 @@ class MotorsBus(abc.ABC):
val = 100 - val if drive_mode else val val = 100 - val if drive_mode else val
bounded_val = min(100.0, max(0.0, val)) bounded_val = min(100.0, max(0.0, val))
unnormalized_values[id_] = int((bounded_val / 100) * (max_ - min_) + min_) unnormalized_values[id_] = int((bounded_val / 100) * (max_ - min_) + min_)
elif self.motors[motor].norm_mode is MotorNormMode.DEGREE:
homing_offset = self.calibration[motor].homing_offset
unnormalized_values[id_] = int(val / 180 * (self.model_resolution_table[self.motors[motor].model] // 2))
unnormalized_values[id_] -= homing_offset
if drive_mode:
unnormalized_values[id_] *= -1
else: else:
# TODO(aliberts): degree mode # TODO(aliberts): degree mode
raise NotImplementedError raise NotImplementedError

View File

@@ -1,3 +1,4 @@
from .config import RobotConfig from .config import RobotConfig
from .robot import Robot from .robot import Robot
from .robot_wrapper import RobotWrapper
from .utils import make_robot_from_config from .utils import make_robot_from_config

View File

@@ -14,10 +14,40 @@
import logging import logging
from pprint import pformat from pprint import pformat
from typing import Type
from lerobot.common.robots import RobotConfig from lerobot.common.robots import RobotConfig
from .robot import Robot from .robot import Robot
from .robot_wrapper import RobotWrapper
def make_robot_config(robot_type: str, **kwargs) -> RobotConfig:
if robot_type == "aloha":
raise NotImplementedError # TODO
elif robot_type == "koch_follower":
from .koch_follower.config_koch_follower import KochFollowerConfig
return KochFollowerConfig(**kwargs)
elif robot_type == "so100_follower":
from .so100_follower.config_so100_follower import SO100FollowerConfig
return SO100FollowerConfig(**kwargs)
elif robot_type == "so100_follower_end_effector":
from .so100_follower_end_effector.config_so100_follower_end_effector import SO100FollowerEndEffectorConfig
return SO100FollowerEndEffectorConfig(**kwargs)
elif robot_type == "stretch":
from .stretch3.configuration_stretch3 import Stretch3RobotConfig
return Stretch3RobotConfig(**kwargs)
elif robot_type == "lekiwi":
from .lekiwi.config_lekiwi import LeKiwiConfig
return LeKiwiConfig(**kwargs)
else:
raise ValueError(f"Robot type '{robot_type}' is not available.")
def make_robot_from_config(config: RobotConfig) -> Robot: def make_robot_from_config(config: RobotConfig) -> Robot:
@@ -29,6 +59,10 @@ def make_robot_from_config(config: RobotConfig) -> Robot:
from .so100_follower import SO100Follower from .so100_follower import SO100Follower
return SO100Follower(config) return SO100Follower(config)
elif config.type == "so100_follower_end_effector":
from .so100_follower_end_effector import SO100FollowerEndEffector
return SO100FollowerEndEffector(config)
elif config.type == "so101_follower": elif config.type == "so101_follower":
from .so101_follower import SO101Follower from .so101_follower import SO101Follower
@@ -97,3 +131,9 @@ def get_arm_id(name, arm_type):
like Aloha, it could be left_follower, right_follower, left_leader, or right_leader. like Aloha, it could be left_follower, right_follower, left_leader, or right_leader.
""" """
return f"{name}_{arm_type}" return f"{name}_{arm_type}"
def convert_joint_m100_100_to_degrees(joint_positions: dict[str, float], mins: dict[str, float], maxs: dict[str, float]) -> dict[str, float]:
return {key: ((value + 100) / 200) * (maxs[key] - mins[key]) + mins[key] for key, value in joint_positions.items()}
def convert_joint_degrees_to_m100_100(joint_positions: dict[str, float], mins: dict[str, float], maxs: dict[str, float]) -> dict[str, float]:
return {key: (value - mins[key]) / (maxs[key] - mins[key]) * 200 - 100 for key, value in joint_positions.items()}

View File

@@ -45,5 +45,9 @@ def make_teleoperator_from_config(config: TeleoperatorConfig) -> Teleoperator:
from tests.mocks.mock_teleop import MockTeleop from tests.mocks.mock_teleop import MockTeleop
return MockTeleop(config) return MockTeleop(config)
elif config.type == "gamepad":
from .gamepad.teleop_gamepad import GamepadTeleop
return GamepadTeleop(config)
else: else:
raise ValueError(config.type) raise ValueError(config.type)

View File

@@ -19,6 +19,8 @@ import os.path as osp
import platform import platform
import subprocess import subprocess
import time import time
import select
import sys
from copy import copy, deepcopy from copy import copy, deepcopy
from datetime import datetime, timezone from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
@@ -244,6 +246,14 @@ def is_valid_numpy_dtype_string(dtype_str: str) -> bool:
return False return False
def enter_pressed() -> bool:
return select.select([sys.stdin], [], [], 0)[0] and sys.stdin.readline().strip() == ""
def move_cursor_up(lines):
"""Move the cursor up by a specified number of lines."""
print(f"\033[{lines}A", end="")
class TimerManager: class TimerManager:
""" """
Lightweight utility to measure elapsed time. Lightweight utility to measure elapsed time.

View File

@@ -22,7 +22,7 @@ import time
import numpy as np import numpy as np
import torch import torch
from lerobot.common.robot_devices.utils import busy_wait from lerobot.common.utils.robot_utils import busy_wait
from lerobot.common.utils.utils import init_logging from lerobot.common.utils.utils import init_logging
from lerobot.scripts.server.kinematics import RobotKinematics from lerobot.scripts.server.kinematics import RobotKinematics
@@ -95,7 +95,7 @@ class InputController:
def gripper_command(self): def gripper_command(self):
"""Return the current gripper command.""" """Return the current gripper command."""
if self.open_gripper_command == self.close_gripper_command: if self.open_gripper_command == self.close_gripper_command:
return "no-op" return "stay"
elif self.open_gripper_command: elif self.open_gripper_command:
return "open" return "open"
elif self.close_gripper_command: elif self.close_gripper_command:

View File

@@ -47,6 +47,7 @@ from lerobot.common.robots import ( # noqa: F401
koch_follower, koch_follower,
make_robot_from_config, make_robot_from_config,
so100_follower, so100_follower,
so100_follower_end_effector,
so101_follower, so101_follower,
) )
from lerobot.common.teleoperators import ( from lerobot.common.teleoperators import (
@@ -57,7 +58,7 @@ from lerobot.common.teleoperators import (
from lerobot.common.utils.utils import init_logging, move_cursor_up from lerobot.common.utils.utils import init_logging, move_cursor_up
from lerobot.common.utils.visualization_utils import _init_rerun from lerobot.common.utils.visualization_utils import _init_rerun
from .common.teleoperators import koch_leader, so100_leader, so101_leader # noqa: F401 from .common.teleoperators import koch_leader, so100_leader, gamepad # noqa: F401
@dataclass @dataclass