Skip to content

Commit 00a2689

Browse files
fix(inverter): skip the discharge target write on AC Coupled GivTCP inverters (#4517)
AC Coupled inverters don't have a working Discharge_Target_SOC_1 register - GivTCP still reports each write as successful, but it never persists between cycles, so adjust_force_export() saw a permanent mismatch and rewrote it every single cycle indefinitely. Confirmed against GivEnergy's own firmware archive (github.com/DJBenson/giv-firmware): "AC Coupled" is a single product line with exactly two firmware releases ever published, not an early/late generational split the way the Hybrid line has Gen1/2/3 - so there's no newer AC-coupled variant this would need to keep working for. Skip the write outright when raw.invertor.model reports "Ac", rather than attempting one already known to be doomed. Supersedes the earlier enable-before-write approach on this same issue - that theory relied on a correlation with a second reporter's data that turned out to be from before #4492/#4518 existed and was confounded by the exact race condition those fixes address, so it was never solid evidence that AC Coupled's register works anywhere. Dropped rather than kept alongside this fix, since it has no remaining supporting evidence. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 7ae4318 commit 00a2689

2 files changed

Lines changed: 83 additions & 6 deletions

File tree

‎apps/predbat/inverter.py‎

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2523,13 +2523,24 @@ def adjust_force_export(self, force_export, new_start_time=None, new_end_time=No
25232523
if force_export:
25242524
target_soc = int(self.reserve_percent)
25252525
if self.rest_data and self.rest_v3:
2526-
current = self.rest_readDischargeTarget()
2527-
if current is None:
2528-
self.log("Inverter {} No current discharge target to read, export target not written".format(self.id))
2529-
elif current != target_soc:
2530-
self.rest_setDischargeTarget(target_soc)
2526+
# AC Coupled inverters don't have a working Discharge_Target_SOC_1 register - GivTCP
2527+
# still reports a write as successful, but it never persists between cycles, so the
2528+
# caller sees a permanent mismatch and rewrites indefinitely (#4517). Confirmed
2529+
# against GivEnergy's own firmware archive (github.com/DJBenson/giv-firmware): "AC
2530+
# Coupled" is a single product line with exactly two firmware releases ever published
2531+
# (D212-A212, A214-D214) - not an early/late generational split the way Hybrid has
2532+
# Gen1/2/3 - so there's no newer AC-coupled variant this would need to keep working for.
2533+
inverter_model = self.rest_data.get("raw", {}).get("invertor", {}).get("model", "")
2534+
if inverter_model == "Ac":
2535+
self.log("Inverter {} is AC Coupled, discharge target register not supported, export target not written".format(self.id))
25312536
else:
2532-
self.log("Inverter {} Current discharge target is already set to {}".format(self.id, current))
2537+
current = self.rest_readDischargeTarget()
2538+
if current is None:
2539+
self.log("Inverter {} No current discharge target to read, export target not written".format(self.id))
2540+
elif current != target_soc:
2541+
self.rest_setDischargeTarget(target_soc)
2542+
else:
2543+
self.log("Inverter {} Current discharge target is already set to {}".format(self.id, current))
25332544
elif "discharge_target_soc" in self.base.args:
25342545
current = self.base.get_arg("discharge_target_soc", index=self.id, required_unit="%")
25352546
try:

‎apps/predbat/tests/test_inverter.py‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1927,6 +1927,66 @@ def test_discharge_target_control_signal(test_name, ha, inv, dummy_rest):
19271927
return failed
19281928

19291929

1930+
def test_discharge_target_skipped_for_ac_coupled(test_name, ha, inv, dummy_rest):
1931+
"""
1932+
Regression test for issue #4517: AC Coupled inverters (raw.invertor.model == "Ac") don't have a
1933+
working Discharge_Target_SOC_1 register - GivTCP reports a write as successful, but it never
1934+
persists between cycles, so the caller sees a permanent mismatch and rewrites indefinitely.
1935+
Confirmed against GivEnergy's own firmware archive: "AC Coupled" is a single product line with
1936+
only two firmware releases ever published, not an early/late generational split - so there's no
1937+
newer AC-coupled variant that would need this write to still happen. Skip it outright rather than
1938+
attempting a write already known to be doomed.
1939+
"""
1940+
failed = False
1941+
print("Test: {}".format(test_name))
1942+
1943+
saved_rest_data = inv.rest_data
1944+
saved_rest_api = inv.rest_api
1945+
saved_rest_v3 = inv.rest_v3
1946+
1947+
try:
1948+
inv.rest_api = "dummy"
1949+
inv.rest_v3 = True
1950+
inv.reserve_percent = 20
1951+
1952+
start_time = "03:33:00"
1953+
end_time = "04:44:00"
1954+
ts = datetime.strptime(start_time, "%H:%M:%S")
1955+
te = datetime.strptime(end_time, "%H:%M:%S")
1956+
1957+
inv.rest_data = {
1958+
"Control": {"Mode": "Timed Export", "Enable_Discharge_Schedule": "on"},
1959+
"Timeslots": {"Discharge_start_time_slot_1": start_time, "Discharge_end_time_slot_1": end_time},
1960+
"raw": {"invertor": {"discharge_target_soc_1": "4", "model": "Ac"}},
1961+
}
1962+
dummy_rest.clear_queue()
1963+
dummy_rest.rest_data = copy.deepcopy(inv.rest_data)
1964+
1965+
inv.adjust_force_export(True, ts, te)
1966+
1967+
commands = dummy_rest.get_commands()
1968+
if commands:
1969+
print("ERROR: {}: AC Coupled should attempt no discharge-target REST commands at all, got {}".format(test_name, commands))
1970+
failed = True
1971+
1972+
# A non-AC-Coupled model (or no model reported at all) must still attempt the write as before.
1973+
for model in ["Hybrid", ""]:
1974+
inv.rest_data["raw"]["invertor"]["model"] = model
1975+
dummy_rest.clear_queue()
1976+
dummy_rest.rest_data = copy.deepcopy(inv.rest_data)
1977+
inv.adjust_force_export(True, ts, te)
1978+
commands = dummy_rest.get_commands()
1979+
if not any(c[0] == "dummy/setDischargeTarget" for c in commands):
1980+
print("ERROR: {}: model={!r} should still attempt setDischargeTarget, got {}".format(test_name, model, commands))
1981+
failed = True
1982+
finally:
1983+
inv.rest_data = saved_rest_data
1984+
inv.rest_api = saved_rest_api
1985+
inv.rest_v3 = saved_rest_v3
1986+
1987+
return failed
1988+
1989+
19301990
def test_discharge_target_read_prefers_control(test_name, ha, inv):
19311991
"""
19321992
Regression test for issue #4517: a discharge target write kept firing every cycle even when
@@ -3173,6 +3233,12 @@ def run_inverter_tests(my_predbat_dummy):
31733233
if failed:
31743234
return failed
31753235

3236+
# Regression test for issue #4517 (follow-up): AC Coupled inverters don't have a working
3237+
# discharge target register, skip the write entirely rather than retrying it forever
3238+
failed |= test_discharge_target_skipped_for_ac_coupled("discharge_target_skipped_for_ac_coupled", ha, inv, dummy_rest)
3239+
if failed:
3240+
return failed
3241+
31763242
# Regression test for issue #4517: the caller deciding whether to write at all must also prefer
31773243
# Control.Discharge_Target_SOC_1 over the slow raw.invertor fallback, or it re-writes every cycle
31783244
failed |= test_discharge_target_read_prefers_control("discharge_target_read_prefers_control", ha, inv)

0 commit comments

Comments
 (0)