Skip to content

Show car charging on the web power flow diagram - #4715

Merged
springfall2008 merged 6 commits into
mainfrom
feat/car-charging-power-flow-diagram
Aug 25, 2026
Merged

Show car charging on the web power flow diagram#4715
springfall2008 merged 6 commits into
mainfrom
feat/car-charging-power-flow-diagram

Conversation

@springfall2008

Copy link
Copy Markdown
Owner

Adds car_charging_power, an optional apps.yaml sensor giving the live power (W) drawn by an EV charger, and puts the car on the web interface power flow diagram.

It is display only — the plan still models car charging from car_charging_energy and is unaffected by this.

What it does

When car_charging_power is set:

  • the power flow diagram gains a Car alongside PV, battery and grid — animated while charging, dashed when configured but idle
  • the House figure becomes load_power - car_charging_power (clamped at zero), so it reads as the rest of the household rather than counting the charger twice
  • predbat.car_charging_power (kW, device_class: power) is published for upstream consumers. It is only published when a charger is configured, so its absence is how a consumer tells "no charger" from "charger idle"

When it is not set the diagram renders exactly as it does today and no sensor is published.

Several chargers can be listed and are summed, as car_charging_energy allows.

Automatic configuration

All five components that already wire car_charging_energy now wire the matching power sensor from the same device list, so entry N is the same charger:

Component car_charging_energy car_charging_power
Ohme sensor.predbat_ohme_energy_today sensor.predbat_ohme_power_watts
myenergi (Zappi) ..._session_energy ..._power
GECloud EV charger ..._evc_energy_active_import_register ..._evc_power_active_import
AlphaESS ..._ev_energy_today ..._ev_power
Gateway MQTT sensor.{pfx}_session_energy sensor.{pfx}_power

Ohme's is tied to the same decision as its energy sensor: when a third-party charger owns the energy figure, Ohme's own power reading is left out rather than reported beside it.

The apps.yaml templates ship a regular expression matching Wallbox and Zappi, the same way car_charging_energy does. Huawei and Kostal ship charger-specific entries (go-eCharger, evcc) rather than the generic regex, so those get commented placeholders pointing at their own charger.

Three cases handled deliberately

Each is covered by a test that failed first:

  • a charger reporting unavailable with nothing plugged in (normal for an EV charger) reads as zero without flagging the run as errored — get_arg's numeric path would otherwise leave Predbat sitting in "with Errors" all day
  • an unresolved re: template default counts as not configured, so a household with no charger never gets a Car drawn in the window before auto_config(final=True) removes the key
  • each entity is resolved individually rather than by index, because auto_config() leaves a None in place of a list entry whose expression found nothing, and an index walk would stop at the hole instead of the chargers after it

Testing

  • new apps/predbat/tests/test_web_power_flow.py (registered as web_power_flow) — 13 cases covering the reader, unit conversion, the published sensor, all three diagram states and the house-remainder clamp
  • auto-map assertions added to test_ohme.py, test_myenergi.py, test_alphaess_publish.py, test_ge_cloud.py and test_gateway.py
  • full suite passes with no skips (151.58s); all pre-commit hooks pass

Docs

car-charging.md, apps-yaml.md, web-interface.md and output-data.md.

🤖 Generated with Claude Code

Adds car_charging_power, an optional apps.yaml sensor giving the live power
(W) drawn by an EV charger. It is display only - the plan still models car
charging from car_charging_energy and is unaffected by this.

When it is set:

- the web interface power flow diagram gains a Car alongside PV, battery and
  grid, animated while charging and dashed when idle
- the House figure becomes load_power minus the car, clamped at zero, so it
  reads as the rest of the household rather than counting the charger twice
- predbat.car_charging_power (kW, device_class power) is published for
  upstream consumers. It is only published when a charger is configured, so
  its absence distinguishes "no charger" from "charger idle"

All five components that already wire car_charging_energy now wire the
matching power sensor from the same device list, so entry N is the same
charger: Ohme, myenergi Zappi, GECloud EV charger, AlphaESS and the gateway.
Ohme's is tied to the same decision as its energy sensor - when a third party
charger owns the energy figure, Ohme's own power reading is left out rather
than reported beside it. The apps.yaml templates ship a regular expression
matching Wallbox and Zappi, as car_charging_energy does.

