Skip to content

Commit 18ca40b

Browse files
committed
fix(inverter): hold software-controlled charging after target overshoot
1 parent 1faff22 commit 18ca40b

2 files changed

Lines changed: 74 additions & 1 deletion

File tree

‎apps/predbat/inverter.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3426,7 +3426,11 @@ def adjust_charge_immediate(self, target_soc, freeze=False):
34263426
if not self.call_service_template("charge_freeze_service", service_data, domain="charge", extra_data=extra_data):
34273427
self.call_service_template("charge_start_service", service_data, domain="charge", extra_data=extra_data)
34283428
elif not self.inv_has_target_soc and target_soc < self.soc_percent:
3429-
self.call_service_template("charge_stop_service", service_data_stop, domain="charge")
3429+
# Software-controlled charging can overshoot between updates. Hold with the
3430+
# configured freeze service, as at equality, instead of resuming house discharge.
3431+
# Without a freeze service, preserve the existing stop fallback.
3432+
if not self.call_service_template("charge_freeze_service", service_data, domain="charge", extra_data=extra_data):
3433+
self.call_service_template("charge_stop_service", service_data_stop, domain="charge")
34303434
else:
34313435
self.call_service_template("charge_start_service", service_data, domain="charge", extra_data=extra_data)
34323436
else:

‎apps/predbat/tests/test_inverter.py‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1906,6 +1906,71 @@ def adjust_charge_immediate(self, target_soc, freeze=False)
19061906
return failed
19071907

19081908

1909+
def test_charge_target_overshoot_hold(my_predbat, ha, inv, dummy_items):
1910+
"""An overshot software charge target should hold, preserving native targets and stop fallbacks."""
1911+
failed = False
1912+
original_args = dict(my_predbat.args)
1913+
original_soc = inv.soc_percent
1914+
original_native_target = inv.inv_has_target_soc
1915+
original_solis_control = inv.inv_has_solis_energy_control
1916+
original_rate = dummy_items["number.charge_rate"]
1917+
cases = [
1918+
("software_overshoot", False, 20, False, True, "charge_freeze"),
1919+
("software_overshoot_without_freeze", False, 20, False, False, "charge_stop"),
1920+
("native_overshoot", True, 20, False, True, "charge_start"),
1921+
("native_overshoot_without_freeze", True, 20, False, False, "charge_start"),
1922+
("software_at_target", False, 22, False, True, "charge_freeze"),
1923+
("software_at_target_without_freeze", False, 22, False, False, "charge_start"),
1924+
("software_below_target", False, 25, False, True, "charge_start"),
1925+
("explicit_freeze", False, 20, True, True, "charge_freeze"),
1926+
("explicit_freeze_without_service", False, 20, True, False, "charge_stop"),
1927+
("charge_ended", False, 0, False, True, "charge_stop"),
1928+
]
1929+
try:
1930+
my_predbat.args.update({"charge_start_service": "charge_start", "charge_stop_service": "charge_stop", "discharge_stop_service": "discharge_stop", "charge_rate": "number.charge_rate", "device_id": "DID0"})
1931+
my_predbat.args.pop("charge_rate_percent", None)
1932+
dummy_items["number.charge_rate"] = 1101
1933+
inv.soc_percent = 22
1934+
inv.inv_has_solis_energy_control = False
1935+
ha.service_store_enable = True
1936+
for name, native_target, target, freeze, has_freeze, expected_service in cases:
1937+
print("**** Running Test: charge_target_{} ****".format(name))
1938+
inv.inv_has_target_soc = native_target
1939+
my_predbat.args["charge_freeze_service"] = "charge_freeze" if has_freeze else None
1940+
my_predbat.last_service_hash.pop("charge", None)
1941+
my_predbat.last_service_hash.pop("discharge", None)
1942+
ha.service_store = []
1943+
inv.adjust_charge_immediate(target, freeze=freeze)
1944+
expected = []
1945+
if target > 0:
1946+
expected.append(["discharge_stop", {"device_id": "DID0"}])
1947+
data = {"device_id": "DID0"}
1948+
if expected_service != "charge_stop":
1949+
data.update({"target_soc": target, "power": 1101})
1950+
expected.append([expected_service, data])
1951+
result = ha.get_service_store()
1952+
if result != expected:
1953+
print("ERROR: charge target {} expected {} got {}".format(name, expected, result))
1954+
failed = True
1955+
# Repeating the same hold must not issue another mode or power command.
1956+
ha.service_store = []
1957+
inv.adjust_charge_immediate(target, freeze=freeze)
1958+
result = ha.get_service_store()
1959+
if result:
1960+
print("ERROR: charge target {} repeated commands {}".format(name, result))
1961+
failed = True
1962+
finally:
1963+
my_predbat.args = original_args
1964+
inv.soc_percent = original_soc
1965+
inv.inv_has_target_soc = original_native_target
1966+
inv.inv_has_solis_energy_control = original_solis_control
1967+
dummy_items["number.charge_rate"] = original_rate
1968+
ha.service_store_enable = False
1969+
my_predbat.last_service_hash.pop("charge", None)
1970+
my_predbat.last_service_hash.pop("discharge", None)
1971+
return failed
1972+
1973+
19091974
def test_call_adjust_export_immediate(test_name, my_predbat, ha, inv, dummy_items, soc, repeat=False, freeze=False, clear=False, charge_stop=False, discharge_start_time="00:00:00", discharge_end_time="23:55:00", no_freeze=False):
19101975
"""
19111976
Tests;
@@ -5842,6 +5907,10 @@ def run_inverter_tests(my_predbat_dummy):
58425907
if failed:
58435908
return failed
58445909

5910+
failed |= test_charge_target_overshoot_hold(my_predbat, ha, inv, dummy_items)
5911+
if failed:
5912+
return failed
5913+
58455914
failed |= test_auto_restart(
58465915
"auto_restart0",
58475916
my_predbat,

0 commit comments

Comments
 (0)