Files
goodwe-addon/goodwe_controller/DECISIONS/MAINT-15-hold-ends-without-soc.md
T
adminandClaude Opus 5.5 cd1bf2a694
checks / unit (pull_request) Successful in 35m22s
infra-pin / fresh (pull_request) Successful in 1s
MAINT-15: the balancing hold ends on time with no SoC reading
HOLD's time check moves into Maintenance._hold(), used by step() and
_no_soc(). A hold that loses its SoC now ends at hold_min, writes
last_completed and logs the same COMPLETE line. The 15 min HOLD line says
the hold still ends at its normal time. Behaviour with a reading is
unchanged (differential trace against db9e231).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 23:56:29 +02:00

2.0 KiB

MAINT-15 - the balancing hold ends on time with no SoC reading

Status: for review. Tests: test_maint15.py. No option added.

The fault. step() sent every tick with no SoC to _no_soc(), and for HOLD that returned 0 W without the time check. A hold that lost its reading never ended: last_completed was never written and the next due date never moved. Harmless for power (0 W), but the cycle was stuck. This was left open in MAINT-14.

Rule

  1. HOLD ends at hold_min whether or not there is a reading. One helper, Maintenance._hold(), holds the time check and is used by both paths. When the time is up it writes last_completed, goes IDLE and logs the same "cycle COMPLETE" line. Before the time is up it commands 0 W as before.
  2. Text. The 15-min "no battery level" line in HOLD now reads "The hold still ends at its normal time." Still once per episode.

Why

HOLD is reached only after the charge counted as done. Every entry into HOLD is in step() or _no_soc() and follows a charge that reached the target, stalled near full (MAINT-13) or timed out near full (MAINT-13/14). Nothing else sets the phase to HOLD (_enter(HOLD has four call sites, all after a done charge). The hold is a wait for the BMS to balance; it never needed the level. So a hold that ends with no reading is a completed cycle. The "cycle COMPLETE" line is true without a reading: it names minutes of hold, not a level.

Cost

  • If the level was lost during the hold, the cycle is recorded as complete without anyone seeing the final level. The charge had counted as done, so this is the same trust MAINT-14 already gives a charge that ends with no reading.

What this does not cover

  • It does not check that the pack is still full at the end of the hold.
  • It does not say why the reading was lost.
  • With a reading, nothing changes (proved by a differential trace against release/1.0 in test_maint15.py).
  • A state file written by an older version with phase: hold loads and ends on time; the file is not changed in format.