# OpenArms Robot OpenArms is a 7 DOF robotic arm with a gripper, designed by [Enactic, Inc.](https://www.enactic.com/) It uses Damiao motors controlled via CAN bus communication and MIT control mode for smooth, precise motion. ## Hardware Overview - **7 DOF per arm** (14 DOF total for dual arm setup) - **1 gripper per arm** (2 grippers total) - **Damiao motors** with 4 different types: - **DM8009** (DM-J8009P-2EC) for shoulders (J1, J2) - high torque - **DM4340** for shoulder rotation and elbow (J3, J4) - **DM4310** (DM-J4310-2EC V1.1) for wrist (J5, J6, J7) and gripper (J8) - **24V power supply** required - **CAN interface device**: - **Linux**: Any SocketCAN-compatible adapter - **macOS**: CANable, PEAK PCAN-USB, or Kvaser USBcan - Proper CAN wiring (CANH, CANL, 120Ω termination) ## Motor Configuration Each arm has the following motor configuration based on the [OpenArm setup guide](https://docs.openarm.dev/software/setup/): | Joint | Motor | Motor Type | Sender CAN ID | Receiver ID | Description | |-------|-------|------------|---------------|-------------|-------------| | J1 | joint_1 | DM8009 | 0x01 | 0x11 | Shoulder pan | | J2 | joint_2 | DM8009 | 0x02 | 0x12 | Shoulder lift | | J3 | joint_3 | DM4340 | 0x03 | 0x13 | Shoulder rotation | | J4 | joint_4 | DM4340 | 0x04 | 0x14 | Elbow flex | | J5 | joint_5 | DM4310 | 0x05 | 0x15 | Wrist roll | | J6 | joint_6 | DM4310 | 0x06 | 0x16 | Wrist pitch | | J7 | joint_7 | DM4310 | 0x07 | 0x17 | Wrist rotation | | J8 | gripper | DM4310 | 0x08 | 0x18 | Gripper | For dual arm setups, the left arm uses IDs 0x09-0x10 for joints 1-8 with the same motor types. ## Quick Start (macOS) If you're on macOS, here's the fastest way to get started: ```bash # 1. Install LeRobot with OpenArms dependencies pip install -e ".[openarms]" # 2. Find your USB-CAN adapter ls /dev/cu.usbmodem* # 3. Test communication python3 -c " import can bus = can.interface.Bus(channel='/dev/cu.usbmodem00000000050C1', interface='slcan', bitrate=1000000) print('✓ CAN interface connected') bus.shutdown() " ``` Then use this configuration: ```python from lerobot.robots.openarms import OpenArmsFollower from lerobot.robots.openarms.config_openarms_follower import OpenArmsFollowerConfig config = OpenArmsFollowerConfig( port="/dev/cu.usbmodem00000000050C1", # Your adapter can_interface="auto", # Auto-detects slcan for /dev/* ports is_dual_arm=True, ) robot = OpenArmsFollower(config) robot.connect() ``` ## Prerequisites ### Software Requirements **Linux:** - Ubuntu 22.04/24.04 (or any Linux with SocketCAN) - Python 3.8+ - `can-utils` and `iproute2` packages - LeRobot with OpenArms dependencies ```bash # Install system dependencies sudo apt install can-utils iproute2 # Install LeRobot with OpenArms support pip install -e ".[openarms]" ``` **macOS:** - macOS 12+ (Monterey or later) - Python 3.8+ - LeRobot with OpenArms dependencies ```bash # Install LeRobot with OpenArms support (includes python-can) pip install -e ".[openarms]" ``` The `openarms` extra installs: - `python-can>=4.2.0` - CAN bus communication library (supports SocketCAN on Linux and SLCAN on macOS) :::tip If you've already installed LeRobot and want to add OpenArms support, run: ```bash pip install -e ".[openarms]" ``` ::: ## Setup Guide ### Step 1: Motor ID Configuration **IMPORTANT**: Before using the robot, motors must be configured with the correct CAN IDs. Refer to the [OpenArm Motor ID Configuration Guide](https://docs.openarm.dev/software/setup/motor-id) for detailed instructions using the Damiao Debugging Tools on Windows. Key points: - Each motor needs a unique **Sender CAN ID** (0x01-0x08 for first arm) - Each motor needs a unique **Receiver/Master ID** (0x11-0x18 for first arm) - Use the Damiao Debugging Tools to set these IDs - For dual arm setups, use 0x09-0x10 for the second arm ### Step 2: Setup CAN Interface Configure your CAN interface as described in the [OpenArm CAN Setup Guide](https://docs.openarm.dev/software/setup/can-setup): #### Linux (SocketCAN) ```bash # Find your CAN interface ip link show # Setup CAN 2.0 at 1 Mbps (standard) sudo ip link set can0 down sudo ip link set can0 type can bitrate 1000000 sudo ip link set can0 up # Verify configuration ip link show can0 ``` #### macOS macOS doesn't have native SocketCAN support. **Use SLCAN (Serial Line CAN)** For USB-CAN adapters that support SLCAN protocol (like CANable): ```bash # Install python-can if not already installed pip install python-can # The adapter will appear as a serial device ls /dev/cu.usbmodem* # Use with python-can slcan interface # Example: /dev/cu.usbmodem14201 ``` In your code, specify the slcan interface: ```python from lerobot.robots.openarms.config_openarms_follower import OpenArmsFollowerConfig config = OpenArmsFollowerConfig( port="/dev/cu.usbmodem14201", # Your USB-CAN adapter can_interface="slcan", # Will auto-detect if set to "auto" ) ``` ### Step 3: Test Motor Communication **On Linux:** Test basic communication as described in the [OpenArm Motor Test Guide](https://docs.openarm.dev/software/setup/configure-test): ```bash # Terminal 1: Monitor CAN traffic candump can0 # Terminal 2: Enable motor 1 cansend can0 001#FFFFFFFFFFFFFFFC # Expected response on Terminal 1: # can0 011 [8] XX XX XX XX XX XX XX XX # Disable motor 1 cansend can0 001#FFFFFFFFFFFFFFFD ``` **On macOS:** Testing is done differently since you'll use serial-based adapters: ```bash # Find your USB-CAN adapter ls /dev/cu.usbmodem* # Example output: /dev/cu.usbmodem00000000050C1 # Test with Python directly (can-utils don't work on macOS) python3 -c " import can bus = can.interface.Bus(channel='/dev/cu.usbmodem00000000050C1', interface='slcan', bitrate=1000000) msg = can.Message(arbitration_id=0x01, data=[0xFF]*7+[0xFC]) bus.send(msg) response = bus.recv(timeout=1.0) if response: print(f'✓ Motor responded: ID 0x{response.arbitration_id:02X}') else: print('✗ No response') bus.shutdown() " ``` ## Usage ### Basic Setup **On Linux:** ```python from lerobot.robots.openarms import OpenArmsFollower from lerobot.robots.openarms.config_openarms_follower import OpenArmsFollowerConfig # Configure for dual arm setup config = OpenArmsFollowerConfig( port="can0", can_interface="socketcan", # Or "auto" for auto-detection id="openarms_dual", is_dual_arm=True, ) robot = OpenArmsFollower(config) robot.connect() ``` **On macOS:** ```python from lerobot.robots.openarms import OpenArmsFollower from lerobot.robots.openarms.config_openarms_follower import OpenArmsFollowerConfig # Find your USB-CAN adapter first # ls /dev/cu.usbmodem* config = OpenArmsFollowerConfig( port="/dev/cu.usbmodem14201", # Your adapter's serial port can_interface="slcan", # Or "auto" for auto-detection id="openarms_dual", is_dual_arm=True, ) robot = OpenArmsFollower(config) robot.connect() ``` ### Calibration On first use, you'll need to calibrate the robot: ```python robot.calibrate() ``` The calibration process will: 1. Disable torque on all motors 2. Ask you to position arms in **hanging position with grippers closed** 3. Set this as the zero position 4. Ask you to move each joint through its full range 5. Record min/max positions for each joint 6. Save calibration to file ### Reading Observations The robot provides comprehensive state information: ```python observation = robot.get_observation() # Observation includes for each motor: # - {motor_name}.pos: Position in degrees # - {motor_name}.vel: Velocity in degrees/second # - {motor_name}.torque: Motor torque # - {camera_name}: Camera images (if configured) print(f"Right arm joint 1 position: {observation['right_joint_1.pos']:.1f}°") print(f"Right arm joint 1 velocity: {observation['right_joint_1.vel']:.1f}°/s") print(f"Right arm joint 1 torque: {observation['right_joint_1.torque']:.3f} N·m") ``` ### Sending Actions ```python # Send target positions (in degrees) action = { "right_joint_1.pos": 45.0, "right_joint_2.pos": -30.0, # ... all joints "right_gripper.pos": 45.0, # Half-closed } actual_action = robot.send_action(action) ``` ### Gripper Control ```python # Open gripper robot.open_gripper(arm="right") # Close gripper robot.close_gripper(arm="right") ``` ## Safety Features ### 1. Maximum Relative Target Limits how far a joint can move in a single command to prevent sudden movements: ```python config = OpenArmsFollowerConfig( port="can0", # Limit all joints to 10 degrees per command max_relative_target=10.0, # Or set per-motor limits max_relative_target={ "right_joint_1": 15.0, # Slower moving joint "right_joint_2": 10.0, "right_gripper": 5.0, # Very slow gripper } ) ``` **How it works**: If current position is 50° and you command 80°, with `max_relative_target=10.0`, the robot will only move to 60° in that step. ### 2. Torque Limits Control maximum torque output, especially important for grippers and teleoperation: ```python config = OpenArmsFollowerConfig( port="can0", # Gripper torque limit (fraction of motor's max torque) gripper_torque_limit=0.5, # 50% of max torque ) ``` Lower torque limits prevent damage when gripping delicate objects. ### 3. MIT Control Gains Control responsiveness and stability via PID-like gains: ```python config = OpenArmsFollowerConfig( port="can0", position_kp=10.0, # Position gain (higher = more responsive) position_kd=0.5, # Velocity damping (higher = more damped) ) ``` **Guidelines**: - **For following (robot)**: Higher gains for responsiveness - `position_kp=10.0`, `position_kd=0.5` - **For teleoperation (leader)**: Lower gains or disable torque for manual movement - `manual_control=True` (torque disabled) ### 4. Velocity Limits Velocity limits are enforced by the Damiao motors based on motor type. For DM4310: - Max velocity: 30 rad/s ≈ 1718°/s The motors will automatically limit velocity to safe values. ## Teleoperation ### Leader Arm Setup The leader arm is moved manually (torque disabled) to generate commands: ```python from lerobot.teleoperators.openarms import OpenArmsLeader from lerobot.teleoperators.openarms.config_openarms_leader import OpenArmsLeaderConfig config = OpenArmsLeaderConfig( port="can1", # Separate CAN interface for leader id="openarms_leader", manual_control=True, # Torque disabled for manual movement is_dual_arm=True, ) leader = OpenArmsLeader(config) leader.connect() # Read current position as action action = leader.get_action() # action contains positions for all joints in degrees ``` ### Safety Considerations for Teleoperation 1. **Use separate CAN interfaces** for leader and follower to avoid conflicts 2. **Enable max_relative_target** on follower to smooth abrupt movements 3. **Lower torque limits** on follower to prevent damage from tracking errors 4. **Test with one arm** before enabling dual arm teleoperation 5. **Have emergency stop** ready (power switch or CAN disable) ```python # Recommended follower config for teleoperation follower_config = OpenArmsFollowerConfig( port="can0", max_relative_target=5.0, # Small steps for smooth following gripper_torque_limit=0.3, # Low torque for safety position_kp=5.0, # Lower gains for gentler following position_kd=0.3, ) ``` ## Troubleshooting ### Motors Not Responding **Linux:** 1. **Check power supply**: 24V with sufficient current 2. **Verify CAN interface**: `ip link show can0` should show "UP" 3. **Test with cansend**: Follow [motor test guide](https://docs.openarm.dev/software/setup/configure-test) 4. **Check motor IDs**: Use Damiao Debugging Tools to verify IDs 5. **Check termination**: 120Ω resistor should be enabled on CAN interface **macOS:** 1. **Check power supply**: 24V with sufficient current 2. **Find adapter**: `ls /dev/cu.usbmodem*` should show your device 3. **Test connection**: Use Python script above to test communication 4. **Check motor IDs**: Use Damiao Debugging Tools on Windows 5. **Verify drivers**: Ensure USB-CAN adapter drivers are installed 6. **Try different baudrate**: Some adapters default to different rates ### macOS-Specific Issues **"No such interface" error:** ```python # Try auto-detection config.can_interface = "auto" # Or explicitly list available interfaces import can print(can.detect_available_configs()) ``` **Permission denied on `/dev/cu.*`:** ```bash # Add user to dialout group (if applicable) sudo dscl . -append /Groups/_dialout GroupMembership $USER # Or run with sudo (not recommended) sudo python your_script.py ``` **Adapter not showing up:** ```bash # Check USB devices system_profiler SPUSBDataType # Reinstall python-can pip install --upgrade --force-reinstall python-can ``` ### Motor Shaking/Unstable - **Lower control gains**: Reduce `position_kp` and `position_kd` - **Check calibration**: Re-run calibration procedure - **Verify power**: Insufficient current can cause instability - **Check mechanical**: Loose connections, binding, or damaged components ### CAN Bus Errors **Linux:** ```bash # Check for errors ip -s link show can0 # Reset CAN interface sudo ip link set can0 down sudo ip link set can0 up ``` **macOS:** ```bash # Reconnect USB adapter # Unplug and replug the USB cable # Restart Python script # The slcan interface auto-reconnects ``` ### Position Drift - **Re-calibrate**: Run calibration procedure - **Set zero position**: Use `robot.bus.set_zero_position()` with arm in known position - **Check temperature**: Motors may drift when hot ## Technical Details ### Position Units All positions are in **degrees**: - Motor internal representation: radians - User API: degrees - Automatic conversion handled by `DamiaoMotorsBus` ### Control Mode OpenArms uses **MIT control mode** which allows simultaneous control of: - Position (degrees) - Velocity (degrees/second) - Torque (N·m) - Position gain (Kp) - Velocity damping (Kd) ### Communication - **Protocol**: CAN 2.0 at 1 Mbps (or CAN-FD at 5 Mbps) - **Frame format**: Standard 11-bit IDs - **Update rate**: Typically 50-100 Hz depending on motor count - **Latency**: ~10-20ms per motor command ## References - [OpenArm Official Documentation](https://docs.openarm.dev/) - [OpenArm Setup Guide](https://docs.openarm.dev/software/setup/) - [Motor ID Configuration](https://docs.openarm.dev/software/setup/motor-id) - [CAN Interface Setup](https://docs.openarm.dev/software/setup/can-setup) - [Motor Communication Test](https://docs.openarm.dev/software/setup/configure-test) - [Damiao Motor Documentation](https://wiki.seeedstudio.com/damiao_series/) - [Enactic GitHub](https://github.com/enactic/openarm_can)