EnphaseCloud: activate CFG/DTG/RBD after schedule write (supersedes #4253)#4263
Merged
Conversation
…ding schedules After writing a CFG (charge-from-grid) schedule via the Enphase Cloud API, a second activation call is required to transition the schedule from "pending" to "active" status. Without this call, the Enphase gateway never picks up the schedule and the battery never charges. This adds _activate_cfg_mode which PUTs /batterySettings/<site_id> with chargeFromGrid: true and the acceptedItcDisclaimer timestamp, mirroring the "Apply" button behaviour in the Enphase app. The method is called after every CFG schedule write (when charge is enabled), using the existing _write_schedule return value to avoid unnecessary API calls. The existing _ensure_charge_from_grid only handles the one-time ITC disclaimer and omits both the acceptedItcDisclaimer timestamp and ?userId=<u>&source=enho query params required by the activation flow. Fixes #4252
Review fixes:
- Use timezone-aware datetime in _activate_cfg_mode
- Guard battery_settings cache update behind API success in _set_charge_from_grid
- Replace fragile year-based test assertion with recency check
- Extract shared _set_charge_from_grid helper to reduce duplication
DTG/RBD activation (confirmed from real Enphase web app traffic):
- Add _activate_dtg_mode: PUT {dtgControl: {enabled: true}} after DTG write
- Add _activate_rbd_mode: PUT {rbdControl: {enabled: true}} after RBD write
- Wire activation calls into apply_battery_schedule, gated like CFG
- Add 7 tests covering unit + integration for both families
…chedules_match is True when cloud CFG matches desired schedule
- _activate_cfg_mode: return bool and log warning on failure - _activate_dtg_mode: return bool and log warning on failure - _activate_rbd_mode: return bool and log warning on failure - MockEnphaseAPI: record params in request_log - Tests: assert source/userId params on all activation calls
…t apply retries When _write_schedule succeeded but _activate_cfg_mode/_activate_dtg_mode/ _activate_rbd_mode failed, the optimistic cache update left the cached schedule matching the cloud — making _write_schedule a no-op on the next cycle and preventing activation retry. Now each activation method calls _invalidate_cached_schedule on failure, which removes startTime from the cached entry (keeping the id) so schedules_equal detects a diff and _write_schedule re-issues a PUT update plus activation next time.
…n invalidation Removing startTime from the cache causes schedules_equal to return 'not enabled', which incorrectly no-ops a disable request after an activation failure. Setting it to an empty string forces a permanent mismatch regardless of the desired enabled state, so both re-enabled and disabled requests are correctly written to the cloud.
…e and fix backward compat - _set_charge_from_grid now takes explicit params kwarg (default None) so _ensure_charge_from_grid can omit params it didn't previously send, preserving backward compat with the original direct PUT call. - chargeFromGrid is forced to True after building the body from extra_body, preventing callers from accidentally overriding it. - Fix _invalidate_cached_schedule docstring to accurately describe when schedules_equal returns False (when enabled=True; when enabled=False it returns True — which is the desired no-op for disable requests). - Relax age >= 0 assertion to >= -5 to tolerate minor clock skew.
… status is pending If a schedule from a previous run is stuck in pending status on the cloud, schedules_equal matches the cached entry (it ignores status) and _write_schedule no-ops. Added _is_schedule_pending check: when the schedule matches but cloud status reports 'pending', activation is attempted without re-writing — covering the bootstrap case where a schedule was written but never activated, and the normal flow where activation fails (cache invalidation) is handled separately.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…g, dedupe) Follow-up review fixes on top of the CFG/DTG/RBD activation work: - Fix AttributeError in _is_schedule_pending when a matching cached schedule has status=None (the shape get_schedules produces when the cloud omits scheduleStatus) — this aborted apply_battery_schedule in steady state. - Clear the cached pending marker on DTG/RBD activation success (as CFG already did) so they are not re-activated every apply cycle. - Collapse the three near-identical activation methods into a shared _activate_control_mode helper and the three wiring blocks into _write_and_activate; remove the now-dead _set_charge_from_grid. - Drop the redundant cold-start batterySettings PUT: _ensure_charge_from_grid now only accepts the ITC disclaimer and the single activation PUT enables chargeFromGrid (force-activated when the setting is still off). Adds 7 tests covering the null-status path, DTG/RBD pending clearing, the single-PUT cold start, and the matching-but-not-activated case. Co-Authored-By: Dan Hughes <github@danhughes.dev> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates the EnphaseCloud integration to ensure written schedules actually become effective on the gateway by issuing the required post-write activation PUT /batterySettings/{id} for CFG/DTG/RBD modes, and adds/updates unit tests to validate the activation behavior and related edge cases.
Changes:
- Add a shared activation helper and wire schedule writes through a
_write_and_activateflow that activates when a schedule is written, stuck pending, or needs a control-setting enable. - Make pending-status detection null-safe and improve retry behavior by invalidating cached schedules on activation failure and clearing pending markers on activation success.
- Extend Enphase API tests to cover activation calls (including query params), pending/no-crash cases, and “matches-but-not-activated” scenarios.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| apps/predbat/enphase.py | Adds activation + pending-handling helpers and updates schedule application to activate CFG/DTG/RBD after writes (or when pending/needs enable). |
| apps/predbat/tests/test_enphase_api.py | Updates HTTP mock logging to capture query params and adds unit tests covering activation, pending behavior, cache invalidation, and null-status handling. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Supersedes #4253 (by @dan1elhughes) and fixes #4252. The original commits are preserved with their authorship — this branch is built directly on top of @dan1elhughes's work and layers on review fixes for correctness and cleanup.
What the base work does (credit: @dan1elhughes)
Enphase Cloud schedule writes land in "pending" status and never commit to the gateway until a follow-up
PUT /batterySettings/{id}activates each mode:acceptedItcDisclaimer(+chargeFromGrid)dtgControl.enabledrbdControl.enabledReview fixes added on top
_is_schedule_pendingdidentry.get("status", "").lower(), which raisedAttributeErrorwhen the cached status wasNone— the shapeget_schedulesproduces when the cloud omitsscheduleStatus. This abortedapply_battery_schedulein the common steady state (matching, enabled schedule), and because CFG runs first it also skipped the DTG/RBD writes+activations. Now null-safe._activate_control_modehelper, and the three near-identical wiring blocks into_write_and_activate; the now-dead_set_charge_from_gridis removed._ensure_charge_from_gridnow only accepts the one-time ITC disclaimer; the single activation PUT enableschargeFromGrid(force-activated when the setting is still off), which also closes the matching-but-never-activated gap.Testing
7 new/updated unit tests: null-status path (no crash), DTG/RBD pending-marker clearing, single-PUT cold start, and matching-but-not-activated activation. Full
./run_all --quicksuite passes; pre-commit clean.🤖 Generated with Claude Code