Reviewed-on: #6
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
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
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:
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
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 .