diff --git a/goodwe_controller/test_opt34.py b/goodwe_controller/test_opt34.py index 7473000..a81544e 100644 --- a/goodwe_controller/test_opt34.py +++ b/goodwe_controller/test_opt34.py @@ -268,6 +268,7 @@ PROFILE_LATE = ('{"periods": [' CHOICE = {"a": (True, "leftover_solar", False), "b": (True, "cheap_hours", False)} LAST = at(3, 17, 5) +MIDNIGHT = at(4, 0, 0) EVENING = [at(3, 16, 0) + timedelta(minutes=30 * i) for i in range(12)] \ + [at(3, 21, 55)] @@ -290,7 +291,12 @@ def sensors(*, silent=True, fresh_from=None, fresh=(1.0, 5.0), when = now if fresh_until is None else min(now, fresh_until) return st(fresh[0] if ent == T_ENT else fresh[1], when) when = min(now, LAST) if silent else now - return st(0.4 if ent == T_ENT else 6.0, when) + # OPT-36: a fresh sensor reads the day it is on. After midnight "today" + # is the 4th (6.0 kWh, the figure the 17:05 reading called tomorrow's), + # not the evening's 0.4 kWh left of the 3rd; with that, fresh and + # kept are the same figure all night (before, the plans only agreed + # because A was capped at the ceiling in both). + return st(0.4 if ent == T_ENT and now < MIDNIGHT else 6.0, when) return f @@ -509,12 +515,14 @@ for k in R: fa, si, rr = R["fresh"], R["silent"], R["restart"] # a. silent from 17:05 -check("a fresh figure all night: A ends high (OPT-36: the ceiling, 87 % " - "before B was counted), B full", fa["soc"][0] > 80.0 and fa["soc"][1] > 94.5) +# OPT-36: A's level is 95 % to ~05:00, then 78.4 % when B's cheap run is longer +# (B's give 5.9 -> 7.1 kWh); A, already at 81.5 %, stops there. +check("a fresh figure all night: A ends at 81.5 % (the plan's 78.4 % came " + "after it had passed), B full", + abs(fa["soc"][0] - 81.5) < 0.5 and fa["soc"][1] > 94.5) check("sensors silent from 17:05: A ends where the fresh run ends it " - f"({si['soc'][0]:.1f} against {fa['soc'][0]:.1f} %; OPT-36: the " - "plans are equal at 04:30 below, the path to them is not)", - abs(si["soc"][0] - fa["soc"][0]) < 15.0 and si["soc"][0] > 75.0) + f"({si['soc'][0]:.1f} against {fa['soc'][0]:.1f} %)", + abs(si["soc"][0] - fa["soc"][0]) < 0.3) check("... and so does B (Cheap hours; the wide no-forecast gap does not " f"stop its slices): {si['soc'][1]:.1f} against {fa['soc'][1]:.1f} %", abs(si["soc"][1] - fa["soc"][1]) < 0.3) @@ -530,7 +538,7 @@ check("01:00 and 04:30: the kept figure is in use, with a day plan, and the " and m4["target_a"] is not None and near(m4["target_a"], fa["marks"]["04:30"]["target_a"], 1e-9)) check("... and the level is the fresh run's (the kept figure counts as a " - "fresh one)", m4["target_a"] > 50.0) + "fresh one)", near(m4["target_a"], 95.0, 0.2)) m6 = si["marks"]["06:30"] check("06:30: the kept figure has ended (the bell started) and the screen " "does not say 'kept' (the charge was done by then in this shape)", @@ -653,15 +661,19 @@ md = fd["marks"] print(f" f1_dark: A level 01:55 {md['01:55']['target_a']} -> 02:15 " f"{md['02:15']['target_a']}; A {fd['soc_at']['06:55'][0]:.1f} % " "at 06:55") +# OPT-36: the level (82.3 % at 02:15) falls to 64.2 % by 04:00 as B's cheap run +# is made longer (B's expected give grows); A stops at its SoC of that moment +# (72.8 %) and the latch holds 64.2 % after the figure ends. check("F-1 darker fresh figure at 02:00: the level rises, is held after " - "05:00 with no forecast (never 'read 17:05'), and A charges to it", + "05:00 with no forecast (never 'read 17:05'), and A charges while it is " + "above its level", md["02:15"]["target_a"] is not None and md["01:55"]["target_a"] is not None and md["02:15"]["target_a"] > md["01:55"]["target_a"] + 3.0 and all("17:05" not in md[h]["line"] for h in md if h >= "02:15") and all(md[h]["fc_none"] for h in md if "05:15" <= h < "07:00") - and fd["soc_at"]["06:55"][0] > fd["soc_at"]["02:15"][0] + 8.0 - and fd["soc_at"]["06:55"][0] < md["02:15"]["target_a"] + 1.5) + and near(md["05:15"]["target_a"], 64.24, 0.2) + and near(fd["soc_at"]["06:55"][0], 72.8, 0.5)) tb = R["tail_bfull"] check("B full: the run ends at 06:33 before A is at its level, so the next " diff --git a/goodwe_controller/test_ux54.py b/goodwe_controller/test_ux54.py index 5bf57d0..8838b4b 100644 --- a/goodwe_controller/test_ux54.py +++ b/goodwe_controller/test_ux54.py @@ -225,9 +225,7 @@ check("Leftover solar and Cheap hours: a give block exactly on the give run " # OPT-36: a larger evening (4.8 kW) so that B (Cheap hours) does not cover the # dear hours alone and A still buys from the grid; A starts at 30 %. c_n = S.make("dynamic", at(0, 18), soc=(30.0, 40.0), evening=4800.0) -RUN_END = c_n.mode_runs.cheap.end -END_S = RUN_END.astimezone(S.brussels(RUN_END).tzinfo).strftime("%H:%M") -END_H = S.brussels(RUN_END).hour + S.brussels(RUN_END).minute / 60.0 +END_S, END_H = "03:00", 3.0 # the run's end, recomputed for this house nb = tab(c_n, at(0, 18))["days"][0]["batteries"] check("in the night before the cheap run both charge from the grid in it " f"(01:00 to {END_S}), then give at 17:00", @@ -251,11 +249,11 @@ def at_h(b, h): return dict((round(x, 3), v) for x, v in b["levels"]).get(h, float("nan")) -check("the curve charges in a grid block: A (planned level 64 %) and B " +check("the curve charges in a grid block: A (planned level 73 %) and B " "(Cheap hours, to the end of its run) are well above where they " "were at 01:00, and stay put after the run", all(at_h(b, END_H) - at_h(b, 1.0) > 8.0 - and abs(at_h(b, END_H + 1.0) - at_h(b, END_H)) < 1e-6 + and abs(at_h(b, 4.0) - at_h(b, END_H)) < 1e-6 for b in nb) and nb[0]["levels"][0][1] == 30.0) # Modes off: the verdict's legs. c_f = S.make("off", at(10, 7))