Skip to content

Commit 529e6f8

Browse files
mgazzaclaude
andcommitted
fix(gateway): detect coordinator staging before discharge ownership checks
A coordinator whose discharge end entity was not (yet) owned by the gateway was refused with an ownership error instead of keeping its staged separate-endpoint sequence. Identify coordinators first, as that decision only needs the entity names and discovery record. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BaJjLpuMEtQYfvmTRcFJf
1 parent 431bd77 commit 529e6f8

2 files changed

Lines changed: 13 additions & 2 deletions

File tree

‎apps/predbat/inverter.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2944,13 +2944,13 @@ def write_and_poll_gateway_discharge_window(self, new_start, new_end):
29442944
gateway = components.get_component("gateway") if components else None
29452945
names = ("discharge_start_time", "discharge_end_time")
29462946
target = (new_start, new_end)
2947+
if gateway is not None and gateway.discharge_window_uses_staging(*entities):
2948+
return None # Coordinator slot staging must precede its enable write.
29472949
if owners != ["gateway", "gateway"] or gateway is None:
29482950
self.base.record_status(f"Warn: Inverter {self.id} Hub discharge window has inconsistent entity ownership", had_errors=True)
29492951
return False
29502952
if not all(self.check_write_entity("write_and_poll_gateway_discharge_window", name, entity, value) for name, entity, value in zip(names, entities, target)):
29512953
return False
2952-
if gateway.discharge_window_uses_staging(*entities):
2953-
return None # Coordinator slot staging must precede its enable write.
29542954

29552955
def read_pair():
29562956
"""Read both endpoint entities without substituting desired values."""

‎apps/predbat/tests/test_gateway_discharge_window.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Joined engine discharge window, command serialization and ACK regressions."""
2+
23
# cspell:ignore excepthook
34

45
import asyncio
@@ -209,6 +210,16 @@ def test_other_provider_and_coordinator_keep_individual_sequence(self):
209210
self.assertEqual([json.loads(command["schedule_json"]) for command in wire if command["command"] == "set_discharge_slot"], [{"start": 255}, {"end": 301}])
210211
self.assertEqual(wire[-1]["command"], "set_discharge_enable")
211212

213+
def test_coordinator_with_partial_ownership_keeps_staged_sequence(self):
214+
"""A coordinator is identified before ownership checks, so it is never refused by them."""
215+
for kind in GATEWAY_COORDINATOR_TYPES:
216+
inv, gw, base, wire, mode = self.fixture()
217+
gw._discovery = {"CE123456789": {"type": kind}}
218+
base.dashboard_index_app[base.END] = None
219+
self.apply(inv)
220+
self.assertFalse(any("inconsistent entity ownership" in error for error in base.errors), kind)
221+
self.assertEqual([json.loads(command["schedule_json"]) for command in wire if command["command"] == "set_discharge_slot"], [{"start": 255}, {"end": 301}])
222+
212223
def test_disable_and_midnight_zero_window_remain_valid(self):
213224
"""A disable request never activates export and may program midnight endpoints."""
214225
inv, gw, base, wire, mode = self.fixture(enabled="on")

0 commit comments

Comments
 (0)