# 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 

```bash
# Install system dependencies
sudo apt install can-utils iproute2

# Install LeRobot with OpenArms support
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)
- Each motor needs a unique **Receiver/Master ID** (0x11-0x18)
- Use the Damiao Debugging Tools to set these IDs

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

# Configure can0, 1, 2, 3
sudo ip link set can0 down
sudo ip link set can0 type can bitrate 1000000
sudo ip link set can0 up

sudo ip link set can1 down
sudo ip link set can1 type can bitrate 1000000
sudo ip link set can1 up

sudo ip link set can2 down
sudo ip link set can2 type can bitrate 1000000
sudo ip link set can2 up

sudo ip link set can3 down
sudo ip link set can3 type can bitrate 1000000
sudo ip link set can3 up

# Verify configuration
ip link show can0
```

or run:

`examples/openarms/setup_can.sh`

### Testing canbus and motor connection

Please run this script to check if all motors can be found and to find your can-fd speed: `python examples/openarms/debug_can_communication.py`

## Usage

### Basic Setup


```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()
```

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

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

```bash
# Check for errors
ip -s link show can0

# Reset CAN interface
sudo ip link set can0 down
sudo ip link set can0 up
```

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