Skip to content

fix(loadpoint): Charge now keeps charging across a charger reconnect - #1848

Merged
frahlg merged 2 commits into
masterfrom
fix/charge-now-socket-reconnect
Oct 25, 2026
Merged

frahlg merged 2 commits into
masterfrom
fix/charge-now-socket-reconnect

Conversation

@frahlg

@frahlg frahlg commented Oct 25, 2026 •

Copy link
Copy Markdown
Member

Problem

A charger's socket drop stopped Charge now. When an OCPP charger lost its
Wi-Fi and came back, still plugged in and with the same serial, FTW turned
Charge now into a saved 0 W pause with manual_restore_unconfirmed and
waited for someone to press Start. No notice went out, since FTW ordered the
0 W itself. Fredrik's rule (#1772):
Charge now runs until Stop, unplug or the car stops asking; nothing else
stops it. Decision in #1788: the
same charger back without an observed unplug keeps Charge now, as
#1778 does after a restart.

Change

go/internal/loadpoint/controller_hold_restore.go, restoreManualHoldForSession:

  • A new socket (OCPP connection_generation) or a missing hardware identity
    no longer counts as a change. The hold keeps its power, and the binding
    keeps the last proven charger (proofLost).
  • When the charger proves itself again: the same serial keeps Charge now;
    another serial, or a session ID that differs from the one before, pauses
    it and asks for Start, as before.
  • Unchanged: an observed unplug, Stop, a car that stops asking and a config
    change end it; a counter reset or new session on the same socket still
    pauses it; FTW sends nothing while the socket is down; the fuse guard.
  • A Start pressed before the charger ever proved its identity binds to the
    first proof, as before, now also across a socket drop.

session_state.go: one comment that described the old rule. No setting, no
timer, no new state on disk.

The accepted risk, as in #1778: another car plugged in during the outage,
with the unplug unseen, gets Charge now until it is full, unplugged or
stopped. It costs money at worst.

Tests that encoded the old rule

Each now says the opposite, because each asserted the pause this PR removes:

  • TestManualStartWithoutHardwareCannotFollowAnotherSocket →
    TestManualStartBeforeHardwareProofSurvivesAReconnect. It asserted that a
    new socket pauses a Start made before any hardware proof. Now the Start
    keeps 4 140 W across the new socket, binds to the first proof (easee:A),
    keeps it across another socket, and pauses with unconfirmed only when
    easee:B turns up.
  • TestLostHardwareProofPausesOldStartButAllowsANewExplicitStart →
    TestLostHardwareProofKeepsStartUntilAnotherSessionProvesItself. It
    asserted that a lost identity pauses the Start. Now the Start keeps
    4 140 W while the identity is gone and when easee:A/session-1 returns;
    session-2 on the same hardware pauses it; a new Start works and binds
    to session-2; easee:B after another loss pauses again.
  • TestOCPPReconnectDoesNotUnplugOrResumeOldStart →
    TestOCPPReconnectIsNotAnUnplug (real OCPP server and charge point). It
    asserted 0 W during the outage and after any reconnect. Now the hold keeps
    its power during the outage (still no command, still unavailable), the
    same serial keeps it after the reconnect, and another serial pauses it.
    The checks for no invented plug edge, a real unplug and a fresh Start stay.

New: TestChargeNowSurvivesOCPPSocketBlip (cmd/ftw, real OCPP path) holds
the rig's s13 criteria at unit level, at 11 040 W: the same charger, with
and without a tick during the outage, gets only 11 040 W after the return,
stays plugged in and raises no plug-in event; another serial pauses;
Available after the return clears the hold.

The three rewritten tests, this one and the two from the review (below)
fail before this change and pass after. go vet and go test for
internal/loadpoint, cmd/ftw, internal/ocpp/... and test/site pass on
the final commit; make verify passed on the commit before the review fix.

Site rig s13

s13-25 ("Charge now goes on after the charger returns", charger gone for
10 s) was a known failure for #1788. It passes with this change, so its
known line and the README mark go, as the rig's rules ask.

make siterig SITERIG_SET=s13-25 on this branch: pass in 4 min 40 s.
Car at least 10 957 W from the drop to the end, 10 972 W over the last
60 s, no sample with the car unplugged.

