# dora-teleop dora-rs teleoperation node: normalises two control sources — a gamepad read from `dora-joy`, and a web console served by this node itself — into one owner's lease, and emits velocity set-points and robot-agnostic commands. It carries no hardware driver: pad I/O lives in `dora-joy`, arm I/O in a robot node. **Requirements:** dora 1.0+ (verified with `dora-cli 1.0.1`), Python 3.12+, `uv`. ## System ```mermaid flowchart LR pad([gamepad]) --> joy browser([browser]) -- "ws: held directions, verbs" --> web web -- "ui_state snapshot" --> browser joy["dora-joy — sibling repo"] subgraph teleop["dora-teleop"] web["web console — built in, HTTP + ws on one port"] core["command arbitration"] end joy -- "joy: axes, buttons, present" --> core web -- "held directions, ±1" --> core core -- "twist SI · command verbs (standard)" --> arm core -- "jog · request (deprecated)" --> arm arm -. "state · poses · response" .-> core arm["robot node"] ``` ## Quick start ```bash uv sync dora run dataflow.yml --uv # teleop + bench stand-in: no pad, no arm ``` Open `http://localhost:7000` and drive the robot from the console. The bench answers services and publishes telemetry so the loop closes. ### Watching the wire Every dataflow sets `debug: enable_debug_inspection: true`, so dora can inspect the wire without any node printing. This needs a coordinator, so it works for the arm-free dataflow: ```bash dora up dora start dataflow.yml --uv --name teleop-bench dora topic list -d teleop-bench # every edge in the graph dora topic echo -d teleop-bench teleop/jog # every velocity, live dora topic hz -d teleop-bench teleop/jog --duration 10 # the re-send cadence dora stop --name teleop-bench && dora down ``` ## Running with hardware | Dataflow | Nodes | Needs | |------------------------|--------------------------------------|--------------------| | `dataflow.yml` | teleop + bench stand-in | nothing | | `dataflow-robot.yml` | joy + teleop + ulite6 | a pad and an arm | | `dataflow-gamepad.yml` | the same, plus the probe, no console | a pad and an arm | ## Gamepad | Control | Does (base frame) | Index | Units | |----------------------|----------------------------------------------------------------|--------------|-------| | Left stick X / Y | Translate in the horizontal plane: `+X` stick up, `+Y` stick right | axes 0, 1 | mm/s | | Left / right trigger | Translate along Z: right trigger lifts (`+Z`), left lowers | axes 4, 5 | mm/s | | Right stick X / Y | Rotate: `+roll` stick right, `+pitch` stick up | axes 2, 3 | deg/s | | LB / RB | Yaw: RB is `+yaw` | buttons 6, 7 | deg/s | | A | Close the tool | button 0 | — | | B | Open the tool | button 1 | — | | X | Release the E-STOP and clear the arm's error | button 3 | — | | Y | Toggle freedrive | button 4 | — | | Back | **E-STOP** — latches until X | button 11 | — | The sticks move the arm in its horizontal X/Y plane only; the triggers are the one path to Z. Up on the stick sends the arm away from the operator, the same `+X` convention the console draws. ## Inputs and outputs | Direction | Name | Description | |-----------|----------------|--------------------------------------------------------| | in | `tick` | 50 ms — change detection, deadman, telemetry | | in | `joy` | One pad reading from `dora-joy` | | in | `response` | An answer from the arm | | in | `motion_state` | How a motion ended | | in | `robot_state` | The arm's own state | | in | `tcp_pose` | Tool-tip pose, six float32, mm and degrees | | in | `joint_pose` | Joint angles, six float32, degrees | | out | `twist` | **Standard.** `{linear, angular, frame}`, SI | | out | `command` | **Standard.** `{action, value}`, robot-agnostic verbs | | out | `jog` | *Deprecated.* mm/s + deg/s, for `dora-ulite6` | | out | `request` | *Deprecated.* A service call, `request_id` in metadata | | out | `ui_state` | The console's telemetry snapshot, as JSON | ## Environment variables | Variable | Default | Description | |--------------------------|-----------|--------------------------------------------------------| | `SPEED_MM_S` | `60.0` | Translation speed at full deflection | | `SPEED_DEG_S` | `30.0` | Rotation speed at full deflection | | `DEADZONE` | `0.12` | Stick travel near centre to ignore | | `EXPO` | `0.4` | How much of the curve is cubic; 0 is linear | | `JOG_TTL_S` | `0.4` | Owner silence before the arm is stopped | | `JOG_KEEPALIVE_S` | `0.2` | How often an unchanged velocity is repeated | | `JOY_TTL_S` | `0.2` | `joy` silence before the pad reads as gone | | `GAMEPAD_IDLE_RELEASE_S` | `2.0` | Untouched pad before it hands the arm back | | `STATE_POLL_S` | `0.5` | How often to ask the arm what telemetry does not carry | | `WEB_HOST` | `0.0.0.0` | Address the console binds to | | `WEB_PORT` | `7000` | Console port; `0` opens no port at all | ## Development ```bash uv sync --group dev uv run pytest # 149 tests, no gamepad, arm, browser or daemon needed uv run ruff check . uv run ruff format --check . ```