From 103ac922d250e9707cade92f7242956b9f1eba89 Mon Sep 17 00:00:00 2001 From: Alexander Soare Date: Thu, 22 Aug 2024 09:53:40 +0100 Subject: [PATCH 01/13] Add safety limits on relative action target --- lerobot/scripts/control_robot.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lerobot/scripts/control_robot.py b/lerobot/scripts/control_robot.py index 56321e768..890aeb258 100644 --- a/lerobot/scripts/control_robot.py +++ b/lerobot/scripts/control_robot.py @@ -287,6 +287,7 @@ def record( robot: Robot, policy: torch.nn.Module | None = None, hydra_cfg: DictConfig | None = None, + policy_action_safety_cap: torch.Tensor | None = None, fps: int | None = None, root="data", repo_id="lerobot/debug", @@ -313,6 +314,15 @@ def record( if not video: raise NotImplementedError() + if policy_action_safety_cap is None and policy is not None: + policy_action_safety_cap = torch.tensor([10.0, 10.0, 10.0, 10.0, 10.0, 15.0]) + logging.info( + "Actions from the policy will be clamped such that they result in a maximum relative positional " + f"target magnitude of no greater than {policy_action_safety_cap.tolist()}. This is for safety " + "reasons (mostly to avoid damaging your motors). Any instances of capping will be logged. You " + "may override these values by passing `policy_action_safety_cap`." + ) + if not robot.is_connected: robot.connect() @@ -485,6 +495,20 @@ def on_press(key): # Move to cpu, if not already the case action = action.to("cpu") + # Cap relative action target magnitude for safety. + current_pos = observation["observation.state"].cpu() + diff = action - current_pos + safe_diff = diff.clone() + safe_diff = torch.minimum(diff, policy_action_safety_cap) + safe_diff = torch.maximum(diff, -policy_action_safety_cap) + safe_action = current_pos + safe_diff + if not torch.allclose(safe_action, action): + logging.warning( + "Relative action magnitude had to be clamped to be safe.\n" + f" requested relative action target: {diff}\n" + f" clamped relative action target: {safe_diff}" + ) + # Order the robot to move robot.send_action(action) action = {"action": action} From 18681690f4fd9693c2de5a19cb271c8d525ee749 Mon Sep 17 00:00:00 2001 From: Alexander Soare Date: Thu, 22 Aug 2024 09:54:27 +0100 Subject: [PATCH 02/13] slightly increase gripper limit --- lerobot/scripts/control_robot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerobot/scripts/control_robot.py b/lerobot/scripts/control_robot.py index 890aeb258..a824465e5 100644 --- a/lerobot/scripts/control_robot.py +++ b/lerobot/scripts/control_robot.py @@ -315,7 +315,7 @@ def record( raise NotImplementedError() if policy_action_safety_cap is None and policy is not None: - policy_action_safety_cap = torch.tensor([10.0, 10.0, 10.0, 10.0, 10.0, 15.0]) + policy_action_safety_cap = torch.tensor([10.0, 10.0, 10.0, 10.0, 10.0, 16.0]) logging.info( "Actions from the policy will be clamped such that they result in a maximum relative positional " f"target magnitude of no greater than {policy_action_safety_cap.tolist()}. This is for safety " From 4b5da7a2191d4f14fd3e302751a326b4ccf34a17 Mon Sep 17 00:00:00 2001 From: Alexander Soare Date: Thu, 22 Aug 2024 11:53:19 +0100 Subject: [PATCH 03/13] fix negative capping --- lerobot/scripts/control_robot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerobot/scripts/control_robot.py b/lerobot/scripts/control_robot.py index a824465e5..5da35961d 100644 --- a/lerobot/scripts/control_robot.py +++ b/lerobot/scripts/control_robot.py @@ -500,7 +500,7 @@ def on_press(key): diff = action - current_pos safe_diff = diff.clone() safe_diff = torch.minimum(diff, policy_action_safety_cap) - safe_diff = torch.maximum(diff, -policy_action_safety_cap) + safe_diff = torch.maximum(safe_diff, -policy_action_safety_cap) safe_action = current_pos + safe_diff if not torch.allclose(safe_action, action): logging.warning( From d8c49e632603012938c0d8b508f621b3a6500b1d Mon Sep 17 00:00:00 2001 From: Alexander Soare Date: Thu, 22 Aug 2024 11:56:03 +0100 Subject: [PATCH 04/13] actually use the safe_action --- lerobot/scripts/control_robot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lerobot/scripts/control_robot.py b/lerobot/scripts/control_robot.py index 5da35961d..50195f8e0 100644 --- a/lerobot/scripts/control_robot.py +++ b/lerobot/scripts/control_robot.py @@ -510,8 +510,8 @@ def on_press(key): ) # Order the robot to move - robot.send_action(action) - action = {"action": action} + robot.send_action(safe_action) + action = {"action": safe_action} for key in action: if key not in ep_dict: From eebc218ffc2b83bb944eede507e61682285ffbc3 Mon Sep 17 00:00:00 2001 From: Alexander Soare Date: Thu, 22 Aug 2024 12:01:26 +0100 Subject: [PATCH 05/13] squeeze --- lerobot/scripts/control_robot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerobot/scripts/control_robot.py b/lerobot/scripts/control_robot.py index 50195f8e0..147290d15 100644 --- a/lerobot/scripts/control_robot.py +++ b/lerobot/scripts/control_robot.py @@ -496,7 +496,7 @@ def on_press(key): action = action.to("cpu") # Cap relative action target magnitude for safety. - current_pos = observation["observation.state"].cpu() + current_pos = observation["observation.state"].cpu().squeeze(0) diff = action - current_pos safe_diff = diff.clone() safe_diff = torch.minimum(diff, policy_action_safety_cap) From aeb176b73bee5281034093db34f53a606799ae90 Mon Sep 17 00:00:00 2001 From: Alexander Soare Date: Fri, 23 Aug 2024 08:34:51 +0100 Subject: [PATCH 06/13] remove clone --- lerobot/scripts/control_robot.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lerobot/scripts/control_robot.py b/lerobot/scripts/control_robot.py index 147290d15..b1a8844b8 100644 --- a/lerobot/scripts/control_robot.py +++ b/lerobot/scripts/control_robot.py @@ -498,7 +498,6 @@ def on_press(key): # Cap relative action target magnitude for safety. current_pos = observation["observation.state"].cpu().squeeze(0) diff = action - current_pos - safe_diff = diff.clone() safe_diff = torch.minimum(diff, policy_action_safety_cap) safe_diff = torch.maximum(safe_diff, -policy_action_safety_cap) safe_action = current_pos + safe_diff From 52fb2420e805c013f4ba5b9223c1f39d2d64779a Mon Sep 17 00:00:00 2001 From: Alexander Soare Date: Fri, 23 Aug 2024 11:55:56 +0100 Subject: [PATCH 07/13] add documentation --- examples/7_get_started_with_real_robot.md | 4 ++ lerobot/common/robot_devices/robots/koch.py | 50 +++++++++++++++++---- lerobot/scripts/control_robot.py | 27 +---------- 3 files changed, 48 insertions(+), 33 deletions(-) diff --git a/examples/7_get_started_with_real_robot.md b/examples/7_get_started_with_real_robot.md index f738ec29a..c0db03884 100644 --- a/examples/7_get_started_with_real_robot.md +++ b/examples/7_get_started_with_real_robot.md @@ -316,9 +316,12 @@ robot = KochRobot( leader_arms={"main": leader_arm}, follower_arms={"main": follower_arm}, calibration_path=".cache/calibration/koch.pkl", + max_relative_target=[10, 10, 10, 10, 10, 15], ) ``` +Notice the parameter `max_relative_target`. This is a safety measure that prevents someone from providing a positional target that is too far from the current robot position (which would then cause the robot to move too quickly, potentially burning out the motors or making violent impact with another object). + **Calibrate and Connect the KochRobot** Next, you'll need to calibrate your robot to ensure that the leader and follower arms have the same position values when they are in the same physical position. This calibration is essential because it allows a neural network trained on one Koch robot to work on another. @@ -615,6 +618,7 @@ robot = KochRobot( "laptop": OpenCVCamera(0, fps=30, width=640, height=480), "phone": OpenCVCamera(1, fps=30, width=640, height=480), }, + max_relative_target=[10, 10, 10, 10, 10, 15], ) robot.connect() ``` diff --git a/lerobot/common/robot_devices/robots/koch.py b/lerobot/common/robot_devices/robots/koch.py index f5966999a..6912086a4 100644 --- a/lerobot/common/robot_devices/robots/koch.py +++ b/lerobot/common/robot_devices/robots/koch.py @@ -1,3 +1,4 @@ +import logging import pickle import time from dataclasses import dataclass, field, replace @@ -164,6 +165,7 @@ class KochRobotConfig: leader_arms: dict[str, MotorsBus] = field(default_factory=lambda: {}) follower_arms: dict[str, MotorsBus] = field(default_factory=lambda: {}) cameras: dict[str, Camera] = field(default_factory=lambda: {}) + max_relative_target: list[float] | None = None class KochRobot: @@ -392,7 +394,7 @@ def teleop_step( # Send action for name in self.follower_arms: before_fwrite_t = time.perf_counter() - self.follower_arms[name].write("Goal_Position", follower_goal_pos[name]) + self.send_action(torch.tensor(follower_goal_pos[name]), [name]) self.logs[f"write_follower_{name}_goal_pos_dt_s"] = time.perf_counter() - before_fwrite_t # Early exit when recording data is not requested @@ -474,21 +476,53 @@ def capture_observation(self): obs_dict[f"observation.images.{name}"] = torch.from_numpy(images[name]) return obs_dict - def send_action(self, action: torch.Tensor): - """The provided action is expected to be a vector.""" + def send_action(self, action: torch.Tensor, follower_names: list[str] | None = None): + """Command the follower arms to move to a target joint configuration. + + The relative action magnitude may be clipped depending on the configuration parameter + `max_relative_target`. + + Args: + action: tensor containing the concatenated joint positions for the follower arms. + follower_names: Pass follower arm names to only control a subset of all the follower arms. + """ if not self.is_connected: raise RobotDeviceNotConnectedError( "KochRobot is not connected. You need to run `robot.connect()`." ) + if follower_names is None: + follower_names = list(self.follower_arms) + elif not set(follower_names).issubset(self.follower_arms): + raise ValueError( + f"You provided {follower_names=} but only the following arms are registered: " + f"{list(self.follower_arms)}" + ) + from_idx = 0 to_idx = 0 follower_goal_pos = {} - for name in self.follower_arms: - if name in self.follower_arms: - to_idx += len(self.follower_arms[name].motor_names) - follower_goal_pos[name] = action[from_idx:to_idx].numpy() - from_idx = to_idx + for name in follower_names: + to_idx += len(self.follower_arms[name].motor_names) + this_action = action[from_idx:to_idx] + + if self.config.max_relative_target is not None: + max_relative_target = torch.tensor(self.config.max_relative_target) + # Cap relative action target magnitude for safety. + current_pos = torch.tensor(self.follower_arms[name].read("Present_Position")) + diff = this_action - current_pos + safe_diff = torch.minimum(diff, max_relative_target) + safe_diff = torch.maximum(safe_diff, -max_relative_target) + safe_action = current_pos + safe_diff + if not torch.allclose(safe_action, action): + logging.warning( + "Relative action magnitude had to be clamped to be safe.\n" + f" requested relative action target: {diff}\n" + f" clamped relative action target: {safe_diff}" + ) + + follower_goal_pos[name] = safe_action.numpy() + from_idx = to_idx for name in self.follower_arms: self.follower_arms[name].write("Goal_Position", follower_goal_pos[name].astype(np.int32)) diff --git a/lerobot/scripts/control_robot.py b/lerobot/scripts/control_robot.py index b1a8844b8..56321e768 100644 --- a/lerobot/scripts/control_robot.py +++ b/lerobot/scripts/control_robot.py @@ -287,7 +287,6 @@ def record( robot: Robot, policy: torch.nn.Module | None = None, hydra_cfg: DictConfig | None = None, - policy_action_safety_cap: torch.Tensor | None = None, fps: int | None = None, root="data", repo_id="lerobot/debug", @@ -314,15 +313,6 @@ def record( if not video: raise NotImplementedError() - if policy_action_safety_cap is None and policy is not None: - policy_action_safety_cap = torch.tensor([10.0, 10.0, 10.0, 10.0, 10.0, 16.0]) - logging.info( - "Actions from the policy will be clamped such that they result in a maximum relative positional " - f"target magnitude of no greater than {policy_action_safety_cap.tolist()}. This is for safety " - "reasons (mostly to avoid damaging your motors). Any instances of capping will be logged. You " - "may override these values by passing `policy_action_safety_cap`." - ) - if not robot.is_connected: robot.connect() @@ -495,22 +485,9 @@ def on_press(key): # Move to cpu, if not already the case action = action.to("cpu") - # Cap relative action target magnitude for safety. - current_pos = observation["observation.state"].cpu().squeeze(0) - diff = action - current_pos - safe_diff = torch.minimum(diff, policy_action_safety_cap) - safe_diff = torch.maximum(safe_diff, -policy_action_safety_cap) - safe_action = current_pos + safe_diff - if not torch.allclose(safe_action, action): - logging.warning( - "Relative action magnitude had to be clamped to be safe.\n" - f" requested relative action target: {diff}\n" - f" clamped relative action target: {safe_diff}" - ) - # Order the robot to move - robot.send_action(safe_action) - action = {"action": safe_action} + robot.send_action(action) + action = {"action": action} for key in action: if key not in ep_dict: From 94386b72f36a4be2b24881dcacde9d1cac83baf4 Mon Sep 17 00:00:00 2001 From: Alexander Soare Date: Fri, 23 Aug 2024 11:58:59 +0100 Subject: [PATCH 08/13] add documentation --- lerobot/common/robot_devices/robots/koch.py | 30 ++++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/lerobot/common/robot_devices/robots/koch.py b/lerobot/common/robot_devices/robots/koch.py index 6912086a4..8de2764cb 100644 --- a/lerobot/common/robot_devices/robots/koch.py +++ b/lerobot/common/robot_devices/robots/koch.py @@ -208,7 +208,15 @@ class KochRobot: }, ), } - robot = KochRobot(leader_arms, follower_arms) + robot = KochRobot( + leader_arms=leader_arms, + follower_arms=follower_arms, + max_relative_target=[10, 10, 10, 10, 10, 15], + ) + + Notice the parameter `max_relative_target`. This is a safety measure that prevents someone from providing + a positional target that is too far from the current robot position (which would then cause the robot to + move too quickly, potentially burning out the motors or making violent impact with another object). # Connect motors buses and cameras if any (Required) robot.connect() @@ -220,7 +228,11 @@ class KochRobot: Example of highest frequency data collection without camera: ```python # Assumes leader and follower arms have been instantiated already (see first example) - robot = KochRobot(leader_arms, follower_arms) + robot = KochRobot( + leader_arms=leader_arms, + follower_arms=follower_arms, + max_relative_target=[10, 10, 10, 10, 10, 15], + ) robot.connect() while True: observation, action = robot.teleop_step(record_data=True) @@ -238,7 +250,12 @@ class KochRobot: } # Assumes leader and follower arms have been instantiated already (see first example) - robot = KochRobot(leader_arms, follower_arms, cameras) + robot = KochRobot( + leader_arms=leader_arms, + follower_arms=follower_arms, + cameras=cameras, + max_relative_target=[10, 10, 10, 10, 10, 15], + ) robot.connect() while True: observation, action = robot.teleop_step(record_data=True) @@ -247,7 +264,12 @@ class KochRobot: Example of controlling the robot with a policy (without running multiple policies in parallel to ensure highest frequency): ```python # Assumes leader and follower arms + cameras have been instantiated already (see previous example) - robot = KochRobot(leader_arms, follower_arms, cameras) + robot = KochRobot( + leader_arms=leader_arms, + follower_arms=follower_arms, + cameras=cameras, + max_relative_target=[10, 10, 10, 10, 10, 15], + ) robot.connect() while True: # Uses the follower arms and cameras to capture an observation From f83e6cc4f4c34b5d99137a730d3abe2b80f379cd Mon Sep 17 00:00:00 2001 From: Alexander Soare Date: Fri, 23 Aug 2024 12:28:18 +0100 Subject: [PATCH 09/13] make it possible to specify a catch-all integer --- lerobot/common/robot_devices/robots/koch.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lerobot/common/robot_devices/robots/koch.py b/lerobot/common/robot_devices/robots/koch.py index 8de2764cb..d4064d3ad 100644 --- a/lerobot/common/robot_devices/robots/koch.py +++ b/lerobot/common/robot_devices/robots/koch.py @@ -165,7 +165,7 @@ class KochRobotConfig: leader_arms: dict[str, MotorsBus] = field(default_factory=lambda: {}) follower_arms: dict[str, MotorsBus] = field(default_factory=lambda: {}) cameras: dict[str, Camera] = field(default_factory=lambda: {}) - max_relative_target: list[float] | None = None + max_relative_target: list[float] | float | None = None class KochRobot: @@ -529,6 +529,8 @@ def send_action(self, action: torch.Tensor, follower_names: list[str] | None = N this_action = action[from_idx:to_idx] if self.config.max_relative_target is not None: + if not isinstance(self.config.max_relative_target, list): + max_relative_target = [self.config.max_relative_target for _ in range(from_idx, to_idx)] max_relative_target = torch.tensor(self.config.max_relative_target) # Cap relative action target magnitude for safety. current_pos = torch.tensor(self.follower_arms[name].read("Present_Position")) From bee40cd8ad69684b2b48ded206b5ce23f6a14ed0 Mon Sep 17 00:00:00 2001 From: Alexander Soare Date: Fri, 23 Aug 2024 12:28:31 +0100 Subject: [PATCH 10/13] add limits to koch.yaml --- lerobot/configs/robot/koch.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/lerobot/configs/robot/koch.yaml b/lerobot/configs/robot/koch.yaml index 224040ab2..2fd7ab9c8 100644 --- a/lerobot/configs/robot/koch.yaml +++ b/lerobot/configs/robot/koch.yaml @@ -37,3 +37,4 @@ cameras: fps: 30 width: 640 height: 480 +max_relative_target: [10.0, 10.0, 10.0, 10.0, 10.0, 15.0] From e726a291435329c6b889c1fabcab3fa87bb1086b Mon Sep 17 00:00:00 2001 From: Alexander Soare Date: Mon, 26 Aug 2024 09:16:06 +0100 Subject: [PATCH 11/13] scalar max_relative_target in koch.yaml --- lerobot/configs/robot/koch.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerobot/configs/robot/koch.yaml b/lerobot/configs/robot/koch.yaml index 2fd7ab9c8..d13e47f2c 100644 --- a/lerobot/configs/robot/koch.yaml +++ b/lerobot/configs/robot/koch.yaml @@ -37,4 +37,4 @@ cameras: fps: 30 width: 640 height: 480 -max_relative_target: [10.0, 10.0, 10.0, 10.0, 10.0, 15.0] +max_relative_target: 15.0 From 3560f97b3579e5ee45a8b6aa911d1b56aadd5cd3 Mon Sep 17 00:00:00 2001 From: Alexander Soare Date: Mon, 26 Aug 2024 14:04:25 +0100 Subject: [PATCH 12/13] revision --- examples/7_get_started_with_real_robot.md | 4 ---- lerobot/common/robot_devices/robots/koch.py | 23 ++++++++++++++------- lerobot/configs/robot/koch.yaml | 5 ++++- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/examples/7_get_started_with_real_robot.md b/examples/7_get_started_with_real_robot.md index c0db03884..f738ec29a 100644 --- a/examples/7_get_started_with_real_robot.md +++ b/examples/7_get_started_with_real_robot.md @@ -316,12 +316,9 @@ robot = KochRobot( leader_arms={"main": leader_arm}, follower_arms={"main": follower_arm}, calibration_path=".cache/calibration/koch.pkl", - max_relative_target=[10, 10, 10, 10, 10, 15], ) ``` -Notice the parameter `max_relative_target`. This is a safety measure that prevents someone from providing a positional target that is too far from the current robot position (which would then cause the robot to move too quickly, potentially burning out the motors or making violent impact with another object). - **Calibrate and Connect the KochRobot** Next, you'll need to calibrate your robot to ensure that the leader and follower arms have the same position values when they are in the same physical position. This calibration is essential because it allows a neural network trained on one Koch robot to work on another. @@ -618,7 +615,6 @@ robot = KochRobot( "laptop": OpenCVCamera(0, fps=30, width=640, height=480), "phone": OpenCVCamera(1, fps=30, width=640, height=480), }, - max_relative_target=[10, 10, 10, 10, 10, 15], ) robot.connect() ``` diff --git a/lerobot/common/robot_devices/robots/koch.py b/lerobot/common/robot_devices/robots/koch.py index aae60710a..d6d5e1f5c 100644 --- a/lerobot/common/robot_devices/robots/koch.py +++ b/lerobot/common/robot_devices/robots/koch.py @@ -164,6 +164,11 @@ class KochRobotConfig: leader_arms: dict[str, MotorsBus] = field(default_factory=lambda: {}) follower_arms: dict[str, MotorsBus] = field(default_factory=lambda: {}) cameras: dict[str, Camera] = field(default_factory=lambda: {}) + + # Optionally limit 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 (assumes all follower arms have the same number of + # motors). max_relative_target: list[float] | float | None = None # Optionally set the leader arm in torque mode with the gripper motor set to this angle. This makes it @@ -171,6 +176,16 @@ class KochRobotConfig: # gripper is not put in torque mode. gripper_open_degree: float | None = None + def __post_init__(self): + if self.max_relative_target is not None and isinstance(self.max_relative_target, list): + n_expected_motors = len(self.max_relative_target) + for name in self.follower_arms: + if len(self.follower_arms[name]["motors"]) != n_expected_motors: + raise ValueError( + f"{len(self.max_relative_target)=} but {len(self.follower_arms[name]['motors'])=}." + "Please make sure the lengths match." + ) + class KochRobot: # TODO(rcadene): Implement force feedback @@ -215,13 +230,8 @@ class KochRobot: robot = KochRobot( leader_arms=leader_arms, follower_arms=follower_arms, - max_relative_target=[10, 10, 10, 10, 10, 15], ) - Notice the parameter `max_relative_target`. This is a safety measure that prevents someone from providing - a positional target that is too far from the current robot position (which would then cause the robot to - move too quickly, potentially burning out the motors or making violent impact with another object). - # Connect motors buses and cameras if any (Required) robot.connect() @@ -235,7 +245,6 @@ class KochRobot: robot = KochRobot( leader_arms=leader_arms, follower_arms=follower_arms, - max_relative_target=[10, 10, 10, 10, 10, 15], ) robot.connect() while True: @@ -258,7 +267,6 @@ class KochRobot: leader_arms=leader_arms, follower_arms=follower_arms, cameras=cameras, - max_relative_target=[10, 10, 10, 10, 10, 15], ) robot.connect() while True: @@ -272,7 +280,6 @@ class KochRobot: leader_arms=leader_arms, follower_arms=follower_arms, cameras=cameras, - max_relative_target=[10, 10, 10, 10, 10, 15], ) robot.connect() while True: diff --git a/lerobot/configs/robot/koch.yaml b/lerobot/configs/robot/koch.yaml index 1a3943d94..d40d5ff38 100644 --- a/lerobot/configs/robot/koch.yaml +++ b/lerobot/configs/robot/koch.yaml @@ -37,7 +37,10 @@ cameras: fps: 30 width: 640 height: 480 -max_relative_target: 15.0 +# `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: null # Sets the leader arm in torque mode with the gripper motor set to this angle. This makes it possible # to squeeze the gripper and have it spring back to an open position on its own. gripper_open_degree: 35.156 From 104e1954f460573257e72c737f6b60e491f4450b Mon Sep 17 00:00:00 2001 From: Alexander Soare Date: Mon, 26 Aug 2024 14:20:29 +0100 Subject: [PATCH 13/13] use __setattr__ instead of __post_init__ for validation --- lerobot/common/robot_devices/robots/koch.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lerobot/common/robot_devices/robots/koch.py b/lerobot/common/robot_devices/robots/koch.py index d6d5e1f5c..cdd81250e 100644 --- a/lerobot/common/robot_devices/robots/koch.py +++ b/lerobot/common/robot_devices/robots/koch.py @@ -3,6 +3,7 @@ import time from dataclasses import dataclass, field, replace from pathlib import Path +from typing import Sequence import numpy as np import torch @@ -176,15 +177,18 @@ class KochRobotConfig: # gripper is not put in torque mode. gripper_open_degree: float | None = None - def __post_init__(self): - if self.max_relative_target is not None and isinstance(self.max_relative_target, list): - n_expected_motors = len(self.max_relative_target) + def __setattr__(self, prop: str, val): + if prop == "max_relative_target" and val is not None and isinstance(val, Sequence): for name in self.follower_arms: - if len(self.follower_arms[name]["motors"]) != n_expected_motors: + if len(self.follower_arms[name].motors) != len(val): raise ValueError( - f"{len(self.max_relative_target)=} but {len(self.follower_arms[name]['motors'])=}." - "Please make sure the lengths match." + f"len(max_relative_target)={len(val)} but the follower arm with name {name} has " + f"{len(self.follower_arms[name].motors)} motors. Please make sure that the " + f"`max_relative_target` list has as many parameters as there are motors per arm. " + "Note: This feature does not yet work with robots where different follower arms have " + "different numbers of motors." ) + super().__setattr__(prop, val) class KochRobot: