Skip to content

Commit 6e620b2

Browse files
test(inverter): cover FoxCloud in the idle-cycle button press regression
test_force_export_off_does_not_press_every_cycle named the FoxCloud/TESLA/ Enphase/Deye/Sunsynk/AlphaESS cloud types in its docstring as the affected set but only parametrised GS_fb00 and GS, so the cloud behaviour rested on GS_fb00 happening to share has_discharge_enable_time rather than on a test. FoxCloud stands in for those types, and its expected press count is 0 rather than 1: being HH:MM:SS format it never takes the H M unconditional register rewrite, so the commit-once safety net gated on that format is skipped too and an idle cloud inverter should be silent, not spend one press on a first post-restart commit the way GS_fb00 does. Also adds a positive control - entering an export window straight after the idle cycles must still commit exactly once. Without it an inverter that had stopped pressing altogether would satisfy the idle assertion just as well, which is the failure mode that would actually matter to a Fox user. inv.sleep is stubbed for the loop: TestHAInterface applies a service call synchronously, so the write-and-poll read-back already sees the new value and the sleep between them is pure wall clock. discharge_target_soc and the idle time selects join saved_items, as the positive control writes them and this test shares my_predbat/ha with the rest of the run. Verified against two mutations of inverter.py, each reverted after: restoring the pre-fix None comparison (times_supplied = True) makes FoxCloud press on all 4 idle cycles, and disabling the press block trips the positive control. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent dabd591 commit 6e620b2

1 file changed

Lines changed: 29 additions & 2 deletions

File tree

‎apps/predbat/tests/test_inverter.py‎

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2588,6 +2588,13 @@ def test_force_export_off_does_not_press_every_cycle(test_name, ha, my_predbat):
25882588
inverter still reports a real time - and None never compares equal, so every cycle looked like a
25892589
change and pressed the button. That is most of the day, not just export windows.
25902590
2591+
FoxCloud stands in for the cloud types here. They reach the bug by the same route as GS_fb00 but
2592+
settle somewhere different: being HH:MM:SS format they never take the H M unconditional register
2593+
rewrite, so the commit-once safety net gated on that format is skipped too and an idle cloud
2594+
inverter should press zero times - not the one GS_fb00 spends on its first post-restart commit.
2595+
Pinning that separately keeps the cloud behaviour from resting on GS_fb00 happening to share
2596+
has_discharge_enable_time.
2597+
25912598
Plain GS takes the midnight-override path and is not affected by this None-comparison route, so it
25922599
is checked here too to pin the difference down. (GS was affected by a separate bug - #4711's
25932600
unconditional H M register rewrite, which GS also uses - but that is a different code path to the
@@ -2601,13 +2608,24 @@ def test_force_export_off_does_not_press_every_cycle(test_name, ha, my_predbat):
26012608
# later tests and making results order-dependent.
26022609
unset = object()
26032610
saved_args = {key: my_predbat.args.get(key, unset) for key in ("inverter_type", "discharge_start_time", "discharge_end_time", "scheduled_discharge_enable")}
2604-
saved_items = {key: ha.dummy_items.get(key, unset) for key in ("select.discharge_start_time", "select.discharge_end_time", "switch.scheduled_discharge_enable", "select.inverter_mode")}
2611+
saved_items = {
2612+
key: ha.dummy_items.get(key, unset)
2613+
for key in ("select.discharge_start_time", "select.discharge_end_time", "switch.scheduled_discharge_enable", "select.inverter_mode", "number.discharge_target_soc", "select.idle_start_time", "select.idle_end_time")
2614+
}
2615+
2616+
# Times for the positive control below - any window will do, it just has to differ from idle.
2617+
export_start = datetime.strptime("18:00:00", "%H:%M:%S")
2618+
export_end = datetime.strptime("19:00:00", "%H:%M:%S")
26052619

26062620
try:
2607-
for inverter_type, expected_presses in (("GS_fb00", 1), ("GS", 1)):
2621+
for inverter_type, expected_presses in (("GS_fb00", 1), ("GS", 1), ("FoxCloud", 0)):
26082622
my_predbat.args["inverter_type"] = [inverter_type]
26092623
inv = Inverter(my_predbat, 0, quiet=True)
26102624
inv.rest_data = None
2625+
# Only the press count matters here. TestHAInterface applies a service call synchronously,
2626+
# so the write-and-poll read-back already sees the new value and the sleep between them is
2627+
# pure wall clock - several seconds per register write, across three inverter types.
2628+
inv.sleep = lambda seconds: None
26112629

26122630
ha.dummy_items["select.discharge_start_time"] = "00:00:00"
26132631
ha.dummy_items["select.discharge_end_time"] = "00:00:00"
@@ -2628,6 +2646,15 @@ def test_force_export_off_does_not_press_every_cycle(test_name, ha, my_predbat):
26282646
if len(presses) > expected_presses:
26292647
print(f"ERROR: {test_name}: {inverter_type} pressed the button {len(presses)} times over 4 idle cycles, expected at most {expected_presses}")
26302648
failed = True
2649+
2650+
# Positive control: the suppression has to be specific to idle cycles. An inverter that had
2651+
# stopped committing altogether would satisfy the check above just as well, so entering an
2652+
# export window straight afterwards must still commit, exactly once.
2653+
idle_presses = len(presses)
2654+
inv.adjust_force_export(True, export_start, export_end)
2655+
if len(presses) != idle_presses + 1:
2656+
print(f"ERROR: {test_name}: {inverter_type} should press once on entering an export window, pressed {len(presses) - idle_presses} times")
2657+
failed = True
26312658
finally:
26322659
for key, value in saved_args.items():
26332660
if value is unset:

0 commit comments

Comments
 (0)