Rule 2 graded each battery against last cycle's command and graded charges too. On plantsim's battery (10 s lag) at the 2 s meter it warned at every drain or charge start and 15 times an hour under the peak guard at N=2; a charge tapering near full warned 11 times in 50 min (SAFETY-42 section 7). Now a discharge is graded against the least it stood at over SHORT_CYCLES / SHORT_SPAN_S. DOCS lists `mode-bound` under Loop reason. test_safety85 part 7: peak guard with a dead battery, lagging plant, taper; part 3: the bound ends with maintenance. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
92 lines
5.4 KiB
Markdown
92 lines
5.4 KiB
Markdown
# SAFETY-85 - no false "not keeping up" while another owner sets the battery
|
|
|
|
**Status: implemented 2026-10-01, for review.** Tests: `test_safety85.py`.
|
|
|
|
**The defect.** "Battery is not keeping up" (SAFETY-40, `goodwe_integrator_bound`)
|
|
fired about every 2.5 min during maintenance (drain, charge, HOLD) and under the
|
|
peak guard, while the battery gave exactly what it was told. Each warning
|
|
re-created the HA notification. Rig counts: 24 in an hour (SAFETY-83 F1), 43 in
|
|
7 episodes (SAFETY-86), 35 in 16 (SAFETY-87), 12 in 6 (SAFETY-84). The page
|
|
counter counted cycles (861), not episodes.
|
|
|
|
**Cause, measured on the real Controller.**
|
|
- N=1: when a higher claim decides, `cycle()` re-seeds the integrator from the
|
|
target each cycle, then `compute()` adds `gain * error` for a grid error the
|
|
loop does not own. Target + kick crosses the rail and sets `i_clamped`. The
|
|
carried integrator is not wound (SAFETY-48 caps the lead at one slew step),
|
|
so control comes back one slew step at a time.
|
|
- N>=2: a maintenance pin is a unit claim. The loop stays the site winner and
|
|
winds to its rail for watts the pinned battery may not give. When the pin
|
|
lifts, the site jumps: 2500 W in one cycle when a HOLD ends under a 4500 W
|
|
house, 1390 W when a charge pin lifts (`s84 n2_charge_dear`, t=1800 s).
|
|
|
|
**Decision.** Three rules; the alarm, its text and its id are unchanged.
|
|
1. **The loop's clamp counts only on a cycle the loop decided.** The winner is
|
|
the loop. Its number is on the wire, or only a rating (`device`,
|
|
`supervised`) cut it. No bound held it, and nothing was unmet.
|
|
(`Controller._loop_alarm_input`.)
|
|
2. **A battery another owner set to discharge alarms when it gives less than
|
|
it was told.** This covers every battery on a cycle the loop did not
|
|
decide, and a battery with its own pin otherwise. The test is more than
|
|
`saturation_w` short of the least it stood at over `SHORT_CYCLES` cycles
|
|
and `SHORT_SPAN_S` (the plant's measured settle, SAFETY-13/73), for
|
|
`saturation_cycles` cycles in a row (`clamp.delivery_short`). It covers a
|
|
real shortfall during maintenance or under the peak guard. Before, that
|
|
shortfall was silent unless the kick happened to reach the rail (house
|
|
1000 W, drain 2000 W, battery gives nothing: 0 warnings before, 4 in 10 min
|
|
now).
|
|
3. **At N>=2, while maintenance holds a battery, the loop is bounded** by
|
|
OPT-11's `loop_bound()` (what the batteries may place). It does not wind,
|
|
and control comes back one slew step a cycle. Why maintenance only: that is
|
|
where the jump was measured and where the owner order puts maintenance
|
|
above the loop. Other N>=2 limits (pack cut-out, SAFETY-73 refusal) keep
|
|
the old law. Rule 1 still silences them (the target is unmet), so they no
|
|
longer warn either.
|
|
|
|
Fixing the windup was chosen over only ignoring the wound part, because at
|
|
N>=2 the wound integrator moves real watts at hand-back. At N=1 there was
|
|
nothing to fix: re-seeding already prevents windup.
|
|
|
|
**What SAFETY-03/40 protect is kept.** With the loop in charge, the integrator
|
|
bound still warns (a runaway, a house above the rating), and a saturated
|
|
inverter under the loop's own command still does not alarm (SAFETY-03 AC 2):
|
|
rule 2 grades only batteries another owner set.
|
|
|
|
**The counter.** `ClampWatchdog.bound_episodes` counts episodes that had a
|
|
bound engagement. The status row shows it ("N times since start").
|
|
`bound` (cycles) stays for the log and the tests.
|
|
|
|
**Re-pinned digests.** Every change is intended; nothing at N=1 moves a watt.
|
|
|
|
| digest | what changed |
|
|
|---|---|
|
|
| s78 `n1_drain_owner` | the false clamp rows are gone from the log; commands identical |
|
|
| s78 `n2_drain`, s84 `n2_charge`, `n2_charge_banked`, s79 `n2_maint` (test_peak_margin) | loop reason (`mode-bound`), integrator and loop claim while bounded; commands identical |
|
|
| opt11 `n2_maint` (test_modes) | A drops out at t=1655 s: B follows the house at once, 8 cycles differ (max 920 W) |
|
|
| s84 `n2_charge_dear` | A's pin lifts at t=1800 s: 400 W a cycle, not a 1390 W jump; the surplus step after it; 98 cycles |
|
|
|
|
test_safety86's "without the ceiling the log floods" now needs the ceiling
|
|
and the bound both removed: either one alone keeps the loop inside what the
|
|
batteries can give. The `alarmed()` helpers in test_safety84/86 also read
|
|
SAFETY-81's quarter stamp, because ALLOCATOR rows can push its row out of
|
|
the 40-row log.
|
|
|
|
**Review (PR #163).** The first draft graded each battery against last
|
|
cycle's command and graded charges too. On plantsim's battery (10 s lag) at
|
|
the live 2 s meter, every drain or charge start warned once, and an N=2 drain
|
|
or HOLD under the peak guard with a stepping house warned 15 times an hour. A
|
|
maintenance charge tapering near full warned 11 times in 50 min, which
|
|
SAFETY-42 section 7 had ruled out. With the settle window and discharge only:
|
|
0 in all of those cases. A battery that gives nothing still warns, lagging
|
|
or not. At a hypothetical 20 s lag, it warns at most twice an hour. The cost:
|
|
a battery that does not take a charge another owner set is not named by this
|
|
alarm (the same gap SAFETY-42 states; the charge has its 36 h timeout).
|
|
`test_safety85.py` part 7.
|
|
|
|
**Not changed, open for the owner.**
|
|
- With the loop in charge and the house above the batteries' total rating,
|
|
it still warns (SAFETY-40). On the rig's 2 x 2000 W N=2, most of SAFETY-84's
|
|
count came from houses of 4500 and 6500 W.
|
|
- TEL-10's `Loop reason` reads `mode-bound` while the maintenance bound holds
|
|
the loop, with modes off. DOCS lists the value with both meanings.
|