Skip to content

Broadcast the control loop's own health with STATUS - #9

Closed
Jepson2k wants to merge 1 commit into
mainfrom
claude/par6-backend-setup-q9l7bx
Closed

Broadcast the control loop's own health with STATUS#9
Jepson2k wants to merge 1 commit into
mainfrom
claude/par6-backend-setup-q9l7bx

Conversation

@Jepson2k

@Jepson2k Jepson2k commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Whether the control loop is keeping up is a question a display asks continuously, and today the only way to answer it is the LOOP_STATS query — polling for something the controller already knows on every tick. This puts the two numbers that actually say a loop is unwell on the status broadcast instead.

What changes

  • pack_status gains a trailing loop_health element: (p99_period_s, overruns). It appends at the tail, so a decoder that stops at the fields it knows is unaffected, and decode_status_bin_into fills it only when present.
  • StatusBuffer gains loop_health and drive_health dicts, matching the waldoctl status contract. Empty from producers that predate the fields, which is how a consumer tells "loop healthy" from "loop not reported".
  • StatusCache carries the two values and includes them in its change detection.

Why only the tail and the miss count

The mean, the rest of the distribution and the boot constants (target rate, scheduling policy) stay in LOOP_STATS: a display reads those once, and putting them on every frame would be bloat. The percentile is what breaks a control loop, and the overrun count is what proves it already did.

Why drive_health is declared but never filled

PAROL6's drivers report per-joint error flags over the serial link (Temperature_error_in, Position_error_in), not analog temperature, current or supply registers. An empty dict is the honest answer for a backend with no such sensor, and it is what tells a consumer "not reported" rather than "all zero". Nothing here invents a reading.

Cache cost

The status payload is re-encoded only when something changed. The percentile is recomputed once per stats window and overruns are rare, so this turns the payload over about as often as the window does rather than on every tick.

Tests

tests/integration/test_loop_health_broadcast.py waits for a frame carrying a warmed-up percentile and checks it against what loop_stats() answers about the same loop, so a stale or invented copy fails it.

Full suite: 275 passed, 1 failed, 8 skipped. The failure is tests/unit/test_pdeathsig.py::test_child_exits_on_parent_death, which fails identically on this branch's base in the same container (it needs a parent-death signal this environment does not deliver).

ruff check and ruff format are clean. ty check parol6/ reports the same 7 diagnostics as the base — all pre-existing overrides that lag newer waldoctl client signatures.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GdLL4oE6RejS9yPeSkQXpF


Generated by Claude Code

Whether the loop is keeping up is a question a display asks continuously,
and answering it through the LOOP_STATS query means polling for something
the controller already knows every tick. The period tail and the
deadline-miss count now ride the status broadcast instead, appended at the
tail so a decoder that stops at the fields it knows is unaffected.

The status cache re-encodes on change, and these two change slowly: the
percentile is recomputed once per stats window and overruns are rare, so
the payload turns over about as often as the window does rather than every
tick.

StatusBuffer also declares drive_health, which PAROL6 never fills: the
drivers report per-joint error FLAGS over the serial link, not analog
temperature or current registers, and flags are a fault surface rather
than a trend. An empty dict is the honest answer, and it is what tells a
consumer "this backend has no such sensor" rather than "all zero".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdLL4oE6RejS9yPeSkQXpF
@Jepson2k
Jepson2k force-pushed the claude/par6-backend-setup-q9l7bx branch from e424955 to 426b1d6 Compare September 4, 2026 00:43
@Jepson2k Jepson2k closed this Sep 4, 2026
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