Skip to content

fix(loadpoint): one unplug reading does not end Charge now - #1898

Merged
frahlg merged 2 commits into
masterfrom
d3-unplug-grace-keeps-charge-now
Oct 27, 2026
Merged

frahlg merged 2 commits into
masterfrom
d3-unplug-grace-keeps-charge-now

Conversation

@frahlg

@frahlg frahlg commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

Item D3 of the stable plan.

Problem

The loadpoint cleared Charge now on the first Connected=false reading (controller.go:1429-1437). The cars runtime waits 30 s (cars.UnplugGrace) before it ends the session, and resumes it if the cable comes back. So the two layers disagree on the same flap.

Scenario: Charge now runs at 4140 W on device dev-1, session sess-1. One 5 s poll reads not connected, for example an OCPP Unavailable status after a charger reboot. The next poll shows the same device and session. On master the hold is gone and the car falls back to the plan, which at night is 0 W. The clear is also saved to disk, so a restart does not bring it back.

Change

  • Add loadpoint.UnplugGrace (30 s). cars.UnplugGrace now points to it, so both layers use one constant.
  • tickOne clears the hold only once the cable has read out for UnplugGrace.
  • While the hold waits out the grace, observeTick skips the session check, since an unplugged reading says nothing about the next car.
  • When the cable comes back within the grace, the first reading is compared with the hardware the hold was bound to. Another device or session clears the hold and the plan decides, as an unplug did before. The same hardware keeps it.
  • Some chargers name the session a reading or more after the cable (Easee Cloud keeps session_id nil until it confirms the session). Until a session is named, the binding keeps the one from before the unplug. If the charger then names another session, the hold turns into a pause, as a new session after a restart or socket drop does. The same session keeps Charge now.

Unplug reset of surplus state and of the SoC record is unchanged.

Tests

  • New controller_unplug_grace_test.go: TestOneUnpluggedReadingKeepsChargeNow (the audit repro from caa-29, renamed), TestUnpluggedReadingWithoutSessionKeepsChargeNow and TestOtherSessionAfterShortUnplugClearsChargeNow. The first two fail on master with held=false last order=0 W; the third passes on both.
  • TestLateOtherSessionAfterShortUnplugEndsChargeNow and TestLateSameSessionAfterShortUnplugKeepsChargeNow cover a session named one reading after the cable. Without the second commit the first fails with late other session inherited Charge now: last=4140 W.
  • Six tests asserted that one reading ends the hold. They now keep the cable out for UnplugGrace first: TestPersistentManualHoldClearedOnUnplug (also checks the hold survives the first reading), TestChargeNowWithoutSessionIDResumesAfterRestart, TestMissingReadingDoesNotEndManualSession, TestChargeNowSurvivesOCPPSocketBlip, TestOCPPReconnectIsNotAnUnplug and TestOldSocketStatusCannotClearPauseAfterReconnect.
  • go test -race -count=1 on loadpoint, cars, api, control, ocpp, appproto, appuplink, mpc, state, units and cmd/ftw: all ok.
  • go vet ./... and go build ./...: ok.

Risk

Low. No command changes: tickOne still returns before dispatch while unplugged, and the fuse budget only counts connected chargers. A Charge now or Pause hold stays visible for up to 30 s after a real unplug. On a charger that never reports a session id, a different car plugged in within 30 s keeps Charge now; that is the same trade #1848 accepted for socket drops. A charger that reports the session id late is covered: the later id is compared with the one before the unplug.

🤖 Generated with Claude Code

The loadpoint cleared a manual hold on the first Connected=false reading,
while the cars runtime keeps the session for UnplugGrace (30 s). Clear the
hold only after the cable has read out for UnplugGrace, or when the next
connected reading names another device or session. Both layers now share
loadpoint.UnplugGrace.

Item D3 of the stable plan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

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 🔄 Running since 2026-10-27T13:34:25.872053Z fd21445 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.

…plug

rebindAfterUnplug carried the old session id into the binding, but
restoreManualHoldForSession ran on the same tick and overwrote it with
the empty id the charger reported. A charger that names the session one
reading after the cable, as Easee Cloud does, then let a new session
inherit Charge now at full power.

Keep the last proven session id while the charger names none, so the
later proof is compared with it. Add tests for another session and for
the same session named one reading late.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@frahlg
frahlg merged commit c0d1447 into master Oct 27, 2026
23 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