Skip to content

fix(tesla_wall_connector): no charge power through an open contactor (0.1.2) - #204

Open
frahlg wants to merge 3 commits into
mainfrom
fix/tesla-wc-idle-phantom-power
Open

frahlg wants to merge 3 commits into
mainfrom
fix/tesla-wc-idle-phantom-power

Conversation

@frahlg

@frahlg frahlg commented Oct 30, 2026

Copy link
Copy Markdown
Member

What happened

A Tesla Wall Connector Gen 3 showed charge power with no car on the cable.
The setup was FTW v0.157.3-beta.1 as the Home Assistant app, reading the
charger over HTTP. The device connection test read:

EV: 184 W
ev_l1_a: 0.80
ev_l1_v: 0.00
ev_power_w: 184
evse_state: 1.00

The flow diagram showed 284 W. Home Assistant's own Wall Connector
integration showed, at the same time: total power 0 W, vehicle disconnected,
contactor open, phase A 0.80 A at 0 V, phase B 0.60 A at 0 V.

Cause

The Gen 3 measures its leg voltages on the car side of the contactor. With
the contactor open they read 0 V. The leg currents still read sensor offset,
here 0.8 A and 0.6 A.

0.1.1 summed V × A over the legs and skipped legs under 50 V, so the sum was
0 W. On a sum of 0 it fell back to grid_v × vehicle_current_a. grid_v is
measured on the grid side of the open contactor, about 230 V, and
vehicle_current_a carries the same offset: 230 V × 0.8 A = 184 W. So the
driver did not use a fixed 230 V, but it used a voltage the car could not
see. It never looked at contactor_closed or vehicle_connected before it
computed power; it used them only for charging and connected.

The 284 W is not phase B adding in. Legs at 0 V never count. It is most
likely another sample of the same fallback with a larger offset, for example
1.2 A × 237 V, which Core shows smoothed. The tests cover that sample too.

Fix

  • Charge power and charging need a closed contactor. With the contactor
    open, no current reaches a car. A reply without contactor_closed falls
    back to vehicle_connected, then to the EVSE charging states (10 and 11).
  • An explicit contactor_closed goes first. The contactor is the physical
    switch and closes only with a car on the cable, so a status flag does not
    cancel volts and amps measured through it.
  • The grid_v × vehicle_current_a fallback now runs only when the reply has
    no leg voltage at all. A leg that reads 0 V carries no power. Home
    Assistant computes total power the same way, V × A per leg with no
    fallback, which is why it read 0 W.
  • charging follows the contactor. Before, EVSE state 10 or 11 said
    charging even with the contactor open.
  • Measured leg power through a closed contactor and split-phase installs do
    not change.
  • ev_l1_a and ev_l1_v still show the raw sensor reading, so the
    connection test keeps showing 0.80 A next to EV 0 W.
  • The sign rule holds: charging is positive.

Version 0.1.1 → 0.1.2 (make bump-driver), with a CHANGELOG entry.

What the phantom did in FTW Core

No Core change in this PR. Read on origin/master (1c2ffbc0):

  • Flow diagram and API show the driver's EV power (ev_w, smoothed).
  • Home use is grid − PV − battery − EV
    (go/internal/telemetry/forecast.go). A 184–284 W phantom made Home use
    read that much too low. When the house drew less than the phantom, the
    balance went negative and Home use was marked invalid
    (invalid_balance). The load models learn from these values.
  • Battery dispatch (go/internal/control/dispatch.go) counts EV power
    over 100 W (evActiveThresholdW) as a car charging. In planner modes with
    "battery covers EV" off, the battery then leaves that share to the grid,
    so the house could import 184–284 W that the battery would otherwise
    cover.
  • Fuse rule: no risk found. The fuse limit works from the site meter.
    The EV fuse budget counts only Core's own commands, and this read-only
    charger gets none. The phantom reached only
    republishFuseEVCapAfterFuseDischarge, while the fuse was saturated, and
    the Wall Connector cannot act on that cap.

Tests

Failing test first (a1c747e), fix second (93c02db), review changes third
(34e69b8). The new harness test runs the driver through the repo's host
mock:

  • idle, contactor open, 0 V, 0.8 / 0.6 A offset → 0 W (0.1.1 gives 184.0 W)
  • the larger offset sample → 0 W
  • reply without contactor_closed, no car → 0 W
  • car on the cable, contactor open → 0 W, connected, not charging
  • EVSE state 11 with the contactor open → 0 W, not charging
  • contactor says closed, every leg at 0 V → 0 W (no grid_v stand-in)
  • split-phase, idle → 0 W
  • three-phase charging, 16 A → 11 107 W, 3 phases, charging
  • one-phase car, ghost voltages on B and C → 3 680 W, 1 phase
  • contactor closed, measured leg, vehicle_connected false → 3 680 W
  • contactor closed, legs at about 0 V → 0 W
  • reply with no leg voltages → 2 300 W from grid_v
  • split-phase charging → 7 680 W
  • reply without contactor_closed, EVSE state 11 → 3 680 W