Three cases the reader handles deliberately, each covered by a test that
failed first:

- a charger reporting 'unavailable' with nothing plugged in reads as zero
  without flagging the run as errored, which get_arg's numeric path would
  otherwise do and leave Predbat in "with Errors" all day
- an unresolved "re:" template default counts as not configured, so a house
  with no charger never gets a Car drawn before auto_config(final) runs
- each entity is resolved individually rather than by index, as auto_config()
  leaves a None in place of a list entry whose expression found nothing and an
  index walk would stop at the hole instead of the chargers after it

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 24, 2026 19:54

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

Validation mishandles transient unavailable states, stale published sensors are not cleaned up, and the transition test does not cover shared publication state.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds optional live EV-charger power monitoring to Predbat’s web power-flow diagram and publishes it as a kW sensor without changing charging-plan calculations.

Changes:

  • Reads, aggregates, converts, and publishes charger power.
  • Adds a Car diagram node and subtracts charging power from household load.
  • Adds integration mappings, templates, documentation, and tests.
File summaries
File Summary
templates/teslemetry.yaml Adds charger-power configuration.
templates/tesla_powerwall.yaml Adds charger-power configuration.
templates/sunsynk.yaml Adds charger-power configuration.
templates/solis_cloud.yaml Adds charger-power configuration.
templates/solax_sx4.yaml Adds charger-power configuration.
templates/solax_cloud.yaml Adds charger-power configuration.
templates/solaredge.yaml Adds charger-power configuration.
templates/solar_assistant_growatt_sph.yaml Adds charger-power configuration.
templates/solar_assistant_growatt_spa.yaml Adds charger-power configuration.
templates/sofar.yaml Adds charger-power configuration.
templates/sofar_modbus.yaml Adds charger-power configuration.
templates/sigenergy_sigenstor.yaml Adds charger-power configuration.
templates/sigenergy_cloud.yaml Adds charger-power configuration.
templates/luxpower.yaml Adds charger-power configuration.
templates/kostal.yaml Adds a charger-specific placeholder.
templates/huawei.yaml Adds a charger-specific placeholder.
templates/hanchu_cloud.yaml Adds charger-power configuration.
templates/givenergy_givtcp.yaml Adds charger-power configuration.
templates/givenergy_ems.yaml Adds charger-power configuration.
templates/givenergy_cloud.yaml Adds charger-power configuration.
templates/ginlong_solis.yaml Adds charger-power configuration.
templates/ge_cloud_octopus_standalone.yaml Adds charger-power configuration.
templates/fronius.yaml Adds charger-power configuration.
templates/fox.yaml Adds charger-power configuration.
templates/fox_cloud.yaml Adds charger-power configuration.
templates/ep_cube_cloud.yaml Adds charger-power configuration.
templates/enphase_cloud.yaml Adds charger-power configuration.
templates/alphaess_cloud.yaml Adds charger-power configuration.
docs/web-interface.md Documents the diagram changes.
docs/output-data.md Documents the published sensor.
docs/car-charging.md Documents charger-power behavior.
docs/apps-yaml.md Documents the configuration option.
apps/predbat/web.py Renders the Car power-flow node.
apps/predbat/unit_test.py Registers the new tests.
apps/predbat/tests/test_web_power_flow.py Tests charger reading and diagram behavior.
apps/predbat/tests/test_ohme.py Tests Ohme mapping.
apps/predbat/tests/test_myenergi.py Tests Zappi mapping.
apps/predbat/tests/test_ge_cloud.py Tests GECloud mapping.
apps/predbat/tests/test_gateway.py Tests Gateway mapping.
apps/predbat/tests/test_alphaess_publish.py Tests AlphaESS mapping.
apps/predbat/predbat.py Initializes charger-power state.
apps/predbat/ohme.py Maps Ohme power sensors.
apps/predbat/myenergi.py Maps Zappi power sensors.
apps/predbat/gecloud.py Maps GECloud power sensors.
apps/predbat/gateway.py Maps Gateway power sensors.
apps/predbat/execute.py Reads and publishes charger power.
apps/predbat/config.py Adds charger-power configuration schema.
apps/predbat/alphaess.py Maps AlphaESS power sensors.
Review details

