Compare commits

..

77 Commits

Author SHA1 Message Date
Michel Aractingi e099fc7b23 fix(tests) fixes for reachy2 tests; removing reachy2 references from the script 2025-09-04 23:57:15 +02:00
Gaëlle Lannuzel 10c66a2459 Update minimal version 2025-09-04 17:17:09 +02:00
Gaëlle Lannuzel c10e640fb6 Update minimal version 2025-09-04 16:54:32 +02:00
glannuzel 1c9ee16b1d Update reachy2.mdx 2025-09-03 17:49:40 +02:00
glannuzel 1e0bf5732c Update reachy2.mdx 2025-09-03 14:55:22 +02:00
glannuzel a539ccb92c Import joints dict from classes 2025-09-03 12:42:47 +02:00
glannuzel bc88445eae Use ensure_safe_goal_position 2025-09-01 11:31:48 +02:00
glannuzel 5a0f1c032c Fix vel value type 2025-08-28 19:43:36 +02:00
glannuzel 4f277a7c43 Update documentation 2025-08-28 19:18:48 +02:00
glannuzel d023e33cd8 Use odometry for vel with present_position 2025-08-28 18:40:05 +02:00
glannuzel 5264feecef Use disable_torque_on_disconnect 2025-08-28 17:55:30 +02:00
glannuzel 67843db756 Add check no part + test 2025-08-28 17:44:23 +02:00
glannuzel 8e8ebe3732 Add cameras tests 2025-08-28 15:33:56 +02:00
glannuzel f31acfd67f Add use_present_position to teleoperator 2025-08-28 15:15:05 +02:00
glannuzel b31b512954 Delete commented lines 2025-08-27 12:55:00 +02:00
glannuzel 95efab22eb Mock cameras in test_reachy2 2025-08-27 12:52:17 +02:00
glannuzel 3225aeb287 Remove useless elements 2025-08-27 12:13:53 +02:00
glannuzel 128d39fbe5 Fix remainging fake_teleoperator 2025-08-27 12:12:37 +02:00
glannuzel d5adffa289 Create test_reachy2_teleoperator.py 2025-08-27 11:25:34 +02:00
glannuzel 42d11c20c2 Rename reachy2_teleoperator 2025-08-27 11:25:28 +02:00
glannuzel 8b59d28610 Remove useless import and args 2025-08-26 18:40:40 +02:00
glannuzel 9281649228 Update reachy2_camera tests 2025-08-26 18:35:44 +02:00
glannuzel 4259c38856 Update reachy2_cameras depth + CameraManager 2025-08-26 18:35:31 +02:00
glannuzel ecca518072 Update send_action test 2025-08-26 16:31:52 +02:00
glannuzel 31117fcf94 Update test_reachy2.py 2025-08-26 16:04:45 +02:00
glannuzel 56807e772f Fix generate_joints 2025-08-26 16:04:33 +02:00
glannuzel ecad00e0cd Create test_reachy2.py 2025-08-26 13:01:02 +02:00
glannuzel 32ddf7d46e Update pyproject.toml 2025-08-26 11:28:49 +02:00
glannuzel 6560f47d15 Run pre-commit hooks 2025-08-22 10:42:27 +02:00
glannuzel fd75203cfa Disconnect from camera 2025-08-21 18:03:30 +02:00
glannuzel fc6b709da0 Update cameras 2025-08-21 17:34:35 +02:00
glannuzel b07c3b271c Clean code 2025-08-21 15:06:12 +02:00
glannuzel 67acccf778 Delete test files 2025-08-21 15:00:53 +02:00
glannuzel 130a6a969b Merge remote-tracking branch 'upstream/main' into update-lerobot 2025-08-21 14:58:12 +02:00
glannuzel ffaaa63af6 Remove print 2025-08-21 14:55:56 +02:00
glannuzel bdf2e0b71c Fix type in send_action 2025-08-21 14:46:55 +02:00
glannuzel 60c342ad2d Clean files and add types 2025-08-19 15:26:41 +02:00
glannuzel 6e6031bb37 Update reachy2.mdx 2025-08-18 17:45:11 +02:00
glannuzel b32ece5e20 Update reachy2.mdx 2025-08-18 17:27:50 +02:00
glannuzel e3ed212295 Add documentation 2025-08-14 17:31:07 +02:00
glannuzel bdb566201e Add reachy2 dependencies 2025-08-13 13:34:09 +02:00
glannuzel b23a882ccc Add reachy2 in imports 2025-08-13 11:54:09 +02:00
glannuzel d855bc4f4f black + isort 2025-08-11 17:51:01 +02:00
Gaëlle Lannuzel 58be04d46d Merge pull request #7 from pollen-robotics/6-choose-cameras-in-config
6 choose cameras in config
2025-08-11 17:40:08 +02:00
glannuzel 44cdd5ac90 Call generate_joints_dict on _init_ 2025-08-11 17:38:25 +02:00
glannuzel ab6bbd68a7 Modify get_frame() requested size 2025-08-11 17:37:58 +02:00
glannuzel 0c8b0a7cf0 Add arguments for cameras 2025-08-11 17:37:42 +02:00
Gaëlle Lannuzel 091fbd824e Merge pull request #5 from pollen-robotics/separate-parts-in-config
Separate parts in config
2025-08-11 10:22:32 +02:00
glannuzel 2379c3607a Open gripper on start 2025-08-11 10:13:08 +02:00
glannuzel 146403afc1 Create test_robot_client.py 2025-08-08 18:41:17 +02:00
glannuzel 674996db96 Fix forgotten method call 2025-08-08 16:40:22 +02:00
glannuzel cba73399f1 Divide joinits into several dicts in teleoperator 2025-08-08 14:22:49 +02:00
glannuzel 8236f7b52b Divide joints in multiple dicts 2025-08-08 14:19:18 +02:00
glannuzel 20f5f45ac2 Add resume 2025-08-07 11:29:15 +02:00
glannuzel 33b6a4ddcd Remove useless imports 2025-08-06 14:08:07 +02:00
glannuzel f9b8be5730 Use same ip for cameras 2025-08-06 13:52:39 +02:00
glannuzel 346d4950c5 No need for new isntance 2025-08-06 13:27:25 +02:00
glannuzel 75652a39b4 Usable with or without mobile base 2025-08-06 13:15:03 +02:00
glannuzel ee406bdfbf Replay 2025-08-06 11:45:12 +02:00
glannuzel 58b6dd0f73 Update with use_external_commands 2025-08-06 11:44:42 +02:00
glannuzel 6cc6fb36f4 Update data_acquisition_server.py 2025-08-05 12:42:24 +02:00
glannuzel 8217f44235 Update tests 2025-08-04 16:28:42 +02:00
glannuzel ddaca00801 Try adding mobile_base velocity 2025-07-29 15:12:05 +02:00
glannuzel 096cdeb8ad Future depth work 2025-07-29 14:40:10 +02:00
glannuzel 042e34898a Add all rgb cameras 2025-07-29 14:25:15 +02:00
glannuzel c9dc12106f Update utils.py 2025-07-29 14:07:48 +02:00
glannuzel 9735606cbb Create test_reachy2_camera.py 2025-07-29 14:07:39 +02:00
glannuzel 18e7232f88 Add teleop_left camera to observation 2025-07-29 14:07:26 +02:00
glannuzel 8b9fc0d2e9 Add reachy2camera to cameras 2025-07-29 14:06:57 +02:00
glannuzel 08c47eac4b Saving test scripts 2025-07-29 10:26:03 +02:00
glannuzel 8de5610273 Try adding a fake Reachy teleoperator 2025-07-29 10:24:50 +02:00
glannuzel 6acb6bd3e5 Fix observation state 2025-07-29 10:18:47 +02:00
glannuzel 32b08a242d Modify observation_features 2025-07-28 17:59:14 +02:00
glannuzel 2d646fbc64 Remove print 2025-07-28 17:18:43 +02:00
glannuzel 40cb84c005 Fix joint shape 2025-07-28 17:18:00 +02:00
glannuzel 0800406d2b Start adding Reachy 2 (no camera) 2025-07-28 16:37:48 +02:00
Gaëlle Lannuzel 4a1233306b Merge pull request #3 from huggingface/main
Update LeRobot
2025-07-28 10:51:15 +02:00
24 changed files with 63 additions and 161 deletions
+21 -9
View File
@@ -37,14 +37,14 @@ from tqdm import tqdm
from lerobot.datasets.lerobot_dataset import LeRobotDataset
from lerobot.datasets.video_utils import (
decode_video_frames,
decode_video_frames_torchvision,
encode_video_frames,
)
from lerobot.utils.benchmark import TimeBenchmark
BASE_ENCODING = OrderedDict(
[
("vcodec", "h264"),
("vcodec", "libx264"),
("pix_fmt", "yuv444p"),
("g", 2),
("crf", None),
@@ -147,6 +147,18 @@ def sample_timestamps(timestamps_mode: str, ep_num_images: int, fps: int) -> lis
return [idx / fps for idx in frame_indexes]
def decode_video_frames(
video_path: str,
timestamps: list[float],
tolerance_s: float,
backend: str,
) -> torch.Tensor:
if backend in ["pyav", "video_reader"]:
return decode_video_frames_torchvision(video_path, timestamps, tolerance_s, backend)
else:
raise NotImplementedError(backend)
def benchmark_decoding(
imgs_dir: Path,
video_path: Path,
@@ -394,9 +406,9 @@ if __name__ == "__main__":
nargs="*",
default=[
"lerobot/pusht_image",
"CarolinePascal/aloha_mobile_shrimp_image",
"CarolinePascal/paris_street",
"CarolinePascal/kitchen",
"aliberts/aloha_mobile_shrimp_image",
"aliberts/paris_street",
"aliberts/kitchen",
],
help="Datasets repo-ids to test against. First episodes only are used. Must be images.",
)
@@ -404,7 +416,7 @@ if __name__ == "__main__":
"--vcodec",
type=str,
nargs="*",
default=["h264", "hevc", "libsvtav1"],
default=["libx264", "hevc", "libsvtav1"],
help="Video codecs to be tested",
)
parser.add_argument(
@@ -434,7 +446,7 @@ if __name__ == "__main__":
# nargs="*",
# default=[0, 1],
# help="Use the fastdecode tuning option. 0 disables it. "
# "For h264 and h265/hevc, only 1 is possible. "
# "For libx264 and libx265/hevc, only 1 is possible. "
# "For libsvtav1, 1, 2 or 3 are possible values with a higher number meaning a faster decoding optimization",
# )
parser.add_argument(
@@ -453,8 +465,8 @@ if __name__ == "__main__":
"--backends",
type=str,
nargs="*",
default=["torchcodec", "pyav", "video_reader"],
help="Video decoding backend to be tested.",
default=["pyav", "video_reader"],
help="Torchvision decoding backend to be tested.",
)
parser.add_argument(
"--num-samples",
-2
View File
@@ -41,8 +41,6 @@
- sections:
- local: notebooks
title: Notebooks
- local: feetech
title: Updating Feetech Firmware
title: "Resources"
- sections:
- local: contributing
-71
View File
@@ -1,71 +0,0 @@
# Feetech Motor Firmware Update
This tutorial guides you through updating the firmware of Feetech motors using the official Feetech software.
## Prerequisites
- Windows computer (Feetech software is only available for Windows)
- Feetech motor control board
- USB cable to connect the control board to your computer
- Feetech motors connected to the control board
## Step 1: Download Feetech Software
1. Visit the official Feetech software download page: [https://www.feetechrc.com/software.html](https://www.feetechrc.com/software.html)
2. Download the latest version of the Feetech debugging software (FD)
3. Install the software on your Windows computer
## Step 2: Hardware Setup
1. Connect your Feetech motors to the motor control board
2. Connect the motor control board to your Windows computer via USB cable
3. Ensure power is supplied to the motors
## Step 3: Configure Connection
1. Launch the Feetech debugging software
2. Select the correct COM port from the port dropdown menu
- If unsure which port to use, check Windows Device Manager under "Ports (COM & LPT)"
3. Set the appropriate baud rate (typically 1000000 for most Feetech motors)
4. Click "Open" to establish communication with the control board
## Step 4: Scan for Motors
1. Once connected, click the "Search" button to detect all connected motors
2. The software will automatically discover and list all motors on the bus
3. Each motor will appear with its ID number
## Step 5: Update Firmware
For each motor you want to update:
1. **Select the motor** from the list by clicking on it
2. **Click on Upgrade tab**:
3. **Click on Online button**:
- If an potential firmware update is found, it will be displayed in the box
4. **Click on Upgrade button**:
- The update progress will be displayed
## Step 6: Verify Update
1. After the update completes, the software should automatically refresh the motor information
2. Verify that the firmware version has been updated to the expected version
## Important Notes
⚠️ **Warning**: Do not disconnect power or USB during firmware updates, it will potentially brick the motor.
## Bonus: Motor Debugging on Linux/macOS
For debugging purposes only, you can use the open-source Feetech Debug Tool:
- **Repository**: [FT_SCServo_Debug_Qt](https://github.com/CarolinePascal/FT_SCServo_Debug_Qt/tree/fix/port-search-timer)
### Installation Instructions
Follow the instructions in the repository to install the tool, for Ubuntu you can directly install it, for MacOS you need to build it from source.
**Limitations:**
- This tool is for debugging and parameter adjustment only
- Firmware updates must still be done on Windows with official Feetech software
+2 -5
View File
@@ -440,9 +440,8 @@ class LeRobotDataset(torch.utils.data.Dataset):
download_videos (bool, optional): Flag to download the videos. Note that when set to True but the
video files are already present on local disk, they won't be downloaded again. Defaults to
True.
video_backend (str | None, optional): Video backend to use for decoding videos. Defaults to 'torchcodec'
when available on the platform; otherwise, defaults to torchvision's default backend : 'pyav'.
You can also use 'video_reader' which is another decoder of torchvision.
video_backend (str | None, optional): Video backend to use for decoding videos. Defaults to torchcodec when available int the platform; otherwise, defaults to 'pyav'.
You can also use the 'pyav' decoder used by Torchvision, which used to be the default option, or 'video_reader' which is another decoder of Torchvision.
batch_encoding_size (int, optional): Number of episodes to accumulate before batch encoding videos.
Set to 1 for immediate encoding (default), or higher for batched encoding. Defaults to 1.
"""
@@ -826,8 +825,6 @@ class LeRobotDataset(torch.utils.data.Dataset):
"""
if not episode_data:
episode_buffer = self.episode_buffer
else:
episode_buffer = episode_data
validate_episode_buffer(episode_buffer, self.meta.total_episodes, self.features)
+1 -8
View File
@@ -209,14 +209,7 @@ def record_loop(
(
t
for t in teleop
if isinstance(
t,
(
so100_leader.SO100Leader,
so101_leader.SO101Leader,
koch_leader.KochLeader,
),
)
if isinstance(t, (so100_leader.SO100Leader, so101_leader.SO101Leader, koch_leader.KochLeader))
),
None,
)
@@ -29,10 +29,10 @@ class BiSO100FollowerConfig(RobotConfig):
# Optional
left_arm_disable_torque_on_disconnect: bool = True
left_arm_max_relative_target: float | dict[str, float] | None = None
left_arm_max_relative_target: int | None = None
left_arm_use_degrees: bool = False
right_arm_disable_torque_on_disconnect: bool = True
right_arm_max_relative_target: float | dict[str, float] | None = None
right_arm_max_relative_target: int | None = None
right_arm_use_degrees: bool = False
# cameras (shared between both arms)
+3 -3
View File
@@ -44,8 +44,8 @@ class HopeJrArmConfig(RobotConfig):
disable_torque_on_disconnect: bool = True
# `max_relative_target` limits the magnitude of the relative positional target vector for safety purposes.
# Set this to a positive scalar to have the same value for all motors, or a dictionary that maps motor
# names to the max_relative_target value for that motor.
max_relative_target: float | dict[str, float] | None = None
# Set this to a positive scalar to have the same value for all motors, or a list that is the same length as
# the number of motors in your follower arms.
max_relative_target: int | None = None
cameras: dict[str, CameraConfig] = field(default_factory=dict)
@@ -28,9 +28,9 @@ class KochFollowerConfig(RobotConfig):
disable_torque_on_disconnect: bool = True
# `max_relative_target` limits the magnitude of the relative positional target vector for safety purposes.
# Set this to a positive scalar to have the same value for all motors, or a dictionary that maps motor
# names to the max_relative_target value for that motor.
max_relative_target: float | dict[str, float] | None = None
# Set this to a positive scalar to have the same value for all motors, or a list that is the same length as
# the number of motors in your follower arms.
max_relative_target: int | None = None
# cameras
cameras: dict[str, CameraConfig] = field(default_factory=dict)
@@ -110,7 +110,6 @@ class KochFollower(Robot):
return self.bus.is_calibrated
def calibrate(self) -> None:
self.bus.disable_torque()
if self.calibration:
# Calibration file exists, ask user whether to use it or run new calibration
user_input = input(
@@ -121,6 +120,7 @@ class KochFollower(Robot):
self.bus.write_calibration(self.calibration)
return
logger.info(f"\nRunning calibration of {self}")
self.bus.disable_torque()
for motor in self.bus.motors:
self.bus.write("Operating_Mode", motor, OperatingMode.EXTENDED_POSITION.value)
+3 -3
View File
@@ -39,9 +39,9 @@ class LeKiwiConfig(RobotConfig):
disable_torque_on_disconnect: bool = True
# `max_relative_target` limits the magnitude of the relative positional target vector for safety purposes.
# Set this to a positive scalar to have the same value for all motors, or a dictionary that maps motor
# names to the max_relative_target value for that motor.
max_relative_target: float | dict[str, float] | None = None
# Set this to a positive scalar to have the same value for all motors, or a list that is the same length as
# the number of motors in your follower arms.
max_relative_target: int | None = None
cameras: dict[str, CameraConfig] = field(default_factory=lekiwi_cameras_config)
@@ -30,9 +30,9 @@ class SO100FollowerConfig(RobotConfig):
disable_torque_on_disconnect: bool = True
# `max_relative_target` limits the magnitude of the relative positional target vector for safety purposes.
# Set this to a positive scalar to have the same value for all motors, or a dictionary that maps motor
# names to the max_relative_target value for that motor.
max_relative_target: float | dict[str, float] | None = None
# Set this to a positive scalar to have the same value for all motors, or a list that is the same length as
# the number of motors in your follower arms.
max_relative_target: int | None = None
# cameras
cameras: dict[str, CameraConfig] = field(default_factory=dict)
@@ -161,11 +161,6 @@ class SO100Follower(Robot):
self.bus.write("I_Coefficient", motor, 0)
self.bus.write("D_Coefficient", motor, 32)
if motor == "gripper":
self.bus.write("Max_Torque_Limit", motor, 500) # 50% of max torque to avoid burnout
self.bus.write("Protection_Current", motor, 250) # 50% of max current to avoid burnout
self.bus.write("Overload_Torque", motor, 25) # 25% torque when overloaded
def setup_motors(self) -> None:
for motor in reversed(self.bus.motors):
input(f"Connect the controller board to the '{motor}' motor only and press enter.")
@@ -30,9 +30,9 @@ class SO101FollowerConfig(RobotConfig):
disable_torque_on_disconnect: bool = True
# `max_relative_target` limits the magnitude of the relative positional target vector for safety purposes.
# Set this to a positive scalar to have the same value for all motors, or a dictionary that maps motor
# names to the max_relative_target value for that motor.
max_relative_target: float | dict[str, float] | None = None
# Set this to a positive scalar to have the same value for all motors, or a list that is the same length as
# the number of motors in your follower arms.
max_relative_target: int | None = None
# cameras
cameras: dict[str, CameraConfig] = field(default_factory=dict)
@@ -157,13 +157,6 @@ class SO101Follower(Robot):
self.bus.write("I_Coefficient", motor, 0)
self.bus.write("D_Coefficient", motor, 32)
if motor == "gripper":
self.bus.write(
"Max_Torque_Limit", motor, 500
) # 50% of the max torque limit to avoid burnout
self.bus.write("Protection_Current", motor, 250) # 50% of max current to avoid burnout
self.bus.write("Overload_Torque", motor, 25) # 25% torque when overloaded
def setup_motors(self) -> None:
for motor in reversed(self.bus.motors):
input(f"Connect the controller board to the '{motor}' motor only and press enter.")
@@ -24,6 +24,11 @@ from ..config import RobotConfig
@RobotConfig.register_subclass("stretch3")
@dataclass
class Stretch3RobotConfig(RobotConfig):
# `max_relative_target` limits the magnitude of the relative positional target vector for safety purposes.
# Set this to a positive scalar to have the same value for all motors, or a list that is the same length as
# the number of motors in your follower arms.
max_relative_target: int | None = None
# cameras
cameras: dict[str, CameraConfig] = field(
default_factory=lambda: {
+1 -1
View File
@@ -74,7 +74,7 @@ def make_robot_from_config(config: RobotConfig) -> Robot:
def ensure_safe_goal_position(
goal_present_pos: dict[str, tuple[float, float]], max_relative_target: float | dict[str, float]
goal_present_pos: dict[str, tuple[float, float]], max_relative_target: float | dict[float]
) -> dict[str, float]:
"""Caps relative action target magnitude for safety."""
+3 -3
View File
@@ -28,15 +28,15 @@ class ViperXConfig(RobotConfig):
# /!\ FOR SAFETY, READ THIS /!\
# `max_relative_target` limits the magnitude of the relative positional target vector for safety purposes.
# Set this to a positive scalar to have the same value for all motors, or a dictionary that maps motor
# names to the max_relative_target value for that motor.
# Set this to a positive scalar to have the same value for all motors, or a list that is the same length as
# the number of motors in your follower arms.
# For Aloha, for every goal position request, motor rotations are capped at 5 degrees by default.
# When you feel more confident with teleoperation or running the policy, you can extend
# this safety limit and even removing it by setting it to `null`.
# Also, everything is expected to work safely out-of-the-box, but we highly advise to
# first try to teleoperate the grippers only (by commenting out the rest of the motors in this yaml),
# then to gradually add more motors (by uncommenting), until you can teleoperate both arms fully
max_relative_target: float | dict[str, float] = 5.0
max_relative_target: int | None = 5
# cameras
cameras: dict[str, CameraConfig] = field(default_factory=dict)
@@ -302,6 +302,11 @@ class RobotClient:
self.logger.debug(f"Current latest action: {latest_action}")
# Get queue state before changes
old_size, old_timesteps = self._inspect_action_queue()
if not old_timesteps:
old_timesteps = [latest_action] # queue was empty
# Get queue state before changes
old_size, old_timesteps = self._inspect_action_queue()
if not old_timesteps:
@@ -88,7 +88,6 @@ class KochLeader(Teleoperator):
return self.bus.is_calibrated
def calibrate(self) -> None:
self.bus.disable_torque()
if self.calibration:
# Calibration file exists, ask user whether to use it or run new calibration
user_input = input(
@@ -99,6 +98,7 @@ class KochLeader(Teleoperator):
self.bus.write_calibration(self.calibration)
return
logger.info(f"\nRunning calibration of {self}")
self.bus.disable_torque()
for motor in self.bus.motors:
self.bus.write("Operating_Mode", motor, OperatingMode.EXTENDED_POSITION.value)
+2
View File
@@ -23,6 +23,8 @@ import pytest
from lerobot.cameras.reachy2_camera import Reachy2Camera, Reachy2CameraConfig
from lerobot.errors import DeviceNotConnectedError
pytest.importorskip("reachy2_sdk")
PARAMS = [
("teleop", "left"),
("teleop", "right"),
-1
View File
@@ -28,7 +28,6 @@ pytest_plugins = [
"tests.fixtures.files",
"tests.fixtures.hub",
"tests.fixtures.optimizers",
"tests.plugins.reachy2_sdk",
]
-30
View File
@@ -1,30 +0,0 @@
import sys
import types
from unittest.mock import MagicMock
def _install_reachy2_sdk_stub():
sdk = types.ModuleType("reachy2_sdk")
sdk.__path__ = []
sdk.ReachySDK = MagicMock(name="ReachySDK")
media = types.ModuleType("reachy2_sdk.media")
media.__path__ = []
camera = types.ModuleType("reachy2_sdk.media.camera")
camera.CameraView = MagicMock(name="CameraView")
camera_manager = types.ModuleType("reachy2_sdk.media.camera_manager")
camera_manager.CameraManager = MagicMock(name="CameraManager")
sdk.media = media
media.camera = camera
media.camera_manager = camera_manager
# Register in sys.modules
sys.modules.setdefault("reachy2_sdk", sdk)
sys.modules.setdefault("reachy2_sdk.media", media)
sys.modules.setdefault("reachy2_sdk.media.camera", camera)
sys.modules.setdefault("reachy2_sdk.media.camera_manager", camera_manager)
def pytest_sessionstart(session):
_install_reachy2_sdk_stub()
+2
View File
@@ -51,6 +51,8 @@ PARAMS = [
{"with_left_teleop_camera": False, "with_torso_camera": True},
]
pytest.importorskip("reachy2_sdk")
def _make_reachy2_sdk_mock():
class JointSpy:
@@ -28,6 +28,8 @@ from lerobot.teleoperators.reachy2_teleoperator import (
Reachy2TeleoperatorConfig,
)
pytest.importorskip("reachy2_sdk")
# {lerobot_keys: reachy2_sdk_keys}
REACHY2_JOINTS = {
**REACHY2_NECK_JOINTS,