Skip to content

fix(teslemetry): model Powerwalls as AC coupled, publish site_info for review - #4652

Merged
springfall2008 merged 1 commit into
mainfrom
fix/teslemetry-ac-coupled-hybrid
Aug 22, 2026
Merged

fix(teslemetry): model Powerwalls as AC coupled, publish site_info for review#4652
springfall2008 merged 1 commit into
mainfrom
fix/teslemetry-ac-coupled-hybrid

Conversation

@springfall2008

Copy link
Copy Markdown
Owner

The bug

Tesla Powerwall batteries are AC coupled, but nothing in teslemetry.py or the TESLA inverter type sets inverter_hybrid, so it stays at Predbat's default of on.

That only bites because of how it combines with inverter_limit. fetch_site_info publishes inverter_limit from site_info, and max_site_meter_power_ac is usually the 1e9 "unlimited" sentinel:

"max_site_meter_power_ac": 1000000000,
"nameplate_power": 5000,
"components": {"battery_type": "ac_powerwall", "solar_type": "pv_panel", ...},
"batteries": [{"part_name": "Powerwall 2", "nameplate_max_discharge_power": 5000, ...}]

so the guard fires and falls back to nameplate_power — the Powerwall's own AC rating. With inverter_hybrid on, get_total_inverted() folds pv_ac into 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:

metric clipped export export windows
before (hybrid=True) −1100.48p 17.71 kWh 123.67 kWh night + 05:00–10:00 + 14:30
after (hybrid=False) −1277.41p 0.00 kWh 139.62 kWh night only + small 02:00–04:30 arbitrage

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_power fallback is deliberately 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 (5 kW PW2, 11.5 kW PW3).

Changes

  1. automatic_config turns inverter_hybrid off for the Powerwall, mirroring what GECloud already does for its AC/AIO models.
  2. New sensor.<prefix>_teslemetry_site_info entity 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, not at the top level). The large tariff blobs are dropped — the same ones _summarize_for_log already hides. ~2.9 KB of attributes.
  3. 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.

Caveat

automatic_config only runs with teslemetry_automatic enabled. Sites that wired the TESLA inverter type manually in apps.yaml still 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

  • 7 new unit tests (5 teslemetry, 1 ComponentBase.set_state_external, plus fixture nesting corrected against a real payload).
  • ./run_all --quick: all passing.
  • pre-commit (ruff, black, cspell) clean. interrogate unchanged or improved on every touched file (component_base 88.9→89.2%, gecloud 98.3→100%).
  • Verified end to end against the real site_info payload: auto-config writes switch.predbat_inverter_hybrid = False and the entity renders all the nested modelling fields.

🤖 Generated with Claude Code

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>
Copilot AI lite review requested due to automatic review settings August 22, 2026 14:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 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_info as sensor.<prefix>_teslemetry_site_info and set switch.<prefix>_inverter_hybrid = False during automatic_config().
  • Refactor: move set_state_external() onto ComponentBase and 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.

Comment thread apps/predbat/teslemetry.py
@springfall2008
springfall2008 merged commit b993747 into main Aug 22, 2026
3 checks passed
@springfall2008
springfall2008 deleted the fix/teslemetry-ac-coupled-hybrid branch August 22, 2026 14:59
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.

2 participants