Skip to content

fix(energysite): implement grid_import_export over local TEDAPI v1r - #5

Merged
Bre77 merged 1 commit into
mainfrom
fm/apw-export-local-impl
Jul 19, 2026
Merged

fix(energysite): implement grid_import_export over local TEDAPI v1r#5
Bre77 merged 1 commit into
mainfrom
fm/apw-export-local-impl

Conversation

@Bre77

@Bre77 Bre77 commented Jul 19, 2026

Copy link
Copy Markdown
Member

Intent

Restores evening battery→grid export by implementing PowerwallEnergySite.grid_import_export over the local TEDAPI path instead of leaving it a NotImplementedError placeholder that fell back to the cloud.

Every evening automation on a system running the local energy router issues two coupled commands: operation (mode → autonomous) and grid_import_export (export rule → battery_ok). operation is implemented locally and lands on the gateway; grid_import_export was not, so the router failed it over to the cloud EnergySite. That cloud PATCH returns 200 {'response': ''} — success-shaped, but the export rule on the gateway never actually moves off never, because the coupled mode change is now happening out-of-band on the local plane. Net effect: default_real_mode=autonomous + customer_preferred_export_rule=never → the battery is allowed to run but forbidden from exporting.

Read-back evidence (cloud site_info, 07:30–09:30 UTC export window) shows the split lining up exactly with the local-router go-live:

AEST day path default_real_mode / customer_preferred_export_rule grid export
07-11, 07-12 cloud autonomous / battery_ok ✅ -10kW / -10kW min
07-14 → 07-18 local autonomous / never ❌ ~0

100% of evening export cycles have failed this way since the local router went live on 2026-07-14.

What changed

  • PowerwallClient.set_grid_import_export: writes site_info.customer_preferred_export_rule (GRID_EXPORT_RULES: battery_ok/pv_only/never) and/or site_info.disallow_charge_from_grid_with_solar_installed via write_config. Both fields live in the same config.json document as backup_reserve_percent, so passing both writes them in one atomic read-modify-write rather than two round-trips. Field names and the export-rule enum were confirmed against the sibling jasonacox/pypowerwall project's v1r write path (set_grid_export/set_grid_charging), which targets the same local gateway schema — this repo has no way to read them back off real hardware itself.
  • PowerwallEnergySite.grid_import_export: moved out of the placeholder section, maps onto the new client method, matches the cloud method's signature and _ok_response() return shape — no router changes needed.
  • Tests: tests/test_grid_import_export.py (new) covers the client-level writer — enum validation, the atomic-both-fields write, and rejecting a no-argument call. tests/test_energysite.py moves grid_import_export out of the placeholder-raises-NotImplementedError table into its own mapping tests.
  • AGENTS.md: documents the field mapping and flags the router hazard this bug came from (don't split operation/backup/grid_import_export across local/cloud backends — they share one config.json document).

Testing

uv run ruff check ., uv run mypy, and uv run pytest (100 passed) all pass. This has not been exercised against real Powerwall hardware — per the task's constraints, no commands were sent to any live gateway. The field names are cross-checked against a second independent local-API implementation (pypowerwall) rather than verified by a live write/read-back round trip; the customer's existing 17:30 AEST export automation will be the live verification once this is released and installed.

… export bug

PowerwallEnergySite.grid_import_export previously raised NotImplementedError,
so a per-command-failover router fell back to the cloud PATCH for it while
operation() (mode) succeeded locally. That split coupled writes across two
control planes: the cloud export-rule PATCH returned 200 but silently failed
to actuate, leaving customer_preferred_export_rule stuck at "never" even
though default_real_mode reached "autonomous" locally.

Wire grid_import_export onto a local config.json write via
PowerwallClient.set_grid_import_export, writing
site_info.customer_preferred_export_rule and/or
site_info.disallow_charge_from_grid_with_solar_installed in a single atomic
read-modify-write (field names/enum confirmed against jasonacox/pypowerwall's
v1r write path, which targets the same local gateway schema). This keeps
operation and grid_import_export on the same control plane going forward.
charge-from-grid keys) once the exact local fields are confirmed. Its
semantics differ enough from ``curtail`` that mapping to that would be
wrong, so this stays a placeholder and falls back to the cloud."""
raise NotImplementedError(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this error causing the TeslaFleetApi Router to route to cloud, or was it swallowed silently?

@Bre77
Bre77 merged commit a6638e0 into main Jul 19, 2026
8 checks passed
@Bre77 Bre77 mentioned this pull request Jul 19, 2026
Bre77 added a commit that referenced this pull request Jul 31, 2026
The local setters and the merged set_grid_import_export (#5) write the
same two site_info keys. Keep the ergonomic single-purpose wrappers, but
make them delegate so validation and the read-modify-write live in one
place; set_grid_import_export remains the way to set both atomically.

Also corrects the EnergySite adapter docs, which still listed
grid_import_export as unimplemented after #5 wired it up locally.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant