fix(teslemetry): model Powerwalls as AC coupled, publish site_info for review - #4652
Conversation
Tesla Powerwall batteries are AC coupled, but nothing in the Teslemetry component or the TESLA inverter type set inverter_hybrid, so it stayed at Predbat's default of on. That matters because of how the two settings combine. Teslemetry publishes inverter_limit from site_info, and max_site_meter_power_ac is usually the 1e9 "unlimited" sentinel, so it falls back to nameplate_power - the Powerwall's own AC rating (5 kW on a PW2). With inverter_hybrid on, get_total_inverted() folds pv_ac into the same budget, so that 5 kW became a cap on battery + PV combined and a separately inverted solar array was modelled as clipping against a limit it never passes through. On a reported site (13.5 kWh PW2, solar measured at 24 kW, 48.6 kWh exported in one day) that invented 17.7 kWh of clipping and, with it, a 05:00-10:00 export window whose only purpose was to "recover" the phantom clipped solar. Turning inverter_hybrid off takes clipping to 0.00 kWh and the morning export window disappears; the plan keeps only its genuine overnight exports. The nameplate_power fallback itself is left alone - it is correct once hybrid is off, because a non-hybrid inverter_limit caps only the battery path, which is exactly the Powerwall's rating (and 11.5 kW on a PW3). Also publishes site_info as sensor.<prefix>_teslemetry_site_info so the device's own view of the site - capacity, AC rating, coupling, export rule - is reviewable without reading a debug log. The response is republished wholesale rather than as a hand-picked subset, so future firmware fields appear without a code change and nothing is lost to a wrong guess about nesting (batteries, customer_preferred_export_rule and net_meter_mode all live under components). The large tariff blobs are dropped, the same ones _summarize_for_log already hides. Supporting refactor: set_state_external moves onto ComponentBase, so components call self.set_state_external() instead of reaching through self.base.ha_interface. All 11 call sites across teslemetry, gecloud, web and web_mcp are migrated, the shared MockBase gains an ha_interface so the standalone CLI harnesses no longer crash on a component that auto-configures a Predbat setting, and GECloud's now-redundant local MockHAInterface is removed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
The new site_info entity republishes nested identifiers (e.g., device serial numbers) as HA attributes without redaction/unrecorded handling, which has avoidable privacy/recorder exposure implications.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR fixes Tesla Powerwall modelling by ensuring Powerwalls are treated as AC-coupled (non-hybrid) during Teslemetry auto-configuration, and adds a reviewable HA entity that republishes Teslemetry site_info so key modelling inputs are visible without debug logs. It also refactors set_state_external onto ComponentBase to standardize how components update Predbat-owned config entities.
Changes:
- Teslemetry: publish
site_infoassensor.<prefix>_teslemetry_site_infoand setswitch.<prefix>_inverter_hybrid = Falseduringautomatic_config(). - Refactor: move
set_state_external()ontoComponentBaseand migrate component call sites to use it. - Tests/mocks: add unit tests for the new behavior and update mocks to support the new
set_state_external(..., attributes=...)signature.
File summaries
| File | Description |
|---|---|
| apps/predbat/teslemetry.py | Publishes site_info review entity; disables inverter_hybrid for Powerwall in automatic_config; refactors tariff-blob filtering. |
| apps/predbat/component_base.py | Adds ComponentBase.set_state_external() forwarding to HA interface. |
| apps/predbat/web.py | Migrates web UI entity/config updates to use self.set_state_external(). |
| apps/predbat/web_mcp.py | Migrates MCP “set_config” to use self.set_state_external(). |
| apps/predbat/gecloud.py | Migrates hybrid-switch write to self.set_state_external() and removes redundant GECloud CLI mock HA interface. |
| apps/predbat/mock_base.py | Adds minimal ha_interface stub so CLI harnesses don’t crash when components call set_state_external(). |
| apps/predbat/tests/test_teslemetry.py | Adds Teslemetry tests for hybrid disabling and site_info entity publishing/filtering/non-mutation. |
| apps/predbat/tests/test_component_base.py | Adds test validating ComponentBase.set_state_external() forwards attributes intact. |
| apps/predbat/tests/test_ge_cloud.py | Updates mock set_state_external signature to accept attributes. |
Review details
- Files reviewed: 9/9 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The bug
Tesla Powerwall batteries are AC coupled, but nothing in
teslemetry.pyor theTESLAinverter type setsinverter_hybrid, so it stays at Predbat's default of on.That only bites because of how it combines with
inverter_limit.fetch_site_infopublishesinverter_limitfrom site_info, andmax_site_meter_power_acis usually the 1e9 "unlimited" sentinel:so the guard fires and falls back to
nameplate_power— the Powerwall's own AC rating. Withinverter_hybridon,get_total_inverted()foldspv_acinto the same budget, so 5 kW becomes a cap on battery + PV combined, and a separately inverted solar array is modelled as clipping against a limit it never passes through.Impact
On a reported site (13.5 kWh Powerwall 2, solar measured at 24.19 kW, 52.1 kWh generated and 48.6 kWh exported in one day), replaying the user's saved plan:
hybrid=True)hybrid=False)The fabricated clipping made the optimiser add a morning export window whose only purpose was to empty the battery so it could "recover" solar that was never actually being clipped. With the flag corrected the clipping goes to zero and the window disappears.
The
nameplate_powerfallback is deliberately left alone — it is correct once hybrid is off, because a non-hybridinverter_limitcaps only the battery path, which is exactly the Powerwall's rating (5 kW PW2, 11.5 kW PW3).Changes
automatic_configturnsinverter_hybridoff for the Powerwall, mirroring what GECloud already does for its AC/AIO models.sensor.<prefix>_teslemetry_site_infoentity so the device's own view of the site — capacity, AC rating, coupling, export rule — is reviewable without reading a debug log. The response is republished wholesale rather than as a hand-picked subset, so future firmware fields appear without a code change and nothing is lost to a wrong guess about nesting (batteries,customer_preferred_export_ruleandnet_meter_modeall live undercomponents, not at the top level). The large tariff blobs are dropped — the same ones_summarize_for_logalready hides. ~2.9 KB of attributes.set_state_externalmoves ontoComponentBase, so components callself.set_state_external()instead of reaching throughself.base.ha_interface. All 11 call sites acrossteslemetry,gecloud,webandweb_mcpare migrated, the sharedMockBasegains anha_interfaceso the standalone CLI harnesses no longer crash on a component that auto-configures a Predbat setting, and GECloud's now-redundant localMockHAInterfaceis removed.Caveat
automatic_configonly runs withteslemetry_automaticenabled. Sites that wired theTESLAinverter type manually inapps.yamlstill need to turn the Inverter Hybrid switch off themselves — happy to push the default into the inverter type instead if you'd prefer that covered too.Testing
ComponentBase.set_state_external, plus fixture nesting corrected against a real payload)../run_all --quick: all passing.interrogateunchanged or improved on every touched file (component_base88.9→89.2%,gecloud98.3→100%).switch.predbat_inverter_hybrid = Falseand the entity renders all the nested modelling fields.🤖 Generated with Claude Code