diff --git a/lerobot/common/robot_devices/cameras/opencv.py b/lerobot/common/robot_devices/cameras/opencv.py index 36e7b6d8..235c613e 100644 --- a/lerobot/common/robot_devices/cameras/opencv.py +++ b/lerobot/common/robot_devices/cameras/opencv.py @@ -155,7 +155,10 @@ class OpenCVCamera: python lerobot/common/robot_devices/cameras/opencv.py --images-dir outputs/images_from_opencv_cameras ``` - Example of uage of the class: + When an OpenCVCamera is instantiated, if no specific config is provided, the default fps, width, height and color + of the given camera will be used. + + Example of usage of the class: ```python camera = OpenCVCamera(camera_index=0) camera.connect() @@ -163,6 +166,18 @@ class OpenCVCamera: # when done using the camera, consider disconnecting camera.disconnect() ``` + + Example of changing default fps, width, height and color: + ```python + camera = OpenCVCamera(0, fps=30, width=1280, height=720) + camera = connect() # applies the settings, might error out if these settings are not compatible with the camera + + camera = OpenCVCamera(0, fps=90, width=640, height=480) + camera = connect() + + camera = OpenCVCamera(0, fps=90, width=640, height=480, color="bgr") + camera = connect() + ``` """ def __init__(self, camera_index: int, config: OpenCVCameraConfig | None = None, **kwargs): diff --git a/lerobot/common/robot_devices/motors/dynamixel.py b/lerobot/common/robot_devices/motors/dynamixel.py index 0eb93b79..2fb11abf 100644 --- a/lerobot/common/robot_devices/motors/dynamixel.py +++ b/lerobot/common/robot_devices/motors/dynamixel.py @@ -1,6 +1,7 @@ import enum import time from copy import deepcopy +from pathlib import Path import numpy as np from dynamixel_sdk import ( @@ -159,6 +160,35 @@ def get_log_name(var_name, fn_name, data_name, motor_names): return log_name +def find_available_ports(): + ports = [] + for path in Path("/dev").glob("tty.usbmodem*"): + ports.append(str(path)) + return ports + + +def find_port(): + print("Finding all available ports for the DynamixelMotorsBus.") + ports_before = find_available_ports() + print(ports_before) + + print("Remove the usb cable from your DynamixelMotorsBus and press Enter when done.") + input() + + time.sleep(0.5) + ports_after = find_available_ports() + ports_diff = list(set(ports_before) - set(ports_after)) + + if len(ports_diff) == 1: + port = ports_diff[0] + print(f"The port of this DynamixelMotorsBus is {port}.") + print("Reconnect the usb cable.") + elif len(ports_diff) == 0: + raise OSError(f"Could not detect the port. No difference was found ({ports_diff}).") + else: + raise OSError(f"Could not detect the port. More than one port was found ({ports_diff}).") + + class TorqueMode(enum.Enum): ENABLED = 1 DISABLED = 0 @@ -179,7 +209,23 @@ class DriveMode(enum.Enum): class DynamixelMotorsBus: + # TODO(rcadene): Add a script to find the motor indices without DynamixelWizzard2 """ + The DynamixelMotorsBus class allows to efficiently read and write to the attached motors. It relies on + the python dynamixel sdk to communicate with the motors. For more info, see the [Dynamixel SDK Documentation](https://emanual.robotis.com/docs/en/software/dynamixel/dynamixel_sdk/sample_code/python_read_write_protocol_2_0/#python-read-write-protocol-20). + + A DynamixelMotorsBus instance requires a port (e.g. `DynamixelMotorsBus(port="/dev/tty.usbmodem575E0031751"`)). + To find the port, you can run our utility script: + ```bash + python lerobot/common/robot_devices/motors/dynamixel.py + >>> Finding all available ports for the DynamixelMotorsBus. + >>> ['/dev/tty.usbmodem575E0032081', '/dev/tty.usbmodem575E0031751'] + >>> Remove the usb cable from your DynamixelMotorsBus and press Enter when done. + >>> The port of this DynamixelMotorsBus is /dev/tty.usbmodem575E0031751. + >>> Reconnect the usb cable. + ``` + To find the motor indices, use [DynamixelWizzard2](https://emanual.robotis.com/docs/en/software/dynamixel/dynamixel_wizard2). + Example of usage for 1 motor connected to the bus: ```python motor_name = "gripper" @@ -194,6 +240,7 @@ class DynamixelMotorsBus: motors_bus.teleop_step() + # when done, consider disconnecting motors_bus.disconnect() ``` """ @@ -450,3 +497,8 @@ class DynamixelMotorsBus: def __del__(self): if getattr(self, "is_connected", False): self.disconnect() + + +if __name__ == "__main__": + # Helper to find the usb port associated to all your DynamixelMotorsBus. + find_port() diff --git a/lerobot/common/robot_devices/robots/factory.py b/lerobot/common/robot_devices/robots/factory.py index 593603e5..fd6051a3 100644 --- a/lerobot/common/robot_devices/robots/factory.py +++ b/lerobot/common/robot_devices/robots/factory.py @@ -1,5 +1,7 @@ def make_robot(name): if name == "koch": + # TODO(rcadene): Add configurable robot from command line and yaml config + # TODO(rcadene): Add example with and without cameras from lerobot.common.robot_devices.cameras.opencv import OpenCVCamera from lerobot.common.robot_devices.motors.dynamixel import DynamixelMotorsBus from lerobot.common.robot_devices.robots.koch import KochRobot diff --git a/tests/test_motors.py b/tests/test_motors.py index 3fad6ca2..18291540 100644 --- a/tests/test_motors.py +++ b/tests/test_motors.py @@ -3,7 +3,7 @@ import time import numpy as np import pytest -from lerobot.common.robot_devices.motors.dynamixel import DynamixelMotorsBus +from lerobot.common.robot_devices.motors.dynamixel import DynamixelMotorsBus, find_port from lerobot.common.robot_devices.utils import RobotDeviceAlreadyConnectedError, RobotDeviceNotConnectedError @@ -81,3 +81,7 @@ def test_motors_bus(): time.sleep(1) new_values = motors_bus.read("Present_Position") assert (new_values == values).all() + + +def test_find_port(): + find_port()