From 9e32672e450d2a7e643d26f6a8f8d05b82d67959 Mon Sep 17 00:00:00 2001 From: Fredrik Ahlgren Date: Sun, 30 Aug 2026 11:31:18 +0200 Subject: [PATCH] fix(ocpp): first UI enable works, and the save tells the truth about restarting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exercised #999's Chargers panel in a browser and hit two defects on the first enable: The username field rendered empty with a "ftw" placeholder, so a fresh save posted username: "" and validation rejected it — every first enable failed with a 400 the operator had no reason to expect. The field now carries the real default. Worse, after fixing the payload the save reported "no restart needed" while the listener never opened: the central system is started once in main.go and the config applier neither starts nor stops it, but RestartRequiredFor had no ocpp entry — the exact silent failure that file's own comment warns about. Saving an OCPP change now raises the restart prompt, and after a restart the port listens (verified live: enable → save → prompt names ocpp → restart → 8887 accepting). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01C1d4uknzs7QUv7nE9rW4fi --- .changeset/ocpp-ui-honest-toggle.md | 11 +++++++++++ go/internal/config/restart_required.go | 8 ++++++++ go/internal/config/restart_required_test.go | 3 +++ web/settings/tabs/loadpoints.js | 2 +- web/settings/tabs/loadpoints.test.mjs | 4 ++++ 5 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 .changeset/ocpp-ui-honest-toggle.md diff --git a/.changeset/ocpp-ui-honest-toggle.md b/.changeset/ocpp-ui-honest-toggle.md new file mode 100644 index 000000000..258772fc3 --- /dev/null +++ b/.changeset/ocpp-ui-honest-toggle.md @@ -0,0 +1,11 @@ +--- +"ftw": patch +--- + +Enabling the OCPP server from the Chargers panel now works on the first +try: the username field carries the real default ("ftw") instead of a +placeholder that validation then rejected, and saving an OCPP change +honestly reports that a restart is required — the central system +listener only starts at boot, so the previous "no restart needed" +answer left the port silently closed after an apparently successful +save. diff --git a/go/internal/config/restart_required.go b/go/internal/config/restart_required.go index 48e12ce67..79582b35b 100644 --- a/go/internal/config/restart_required.go +++ b/go/internal/config/restart_required.go @@ -77,6 +77,14 @@ func RestartRequiredFor(oldCfg, newCfg *Config) []string { if oldCfg.FleetPing.Resolved() != newCfg.FleetPing.Resolved() { reasons = append(reasons, "fleet_ping.endpoint — the sender resolves its endpoint at startup") } + // The OCPP central system is started once in main.go; the config + // applier neither starts, stops nor re-arms it. Without this entry + // the Chargers panel's enable toggle saved cleanly, reported no + // restart needed, and the listener never opened — the exact silent + // failure the comment at the top of this file warns about. + if !pointerEqual(oldCfg.OCPP, newCfg.OCPP) { + reasons = append(reasons, "ocpp — the central system listener is started at startup") + } if !pointerEqual(oldCfg.EVCharger, newCfg.EVCharger) { reasons = append(reasons, "ev_charger — EV charger client is constructed once at startup") } diff --git a/go/internal/config/restart_required_test.go b/go/internal/config/restart_required_test.go index 0a3f1286e..b51ef189e 100644 --- a/go/internal/config/restart_required_test.go +++ b/go/internal/config/restart_required_test.go @@ -83,6 +83,9 @@ func TestRestartRequiredFor_BootSections(t *testing.T) { {"ev_charger added", func(c *Config) { c.EVCharger = &EVCharger{Provider: "easee", Username: "a@b.c"} }, "ev_charger"}, + {"ocpp enabled", func(c *Config) { + c.OCPP = &OCPP{Enabled: true, Port: 8887, Username: "ftw", Password: "long-random-string"} + }, "ocpp"}, {"caldav credentials changed", func(c *Config) { c.CalDAV = &CalDAV{Enabled: true, Username: "calendar-user", Password: "rotated"} }, "caldav"}, diff --git a/web/settings/tabs/loadpoints.js b/web/settings/tabs/loadpoints.js index b195f499a..01bb1bfc7 100644 --- a/web/settings/tabs/loadpoints.js +++ b/web/settings/tabs/loadpoints.js @@ -142,7 +142,7 @@ '' + '
' + '' + - '' + + '' + '
' + '' + '
' + diff --git a/web/settings/tabs/loadpoints.test.mjs b/web/settings/tabs/loadpoints.test.mjs index 3befb0274..ab2ecf95a 100644 --- a/web/settings/tabs/loadpoints.test.mjs +++ b/web/settings/tabs/loadpoints.test.mjs @@ -196,6 +196,10 @@ describe("OCPP server form", () => { // Defaults an operator would otherwise have to go and look up. assert.match(html, /data-path="ocpp\.port" value="8887"/); assert.match(html, /data-path="ocpp\.path" value="\/"/); + // The username default is a real value, not a placeholder: validation + // requires it when the server is enabled, so a field that only hinted + // "ftw" made every first enable fail with a 400. + assert.match(html, /data-path="ocpp\.username" value="ftw"/); }); it("never renders the stored password back into the page", () => {