MAINT-14: accurate no-level wording per phase; name the limit that ended the charge
checks / unit (pull_request) Successful in 34m54s
infra-pin / fresh (pull_request) Successful in 1s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
glenn schrooyen
2026-10-08 21:25:35 +02:00
co-authored by Claude Opus 5.5
parent f39e52efd3
commit ef35f4b925
3 changed files with 34 additions and 4 deletions
@@ -60,7 +60,8 @@ allows discharge.
- **HOLD with no SoC** still holds 0 W and, as before, does not end while the
reading is missing: its time check sits behind the same early return. A HOLD
needs no level to end, so this is a candidate follow-up, not part of this fix.
needs no level to end, so this is a candidate follow-up, not part of this fix. It is card MAINT-15. The "no battery level" line in
HOLD says only that the hold waits for the level.
- It does not tell the operator why the reading is missing (sensor, register,
link). It does not reuse the stale-input gate; it is the cycle's own rule.
- The charge itself is still driven by the control loop, which has its own
+9 -3
View File
@@ -563,8 +563,11 @@ class Maintenance:
if (not self._nosoc_said and (now - self._nosoc_since).total_seconds()
>= NOSOC_NOTE_MIN * 60):
self._nosoc_said = True
tail = ("The hold waits for the level to come back."
if phase == HOLD else
f"The time limit of the {phase} still applies.")
msg = (f"maintenance: no battery level for {NOSOC_NOTE_MIN} min. "
f"The time limit of the {phase} still applies.")
f"{tail}")
_LOG.warning("%s", msg)
events.append(msg)
if phase == HOLD:
@@ -587,9 +590,12 @@ class Maintenance:
f"charge counts as done -> holding "
f"{self.cfg.hold_min} min to balance")
return MaintResult(HOLD, True, 0.0, False, events)
if elapsed > CHARGE_WALL_TIMEOUT_MIN:
lim = f"the {CHARGE_WALL_TIMEOUT_MIN // 1440}-day limit"
else:
lim = f"{trk['open_min'] / 60:.0f} h of charging time"
return self._fail(now, "charge",
f"charge stuck, battery level unknown, after "
f"{trk['open_min'] / 60:.0f} h of charging time")
f"charge stuck, battery level unknown, after {lim}")
return MaintResult(CHARGE, False, None, True, events)
def abort(self, now: datetime, why: str) -> list:
+23
View File
@@ -240,6 +240,29 @@ m = in_charge()
res, _, out, ev = run(m, T0, 5, None)
check("a 5 min gap says nothing", ev == [])
print("5b. the words per phase")
m = in_charge()
m.tick(T0 + MIN, 100.0)
_, _, _, ev = run(m, T0 + 2 * MIN, 30, None)
note = [e for e in ev if "no battery level for" in e]
check("HOLD: the line exists and claims no time limit",
len(note) == 1 and "time limit" not in note[0] and "waits" in note[0])
m = in_charge()
_, _, _, ev = run(m, T0, 30, None)
check("charge keeps its wording", any("time limit of the charge" in e for e in ev))
m = in_charge()
run(m, T0, 10, 70.0)
_, _, out, ev = run(m, T0 + 10 * MIN, CHARGE_WALL_TIMEOUT_MIN + 30, None,
gate=False, step=5)
fail = [e for e in ev if "ABORT" in e]
check("7-day backstop: the reason names the 7-day limit, not '0 h'",
len(fail) == 1 and "7-day limit" in fail[0] and "0 h" not in fail[0])
m = in_charge()
_, _, out, ev = run(m, T0, CHARGE_TIMEOUT_MIN + 10, None)
fail = [e for e in ev if "ABORT" in e]
check("charge-time limit: the reason gives the hours of charging time",
len(fail) == 1 and "36 h of charging time" in fail[0])
print("6. restart while the SoC is missing")
with tempfile.TemporaryDirectory() as d:
path = os.path.join(d, "state.json")