Skip to content

feat(loadpoint): tell the driver Core's current cap per phase - #1809

Merged
frahlg merged 1 commit into
masterfrom
feat/ev-command-cap-amps
Oct 22, 2026
Merged

frahlg merged 1 commit into
masterfrom
feat/ev-command-cap-amps

Conversation

@frahlg

@frahlg frahlg commented Oct 22, 2026

Copy link
Copy Markdown
Member

Problem

srcfl/device-drivers#190 makes easee_cloud start a 6 A order at 7 A, because Easee firmware waits about 5 minutes before it starts a car at 6 A (Göran's runs, 22 Oct 2026; evcc does the same). The driver can only tell whether 7 A fits from max_amps_per_phase. Core sends the site's main fuse rating there (for example 25 A), or a manual hold's limit, never the room Core has left.

So when Core orders 6 A because a phase only has room for 6.4 A, the driver still writes 7 A. The car draws 7 A until the next reading, Core sees an overage, cuts to 0 W, and the next start boosts again. The car cycles on and off in exactly the tight case.

Change

Each ev_set_current order now carries cap_amps_per_phase: the most current per phase Core allows this charger right now, on the phases the order names. Core already decides this in freezeFuseBudget; this PR only reports it. No order changes, and no fuse rule is added.

The value, in amps, rounded down to two decimals, is the least of:

  • the charger's share of the EV cap (control.EVFuseBudget: fuse, total import and the hour's import limit), over voltage × phases;
  • its last command's current plus its room on the busiest phase (control.EVFusePhaseRoomA), when the meter reports phase currents. This is the same credit and room phaseRoomCapW uses;
  • the charger's MaxChargeW over voltage × phases;
  • the order's final max_amps_per_phase, after the hold's limit, the installation limits and the one-phase charger limit. The field never exceeds it.

It is not limited by what the plan or a hold wants, so it can be above the order. A plan or a hold that asks for 6 A on a phase with room for 15 A gives cap_amps_per_phase: 15. A hold's max_amps_per_phase does limit it.

Between chargers, each keeps its own share. Room that no charger took goes to the first charger with a share in the split's order, so two chargers are never told the same room. With 13 A of room and two 6 A orders, the first gets 7 and the second 6.

The field is absent on a 0 W order, when no fuse budget is wired (no site meter or fuse), and without a voltage. A driver must read a missing field as "no room above the order".

Evidence

New go/internal/loadpoint/controller_cap_amps_test.go, run through Tick:

Case Order cap_amps_per_phase
room 6.4 A takes a 16 A plan to 6 A 4140 W 3p 6.4
room for exactly 7 A 4140 W 3p 7
last 6 A order not shown yet, 0.4 A more room 4140 W 3p 6.4
room ample (15 A) 4140 W 3p 15
room past the charger's 16 A 4140 W 3p 16
total budget 4500 W 4140 W 3p 6.52
no phase currents, total 5175 W 4140 W 3p 7.5
manual hold, limit 6.5 A 4140 W 3p 6.5
manual hold, no limit 4140 W 3p 15
one phase, total 1500 W 1380 W 1p 6.52
one phase, room 7.5 A 1380 W 1p 7.5
room 5 A 0 W absent
no fuse budget 4140 W 3p absent
two chargers, 13 A of room 4140 W each 7 and 6
  • Before the change: every case that expects a value fails with cap_amps_per_phase missing.
  • After: all pass.
  • go vet ./...: clean.
  • go test ./internal/... ./cmd/ftw/...: 60 packages ok, 0 failed (bundled drivers synced with scripts/sync-bundled-drivers.sh).
  • go test ./internal/control/ -run TestSiteSimFieldMatrix: 340 cases, 29 fail an invariant (idle 23, own-raise 6), all known. That is the same count as master without this change.

Limits

  • The field inherits Core's own accounting, including the credit for the charger's last command. If an order has outlived its pending window and the car still draws nothing, that credit counts current that is not flowing. This is the same room Core already uses for its own orders. The boost lasts at most 90 s, which is within the default 180 s pending window.
  • The two-charger hand-out is conservative: the first charger in order gets all spare room even if it does not use it.
  • Not run on hardware. The driver side has harness tests only.

Pairing

Open PRs on the same files

Do not merge without Fredrik's explicit yes.

🤖 Generated with Claude Code

