Readings that Home Assistant delivers late but still changing passed every staleness gate, so the loop corrected again each cycle an error its last command had already fixed (10 s late: the give run swung and exported to -1.9 kW), and the peak guard pushed to the inverter limit on the same late figures (OPT-34 F-2). app/lateness.py fits how long after a power request the meter shows it. Over 8 s the readings are late: a request the battery reading has not answered holds the loop (it may not lead the reading by more than 100 W) and caps the peak guard at what stands; unanswered for 23 s it holds, then 0 W after stale_input_s. An amber row and one log line per episode say so. With readings on time nothing changes (digests equal). DECISIONS/SAFETY-128-late-readings.md; test_safety128.py. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
4.8 KiB
SAFETY-128 - a reading that does not answer the last command is not current
Status: FIXED on release/1.0 (after 0.20.0). Code: app/lateness.py,
control.compute(hold=, hold_w=), peakguard.cap_floor(),
Controller._readings_late(). Tests: test_safety128.py
(tools/safety128_rig.py, TEST-23's plant).
Cause (SAFETY-127 report). Home Assistant can deliver readings late but still changing. Every gate judges arrival (SAFETY-01) or a value that stops changing (SAFETY-18, SAFETY-37), so a late reading passes all of them. The loop then corrects, each cycle, an error its last command has already fixed: a loop with a dead time far longer than its cycle, which swings and grows. The peak guard projects the quarter from the same late figures and pushes to the inverter limit in its last seconds (OPT-34 rig F-2).
Rule.
- Find late readings.
Delayfits, every 4 s over the last 40 s of new meter values, how long after a power request the meter shows it: the meter is the house less the batteries, someter(t) + request(t - d)is flattest at the rightd. OverDELAY_Sthe readings are late (late_seen). The fit counts only if the requests explain half the meter's swing; a latedonly if it fits 30 % better than any fresh one and better than a slow battery answering at once (10 or 20 s lag, the SAFETY-85/94 rigs). A silent meter adds no values (SAFETY-99/125). A battery that gives nothing under its ask, or refuses, spoils the fit and is left out for 40 s. With no fit for 120 s (a steady site shows nothing) the verdict is dropped. Nothing below acts unlesslate_seen, so with readings on time control is unchanged. - Hold. While
late_seen, a request more thanANSWER_Wfrom the battery reading is pending until that reading comes withinANSWER_Wof it, or half way. Meanwhile the loop may not lead the reading by more thanANSWER_Wthat way (compute(hold=, hold_w=); it may come back), and the peak guard's floor is capped at the request that stands (cap_floor()). The loop is not held while a battery gives nothing under its ask or refuses: SAFETY-52/94 need it to go on asking. - Late. Pending for
LATE_Swith no reading movingANSWER_Wtowards it is the late verdict: control holds, then afterstale_input_sgoes to 0 W ("readings late"), as for a missing input. It ends when a reading starts towards the request. - Seen. While
late_seenan amber row says "Home Assistant readings arrive about N seconds late, so control holds the batteries." (NL too), and "every inverter is at 0 W" past the hold. One WARNING (READINGS_LATE) and one action-log line per episode, and one when it ends.
Constants (app/lateness.py).
| Value | Why | |
|---|---|---|
ANSWER_W |
100 W | control.PROBE_ANSWER_W: a reading this far towards the ask has answered it (SAFETY-52's probe). |
DELAY_S |
8 s | freshness.PLANT_SETTLE_S 6 s + one LOOP_TICK_S 2 s: a fresh meter shows a request within the plant's settle (SAFETY-75's 6 s dead time) and one meter tick. The fit reads 0 s at 1/2/5 s meters, 5-7 s with a 6 s dead time, 9 s at 10 s late. |
LATE_S |
23 s | 2 x PLANT_SETTLE_S + PUBLISH_PERIOD_S 10 s + READ_PERIOD_S 1 s: SAFETY-75's 6 s dead time, the 6 s settle, one publish and one read, the slowest healthy battery answer. |
FIT_S, FIT_EVERY_S |
40 s, 4 s | Short enough to find 10 s and 30 s late within 50-60 s of the start (the swing has not grown yet); one 2 s cycle in two. |
MAX_DELAY_S |
90 s | F-2 measured 17-78 s. |
FIT_SHARE, FIT_BETTER |
0.5, 0.7 | Below these a battery that stops following (a refusal at N=2) was read as 23 s late. |
SLOW_TAUS_S |
10 s, 20 s | The SAFETY-85/94 lagging battery: without this check 16 of 36 of its reversals read as 11-56 s late. |
FIT_GONE_S |
120 s | The verdict outlives the lag by at most FIT_S + this when the site goes steady. |
What it costs.
- Late readings are found only after the requests have moved 100 W and the meter has shown it late: 48-68 s after the lag starts in the tests. Until then control is as before.
- The row can stay up to 160 s after the readings are on time again, while the site is steady.
- A battery that cannot follow while readings are late (at a limit, not reading 0 W) is held, then set to 0 W after 38 s, until it moves.
- The fit costs about 93 x 20 sums every 4 s.
- With readings late, a large step is followed in steps of the lag: the loop waits for each request to show before the next.
Left out. The round trip setpoint -> Home Assistant: the fitted delay gives
the same figure from readings the add-on already has. The arbitrage charge's
own sizing on late readings (charge_room_w()) is not capped here; the test
shows it swinging while the peak guard holds (see the pull request).