SAFETY-96 review: keep the refusal count over a refused write; test a fast board
F1: resetting the count on each refused write hid a battery that really refuses behind a link refusing 1 write in 3 (never learned; learned at 358 s with the count kept). F2: a board at 0 W before its link is stale learned no refusal untested (mutation R7 survived); now a 1 s-lag case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
88b9cd470a
commit
956711b801
@@ -35,7 +35,9 @@ take over one slew step a cycle. At a rejoin it enters at its reading.
|
|||||||
(`inputs_ok`), as before. A refused battery (SAFETY-73) stays in the sum:
|
(`inputs_ok`), as before. A refused battery (SAFETY-73) stays in the sum:
|
||||||
it is asked ~0 W and reads ~0 W. A watchdog-stopped battery (SAFETY-02/30)
|
it is asked ~0 W and reads ~0 W. A watchdog-stopped battery (SAFETY-02/30)
|
||||||
is pinned at 0 W and stays in the sum. New: no refusal is learned on a
|
is pinned at 0 W and stays in the sum. New: no refusal is learned on a
|
||||||
battery whose writes are refused; its board going to 0 W is no refusal.
|
battery whose writes are refused; its board going to 0 W is no refusal. Its
|
||||||
|
count is kept, not reset, so a link that refuses 1 write in 3 does not hide
|
||||||
|
a battery that really refuses (review).
|
||||||
|
|
||||||
**Before -> after** (2 x 2500 W, 10 s lag, 2 s meter; freeze cycles not
|
**Before -> after** (2 x 2500 W, 10 s lag, 2 s meter; freeze cycles not
|
||||||
about the remaining battery's own lag):
|
about the remaining battery's own lag):
|
||||||
|
|||||||
@@ -1402,7 +1402,8 @@ class Controller:
|
|||||||
if inv.rejected_since is not None:
|
if inv.rejected_since is not None:
|
||||||
# SAFETY-96: its writes are refused, so its board may have
|
# SAFETY-96: its writes are refused, so its board may have
|
||||||
# gone to 0 W on its own; that is no refusal by the battery.
|
# gone to 0 W on its own; that is no refusal by the battery.
|
||||||
inv.ref_count = 0
|
# The count is kept, as across a gap, so a link that refuses
|
||||||
|
# now and then does not hide a battery that does refuse.
|
||||||
continue
|
continue
|
||||||
sign = self._gave_nothing(inv)
|
sign = self._gave_nothing(inv)
|
||||||
if sign:
|
if sign:
|
||||||
|
|||||||
@@ -76,18 +76,22 @@ def without(fn):
|
|||||||
|
|
||||||
def run(n=2, *, house=lambda t: 2400.0, dur=600.0, store=None, opt=None,
|
def run(n=2, *, house=lambda t: 2400.0, dur=600.0, store=None, opt=None,
|
||||||
soc=None, reject=None, back=None, wd=None, before=None, lag=10.0,
|
soc=None, reject=None, back=None, wd=None, before=None, lag=10.0,
|
||||||
blind=None):
|
blind=None, flaky=None, dead=None):
|
||||||
"""Rows of one run. Writes to the units in `reject` (name -> from s) are
|
"""Rows of one run. Writes to the units in `reject` (name -> from s) are
|
||||||
refused from that time until `back` s; the add-on sees the refusal at
|
refused from that time until `back` s; the add-on sees the refusal at
|
||||||
once (a heartbeat write). A refused unit holds its last command, or with
|
once (a heartbeat write). A refused unit holds its last command, or with
|
||||||
`wd` its board goes to 0 W `wd` s after the first refused write. B's
|
`wd` its board goes to 0 W `wd` s after the first refused write. B's
|
||||||
reading is missing in the `blind` (from, to) window.
|
reading is missing in the `blind` (from, to) window. `flaky(u, t)`:
|
||||||
|
writes to u refused at t, each cycle's verdict seen (a heartbeat).
|
||||||
|
`dead` (u, from s): u gives nothing from then on, whatever it is told.
|
||||||
Each row: (t, plant commands, add-on commands, loop reason, site target,
|
Each row: (t, plant commands, add-on commands, loop reason, site target,
|
||||||
grid, circulation tripped)."""
|
grid, circulation tripped)."""
|
||||||
reject = reject or {}
|
reject = reject or {}
|
||||||
real_p, real_s, real_tick = rig._Hass.power, rig._Hass.set_number, rig.TICK
|
real_p, real_s, real_tick = rig._Hass.power, rig._Hass.set_number, rig.TICK
|
||||||
|
|
||||||
def refused(u, t):
|
def refused(u, t):
|
||||||
|
if flaky is not None:
|
||||||
|
return flaky(u, t)
|
||||||
return u in reject and t >= reject[u] and (back is None or t < back)
|
return u in reject and t >= reject[u] and (back is None or t < back)
|
||||||
|
|
||||||
def power(self, u):
|
def power(self, u):
|
||||||
@@ -98,6 +102,8 @@ def run(n=2, *, house=lambda t: 2400.0, dur=600.0, store=None, opt=None,
|
|||||||
c = self.cmd[u]
|
c = self.cmd[u]
|
||||||
if wd is not None and refused(u, now) and now - reject[u] >= wd:
|
if wd is not None and refused(u, now) and now - reject[u] >= wd:
|
||||||
c = 0.0
|
c = 0.0
|
||||||
|
if dead is not None and u == dead[0] and now >= dead[1]:
|
||||||
|
c = 0.0
|
||||||
d = c - tgt
|
d = c - tgt
|
||||||
tgt += math.copysign(min(abs(d), 80.0), d)
|
tgt += math.copysign(min(abs(d), 80.0), d)
|
||||||
a += (tgt - a) * (1 - math.exp(-0.1 / lag))
|
a += (tgt - a) * (1 - math.exp(-0.1 / lag))
|
||||||
@@ -114,7 +120,10 @@ def run(n=2, *, house=lambda t: 2400.0, dur=600.0, store=None, opt=None,
|
|||||||
|
|
||||||
def hook(c, t):
|
def hook(c, t):
|
||||||
for inv in c.inverters:
|
for inv in c.inverters:
|
||||||
if refused(inv.name[-1], t) and inv.rejected_since is None:
|
if flaky is not None:
|
||||||
|
inv.note_write(not flaky(inv.name[-1], t),
|
||||||
|
rig.BASE + timedelta(seconds=t))
|
||||||
|
elif refused(inv.name[-1], t) and inv.rejected_since is None:
|
||||||
inv.rejected_since = rig.BASE + timedelta(seconds=t)
|
inv.rejected_since = rig.BASE + timedelta(seconds=t)
|
||||||
if before is not None:
|
if before is not None:
|
||||||
before(c, t)
|
before(c, t)
|
||||||
@@ -323,6 +332,35 @@ for v, w in (STUCK, BOARD):
|
|||||||
>= 1000.0)
|
>= 1000.0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Review: a battery that does refuse, behind a link that refuses one write
|
||||||
|
# in three, is still found out (the count is kept over a refused write).
|
||||||
|
_fl_rows, _fl = run(dur=700.0, dead=("a", 300.0),
|
||||||
|
flaky=lambda u, t: u == "a" and t >= 300.0
|
||||||
|
and int(t / 2) % 3 == 0)
|
||||||
|
_fl_at = next((r[0] for r in _fl_rows if r[2][0] == 0.0 and r[0] > 300.0),
|
||||||
|
None)
|
||||||
|
_fl_end = first((r[2] for r in _fl_rows[-1:]), "the last row")
|
||||||
|
print(f" A gives nothing from 300 s, 1 write in 3 refused: A's share is "
|
||||||
|
f"taken off at {_fl_at} s; A {_fl.inverters[0].refused}, last "
|
||||||
|
f"commands {_fl_end}")
|
||||||
|
check("⭐ a battery that refuses behind a link refusing 1 write in 3: its "
|
||||||
|
"refusal is still learned and B carries the house",
|
||||||
|
_fl.inverters[0].refused == 1 and _fl_at is not None
|
||||||
|
and _fl_at <= 400.0 and present(_fl_end)
|
||||||
|
and abs(_fl_end[1] - 2410.0) <= 20.0)
|
||||||
|
|
||||||
|
# Review: a fast battery whose board goes to 0 W 1 s after the first refused
|
||||||
|
# write reads ~0 W long before its link is stale (30 s): no refusal either.
|
||||||
|
_ref5 = []
|
||||||
|
_e5_rows, _e5 = run(reject={"a": 300.0}, back=420.0, wd=1.0, lag=1.0,
|
||||||
|
dur=700.0,
|
||||||
|
before=lambda c, t: _ref5.append(c.inverters[0].refused))
|
||||||
|
check("fast battery, board 0 W 1 s after the first refused write: no "
|
||||||
|
"refusal is learned, A is back in the split within 4 s of its link "
|
||||||
|
"returning",
|
||||||
|
not any(_ref5) and rejoined(_e5_rows) <= 424.0)
|
||||||
|
|
||||||
_seen = []
|
_seen = []
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user