Skip to content

fix(ev): manual charge runs until the car is full - #1056

Merged
frahlg merged 1 commit into
masterfrom
1002-manual-charge-until-full
Sep 4, 2026
Merged

fix(ev): manual charge runs until the car is full#1056
frahlg merged 1 commit into
masterfrom
1002-manual-charge-until-full

Conversation

@frahlg

@frahlg frahlg commented Sep 4, 2026

Copy link
Copy Markdown
Member

What changed

  • Manual tab: "Charge now" installs a persistent hold with no SoC release. It runs until the car stops asking for current, Stop, or unplug; the plan takes over after that. The button and status copy say so, and the plan strip no longer claims a stop at a percentage for a hold that has none.
  • API: POST /api/loadpoints/{id}/manual_hold refuses a release_at_soc_pct the SoC estimate already meets with 409 and a message naming the estimate. Omitting the field, or a target above the estimate, still installs.
  • Tests: web/ev-charge-now.test.mjs rewritten for the new contract, web/ev-plan-status.test.mjs updated for the strip copy, TestManualHoldRefusesReleaseTargetAlreadyMet added in go/internal/api.
  • Changeset: patch.

Why

Field report from the maintainer's site today (Easee carport, estimate at 80 %, schedule 80 % by 07:00). "Charge now → 80 %" from #1007 releases at the schedule target. The estimate already sat there, so the hold cleared on the next tick. The box log shows five releases between 06:15 and 06:18 and no charge. The only way to change the 80 was to save a different schedule, and the operator ended up trying to drag the current-charge slider below 80 % to get around it.

The estimate is a guess on chargers that cannot read the car. Gating a manual start on it turns Start into a no-op exactly when the operator wants to go past the plan. Manual now means: charger runs. The car-declined auto-release (Easee emits request_active since device-drivers#103), Stop and unplug still end it, so the "Start pins the wallbox forever" concern from #1002 stays covered.

The 409 exists so no caller, scripted or otherwise, can install the same silent no-op through the API. The app door (appproto) never set a release target, so it needs no change.

Verification

  • npm test: 499 pass, 0 fail.
  • go test ./internal/api/ ./internal/loadpoint/ and go vet ./internal/api/: pass.
  • Browser: served the branch's web/ locally with /api proxied to the live box. Opened the EV modal and the Manual tab with a hold active: the strip reads "Manual charge at 11.0 kW — plan and PV logic are off until the car is full, Stop or unplug." and the tab reads "Charging at the slider's amps until the car is full, Stop or unplug (fuse still limits)." with Update / Stop. The idle state ("Charge now") was not driven live since that would have stopped a running charge; it is pinned by the web test.

Refs #1002. Follows #1007.

🤖 Generated with Claude Code


Note

Medium Risk
Changes operator-facing EV charging control and manual-hold API validation; mistaken SoC estimates could affect who gets a 409, but core dispatch logic is unchanged beyond refusing no-op holds.

Overview
EV manual charge no longer ties "Charge now" to the schedule's target SoC. The dashboard installs a persistent manual_hold without release_at_soc_pct, and copy/buttons say charging continues until the car stops drawing, Stop, or unplug (API-installed holds that still have a release target are explained in the UI when active).

API: POST /api/loadpoints/{id}/manual_hold returns 409 when release_at_soc_pct is set but the loadpoint's estimated SoC already meets or exceeds that target, so callers cannot install a hold that the controller would clear on the next tick.

Tests cover the new 409 behavior (TestManualHoldRefusesReleaseTargetAlreadyMet) and the updated web contract (ev-charge-now.test.mjs, ev-plan-status.test.mjs). Patch changeset included.

Reviewed by Cursor Bugbot for commit c08b011. Bugbot is set up for automated code reviews on this repo. Configure here.

"Charge now → target" (#1007) released the hold the moment the SoC
estimate reached the schedule target. On a charger that cannot read the
car the estimate is a guess, and when it already sat at the target the
button released itself on the next tick: five presses, no charge
(box log 2026-09-04 06:15–06:18). The manual tab no longer sends a SoC
release; Start runs until the car stops asking for current, Stop, or
unplug, and the plan takes over after that. The API refuses a
release_at_soc_pct the estimate already meets (409) instead of
installing a hold that clears on the next tick.

Refs #1002.

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

chatgpt-codex-connector Bot commented Sep 4, 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-09-04T04:55:40.898412Z c08b011 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: c08b011daa

ℹ️ 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 thread web/app.js
Comment on lines +3054 to +3055
// CONTROL write — strict (FIX-B): persistent manual hold (hold_s:0)
// with no SoC release — see the note above the slider.

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 Keep a fallback release for chargers without request state

When Charge now is used with OCPP or any driver that omits request_active, this creates a persistent hold with neither a SoC release nor a usable full signal. The telemetry adapter defaults the missing field to true (go/cmd/ftw/main.go:1883-1902), OCPP's pushReading omits it (go/internal/ocpp/handlers.go:578-584), and the controller only auto-releases when a driver explicitly reports false (go/internal/loadpoint/controller.go:1520-1535). Consequently, after the vehicle reaches its limit while remaining plugged in, the manual hold continues to bypass plan/PV control until Stop or unplug—and may resume fixed-rate charging if the vehicle requests current again—contrary to the new promise that the plan takes over; retain a fallback release for these chargers or gate no-target holds on the capability.

Useful? React with 👍 / 👎.

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

Approved: Cursor Bugbot completed successfully and reported no findings that need human review. No approval-policy files apply, and no reviewers were assigned.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@frahlg
frahlg merged commit 62640a5 into master Sep 4, 2026
17 checks passed
@frahlg
frahlg deleted the 1002-manual-charge-until-full branch September 4, 2026 04:58
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