Repository navigation
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
MotionGateis told the outcome and forgets an undelivered action, so the next tick offers it again.
ASTOPis retried without limit; a motion is retried up to--dispatch-retriestimes (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 gainsdelivery(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 sawSTOPonly.v1.8.4— ticks 0 and 1no_reply→retry, tick 2
delivered; the body saw exactly oneTROTand the shutdownSTOP.
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.