# Motors

`MotorsBus` is the low-level interface to a chain of servos on a serial bus. Robots use it to read positions
and write goal positions; you rarely touch it directly unless you are adding hardware.

See [Bring Your Own Hardware](../integrate_hardware) for adding a new bus, and
[Updating Feetech Firmware](../feetech) and [Damiao Motors and CAN Bus](../damiao) for device-specific notes.

## MotorsBus[[lerobot.motors.motors_bus.SerialMotorsBus]]

#### lerobot.motors.motors_bus.SerialMotorsBus[[lerobot.motors.motors_bus.SerialMotorsBus]]

```python
lerobot.motors.motors_bus.SerialMotorsBus(port: str, motors: dict[str, Motor], calibration: dict[str, MotorCalibration] | None = None)
```

[Source](https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/motors_bus.py#L303)

A SerialMotorsBus allows to efficiently read and write to motors connected via serial communication.
It represents several motors daisy-chained together and connected through a serial port.
There are currently two implementations of this class:
- DynamixelMotorsBus
- FeetechMotorsBus

This class is specifically for serial-based motor protocols (Dynamixel, Feetech, etc.).

A MotorsBus subclass instance requires a port (e.g. `FeetechMotorsBus(port="/dev/tty.usbmodem575E0031751"`)).

To find the port, you can run our utility script:
```bash
lerobot-find-port.py
>>> Finding all available ports for the MotorsBus.
>>> ["/dev/tty.usbmodem575E0032081", "/dev/tty.usbmodem575E0031751"]
>>> Remove the usb cable from your MotorsBus and press Enter when done.
>>> The port of this MotorsBus is /dev/tty.usbmodem575E0031751.
>>> Reconnect the usb cable.
```

Example of usage for 1 Feetech sts3215 motor connected to the bus:
```python
bus = FeetechMotorsBus(
    port="/dev/tty.usbmodem575E0031751",
    motors={"my_motor": (1, "sts3215")},
)
bus.connect()

position = bus.read("Present_Position", "my_motor", normalize=False)

# Move from a few motor steps as an example
few_steps = 30
bus.write("Goal_Position", "my_motor", position + few_steps, normalize=False)

# When done, properly disconnect the port using
bus.disconnect()
```

#### broadcast_ping[[lerobot.motors.motors_bus.SerialMotorsBus.broadcast_ping]]

```python
broadcast_ping(num_retry: int = 0, raise_on_error: bool = False)
```

