Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ To get started with Orca Core, follow these steps:

### Serial Port Permissions (Linux)

On Linux, the serial port (e.g., `/dev/ttyUSB0`) is owned by the `dialout` group. If your user is not in this group, you will get a **permission denied** error and motors won't be detected.
On Linux, the serial port (e.g., `/dev/ttyACM0`) is owned by the `dialout` group. If your user is not in this group, you will get a **permission denied** error and motors won't be detected.

**Permanent fix** (requires re-login):

Expand All @@ -76,23 +76,23 @@ sudo usermod -aG dialout $USER
**Temporary fix** (resets on reboot/replug):

```sh
sudo chmod 666 /dev/ttyUSB0
sudo chmod 666 /dev/ttyACM0
```

### Serial Port Path
### Serial port, baudrate, and motor type

Make sure the `port` field in your `config.yaml` matches your operating system:
By default these are all **auto-detected** at connect time.

| OS | Example port |
|-------|---------------------------------|
| Linux | `/dev/ttyUSB0` |
| macOS | `/dev/tty.usbserial-XXXXXXXX` |
However, you can declare them explicitly in `config.yaml`. Useful when:

---
- **multiple hands are connected** at once → `port` disambiguates which one
- **motors run at a non-default baudrate** → `baudrate` skips the probe sweep
- **the auto-detection picks the wrong family** → `motor_type` forces a specific one

**Note:**
- Always ensure your `config.yaml` matches your hardware and wiring.
- All scripts in the `scripts/` folder take the model path as their first argument.
- For more advanced usage, see the other scripts and the API documentation.
```yaml
# Optional overrides: auto-detected if omitted
port: /dev/ttyACM0 # or /'dev/cu.usbmodemXXXX' on macOS
baudrate: 1000000 # 1M for v2; 3M for v1
motor_type: dynamixel # or 'feetech'
```

---
22 changes: 19 additions & 3 deletions docs/pages/getting-started-docs/setting-up-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,32 @@ This file defines parameters crucial for the hand's operation, including communi

```yaml
version: 0.2.0
baudrate: 3000000
port: /dev/ttyUSB0
max_current: 400
type: right
control_mode: current_based_position
```

**What should be changed?**

You should change the `port` to match your system (Linux or macOS). Change `type` to right or left depending on the hand assembly you have. `max_current` is set to a value found to be sufficient; you can adjust it depending on the needs of your tasks. The `baudrate` or `control_mode` should not be changed based on the current implementation in the repo. If you decide to change them you have to adapt the code accordingly.
Change `type` to right or left depending on the hand assembly. `max_current` is set to a sane default; adjust if your tasks need more or less. `control_mode` should generally stay at `current_based_position`.

#### Optional driver overrides

`port`, `baudrate`, and `motor_type` are **not in the bundled configs** — they're auto-detected at connect time:

- The serial port is found by USB VID, falling back to "the only adapter present" or an interactive picker.
- The motor family (Dynamixel vs Feetech) is identified by pinging factory defaults on the bus.
- The baudrate comes from the family's known set (1M / 3M for Dynamixel, 1M for Feetech).

Drop any of these into `config.yaml` if you need to override that behaviour:

```yaml
port: /dev/cu.usbmodemXXXX # only when multiple adapters are connected
baudrate: 1000000 # only when motors are configured for a non-default rate
motor_type: feetech # only when probing might misidentify the bus
```

You won't normally need any of them — the script will tell you on the command line if a probe failed and asking for an override would help.

---

Expand Down
10 changes: 7 additions & 3 deletions docs/pages/orca-core-docs/orca-core-scripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,17 +239,21 @@ python scripts/tension.py /path/to/orcahand_v1_left/config.yaml
</details>

<details>
<summary><strong>test.py</strong></summary>
<summary><strong>stress_test.py</strong></summary>

