`set_cartesian_velocity` passed `duration=velocity_command_ttl_s` on every message, so each one re-armed a timed segment and the controller re-planned the same motion. On a real Lite6 that is not smooth travel: the arm moves in steps. Reducing how often the velocity is sent does not help — re-arming four times a second still steps — so it now uses `duration=0`, the SDK's "always effective, will not stop automatically", and skips a velocity that has not changed. One command stands until a different one replaces it. That removes the controller-side expiry, which `_expire_jog` was relying on: it flipped `_jogging` and reported `ready` without commanding anything, because the controller's own timer had already stopped the arm. With the velocity held indefinitely that would leave an arm travelling while the node reported idle, so `_expire_jog` now queues the stop itself. It goes through the job queue rather than the driver, since it runs on the reporter thread and the control thread owns the driver. `VELOCITY_COMMAND_TTL_S` is unchanged in value and meaning to a caller, but it is now enforced here rather than by the controller. Both halves are one commit on purpose: `duration=0` without the stop in `_expire_jog` is an arm that never stops when its sender dies. Adds tests/test_driver.py — the driver had no direct coverage, because FakeArm replaces it everywhere else, which is why this was invisible to the suite.
135 lines
5.5 KiB
YAML
135 lines
5.5 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 no robot.
|
|
#
|
|
# The `request` and `response` ports are deliberately **untyped**. They carry
|
|
# dora's pattern-correlation metadata (`request_id`), and dora defines that as
|
|
# the marker for a port where several Arrow schemas legitimately flow — one per
|
|
# service — distinguished by metadata rather than by a fixed Arrow type. The
|
|
# runtime skips type checks on such messages, so annotating them would claim a
|
|
# contract neither side keeps.
|
|
apiVersion: 1
|
|
name: dora-ulite6
|
|
namespace: liria
|
|
description: UFactory Lite6 arm node serving motion and control services, and publishing joint and tool pose
|
|
categories: [robot, actuator]
|
|
keywords: [ufactory, lite6, xarm, robot-arm, manipulator, service, vacuum, gripper]
|
|
|
|
runtime: python
|
|
entrypoint: dora-ulite6
|
|
# `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:
|
|
type: std/core/v1/UInt64
|
|
required: true
|
|
description: >-
|
|
Each tick publishes whatever the arm threads produced since the last one.
|
|
It does not drive the arm — the tick rate is the publication rate, and
|
|
TELEMETRY_INTERVAL_MS is how often the arm is actually read. Connect to
|
|
dora/timer/millis/N.
|
|
request:
|
|
required: true
|
|
description: >-
|
|
A service request: a one-row struct {service, ...arguments}, with the
|
|
caller's `request_id` in the metadata. Services: move_to, move_joints,
|
|
home, home_and_clear, clear, recover, stop, freedrive, grasp, release,
|
|
get_state. Answered on `response` under the same request_id, exactly once.
|
|
A motion takes `wait`: true (the default) holds the caller until the arm
|
|
stops; false answers at once with an acknowledgement and reports the rest
|
|
on `motion_state`.
|
|
jog:
|
|
required: false
|
|
description: >-
|
|
A continuous velocity command: a one-row struct {velocity} of 6 numbers
|
|
[vx, vy, vz, wx, wy, wz] in mm/s and deg/s. A topic rather than a service
|
|
because a sender keeps sending and nothing is published in answer. It
|
|
drives the arm, so the first non-zero one preempts a motion in play and
|
|
robot_state turns to moving.
|
|
|
|
The velocity is set on the controller once and held: an unchanged one is
|
|
not sent to the arm again, because re-arming it makes the controller
|
|
re-plan the same motion and the arm step rather than travel. A sender that
|
|
goes quiet for longer than VELOCITY_COMMAND_TTL_S is stopped by this node,
|
|
which is the deadman behind a sender that dies mid-jog.
|
|
|
|
outputs:
|
|
response:
|
|
description: >-
|
|
The answer to one service request, under its `request_id`: a struct
|
|
{ok, service, message, ...extras}. get_state adds connected, ready,
|
|
tool_engaged, freedrive, pose and joints. Exactly one response per
|
|
request, including for one that was refused or preempted.
|
|
motion_state:
|
|
description: >-
|
|
Where the current motion stands, published on every change: a struct
|
|
{request_id, service, state, message} with state one of accepted, moving,
|
|
done, failed, preempted. The request_id is in the fields rather than the
|
|
metadata because this is the arm's state, not an answer to a request. A
|
|
new motion preempts the one in play, so there is only ever one.
|
|
joint_pose:
|
|
type: std/core/v1/Float32
|
|
description: The six joint angles in degrees, base to wrist.
|
|
tcp_pose:
|
|
type: std/core/v1/Float32
|
|
description: >-
|
|
The mounted tool's tip as [x, y, z, roll, pitch, yaw] in mm and degrees,
|
|
in the robot base frame. With TOOL_TYPE none this is the flange itself.
|
|
robot_state:
|
|
type: std/core/v1/String
|
|
description: >-
|
|
One of connecting, ready, moving, recovering, unavailable. Published on
|
|
every change, never repeated.
|
|
|
|
env:
|
|
ROBOT_IP:
|
|
type: string
|
|
description: Controller address. Defaults to 192.168.1.192.
|
|
TOOL_TYPE:
|
|
type: string
|
|
description: What is mounted at the flange - none, vacuum or gripper. Defaults to none.
|
|
TOOL_OFFSET_MM:
|
|
type: float
|
|
description: Extra reach past the flange, added to the mounted tool's own. Defaults to 0.
|
|
DEFAULT_SPEED:
|
|
type: float
|
|
description: Cartesian speed in mm/s for a move_to with no speed of its own. Defaults to 30.
|
|
DEFAULT_JOINT_SPEED:
|
|
type: float
|
|
description: Joint speed in deg/s for a move_joints with no speed of its own. Defaults to 20.
|
|
TELEMETRY_INTERVAL_MS:
|
|
type: int
|
|
description: >-
|
|
How often the reporter reads the arm, and so how often telemetry is
|
|
produced. Keeps running through a motion. Defaults to 100.
|
|
RECONNECT_INTERVAL_S:
|
|
type: float
|
|
description: How long to wait before trying an unreachable arm again. Defaults to 5.
|
|
INIT_MODE:
|
|
type: string
|
|
description: Where the arm goes on startup - none, home or pose. Defaults to none.
|
|
|
|
example: |
|
|
- id: ulite6
|
|
git: ssh://git@gitea.liria.cl:5001/liria/dora-ulite6.git
|
|
tag: v0.3.0
|
|
build: pip install .
|
|
path: dora-ulite6
|
|
inputs:
|
|
tick: dora/timer/millis/100
|
|
request: planner/request
|
|
outputs:
|
|
- response
|
|
- joint_pose
|
|
- tcp_pose
|
|
- robot_state
|
|
env:
|
|
ROBOT_IP: "192.168.1.192"
|
|
TOOL_TYPE: "vacuum"
|