diff --git a/goodwe_controller/test_safety87.py b/goodwe_controller/test_safety87.py index e410ba2..0f5ff80 100644 --- a/goodwe_controller/test_safety87.py +++ b/goodwe_controller/test_safety87.py @@ -33,6 +33,7 @@ import json import logging import os import sys +import types HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, HERE) @@ -393,6 +394,33 @@ check("...and at N=1 the floor-limited claim is unchanged: 0 W, 'SoC floor'", peakguard.claim(_lim).lo == 0.0 and peakguard.claim(_lim).reason == "SoC floor") + + +def three(socs, cuts): + """`_peak_on_able()` on a stub roster of three (SIMULATED packs).""" + names = ("x", "y", "z") + invs = [types.SimpleNamespace(name=n, refused=0, rating_w=lambda: 2500.0) + for n in names] + stub = types.SimpleNamespace( + inverters=invs, o={"peak_soc_floor": 20}, + packs=[peakguard.Pack(n, so, c) for n, so, c in zip(names, socs, cuts)], + live_units=lambda: names) + return Controller._peak_on_able(stub, _lim, [MaintResult( + IDLE, False, None, False, [])] * 3) + + +# x 25 % (cut-out 10 -> floor 20), y 40 % (cut-out 30), z 15 % (held). x and +# y each clear their own floor, but 25 % is not above y's 30 %: the pair is +# graded min SoC against max floor (SAFETY-32), so nothing is asked. +_t3 = three((25.0, 40.0, 15.0), (10, 30, 10)) +_t3ok = three((35.0, 40.0, 15.0), (10, 30, 10)) +check("⭐ three batteries, z held: the two others are graded as a pair, the " + "EMPTIEST of them (25 %) against the HIGHEST of their floors (30 %), " + "so nothing is asked; at 35 % they are asked for the need", + _t3.held == ("z",) and _t3.floor_limited and _t3.floor_w == 0.0 + and _t3ok.held == ("z",) and not _t3ok.floor_limited + and _t3ok.floor_w == 2000.0) + # ============================================================================ # print("\n6. the allocator: a held battery gives last") @@ -474,6 +502,13 @@ check("...the row names A and says the others cannot hold the limit alone, " len(_row) == 1 and _row[0]["text"].startswith( "inverter-a battery at its peak reserve. The others cannot hold " "the 3 500 W peak limit alone.")) +_c65._peak_floor_limited = False +_blip = [r for r in _c65.checks() if r.get("id") == "peak_guard"] +_c65._peak_floor_limited = True +check("...and while the alarm is not latched (a one-cycle miss) the row is " + "the ordinary amber guard row, not the red reserve row", + len(_blip) == 1 and _blip[0]["warn"] and not _blip[0].get("runs") + and "peak reserve" not in _blip[0]["text"]) _c65.auto = False _stopped = _c65.status_payload() _c65.auto = True