SAFETY-87: peak guard uses every battery that can still give
At N>=2 a battery at or under its own peak reserve is held: it gives nothing for the peak (weight 0 while the site gives) and the guard's need is placed on the live, non-refusing batteries above theirs. The guard is floor-limited, and alarms, only when those cannot hold the limit at full power for ALARM_HOLD_S. The peak alarms no longer read "Not ready" or hold the Home page at 0 W, because control keeps running. N=1 is byte-identical. DECISIONS/SAFETY-87-guard-uses-able-batteries.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019DBUfvndgHwaV339Fvd2gr
This commit is contained in:
@@ -217,7 +217,7 @@ def peak_saturated(ceiling_w, *, shadow: bool = False) -> Notice:
|
||||
|
||||
|
||||
def peak_reserve(ceiling_w, *, soc_unread: bool, floor_unknown: bool,
|
||||
packs=()) -> Notice:
|
||||
packs=(), others: bool = False) -> Notice:
|
||||
"""SAFETY-24/25/32: the peak guard may not draw the battery any lower.
|
||||
|
||||
The last sentence is SAFETY-25 and must stay: the reserve binds the peak
|
||||
@@ -227,7 +227,8 @@ def peak_reserve(ceiling_w, *, soc_unread: bool, floor_unknown: bool,
|
||||
roster means some inverter's cut-out level is missing (the peak reserve
|
||||
itself always has a value) - so the action names the cut-out level.
|
||||
Both are named by their setting labels, "Peak reserve (%)" and "Battery
|
||||
cut-out level (%)".
|
||||
cut-out level (%)". `others`: the other batteries give all they can and
|
||||
it is not enough (SAFETY-87), so the limit MAY be passed.
|
||||
"""
|
||||
if soc_unread:
|
||||
why = "The battery level cannot be read"
|
||||
@@ -241,6 +242,12 @@ def peak_reserve(ceiling_w, *, soc_unread: bool, floor_unknown: bool,
|
||||
why = (f"The battery of {names(packs)} is at its peak reserve" if packs
|
||||
else "The battery is at its peak reserve")
|
||||
act = "Switch off large loads, or lower the peak reserve."
|
||||
if others:
|
||||
return Notice(ID_PEAK_RESERVE, ALARM, T_PEAK_RESERVE,
|
||||
f"{why}, and the other batteries cannot keep this "
|
||||
f"15-minute period under your {watts(ceiling_w)} peak "
|
||||
f"limit alone. {act} Home use can still draw a battery "
|
||||
f"below its reserve, down to its cut-out level.")
|
||||
return Notice(ID_PEAK_RESERVE, ALARM, T_PEAK_RESERVE,
|
||||
f"{why}, so this 15-minute period will go over your "
|
||||
f"{watts(ceiling_w)} peak limit and raise this month's peak. "
|
||||
@@ -470,8 +477,9 @@ def row_peak_saturated(ceiling_w) -> str:
|
||||
|
||||
|
||||
def row_peak_reserve(ceiling_w, *, soc_unread: bool, floor_unknown: bool,
|
||||
packs=()) -> str:
|
||||
"""SAFETY-24/25/32. The second sentence is SAFETY-25 and must stay."""
|
||||
packs=(), others: bool = False) -> str:
|
||||
"""SAFETY-24/25/32. The last sentence is SAFETY-25 and must stay.
|
||||
`others`: as in `peak_reserve()` (SAFETY-87)."""
|
||||
if soc_unread:
|
||||
why = "Battery level cannot be read"
|
||||
elif floor_unknown:
|
||||
@@ -479,6 +487,10 @@ def row_peak_reserve(ceiling_w, *, soc_unread: bool, floor_unknown: bool,
|
||||
else:
|
||||
why = (f"{names(packs)} battery at its peak reserve" if packs
|
||||
else "Battery at its peak reserve")
|
||||
if others:
|
||||
return (f"{why}. The others cannot hold the {watts(ceiling_w)} peak "
|
||||
f"limit alone. Home use still draws it lower, to the cut-out "
|
||||
f"level.")
|
||||
return (f"{why}, so the {watts(ceiling_w)} peak limit will be passed. Home "
|
||||
f"use still draws the battery below it, down to the cut-out level.")
|
||||
|
||||
@@ -1014,6 +1026,9 @@ def status_samples() -> list:
|
||||
("row_peak_guard_off", AMBER, row_peak_guard_off()),
|
||||
("row_peak_reserve", RED, row_peak_reserve(
|
||||
5000, soc_unread=False, floor_unknown=False, packs=["inverter-b"])),
|
||||
("row_peak_reserve", RED, row_peak_reserve(
|
||||
5000, soc_unread=False, floor_unknown=False, packs=["inverter-b"],
|
||||
others=True)),
|
||||
("row_peak_reserve", RED, row_peak_reserve(
|
||||
5000, soc_unread=True, floor_unknown=False)),
|
||||
("row_peak_reserve", RED, row_peak_reserve(
|
||||
@@ -1116,6 +1131,8 @@ def samples() -> list:
|
||||
peak_reserve(5000, soc_unread=False, floor_unknown=False),
|
||||
peak_reserve(5000, soc_unread=False, floor_unknown=False,
|
||||
packs=["inverter-b"]),
|
||||
peak_reserve(5000, soc_unread=False, floor_unknown=False,
|
||||
packs=["inverter-b"], others=True),
|
||||
peak_reserve(5000, soc_unread=True, floor_unknown=False),
|
||||
peak_reserve(5000, soc_unread=False, floor_unknown=True),
|
||||
circulating(["inverter-a"], ["inverter-b"]),
|
||||
|
||||
Reference in New Issue
Block a user