fix(energysite): fill live_status energy_left/total_pack_energy from get_status() - #3
Merged
Conversation
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>
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.
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 populatesenergy_leftandtotal_pack_energy(previously alwaysNone) by readingcontrol.systemStatus.nominalEnergyRemainingWh/nominalFullPackEnergyWhvia a new_system_status_fieldhelper.get_battery_soe()call with a singleget_status()read that feedspercentage_charged(via the clientbattery_levelhelper),energy_left, andtotal_pack_energyfrom 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) stayNone.tests/test_energysite.pywith assertions for the new Wh values and a missing-status-yields-Nonecase (40 passing), and updatedREADME.md/AGENTS.mdto document thelive_statusdata 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 devplusuv run pytest tests/test_energysite.pypassed 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 realPowerwallEnergySite.live_status()and captured the resulting Fleet-API-shapedresponse:energy_left=52777.8andtotal_pack_energy=100000.0are now populated (previously hard-codedNone) andpercentage_charged=50.2924comes from the same singleget_status()read viabattery_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.Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
src/aiopowerwall/energysite.py:251- Minor semantic inconsistency at empty-battery edge:energy_leftcomes from_system_status_field(isinstance check, so a legitimatenominalEnergyRemainingWhof 0 yields 0.0), whilepercentage_chargedcomes frombattery_level→battery_level_raw, whoseif not remaining or not fullguard 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 newtest_live_status_missing_status_yields_noneand extendedtest_live_status_shape_and_available_valuesEnd-to-end demo drivingPowerwallEnergySite.live_status()with a fake local gateway, printing the Fleet-API-shapedresponseobject the tesla-fleet-api EnergyRouter would consume✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.