SAFETY-84 review: check the charging pack gives under the guard, and no-P1 at N=2
A 6500 W house needs more than B alone: A must give (the N>=2 suspension), with a non-vacuous run without it (banks over the cap, 'Load too high'). Without P1 the charge is not sized, as at N=1: A charges at 2000 W. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,8 +12,9 @@ DECISIONS/SAFETY-84-charge-at-two.md has the record.
|
||||
1. N=2 in a cheap window, house below, at and above the charge rate and a
|
||||
solar surplus: the charge runs, no opposite split, no refusal, and it ends.
|
||||
2. The peak guard: the charge yields just enough to hold the aim (cap -
|
||||
margin, and a banked month peak - margin); over the aim the guard wins
|
||||
and nothing parks; no false 'Load too high'.
|
||||
margin, and a banked month peak - margin); over the aim the guard wins,
|
||||
the charging battery gives too, and nothing parks; no false 'Load too
|
||||
high'. Without P1 the charge is not sized, as at N=1.
|
||||
3. Outside a cheap window nothing changes.
|
||||
4. A safety stop parks both batteries without a refusal, and the charge
|
||||
resumes after it.
|
||||
@@ -197,6 +198,56 @@ check("non-vacuous: charging at the full rate instead, the guard has to "
|
||||
"step in and the batteries give back on many cycles",
|
||||
_gave > 50)
|
||||
|
||||
|
||||
def peak_run(house):
|
||||
rows, c = run(house, dur=1800.0)
|
||||
return rows, c, [r for r in rows if r[-1][1] and r[-1][1][0]]
|
||||
|
||||
|
||||
# A 6500 W house needs 3050 W off the meter: more than B's 2500 W alone.
|
||||
_p_rows, _p, _p_eng = peak_run(6500.0)
|
||||
print(f" house 6500: banked {[round(v) for v in banked(_p)]} W, A gives "
|
||||
f"on {sum(cmd(r)['a'] > 0.0 for r in _p_eng)} of {len(_p_eng)} "
|
||||
f"engaged cycles")
|
||||
check("⭐ house 6500 W, more than B alone can cover: the charge is suspended "
|
||||
"and A, the battery being filled, gives too; the quarter lands on the "
|
||||
"aim, nothing is refused, no 'Load too high'",
|
||||
len(_p_eng) > 100 and all(cmd(r)["a"] > 0.0 for r in _p_eng)
|
||||
and all(v <= AIM + TOL for v in banked(_p))
|
||||
and refused(_p_rows) == 0 and not alarmed(_p))
|
||||
_real = s84.m.peakguard.shaping_suspended
|
||||
s84.m.peakguard.shaping_suspended = lambda *a: False
|
||||
try:
|
||||
_ns_rows, _ns, _ns_eng = peak_run(6500.0)
|
||||
finally:
|
||||
s84.m.peakguard.shaping_suspended = _real
|
||||
check("non-vacuous: with the suspension taken out, A is held at 0 W, the "
|
||||
"quarter goes over the cap and 'Load too high' fires",
|
||||
not any(cmd(r)["a"] > 0.0 for r in _ns_eng)
|
||||
and max(banked(_ns)) > 3500.0 and alarmed(_ns))
|
||||
|
||||
|
||||
def prices_only(c, t):
|
||||
"""A cheap price and no P1 telegram (`meter_source: off`)."""
|
||||
if not getattr(c.hass, "_px", False):
|
||||
read, px = c.hass.number, {"sensor.pnow": 0.05, "sensor.pavg": 0.20}
|
||||
|
||||
async def number(e, invert=False):
|
||||
return px[e] if e in px else await read(e, invert)
|
||||
c.hass.number, c.hass._px = number, True
|
||||
|
||||
|
||||
_p1 = s84.s79.P1
|
||||
s84.s79.P1 = {"meter_source": "off", "peak_cap_w": 3500}
|
||||
try:
|
||||
_off_rows, _off = run(800.0, dur=600.0, before=prices_only)
|
||||
finally:
|
||||
s84.s79.P1 = _p1
|
||||
check("⭐ with no P1 meter nothing measures the quarter and the charge is not "
|
||||
"sized, as at N=1: A charges at 2000 W on every cycle, nothing refused",
|
||||
_off.peak_guard is None and all(cmd(r)["a"] == -2000.0 for r in _off_rows)
|
||||
and refused(_off_rows) == 0)
|
||||
|
||||
# ============================================================================ #
|
||||
print("\n3. outside a cheap window nothing changes")
|
||||
_d = {h: run(h, cheap=False, dur=600.0) for h in (800.0, -1500.0)}
|
||||
|
||||
Reference in New Issue
Block a user