Broadcast the control loop's own health with STATUS - #38
Merged
Merged
Conversation
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
The counter only ever climbs, so comparing a status frame against a query taken after it asserts that the loop missed no deadline in between. On a loaded macOS runner it misses one or two, which is the loop reporting honestly, not the broadcast disagreeing with the query. The sample is now bracketed by a query on either side, which is the actual invariant: one monotone counter, read three times in order. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Bo12kumRx9PHnY9bL8qgn
The shared status buffer can still hold a frame captured before the first query, so bracketing the broadcast sample between two queries failed from below: the frame was older than the lower bound, not disagreeing with it. Waiting for a frame whose count has reached that reading puts the sample between the two by construction, and the only assertion left is the one that means something — the broadcast never runs past a query taken after it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Bo12kumRx9PHnY9bL8qgn
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.
Whether the control loop is keeping up is a question a display asks continuously, and answering it through the loop-statistics query means polling for something the status broadcast is already sending 50 times a second. The p99 period and the overrun count now ride along with STATUS, in a
loop_healthmapping.drive_healthis declared beside it and stays empty here, deliberately: the drives report per-joint error flags over the serial link, not analog temperature or current registers, so there is nothing to put in it. Empty is what tells a consumer "no such sensor" rather than "all zero", and the flags surface as faults instead of a trend.The wire field is length-guarded like every other appended slot, so a producer that predates it decodes as an empty mapping and a consumer can tell "loop healthy" from "loop not reported".
This is the parol6 half of the coordinated
claude/par6-backend-setup-q9l7bxwork; the waldoctl half is already merged and released in v0.12.0. The branch came over from the Jepson2k fork, where it was raised against a stale main, and is cherry-picked onto current upstream main here.Ordering note: type checking on this branch reports two errors that are on
maintoday and are not from this commit — the sync client and the robot factory are annotated as holdingToolSpecwheremake_sync_toolreturnsSyncTool. #37 fixes exactly that. Merge #37 first and this goes green once main is merged back in.🤖 Generated with Claude Code
https://claude.ai/code/session_014Bo12kumRx9PHnY9bL8qgn