mirror of
https://github.com/huggingface/lerobot.git
synced 2026-07-24 02:06:15 +00:00
chore(robots): homogenize bi setups
This commit is contained in:
@@ -39,15 +39,17 @@ class BiOpenArmFollower(Robot):
|
|||||||
super().__init__(config)
|
super().__init__(config)
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
# Top-level cameras are distributed evenly: each arm's OpenArmFollower
|
# Top-level cameras are opened by `left_arm` for convenience, but their
|
||||||
# will only open the cameras assigned to it. Per-arm cameras are used
|
# keys stay unprefixed in observations (tracked via `_top_level_cam_keys`).
|
||||||
# as fallback when top-level cameras are empty.
|
self._top_level_cam_keys = set(config.cameras)
|
||||||
if config.cameras:
|
_collisions = self._top_level_cam_keys & set(
|
||||||
left_cameras = config.cameras
|
config.left_arm_config.cameras
|
||||||
right_cameras = {}
|
) | self._top_level_cam_keys & set(config.right_arm_config.cameras)
|
||||||
else:
|
if _collisions:
|
||||||
left_cameras = config.left_arm_config.cameras
|
raise ValueError(
|
||||||
right_cameras = config.right_arm_config.cameras
|
f"Top-level camera names collide with per-arm camera names: {sorted(_collisions)}"
|
||||||
|
)
|
||||||
|
left_arm_cameras = {**config.left_arm_config.cameras, **config.cameras}
|
||||||
|
|
||||||
left_arm_config = OpenArmFollowerConfig(
|
left_arm_config = OpenArmFollowerConfig(
|
||||||
id=f"{config.id}_left" if config.id else None,
|
id=f"{config.id}_left" if config.id else None,
|
||||||
@@ -56,7 +58,7 @@ class BiOpenArmFollower(Robot):
|
|||||||
disable_torque_on_disconnect=config.left_arm_config.disable_torque_on_disconnect,
|
disable_torque_on_disconnect=config.left_arm_config.disable_torque_on_disconnect,
|
||||||
use_velocity_and_torque=config.left_arm_config.use_velocity_and_torque,
|
use_velocity_and_torque=config.left_arm_config.use_velocity_and_torque,
|
||||||
max_relative_target=config.left_arm_config.max_relative_target,
|
max_relative_target=config.left_arm_config.max_relative_target,
|
||||||
cameras=left_cameras,
|
cameras=left_arm_cameras,
|
||||||
side=config.left_arm_config.side,
|
side=config.left_arm_config.side,
|
||||||
can_interface=config.left_arm_config.can_interface,
|
can_interface=config.left_arm_config.can_interface,
|
||||||
use_can_fd=config.left_arm_config.use_can_fd,
|
use_can_fd=config.left_arm_config.use_can_fd,
|
||||||
@@ -75,7 +77,7 @@ class BiOpenArmFollower(Robot):
|
|||||||
disable_torque_on_disconnect=config.right_arm_config.disable_torque_on_disconnect,
|
disable_torque_on_disconnect=config.right_arm_config.disable_torque_on_disconnect,
|
||||||
use_velocity_and_torque=config.right_arm_config.use_velocity_and_torque,
|
use_velocity_and_torque=config.right_arm_config.use_velocity_and_torque,
|
||||||
max_relative_target=config.right_arm_config.max_relative_target,
|
max_relative_target=config.right_arm_config.max_relative_target,
|
||||||
cameras=right_cameras,
|
cameras=config.right_arm_config.cameras,
|
||||||
side=config.right_arm_config.side,
|
side=config.right_arm_config.side,
|
||||||
can_interface=config.right_arm_config.can_interface,
|
can_interface=config.right_arm_config.can_interface,
|
||||||
use_can_fd=config.right_arm_config.use_can_fd,
|
use_can_fd=config.right_arm_config.use_can_fd,
|
||||||
@@ -95,22 +97,19 @@ class BiOpenArmFollower(Robot):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def _motors_ft(self) -> dict[str, type]:
|
def _motors_ft(self) -> dict[str, type]:
|
||||||
left_arm_motors_ft = self.left_arm._motors_ft
|
|
||||||
right_arm_motors_ft = self.right_arm._motors_ft
|
|
||||||
|
|
||||||
# Right first, then left — matches the teleoperator (OpenArmMini) ordering
|
|
||||||
# and the dataset feature names recorded during data collection.
|
|
||||||
return {
|
return {
|
||||||
**{f"right_{k}": v for k, v in right_arm_motors_ft.items()},
|
**{f"left_{k}": v for k, v in self.left_arm._motors_ft.items()},
|
||||||
**{f"left_{k}": v for k, v in left_arm_motors_ft.items()},
|
**{f"right_{k}": v for k, v in self.right_arm._motors_ft.items()},
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _cameras_ft(self) -> dict[str, tuple]:
|
def _cameras_ft(self) -> dict[str, tuple]:
|
||||||
# Cameras already have unique user-chosen names (e.g. "left_wrist", "base",
|
out: dict[str, tuple] = {}
|
||||||
# "right_wrist"), so we merge them directly — unlike motors which need the
|
for k, v in self.left_arm._cameras_ft.items():
|
||||||
# left_/right_ prefix to disambiguate identical per-arm joint names.
|
out[k if k in self._top_level_cam_keys else f"left_{k}"] = v
|
||||||
return {**self.left_arm._cameras_ft, **self.right_arm._cameras_ft}
|
for k, v in self.right_arm._cameras_ft.items():
|
||||||
|
out[f"right_{k}"] = v
|
||||||
|
return out
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def observation_features(self) -> dict[str, type | tuple]:
|
def observation_features(self) -> dict[str, type | tuple]:
|
||||||
@@ -148,21 +147,15 @@ class BiOpenArmFollower(Robot):
|
|||||||
|
|
||||||
@check_if_not_connected
|
@check_if_not_connected
|
||||||
def get_observation(self) -> RobotObservation:
|
def get_observation(self) -> RobotObservation:
|
||||||
obs_dict = {}
|
obs_dict: RobotObservation = {}
|
||||||
|
|
||||||
# Camera keys that should NOT get the arm prefix (they already have unique names)
|
# Add "left_" prefix to per-arm keys; keep top-level camera keys unprefixed.
|
||||||
left_cam_keys = set(self.left_arm.cameras.keys())
|
for key, value in self.left_arm.get_observation().items():
|
||||||
right_cam_keys = set(self.right_arm.cameras.keys())
|
obs_dict[key if key in self._top_level_cam_keys else f"left_{key}"] = value
|
||||||
|
|
||||||
# Right first, then left — matches the teleoperator (OpenArmMini) ordering
|
# Add "right_" prefix
|
||||||
# and the dataset feature names recorded during data collection.
|
for key, value in self.right_arm.get_observation().items():
|
||||||
right_obs = self.right_arm.get_observation()
|
obs_dict[f"right_{key}"] = value
|
||||||
for key, value in right_obs.items():
|
|
||||||
obs_dict[key if key in right_cam_keys else f"right_{key}"] = value
|
|
||||||
|
|
||||||
left_obs = self.left_arm.get_observation()
|
|
||||||
for key, value in left_obs.items():
|
|
||||||
obs_dict[key if key in left_cam_keys else f"left_{key}"] = value
|
|
||||||
|
|
||||||
return obs_dict
|
return obs_dict
|
||||||
|
|
||||||
@@ -189,7 +182,7 @@ class BiOpenArmFollower(Robot):
|
|||||||
prefixed_sent_action_left = {f"left_{key}": value for key, value in sent_action_left.items()}
|
prefixed_sent_action_left = {f"left_{key}": value for key, value in sent_action_left.items()}
|
||||||
prefixed_sent_action_right = {f"right_{key}": value for key, value in sent_action_right.items()}
|
prefixed_sent_action_right = {f"right_{key}": value for key, value in sent_action_right.items()}
|
||||||
|
|
||||||
return {**prefixed_sent_action_right, **prefixed_sent_action_left}
|
return {**prefixed_sent_action_left, **prefixed_sent_action_right}
|
||||||
|
|
||||||
@check_if_not_connected
|
@check_if_not_connected
|
||||||
def disconnect(self):
|
def disconnect(self):
|
||||||
|
|||||||
@@ -32,5 +32,7 @@ class BiOpenArmFollowerConfig(RobotConfig):
|
|||||||
left_arm_config: OpenArmFollowerConfigBase
|
left_arm_config: OpenArmFollowerConfigBase
|
||||||
right_arm_config: OpenArmFollowerConfigBase
|
right_arm_config: OpenArmFollowerConfigBase
|
||||||
|
|
||||||
# Top-level cameras shared across both arms.
|
# Top-level cameras not attached to a specific side. Keys are kept as-is in
|
||||||
|
# observations (no `left_`/`right_` prefix). Per-arm cameras (declared on
|
||||||
|
# `{left,right}_arm_config.cameras`) are prefixed.
|
||||||
cameras: dict[str, CameraConfig] = field(default_factory=dict)
|
cameras: dict[str, CameraConfig] = field(default_factory=dict)
|
||||||
|
|||||||
@@ -41,6 +41,18 @@ class BiRebotB601Follower(Robot):
|
|||||||
super().__init__(config)
|
super().__init__(config)
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
|
# Top-level cameras are opened by `left_arm` for convenience, but their
|
||||||
|
# keys stay unprefixed in observations (tracked via `_top_level_cam_keys`).
|
||||||
|
self._top_level_cam_keys = set(config.cameras)
|
||||||
|
_collisions = self._top_level_cam_keys & set(
|
||||||
|
config.left_arm_config.cameras
|
||||||
|
) | self._top_level_cam_keys & set(config.right_arm_config.cameras)
|
||||||
|
if _collisions:
|
||||||
|
raise ValueError(
|
||||||
|
f"Top-level camera names collide with per-arm camera names: {sorted(_collisions)}"
|
||||||
|
)
|
||||||
|
left_arm_cameras = {**config.left_arm_config.cameras, **config.cameras}
|
||||||
|
|
||||||
left_arm_config = RebotB601FollowerRobotConfig(
|
left_arm_config = RebotB601FollowerRobotConfig(
|
||||||
id=f"{config.id}_left" if config.id else None,
|
id=f"{config.id}_left" if config.id else None,
|
||||||
calibration_dir=config.calibration_dir,
|
calibration_dir=config.calibration_dir,
|
||||||
@@ -49,7 +61,7 @@ class BiRebotB601Follower(Robot):
|
|||||||
dm_serial_baud=config.left_arm_config.dm_serial_baud,
|
dm_serial_baud=config.left_arm_config.dm_serial_baud,
|
||||||
disable_torque_on_disconnect=config.left_arm_config.disable_torque_on_disconnect,
|
disable_torque_on_disconnect=config.left_arm_config.disable_torque_on_disconnect,
|
||||||
max_relative_target=config.left_arm_config.max_relative_target,
|
max_relative_target=config.left_arm_config.max_relative_target,
|
||||||
cameras=config.left_arm_config.cameras,
|
cameras=left_arm_cameras,
|
||||||
motor_can_ids=config.left_arm_config.motor_can_ids,
|
motor_can_ids=config.left_arm_config.motor_can_ids,
|
||||||
pos_vel_velocity=config.left_arm_config.pos_vel_velocity,
|
pos_vel_velocity=config.left_arm_config.pos_vel_velocity,
|
||||||
gripper_torque_ratio=config.left_arm_config.gripper_torque_ratio,
|
gripper_torque_ratio=config.left_arm_config.gripper_torque_ratio,
|
||||||
@@ -86,10 +98,12 @@ class BiRebotB601Follower(Robot):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def _cameras_ft(self) -> dict[str, tuple]:
|
def _cameras_ft(self) -> dict[str, tuple]:
|
||||||
return {
|
out: dict[str, tuple] = {}
|
||||||
**{f"left_{k}": v for k, v in self.left_arm._cameras_ft.items()},
|
for k, v in self.left_arm._cameras_ft.items():
|
||||||
**{f"right_{k}": v for k, v in self.right_arm._cameras_ft.items()},
|
out[k if k in self._top_level_cam_keys else f"left_{k}"] = v
|
||||||
}
|
for k, v in self.right_arm._cameras_ft.items():
|
||||||
|
out[f"right_{k}"] = v
|
||||||
|
return out
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def observation_features(self) -> dict[str, type | tuple]:
|
def observation_features(self) -> dict[str, type | tuple]:
|
||||||
@@ -122,9 +136,11 @@ class BiRebotB601Follower(Robot):
|
|||||||
|
|
||||||
@check_if_not_connected
|
@check_if_not_connected
|
||||||
def get_observation(self) -> RobotObservation:
|
def get_observation(self) -> RobotObservation:
|
||||||
obs_dict = {}
|
obs_dict: RobotObservation = {}
|
||||||
obs_dict.update({f"left_{k}": v for k, v in self.left_arm.get_observation().items()})
|
for k, v in self.left_arm.get_observation().items():
|
||||||
obs_dict.update({f"right_{k}": v for k, v in self.right_arm.get_observation().items()})
|
obs_dict[k if k in self._top_level_cam_keys else f"left_{k}"] = v
|
||||||
|
for k, v in self.right_arm.get_observation().items():
|
||||||
|
obs_dict[f"right_{k}"] = v
|
||||||
return obs_dict
|
return obs_dict
|
||||||
|
|
||||||
@check_if_not_connected
|
@check_if_not_connected
|
||||||
|
|||||||
@@ -14,7 +14,9 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
from lerobot.cameras import CameraConfig
|
||||||
|
|
||||||
from ..config import RobotConfig
|
from ..config import RobotConfig
|
||||||
from ..rebot_b601_follower import RebotB601FollowerConfig
|
from ..rebot_b601_follower import RebotB601FollowerConfig
|
||||||
@@ -27,3 +29,8 @@ class BiRebotB601FollowerConfig(RobotConfig):
|
|||||||
|
|
||||||
left_arm_config: RebotB601FollowerConfig
|
left_arm_config: RebotB601FollowerConfig
|
||||||
right_arm_config: RebotB601FollowerConfig
|
right_arm_config: RebotB601FollowerConfig
|
||||||
|
|
||||||
|
# Top-level cameras not attached to a specific side. Keys are kept as-is in
|
||||||
|
# observations (no `left_`/`right_` prefix). Per-arm cameras (declared on
|
||||||
|
# `{left,right}_arm_config.cameras`) are prefixed.
|
||||||
|
cameras: dict[str, CameraConfig] = field(default_factory=dict)
|
||||||
|
|||||||
@@ -39,6 +39,18 @@ class BiSOFollower(Robot):
|
|||||||
super().__init__(config)
|
super().__init__(config)
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
|
# Top-level cameras are opened by `left_arm` for convenience, but their
|
||||||
|
# keys stay unprefixed in observations (tracked via `_top_level_cam_keys`).
|
||||||
|
self._top_level_cam_keys = set(config.cameras)
|
||||||
|
_collisions = self._top_level_cam_keys & set(
|
||||||
|
config.left_arm_config.cameras
|
||||||
|
) | self._top_level_cam_keys & set(config.right_arm_config.cameras)
|
||||||
|
if _collisions:
|
||||||
|
raise ValueError(
|
||||||
|
f"Top-level camera names collide with per-arm camera names: {sorted(_collisions)}"
|
||||||
|
)
|
||||||
|
left_arm_cameras = {**config.left_arm_config.cameras, **config.cameras}
|
||||||
|
|
||||||
left_arm_config = SOFollowerRobotConfig(
|
left_arm_config = SOFollowerRobotConfig(
|
||||||
id=f"{config.id}_left" if config.id else None,
|
id=f"{config.id}_left" if config.id else None,
|
||||||
calibration_dir=config.calibration_dir,
|
calibration_dir=config.calibration_dir,
|
||||||
@@ -46,7 +58,7 @@ class BiSOFollower(Robot):
|
|||||||
disable_torque_on_disconnect=config.left_arm_config.disable_torque_on_disconnect,
|
disable_torque_on_disconnect=config.left_arm_config.disable_torque_on_disconnect,
|
||||||
max_relative_target=config.left_arm_config.max_relative_target,
|
max_relative_target=config.left_arm_config.max_relative_target,
|
||||||
use_degrees=config.left_arm_config.use_degrees,
|
use_degrees=config.left_arm_config.use_degrees,
|
||||||
cameras=config.left_arm_config.cameras,
|
cameras=left_arm_cameras,
|
||||||
)
|
)
|
||||||
|
|
||||||
right_arm_config = SOFollowerRobotConfig(
|
right_arm_config = SOFollowerRobotConfig(
|
||||||
@@ -77,13 +89,12 @@ class BiSOFollower(Robot):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def _cameras_ft(self) -> dict[str, tuple]:
|
def _cameras_ft(self) -> dict[str, tuple]:
|
||||||
left_arm_cameras_ft = self.left_arm._cameras_ft
|
out: dict[str, tuple] = {}
|
||||||
right_arm_cameras_ft = self.right_arm._cameras_ft
|
for k, v in self.left_arm._cameras_ft.items():
|
||||||
|
out[k if k in self._top_level_cam_keys else f"left_{k}"] = v
|
||||||
return {
|
for k, v in self.right_arm._cameras_ft.items():
|
||||||
**{f"left_{k}": v for k, v in left_arm_cameras_ft.items()},
|
out[f"right_{k}"] = v
|
||||||
**{f"right_{k}": v for k, v in right_arm_cameras_ft.items()},
|
return out
|
||||||
}
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def observation_features(self) -> dict[str, type | tuple]:
|
def observation_features(self) -> dict[str, type | tuple]:
|
||||||
@@ -120,15 +131,15 @@ class BiSOFollower(Robot):
|
|||||||
|
|
||||||
@check_if_not_connected
|
@check_if_not_connected
|
||||||
def get_observation(self) -> RobotObservation:
|
def get_observation(self) -> RobotObservation:
|
||||||
obs_dict = {}
|
obs_dict: RobotObservation = {}
|
||||||
|
|
||||||
# Add "left_" prefix
|
# Add "left_" prefix to per-arm keys; keep top-level camera keys unprefixed.
|
||||||
left_obs = self.left_arm.get_observation()
|
for key, value in self.left_arm.get_observation().items():
|
||||||
obs_dict.update({f"left_{key}": value for key, value in left_obs.items()})
|
obs_dict[key if key in self._top_level_cam_keys else f"left_{key}"] = value
|
||||||
|
|
||||||
# Add "right_" prefix
|
# Add "right_" prefix
|
||||||
right_obs = self.right_arm.get_observation()
|
for key, value in self.right_arm.get_observation().items():
|
||||||
obs_dict.update({f"right_{key}": value for key, value in right_obs.items()})
|
obs_dict[f"right_{key}"] = value
|
||||||
|
|
||||||
return obs_dict
|
return obs_dict
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,9 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
from lerobot.cameras import CameraConfig
|
||||||
|
|
||||||
from ..config import RobotConfig
|
from ..config import RobotConfig
|
||||||
from ..so_follower import SOFollowerConfig
|
from ..so_follower import SOFollowerConfig
|
||||||
@@ -27,3 +29,8 @@ class BiSOFollowerConfig(RobotConfig):
|
|||||||
|
|
||||||
left_arm_config: SOFollowerConfig
|
left_arm_config: SOFollowerConfig
|
||||||
right_arm_config: SOFollowerConfig
|
right_arm_config: SOFollowerConfig
|
||||||
|
|
||||||
|
# Top-level cameras not attached to a specific side. Keys are kept as-is in
|
||||||
|
# observations (no `left_`/`right_` prefix). Per-arm cameras (declared on
|
||||||
|
# `{left,right}_arm_config.cameras`) are prefixed.
|
||||||
|
cameras: dict[str, CameraConfig] = field(default_factory=dict)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ from ..openarm_leader import OpenArmLeaderConfigBase
|
|||||||
@TeleoperatorConfig.register_subclass("bi_openarm_leader")
|
@TeleoperatorConfig.register_subclass("bi_openarm_leader")
|
||||||
@dataclass
|
@dataclass
|
||||||
class BiOpenArmLeaderConfig(TeleoperatorConfig):
|
class BiOpenArmLeaderConfig(TeleoperatorConfig):
|
||||||
"""Configuration class for Bi OpenArm Follower robots."""
|
"""Configuration class for Bi OpenArm Leader teleoperators."""
|
||||||
|
|
||||||
left_arm_config: OpenArmLeaderConfigBase
|
left_arm_config: OpenArmLeaderConfigBase
|
||||||
right_arm_config: OpenArmLeaderConfigBase
|
right_arm_config: OpenArmLeaderConfigBase
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from .bi_rebot_102_leader import BiRebotArm102Leader
|
from .bi_rebot_102_leader import BiRebot102Leader
|
||||||
from .config_bi_rebot_102_leader import BiRebotArm102LeaderConfig
|
from .config_bi_rebot_102_leader import BiRebot102LeaderConfig
|
||||||
|
|
||||||
__all__ = ["BiRebotArm102Leader", "BiRebotArm102LeaderConfig"]
|
__all__ = ["BiRebot102Leader", "BiRebot102LeaderConfig"]
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ from lerobot.utils.decorators import check_if_already_connected, check_if_not_co
|
|||||||
|
|
||||||
from ..rebot_102_leader import RebotArm102Leader, RebotArm102LeaderTeleopConfig
|
from ..rebot_102_leader import RebotArm102Leader, RebotArm102LeaderTeleopConfig
|
||||||
from ..teleoperator import Teleoperator
|
from ..teleoperator import Teleoperator
|
||||||
from .config_bi_rebot_102_leader import BiRebotArm102LeaderConfig
|
from .config_bi_rebot_102_leader import BiRebot102LeaderConfig
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class BiRebotArm102Leader(Teleoperator):
|
class BiRebot102Leader(Teleoperator):
|
||||||
"""Bimanual Seeed Studio StarArm102 / reBot Arm 102 leader.
|
"""Bimanual Seeed Studio StarArm102 / reBot Arm 102 leader.
|
||||||
|
|
||||||
Composes two single-arm :class:`RebotArm102Leader` instances. Action keys of
|
Composes two single-arm :class:`RebotArm102Leader` instances. Action keys of
|
||||||
@@ -35,10 +35,10 @@ class BiRebotArm102Leader(Teleoperator):
|
|||||||
leader can teleoperate a bimanual reBot B601 follower.
|
leader can teleoperate a bimanual reBot B601 follower.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
config_class = BiRebotArm102LeaderConfig
|
config_class = BiRebot102LeaderConfig
|
||||||
name = "bi_rebot_102_leader"
|
name = "bi_rebot_102_leader"
|
||||||
|
|
||||||
def __init__(self, config: BiRebotArm102LeaderConfig):
|
def __init__(self, config: BiRebot102LeaderConfig):
|
||||||
super().__init__(config)
|
super().__init__(config)
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from ..rebot_102_leader import RebotArm102LeaderConfig
|
|||||||
|
|
||||||
@TeleoperatorConfig.register_subclass("bi_rebot_102_leader")
|
@TeleoperatorConfig.register_subclass("bi_rebot_102_leader")
|
||||||
@dataclass
|
@dataclass
|
||||||
class BiRebotArm102LeaderConfig(TeleoperatorConfig):
|
class BiRebot102LeaderConfig(TeleoperatorConfig):
|
||||||
"""Configuration class for the bimanual reBot Arm 102 leader teleoperator."""
|
"""Configuration class for the bimanual reBot Arm 102 leader teleoperator."""
|
||||||
|
|
||||||
left_arm_config: RebotArm102LeaderConfig
|
left_arm_config: RebotArm102LeaderConfig
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
import logging
|
import logging
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
|
|
||||||
|
from lerobot.types import RobotAction
|
||||||
from lerobot.utils.decorators import check_if_already_connected, check_if_not_connected
|
from lerobot.utils.decorators import check_if_already_connected, check_if_not_connected
|
||||||
|
|
||||||
from ..so_leader import SOLeader, SOLeaderTeleopConfig
|
from ..so_leader import SOLeader, SOLeaderTeleopConfig
|
||||||
@@ -93,7 +94,7 @@ class BiSOLeader(Teleoperator):
|
|||||||
self.right_arm.setup_motors()
|
self.right_arm.setup_motors()
|
||||||
|
|
||||||
@check_if_not_connected
|
@check_if_not_connected
|
||||||
def get_action(self) -> dict[str, float]:
|
def get_action(self) -> RobotAction:
|
||||||
action_dict = {}
|
action_dict = {}
|
||||||
|
|
||||||
# Add "left_" prefix
|
# Add "left_" prefix
|
||||||
|
|||||||
@@ -104,9 +104,9 @@ def make_teleoperator_from_config(config: TeleoperatorConfig) -> "Teleoperator":
|
|||||||
|
|
||||||
return RebotArm102Leader(config)
|
return RebotArm102Leader(config)
|
||||||
elif config.type == "bi_rebot_102_leader":
|
elif config.type == "bi_rebot_102_leader":
|
||||||
from .bi_rebot_102_leader import BiRebotArm102Leader
|
from .bi_rebot_102_leader import BiRebot102Leader
|
||||||
|
|
||||||
return BiRebotArm102Leader(config)
|
return BiRebot102Leader(config)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
return cast("Teleoperator", make_device_from_device_class(config))
|
return cast("Teleoperator", make_device_from_device_class(config))
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from unittest.mock import MagicMock, patch
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from lerobot.teleoperators.bi_rebot_102_leader import BiRebotArm102Leader, BiRebotArm102LeaderConfig
|
from lerobot.teleoperators.bi_rebot_102_leader import BiRebot102Leader, BiRebot102LeaderConfig
|
||||||
from lerobot.teleoperators.rebot_102_leader import (
|
from lerobot.teleoperators.rebot_102_leader import (
|
||||||
RebotArm102Leader,
|
RebotArm102Leader,
|
||||||
RebotArm102LeaderConfig,
|
RebotArm102LeaderConfig,
|
||||||
@@ -91,11 +91,11 @@ def test_send_feedback_not_implemented(leader):
|
|||||||
|
|
||||||
def test_bimanual_prefixes_features():
|
def test_bimanual_prefixes_features():
|
||||||
with patch(f"{_MODULE}.require_package", lambda *a, **kw: None):
|
with patch(f"{_MODULE}.require_package", lambda *a, **kw: None):
|
||||||
cfg = BiRebotArm102LeaderConfig(
|
cfg = BiRebot102LeaderConfig(
|
||||||
left_arm_config=RebotArm102LeaderConfig(port="/dev/null0"),
|
left_arm_config=RebotArm102LeaderConfig(port="/dev/null0"),
|
||||||
right_arm_config=RebotArm102LeaderConfig(port="/dev/null1"),
|
right_arm_config=RebotArm102LeaderConfig(port="/dev/null1"),
|
||||||
)
|
)
|
||||||
teleop = BiRebotArm102Leader(cfg)
|
teleop = BiRebot102Leader(cfg)
|
||||||
assert any(k.startswith("left_") for k in teleop.action_features)
|
assert any(k.startswith("left_") for k in teleop.action_features)
|
||||||
assert any(k.startswith("right_") for k in teleop.action_features)
|
assert any(k.startswith("right_") for k in teleop.action_features)
|
||||||
assert "left_gripper.pos" in teleop.action_features
|
assert "left_gripper.pos" in teleop.action_features
|
||||||
|
|||||||
Reference in New Issue
Block a user