Repository navigation
fix(control): a cut ends the reservation of the higher orders before it - #1793
Merged
Merged
Conversation
#1776 counts an EV order as flowing until the charger can have applied it, using the highest order sent within the charger's response time. That included orders a later cut had already replaced. With Easee Cloud's declared 180 s, a charge at 10,350 W, a 25 s pause and a 4,140 W request kept 10,350 W reserved: the restart waited 2 min 47 s with fuse_limit while the phases read about 1.3 A. Reported on a real Easee/Pixii site by segran2 in #1763, reproduced in evMeterSite. The charger follows its latest order, and the meter shows its draw until a cut lands, so a cut ends the reservation of the higher orders before it. A run of rises still counts its highest, which is its latest, so stacked rises stay covered. The restart now comes after 12 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
This was referenced Oct 22, 2026
Merged
frahlg
added a commit
that referenced
this pull request
Oct 22, 2026
Easee Cloud applies a cut after 30-120 s, but the EV fuse cap took each cut from the highest offer of the last 20 s. Once that window passed, the meter still showed the old draw and Core cut again: in the field matrix 10,400 -> 6,900 W, then 0 W after a 6 A kettle step. The same window missed a raise still on its way, so a cut could leave the phase over the limit once the raise landed. The cut now counts our own orders that cannot have shown yet, both ways, from the per-charger, per-phase log of #1776/#1793. A cut on its way covers the overage it was sent for until its charger can have applied it (max(20 s, declared power_max_age_s), 180 s undeclared); Core cuts only what it does not cover. A raise on its way adds to the cut. A cut and every order after it count as not shown; of the orders before it, a raise counts from the lowest. An offer found in force at a restart, which Core did not send in this run, counts as shown. Once a phase's filtered current passes 1.25 x In, nothing on its way holds back a cut. Over the fuse the cap never rises above the latest offers. Rises, starts and the hour's import ceiling are unchanged. Field matrix: 93 of 99 netting rows pass. The other six are cut by the split, which floors 7,600 W to 6,900 W (7600/690*690 = 7599.999...); Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
frahlg
added a commit
that referenced
this pull request
Oct 22, 2026
) * fix(control): a cut on its way covers the overage it was sent for Easee Cloud applies a cut after 30-120 s, but the EV fuse cap took each cut from the highest offer of the last 20 s. Once that window passed, the meter still showed the old draw and Core cut again: in the field matrix 10,400 -> 6,900 W, then 0 W after a 6 A kettle step. The same window missed a raise still on its way, so a cut could leave the phase over the limit once the raise landed. The cut now counts our own orders that cannot have shown yet, both ways, from the per-charger, per-phase log of #1776/#1793. A cut on its way covers the overage it was sent for until its charger can have applied it (max(20 s, declared power_max_age_s), 180 s undeclared); Core cuts only what it does not cover. A raise on its way adds to the cut. A cut and every order after it count as not shown; of the orders before it, a raise counts from the lowest. An offer found in force at a restart, which Core did not send in this run, counts as shown. Once a phase's filtered current passes 1.25 x In, nothing on its way holds back a cut. Over the fuse the cap never rises above the latest offers. Rises, starts and the hour's import ceiling are unchanged. Field matrix: 93 of 99 netting rows pass. The other six are cut by the split, which floors 7,600 W to 6,900 W (7600/690*690 = 7599.999...); Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(control): a cut covers only its own run, on known phases Review of the first commit found three ways a cut on its way could cover more than it removes: - A cut that landed covered a later step on the same phase for the charger's whole response time: 180 s for a fast charger that declares nothing, such as OCPP. A cut now covers only within the run over the limit it was sent in. The run ends once every phase has read under the working limit for evSettle; a short dip, as battery relief gives, does not end it. Probe: 2 min 35 s over the limit becomes 22 s, as master. - A car that draws less than its order retried each cut after 180 s (10 min 36 s over the limit in the review's case). Once the run's first cut has outlasted its charger's response time, cuts cover for evSettle, as before: 5 min 16 s. - A change of phases read as a cut in amps per phase: 15 A on one phase to 6 A on three covered 9 A on every phase. Orders now carry their phase count; across a change the latest order counts in full as a raise. A single-phase cut covers no phase when other chargers share the pool. Without phase currents Core cannot see the hottest phase, so there a cut covers for evSettle, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(control): field rows after the cut netting on master 5ae590a FTW_FIELD_KNOWN=write removes 99 over-cut netting, 1 limit netting and 4 over-cut D rows and adds none. 71 of 338 cases still fail, all listed on master: energy 40 (39 D, 1 A), idle 25 (export, readback, hold3p), own-raise 6 (relief). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Reported on real hardware by @segran2 in #1763 (v0.155.0-beta.1, Easee Cloud + Pixii, 25 A fuse): after an explicit pause, a 4,140 W request was first answered with 0 W and
fuse_limit, and the first admission came about three minutes later, while the phases read 1–6 A. He also found the cause and reproduced it in the released source'sevMeterSite.#1776 counts an EV order as flowing until the charger can have applied it:
commandLog.unshowntakes the highest order sent within the charger's response time (Easee Cloud declarespower_max_age_s=180). That included orders a later cut had already replaced. Sequence: 10,350 W, a 25 s pause at 0 W, then 4,140 W → 10,350 W stays reserved and the restart waits.Change
go/internal/control/ev_fuse_budget.go,unshown: a cut ends the reservation of the higher orders before it. The charger follows its latest order, and the site meter shows its draw until the cut lands, so nothing above the cut can still arrive on top of the reading. A run of rises still counts its highest order, which is also its latest, so the stacking that #1776 fixed stays covered. Watts and current per phase are handled separately, so a 3p→1p switch at lower watts still counts its higher current per phase.Fuse rule unchanged: the site meter's current plus FTW's own orders that cannot show yet. No setting, timer or constant changed.
Evidence
TestEVHardLimitRestartAfterPauseIgnoresReplacedRise, the reported sequence with Easee's 180 s: master 2 min 47 s to the first offer; this change 12 s.a day.go test ./internal/control/... ./internal/loadpoint/... ./internal/sitesim/... -p 2andmake verify(78 packages) pass.The device-simulator rig reruns the fuse cases on this commit before v0.155.0-beta.2.
Drafts #1782, #1789 and #1792 touch the same file and rebase after this.
🤖 Generated with Claude Code