Two problems, both found by the validate job once it could finally run. categories named `teleop` and `input`, neither of which dora defines. The set is fixed — sensor, actuator, robot, transform, filter, ml-inference, llm, speech, communication, recorder, visualization, simulator, debug — and an unknown variant makes the whole manifest unparseable, so every type annotation in it was being ignored. dataflow.yml spanned three repositories through sibling paths, which meant dora could not resolve the other nodes' manifests and reported their typed outputs as unannotated. It also could not be validated in CI, which clones one repo. The house pattern is a self-contained dataflow, and this now follows it: teleop plus a bench stand-in for the arm and the browser, both from this checkout. The real three-node wiring moves to README.md, where a consumer would look for it. The bench answers every service, publishes plausible telemetry, and prints the velocities it is sent, so the node can be driven from a real gamepad with no robot in the room. It is not a simulator and must not become one: it never integrates a velocity or moves a pose.
173 lines
6.4 KiB
YAML
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]
|
|
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"
|