* Start adding Reachy 2 (no camera) * Fix joint shape * Remove print * Modify observation_features * Fix observation state * Try adding a fake Reachy teleoperator * Saving test scripts * Add reachy2camera to cameras * Add teleop_left camera to observation * Create test_reachy2_camera.py * Update utils.py * Add all rgb cameras * Future depth work * Try adding mobile_base velocity * Update tests * Update data_acquisition_server.py * Update with use_external_commands * Replay * Usable with or without mobile base * No need for new isntance * Use same ip for cameras * Remove useless imports * Add resume * Divide joints in multiple dicts * Divide joinits into several dicts in teleoperator * Fix forgotten method call * Create test_robot_client.py * Open gripper on start * Add arguments for cameras * Modify get_frame() requested size * Call generate_joints_dict on _init_ * black + isort * Add reachy2 in imports * Add reachy2 dependencies * Add documentation * Update reachy2.mdx * Update reachy2.mdx * Clean files and add types * Fix type in send_action * Remove print * Delete test files * Clean code * Update cameras * Disconnect from camera * Run pre-commit hooks * Update pyproject.toml * Create test_reachy2.py * Fix generate_joints * Update test_reachy2.py * Update send_action test * Update reachy2_cameras depth + CameraManager * Update reachy2_camera tests * Remove useless import and args * Rename reachy2_teleoperator * Create test_reachy2_teleoperator.py * Fix remainging fake_teleoperator * Remove useless elements * Mock cameras in test_reachy2 * Delete commented lines * Add use_present_position to teleoperator * Add cameras tests * Add check no part + test * Use disable_torque_on_disconnect * Use odometry for vel with present_position * Update documentation * Fix vel value type * Use ensure_safe_goal_position * Import joints dict from classes * Update reachy2.mdx * Update reachy2.mdx * Update minimal version * Update minimal version * fix(tests) fixes for reachy2 tests; removing reachy2 references from the script * Add reachy2_sdk fake as plugins --------- Co-authored-by: Michel Aractingi <michel.aractingi@huggingface.co>
151 lines
4.3 KiB
Python
151 lines
4.3 KiB
Python
#!/usr/bin/env python
|
|
|
|
# Copyright 2025 The HuggingFace Inc. team. All rights reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from lerobot.teleoperators.reachy2_teleoperator import (
|
|
REACHY2_ANTENNAS_JOINTS,
|
|
REACHY2_L_ARM_JOINTS,
|
|
REACHY2_NECK_JOINTS,
|
|
REACHY2_R_ARM_JOINTS,
|
|
REACHY2_VEL,
|
|
Reachy2Teleoperator,
|
|
Reachy2TeleoperatorConfig,
|
|
)
|
|
|
|
# {lerobot_keys: reachy2_sdk_keys}
|
|
REACHY2_JOINTS = {
|
|
**REACHY2_NECK_JOINTS,
|
|
**REACHY2_ANTENNAS_JOINTS,
|
|
**REACHY2_R_ARM_JOINTS,
|
|
**REACHY2_L_ARM_JOINTS,
|
|
}
|
|
|
|
PARAMS = [
|
|
{}, # default config
|
|
{"with_mobile_base": False},
|
|
{"with_mobile_base": False, "with_l_arm": False, "with_antennas": False},
|
|
{"with_r_arm": False, "with_neck": False, "with_antennas": False},
|
|
{"with_mobile_base": False, "with_neck": False},
|
|
{"use_present_position": True},
|
|
]
|
|
|
|
|
|
def _make_reachy2_sdk_mock():
|
|
r = MagicMock(name="ReachySDKMock")
|
|
r.is_connected.return_value = True
|
|
|
|
def _connect():
|
|
r.is_connected.return_value = True
|
|
|
|
def _disconnect():
|
|
r.is_connected.return_value = False
|
|
|
|
# Mock joints with some dummy positions
|
|
joints = {
|
|
k: MagicMock(
|
|
present_position=float(i),
|
|
goal_position=float(i) + 0.5,
|
|
)
|
|
for i, k in enumerate(REACHY2_JOINTS.values())
|
|
}
|
|
r.joints = joints
|
|
|
|
# Mock mobile base with some dummy odometry
|
|
r.mobile_base = MagicMock()
|
|
r.mobile_base.last_cmd_vel = {
|
|
"vx": -0.2,
|
|
"vy": 0.2,
|
|
"vtheta": 11.0,
|
|
}
|
|
r.mobile_base.odometry = {
|
|
"x": 1.0,
|
|
"y": 2.0,
|
|
"theta": 20.0,
|
|
"vx": 0.1,
|
|
"vy": -0.1,
|
|
"vtheta": 8.0,
|
|
}
|
|
|
|
r.connect = MagicMock(side_effect=_connect)
|
|
r.disconnect = MagicMock(side_effect=_disconnect)
|
|
|
|
return r
|
|
|
|
|
|
@pytest.fixture(params=PARAMS, ids=lambda p: "default" if not p else ",".join(p.keys()))
|
|
def reachy2(request):
|
|
with (
|
|
patch(
|
|
"lerobot.teleoperators.reachy2_teleoperator.reachy2_teleoperator.ReachySDK",
|
|
side_effect=lambda *a, **k: _make_reachy2_sdk_mock(),
|
|
),
|
|
):
|
|
overrides = request.param
|
|
cfg = Reachy2TeleoperatorConfig(ip_address="192.168.0.200", **overrides)
|
|
robot = Reachy2Teleoperator(cfg)
|
|
yield robot
|
|
if robot.is_connected:
|
|
robot.disconnect()
|
|
|
|
|
|
def test_connect_disconnect(reachy2):
|
|
assert not reachy2.is_connected
|
|
|
|
reachy2.connect()
|
|
assert reachy2.is_connected
|
|
|
|
reachy2.disconnect()
|
|
assert not reachy2.is_connected
|
|
|
|
reachy2.reachy.disconnect.assert_called_once()
|
|
|
|
|
|
def test_get_action(reachy2):
|
|
reachy2.connect()
|
|
action = reachy2.get_action()
|
|
|
|
expected_keys = set(reachy2.joints_dict)
|
|
expected_keys.update(f"{v}" for v in REACHY2_VEL.keys() if reachy2.config.with_mobile_base)
|
|
assert set(action.keys()) == expected_keys
|
|
|
|
for motor in reachy2.joints_dict.keys():
|
|
if reachy2.config.use_present_position:
|
|
assert action[motor] == reachy2.reachy.joints[REACHY2_JOINTS[motor]].present_position
|
|
else:
|
|
assert action[motor] == reachy2.reachy.joints[REACHY2_JOINTS[motor]].goal_position
|
|
if reachy2.config.with_mobile_base:
|
|
if reachy2.config.use_present_position:
|
|
for vel in REACHY2_VEL.keys():
|
|
assert action[vel] == reachy2.reachy.mobile_base.odometry[REACHY2_VEL[vel]]
|
|
else:
|
|
for vel in REACHY2_VEL.keys():
|
|
assert action[vel] == reachy2.reachy.mobile_base.last_cmd_vel[REACHY2_VEL[vel]]
|
|
|
|
|
|
def test_no_part_declared():
|
|
with pytest.raises(ValueError):
|
|
_ = Reachy2TeleoperatorConfig(
|
|
ip_address="192.168.0.200",
|
|
with_mobile_base=False,
|
|
with_l_arm=False,
|
|
with_r_arm=False,
|
|
with_neck=False,
|
|
with_antennas=False,
|
|
)
|