add dora_ulite6 standalone node with command lifecycle status
Migrates the ulite6 Python dora node from dora_littlehand into a
self-contained repository following the dora-zed structure. Adds
command lifecycle state ("started"/"done"/"failed") and type
("command"/"error") to the status output so coordinator nodes can
reliably sequence robot actions without polling.
This commit is contained in:
+13
@@ -0,0 +1,13 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.egg-info/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
*.egg
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
.pytest_cache/
|
||||||
|
.coverage
|
||||||
|
.ruff_cache/
|
||||||
|
out/
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
This file provides guidance to AI coding assistants when working with code in this repository.
|
||||||
|
|
||||||
|
## What this is
|
||||||
|
|
||||||
|
A dora-rs Python node that interfaces with a UFactory Lite6 robot arm. On each tick it reads joint angles and TCP pose from the robot and publishes them as dora outputs. It also accepts JSON commands (home, move_to, move_to_pose, vacuum_on/off) via a `command` input. A FastAPI REST server with a web UI runs in a background thread for manual control. The robot control wrapper lives in `dora_ulite6/main.py` as the `ULite6Helper` class, and the dora event loop is the `main()` function in the same file.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv pip install -e .
|
||||||
|
```
|
||||||
|
|
||||||
|
Requirements: Python >=3.8, uv, and network access to the UFactory Lite6 robot.
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dora up
|
||||||
|
dora start dataflow.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
Robot behavior is configured through environment variables (see README.md for the full list: ROBOT_IP, DEFAULT_SPEED, DEFAULT_UNITS, API_HOST, API_PORT, VACUUM_ENABLED, INIT_MODE, etc.).
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
- **`dora_ulite6/main.py`** -- Single-file implementation containing:
|
||||||
|
- `ULite6Helper`: Wraps the xArm Python SDK for motion control (go_home, move_to_pose, get_current_position, get_joint_angles_deg, get_pose_matrix, vacuum gripper, status, reset).
|
||||||
|
- `MoveToRequest` / `MoveToPoseRequest`: Pydantic models for API validation.
|
||||||
|
- `create_api()`: FastAPI app factory with REST endpoints and embedded HTML/JS web UI for manual control.
|
||||||
|
- `main()`: Dora event loop. Starts FastAPI in a daemon thread, then processes `tick` (publishes joint_pose and tcp_pose) and `command` (executes robot actions and publishes status) inputs.
|
||||||
|
- **`dora_ulite6/__init__.py`** -- Package marker.
|
||||||
|
- **`pyproject.toml`** -- Package metadata, dependencies, and entry point.
|
||||||
|
- **`dataflow.yml`** -- Example dora dataflow using the git: source format.
|
||||||
|
|
||||||
|
## Key APIs
|
||||||
|
|
||||||
|
- **dora Python API** (`dora`): `Node()`, `node.send_output()`, event iteration via `for event in node`.
|
||||||
|
- **xArm SDK** (`xarm.wrapper.XArmAPI`): `connect()`, `motion_enable()`, `set_mode()`, `set_state()`, `set_position()`, `move_gohome()`, `get_position()`, `get_servo_angle()`, `set_vacuum_gripper()`.
|
||||||
|
- Output data is sent as PyArrow arrays with metadata dicts.
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
|
||||||
|
- **Issue tracker:** Gitea. Use the Gitea MCP tools (`mcp__gitea__*`) to read and manage issues, pull requests, and other repository operations.
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
- Do not mention AI tools in commit messages or documentation.
|
||||||
|
- This controls a real robot. Test changes carefully and preserve safety checks.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
See [AGENTS.md](AGENTS.md) for repository guidance.
|
||||||
@@ -1,2 +1,115 @@
|
|||||||
# dora_ulite6
|
# Dora ULite6 Node
|
||||||
|
|
||||||
|
Dora node for controlling a UFactory Lite6 robot via REST API, web UI, and publishing joint/TCP state.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv pip install -e .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dataflow
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
nodes:
|
||||||
|
- id: ulite6
|
||||||
|
git: ssh://git@gitea.liria.cl:5001/liria/dora_ulite6.git
|
||||||
|
git_checkout: v0.1.0
|
||||||
|
build: uv pip install -e .
|
||||||
|
path: dora_ulite6/main.py
|
||||||
|
inputs:
|
||||||
|
tick: dora/timer/millis/10
|
||||||
|
outputs: [status, joint_pose, tcp_pose]
|
||||||
|
inputs: [command]
|
||||||
|
env:
|
||||||
|
ROBOT_IP: "192.168.1.192"
|
||||||
|
DEFAULT_SPEED: "30"
|
||||||
|
DEFAULT_UNITS: "mm"
|
||||||
|
API_HOST: "0.0.0.0"
|
||||||
|
API_PORT: "8080"
|
||||||
|
VACUUM_ENABLED: "false"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
| Env Variable | Default | Description |
|
||||||
|
|--------------|---------|-------------|
|
||||||
|
| `ROBOT_IP` | `192.168.1.192` | Robot IP address |
|
||||||
|
| `DEFAULT_SPEED` | `30` | Movement speed (mm/s) |
|
||||||
|
| `DEFAULT_UNITS` | `mm` | Position units (mm or m) |
|
||||||
|
| `API_HOST` | `0.0.0.0` | API server host |
|
||||||
|
| `API_PORT` | `8080` | API server port |
|
||||||
|
| `VACUUM_ENABLED` | `false` | Enable vacuum gripper controls |
|
||||||
|
| `INIT_MODE` | `none` | Startup positioning: `home`, `pose`, or `none` |
|
||||||
|
| `INIT_X/Y/Z` | `300/0/250` | Initial pose coordinates (mm) |
|
||||||
|
| `INIT_ROLL/PITCH/YAW` | `180/0/0` | Initial pose orientation (deg) |
|
||||||
|
| `INIT_SPEED` | `50` | Startup movement speed (mm/s) |
|
||||||
|
|
||||||
|
## Web UI
|
||||||
|
|
||||||
|
Access at `http://localhost:8080/`
|
||||||
|
|
||||||
|
- Live status, TCP pose, and joint angles
|
||||||
|
- Home and Reset buttons
|
||||||
|
- Move to position form
|
||||||
|
- Vacuum gripper controls (when enabled)
|
||||||
|
|
||||||
|
## REST API
|
||||||
|
|
||||||
|
Interactive docs at `http://localhost:8080/docs`
|
||||||
|
|
||||||
|
| Method | Endpoint | Description |
|
||||||
|
|--------|----------|-------------|
|
||||||
|
| GET | `/` | Web control interface |
|
||||||
|
| GET | `/api/status` | Robot status (connected, errors) |
|
||||||
|
| GET | `/api/pose` | Current TCP pose |
|
||||||
|
| GET | `/api/joints` | Current joint angles |
|
||||||
|
| GET | `/api/config` | API configuration |
|
||||||
|
| POST | `/api/home` | Go to home position |
|
||||||
|
| POST | `/api/reset` | Clear errors and reset state |
|
||||||
|
| POST | `/api/move_to` | Move to position |
|
||||||
|
| POST | `/api/move_to_pose` | Move to full pose |
|
||||||
|
| POST | `/api/disconnect` | Disconnect from robot |
|
||||||
|
|
||||||
|
### Vacuum Gripper (when `VACUUM_ENABLED=true`)
|
||||||
|
|
||||||
|
| Method | Endpoint | Description |
|
||||||
|
|--------|----------|-------------|
|
||||||
|
| GET | `/api/vacuum` | Get vacuum status |
|
||||||
|
| POST | `/api/vacuum/on` | Turn vacuum on |
|
||||||
|
| POST | `/api/vacuum/off` | Turn vacuum off |
|
||||||
|
|
||||||
|
## CLI Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Get pose
|
||||||
|
curl http://localhost:8080/api/pose
|
||||||
|
|
||||||
|
# Go home
|
||||||
|
curl -X POST http://localhost:8080/api/home
|
||||||
|
|
||||||
|
# Move to position
|
||||||
|
curl -X POST http://localhost:8080/api/move_to \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"x": 200, "y": 0, "z": 300}'
|
||||||
|
|
||||||
|
# Move with orientation
|
||||||
|
curl -X POST http://localhost:8080/api/move_to_pose \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"x": 200, "y": 0, "z": 300, "roll": 180, "pitch": 0, "yaw": 0}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dora Outputs
|
||||||
|
|
||||||
|
- `status` - JSON: `{ok, action, message, timestamp_ns}`
|
||||||
|
- `joint_pose` - 6 joint angles in degrees
|
||||||
|
- `tcp_pose` - `[x, y, z, roll, pitch, yaw]` in mm/deg
|
||||||
|
|
||||||
|
## Dora Inputs
|
||||||
|
|
||||||
|
- `command` - JSON string: `{id, action, payload}`
|
||||||
|
- `id`: unique command id (required)
|
||||||
|
- `action`: `home`, `move_to`, or `move_to_pose`
|
||||||
|
- `payload`: fields for move (`x`, `y`, `z`, `roll`, `pitch`, `yaw`, `speed`)
|
||||||
|
|
||||||
|
`status` will include `command_id` for command responses.
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
nodes:
|
||||||
|
- id: ulite6
|
||||||
|
git: ssh://git@gitea.liria.cl:5001/liria/dora_ulite6.git
|
||||||
|
git_checkout: v0.1.0
|
||||||
|
build: uv pip install -e .
|
||||||
|
path: dora_ulite6/main.py
|
||||||
|
inputs:
|
||||||
|
tick: dora/timer/millis/10
|
||||||
|
outputs:
|
||||||
|
- status
|
||||||
|
- joint_pose
|
||||||
|
- tcp_pose
|
||||||
|
env:
|
||||||
|
ROBOT_IP: "192.168.1.192"
|
||||||
|
DEFAULT_SPEED: "30"
|
||||||
|
DEFAULT_UNITS: "mm"
|
||||||
|
API_HOST: "0.0.0.0"
|
||||||
|
API_PORT: "9000"
|
||||||
|
VACUUM_ENABLED: "true"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""Dora ULite6 node package."""
|
||||||
+1020
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
|||||||
|
[project]
|
||||||
|
name = "dora-ulite6"
|
||||||
|
version = "0.1.0"
|
||||||
|
license = "MIT"
|
||||||
|
authors = [
|
||||||
|
{ name = "Dora" }
|
||||||
|
]
|
||||||
|
description = "Dora node for controlling the UFactory Lite6 robot"
|
||||||
|
|
||||||
|
requires-python = ">=3.8"
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
"dora-rs >= 0.3.9",
|
||||||
|
"numpy < 2.0.0",
|
||||||
|
"scipy >= 1.14.0",
|
||||||
|
"xarm-python-sdk >= 1.17.3",
|
||||||
|
"fastapi >= 0.109.0",
|
||||||
|
"uvicorn >= 0.27.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
dev = ["pytest >=8.1.1", "ruff >=0.9.1"]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
dora-ulite6 = "dora_ulite6.main:main"
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
packages = ["dora_ulite6"]
|
||||||
Executable
+21
@@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Install dependencies for dora-ulite6.
|
||||||
|
# Requires: Python >=3.8
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO_DIR="${SCRIPT_DIR}/.."
|
||||||
|
|
||||||
|
# Install uv if not present
|
||||||
|
if ! command -v uv &>/dev/null; then
|
||||||
|
echo ">>> Installing uv..."
|
||||||
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create venv and install
|
||||||
|
echo ">>> Installing dora-ulite6 in editable mode..."
|
||||||
|
cd "$REPO_DIR"
|
||||||
|
uv venv --seed --allow-existing
|
||||||
|
uv pip install -e ".[dev]"
|
||||||
|
|
||||||
|
echo ">>> Done. Activate with: source .venv/bin/activate"
|
||||||
Reference in New Issue
Block a user