Skip to content

Commit 1089c4f

Browse files
fix(enphase): treat a pending schedule family as supported
The cloud reports a schedule family as scheduleStatus "pending" while a change settles on the gateway - the normal state straight after any write Predbat makes. Only "active"/"enabled"/"supported"/"available" counted as supported, so a site with a perfectly good charge-from-grid family was judged incapable of it, automatic_config raised and run() returned False: Warn: Automatic configuration skipped - Charge-from-grid (CFG) scheduling not supported on this site, cannot configure seen on a site whose cfg family held an active schedule and whose profile reported scheduleSupported true for both cfg and dtg. "pending" now counts as supported, and so does any family that actually holds a schedule, whatever the status string says. "not_supported" still reports unsupported. Also aligns the livestream fallback test with the settled-bucket selection that landed on main in #4430: the helper froze time one bucket too early, and load now falls back to the energy-balance residual rather than being left empty. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent a6d8db8 commit 1089c4f

2 files changed

Lines changed: 39 additions & 6 deletions

File tree

apps/predbat/enphase.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1354,10 +1354,14 @@ async def get_schedules(self, site_id):
13541354
entry = details[0] if details else {}
13551355
details = await self._prune_sibling_schedules(site_id, family_key, details, entry)
13561356
# "supported" gates whether Predbat can use this schedule family. Real accounts report
1357-
# a per-family scheduleStatus ("active" seen so far); treat the usable statuses as
1358-
# supported, with a fallback to the (unverified) boolean flags.
1357+
# a per-family scheduleStatus; "active" and "pending" have both been seen, and
1358+
# "not_supported" is how a genuinely unavailable family reports. "pending" only means a
1359+
# schedule change is still settling on the gateway - which is the normal state straight
1360+
# after any write Predbat makes - so it must not be read as unsupported, or Predbat
1361+
# decides mid-run that the site cannot charge from grid and abandons configuration.
1362+
# A family that actually holds a schedule is supported whatever the status says.
13591363
status_text = str(family_data.get("scheduleStatus", "")).strip().lower()
1360-
supported = status_text in ("active", "enabled", "supported", "available") or bool(family_data.get("scheduleSupported") or family_data.get("forceScheduleSupported"))
1364+
supported = status_text in ("active", "enabled", "supported", "available", "pending") or bool(details) or bool(family_data.get("scheduleSupported") or family_data.get("forceScheduleSupported"))
13611365
parsed[family_key] = {
13621366
"id": entry.get("scheduleId") or entry.get("id"),
13631367
"startTime": entry.get("startTime"),

apps/predbat/tests/test_enphase_api.py

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -692,8 +692,8 @@ def bucket(value):
692692
class _Fixed(original):
693693
@classmethod
694694
def now(cls, tz=None):
695-
"""Return a time inside the interval after the bucket that publish_data reads."""
696-
return original.fromtimestamp(start + int(81.5 * 900), tz)
695+
"""Return a time inside index 82, so the settled bucket publish_data reads is 80."""
696+
return original.fromtimestamp(start + int(82.5 * 900), tz)
697697

698698
enphase_module.datetime = _Fixed
699699
try:
@@ -717,12 +717,18 @@ def test_publish_prefers_the_measured_livestream_reading():
717717

718718

719719
def test_publish_falls_back_to_buckets_without_a_livestream_reading():
720-
"""With no livestream reading the bucket-derived values are still published."""
720+
"""With no livestream reading all four sensors fall back to the settled bucket values.
721+
722+
Load falls back to the energy-balance residual, so the four still agree and a power-flow
723+
display still balances - a livestream failure degrades the sensors rather than blanking them.
724+
"""
721725
api = MockEnphaseAPI()
722726
published = _publish_with_buckets(api)
723727
assert published["pv"] == 1000.0
724728
assert published["grid"] == 400.0
725729
assert published["battery"] == -800.0
730+
assert published["load"] == 600.0 # 1000 + 400 - 800
731+
assert published["load"] == published["pv"] + published["grid"] + published["battery"]
726732

727733

728734
def test_interval_power():
@@ -928,6 +934,28 @@ def test_get_schedules_supported_from_status():
928934
assert api.dtg_supported("12345") is False # 'not_supported' status
929935

930936

937+
def test_get_schedules_pending_family_is_still_supported():
938+
"""A family whose scheduleStatus is 'pending' is supported - a write is in flight, that is all.
939+
940+
The cloud reports a family as 'pending' while a schedule change settles on the gateway, which
941+
happens right after any write Predbat makes. Treating that as unsupported made Predbat decide
942+
the site could not do charge-from-grid at all and abandon automatic configuration, even with an
943+
active schedule sitting in the family.
944+
"""
945+
api = MockEnphaseAPI()
946+
detail = {"scheduleId": "c1", "startTime": "04:30", "endTime": "04:40", "limit": 5, "scheduleType": "CFG", "isDeleted": False, "isEnabled": True, "scheduleStatus": "active"}
947+
payload = {
948+
"type": "BATTERY_SCHEDULES_CONFIG",
949+
"cfg": {"scheduleStatus": "pending", "count": 1, "details": [detail]},
950+
"dtg": {"scheduleStatus": "pending", "count": 1, "details": [dict(detail, scheduleId="d1", scheduleType="DTG")]},
951+
"rbd": {"scheduleStatus": "active", "count": 0},
952+
}
953+
api.set_http_response("/service/batteryConfig/api/v1/battery/sites/12345/schedules", 200, payload)
954+
run_async(api.get_schedules("12345"))
955+
assert api.schedules["12345"]["cfg"]["supported"] is True
956+
assert api.dtg_supported("12345") is True
957+
958+
931959
def test_inverter_def_enphase():
932960
"""EnphaseCloud INVERTER_DEF exists with the agreed capability flags."""
933961
from config import INVERTER_DEF
@@ -2043,6 +2071,7 @@ def run_enphase_api_tests(my_predbat):
20432071
test_automatic_config_no_dtg_raises()
20442072
test_automatic_config_no_charge_support_raises()
20452073
test_get_schedules_supported_from_status()
2074+
test_get_schedules_pending_family_is_still_supported()
20462075
test_inverter_def_enphase()
20472076
test_run_first_polls_all_tiers()
20482077
test_get_today()

0 commit comments

Comments
 (0)