[Source](https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/motors_bus.py#L980)

**Parameters:**

num_retry (int, optional) : Retry attempts.  Defaults to `0`.

raise_on_error (bool, optional) : When `True` failures raise an exception instead of returning `None`. Defaults to `False`.

**Returns:** dict[int, int] | None

Mapping *id → model number* or `None` if the call failed.

Ping every ID on the bus using the broadcast address.

#### configure_motors[[lerobot.motors.motors_bus.SerialMotorsBus.configure_motors]]

```python
configure_motors()
```

[Source](https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/motors_bus.py#L637)

Write implementation-specific recommended settings to every motor.

Typical changes include shortening the return delay, increasing
acceleration limits or disabling safety locks.

#### connect[[lerobot.motors.motors_bus.SerialMotorsBus.connect]]

```python
connect(handshake: bool = True)
```

[Source](https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/motors_bus.py#L513)

**Parameters:**

handshake (bool, optional) : Pings every expected motor and performs additional integrity checks specific to the implementation. Defaults to `True`.

**Raises:** ``DeviceAlreadyConnectedError`` or ``ConnectionError``

- ``DeviceAlreadyConnectedError`` -- The port is already open.
- ``ConnectionError`` -- The underlying SDK failed to open the port or the handshake did not succeed.

Open the serial port and initialise communication.

#### disable_torque[[lerobot.motors.motors_bus.SerialMotorsBus.disable_torque]]

```python
disable_torque(motors: str | list[str] | None = None, num_retry: int = 0)
```

[Source](https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/motors_bus.py#L646)

**Parameters:**

motors ( str | list[str] | None, optional) : Target motors.  Accepts a motor name, an ID, a list of names or `None` to affect every registered motor.  Defaults to `None`.

num_retry (int, optional) : Number of additional retry attempts on communication failure. Defaults to 0.

Disable torque on selected motors.

Disabling Torque allows to write to the motors' permanent memory area (EPROM/EEPROM).

#### disconnect[[lerobot.motors.motors_bus.SerialMotorsBus.disconnect]]

```python
disconnect(disable_torque: bool = True)
```

[Source](https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/motors_bus.py#L546)

**Parameters:**

disable_torque (bool, optional) : If `True` (default) torque is disabled on every motor before closing the port. This can prevent damaging motors if they are left applying resisting torque after disconnect.

Close the serial port (optionally disabling torque first).

#### enable_torque[[lerobot.motors.motors_bus.SerialMotorsBus.enable_torque]]

```python
enable_torque(motors: int | str | list[str] | None = None, num_retry: int = 0)
```

[Source](https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/motors_bus.py#L664)

**Parameters:**

motors (int | str | list[str] | None, optional) : Same semantics as :pymeth:`disable_torque`. Defaults to `None`.

num_retry (int, optional) : Number of additional retry attempts on communication failure. Defaults to 0.

Enable torque on selected motors.

#### get_baudrate[[lerobot.motors.motors_bus.SerialMotorsBus.get_baudrate]]

```python
get_baudrate()
```

[Source](https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/motors_bus.py#L703)

**Returns:** `int`

Baud-rate in bits / second.

Return the current baud-rate configured on the port.

#### ping[[lerobot.motors.motors_bus.SerialMotorsBus.ping]]

```python
ping(motor: NameOrID, num_retry: int = 0, raise_on_error: bool = False)
```

[Source](https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/motors_bus.py#L948)

**Parameters:**

motor (NameOrID) : Target motor (name or ID).

num_retry (int, optional) : Extra attempts before giving up. Defaults to `0`.

raise_on_error (bool, optional) : If `True` communication errors raise exceptions instead of returning `None`. Defaults to `False`.

**Returns:** `int | None`

Motor model number or `None` on failure.

Ping a single motor and return its model number.

#### read[[lerobot.motors.motors_bus.SerialMotorsBus.read]]

```python
read(data_name: str, motor: str, normalize: bool = True, num_retry: int = 0)
```

[Source](https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/motors_bus.py#L994)

**Parameters:**

data_name (str) : Control-table key (e.g. `"Present_Position"`).

motor (str) : Motor name.

normalize (bool, optional) : When `True` (default) scale the value to a user-friendly range as defined by the calibration.

num_retry (int, optional) : Retry attempts.  Defaults to `0`.

**Returns:** `Value`

Raw or normalised value depending on *normalize*.

Read a register from a motor.

#### read_calibration[[lerobot.motors.motors_bus.SerialMotorsBus.read_calibration]]

```python
read_calibration()
```

[Source](https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/motors_bus.py#L734)

**Returns:** dict[str, MotorCalibration]

Mapping *motor name → calibration*.

Read calibration parameters from the motors.

#### record_ranges_of_motion[[lerobot.motors.motors_bus.SerialMotorsBus.record_ranges_of_motion]]

```python
record_ranges_of_motion(motors: NameOrID | Sequence[NameOrID] | None = None, display_values: bool = True)
```

[Source](https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/motors_bus.py#L803)

**Parameters:**

motors (NameOrID | list[NameOrID] | None, optional) : Motors to record. Defaults to every motor (`None`).

display_values (bool, optional) : When `True` (default) a live table is printed to the console.

**Returns:** tuple[dict[str, Value], dict[str, Value]]

Two dictionaries *mins* and *maxes* with the
extreme values observed for each motor.

Interactively record the min/max encoder values of each motor.

Move the joints by hand (with torque disabled) while the method streams live positions. Press
:kbd:`Enter` to finish.

#### reset_calibration[[lerobot.motors.motors_bus.SerialMotorsBus.reset_calibration]]

```python
reset_calibration(motors: NameOrID | Sequence[NameOrID] | None = None)
```

[Source](https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/motors_bus.py#L754)

**Parameters:**

motors (NameOrID | Sequence[NameOrID] | None, optional) : Selection of motors. *None* (default) resets every motor.

Restore factory calibration for the selected motors.

Homing offset is set to `0` and min/max position limits are set to the full usable range.
The in-memory :pyattr:*calibration* is cleared.

#### scan_port[[lerobot.motors.motors_bus.SerialMotorsBus.scan_port]]

```python
scan_port(port: str, *args, **kwargs)
```

[Source](https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/motors_bus.py#L564)

**Parameters:**

port (str) : Serial/USB port to scan (e.g. `"/dev/ttyUSB0"`).

- ***args,** **kwargs : Forwarded to the subclass constructor.

**Returns:** dict[int, list[int]]

Mapping *baud-rate → list of motor IDs*
for every baud-rate that produced at least one response.

Probe *port* at every supported baud-rate and list responding IDs.

#### set_baudrate[[lerobot.motors.motors_bus.SerialMotorsBus.set_baudrate]]

```python
set_baudrate(baudrate: int)
```

[Source](https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/motors_bus.py#L711)

**Parameters:**

baudrate (int) : Desired baud-rate in bits / second.

**Raises:** ``RuntimeError``

- ``RuntimeError`` -- The SDK failed to apply the change.

Set a new UART baud-rate on the port.

#### set_half_turn_homings[[lerobot.motors.motors_bus.SerialMotorsBus.set_half_turn_homings]]

```python
set_half_turn_homings(motors: NameOrID | Sequence[NameOrID] | None = None)
```

[Source](https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/motors_bus.py#L775)

**Parameters:**

motors (NameOrID | list[NameOrID] | None, optional) : Motors to adjust. Defaults to all motors (`None`).

**Returns:** dict[str, Value]

Mapping *motor name → written homing offset*.

Centre each motor range around its current position.

The function computes and writes a homing offset such that the present position becomes exactly one
half-turn (e.g. `2047` on a 12-bit encoder).

#### set_timeout[[lerobot.motors.motors_bus.SerialMotorsBus.set_timeout]]

```python
set_timeout(timeout_ms: int | None = None)
```

[Source](https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/motors_bus.py#L693)

**Parameters:**

timeout_ms (int | None, optional) : Timeout in *milliseconds*. If `None` (default) the method falls back to :pyattr:`default_timeout`.

Change the packet timeout used by the SDK.

#### setup_motor[[lerobot.motors.motors_bus.SerialMotorsBus.setup_motor]]

```python
setup_motor(motor: str, initial_baudrate: int | None = None, initial_id: int | None = None)
```

[Source](https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/motors_bus.py#L589)

**Parameters:**

motor (str) : Key of the motor in :pyattr:`motors`.

initial_baudrate (int | None, optional) : Current baud-rate (skips scanning when provided). Defaults to None.

initial_id (int | None, optional) : Current ID (skips scanning when provided). Defaults to None.

**Raises:** ``RuntimeError`` or ``ConnectionError``

- ``RuntimeError`` -- The motor could not be found or its model number
  does not match the expected one.
- ``ConnectionError`` -- Communication with the motor failed.

Assign the correct ID and baud-rate to a single motor.

This helper temporarily switches to the motor's current settings, disables torque, sets the desired
ID, and finally programs the bus' default baud-rate.

#### sync_read[[lerobot.motors.motors_bus.SerialMotorsBus.sync_read]]

```python
sync_read(data_name: str, motors: NameOrID | Sequence[NameOrID] | None = None, normalize: bool = True, num_retry: int = 0)
```

[Source](https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/motors_bus.py#L1127)

**Parameters:**

data_name (str) : Register name.

motors (NameOrID | Sequence[NameOrID] | None, optional) : Motors to query. `None` (default) reads every motor.

normalize (bool, optional) : Normalisation flag.  Defaults to `True`.

num_retry (int, optional) : Retry attempts.  Defaults to `0`.

**Returns:** dict[str, Value]

Mapping *motor name → value*.

Read the same register from several motors at once.

#### sync_write[[lerobot.motors.motors_bus.SerialMotorsBus.sync_write]]

```python
sync_write(data_name: str, values: Value | dict[str, Value], normalize: bool = True, num_retry: int = 0)
```

[Source](https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/motors_bus.py#L1220)

**Parameters:**

data_name (str) : Register name.

values (Value | dict[str, Value]) : Either a single value (applied to every motor) or a mapping *motor name → value*.

normalize (bool, optional) : If `True` (default) convert values from the user range to raw units.

num_retry (int, optional) : Retry attempts.  Defaults to `0`.

Write the same register on multiple motors.

Contrary to :pymeth:`write`, this *does not* expects a response status packet emitted by the motor, which
can allow for lost packets. It is faster than :pymeth:`write` and should typically be used when
frequency matters and losing some packets is acceptable (e.g. teleoperation loops).

#### torque_disabled[[lerobot.motors.motors_bus.SerialMotorsBus.torque_disabled]]

```python
torque_disabled(motors: str | list[str] | None = None)
```

[Source](https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/motors_bus.py#L676)

Context-manager that guarantees torque is re-enabled.

This helper is useful to temporarily disable torque when configuring motors.

Examples:
>>> with bus.torque_disabled():
...     # Safe operations here
...     pass

#### write[[lerobot.motors.motors_bus.SerialMotorsBus.write]]

```python
write(data_name: str, motor: str, value: Value, normalize: bool = True, num_retry: int = 0)
```

[Source](https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/motors_bus.py#L1066)

**Parameters:**

data_name (str) : Register name.

motor (str) : Motor name.

value (Value) : Value to write.  If *normalize* is `True` the value is first converted to raw units.

normalize (bool, optional) : Enable or disable normalisation. Defaults to `True`.

num_retry (int, optional) : Retry attempts.  Defaults to `0`.

Write a value to a single motor's register.

Contrary to :pymeth:`sync_write`, this expects a response status packet emitted by the motor, which
provides a guarantee that the value was written to the register successfully. In consequence, it is
slower than :pymeth:`sync_write` but it is more reliable. It should typically be used when configuring
motors.

#### write_calibration[[lerobot.motors.motors_bus.SerialMotorsBus.write_calibration]]

```python
write_calibration(calibration_dict: dict[str, MotorCalibration], cache: bool = True)
```

[Source](https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/motors_bus.py#L743)

**Parameters:**

calibration_dict (dict[str, MotorCalibration]) : Calibration obtained from :pymeth:`read_calibration` or crafted by the user.

cache (bool, optional) : Save the calibration to :pyattr:`calibration`. Defaults to True.

Write calibration parameters to the motors and optionally cache them.

## Motor[[lerobot.motors.Motor]]

#### lerobot.motors.Motor[[lerobot.motors.Motor]]

```python
lerobot.motors.Motor(id: int, model: str, norm_mode: MotorNormMode, motor_type_str: str | None = None, recv_id: int | None = None)
```

[Source](https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/motors_bus.py#L185)

## MotorCalibration[[lerobot.motors.MotorCalibration]]

#### lerobot.motors.MotorCalibration[[lerobot.motors.MotorCalibration]]

```python
lerobot.motors.MotorCalibration(id: int, drive_mode: int, homing_offset: int, range_min: int, range_max: int)
```

[Source](https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/motors_bus.py#L176)

## MotorNormMode[[lerobot.motors.MotorNormMode]]

#### lerobot.motors.MotorNormMode[[lerobot.motors.MotorNormMode]]

```python
lerobot.motors.MotorNormMode(*values)
```

[Source](https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/motors_bus.py#L169)