From #1788, the earlier rig copy at 11 040 W with the proxy killed for 15 s:

Before (#1778 + #1779, as on master) After (this change on the same base)
Car while the socket is down 10 957 W or more 10 957 W or more
Car after the charger returns 0 W within 2 s, paused, unconfirmed 10 957 W or more
Last 60 s, 150 s after the return 0 W 10 971 W, charging

No time window

The hold has no timer: the same charger keeps Charge now after a drop of any
length. A window would end Charge now after a longer outage, which protects
no hardware and breaks the rule in #1772. Proof of the charger decides, as
#1778 does after a restart.

Review

An independent review (Claude Sonnet) found no blocker. Acted on:

  • Fixed: a driver that reports its session after its hardware (Easee
    cloud) could come back as {A, ""} after a lost proof, and a later
    {A, session-2} then inherited Charge now. The binding now keeps the last
    proven session through the gap, and a different non-empty session pauses
    in every path (otherSession). Test:
    TestLostProofCannotHandStartToALateNewSession, which fails before.
  • Added: TestChargeNowKeptAcrossReconnectSurvivesARestart: a kept hold
    leaves the saved record unchanged, so a restart after a drop restores
    4 140 W. Fails before.
  • Tightened: the blip test fails if no command follows the return.

Accepted as they are, each within the risk in #1788:

  • A charger that reports status before its Boot gets the held power for the
    ticks until its identity arrives; another serial then pauses it on its
    first proof. The fuse guard applies throughout.
  • A Start or slider change while the identity is missing binds to the next
    proof, as a Start before the first proof always has.
  • A new OCPP transaction (session energy falls) across a reconnect does not
    end Charge now; that is the stricter variant left out of Should a charger socket reconnect end Charge now? #1788.

The s13 rig run above used the commit before the session fix; that fix only
changes drivers that report session IDs, and OCPP reports none.

Order

On master after #1846, whose guard fails any test that dials with ocpp-go's
plain client; the new test uses its chargersocket.ChargePoint16. #1837
(draft) edits the same rig files on other lines.

Not in this PR

  • The same drop also forgets the SoC the owner entered
    (observeConnectionProof clears socConfirmed), so "Charge now to 80 %"
    counts from an assumed level afterwards. Same question, separate change.
  • A stricter check that a new OCPP transaction after the return means
    another car (Should a charger socket reconnect end Charge now? #1788) is not part of this change.

🤖 Generated with Claude Code

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 25, 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-25T13:59:00.347879Z c2665af 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.

@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: c2665af4ad

ℹ️ 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".

Comment on lines +76 to +79
if !changed && current.sessionID == "" {
// A driver can report the session after the hardware. Check
// the next session against the last proven one.
current.sessionID = previous.sessionID

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 Retain the previous session until a non-empty proof arrives

If the hardware identity returns before the session ID and remains session-less for two controller ticks, the previous session is forgotten. The first tick copies previous.sessionID here, but clears proofLost and adopts the current connection generation; the second tick therefore takes the default branch and overwrites the binding with an empty session ID. When a different session subsequently arrives, otherSession cannot detect it, and ObserveSample treats it as a first session proof without changing the generation, so the new session inherits the old Charge now power instead of requiring confirmation. Preserve the last proven session across every session-less tick; adding a second consecutive tick("easee:A", "", 4140, false) to TestLostProofCannotHandStartToALateNewSession exercises this gap.

Useful? React with 👍 / 👎.

A dropped charger socket, or a hardware identity that is missing for a
while, is not an unplug. Charge now keeps its power, and the binding keeps
the last proven charger. When the charger proves itself again, the same
hardware keeps Charge now; another serial or session ID pauses it and asks
for Start, as before. An observed unplug, Stop, a car that stops asking and
a config change still end it.

The site rig's s13 ("Charge now goes on after the charger returns") passes
now, so its known line goes.

Decision: #1788.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@frahlg
frahlg force-pushed the fix/charge-now-socket-reconnect branch from c2665af to 8539edc Compare October 25, 2026 14:03
@frahlg
frahlg merged commit c1befee into master Oct 25, 2026
22 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.

1 participant