Skip to content

fix(energysite): fill live_status energy_left/total_pack_energy from get_status() - #3

Merged
Bre77 merged 1 commit into
mainfrom
fm/pw-router-wire-c3
Jul 3, 2026
Merged

fix(energysite): fill live_status energy_left/total_pack_energy from get_status()#3
Bre77 merged 1 commit into
mainfrom
fm/pw-router-wire-c3

Conversation

@Bre77

@Bre77 Bre77 commented Jul 3, 2026

Copy link
Copy Markdown
Member

Intent

Wire aiopowerwall's newly added Fleet-API-aligned client methods (scaled SoC readers, get_battery_soe/get_battery_soe_raw, battery_level/battery_level_raw, set_operation_mode) into the PowerwallEnergySite router compatibility adapter so it's interface-complete for the tesla-fleet-api EnergyRouter. Studied the sibling python-tesla-fleet-api repo (tag v1.5.2) - tesla_fleet_api/tesla/router.py's Router (hasattr-based per-command dispatch/failover) and tesla_fleet_api/tesla/energysite.py's EnergySite/TeslemetryEnergySite classes - to enumerate the full router-invocable surface. Found the adapter already had 100% method-name parity (every command either mapped to a PowerwallClient call or scaffolded as a documented NotImplementedError placeholder consistent with existing conventions). The actual gap was inside the already-implemented live_status(): it left energy_left and total_pack_energy as documented None gaps even though that data is locally derivable - control.systemStatus.nominalEnergyRemainingWh/nominalFullPackEnergyWh are exactly the fields the new battery_level_raw()/battery_level() helpers already read. Fixed by replacing the separate get_battery_soe() call with a single get_status() call plus the new battery_level() helper, so percentage_charged, energy_left, and total_pack_energy all come from one read (same call count as before, two more Fleet API fields populated instead of None). No behavior for any other command changed; the NotImplementedError placeholder convention for genuinely unsupported commands (storm_mode, grid_import_export, time_of_use_settings, history reads, gRPC device commands, etc.) and the deliberate omission of site_info were left untouched since they're still correct. Added/extended tests in tests/test_energysite.py for the new energy_left/total_pack_energy values plus a missing-status-yields-None case, and updated README.md and AGENTS.md to describe the new live_status data source.

What Changed

  • PowerwallEnergySite.live_status() now populates energy_left and total_pack_energy (previously always None) by reading control.systemStatus.nominalEnergyRemainingWh / nominalFullPackEnergyWh via a new _system_status_field helper.
  • Replaced the separate get_battery_soe() call with a single get_status() read that feeds percentage_charged (via the client battery_level helper), energy_left, and total_pack_energy from one gateway query — same call count, two more Fleet-API fields filled. Remaining non-derivable keys (backup_capable, grid_services_*, storm_mode_active, timestamp, wall_connectors) stay None.
  • Extended tests/test_energysite.py with assertions for the new Wh values and a missing-status-yields-None case (40 passing), and updated README.md / AGENTS.md to document the live_status data source.

Risk Assessment

✅ Low: A small, well-bounded change that populates two previously-None Fleet API fields from an existing single read, with matching test coverage and no behavior change to any other command.

Testing

Baseline uv sync --extra dev plus uv run pytest tests/test_energysite.py passed all 40 tests, covering the new populated-values and missing-status-yields-None cases. To show the intent working the way the router experiences it, I ran a small script that feeds realistic raw gateway payloads (nominalEnergyRemainingWh/nominalFullPackEnergyWh, meter aggregates, grid status) through the real PowerwallEnergySite.live_status() and captured the resulting Fleet-API-shaped response: energy_left=52777.8 and total_pack_energy=100000.0 are now populated (previously hard-coded None) and percentage_charged=50.2924 comes from the same single get_status() read via battery_level(). This is a library/adapter change with no UI surface, so the evidence is a CLI transcript of the actual adapter output rather than a screenshot.

Evidence: live_status() end-to-end output showing populated energy_left/total_pack_energy

live_status().response (fields relevant to this change): { "percentage_charged": 50.2924, "energy_left": 52777.8, "total_pack_energy": 100000.0, "solar_power": 3200.0, "battery_power": -1800.0, "load_power": 1400.0, "grid_power": 1500.0, "grid_status": "Active", "island_status": "on_grid" } Before this change, energy_left and total_pack_energy were hard-coded None.

live_status().response (fields relevant to this change):
{
  "percentage_charged": 50.2924,
  "energy_left": 52777.8,
  "total_pack_energy": 100000.0,
  "solar_power": 3200.0,
  "battery_power": -1800.0,
  "load_power": 1400.0,
  "grid_power": 1500.0,
  "grid_status": "Active",
  "island_status": "on_grid"
}

Before this change, energy_left and total_pack_energy were hard-coded None.

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • ℹ️ src/aiopowerwall/energysite.py:251 - Minor semantic inconsistency at empty-battery edge: energy_left comes from _system_status_field (isinstance check, so a legitimate nominalEnergyRemainingWh of 0 yields 0.0), while percentage_charged comes from battery_levelbattery_level_raw, whose if not remaining or not full guard treats remaining==0 as unknown and returns None. So a fully-drained pack would report energy_left=0.0 but percentage_charged=None. Real gateways rarely report exactly 0 nominal Wh, and the None-on-falsy logic lives in the unchanged client helper, so this is informational only.
✅ **Test** - passed

✅ No issues found.

  • uv run pytest tests/test_energysite.py -q (40 passed), including the new test_live_status_missing_status_yields_none and extended test_live_status_shape_and_available_values
  • End-to-end demo driving PowerwallEnergySite.live_status() with a fake local gateway, printing the Fleet-API-shaped response object the tesla-fleet-api EnergyRouter would consume
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

The router-reachable EnergySite surface was already fully scaffolded, but
live_status() left energy_left/total_pack_energy as documented gaps even
though they're derivable locally: control.systemStatus.nominalEnergyRemainingWh
and nominalFullPackEnergyWh are the same fields battery_level_raw() already
reads. Swap the separate get_battery_soe() call for get_status() + the new
battery_level() helper so percentage_charged, energy_left, and
total_pack_energy all come from one read.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Bre77
Bre77 merged commit 106e925 into main Jul 3, 2026
8 checks passed
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