A test script that connects to the ORCA Hand, enables torque, sets a specific pose for a few joints (index_mcp, middle_pip, pinky_abd), waits for 2 seconds, disables torque, and disconnects.
Cycles the hand between OPEN and CLOSE poses while monitoring motor temperatures, aborting if any motor exceeds the safe operating temperature.

<br><strong>Args:</strong><br>
<ul>
<li><strong>config_path</strong> (<strong>str</strong>, optional): Path to the hand config file (e.g., `/path/to/orcahand_v1_right/config.yaml`). If not provided, uses the default config path.</li>
<li><strong>--num-steps</strong> (<strong>int</strong>, optional): Interpolation steps per move (default 25).</li>
<li><strong>--step-size</strong> (<strong>float</strong>, optional): Sleep between interpolation steps in seconds (default 0.001).</li>
<li><strong>--hold</strong> (<strong>float</strong>, optional): Extra seconds to hold each pose AFTER motion completes (default 0).</li>
<li><strong>--motion-timeout</strong> (<strong>float</strong>, optional): Max seconds to wait for a pose to be reached (default 5).</li>
</ul>

<strong>Example:</strong>
```bash
python scripts/test.py
python scripts/stress_test.py
```
</details>
8 changes: 8 additions & 0 deletions orca_core/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@
],
}

# Baudrates the connect-time probe will try, per motor family, in priority
# order. Used only when ``baudrate`` is not pinned in config.yaml. Feetech
# motors ship at 1M; Dynamixels run at 1M (v2 hands) or 3M (v1 hands).
MOTOR_BAUD_RATES: dict[str, list[int]] = {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we do something to ensure the baudrate is tied to the versioning so that we don't risk using 1M for v1 hands?

"dynamixel": [1_000_000, 3_000_000],
"feetech": [1_000_000],
}

"""
Dynamixel specific! TODO(fracapuano): Add feetech control modes too.
PWM (id: 2) control modeis omitted becayse it bypasses PID controllers entirely.
Expand Down
26 changes: 20 additions & 6 deletions orca_core/hand_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,17 @@

import os
from dataclasses import dataclass, field
from typing import Dict, List, Literal

from .constants import CONTROL_MODES, DEFAULT_MODEL_NAME, JOINT_IDS, JOINT_ROM_DICT, JOINT_TO_MOTOR_MAP, MOTOR_IDS
from typing import Dict, List, Literal, Optional

from .constants import (
CONTROL_MODES,
DEFAULT_MODEL_NAME,
JOINT_IDS,
JOINT_ROM_DICT,
JOINT_TO_MOTOR_MAP,
MOTOR_IDS,
SUPPORTED_MOTOR_TYPES,
)
from .joint_position import OrcaJointPositions
from .utils.utils import get_model_path, read_yaml

Expand Down Expand Up @@ -181,11 +189,11 @@ class OrcaHandConfig(BaseHandConfig):
"""ORCA hand configuration layered on top of the shared base spec."""

calibration_path: str = ""
baudrate: int = 3_000_000
port: str = "/dev/ttyUSB0"
baudrate: Optional[int] = None
port: Optional[str] = None
max_current: int = 300 # mA
control_mode: str = "current_based_position"
motor_type: str = "dynamixel"
motor_type: Optional[str] = None
motor_ids: List[int] = field(default_factory=list)
joint_to_motor_map: Dict[str, int] = field(default_factory=dict)
joint_inversion_dict: Dict[str, bool] = field(default_factory=dict)
Expand Down Expand Up @@ -291,6 +299,12 @@ def validate_config(self) -> None:
if self.control_mode not in CONTROL_MODES:
raise HandConfigValidationError("Invalid control mode.")

if self.motor_type is not None and self.motor_type not in SUPPORTED_MOTOR_TYPES:
raise HandConfigValidationError(
f"Unknown motor_type: {self.motor_type!r}. "
f"Expected one of {SUPPORTED_MOTOR_TYPES} or omit for auto-detection."
)

if self.max_current < self.calibration_current:
raise HandConfigValidationError(
"Max current should be greater than the calibration current."
Expand Down
Loading
Loading