SAFETY-91 review: a held-back battery is released 1 point above its reserve
A SoC reading flipping across the reserve swapped ~1000 W between the batteries on each flip (grid +-500 W with unequal lags). Also grades next_quarter()'s staleness boundary (29.5 / 30.5 s). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -992,6 +992,8 @@ class Controller:
|
||||
# SAFETY-78: names of draining packs that cannot give power now, so
|
||||
# the "waits" line is logged once, not every cycle.
|
||||
self._drain_waits = set()
|
||||
# SAFETY-91: last cycle's held-back batteries (`_held_back()`).
|
||||
self._held_was = frozenset()
|
||||
# Set by amain() once the transport is built, so the status page can show
|
||||
# what only the transport knows (homewizard_local's unchanged_s). Stays
|
||||
# None when P1 is off, or under a transport that has no such counter.
|
||||
@@ -2571,18 +2573,26 @@ class Controller:
|
||||
"""SAFETY-91, N>=2: the batteries at or under their own peak reserve
|
||||
while another can still give (live, not refusing). They weigh 0 in
|
||||
every split where the site gives, so they give last (SAFETY-25
|
||||
option A: still drawn once the others are full)."""
|
||||
option A: still drawn once the others are full). One held back
|
||||
stays so until its SoC reads more than 1 point above its reserve, so
|
||||
a reading flipping across it does not swap the split each cycle."""
|
||||
if len(self.inverters) < 2:
|
||||
return frozenset()
|
||||
floor = float(self.o.get(
|
||||
"peak_soc_floor", peakguard.PEAK_OPTION_DEFAULTS["peak_soc_floor"]))
|
||||
ok, held = peakguard.reserve_split(self.packs, floor)
|
||||
held = set(held) | {
|
||||
p.name for p in ok if p.name in self._held_was
|
||||
and float(p.soc) <= peakguard.pack_floor_pct(p, floor) + 1.0}
|
||||
live = set(self.live_units())
|
||||
if not held or not any(
|
||||
p in ok and inv.name in live and not (inv.refused or 0) > 0
|
||||
p.name not in held and inv.name in live
|
||||
and not (inv.refused or 0) > 0
|
||||
for p, inv in zip(self.packs, self.inverters)):
|
||||
return frozenset()
|
||||
return frozenset(held)
|
||||
self._held_was = frozenset()
|
||||
else:
|
||||
self._held_was = frozenset(held)
|
||||
return self._held_was
|
||||
|
||||
def _drain_rise(self, claims, maint_results) -> None:
|
||||
"""SAFETY-91, N>=2: while a battery is held back (`_held_back()`), a
|
||||
|
||||
Reference in New Issue
Block a user