Skip to content

feat(ev): add Tesla Wall Connector Gen 3 local HTTP - #978

Merged
frahlg merged 5 commits into
masterfrom
cursor/tesla-wall-connector-7a7a
Aug 29, 2026
Merged

feat(ev): add Tesla Wall Connector Gen 3 local HTTP#978
frahlg merged 5 commits into
masterfrom
cursor/tesla-wall-connector-7a7a

Conversation

@frahlg

@frahlg frahlg commented Aug 25, 2026

Copy link
Copy Markdown
Member

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

  • New evcloud provider tesla-wc: LAN HTTP, no auth. ListChargers reads GET /api/1/version (serial) and falls back to GET /api/1/vitals. Bare nan in vitals is repaired so the wizard probe matches the Lua driver.
  • Config EVCharger.Validate() accepts tesla-wc. Modbus is rejected. Leftover Easee/Zaptec username/password are stripped so a provider switch can save after POST /api/config restores ev_charger_password from state.db. Empty http.base_url is allowed as a wizard placeholder.
  • Setup wizard adds Tesla Wall Connector with an http-local host field and writes ev_charger.http.base_url.
  • Settings → EV shows a host field when tesla-wc is selected and clears cloud credentials on the switch. Devices treats an EV-only HTTP driver with no catalog http_hosts as a LAN host, not a cloud account.
  • Lua driver in go/internal/drivers/testdata/tesla_wall_connector.lua (not bundled). Polls vitals, emits ev power / connected / charging / session_wh. Repairs bare nan JSON. 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_vehicle driver. 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

  • No write to the wall connector. A failed command must not mark the driver dead, so set-current acks.
  • Stale / missing vitals skip the emit so the watchdog can see the fault.
  • driver_default_mode is a no-op: the box keeps the car's last request.
  • Lua stays in testdata until srcfl/device-drivers includes it. Operators can drop the same file in the user-drivers directory.
  • Experimental until a live Gen 3 is exercised.

Verification

Local, after the extra test pass:

go test ./internal/evcloud ./internal/config -count=1
go test ./internal/drivers -count=1 -run TeslaWallConnector -v
node --test web/setup.test.mjs web/settings/tabs/ev.test.mjs
npm test

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

Open in Web Open in Cursor 

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>
@frahlg
frahlg marked this pull request as ready for review August 25, 2026 13:22

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment thread web/settings/tabs/ev.js
Comment on lines +67 to +69
providerSel.addEventListener("change", function () {
ctx.captureCurrentTab();
ctx.renderTab("ev");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment on lines +537 to +538
// Empty base_url is allowed so the wizard can write provider
// intent before the host is filled in.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment thread go/internal/config/config.go Outdated
Comment thread go/internal/evcloud/tesla_wc.go

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale comment

Left a non-blocking comment: Cursor Bugbot finished as skipped and reported 2 unresolved issues, so this PR is not approved. Assigned erikarenhill and HuggeK for human review.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@cursor
cursor Bot requested review from Leitet and erikarenhill August 25, 2026 13:28

@miravoss26 miravoss26 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.

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.

cursoragent and others added 2 commits August 25, 2026 17:04
…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"))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9c2a205. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale comment

Left a non-blocking comment: Cursor Bugbot finished as skipped, so this PR is not approved. Reviewers were already assigned; none added.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@miravoss26 miravoss26 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.

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.
  • getJSON bounds 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.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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).

Fix All in Cursor

❌ 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.

Comment thread web/settings/tabs/ev.js
ev.email = "";
ev.password = "";
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1b1eeb5. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale comment

Left a non-blocking comment: Cursor Bugbot finished as skipped, so this PR is not approved. Reviewers were already assigned; none added.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

if err != nil {
return nil, err
}
resp, err := t.client.Do(req)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 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.

Fix in Cursor Fix in Web

Reviewed by Cursor Security Reviewer for commit 1b1eeb5. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Left a non-blocking comment: Cursor Bugbot finished as skipped, so this PR is not approved. Reviewers were already assigned; none added.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@frahlg
frahlg merged commit a95a971 into master Aug 29, 2026
18 checks passed
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.

3 participants