feat(ev): add Tesla Wall Connector Gen 3 local HTTP - #978
Conversation
Register tesla-wc as a LAN HTTP EV provider next to Easee and Zaptec. The wizard and Settings take a host; the picker reads /api/1/version with a vitals fallback. The Lua driver emits plug state, power and session energy. The box has no local current setpoint — commands ack and tesla_vehicle steers a Tesla. Testdata until the device-drivers pin includes it. Signed-off-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e2a3bb555d
ℹ️ 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".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| Label: "Tesla Wall Connector", | ||
| Transport: TransportHTTP, | ||
| NeedsAuth: false, | ||
| LuaDriver: "drivers/tesla_wall_connector.lua", |
There was a problem hiding this comment.
Install the driver before advertising the provider
The normal setup path cannot start this driver: a repo-wide search at this commit finds tesla_wall_connector.lua only under go/internal/drivers/testdata, while the wizard writes only ev_charger and the runtime loads actual devices from Config.Drivers. This descriptor therefore names a nonexistent production path, so selecting and saving Tesla succeeds but FTW never polls vitals or emits the advertised telemetry. Publish/install the driver from the canonical driver repository and create the corresponding driver config before exposing this provider.
AGENTS.md reference: AGENTS.md:L12-L18
Useful? React with 👍 / 👎.
| providerSel.addEventListener("change", function () { | ||
| ctx.captureCurrentTab(); | ||
| ctx.renderTab("ev"); |
There was a problem hiding this comment.
Clear cloud credentials when switching to Tesla
When an existing Easee or Zaptec user selects tesla-wc, this handler captures and retains the now-hidden ev_charger.username/email and password before rerendering. The config API also restores a stored password when the posted password is empty, but Tesla validation rejects any username or password, so saving the provider switch consistently returns HTTP 400. Clear the cloud-only credential fields when changing to the local provider.
Useful? React with 👍 / 👎.
| // Empty base_url is allowed so the wizard can write provider | ||
| // intent before the host is filled in. |
There was a problem hiding this comment.
Reject a Tesla configuration without a host
The setup wizard can select Tesla and finish without entering or probing a host; buildConfig then emits only {provider: "tesla-wc"}, and this explicit allowance lets the final /api/config save succeed. Both ListChargers and the Lua driver's initialization require an address, so the persisted charger is unusable. Require a nonempty http.base_url on the final save or enforce the field in the wizard rather than accepting this provider-only configuration.
Useful? React with 👍 / 👎.
miravoss26
left a comment
There was a problem hiding this comment.
Reviewed the Tesla Wall Connector Gen 3 driver.
What it does: adds a tesla-wc evcloud provider (unauthenticated local HTTP, /api/1/version + /api/1/vitals) plus the matching Lua driver, config validation, and setup-wizard/settings UI. Observation-only — no local current setpoint, so ev_set_current/pause/resume ack without writing hardware and log a one-time warning; control of a Tesla stays on tesla_vehicle, matching the PR's stated scope split with #732 (Charge Amps/OCPP).
Correctness: Validate() correctly rejects a Modbus block and username/password for tesla-wc, and allows an empty base_url as a wizard placeholder. The Lua driver's phase-power math ignores ghost voltages (<50V) on unused legs and falls back to grid_v × vehicle_current_a when the three-phase legs don't resolve — reasonable for a device that ships in both single- and split-phase configurations. repair_json patching bare nan and unbalanced braces is a pragmatic guard against known Tesla firmware quirks, scoped narrowly (regex + brace count, not a general parser replacement).
Security: getJSON/http_get calls are timeout-bounded (15s) and the Go side caps the response body at 1MB (io.LimitReader) before decoding — good, since this is an unauthenticated device response being trusted. No secrets, no code execution, no injection surface; base URL is user-supplied LAN host/IP scoped to this box, same pattern as the existing Modbus providers.
CI's all green (one Cursor Security Agent run still shows in-progress in the JSON I read but everything else passed). Safe to merge from my read.
…switch Lock in ghost-phase ignore, split-phase, idle, JSON repair, and a vitals-NaN wizard probe. Switching from Easee/Zaptec drops leftover cloud credentials so tesla-wc can save. Signed-off-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Signed-off-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
| return raw | ||
| } | ||
| return teslaWCNaN.ReplaceAll(raw, []byte("null")) | ||
| } |
There was a problem hiding this comment.
Wizard JSON repair incomplete
Low Severity
teslaWCRepairJSON claims to match the Lua repair_json so the wizard probe accepts the same vitals bodies as the runtime driver, but it only rewrites bare nan and never closes truncated objects. Firmware that drops a trailing brace (already handled in Lua and covered by driver tests) still fails ListChargers when /api/1/version is missing and the probe falls back to vitals.
Reviewed by Cursor Bugbot for commit 9c2a205. Configure here.
miravoss26
left a comment
There was a problem hiding this comment.
Reviewed the Tesla Wall Connector Gen 3 local-HTTP provider (tesla-wc).
Correctness: matches the accepted scope (Charge Amps stays on the OCPP path in #732, this is the parallel Tesla track). ListChargers reads /api/1/version, falls back to /api/1/vitals, and the nan-repair regex mirrors the existing Lua driver's repair_json so the wizard probe and runtime driver agree on the same malformed-firmware body. Config validation correctly rejects Modbus for this provider and strips leftover Easee/Zaptec username/password + EmailLegacy on switch, which is what lets POST /api/config save cleanly instead of re-restoring a stale cloud password from state.db. Tests cover the HTTP client (incl. the nan repair) and the config-switch stripping.
Security screen:
base(the LAN origin) comes from user-supplied config (cfg.HTTP.BaseURL), never from the wall connector's response — no SSRF-via-response-following risk.getJSONbounds the read (io.LimitReader(..., 1<<20)) and the client has a 15s timeout, so a slow/oversized LAN device can't hang or exhaust memory.- No new secrets, no auth (matches the box's own no-auth API, called out explicitly in the doc comment), no new external network destinations — this is LAN-only, same trust level as the existing Easee/Zaptec/CTEK providers.
No findings. Safe to merge from my read.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 1b1eeb5. Configure here.
| ev.email = ""; | ||
| ev.password = ""; | ||
| } | ||
| } |
There was a problem hiding this comment.
Leftover Tesla URL overrides cloud
Medium Severity
Switching away from tesla-wc captures ev_charger.http.base_url and never clears it. Easee and Zaptec treat that field as the cloud API origin, so a leftover Wall Connector LAN address is used instead of the vendor API after a provider switch.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 1b1eeb5. Configure here.
| if err != nil { | ||
| return nil, err | ||
| } | ||
| resp, err := t.client.Do(req) |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: MEDIUM
ListChargers concatenates the operator-supplied http.base_url onto /api/1/version and /api/1/vitals, then fetches with a default http.Client that follows redirects and does not pin scheme or host. tesla-wc is NeedsAuth: false, so bootstrap POST /api/ev/chargers (and Configure after install) will GET an arbitrary URL. A base such as http://127.0.0.1:PORT/secret? drops the intended path suffix; a 302 can retarget the GET. A 2xx JSON object can echo serial_number/serialNumber back to the caller.
Impact: On an unconfigured box, any LAN client can use FTW as an HTTP GET proxy to loopback or other origins, including following redirects. The Lua HTTP host already re-checks allowed_hosts on redirects; this wizard probe does not.
Reviewed by Cursor Security Reviewer for commit 1b1eeb5. Configure here.




