Skip to content

Commit 35e6758

Browse files
authored
Fixes on robot integration tutorial (#1290)
1 parent 438334d commit 35e6758

File tree

1 file changed

+17
-16
lines changed

1 file changed

+17
-16
lines changed

docs/source/integrate_hardware.mdx

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ If you're using Feetech or Dynamixel motors, LeRobot provides built-in bus inter
2020
Please refer to the [`MotorsBus`](https://github.com/huggingface/lerobot/blob/main/lerobot/common/motors/motors_bus.py) abstract class to learn about its API.
2121
For a good example of how it can be used, you can have a look at our own [SO101 follower implementation](https://github.com/huggingface/lerobot/blob/main/lerobot/common/robots/so101_follower/so101_follower.py)
2222

23-
Use these if compatible! Otherwise, you'll need to find or write a Python interface (not covered in this tutorial):
23+
Use these if compatible. Otherwise, you'll need to find or write a Python interface (not covered in this tutorial):
2424
- Find an existing SDK in Python (or use bindings to C/C++)
2525
- Or implement a basic communication wrapper (e.g., via pyserial, socket, or CANopen)
2626

@@ -32,7 +32,7 @@ For Feetech and Dynamixel, we currently support these servos:
3232
- SCS series (protocol 1): `scs0009`
3333
- Dynamixel (protocol 2.0 only): `xl330-m077`, `xl330-m288`, `xl430-w250`, `xm430-w350`, `xm540-w270`, `xc430-w150`
3434

35-
If you are using Feetech or Dynamixel servos that are not in this list, you can add those in the [Feetech table](https://github.com/huggingface/lerobot/blob/main/lerobot/common/motors/feetech/tables.py) or [Dynamixel table](https://github.com/huggingface/lerobot/blob/main/lerobot/common/motors/dynamixel/tables.py). Depending on the model, this will require you to add model-specific information. In most cases though, there should be a lot of additions to do.
35+
If you are using Feetech or Dynamixel servos that are not in this list, you can add those in the [Feetech table](https://github.com/huggingface/lerobot/blob/main/lerobot/common/motors/feetech/tables.py) or [Dynamixel table](https://github.com/huggingface/lerobot/blob/main/lerobot/common/motors/dynamixel/tables.py). Depending on the model, this will require you to add model-specific information. In most cases though, there shouldn't be a lot of additions to do.
3636

3737
In the next sections, we'll use a `FeetechMotorsBus` as the motors interface for the examples. Replace it and adapt to your motors if necessary.
3838

@@ -158,7 +158,7 @@ def is_connected(self) -> bool:
158158

159159
### `connect()`
160160

161-
This method should establish communication with the hardware. Moreover, if your robot needs calibration is not calibrated, it should start a calibration procedure by default. If your robot needs some specific configuration, this should also be called here.
161+
This method should establish communication with the hardware. Moreover, if your robot needs calibration and is not calibrated, it should start a calibration procedure by default. If your robot needs some specific configuration, this should also be called here.
162162

163163
```python
164164
def connect(self, calibrate: bool = True) -> None:
@@ -272,30 +272,31 @@ Returns a dictionary of sensor values from the robot. These typically include mo
272272
```python
273273
def get_observation(self) -> dict[str, Any]:
274274
if not self.is_connected:
275-
raise RuntimeError("Robot is not connected")
275+
raise ConnectionError(f"{self} is not connected.")
276276

277-
joint_pos = self.motor_interface.read_joint_positions()
278-
gripper = self.motor_interface.read_gripper_state()
279-
image = self.camera.get_frame()
277+
# Read arm position
278+
obs_dict = self.bus.sync_read("Present_Position")
279+
obs_dict = {f"{motor}.pos": val for motor, val in obs_dict.items()}
280280

281-
return {
282-
"joint_positions": joint_pos,
283-
"gripper_open": gripper,
284-
"camera_image": image,
285-
}
281+
# Capture images from cameras
282+
for cam_key, cam in self.cameras.items():
283+
obs_dict[cam_key] = cam.async_read()
284+
285+
return obs_dict
286286
```
287287

288288
### `send_action()`
289289

290290
Takes a dictionary that matches `action_features`, and sends it to your hardware. You can add safety limits (clipping, smoothing) and return what was actually sent.
291291

292+
For simplicity, we won't be adding any modification of the actions in our example here.
293+
292294
```python
293295
def send_action(self, action: dict[str, Any]) -> dict[str, Any]:
294-
if not self.is_connected:
295-
raise RuntimeError("Robot is not connected")
296+
goal_pos = {key.removesuffix(".pos"): val for key, val in action.items()}
296297

297-
self.motor_interface.set_joint_positions(action["joint_position_goals"])
298-
self.motor_interface.set_gripper(action["gripper_command"])
298+
# Send goal position to the arm
299+
self.bus.sync_write("Goal_Position", goal_pos)
299300

300301
return action
301302
```

0 commit comments

Comments
 (0)