Skip to content

RSDK-14385: publish configured joint velocity and acceleration limits - #80

Open
JohnN193 wants to merge 2 commits into
viam-modules:mainfrom
JohnN193:rsdk-14385-publish-configured-joint-limits
Open

RSDK-14385: publish configured joint velocity and acceleration limits#80
JohnN193 wants to merge 2 commits into
viam-modules:mainfrom
JohnN193:rsdk-14385-publish-configured-joint-limits

Conversation

@JohnN193

@JohnN193 JohnN193 commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Ticket 8 of RSDK-14385. get_kinematics opened kinematics/<model>.json and returned the bytes verbatim, so callers got position bounds only. The SVA schema now carries optional per-joint max_velocity and max_acceleration, and the values this arm actually runs at are already in its config, so we attach them on the way out.

What changed

  • New sva_with_joint_limits(sva_json, velocity_rad, accel_rad) in utils.cpp: parse, write the two fields per joint, re-serialize. Config is radians and the schema is degrees, so it converts.
  • get_kinematics pipes the file bytes through it.
  • New sva_joint_limits_test target, plus the missing entry in the test target's explicit executable list in the Makefile.

jsoncpp was already a dependency (conanfile.py, used by trajectory_logger), so this adds none.

What it refuses, and why

The limits are positional, since the config gives us arrays with no joint names, so the vectors must be exactly as long as the document's joints array. Beyond that it rejects:

  • Non-revolute joints. RDK applies DegToRad to max_velocity for revolute and leaves prismatic unconverted, so a radian-derived value on a rail would be read as mm/s. A speed_rad_per_sec config cannot express mm/s.
  • Mimic joints. RDK rejects the entire model with ErrMimicWithLimits if a mimic joint carries limits of its own, so publishing one would be worse than publishing no limits.
  • Untyped joints, which RDK cannot build a frame from either.

None of these are reachable with the three shipped models (all 6-joint, revolute, no mimic). All of them would be silent the day a new model file lands, which is why they are checks rather than comments.

Failure handling

get_kinematics catches, warns with the file and reason, and serves the unpatched document. RDK builds the machine's entire frame system from this call, and validate_config_ accepts a configured DOF that disagrees with the model's joint count (speed_rad_per_sec: 1.0 with acceleration_rad_per_sec2: [1,2,3] validates and yields DOF 3). That used to be harmless here because we returned the file untouched. Losing the limits is exactly what every caller got before this existed, so that is the fallback rather than failing the machine's motion.

A stricter alternative is to reject the mismatch in validate_config_, which would fail such a config at startup instead. That would reject configs that work today, so I left it out; happy to add it if preferred.

Zero

A zero is written as an explicit 0, since only an absent field means unbounded. This module's validation rejects a zero element, but the schema permits one and dropping it would describe an axis that cannot move as unbounded.

Testing

Nine cases: per-joint degree conversion with distinct values, the rest of the document untouched (ids, parents, axes, min/max, links all compared against the original), zero written not dropped, joint-count mismatch, vector-length disagreement, malformed / non-SVA / no-joints, absent kinematic_param_type treated as SVA (matching referenceframe/model_json.go), and non-revolute / mimic / untyped refusal. One case walks src/kinematics/*.json so every shipped model is checked.

Not covered: get_kinematics itself, which needs a configured arm and a controller.

7/7 unit tests green, -Wall -Werror clean, clang-tidy and clang-format clean.

🤖 Generated with Claude Code

`get_kinematics` opened `kinematics/<model>.json` and returned the bytes
verbatim, so callers got position bounds only. The SVA schema now carries
optional per-joint `max_velocity` and `max_acceleration`, and the values
this arm runs at are already in its config, so we add them on the way out
and the motion service can generate trajectories from them.

`sva_with_joint_limits` takes an SVA document and the per-joint limits and
returns the document with the two fields written. Config is radians and the
schema is degrees, so it converts. A zero is a real limit of zero and is
written as such, since only an absent field means unbounded.

It refuses a few things rather than guessing. The limits are positional, so
the vectors have to be exactly as long as the document's `joints` array. A
prismatic joint wants mm/s and RDK does not convert what we write, so a
radian value would be read as millimetres. A mimic joint takes its limits
from its source, and RDK rejects the entire model if one carries limits of
its own. None of these are reachable with the three shipped 6-joint
revolute models, but all of them would be silent the day a new model lands.

`get_kinematics` catches those failures, warns, and serves the unpatched
document. RDK builds the machine's whole frame system from this call, and
`validate_config_` accepts a configured DOF that disagrees with the model's
joint count (a scalar paired with an array of another length), which used
to be harmless here because we returned the file untouched. Losing the
limits is what every caller got before this existed, so that is what we
fall back to rather than failing the machine.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@acmorrow

Copy link
Copy Markdown
Collaborator

JohnN193 - I'm going to hold off on this until we finish viam-modules/universal-robots#227 unless you think there is value in pursuing both in parallel.

@viam-overwatch

Copy link
Copy Markdown

Hey JohnN193 — CI is green and no reviewer is assigned yet. Could you request one when you have a chance?

Auto-comment from overwatch. Will not re-nudge for 7 days.

`validate_joint_limit_attribute` rejects a non-positive element before it
could reach here, so this cannot fire today. That is exactly the problem:
`sva_with_joint_limits` was correct only for as long as that stayed true,
and a caller assembling the vectors itself would have published a negative
speed as though it were a limit.

Zero is still written, since only an absent field means unbounded and a
zero limit is a real one saying the joint does not move. Negative is not a
limit at all.

Also note at the joint-count check that it doubles as the guard against
limits we never populated, since a default-constructed Eigen vector is
empty rather than a run of zeros. The universal-robots module needed an
explicit optional for this because its fixed-size vector defaults to zeros,
which would have been published as an arm configured not to move.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants