Files
dora-teleop/dora-node.yml
T
cristhian 7250396a1f
validate / validate (pull_request) Failing after 58m48s
validate / test (pull_request) Successful in 58m48s
validate / markdownlint (pull_request) Successful in 58m43s
add the node manifest, dataflow, CI and docs
dora-node.yml is the typed contract a consumer wires against, and the only one
they get: no source package is shared between node repositories. The jog,
request and ui ports stay untyped, because dora treats a port carrying
request_id as pattern-correlated and several Arrow schemas legitimately flow
over each — one per service, one per console message.

dataflow.yml runs the whole operator stack from checkouts: this node, the
WebSocket bridge and the arm.

web/ holds a placeholder page, not the real console. It exists so the dataflow
has something to serve and so the node can be checked end to end before the
Control section is ported across from the monolithic repo. It reads telemetry
and can take, release and stop; nothing else.
2026-09-15 15:43:12 -05:00

173 lines
6.4 KiB
YAML

# Node manifest: the typed contract a consumer wires against.
#
# No source package is shared between node repositories, so this file and its
# type URNs are the only contract a consumer has. `dora validate` checks a
# dataflow against it without running the node, so CI needs neither a gamepad
# nor a robot.
#
# `jog`, `request`, `response`, `motion_state` and the two `ui_` ports are
# deliberately **untyped**. dora treats a port carrying `request_id` as
# pattern-correlated and skips type checks on it, and several Arrow schemas
# legitimately flow over each — one per service, one per console message.
# Annotating them would claim a contract neither side keeps.
apiVersion: 1
name: dora-teleop
namespace: liria
description: Hands a robot arm to a human, from a gamepad or a browser
categories: [robot, teleop, input]
keywords: [teleop, gamepad, joystick, jog, arm, manipulator, web-console, operator]
runtime: python
entrypoint: dora-teleop
# `dora build --uv` / `dora run --uv` rewrites this to `uv pip install .` and
# gives the node its own managed venv under `.dora/python-envs/`, so the
# dependencies it sees at runtime are the ones installed here.
build: pip install .
dora: ">=1.0"
inputs:
tick:
description: >-
Drives everything time-based: re-sending the held velocity, expiring a
source that has gone quiet, and publishing the console's telemetry. Use 50
ms. Slower than about 100 ms and the arm node's own velocity expiry starts
firing between ticks, which an operator feels as the arm stuttering.
type: std/core/v1/UInt64
ui_command:
required: false
description: >-
One message from the web console, as the JSON string the WebSocket bridge
publishes on `data_out`. Wire it from the bridge. A message this node does
not recognise is dropped rather than refused, so a page from a newer build
cannot take the arm down.
clients:
required: false
description: >-
Connect and disconnect events from the WebSocket bridge, as
`{connected, event}`. **This is the browser's deadman.** Without it a tab
closing mid-jog leaves the arm travelling until the arm node's own expiry
catches up, because a socket that went away sends nothing to say so.
response:
required: false
description: >-
An answer from the arm node, correlated by `request_id` in the metadata. A
failure is passed to the console as a toast; a `get_state` answer fills in
what telemetry does not carry.
motion_state:
required: false
description: >-
How a motion ended. Needed because this node sends motions with
`wait: false`, and a non-blocking motion reports its outcome here and
nowhere else.
robot_state:
required: false
description: >-
The arm's own state, one of `connecting`, `ready`, `moving`, `recovering`
or `unavailable`. Translated into the vocabulary the console draws.
type: std/core/v1/String
tcp_pose:
required: false
description: >-
Tool-tip pose, six float32 as `[x, y, z, roll, pitch, yaw]` in mm and
degrees, base frame. Shown in the console's pose readout.
type: std/core/v1/Float32
joint_pose:
required: false
description: >-
Joint angles, six float32 in degrees. Shown in the console's joint readout.
type: std/core/v1/Float32
outputs:
jog:
description: >-
A base-frame velocity for the arm, as `{velocity: [vx, vy, vz, wx, wy, wz]}`
in mm/s and deg/s. Re-sent every tick while a source is driving, and
followed by exactly one all-zero vector when it stops — once, not
repeatedly, because a stream of zeroes would keep the arm node believing a
jog is live.
request:
description: >-
A service call on the arm — `stop`, `grasp`, `release`, `clear`,
`freedrive`, `home`, `move_to`, `get_state` — with `request_id` in the
metadata.
ui_state:
description: >-
The console's telemetry snapshot, as a JSON string for the WebSocket
bridge to broadcast. Also carries error toasts. Every key in it is read by
name in the console's `app.js`.
env:
SPEED_MM_S:
type: float
default: 60.0
description: >-
Translation speed at full stick deflection, in mm/s. A stick is analog, so
this is a maximum and anything less moves proportionally slower. Defaults
to 60.0.
SPEED_DEG_S:
type: float
default: 30.0
description: Rotation speed at full deflection, in deg/s. Defaults to 30.0.
DEADZONE:
type: float
default: 0.12
description: >-
Stick travel near centre to ignore, as a fraction. A worn stick that does
not centre would otherwise drive the arm on its own. What is left is
rescaled, so motion still starts from zero just outside the band.
Defaults to 0.12.
EXPO:
type: float
default: 0.4
description: >-
How much of the response curve is cubic, from 0 (linear) to 1. Fine
positioning happens near centre and a linear stick gives it almost no
resolution there. Defaults to 0.4.
JOYSTICK_INDEX:
type: int
default: 0
description: Which pad to open, when more than one is plugged in. Defaults to 0.
JOG_TTL_S:
type: float
default: 0.4
description: >-
How long the owning source may go quiet before the arm is stopped. Keep it
**below** the arm node's VELOCITY_COMMAND_TTL_S (0.5 s by default), which
is the controller-side backstop behind this one. Defaults to 0.4.
GAMEPAD_IDLE_RELEASE_S:
type: float
default: 2.0
description: >-
How long a pad may sit untouched before it hands the arm back, so a
browser can take over without anyone pressing anything. Defaults to 2.0.
STATE_POLL_S:
type: float
default: 0.5
description: >-
How often to call `get_state` for what telemetry does not carry: whether
the tool is engaged and whether freedrive is on. Defaults to 0.5.
example: |
- id: teleop
git: ssh://git@gitea.liria.cl:5001/liria/dora-teleop.git
tag: v0.1.0
build: pip install .
path: dora-teleop
inputs:
tick: dora/timer/millis/50
ui_command: ws-transceiver/data_out
clients: ws-transceiver/clients
response: ulite6/response
motion_state: ulite6/motion_state
robot_state: ulite6/robot_state
tcp_pose: ulite6/tcp_pose
joint_pose: ulite6/joint_pose
outputs:
- jog
- request
- ui_state
env:
SPEED_MM_S: "60"
SPEED_DEG_S: "30"