From 9dab08dfbc2e7efac7b4c69b272bdbca5c7e7311 Mon Sep 17 00:00:00 2001 From: Simon Alibert Date: Tue, 20 May 2025 09:53:01 +0200 Subject: [PATCH 1/6] Remove old .cache folder --- .../aloha_default/left_follower.json | 68 ------------------- .../aloha_default/left_leader.json | 68 ------------------- .../aloha_default/right_follower.json | 68 ------------------- .../aloha_default/right_leader.json | 68 ------------------- .gitignore | 7 +- 5 files changed, 4 insertions(+), 275 deletions(-) delete mode 100644 .cache/calibration/aloha_default/left_follower.json delete mode 100644 .cache/calibration/aloha_default/left_leader.json delete mode 100644 .cache/calibration/aloha_default/right_follower.json delete mode 100644 .cache/calibration/aloha_default/right_leader.json diff --git a/.cache/calibration/aloha_default/left_follower.json b/.cache/calibration/aloha_default/left_follower.json deleted file mode 100644 index 336c238a0..000000000 --- a/.cache/calibration/aloha_default/left_follower.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "homing_offset": [ - 2048, - 3072, - 3072, - -1024, - -1024, - 2048, - -2048, - 2048, - -2048 - ], - "drive_mode": [ - 1, - 1, - 1, - 0, - 0, - 1, - 0, - 1, - 0 - ], - "start_pos": [ - 2015, - 3058, - 3061, - 1071, - 1071, - 2035, - 2152, - 2029, - 2499 - ], - "end_pos": [ - -1008, - -1963, - -1966, - 2141, - 2143, - -971, - 3043, - -1077, - 3144 - ], - "calib_mode": [ - "DEGREE", - "DEGREE", - "DEGREE", - "DEGREE", - "DEGREE", - "DEGREE", - "DEGREE", - "DEGREE", - "LINEAR" - ], - "motor_names": [ - "waist", - "shoulder", - "shoulder_shadow", - "elbow", - "elbow_shadow", - "forearm_roll", - "wrist_angle", - "wrist_rotate", - "gripper" - ] -} diff --git a/.cache/calibration/aloha_default/left_leader.json b/.cache/calibration/aloha_default/left_leader.json deleted file mode 100644 index d933f2bab..000000000 --- a/.cache/calibration/aloha_default/left_leader.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "homing_offset": [ - 2048, - 3072, - 3072, - -1024, - -1024, - 2048, - -2048, - 2048, - -1024 - ], - "drive_mode": [ - 1, - 1, - 1, - 0, - 0, - 1, - 0, - 1, - 0 - ], - "start_pos": [ - 2035, - 3024, - 3019, - 979, - 981, - 1982, - 2166, - 2124, - 1968 - ], - "end_pos": [ - -990, - -2017, - -2015, - 2078, - 2076, - -1030, - 3117, - -1016, - 2556 - ], - "calib_mode": [ - "DEGREE", - "DEGREE", - "DEGREE", - "DEGREE", - "DEGREE", - "DEGREE", - "DEGREE", - "DEGREE", - "LINEAR" - ], - "motor_names": [ - "waist", - "shoulder", - "shoulder_shadow", - "elbow", - "elbow_shadow", - "forearm_roll", - "wrist_angle", - "wrist_rotate", - "gripper" - ] -} diff --git a/.cache/calibration/aloha_default/right_follower.json b/.cache/calibration/aloha_default/right_follower.json deleted file mode 100644 index bc69dfafd..000000000 --- a/.cache/calibration/aloha_default/right_follower.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "homing_offset": [ - 2048, - 3072, - 3072, - -1024, - -1024, - 2048, - -2048, - 2048, - -2048 - ], - "drive_mode": [ - 1, - 1, - 1, - 0, - 0, - 1, - 0, - 1, - 0 - ], - "start_pos": [ - 2056, - 2895, - 2896, - 1191, - 1190, - 2018, - 2051, - 2056, - 2509 - ], - "end_pos": [ - -1040, - -2004, - -2006, - 2126, - 2127, - -1010, - 3050, - -1117, - 3143 - ], - "calib_mode": [ - "DEGREE", - "DEGREE", - "DEGREE", - "DEGREE", - "DEGREE", - "DEGREE", - "DEGREE", - "DEGREE", - "LINEAR" - ], - "motor_names": [ - "waist", - "shoulder", - "shoulder_shadow", - "elbow", - "elbow_shadow", - "forearm_roll", - "wrist_angle", - "wrist_rotate", - "gripper" - ] -} diff --git a/.cache/calibration/aloha_default/right_leader.json b/.cache/calibration/aloha_default/right_leader.json deleted file mode 100644 index d96d1de9b..000000000 --- a/.cache/calibration/aloha_default/right_leader.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "homing_offset": [ - 2048, - 3072, - 3072, - -1024, - -1024, - 2048, - -2048, - 2048, - -2048 - ], - "drive_mode": [ - 1, - 1, - 1, - 0, - 0, - 1, - 0, - 1, - 0 - ], - "start_pos": [ - 2068, - 3034, - 3030, - 1038, - 1041, - 1991, - 1948, - 2090, - 1985 - ], - "end_pos": [ - -1025, - -2014, - -2015, - 2058, - 2060, - -955, - 3091, - -940, - 2576 - ], - "calib_mode": [ - "DEGREE", - "DEGREE", - "DEGREE", - "DEGREE", - "DEGREE", - "DEGREE", - "DEGREE", - "DEGREE", - "LINEAR" - ], - "motor_names": [ - "waist", - "shoulder", - "shoulder_shadow", - "elbow", - "elbow_shadow", - "forearm_roll", - "wrist_angle", - "wrist_rotate", - "gripper" - ] -} diff --git a/.gitignore b/.gitignore index 42f2e7552..97b6af2f8 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,10 @@ # 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. + +# Dev scripts .dev + # Logging logs tmp @@ -91,10 +94,8 @@ coverage.xml .hypothesis/ .pytest_cache/ -# Ignore .cache except calibration +# Ignore .cache .cache/* -!.cache/calibration/ -!.cache/calibration/** # Translations *.mo From 8ab22271481b4ab65ad8037eb16e1ebe6c1a5ed2 Mon Sep 17 00:00:00 2001 From: Simon Alibert Date: Tue, 20 May 2025 13:16:34 +0200 Subject: [PATCH 2/6] Replace deprecated abc.abstractproperty --- lerobot/common/envs/configs.py | 3 ++- lerobot/common/robots/robot.py | 12 ++++++++---- lerobot/common/teleoperators/teleoperator.py | 12 ++++++++---- lerobot/configs/policies.py | 9 ++++++--- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/lerobot/common/envs/configs.py b/lerobot/common/envs/configs.py index 6de3cf03b..c99fba811 100644 --- a/lerobot/common/envs/configs.py +++ b/lerobot/common/envs/configs.py @@ -32,7 +32,8 @@ class EnvConfig(draccus.ChoiceRegistry, abc.ABC): def type(self) -> str: return self.get_choice_name(self.__class__) - @abc.abstractproperty + @property + @abc.abstractmethod def gym_kwargs(self) -> dict: raise NotImplementedError() diff --git a/lerobot/common/robots/robot.py b/lerobot/common/robots/robot.py index 7368863ea..e5af9e79f 100644 --- a/lerobot/common/robots/robot.py +++ b/lerobot/common/robots/robot.py @@ -49,15 +49,18 @@ class Robot(abc.ABC): return f"{self.id} {self.__class__.__name__}" # TODO(aliberts): create a proper Feature class for this that links with datasets - @abc.abstractproperty + @property + @abc.abstractmethod def observation_features(self) -> dict: pass - @abc.abstractproperty + @property + @abc.abstractmethod def action_features(self) -> dict: pass - @abc.abstractproperty + @property + @abc.abstractmethod def is_connected(self) -> bool: pass @@ -66,7 +69,8 @@ class Robot(abc.ABC): """Connects to the robot.""" pass - @abc.abstractproperty + @property + @abc.abstractmethod def is_calibrated(self) -> bool: pass diff --git a/lerobot/common/teleoperators/teleoperator.py b/lerobot/common/teleoperators/teleoperator.py index 5a1ed184e..d8715a552 100644 --- a/lerobot/common/teleoperators/teleoperator.py +++ b/lerobot/common/teleoperators/teleoperator.py @@ -47,15 +47,18 @@ class Teleoperator(abc.ABC): def __str__(self) -> str: return f"{self.id} {self.__class__.__name__}" - @abc.abstractproperty + @property + @abc.abstractmethod def action_features(self) -> dict: pass - @abc.abstractproperty + @property + @abc.abstractmethod def feedback_features(self) -> dict: pass - @abc.abstractproperty + @property + @abc.abstractmethod def is_connected(self) -> bool: pass @@ -64,7 +67,8 @@ class Teleoperator(abc.ABC): """Connects to the teleoperator.""" pass - @abc.abstractproperty + @property + @abc.abstractmethod def is_calibrated(self) -> bool: pass diff --git a/lerobot/configs/policies.py b/lerobot/configs/policies.py index 22eae05fd..1302db1fa 100644 --- a/lerobot/configs/policies.py +++ b/lerobot/configs/policies.py @@ -78,15 +78,18 @@ class PreTrainedConfig(draccus.ChoiceRegistry, HubMixin, abc.ABC): def type(self) -> str: return self.get_choice_name(self.__class__) - @abc.abstractproperty + @property + @abc.abstractmethod def observation_delta_indices(self) -> list | None: raise NotImplementedError - @abc.abstractproperty + @property + @abc.abstractmethod def action_delta_indices(self) -> list | None: raise NotImplementedError - @abc.abstractproperty + @property + @abc.abstractmethod def reward_delta_indices(self) -> list | None: raise NotImplementedError From 8e2a39444255d62869d2fd63ea4f8a157236a029 Mon Sep 17 00:00:00 2001 From: Pepijn <138571049+pkooij@users.noreply.github.com> Date: Tue, 20 May 2025 18:51:21 +0200 Subject: [PATCH 3/6] Add editable -e for feetech install command (#1133) --- docs/source/installation.mdx | 2 +- examples/12_use_so101.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/installation.mdx b/docs/source/installation.mdx index 3823d30e7..8bc761b18 100644 --- a/docs/source/installation.mdx +++ b/docs/source/installation.mdx @@ -55,7 +55,7 @@ conda install ffmpeg -c conda-forge Install 🤗 LeRobot: ```bash -cd lerobot && pip install ".[feetech]" +cd lerobot && pip install -e ".[feetech]" ``` ## Troubleshooting diff --git a/examples/12_use_so101.md b/examples/12_use_so101.md index 60e5e6ad2..2b43022bd 100644 --- a/examples/12_use_so101.md +++ b/examples/12_use_so101.md @@ -61,7 +61,7 @@ conda install ffmpeg -c conda-forge Install 🤗 LeRobot: ```bash -cd lerobot && pip install ".[feetech]" +cd lerobot && pip install -e ".[feetech]" ``` > [!NOTE] From 0bda18eab55bc384a9fb5328918365fe3793bdda Mon Sep 17 00:00:00 2001 From: Simon Alibert Date: Wed, 21 May 2025 20:18:47 +0200 Subject: [PATCH 4/6] Move make_robot_config --- .../datasets/v2/convert_dataset_v1_to_v2.py | 25 ++++++++++++++++++- lerobot/common/robots/utils.py | 24 ------------------ 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/lerobot/common/datasets/v2/convert_dataset_v1_to_v2.py b/lerobot/common/datasets/v2/convert_dataset_v1_to_v2.py index 00fe47d53..136a7a684 100644 --- a/lerobot/common/datasets/v2/convert_dataset_v1_to_v2.py +++ b/lerobot/common/datasets/v2/convert_dataset_v1_to_v2.py @@ -142,7 +142,6 @@ from lerobot.common.datasets.video_utils import ( get_video_info, ) from lerobot.common.robots import RobotConfig -from lerobot.common.robots.utils import make_robot_config V16 = "v1.6" V20 = "v2.0" @@ -598,6 +597,30 @@ def convert_dataset( create_branch(repo_id=repo_id, branch=V20, repo_type="dataset") +def make_robot_config(robot_type: str, **kwargs) -> RobotConfig: + if robot_type == "aloha": + raise NotImplementedError # TODO + + elif robot_type == "koch_follower": + from lerobot.common.robots.koch_follower import KochFollowerConfig + + return KochFollowerConfig(**kwargs) + elif robot_type == "so100_follower": + from lerobot.common.robots.so100_follower import SO100FollowerConfig + + return SO100FollowerConfig(**kwargs) + elif robot_type == "stretch": + from lerobot.common.robots.stretch3 import Stretch3RobotConfig + + return Stretch3RobotConfig(**kwargs) + elif robot_type == "lekiwi": + from lerobot.common.robots.lekiwi import LeKiwiConfig + + return LeKiwiConfig(**kwargs) + else: + raise ValueError(f"Robot type '{robot_type}' is not available.") + + def main(): parser = argparse.ArgumentParser() task_args = parser.add_mutually_exclusive_group(required=True) diff --git a/lerobot/common/robots/utils.py b/lerobot/common/robots/utils.py index 49253ee26..ec0374955 100644 --- a/lerobot/common/robots/utils.py +++ b/lerobot/common/robots/utils.py @@ -20,30 +20,6 @@ from lerobot.common.robots import RobotConfig from .robot import Robot -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 == "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: if config.type == "koch_follower": from .koch_follower import KochFollower From 386ad61007ad1ca296fb675bb9df7329021f20ac Mon Sep 17 00:00:00 2001 From: Simon Alibert Date: Thu, 22 May 2025 11:32:52 +0200 Subject: [PATCH 5/6] Fix normalization drive_mode --- lerobot/common/motors/dynamixel/dynamixel.py | 1 + lerobot/common/motors/feetech/feetech.py | 1 + lerobot/common/motors/motors_bus.py | 35 +++++++++++++------- tests/motors/test_motors_bus.py | 14 ++++---- 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/lerobot/common/motors/dynamixel/dynamixel.py b/lerobot/common/motors/dynamixel/dynamixel.py index 6f964987c..16c4b98a9 100644 --- a/lerobot/common/motors/dynamixel/dynamixel.py +++ b/lerobot/common/motors/dynamixel/dynamixel.py @@ -108,6 +108,7 @@ class DynamixelMotorsBus(MotorsBus): https://emanual.robotis.com/docs/en/software/dynamixel/dynamixel_sdk/sample_code/python_read_write_protocol_2_0/#python-read-write-protocol-20 """ + apply_drive_mode = False available_baudrates = deepcopy(AVAILABLE_BAUDRATES) default_baudrate = DEFAULT_BAUDRATE default_timeout = DEFAULT_TIMEOUT_MS diff --git a/lerobot/common/motors/feetech/feetech.py b/lerobot/common/motors/feetech/feetech.py index 7158ccd42..be97cd7b1 100644 --- a/lerobot/common/motors/feetech/feetech.py +++ b/lerobot/common/motors/feetech/feetech.py @@ -102,6 +102,7 @@ class FeetechMotorsBus(MotorsBus): python feetech sdk to communicate with the motors, which is itself based on the dynamixel sdk. """ + apply_drive_mode = True available_baudrates = deepcopy(SCAN_BAUDRATES) default_baudrate = DEFAULT_BAUDRATE default_timeout = DEFAULT_TIMEOUT_MS diff --git a/lerobot/common/motors/motors_bus.py b/lerobot/common/motors/motors_bus.py index 3801226ad..c06eadd9d 100644 --- a/lerobot/common/motors/motors_bus.py +++ b/lerobot/common/motors/motors_bus.py @@ -252,6 +252,7 @@ class MotorsBus(abc.ABC): ``` """ + apply_drive_mode: bool available_baudrates: list[int] default_baudrate: int default_timeout: int @@ -755,7 +756,7 @@ class MotorsBus(abc.ABC): return mins, maxes - def _normalize(self, data_name: str, ids_values: dict[int, int]) -> dict[int, float]: + def _normalize(self, ids_values: dict[int, int]) -> dict[int, float]: if not self.calibration: raise RuntimeError(f"{self} has no calibration registered.") @@ -764,20 +765,24 @@ class MotorsBus(abc.ABC): motor = self._id_to_name(id_) min_ = self.calibration[motor].range_min max_ = self.calibration[motor].range_max + drive_mode = self.calibration[motor].drive_mode and self.apply_drive_mode + if max_ == min_: + raise ValueError(f"Invalid calibration for motor '{motor}': min and max are equal.") + 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: - normalized_values[id_] = (((bounded_val - min_) / (max_ - min_)) * 200) - 100 + norm = (((bounded_val - min_) / (max_ - min_)) * 200) - 100 + normalized_values[id_] = -norm if drive_mode else norm elif self.motors[motor].norm_mode is MotorNormMode.RANGE_0_100: - normalized_values[id_] = ((bounded_val - min_) / (max_ - min_)) * 100 + norm = ((bounded_val - min_) / (max_ - min_)) * 100 + normalized_values[id_] = 100 - norm if drive_mode else norm else: - # TODO(alibers): velocity and degree modes + # TODO(alibers): degree mode raise NotImplementedError return normalized_values - def _unnormalize(self, data_name: str, ids_values: dict[int, float]) -> dict[int, int]: + def _unnormalize(self, ids_values: dict[int, float]) -> dict[int, int]: if not self.calibration: raise RuntimeError(f"{self} has no calibration registered.") @@ -786,14 +791,20 @@ class MotorsBus(abc.ABC): motor = self._id_to_name(id_) min_ = self.calibration[motor].range_min max_ = self.calibration[motor].range_max + drive_mode = self.calibration[motor].drive_mode and self.apply_drive_mode + if max_ == min_: + raise ValueError(f"Invalid calibration for motor '{motor}': min and max are equal.") + if self.motors[motor].norm_mode is MotorNormMode.RANGE_M100_100: + val = -val if drive_mode else val bounded_val = min(100.0, max(-100.0, val)) unnormalized_values[id_] = int(((bounded_val + 100) / 200) * (max_ - min_) + min_) elif self.motors[motor].norm_mode is MotorNormMode.RANGE_0_100: + val = 100 - val if drive_mode else val bounded_val = min(100.0, max(0.0, val)) unnormalized_values[id_] = int((bounded_val / 100) * (max_ - min_) + min_) else: - # TODO(alibers): velocity and degree modes + # TODO(aliberts): degree mode raise NotImplementedError return unnormalized_values @@ -914,7 +925,7 @@ class MotorsBus(abc.ABC): id_value = self._decode_sign(data_name, {id_: value}) if normalize and data_name in self.normalized_data: - id_value = self._normalize(data_name, id_value) + id_value = self._normalize(id_value) return id_value[id_] @@ -981,7 +992,7 @@ class MotorsBus(abc.ABC): addr, length = get_address(self.model_ctrl_table, model, data_name) if normalize and data_name in self.normalized_data: - value = self._unnormalize(data_name, {id_: value})[id_] + value = self._unnormalize({id_: value})[id_] value = self._encode_sign(data_name, {id_: value})[id_] @@ -1060,7 +1071,7 @@ class MotorsBus(abc.ABC): ids_values = self._decode_sign(data_name, ids_values) if normalize and data_name in self.normalized_data: - ids_values = self._normalize(data_name, ids_values) + ids_values = self._normalize(ids_values) return {self._id_to_name(id_): value for id_, value in ids_values.items()} @@ -1146,7 +1157,7 @@ class MotorsBus(abc.ABC): addr, length = get_address(self.model_ctrl_table, model, data_name) if normalize and data_name in self.normalized_data: - ids_values = self._unnormalize(data_name, ids_values) + ids_values = self._unnormalize(ids_values) ids_values = self._encode_sign(data_name, ids_values) diff --git a/tests/motors/test_motors_bus.py b/tests/motors/test_motors_bus.py index fab2d6a1e..78b7a47da 100644 --- a/tests/motors/test_motors_bus.py +++ b/tests/motors/test_motors_bus.py @@ -125,7 +125,7 @@ def test_read(data_name, id_, value, dummy_motors): ) mock__decode_sign.assert_called_once_with(data_name, {id_: value}) if data_name in bus.normalized_data: - mock__normalize.assert_called_once_with(data_name, {id_: value}) + mock__normalize.assert_called_once_with({id_: value}) @pytest.mark.parametrize( @@ -159,7 +159,7 @@ def test_write(data_name, id_, value, dummy_motors): ) mock__encode_sign.assert_called_once_with(data_name, {id_: value}) if data_name in bus.normalized_data: - mock__unnormalize.assert_called_once_with(data_name, {id_: value}) + mock__unnormalize.assert_called_once_with({id_: value}) @pytest.mark.parametrize( @@ -196,7 +196,7 @@ def test_sync_read_by_str(data_name, id_, value, dummy_motors): ) mock__decode_sign.assert_called_once_with(data_name, {id_: value}) if data_name in bus.normalized_data: - mock__normalize.assert_called_once_with(data_name, {id_: value}) + mock__normalize.assert_called_once_with({id_: value}) @pytest.mark.parametrize( @@ -233,7 +233,7 @@ def test_sync_read_by_list(data_name, ids_values, dummy_motors): ) mock__decode_sign.assert_called_once_with(data_name, ids_values) if data_name in bus.normalized_data: - mock__normalize.assert_called_once_with(data_name, ids_values) + mock__normalize.assert_called_once_with(ids_values) @pytest.mark.parametrize( @@ -270,7 +270,7 @@ def test_sync_read_by_none(data_name, ids_values, dummy_motors): ) mock__decode_sign.assert_called_once_with(data_name, ids_values) if data_name in bus.normalized_data: - mock__normalize.assert_called_once_with(data_name, ids_values) + mock__normalize.assert_called_once_with(ids_values) @pytest.mark.parametrize( @@ -304,7 +304,7 @@ def test_sync_write_by_single_value(data_name, value, dummy_motors): ) mock__encode_sign.assert_called_once_with(data_name, ids_values) if data_name in bus.normalized_data: - mock__unnormalize.assert_called_once_with(data_name, ids_values) + mock__unnormalize.assert_called_once_with(ids_values) @pytest.mark.parametrize( @@ -339,4 +339,4 @@ def test_sync_write_by_value_dict(data_name, ids_values, dummy_motors): ) mock__encode_sign.assert_called_once_with(data_name, ids_values) if data_name in bus.normalized_data: - mock__unnormalize.assert_called_once_with(data_name, ids_values) + mock__unnormalize.assert_called_once_with(ids_values) From e9aac40ba83f481e37d18802a58b05b8f768159d Mon Sep 17 00:00:00 2001 From: Simon Alibert Date: Thu, 22 May 2025 11:34:16 +0200 Subject: [PATCH 6/6] nit --- lerobot/common/motors/motors_bus.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lerobot/common/motors/motors_bus.py b/lerobot/common/motors/motors_bus.py index c06eadd9d..58b1e49bc 100644 --- a/lerobot/common/motors/motors_bus.py +++ b/lerobot/common/motors/motors_bus.py @@ -765,7 +765,7 @@ class MotorsBus(abc.ABC): motor = self._id_to_name(id_) min_ = self.calibration[motor].range_min max_ = self.calibration[motor].range_max - drive_mode = self.calibration[motor].drive_mode and self.apply_drive_mode + drive_mode = self.apply_drive_mode and self.calibration[motor].drive_mode if max_ == min_: raise ValueError(f"Invalid calibration for motor '{motor}': min and max are equal.") @@ -791,7 +791,7 @@ class MotorsBus(abc.ABC): motor = self._id_to_name(id_) min_ = self.calibration[motor].range_min max_ = self.calibration[motor].range_max - drive_mode = self.calibration[motor].drive_mode and self.apply_drive_mode + drive_mode = self.apply_drive_mode and self.calibration[motor].drive_mode if max_ == min_: raise ValueError(f"Invalid calibration for motor '{motor}': min and max are equal.")