ADR 0020: battery profiling complete — refit derived, deliberately not adopted - #573
Merged
Conversation
The status line was stale in a way that mattered: it said the measured knots were still to come and that the shipped LUT was "generic Li-ion folklore". PR #541 replaced that folklore with a measured curve back in July, and the pinned-load confirmation runs the ADR was waiting on have since happened. Records what the campaign actually produced: six discharge runs on two rev4 units over 2026-07-17..07-26, of which only the last two carried the pinned load this ADR defines. The other four were spoiled by IMU pseudo-motion blanking the substituted frame and by logind RemoveIPC deleting the cedar shmem segment at SSH logout -- both of which left solve *attempts* churning at full rate, so the load looked healthy from the outside. That is why the load verdict now requires matches > 0. Headline: about 10 hours on a full charge under a continuously solving load at full brightness with sleep disabled (9h55m and 10h03m). Also fixes the Decision section's 0% anchor, which still described the hardware cutoff voltage while the amendment note above it said the opposite, and records where the raw telemetry lives. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The curve shipped in #541 was explicitly marked PROVISIONAL: it was fitted from two runs that discharged under a degraded load, because solving had silently stopped in both. The confirmation runs have since been done -- 2026-07-25 and 2026-07-26, one per rev4 unit, camera solving 100% of the discharge, 9h55m and 10h03m -- so the provisional marker can come off. Re-fitted from those two runs only. Pooling the four degraded runs in would contradict ADR 0020's own premise (a curve measured at a different load mis-maps voltage to runtime), and the analysis tool warns when asked to do it. The new curve sits up to 21 mV higher through the 5-25% band, so at a given voltage it reads a few points lower near empty and the 10%/5% warnings fire slightly earlier -- the intended direction for a warning. Test expectations that were pinned to the old knots are recomputed, not loosened. One interpolation case moved from 3.70 V to 3.72 V because 3.70 is now exactly the 15% knot and would no longer test interpolation. 1159 unit+smoke tests pass; ruff and mypy clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reverts the knot change from the previous commit and records why, in both the code and ADR 0020. Deriving the refit was right; adopting it was not. It fits the voltage/runtime scatter better and sets the low-battery advisories worse, which is the only thing these knots actually drive. The warner fires on the first noise dip past a threshold, and near the flat knee one ADC LSB (20 mV) spans about three SoC points -- so lifting the low knots ~20 mV moves the crossing a long way in wall-clock time. Replaying both pinned runs through LowBatteryWarner: shipped 5% fires 31 / 38 min before shutdown refit 5% fires 50 / 69 min before shutdown Design intent is ~30 min. The shipped curve's lower knee compensates for BATV quantisation noise and hits it; the refit overshoots roughly 2x. The percentage is advisory UI output, not a measurement, so a curve is only better if the warning lands better. ADR 0020 now records the refit, the replay table and the decision, plus the rule that follows: judge a candidate curve by replaying telemetry through the warner, never by knot distance alone. 1159 unit+smoke tests pass; ruff and mypy clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
brickbots
added a commit
that referenced
this pull request
Aug 6, 2026
The bench campaign closed 2026-07-26 (ADR 0020 updated in PR #573), so WP1 no longer has to leave a TODO where the battery-life number goes: about 10 hours, measured on two units under the pinned worst-case load. Framed as a floor rather than a typical figure, since it was measured with the camera solving continuously, the screen at full brightness and sleep disabled -- ordinary observing is lighter and runs longer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
brickbots
added a commit
that referenced
this pull request
Aug 6, 2026
* docs: plan for updating the user manual for rev4 hardware v2.6.1 ships full software support for rev4, but docs/source/ still describes rev3 exclusively -- and in the power/charging area it is actively wrong for a rev4 owner (states there is no battery indicator and no low-battery warning, documents a slide switch and a PiSugar board that rev4 does not have). This is a plan document only; no .rst is changed. It records the rev3 -> rev4 delta with sources, four structural decisions that need confirming before agents start, six agent-sized work packages with dependencies, the hardware photos that have to be shot by hand, the screenshots agents can produce themselves, and the hardware facts that are not in the repo and must not be guessed. Follows the bringup-implementation-plan.md precedent of keeping plan docs at the repo root. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: correct rev4 directional input to a 5-way joystick The plan described rev4's directional input as a "cluster" of separate switches moved to the right-hand column, following the language in keypad.py and the bring-up glossary. Physically it is one 5-way joystick: four directions plus a centre press that duplicates SQUARE. This matters in two places. For the manual, the joystick is the fastest visual tell between rev3 and rev4 and needs a one-time explanation of how it is operated -- the logical key names are unchanged, so nothing else in the prose moves. For bring-up, a builder whose grid shows one dead direction is reflowing a single component, not hunting for one of five switches that does not exist. Also flags the underlying terminology gap as a grill-with-docs candidate: the bring-up glossary defines "switch" as one physical pushbutton, which a multi-contact joystick is not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: record the four settled decisions, and make 176px capture real Decisions from Rich, now folded into the plan: - rev4 becomes the default voice; v3/v2.5 differences go in notes. - "rev4" is introduced alongside "v3"/"v2.5" with no retro-rename. - 176px is the default for new screenshots, no mass replacement, and a page's existing shots get re-taken when that page is edited anyway. - The rev4 DIY build path is out of scope and gets no scoping note, which deletes WP5 outright. WP0 is implemented here rather than left for an agent, because the plan should not document a flag that does not exist. pf_remote.py launch now takes --display (defaulting to headless_176, rev4's panel) and -fb for the rev4 battery monitor. Verified end to end: launched headless with -fb, captured /api/screen at 176x176 with the battery icon in the title bar, converted to a 352x352 doc-ready PNG. Both skills are updated to match, and the docs skill's product knowledge base is rewritten -- it described rev4 as a planned future revision with no working prototype, which would have poisoned anything written from it. Docs build stays clean: zero warnings under sphinx-build -n. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: resolve the rev4 runtime open question The bench campaign closed 2026-07-26 (ADR 0020 updated in PR #573), so WP1 no longer has to leave a TODO where the battery-life number goes: about 10 hours, measured on two units under the pinned worst-case load. Framed as a floor rather than a typical figure, since it was measured with the camera solving continuously, the screen at full brightness and sleep disabled -- ordinary observing is lighter and runs longer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: surface the 10h runtime and say where WP agents should branch from Two things agents need before they start. The measured runtime figure now appears in the rev3->rev4 delta table in §1, not only in the resolved open question at the bottom, since §1 is where an agent looks first. Branching guidance: WP1-WP4 must not branch from main while #572 is open. They depend on the pf_remote 176px flags, the docs skill's rev4 rules and the corrected product knowledge base, all of which live only on this branch -- and a branch cut from main looks healthy while silently producing 128px screenshots and rev3-default prose. Preferred fix is to merge #572 first; otherwise reset the worktree onto this branch and open WP PRs with --base pointing here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
brickbots
marked this pull request as ready for review
August 6, 2026 23:53
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.
Closes out the battery runtime campaign ADR 0020 has been waiting on since July.
Net effect: documentation only. No
SOC_LUTknot changes — the middle commit shipped the refit and the third takes it back out, with the reasoning preserved in the history because the reasoning is the point.The stale bit
ADR 0020's status said the measured knots were still to come and the shipped LUT was "generic Li-ion folklore, annotated as such in the code." Neither is true: #541 landed a measured curve in July, and the pinned-load confirmation runs it was waiting on have since happened.
What the campaign produced
Six discharges on two rev4 units, identical pinned profile (400 ms / gain 20, brightness 255, sleep off):
Four runs were spoiled by faults that left the workload looking healthy from outside — IMU pseudo-motion blanking the substituted frame, and logind
RemoveIPC=yesdeleting the cedar shmem segment at SSH logout (#548). Both left solve attempts churning at full rate with zero matches, which is why the tool's load verdict now requiresmatches > 0.Headline: about 10 hours on a full charge under a continuously solving load at full brightness with sleep disabled.
The interesting part: the refit was derived and rejected
Fitting the two pinned runs alone gives knots ~20 mV higher through the 5–25% band. Better fit to the scatter — worse setting for the only thing these knots drive. The warner fires on the first noise dip past a threshold, and near the flat knee one ADC LSB (20 mV) spans about three SoC points, so lifting the low knots moves the crossing a long way in wall-clock time. Replaying both pinned runs through
LowBatteryWarner:Intent is ~30 min at 5%. The shipped curve's lower knee compensates for BATV quantisation noise and hits it; the refit overshoots ~2×. The percentage is advisory UI output, not a measurement — a curve is only better if the warning lands better.
The ADR now records the refit values, the replay table, the decision, and the rule that follows: judge a candidate curve by replaying telemetry through the warner, never by knot distance alone. The code comment says the same thing at the point of temptation.
Verification
tools/battery_runtime_analysis.pyover all six runs and over the two pinned runs alone.LowBatteryWarnerunder both curves — that table is measured, not estimated.git diff origin/main...HEADchanges no knot values.Note
user_guide.rststill quotes a v3/PiSugar figure ("four to five hours") for battery life. There is now a real measured rev4 number to replace it with — tracked in the rev4 docs plan (#572), not fixed here.🤖 Generated with Claude Code