# 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)