Accepted text proposal
Issue or Discussion: this conversation — Tesla Wall Connector is a given next charger; Charge Amps stays on the OCPP path in #732.
Maintainer comment that accepted this scope: “ok tesla wall connector är given den ska in” / “låt oss göra dessa parallellt”.
What changed
evcloudprovidertesla-wc: LAN HTTP, no auth.ListChargersreadsGET /api/1/version(serial) and falls back toGET /api/1/vitals. Barenanin vitals is repaired so the wizard probe matches the Lua driver.EVCharger.Validate()acceptstesla-wc. Modbus is rejected. Leftover Easee/Zaptec username/password are stripped so a provider switch can save afterPOST /api/configrestoresev_charger_passwordfrom state.db. Emptyhttp.base_urlis allowed as a wizard placeholder.http-localhost field and writesev_charger.http.base_url.tesla-wcis selected and clears cloud credentials on the switch. Devices treats an EV-only HTTP driver with no cataloghttp_hostsas a LAN host, not a cloud account.go/internal/drivers/testdata/tesla_wall_connector.lua(not bundled). Polls vitals, emitsevpower / connected / charging /session_wh. Repairs barenanJSON.ev_set_current/ pause / resume ack and log; the box has no local setpoint.Why
Tesla Wall Connector Gen 3 is common on Nordic Tesla sites. Its local API is observation-only. Control of a Tesla stays on the existing
tesla_vehicledriver. Charge Amps is not in this PR — it points at local OCPP (#732).Protocol is the public LAN API (
/api/1/vitals,/api/1/version), documented by Home Assistant / ioBroker field notes. evcc charger source was not copied.Boundaries and safety
driver_default_modeis a no-op: the box keeps the car's last request.srcfl/device-driversincludes it. Operators can drop the same file in the user-drivers directory.Verification
Local, after the extra test pass:
All passed. New cases: ghost-phase ignore, split-phase vs per-leg sum, idle, missing brace, vitals HTTP miss, vitals-NaN wizard probe, camelCase serial, leftover cloud creds stripped, Settings EV tab.
OCPP #979 was re-run separately in
/tmp/ftw-ocpp(go test ./internal/ocpp ./internal/config ./internal/loadpoint ./internal/api+ loadpoints/setup web tests) — all passed. No live Wall Connector in this environment.Checklist
api.go; this PR stays on evcloud / config tesla-wc / wizard / testdata.api.gowas not edited.