Conversation
Stop and completions: - Every command a stop, e-stop, reset_state, simulator toggle or teleport discards completes as failed with MOTN_CANCELLED, and wait_command raises it; MotionError is a waldoctl RobotError, so a frontend catches one type on either backend. - A tool action stops in place on stop/e-stop; the tool side channel is a FIFO so tool actions run in queue order with the moves they follow. - reset_state resets the program, not the controller: the protective stop, homed state, outputs and gripper command stay. Tools: - Electric grippers speak move/calibrate/stop/idle, validated at the wire (exactly [position, speed, current_ma], in range); a move before a calibrate is refused; a tool action names the selected tool; the default grip current comes from the tool config. Streams: - jog_l is a 6-DOF twist; jog_j stops one joint short of its soft limit and leaves the others running; jog_l and every servo stream are refused unhomed; servo streams honour their speed fraction, brake on an unreachable target and hold after 0.25 s of client silence. Planned motion: - move_p rounds each corner by a quarter of the shorter leg at one tool speed; move_s is a natural cubic spline on chord-length knots; TRF is an offset in the tool frame at the start of the move; a WRF rel pose rotates about the TCP. - move_c takes a blend radius: a Line|Arc chain shares one cubic corner with move_l, so move_l(r) → move_c(r) → move_l is one continuous path. - LINEAR ramps at the hardware acceleration limit over the path's own length; a TOPPRA failure refuses the move; an IK branch hop above 0.35 rad refuses it as IK_PARTIAL_PATH instead of being smoothed over. A path leaving a wrist singularity (the standby pose is one) turns the wrist first, as a joint move the tool never sees, then runs the cartesian path from the turned wrist. - Targets are checked for finiteness and joint limits at the wire. Queries and control: - joint_speeds is rad/s (is_robot_stopped defaults to 0.01 rad/s); queue()/activity() report snake_case command names; status() carries a ToolStatus; teleport is an acked system command that validates its pose, cancels what was driving the arm and references it. - tcp_speed differentiates across a window of frame-stamped samples instead of assuming the broadcast period, which a client polling between broadcasts halved. - A stream refused in setup latches the refusal as the standing error, so a jog_l or servo sent unhomed is answered in STATUS. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HsNxn193Lto6BF7RZXuc6
…speed - A planned move to within a motor step of where the arm stands is done where it stands, instead of being handed to a timing solver that has no distance to spread and refuses it. - The brake a jog_l runs out after a predicted collision withholds the configurations that would reach the contact and holds the last clear one, so escaping one keep-out never streams into the next. - tcp_speed differentiates across a window of frame-stamped samples rather than over the broadcast period, which a client polling between broadcasts halved and the loop's jitter scattered. - Tests: a servo_l stream is driven the way a client drives it, refreshed until the tool is on target, since a silent stream now brakes and holds; status() is a StatusSnapshot with a ToolStatus; hand-built jog_l states are referenced. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HsNxn193Lto6BF7RZXuc6
Safety and limits: - A blended move_j chain checks every target against the joint limits, relative moves included, so a chain of rel steps cannot plan past one. - jog_j brakes each joint on its own profile (no Ruckig time sync) and keeps the lookahead's measured speed across the datagrams that stream it, so a joint joining the jog cannot carry another past its limit. - Servo targets are checked for finiteness, and servo_j targets against the joint limits, where the controller decodes them. - The streaming executors' graceful stop brakes: ruckig hands out a copy of its targets, so the per-element writes it replaced changed nothing and every jog_l release, IK stop and servo_l brake ended abruptly. Completions and errors: - A stream that preempts planned motion fails every index it discarded with MOTN_CANCELLED, as stop does. - A jog_l braked short of a keep-out ends FAILED with SYS_SELF_COLLISION latched as the error error() reads; the executor latches every failed command's error and records it against its index. - servo_l through an unreachable pose brakes and holds, then resumes from the arm when the stream moves on to a pose it can reach. - A tool action sent right behind a select_tool is judged against that selection, and the teleport's tool check uses it too. - A planned home reports no referencing progress; the seek's step is cleared when the seek ends. - tcp_speed reads zero once the arm has not moved for a window of control ticks, whatever the status rate. Paths: - jog_l integrates translation and rotation separately, so a twist with both keeps the TCP on a straight line; the dry run applies the same speed ceiling to a diagonal. - A move that starts with a wrist turn takes the duration it names, turn included; the turn's interior rows are collision-checked; the dry run takes the same turn instead of calling the move unreachable. - LINEAR, TRAPEZOID and QUINTIC time a cartesian path along the tool's distance, and hold a process move to one tool speed under the tool ceiling, as TOPPRA does. - move_s slerps between the waypoints' rotations read as the wire names them (intrinsic XYZ); rotation angles use atan2, so a repeated pose measures zero. Client and dry run: - Pose moves report as move_j and servo_j; move_j(pose=..., rel=True) raises; the dry run honours select_profile, previews jog_j distance in the right units and answers select_tool/set_tcp_offset with an index. - A gripper move that names no current grips at the middle of the tool's current range (waldoctl's default_current). Simplified: one outcome-ring size, the attachment-changed message as a constant, pose6_to_se3, blend setup on the chain-link mixin, the scaled velocity limit, typed tool side channel, the servo step folded, the teleport refusals in the controller; dead segment accessors, jog minima and motion re-exports removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HsNxn193Lto6BF7RZXuc6
…t speeds in deg/s Planned moves take duration: float = 0.0, speed: float = 0.5 and accel: float = 0.5, as waldoctl now states. planned_move_timing, shared by the live, sync and dry-run clients, checks them before anything is sent: a duration above 0 times the move, otherwise speed does, and a negative or non-finite duration, or a speed or accel outside (0, 1], raises ValueError. The wire is unchanged: the unused field goes out as it already did. Servo streams default to half speed and half accel, jogs to half accel. An electric gripper's move carries current as a fraction of the tool's current range, as it already carried speed, and the controller turns it into the gripper's mA where it builds the hardware command. set_position/open/close take speed and current as typed fractions, and adjust_step is 10 percent points. The status cache publishes joint speeds in deg/s, converted in place when they change, so joint_speeds(), STATUS and is_robot_stopped and wait_motion's thresholds (now 0.5 deg/s) read in degrees like angles. Two tests pass accel=1.0 explicitly where they compare against a preview that does not model the ramps or need a profile margin that half accel would eat. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ead the plan The parity pass failed CI on every runner. Two planner faults and eight tests that asserted wall-clock timing, which the macOS runners run at 40-49 Hz. Planner: - A cartesian joint path is the IK solver's own array, and the solver hands it back Fortran-ordered: its rows were a layout the warmup never compiled, so the first cartesian plan in a process compiled the rad-to-steps kernel for seconds in the middle of a chain. The path is made contiguous where it is built. - The turn a chain takes out of the wrist singularity was the first one in the list that left a chain on one branch; with the split between J4 and J6 settled a hair wrong, the solver snapped the wrist 12 degrees in one row once the tool had left the singularity, which TOPP-RA absorbed and the row-timed profiles stretched a 5 mm move to 8 s over. The first turn whose chain steps no joint further between two rows than the turn's own rows do is taken now, the fixed turns before the solver's hint, so the choice stays the same from run to run. Status: - tcp_speed differentiates over perf_counter stamps; monotonic ticks every 15.6 ms on Windows before Python 3.13 and scattered the readout by a fifth. Tests: - The recompile test plans a cartesian move; a move out of the singularity has to plan under 3 s on LINEAR and TRAPEZOID; a coarse monotonic clock must not scatter tcp_speed. - The profile timing test reads the duration the controller planned from the paused queue; the corner test reads the one tool speed off the plan the arm plays and the geometry off the arm; a spline passes its waypoints within the polyline the status samples draw; the servo and jog tests tick the in-process controller at the control rate on every OS, and the jog_l preview test runs in-process; the keep-out jog ticks through the datagram still in flight before asserting the latch; the process move from home, near-singular at both ends, waits 20 s. - Grip current on the raw calibration-gate move is a fraction. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HsNxn193Lto6BF7RZXuc6
The IK solver returns a cartesian joint path Fortran-ordered, and the planner converts its rows to steps: the warmup compiles the conversion for that strided layout, so the first cartesian plan in a process no longer compiles it in the middle of a move. The path keeps the solver's layout. test_move_p_basic starts clear of the home pose's wrist singularity: a process move runs at the one speed its slowest row allows, and at the singularity that row is the wrist's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HsNxn193Lto6BF7RZXuc6
- A move_c whose end is its start sweeps the whole circle. The arc took the sweep from the angle between start and end, which the arm's settle error leaves a hair above zero, and a full circle ran as a nudge or was refused; it now takes a returned end as a full circle, as ArcSegment and par6 do. - tcp_speed and the stale-broadcast check read one clock, perf_counter. - The servo grace and jog duration tests run the commands' timers on a virtual clock advanced one control interval per tick, so they count ticks on any runner. The keep-out latch test pings the controller after the stop: once the ping is answered, no jog sent before it is still in flight to clear the error. - The blend-chain test waits on the last move's completion rather than on the arm keeping still. - The wrist-turn collision test clears the keep-out it gave its preview: a preview's shapes are the process's, and every later preview planned around it. - The examples step runs demo_showcase and precision against a controller the test starts, as a Waldo Commander session provides one; they home unconditionally, since the arm boots unhomed at the home angles. draw_circle's top circle moves down to 270 mm: at 280 mm following it needs the wrist to flip mid-arc, which a cartesian move refuses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HsNxn193Lto6BF7RZXuc6
demo_showcase waited on its blended zig-zag with wait_motion, which gives up after 10 s without saying so; the chain runs as one 6.6 s path, and on a slow loop the next move timed out behind it. It waits on the scan's last move instead, for up to 30 s. draw_circle's full circle, a 4.9 s path, gets the same 30 s its spline already has 60 s for. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HsNxn193Lto6BF7RZXuc6
The contract this branch implements is released as waldoctl v0.15.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HsNxn193Lto6BF7RZXuc6
The planner plans a blend chain once no command has arrived for the blend hold, 0.1 s as in par6's blend_hold. A test sends a chain one acknowledged command at a time, and on a degraded CI loop (macOS runs ~40 Hz) two of them can arrive further apart than that: the chain is planned in pieces, its corners are not rounded, and from the standby wrist singularity the piece after the split needs a mid-path wrist flip and is refused. PAROL6_BLEND_HOLD_S sets the hold; the test server uses 0.5 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HsNxn193Lto6BF7RZXuc6
With the 0.5 s blend hold the planner keeps move_j(r=20) waiting for a blend partner, and on a slow runner wait_motion gave up waiting for it to start: mid_pose was the pose before the move, move_l reached the planner alongside the held move_j, and its line back across the wrist singularity failed IK. The move's own completion is what the test needs before reading the pose. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QGctXgWU5nqo9ERf6t5A4D
The via-point check measured the via point's distance to the straight chord between consecutive TCP samples. On the macOS runner the loop runs near 47 Hz with ticks up to 84 ms late, so samples land ~36° apart and a chord cuts 1.5 mm inside the arc while the arm stays on it. The test now holds every sample to the circle and asks for one whole turn about its centre, which passes the via point opposite the start. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QGctXgWU5nqo9ERf6t5A4D
A queued command the pipeline drops (a stream preempting the queue, a plan that fails, a failed inline command, a world-guard refusal, reconnect, attachment change, simulator switch) now goes through one drop point: the planner is resynced to the tool, TCP, shapes and profile the controller actually holds, and each failing command records its own attributed error before its followers fail MOTN_CANCELLED. A wait on the failed command raises its error after a later command clears the standing one. Stream refusals are attributed to the stream's own index and latch the standing error only when nothing else is in flight; an unhomed jog_l or servo is refused before it can preempt a running home. A teleport clears the standing error and collision, lands before motion read in the same batch, accepts the tool positions status reports, and the pneumatic jaw reads back where it was put. The completion ring looks indices up in O(1). PAROL6_BLEND_HOLD_S must be a positive duration, and a blend chain is held while earlier motion plays, as par6 holds it. The integration fixture clears a latched protective stop before resetting, so one failed test no longer disables the server for every later one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QGctXgWU5nqo9ERf6t5A4D
- A joint parked on its limit step reads back up to half a step past the radian limit; the limit checks (move_j, blend links, wire, IK, the wrist turn) allow that half step, so relative moves and replayed rows are no longer refused. - TRAPEZOID, QUINTIC and RUCKIG size multi-row paths by path length and time blended move_j chains along the chain, so a closed circle and an out-and-back chain run instead of collapsing to their endpoints; the cartesian speed ceiling applies under every profile, and rows are one tick apart so a move lasts the time it was planned for. - An IK step too big to take is bisected and re-solved before it is refused, so a turn a hair off the wrist singularity plans; a genuine branch flip is still refused. - Lines interpolate position and rotation separately (no screw bow); move_p and move_s are sampled by geometry on the combined metric; a spline turns the tool at a waypoint that only turns it without swinging its position. - One arc and one TRF resolver: a collinear via fails the move_c on its own index whether or not it follows a blend. A failing chain previews its first move before the second. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QGctXgWU5nqo9ERf6t5A4D
…-outs - A cancelled stream (stop, estop, teleport, a planned move, a change of stream type, a failed or raising command) resets both streaming executors, so the next jog or servo starts from where the arm is rather than resuming the discarded stream's position and velocity, and a jog_l never solves from zero seeds. - Every servo_l and jog_l joint step, brakes included, is clamped to the joint speeds, so a stream going silent or losing IK never sends the lag the clamp was holding back in one tick; servo_l resumes when its client resends the target it braked short of. - jog_j releases a joint's limit latch once it leaves the jog or turns back, never commands a joint across its limit whatever the accel does mid-brake, and stops a joint the arm reports past it. - servo_j, servo_j_pose and servo_l refuse a colliding target and brake to a SYS_SELF_COLLISION failure on a predicted contact, as jog_l does. - jog_l resolves its twist against the tool as it stands, and servo_l takes each target from where the stream is, so axes follow the tool and a steady turn never wraps back at half a turn; the preview follows. - Jog durations count control ticks; the hot paths reuse their buffers and skip re-solving and re-applying what has not changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QGctXgWU5nqo9ERf6t5A4D
waldoctl marks tool_action QUEUED: it enters the controller's command
queue with the motion around it. The side channel that ran tool actions
beside the motion is gone. move_l(A); close(); move_l(B) closes the jaws
once the arm rests at A, holds the arm while they close, and leaves A
once they have closed, with no blend across the close. A tool action
waits under pause, counts in queued_duration and shows in queue(), and a
wait on it covers the motion queued ahead of it.
A tool action refused when its turn comes (not the fitted tool, not
calibrated) fails on its own index and cancels what is queued behind it.
Stop, estop, reset_state, teleport and a stream preempting the queue drop
queued tool actions and halt a running one where it is, keeping its
grip; a serial reconnect halts the tool before it forgets the
calibration. tool.stop() stays immediate: it halts the running action
("a tool stop"), completes when the jaws are still, and keeps what is
queued behind it. The preview orders tool actions the same way, a
release keeps the jaws where they are, and a calibration takes its two
seconds; the pneumatic valve dwells its travel. The sync tool_action
defaults to wait=False, as the contract has it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QGctXgWU5nqo9ERf6t5A4D
…ocess tests A trajectory's duration is now the time the player spends on it, one tick per row including the start row, so the preview and the controller's queued_duration agree to the tick. The in-process controller fixture puts the process-wide robot model back (no tool, no program shapes) when it closes: a shape one test attached had every later in-process plan refusing a self-collision with it. The r=0 blend test waits on its last move's own index, since that move's radius holds it for a partner while the arm rests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QGctXgWU5nqo9ERf6t5A4D
On macOS the loopback socket can hand a datagram over a tick or two after it was sent. The jog_l-after-a-cancel test now drains the controller's socket before waiting for the jog to end, so it never mistakes a jog not yet read for one already finished; the teleport tests wait up to two seconds for an acknowledgement the controller has already sent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QGctXgWU5nqo9ERf6t5A4D
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Makes parol6 behave as par6 does on every waldoctl method the two backends share, then closes the review findings from that pass. Each commit message has the full breakdown; the main points:
Stop and completions. Every command that a stop, e-stop,
reset_state, simulator toggle, teleport or preempting stream discards completes as failed withMOTN_CANCELLED, andwait_commandraises it.MotionErroris a waldoctlRobotError, so a frontend catches one type on either backend.reset_stateresets the program, not the controller.Safety and limits. Targets are checked for finiteness and joint limits at the wire, including every target in a blended
move_jchain with relative steps.jog_jbrakes each joint on its own profile, so a joint joining a jog cannot carry another past its limit. The streaming executors' graceful stop now actually brakes. Before, ruckig handed out a copy of its targets, so the writes to them changed nothing and everyjog_lrelease, IK stop andservo_lbrake ended abruptly.Streams.
jog_lis a 6-DOF twist that integrates translation and rotation separately, so the TCP stays on a straight line.jog_land servo streams are refused unhomed, and the refusal is latched as the standing error. Servo streams honour their speed fraction, brake and hold on an unreachable target, and hold after 0.25 s of client silence. Ajog_lbraked short of a keep-out endsFAILEDwithSYS_SELF_COLLISION.Planned motion.
move_prounds each corner at one tool speed.move_sis a natural cubic spline, and it slerps rotations as the wire names them (intrinsic XYZ).move_ctakes a blend radius, somove_l(r) → move_c(r) → move_lis one continuous path. A path leaving a wrist singularity turns the wrist first, and that turn counts toward the move's duration and is collision-checked. An IK branch hop above 0.35 rad refuses the move withIK_PARTIAL_PATHinstead of smoothing it over. A move to within a motor step of where the arm stands is done in place.Tools and queries. Electric grippers speak move/calibrate/stop/idle and are validated at the wire. A move before a calibrate is refused. The default grip current comes from the tool config.
joint_speedsis rad/s.tcp_speeddifferentiates across frame-stamped samples, so it no longer depends on the broadcast period.status()carries aToolStatus. The dry run honoursselect_profileand previewsjog_jin the right units.Companion branches
This depends on the waldoctl changes on
Jepson2k/waldoctl@claude/tender-darwin-9dq67v(MOTN_CANCELLED/RobotError.cancelled,default_current, and the contract docs). CI picks that branch up by name. The waldoctl pin here is stillv0.14.0, so the waldoctl branch needs to be merged and tagged, and this PR's pin bumped to that tag, before this merges. A matching Waldo-Commander branch also exists.— written by Claude on behalf of @Jepson2k
🤖 Generated with Claude Code