Files
goodwe-addon/goodwe_controller/DECISIONS/SAFETY-119-bound-after-hand-back.md
T
glenn schrooyenandClaude 8bf25696cb
checks / unit (pull_request) Successful in 44m51s
infra-pin / fresh (pull_request) Successful in 1s
SAFETY-121: a recurring overload warns every quarter
TEST-23 F-1: on a 5 s meter a steady overload warned once in 70 min. The
loop was at its bound 4-5 cycles a quarter, all inside SAFETY-119's settle
of 12 cycles (60 s) and under its budget of 8 per 900 s. The simulation
fed the meter a cycle behind the battery reading, so the guard and the
charge chased each other and gave ~20 bound cycles a quarter.

The settle is now 4 cycles and 22 s (HANDBACK_CYCLES, SHORT_SPAN_S), and
the rolling budget one number, 11 per 900 s, at every cycle length. The
cycle length is no longer tracked. Commands unchanged; the re-pinned
digests of test_safety84/87 differ from bee7431 in OUTPUT_CLAMP rows only.
test_safety119 runs the rig's shape (meter in the same cycle) at 1-15 s
and checks a stop keeps the budget (review F2, X3).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-21 10:09:14 +02:00

5.6 KiB

SAFETY-119 - no "not keeping up" when the loop takes the wire back

Status: merged (bee7431); the settle and the budget changed in SAFETY-121 (DECISIONS/SAFETY-121-bound-every-quarter.md: 4 cycles and 22 s, one budget of 11). The rolling-budget sections below describe bee7431. Tests: test_safety119.py.

Cause. When another owner (the peak guard, a maintenance charge) has set the wire and the loop wins again, cycle() re-seeds the integrator from that owner's command, and compute() adds gain * error from a meter that does not show that command yet. On the TEST-23 rig (SAFETY-118 I-2) the guard stepped from -2000 W to +429 W with 5 s left. A cycle later the loop's ask crossed the +2500 W rail and beat the guard's floor, so SAFETY-85 rule 1 counted it as the loop's own bound and raised goodwe_integrator_bound. The battery gave what it was told, and the cap held.

Decision. After a hand-back, the loop's integrator bound is not counted until the plant's settle has passed (SHORT_CYCLES cycles and SHORT_SPAN_S, the window SAFETY-85 grades a battery on). After that it counts as before. An over-rating (clamped, a bug) on a cycle the loop decided always counts. No command changes, and no text was added or changed.

A rolling budget (round 3). The settle above is per hand-back, and losing the wire restarts it. Across all hand-backs the bound is hidden on at most a budget of cycles in any 15 minutes (ROLLING_BUDGET, ROLLING_S); a cycle over the budget counts. Round 1 restarted the settle at every hand-back, so a guard and a charge taking turns hid the bound for good (0 warnings in 2 h where 28 were raised before). Round 2 gave each run of hand-backs its own budget; at 10 s and 15 s meters each quarter then started a fresh one and the loop's bound was never counted. A quarter-edge kick is a few bound cycles in one quarter, a real bound that stays is many: a budget per 15 minutes tells them apart.

The budget follows the cycle length. 10 cycles up to a 3.5 s cycle, 8 up to 11 s, 6 up to 16 s, 4 beyond. The cycle length is the time between two decided cycles (a stop, or a gap over two minutes, is not one); until it is known, and without a monotonic clock, the budget is 8. Without a monotonic clock the budget never refills.

Why not one number. Measured over 2 h (goodwe-addon-checks:1), the quarter-edge case (house 800 W, cap 2500 W, no forecast, 30 seeds) and the case below. No single budget held both at 1 to 15 s: 3 to 6 let the false alarm back at 2 s; 7 on 10 of 30 seeds at 2 s; 8 on 1 of 30 (seed 25) at 2 s, and warned in only 1 to 3 of 7 quarters at 15 s; 9 and 10 miss quarters at 10 s. A slow cycle needs a small budget to warn, a fast one a large budget to stay quiet. Per band, the largest budget that still warns: up to 3.5 s, 10 (9 and 10 had no false alarm at 1 and 2 s; above 10 was not swept); up to 11 s, 8 (9 misses quarters at 10 s); up to 16 s, 6 (7 leaves two quiet quarters in a row at 15 s, 4 false-alarms at 12 s on 7 of 30 seeds); beyond, 4 (3 false-alarms at 20 and 30 s on 14 of 30, 5 leaves five quiet quarters at 20 s).

What holds. Quarter-edge case, 30 seeds each: 0 notifications at 1, 2, 3, 4, 5, 10, 12, 15, 20 and 30 s. Repeated hand-backs (2 x 2500 W, cheap-window charge, no usable forecast, cap 5000 W, house 5050 and 5100 W, constant and with noise, 8 runs per meter):

meter warnings in 2 h quarters warned (of 7 after the first) without the fix
1 s 56 to 63 7 56 to 63
2 s 49 to 56 7 54 to 56
5 s 19 to 23 7 28
10 s 7 to 10 7 14
15 s 4 to 10 4 to 7, never two quiet in a row 14
30 s 3 to 7 3 to 5, up to two quiet in a row 7 to 11

At 30 s no budget does better: 4 leaves two quiet quarters, 3 brings back the false alarm. Single hand-back, the loop at its rail, first warning after the hand-back compared with no fix: 10 s at 1 s, 20 s at 2 s, 40 s at 5 s (default), 80 s at 10 s, 90 s at 15 s, 80 s at 20 s, 120 s at 30 s, 240 s at 60 s. At 60 s the rig is a different regime (the house is not over the cap in the case above), so nothing more is claimed there. The cap held at 1 to 10 s in the quarter-edge case and at 1 to 5 s in the case above; at slower meters the meter's lag puts quarters over it, the same with and without the fix (commands are identical).

Limits. These runs have one cycle per meter reading, at a steady interval. A 20 or 30 s cycle needs cycle_floor_s raised; at the default 15 s, a slower meter adds cycles on an old reading. Measured that way (11 seeds): a 15 s meter that varies by up to 30 % false-alarmed on 5 seeds, a 30 s meter on 10 (0 to 4 in 2 h, 8 to 15 without the fix); a 20 s meter on none. The case above still warned every quarter there.

Kept. The loop in charge at its rail warns on the same cycle as before. The start and a restart after a stop are not hand-backs. The quarter-edge kick of test_safety119.py part 1 stays hidden (see above).

Re-pinned. test_safety84 (all six) and test_safety87 (n1_crossing, n1_above, n1_charge_low): only OUTPUT_CLAMP log rows differ; commands and claims are the same (test_safety119.py part 6).

Not hidden. A battery that gives less than another owner told it still alarms through SAFETY-85 rule 2. "Always counts" for an over-rating holds on cycles the loop decided; on others SAFETY-85 rule 1 already dropped it.

Not changed. The kick itself is control behaviour: for a few seconds at a quarter's edge the loop asks up to its rail. In test_safety119.py part 1 that is more discharge than the guard asked at a quarter's end, and a few seconds of up to 500 W more charge at the next quarter's start.