Describe the bug
EnphaseCloud inverter: Predbat writes the CFG schedule to the Enphase Cloud API (HTTP 200, correct times/limits), but the schedule stays in "scheduleStatus": "pending" indefinitely. The Enphase gateway never picks it up, so the battery never charges from the grid.
The schedule write itself is correct — the API returns the expected values. But the schedule never transitions from "pending" to "active", and the battery never charges. This happens on every write cycle, including after container restarts (clean cache).
The Enphase Cloud API requires a second call to activate a written schedule: a PUT /batterySettings/{id}?userId={u}&source=enho with {"chargeFromGrid": true, "acceptedItcDisclaimer": "<current-iso8601-timestamp>"}. This is the equivalent of pressing "Apply" in the Enphase app. Without it, the schedule is queued but never committed to the gateway.
Expected behaviour
After Predbat writes a CFG schedule, the Enphase gateway should pick it up and start charging the battery from the grid during the charge window. The schedule should transition to "scheduleStatus": "active" and the battery should show positive charge power.
Predbat version
v8.45.2
Environment details
- Inverter: EnphaseCloud (IQ 5P, 3× Encharge batteries, ~15 kWh usable capacity)
- Install: Docker container (standalone, not HA add-on), Debian bookworm-slim
- Mode: Control charge & discharge (
set_read_only: False)
- Enphase config:
enphase_automatic: False — our PV is on a separate dumb string inverter measured by myenergi, not Enphase microinverters. We don't want the Enphase Cloud auto-managing the battery against its own (empty) production readings, which would conflict with Predbat's plan and use incorrect generation data.
- Tariff: Octopus Intelligent Go via BottlecapDave HA integration
Screenshots
N/A (API-level bug — schedule status is the evidence)
Log file
Every _write_schedule call returns "scheduleStatus": "pending":
2026-07-14 19:32:13.011176: Enphase: Updating CFG schedule d9873c9e-... on site 5886607: 19:30-23:59 limit=100 enabled=True
2026-07-14 19:32:13.389959: Enphase API: PUT .../battery/sites/5886607/schedules/d9873c9e-... -> 200
{"scheduleStatus": "pending", "startTime": "19:30", "endTime": "23:59", "limit": 100, "isEnabled": true}
2026-07-14 20:05:57.591157: Enphase: Updating CFG schedule d9873c9e-... on site 5886607: 19:00-23:59 limit=100 enabled=True
2026-07-14 20:05:57.989006: Enphase API: PUT .../battery/sites/5886607/schedules/d9873c9e-... -> 200
{"scheduleStatus": "pending", "startTime": "19:00", "endTime": "23:59", "limit": 100, "isEnabled": true}
2026-07-14 20:23:57.077385: Enphase API: PUT .../battery/sites/5886607/schedules/d9873c9e-... -> 200
{"scheduleStatus": "pending", "startTime": "19:35", "endTime": "23:59", "limit": 100, "isEnabled": true}
The periodic GET /schedules/ also confirms the schedule is stuck in pending:
2026-07-14 19:58:31.362534: Enphase API: GET .../battery/sites/5886607/schedules -> 200
{"cfg": {"scheduleStatus": "pending", ...}, "dtg": {"scheduleStatus": "pending", ...},
"rbd": {"scheduleStatus": "active", ...}, "anySchedulePending": true}
After manually calling PUT /batterySettings/{id}?userId={u}&source=enho with {"chargeFromGrid": true, "acceptedItcDisclaimer": "2026-07-14T20:25:00"}, the schedule immediately transitions to "active" and the battery starts charging at ~9.4 kW.
Root cause analysis
apply_battery_schedule only calls _write_schedule (the PUT /schedules/ endpoint). It never makes the PUT /batterySettings/ activation call.
_ensure_charge_from_grid does call PUT /batterySettings/ but is not equivalent:
- Calls a separate
POST /batterySettings/acceptDisclaimer/ instead of including acceptedItcDisclaimer in the same payload
- Omits
?userId={u}&source=enho query params
- Is cached (runs once), so subsequent schedule changes aren't activated
- Runs before
_write_schedule, not after
Evidence table
| Action |
Schedule status |
Result |
Predbat _write_schedule only (5+ cycles) |
"pending" |
Battery does not charge (0 W) |
| Container restart + fresh write (clean cache) |
"pending" |
Battery does not charge |
PUT /batterySettings/ with acceptedItcDisclaimer timestamp |
"active" |
Battery charges at 9.4 kW |
| Same schedule PUT (no-op) + activation call |
"active" |
Battery charges |
The activation call alone — without changing the schedule — moves "pending" → "active", confirming the schedule write is correct and only the activation is missing.
Proposed fix
Add a method that mirrors the activation call, and invoke it after every CFG schedule write:
async def _activate_cfg_mode(self, site_id):
"""Activate charge-from-grid mode after writing the CFG schedule."""
params = {"source": "enho"}
if self.user_id:
params["userId"] = self.user_id
now_iso = datetime.now().isoformat()
await self.request_json(
"PUT",
f"{BATTERY_CONFIG_BASE}/batterySettings/{site_id}",
family="battery_config",
params=params,
json_body={
"chargeFromGrid": True,
"acceptedItcDisclaimer": now_iso,
},
)
self.battery_settings.setdefault(site_id, {})["chargeFromGrid"] = True
Then in apply_battery_schedule (~line 821-825):
if charge.get("enable"):
await self._ensure_charge_from_grid(site_id)
- wrote |= await self._write_schedule(site_id, SCHEDULE_CHARGE, ...)
+ wrote_cfg = await self._write_schedule(site_id, SCHEDULE_CHARGE, ...)
+ if wrote_cfg and charge.get("enable"):
+ await self._activate_cfg_mode(site_id)
+ wrote |= wrote_cfg
_ensure_charge_from_grid stays for the one-time ITC disclaimer. The new call fires on every schedule change.
Describe the bug
EnphaseCloud inverter: Predbat writes the CFG schedule to the Enphase Cloud API (HTTP 200, correct times/limits), but the schedule stays in
"scheduleStatus": "pending"indefinitely. The Enphase gateway never picks it up, so the battery never charges from the grid.The schedule write itself is correct — the API returns the expected values. But the schedule never transitions from "pending" to "active", and the battery never charges. This happens on every write cycle, including after container restarts (clean cache).
The Enphase Cloud API requires a second call to activate a written schedule: a
PUT /batterySettings/{id}?userId={u}&source=enhowith{"chargeFromGrid": true, "acceptedItcDisclaimer": "<current-iso8601-timestamp>"}. This is the equivalent of pressing "Apply" in the Enphase app. Without it, the schedule is queued but never committed to the gateway.Expected behaviour
After Predbat writes a CFG schedule, the Enphase gateway should pick it up and start charging the battery from the grid during the charge window. The schedule should transition to
"scheduleStatus": "active"and the battery should show positive charge power.Predbat version
v8.45.2
Environment details
set_read_only: False)enphase_automatic: False— our PV is on a separate dumb string inverter measured by myenergi, not Enphase microinverters. We don't want the Enphase Cloud auto-managing the battery against its own (empty) production readings, which would conflict with Predbat's plan and use incorrect generation data.Screenshots
N/A (API-level bug — schedule status is the evidence)
Log file
Every
_write_schedulecall returns"scheduleStatus": "pending":The periodic
GET /schedules/also confirms the schedule is stuck in pending:After manually calling
PUT /batterySettings/{id}?userId={u}&source=enhowith{"chargeFromGrid": true, "acceptedItcDisclaimer": "2026-07-14T20:25:00"}, the schedule immediately transitions to "active" and the battery starts charging at ~9.4 kW.Root cause analysis
apply_battery_scheduleonly calls_write_schedule(the PUT /schedules/ endpoint). It never makes thePUT /batterySettings/activation call._ensure_charge_from_griddoes callPUT /batterySettings/but is not equivalent:POST /batterySettings/acceptDisclaimer/instead of includingacceptedItcDisclaimerin the same payload?userId={u}&source=enhoquery params_write_schedule, not afterEvidence table
_write_scheduleonly (5+ cycles)"pending""pending"PUT /batterySettings/withacceptedItcDisclaimertimestamp"active""active"The activation call alone — without changing the schedule — moves "pending" → "active", confirming the schedule write is correct and only the activation is missing.
Proposed fix
Add a method that mirrors the activation call, and invoke it after every CFG schedule write:
Then in
apply_battery_schedule(~line 821-825):if charge.get("enable"): await self._ensure_charge_from_grid(site_id) - wrote |= await self._write_schedule(site_id, SCHEDULE_CHARGE, ...) + wrote_cfg = await self._write_schedule(site_id, SCHEDULE_CHARGE, ...) + if wrote_cfg and charge.get("enable"): + await self._activate_cfg_mode(site_id) + wrote |= wrote_cfg_ensure_charge_from_gridstays for the one-time ITC disclaimer. The new call fires on every schedule change.