diff --git a/goodwe_controller/test_opt34.py b/goodwe_controller/test_opt34.py index 1d3b74f..516f55a 100644 --- a/goodwe_controller/test_opt34.py +++ b/goodwe_controller/test_opt34.py @@ -36,6 +36,8 @@ DECISIONS note and the dev report. and before a charge has started, up and down; e. the last hour, after the kept figure has ended at 06:00: the charge under way goes on to its level, also over a restart; the screens say so. +3. The text, EN and NL. +4. `_latch_charge()` alone: a battery that leaves the plan leaves the latch. """ import asyncio @@ -566,6 +568,63 @@ check("'kept for tonight' is unchanged (EN and NL)", and nl["home_words"]["solar"]["kept"] == "Zonneprognose van {at}, bewaard voor vannacht.") +# --------------------------------------------------------------------------- # +print("\n4. The latch alone: a battery that leaves the plan leaves the latch") +from types import SimpleNamespace # noqa: E402 +from app import modetrade, priceruns # noqa: E402 + +_tmp = tempfile.mkdtemp(prefix="o34-") +lc = Controller(dict(opts()), rp.Hass(("a", "b"), lambda t: 300.0, + {"a": 40.0, "b": 40.0}), + rp._Store(), rp._Pub(), stop=asyncio.Event(), + peak_db_path=os.path.join(_tmp, "tariff.db"), + price_history_path=os.path.join(_tmp, "ph.json")) +n0 = at(4, 1, 0) +lruns = SimpleNamespace(cheap=priceruns.Run(at(3, 22, 0), at(4, 7, 0), 0.26)) +charging = SimpleNamespace(action=modetrade.CHARGE, + roles={A: modetrade.CHARGE, B: modetrade.CHARGE}) +resting = SimpleNamespace(action=None, roles={A: modetrade.HOLD, + B: modetrade.HOLD}) + + +def latch_step(fc, tick, targets, now=n0): + lc.solar_fc, lc.mode_tick = fc, tick + lc._latch_charge(now, lruns, targets) + return lc._charge_latch, lc.store.data.get(m.CHARGE_LATCH_KEY) + + +FC = object() # a forecast is read (fresh or kept) +lt, saved = latch_step(FC, charging, {A: 80.0, B: 70.0}) +check("both batteries charge with a forecast: both are latched at their " + "levels, to the run's end + one slot, and saved", + lt is not None and lt[1] == {A: 80.0, B: 70.0} + and lt[0] == at(4, 7, 15) and saved is not None + and saved["levels"] == {A: 80.0, B: 70.0}) +lt, saved = latch_step(FC, charging, {B: 70.0}) +check("A leaves the plan (its mode or use changed): A is dropped from the " + "latch at once, B stays", lt is not None and lt[1] == {B: 70.0} + and saved["levels"] == {B: 70.0}) +lc.solar_fc = None +check("... and with the forecast then gone, A has no latched level (it is " + "not charged back to 80 %)", lc.plan_targets() == {B: 70.0}) +lt, saved = latch_step(FC, resting, {A: 80.0, B: 70.0}) +check("A back in the plan while nothing charges: it does not rejoin", + lt is not None and lt[1] == {B: 70.0}) +lt, saved = latch_step(FC, resting, {A: 80.0}) +check("B leaves the plan too: the latch is empty, so it is gone and " + "cleared from the store", lt is None and saved is None + and lc.plan_targets() is None) +latch_step(FC, charging, {A: 80.0}) +lc.solar_fc = None +ia = next(i for i in lc.inverters if i.name == A) +ia.soc = 50.0 +st_below = homeview.plan_state(lc) +ia.soc = 80.0 +st_at = homeview.plan_state(lc) +check("the screen says the charge goes on only while the latched battery is " + f"below its level ({st_below!r} at 50 %, {st_at!r} at 80 %)", + st_below == "latched" and st_at == "no_forecast") + print() if fails: print(f"{len(fails)} FAILED:")