Repository navigation
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
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
force-pushed
the
fix/tesla-wc-idle-phantom-power
branch
from
October 30, 2026 08:00
34e69b8 to
060567a
Compare
This branch has not been deployed
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.
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:
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_vismeasured on the grid side of the open contactor, about 230 V, and
vehicle_current_acarries the same offset: 230 V × 0.8 A = 184 W. So thedriver did not use a fixed 230 V, but it used a voltage the car could not
see. It never looked at
contactor_closedorvehicle_connectedbefore itcomputed power; it used them only for
chargingandconnected.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
chargingneed a closed contactor. With the contactoropen, no current reaches a car. A reply without
contactor_closedfallsback to
vehicle_connected, then to the EVSE charging states (10 and 11).contactor_closedgoes first. The contactor is the physicalswitch and closes only with a car on the cable, so a status flag does not
cancel volts and amps measured through it.
grid_v × vehicle_current_afallback now runs only when the reply hasno 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.
chargingfollows the contactor. Before, EVSE state 10 or 11 saidcharging even with the contactor open.
not change.
ev_l1_aandev_l1_vstill show the raw sensor reading, so theconnection test keeps showing 0.80 A next to EV 0 W.
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):ev_w, smoothed).(
go/internal/telemetry/forecast.go). A 184–284 W phantom made Home useread 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.go/internal/control/dispatch.go) counts EV powerover 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.
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, andthe 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:
contactor_closed, no car → 0 Wgrid_vstand-in)vehicle_connectedfalse → 3 680 Wgrid_vcontactor_closed, EVSE state 11 → 3 680 WThe vitals field names and shape follow the Gen 3
/api/1/vitalsreply.The report did not show
grid_vorvehicle_current_a; the test uses230 V and 0.8 A, the values that give the reported 184 W.
Run on this machine at 34e69b8:
The new harness test, run against 0.1.1 from
origin/main, stops at thefirst case with
idle unit with open contactor reported 184.0 W.Not in this PR
drivers/BUNDLED_SOURCE.json, now 46b28a1 with 0.1.1. Move the pin in aseparate FTW PR after this merges.
go/internal/drivers/testdata/tesla_wall_connector.luain 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
TestTeslaWallConnectorPhaseFallbackToGridcase (legs at 2/1/0 V)expects the old fallback and would read 0 W against 0.1.2.
and C read grid voltage, so their offset (about 0.5–1 A each) adds about
100–250 W while the car charges, and
phasesreads 3. Fixing that needsvitals from such a session.
contactor_closed,vehicle_connectedandevse_statenow reads 0 W, where 0.1.1 summed the legs. Gen 3 vitalscarry all three; this only matters for a reply that does not.
connectedstill trusts the EVSE state list whenvehicle_connectedisfalse. That predates this change.
/api/1/vitalsreply from the idle unit, toconfirm
grid_vandvehicle_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:
vehicle_connected=falsecancel an explicit
contactor_closed=true, which could hide measuredload. The contactor now goes first (34e69b8). That opened a path back to
the phantom (closed contactor, 0 V legs,
grid_vfallback), so thefallback now needs a reply with no leg voltage.
grid_vorvehicle_current_a. TheCHANGELOG and test comments now say which values are chosen to match the
reported watts.
Left as is:
contactor_closedgiven as1or"true"reads as open. Gen 3 sendsJSON booleans.
connectednotes above.🤖 Generated with Claude Code