Skip to content

OpenPAVE v1.8.4 — A command that did not reach the body is sent again

Latest

Choose a tag to compare

@odincodeshen odincodeshen released this 09 Oct 16:04
· 1 commit to main since this release

OpenPAVE v1.8.4 — A command that did not reach the body is sent again

A patch release on v1.8.3 that fixes the one
functional flaw left from the v1.8.3 audit: in run_live, a command that failed to reach the body was never
sent again.

The bug

MotionGate recorded an action as sent when it decided to send it — before the send happened. If the send then
got no reply (a lost packet, a body that was not up yet, a dropped link) the gate still believed the body was
doing it, and because dispatch is edge-triggered it never sent the same action again until the model's decision
changed.

Reproduced on a real raw_zenoh link on the DGX Spark with v1.8.3: the brain was started before the body, its
first TROT got no_reply, the body came up 9 s later — and for the remaining 30 s the brain kept deciding TROT
but never sent it again; the body received only the final STOP. A lost STOP was just as stuck. Present
since v1.8.0.

Two related gaps made it worse: a trot whose reply was lost may still have reached the body, yet the gate would
skip the STOP at shutdown; and the body's lease (from v1.8.2) armed a motion even when it failed, then
answered a repeat of it with "completed (renewed)" without running it — a retry would have made a half-started gait
look healthy.

What changed

  • MotionGate is told the outcome and forgets an undelivered action, so the next tick offers it again.
    A STOP is retried without limit; a motion is retried up to --dispatch-retries times (default 3) and
    then abandoned; a definitive refusal (rejected / unsupported) is never retried. A retry past the
    confirmation window must be re-confirmed.
  • A transport exception no longer ends the loop — it is a failed delivery, reported and retried. Each tick's
    JSON gains delivery (delivered / retry / abandoned / refused).
  • A motion with an unknown outcome keeps a STOP owed — sent when the loop next decides to stop and at shutdown;
    an unconfirmed watchdog STOP is retried on the next failing tick.
  • The body no longer mistakes a repeat of a failed motion for a renewal — only a completed motion is
    renewable; repeating a failed one runs it again. A failed motion is still armed, so the lease still stops it.
    (lease.py,
    run_live.py)

Verification

  • 218 unit tests (200 in v1.8.3) on Python 3.9 and, from a clean checkout of the tag, on Python 3.12.
  • Each part of the fix is mutation-checked — six separate reverts each turn specific tests red.
  • Real raw_zenoh, same scenario before and after (brain started 9 s before the body): v1.8.3 — one failed
    attempt, never retried, the body saw STOP only. v1.8.4 — ticks 0 and 1 no_reply → retry, tick 2
    delivered; the body saw exactly one TROT and the shutdown STOP.

Not verified: a physical actuator or the real dog; device_connect's serve() wiring against a live runtime.
Known limit: a renewal sent after the body's lease has already expired starts the motion again, because the body
cannot tell a renewal from a new command; an explicit renew-only message would fix that and is not in this release.

Compatibility

run_live behaves the same except that undelivered commands are re-sent and a transport exception no longer ends
the loop; the per-tick JSON gains a delivery field and --dispatch-retries is new. The body change only alters how
a repeat of a failed motion is handled. No change to the seam interface or the capability contract.

Full notes ·
compare v1.8.3…v1.8.4