The vitals field names and shape follow the Gen 3 /api/1/vitals reply.
The report did not show grid_v or vehicle_current_a; the test uses
230 V and 0.8 A, the values that give the reported 184 W.

Run on this machine at 34e69b8:

make test-driver ID=tesla_wall_connector   40 passed, 13 skipped
make check                                 5008 passed, 936 skipped

The new harness test, run against 0.1.1 from origin/main, stops at the
first case with idle unit with open contactor reported 184.0 W.

Not in this PR

  • FTW pin. FTW bundles drivers from the commit in
    drivers/BUNDLED_SOURCE.json, now 46b28a1 with 0.1.1. Move the pin in a
    separate FTW PR after this merges.
  • FTW's test copy. go/internal/drivers/testdata/tesla_wall_connector.lua
    in FTW is an older copy with its own Go tests. It keeps the old fallback.
    The pin PR should refresh it or point those tests at the bundled driver.
    Its TestTeslaWallConnectorPhaseFallbackToGrid case (legs at 2/1/0 V)
    expects the old fallback and would read 0 W against 0.1.2.
  • One-phase car on a three-phase unit. With the contactor closed, legs B
    and C read grid voltage, so their offset (about 0.5–1 A each) adds about
    100–250 W while the car charges, and phases reads 3. Fixing that needs
    vitals from such a session.
  • A reply with none of contactor_closed, vehicle_connected and
    evse_state now reads 0 W, where 0.1.1 summed the legs. Gen 3 vitals
    carry all three; this only matters for a reply that does not.
  • connected still trusts the EVSE state list when vehicle_connected is
    false. That predates this change.
  • The driver header still says it is FTW's in-tree test copy. That is stale.
  • Ask the reporter for one raw /api/1/vitals reply from the idle unit, to
    confirm grid_v and vehicle_current_a.

Review

An independent review (Sonnet subagent, given the code and the field
report, told not to trust the commit messages) found nothing that blocks.
It ran the first test version against 0.1.1 (8 cases fail there; the
charging cases pass on both) and reached the same cause: the fallback is
the only path in 0.1.1 that gives power with every leg at 0 V.

Acted on:

  • Order of the checks. The first version let vehicle_connected=false
    cancel an explicit contactor_closed=true, which could hide measured
    load. The contactor now goes first (34e69b8). That opened a path back to
    the phantom (closed contactor, 0 V legs, grid_v fallback), so the
    fallback now needs a reply with no leg voltage.
  • Wording. The report did not show grid_v or vehicle_current_a. The
    CHANGELOG and test comments now say which values are chosen to match the
    reported watts.

Left as is:

  • contactor_closed given as 1 or "true" reads as open. Gen 3 sends
    JSON booleans.
  • The missing-fields and connected notes above.

🤖 Generated with Claude Code

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-30T07:59:42.405335Z 34e69b8 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

frahlg and others added 3 commits October 30, 2026 09:00
The vitals of an idle Gen 3 (no car, contactor open, 0 V on every leg,
0.8 A and 0.6 A of sensor offset) give 184 W today: the same number a
field connection test showed. Adds the idle, car-waiting and charging
cases. Fails on 0.1.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
…(0.1.2)

With the contactor open the output legs read 0 V, the leg sum was 0, and
the driver fell back to grid_v × vehicle_current_a. That multiplied the
grid-side voltage by the sensor offset of an idle unit and reported
about 180-290 W to a car that was not there.

Charge power and charging now need a closed contactor and a car on the
cable. A reply without contactor_closed falls back to the EVSE charging
states. Power through a closed contactor is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
… leg voltage

From review. An explicit contactor_closed now goes before
vehicle_connected: the contactor is the physical switch, and a status
flag should not cancel volts and amps measured through it. With that
order, a closed contactor with every leg at 0 V would still have reached
the grid_v fallback, so the fallback now runs only when the reply has no
leg voltage at all. A leg that reads 0 V carries no power, as in Home
Assistant's own calculation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
@frahlg
frahlg force-pushed the fix/tesla-wc-idle-phantom-power branch from 34e69b8 to 060567a Compare October 30, 2026 08:00

This branch has not been deployed

No deployments
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