SAFETY-128: control does not act on late Home Assistant readings
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
This commit is contained in:
co-authored by
Claude
parent
77fe31b161
commit
09e1fbb898
@@ -0,0 +1,73 @@
|
||||
# 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.**
|
||||
1. *Find late readings.* `Delay` fits, 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, so `meter(t) + request(t - d)` is flattest
|
||||
at the right `d`. Over `DELAY_S` the readings are late (`late_seen`). The
|
||||
fit counts only if the requests explain half the meter's swing; a late `d`
|
||||
only 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 unless `late_seen`, so with readings on time control is
|
||||
unchanged.
|
||||
2. *Hold.* While `late_seen`, a request more than `ANSWER_W` from the battery
|
||||
reading is pending until that reading comes within `ANSWER_W` of it, or half
|
||||
way. Meanwhile the loop may not lead the reading by more than `ANSWER_W`
|
||||
that 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.
|
||||
3. *Late.* Pending for `LATE_S` with no reading moving `ANSWER_W` towards it is
|
||||
the late verdict: control holds, then after `stale_input_s` goes to 0 W
|
||||
("readings late"), as for a missing input. It ends when a reading starts
|
||||
towards the request.
|
||||
4. *Seen.* While `late_seen` an 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).
|
||||
Reference in New Issue
Block a user