Low battery: blind-floor shutdown, 10%/5% warnings, re-anchored SOC_LUT (ADR 0021)#541
Open
brickbots wants to merge 1 commit into
Open
Low battery: blind-floor shutdown, 10%/5% warnings, re-anchored SOC_LUT (ADR 0021)#541brickbots wants to merge 1 commit into
brickbots wants to merge 1 commit into
Conversation
…e-anchored SOC_LUT Implements docs/adr/0021-blind-floor-shutdown.md end to end: - BatteryState represents ADC blindness honestly: a raw-0 BATV read publishes None for every ADC-derived field (never the 2.304 V decode artifact), with an adc_blind property; status-derived fields stay valid below the floor. Title-bar icon shows empty when blind. - Debounced low-battery shutdown: LowBatteryShutdownTrigger (pure, next to plan_charging_writes) fires after 4 consecutive ADC-blind polls (~20 s) on battery only — never on external power, never from SoC. battery_monitor puts "low_battery_shutdown" on ui_queue; the main loop shows the final warning and routes through the existing user-shutdown chokepoint (SHUTDOWN earcon wait, GPIO14 latch, ADR 0007/0008). On the Fake hardware platform the OS power-off is skipped, so battery_fake's scripted blind tail can never power off a host machine. - Advisory warnings at 10% and 5% SoC: LowBatteryWarner (pure) fires once per crossing, on battery only, with quantisation-jitter hysteresis; a None estimate (blind twilight) suppresses without re-arming so intermittent blind/sane alternation cannot re-fire the warning. Popup + console + LOW_BATTERY earcon (now wired); watcher uses a monotonic clock so GPS time steps can't silence it. - SOC_LUT replaced with the re-anchored provisional fit from the 2026-07-17 bench campaign (0% = blind-floor shutdown; all knots measured, none extrapolated; pending pinned-load confirmation runs). The old curve read ~21% at the blind floor, so the warnings could never have fired on it. - tools/battery_runtime_analysis.py promoted from battery-runtime-test (tool and derived knots merge together, ADR 0020), with a blind-floor anchor mode as default: T_cutoff = the row where the shutdown debounce would fire; --anchor power-death reproduces the old fit. - battery_fake: each sweep lap ends in an ADC-blind tail that trips the debounce, so -fh -fb exercises warnings and the shutdown warning end to end with no hardware. - i18n: new UI strings wrapped and hand-added to the four tracked .po catalogs (AI-translated, flagged for review), .mo recompiled. - Pure-function tests for the trigger, warner, blind decode, blind icon, and the new LUT; ADR 0021 status flipped to implemented; Battery CONTEXT.md glossary updated. Verified headless (-fh -fb, pifinder-remote): 10% and 5% popups fire once each per discharge, blind tail shows the empty icon, and the "Low battery / Shutting down" warning appears at the debounce point. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
brickbots
marked this pull request as ready for review
July 19, 2026 19:35
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.
Implements ADR 0021: when the BQ25895's ADC goes blind (~3.5 V) while on battery, PiFinder performs a clean, warned software shutdown instead of running blind into an SD-corrupting hard power cut. Advisory warnings at 10% and 5% state of charge precede it.
What's in here
Honest blindness. A raw-0 BATV read no longer decodes to a fake 2.304 V measurement:
BatteryStatepublishesNonefor every ADC-derived field (with anadc_blindproperty), so nothing can feed the artifact into the discharge curve. Status bits (charge phase, power source) are plain register reads and stay valid below the floor — they gate the trigger. The title-bar icon shows empty while blind.Debounced shutdown trigger.
LowBatteryShutdownTrigger(pure, unit-tested likeplan_charging_writes): 4 consecutive ADC-blind polls (~20 s) whileon_external_poweris false → the battery monitor putslow_battery_shutdownonui_queue. Single blind reads never trigger (conversions fail intermittently in the 3.50–3.55 V twilight); external power always inhibits and resets. The main loop shows the final warning and routes through the existing user-shutdown chokepoint (SHUTDOWN earcon delivery wait → GPIO14 latch, per ADR 0007/0008). Never SoC-triggered.Warnings at 10% / 5%.
LowBatteryWarner(pure): once per crossing, on battery only, most-severe-only on a fast drop through both, ±2-point hysteresis so 20 mV ADC quantisation can't re-fire it, and aNoneestimate (blind twilight) suppresses without re-arming. Warning = popup + console line + theLOW_BATTERYearcon (previously defined-but-unwired). The watcher runs on a monotonic clock so a backwards GPS time step can't silence it.Re-anchored SOC_LUT (hard dependency). The shipped curve read ~21% at the blind floor — 10%/5% could never fire on it. Replaced with the provisional fit from the 2026-07-17 bench campaign, 0% re-anchored to the blind-floor shutdown: all knots measured, none extrapolated. Marked provisional pending pinned-load confirmation runs.
Analysis tool promoted (
python/tools/battery_runtime_analysis.py, per ADR 0020 tool-and-knots-merge-together): now with a blind-floor anchor mode as default —T_cutoff= the row where the shutdown debounce would fire (same 4-poll count), sane rows only for voltage pairing;--anchor power-deathreproduces the old fit. The knot-refresh follow-up PR after the confirmation runs is a re-run of this tool plus a paste.Dev/test.
battery_fakenow ends each sweep lap in an ADC-blind tail that trips the debounce, so-fh -fbexercises warnings → blind icon → shutdown warning end to end. On the Fake hardware platform the main loop skips the actual OS power-off (the request can only come from the scripted fake there), so a lap can never power off a real Pi running-fhfor docs screenshots, or a dev box.Verification
-fh -fb, pifinder-remote skill): observed the 10% popup, the 5% popup, the blind-tail empty icon, and the "Low battery / Shutting down" final warning fire at the expected times, once per discharge lap.Notes for review
.pocatalogs gained the two new msgids with AI translations flaggedneeds human review(hand-added; no babel extract run per the repo's i18n practice). The English warning string is worded so%sits at end-of-string — babel 2.16 parses"% le"-style sequences as python-format placeholders and rejects the catalog otherwise.battery-runtime-test, run the pinned-load confirmation runs (they double as live acceptance tests and now end cleanly at the shutdown), then the knot-refresh PR.🤖 Generated with Claude Code