Suppressed comments (3)

apps/predbat/execute.py:1075

  • This flag is set before the sensor loop and before self.car_charging_power is assigned at line 1090. The WebInterface runs in a separate thread and reads these two fields independently (web.py:591-592), so while a HA read is in progress it can render a configured Car using the previous cycle's power while load_power is already current. Compute and publish a consistent snapshot, or synchronize the paired reads/writes.
        self.car_charging_power_configured = True

apps/predbat/ohme.py:486

  • The branch is skipped for any truthy existing energy argument, including the valid explicit sensor.predbat_ohme_energy_today. In that configuration set_arg_auto("car_charging_power", POWER_WATTS_ENTITY) is never reached, so the same Ohme charger does not get automatic power wiring and the new display/sensor is absent unless power is configured separately. Distinguish the Ohme-owned energy entity (including the supported list form) from third-party energy sensors, and add a regression case.
            # Live charge power, for the web power flow diagram and the predbat.car_charging_power
            # sensor. Deliberately tied to the same decision as the energy sensor above: the two
            # have to describe the same charger, so when another charger owns the energy figure
            # Ohme's own power reading is left out rather than reported beside it.
            self.set_arg_auto("car_charging_power", POWER_WATTS_ENTITY)

docs/output-data.md:290

  • This says the sensor is published only when the key is set in apps.yaml, but the supported Ohme, myenergi, GECloud, AlphaESS and Gateway integrations can populate the runtime argument through automatic configuration without an apps.yaml entry. In those setups the sensor is still published. Describe the condition as configured in apps.yaml or by supported charger auto-discovery.
