Update pre-commit-config.yaml + pyproject.toml + ceil rerun & transformer dependencies version (#1520)

* chore: update .gitignore

* chore: update pre-commit

* chore(deps): update pyproject

* fix(ci): multiple fixes

* chore: pre-commit apply

* chore: address review comments

* Update pyproject.toml

Co-authored-by: Ben Zhang <5977478+ben-z@users.noreply.github.com>
Signed-off-by: Steven Palma <imstevenpmwork@ieee.org>

* chore(deps): add todo

---------

Signed-off-by: Steven Palma <imstevenpmwork@ieee.org>
Co-authored-by: Ben Zhang <5977478+ben-z@users.noreply.github.com>
This commit is contained in:
Steven Palma
2025-07-17 14:30:20 +02:00
committed by GitHub
parent 0938a1d816
commit 378e1f0338
78 changed files with 1450 additions and 636 deletions

View File

@@ -21,16 +21,13 @@ Please refer to the [`MotorsBus`](https://github.com/huggingface/lerobot/blob/ma
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/robots/so101_follower/so101_follower.py)
Use these if compatible. Otherwise, you'll need to find or write a Python interface (not covered in this tutorial):
- Find an existing SDK in Python (or use bindings to C/C++)
- Or implement a basic communication wrapper (e.g., via pyserial, socket, or CANopen)
You're not alone—many community contributions use custom boards or firmware!
For Feetech and Dynamixel, we currently support these servos:
- Feetech:
- STS & SMS series (protocol 0): `sts3215`, `sts3250`, `sm8512bl`
- SCS series (protocol 1): `scs0009`
- Dynamixel (protocol 2.0 only): `xl330-m077`, `xl330-m288`, `xl430-w250`, `xm430-w350`, `xm540-w270`, `xc430-w150`
For Feetech and Dynamixel, we currently support these servos: - Feetech: - STS & SMS series (protocol 0): `sts3215`, `sts3250`, `sm8512bl` - SCS series (protocol 1): `scs0009` - Dynamixel (protocol 2.0 only): `xl330-m077`, `xl330-m288`, `xl430-w250`, `xm430-w350`, `xm540-w270`, `xc430-w150`
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/motors/feetech/tables.py) or [Dynamixel table](https://github.com/huggingface/lerobot/blob/main/lerobot/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.
@@ -41,6 +38,8 @@ In the next sections, we'll use a `FeetechMotorsBus` as the motors interface for
Youll first need to specify the config class and a string identifier (`name`) for your robot. If your robot has special needs that you'd like to be able to change easily, it should go here (e.g. port/address, baudrate).
Here, we'll add the port name and one camera by default for our robot:
<!-- prettier-ignore-start -->
```python
from dataclasses import dataclass, field
@@ -64,6 +63,7 @@ class MyCoolRobotConfig(RobotConfig):
}
)
```
<!-- prettier-ignore-end -->
Have a look at our [Cameras tutorial](./cameras) to understand how to detect and add your camera.
@@ -71,6 +71,7 @@ Next, we'll create our actual robot class which inherits from `Robot`. This abst
Here we'll create a simple 5-DoF robot with one camera. It could be a simple arm but notice that the `Robot` abstract class does not assume anything on your robot's form factor. You can let you imagination run wild when designing new robots!
<!-- prettier-ignore-start -->
```python
from lerobot.cameras import make_cameras_from_configs
from lerobot.motors import Motor, MotorNormMode
@@ -96,10 +97,11 @@ class MyCoolRobot(Robot):
)
self.cameras = make_cameras_from_configs(config.cameras)
```
<!-- prettier-ignore-end -->
## Step 2: Define Observation and Action Features
These two properties define the *interface contract* between your robot and tools that consume it (such as data collection or learning pipelines).
These two properties define the _interface contract_ between your robot and tools that consume it (such as data collection or learning pipelines).
> [!WARNING]
> Note that these properties must be callable even if the robot is not yet connected, so avoid relying on runtime hardware state to define them.
@@ -109,6 +111,8 @@ These two properties define the *interface contract* between your robot and tool
This property should return a dictionary describing the structure of sensor outputs from your robot. The keys match what `get_observation()` returns, and the values describe either the shape (for arrays/images) or the type (for simple values).
Example for our 5-DoF arm with one camera:
<!-- prettier-ignore-start -->
```python
@property
def _motors_ft(self) -> dict[str, type]:
@@ -130,6 +134,8 @@ def _cameras_ft(self) -> dict[str, tuple]:
def observation_features(self) -> dict:
return {**self._motors_ft, **self._cameras_ft}
```
<!-- prettier-ignore-end -->
In this case, observations consist of a simple dict storing each motor's position and a camera image.
### `action_features`
@@ -137,10 +143,13 @@ In this case, observations consist of a simple dict storing each motor's positio
This property describes the commands your robot expects via `send_action()`. Again, keys must match the expected input format, and values define the shape/type of each command.
Here, we simply use the same joints proprioceptive features (`self._motors_ft`) as with `observation_features`: the action sent will simply the goal position for each motor.
<!-- prettier-ignore-start -->
```python
def action_features(self) -> dict:
return self._motors_ft
```
<!-- prettier-ignore-end -->
## Step 3: Handle Connection and Disconnection
@@ -150,16 +159,19 @@ These methods should handle opening and closing communication with your hardware
This property should simply reflect that communication with the robot's hardware is established. When this property is `True`, it should be possible to read and write to the hardware using `get_observation()` and `send_action()`.
<!-- prettier-ignore-start -->
```python
@property
def is_connected(self) -> bool:
return self.bus.is_connected and all(cam.is_connected for cam in self.cameras.values())
```
<!-- prettier-ignore-end -->
### `connect()`
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.
<!-- prettier-ignore-start -->
```python
def connect(self, calibrate: bool = True) -> None:
self.bus.connect()
@@ -171,25 +183,31 @@ def connect(self, calibrate: bool = True) -> None:
self.configure()
```
<!-- prettier-ignore-end -->
### `disconnect()`
This method should gracefully terminate communication with the hardware: free any related resources (threads or processes), close ports, etc.
Here, we already handle this in our `MotorsBus` and `Camera` classes so we just need to call their own `disconnect()` methods:
<!-- prettier-ignore-start -->
```python
def disconnect(self) -> None:
self.bus.disconnect()
for cam in self.cameras.values():
cam.disconnect()
```
<!-- prettier-ignore-end -->
## Step 4: Support Calibration and Configuration
LeRobot supports saving and loading calibration data automatically. This is useful for joint offsets, zero positions, or sensor alignment.
> Note that depending on your hardware, this may not apply. If that's the case, you can simply leave these methods as no-ops:
> ```python
<!-- prettier-ignore-start -->
```python
> @property
> def is_calibrated(self) -> bool:
> return True
@@ -202,7 +220,8 @@ LeRobot supports saving and loading calibration data automatically. This is usef
This should reflect whether your robot has the required calibration loaded.
```python
```
<!-- prettier-ignore-end -->python
@property
def is_calibrated(self) -> bool:
return self.bus.is_calibrated
@@ -216,6 +235,8 @@ The goal of the calibration is twofold:
It should implement the logic for calibration (if relevant) and update the `self.calibration` dictionary. If you are using Feetech or Dynamixel motors, our bus interfaces already include methods to help with this.
<!-- prettier-ignore-start -->
```python
def calibrate(self) -> None:
self.bus.disable_torque()
@@ -245,11 +266,13 @@ def calibrate(self) -> None:
self._save_calibration()
print("Calibration saved to", self.calibration_fpath)
```
<!-- prettier-ignore-end -->
### `configure()`
Use this to set up any configuration for your hardware (servos control modes, controller gains, etc.). This should usually be run at connection time and be idempotent.
<!-- prettier-ignore-start -->
```python
def configure(self) -> None:
with self.bus.torque_disabled():
@@ -260,6 +283,7 @@ def configure(self) -> None:
self.bus.write("I_Coefficient", motor, 0)
self.bus.write("D_Coefficient", motor, 32)
```
<!-- prettier-ignore-end -->
## Step 5: Implement Sensors Reading and Action Sending
@@ -269,6 +293,7 @@ These are the most important runtime functions: the core I/O loop.
Returns a dictionary of sensor values from the robot. These typically include motor states, camera frames, various sensors, etc. In the LeRobot framework, these observations are what will be fed to a policy in order to predict the actions to take. The dictionary keys and structure must match `observation_features`.
<!-- prettier-ignore-start -->
```python
def get_observation(self) -> dict[str, Any]:
if not self.is_connected:
@@ -284,6 +309,7 @@ def get_observation(self) -> dict[str, Any]:
return obs_dict
```
<!-- prettier-ignore-end -->
### `send_action()`
@@ -291,6 +317,7 @@ Takes a dictionary that matches `action_features`, and sends it to your hardware
For simplicity, we won't be adding any modification of the actions in our example here.
<!-- prettier-ignore-start -->
```python
def send_action(self, action: dict[str, Any]) -> dict[str, Any]:
goal_pos = {key.removesuffix(".pos"): val for key, val in action.items()}
@@ -300,6 +327,7 @@ def send_action(self, action: dict[str, Any]) -> dict[str, Any]:
return action
```
<!-- prettier-ignore-end -->
## Adding a Teleoperator