Each ev_set_current order now carries cap_amps_per_phase: the most
current per phase Core allows this charger on the phases the order
names. It is the charger's share of the EV fuse/import cap and of the
busiest phase's room after the split between chargers, within the
order's max_amps_per_phase and the charger's maximum, and not limited
by what the plan or a hold wants. Room that no charger took goes to
the first charger with a share, so two chargers are never told the
same room. Omitted on a 0 W order, without a fuse budget, or without
a voltage.

max_amps_per_phase is the fuse rating. easee_cloud (srcfl/device-drivers#190)
needs the room left to start a 6 A order at 7 A only when 7 A fits.
No order changes.

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

chatgpt-codex-connector Bot commented Oct 22, 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-22T17:08:04.855614Z 98e1068 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.

frahlg added a commit to srcfl/device-drivers that referenced this pull request Oct 22, 2026
The 7 A start for a 6 A order now needs Core's cap_amps_per_phase
(srcfl/ftw#1809) to allow 7 A, besides max_amps_per_phase and the
charger's static limit. max_amps_per_phase is the fuse rating, not
the room left: on a phase with room for 6.4 A the car drew 7 A, Core
cut to 0 W, and the next start boosted again. A Core that sends no cap
gets no boost.

Review findings on #190:
- P1: every 6 A order during a boost checks the gate again; one that no
  longer fits writes 6 A at once and ends the boost.
- P2: the 90 s window ends in driver_command too, and driver_poll ends
  it before reading observations, so a failed read cannot keep 7 A.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
frahlg added a commit to srcfl/device-drivers that referenced this pull request Oct 22, 2026
The 7 A start for a 6 A order now needs Core's cap_amps_per_phase
(srcfl/ftw#1809) to allow 7 A, besides max_amps_per_phase and the
charger's static limit. max_amps_per_phase is the fuse rating, not
the room left: on a phase with room for 6.4 A the car drew 7 A, Core
cut to 0 W, and the next start boosted again. A Core that sends no cap
gets no boost.

Review findings on #190:
- P1: every 6 A order during a boost checks the gate again; one that no
  longer fits writes 6 A at once and ends the boost.
- P2: the 90 s window ends in driver_command too, and driver_poll ends
  it before reading observations, so a failed read cannot keep 7 A.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
@frahlg
frahlg merged commit 0cb9bf2 into master Oct 22, 2026
30 of 32 checks passed
frahlg added a commit to srcfl/device-drivers that referenced this pull request Oct 22, 2026
* fix(easee_cloud): start a 6 A order at 7 A (1.3.7)

Signed-off-by: Lödkolven <lodkolven@users.noreply.github.com>

* fix(easee_cloud): boost only with a read static limit (Codex review)

Signed-off-by: Lödkolven <lodkolven@users.noreply.github.com>

* docs(changelog): wrap easee_cloud 1.3.7

Signed-off-by: Lödkolven <lodkolven@users.noreply.github.com>

* fix(easee_cloud): boost only when Core's cap allows 7 A

The 7 A start for a 6 A order now needs Core's cap_amps_per_phase
(srcfl/ftw#1809) to allow 7 A, besides max_amps_per_phase and the
charger's static limit. max_amps_per_phase is the fuse rating, not
the room left: on a phase with room for 6.4 A the car drew 7 A, Core
cut to 0 W, and the next start boosted again. A Core that sends no cap
gets no boost.

Review findings on #190:
- P1: every 6 A order during a boost checks the gate again; one that no
  longer fits writes 6 A at once and ends the boost.
- P2: the 90 s window ends in driver_command too, and driver_poll ends
  it before reading observations, so a failed read cannot keep 7 A.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>

---------

Signed-off-by: Lödkolven <lodkolven@users.noreply.github.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Co-authored-by: Lödkolven <lodkolven@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
frahlg added a commit that referenced this pull request Oct 23, 2026
…#1811)

Move the device-drivers pin from 5cc7bb1a to 219c462e
(srcfl/device-drivers#190 and #191). Between the two pins only
easee_cloud changed.

An Easee Cloud charger given a 6 A order now starts at 7 A per phase
when Core's cap_amps_per_phase (#1809) allows 7 A, which avoids
Easee's roughly five-minute delay at 6 A, and steps down to 6 A once
the charger reports charging or after 90 s. 1.3.8 adds two review
fixes: no 7 A before the first poll, and a pause or stand-down cannot
leave 7 A for the phaseMode re-assert.

Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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