- predbat.car_charging_power - The current power drawn by your car charger in kW, only published when **car_charging_power** is set in `apps.yaml` (see [car charging](car-charging.md#configure-appsyaml-for-your-car-charging))
  • Files reviewed: 48/48 changed files
  • Comments generated: 3
  • 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/config.py Outdated
Comment thread apps/predbat/execute.py
Comment thread apps/predbat/tests/test_web_power_flow.py Outdated
springfall2008 and others added 5 commits August 24, 2026 21:12
The PV, battery and grid arms were drawn at 45 degrees when the angle between
the circles they join is 33.7, so each one left its circle off-centre and its
arrowhead stopped in open space short of the House. Every arm now runs along
the line joining the two centres, which is the geometry the new car arm
already used.

Aligning them exposed a second problem: the arrowhead is drawn beyond the end
of the line, not on it. markerUnits defaults to strokeWidth, so markerWidth
10 on a 2px stroke is 20 user units of arrowhead past the end vertex. With
the lines stopping short that overhang was invisible; ending them on the
circle edge drove the arrowheads inside the circles they point at. Each line
now stops 20 units short and the arrowhead fills the gap, landing its point
on the edge.

The power readings are placed perpendicular to their arm and symmetric about
the House, far enough out that the whole label clears the line rather than
being printed across it.

Both are covered by geometric tests that failed first: every arm is checked
tail-on-one-edge and point-on-the-other, colinear with the two centres, in
both flow directions; and every label's text box is checked against every
arm for overlap.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Each circle now carries the Material Design Icon of the thing it is - solar
panel, battery, transmission tower, home and car - taken from the @mdi/font
stylesheet the web interface already loads, drawn in white on a circle
coloured for that node: yellow PV, green battery, grey grid, brown house and
red car. The name moves into a <title>, so hovering still identifies a circle
and the markup still reads as PV/Battery/Grid/House/Car.

The battery icon follows the battery: low, medium or high by state of charge,
switching to the charging variants of the same three levels when power is
flowing in. battery-plus and battery-minus exist but carry no level, so they
would trade the state of charge away for a sign the arrow already shows.

The PV arm keeps a deeper yellow than its circle. A #FDD835 line is close to
invisible against the page, which the circle behind a 44px glyph is not.

Two problems found in the existing code while wiring the battery icon up:

- battery_charging was inverted. It read battery_power >= 10, but positive is
  DISCHARGE in Predbat (gateway.py negates the firmware's sign for exactly
  this reason) and the branch under it draws Battery -> House. Renamed to
  battery_to_house, with battery_charging now meaning power flowing in. A
  test asserts the charging icon appears on a negative reading, so it cannot
  silently invert again.
- battery_discharging and grid_exporting were computed and never used, and
  the first of them was wrong. Removed.

Every colour edit is scoped to the diagram: #4CAF50 appears 23 times in
web.py but only 12 of those are the diagram, the rest being unrelated buttons
and status text.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
apps.yaml validation flagged an unavailable charger. update_car_charging_power
deliberately reads a charger sitting at 'unavailable' as zero without recording
an error, because that is what an EV charger reports with nothing plugged into
it - but validate_config reads the same entity by a separate path and requires
its state to parse as a float, so it still ended the run reporting errors. A
transient_ok flag on the schema entry lets that one key accept Home Assistant's
unavailable/unknown placeholders. Nothing else is loosened: a missing entity is
still an error, so a typo in the name is still caught, and a state of 'banana'
is still rejected - both covered by tests.

The reader now publishes car_charging_power and car_charging_power_configured
together, once every sensor has been read. Setting the flag first left a window
where the web server, which runs in its own thread and reads the pair
independently, could draw a car declared but carrying the previous cycle's
power. fetch_inverter_data publishes its accumulated totals in one go for the
same reason.

Ohme skipped its own power sensor when car_charging_energy was already set to
its own energy entity - by hand in apps.yaml, or left behind by an earlier run.
Only a third-party energy sensor should hold the power wiring back. The
single-item list form is unwrapped before comparing, as that is how the entity
round-trips through apps.yaml.

The published sensor's documentation said it appears only when the key is set
in apps.yaml, but auto-discovery configures it for supported chargers with no
apps.yaml entry at all.

The unconfigured-publication test no longer deletes the entity by hand before
checking it is absent, so it cannot pass on the strength of that deletion, and
it now checks all four stores dashboard_item writes to rather than only the HA
state. Those stores are cleaned up afterwards so nothing leaks into later tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
car_charging_energy has the same exposure car_charging_power had: an EV charger
routinely reports 'unavailable' or 'unknown' with nothing plugged into it, and
validate_config required the state to parse as a float, so a car sitting on the
driveway left the whole run reporting configuration errors.

Nothing downstream needs the reading to be present right now. minute_data()
skips unavailable and unknown samples outright (utils.py), so the daily
incrementing series is built from the readings that exist, and a charger that
really has stopped reporting is already covered by a far better targeted
warning in fetch.py - car_charging_hold falling back to the threshold heuristic
because no data could be loaded.

transient_ok therefore applies to this key too. Validation is not loosened
anywhere else: a state of 'banana' is still an error and an entity that does
not exist is still an error, so a typo in the name is still caught. Both keys
now carry all five cases in the validator's own tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The advice was to wrap a charger reporting 'unavailable' or 'unknown' in a
template substituting zero. That is unnecessary and actively harmful.

Unnecessary because minute_data() skips unavailable and unknown samples when
it loads the history (utils.py), and validate_config no longer reports them as
a configuration error, so such a sensor works as it is.

Harmful because clean_incrementing_reverse treats any zero as a reset
(utils.py:740 - "if nxt <= 0 ..."). On a daily incrementing sensor a zero part
way through the day is therefore indistinguishable from the midnight reset, so
the series rebases there and the charging recorded before it is counted twice.

The utility meter advice above it is unchanged - that addresses a different
sensor shape, one reporting cumulative energy per charge rather than per day.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@springfall2008
springfall2008 merged commit e3ada1a into main Aug 25, 2026
2 checks passed
@springfall2008
springfall2008 deleted the feat/car-charging-power-flow-diagram branch August 25, 2026 08:19
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