Repository navigation
fix(control): the battery leaves the hour's overage to the EV cut Core sent (#1674) - #1706
Conversation
3ac062b to
88e0284
Compare
88e0284 to
9f0116e
Compare
…e sent (#1674) Göran, 17 October 14:22:38 UTC: an overage of the import ceiling stopped the car, and in the same tick the battery's fuse guard discharged for the same watts. When the stop reached the meter the battery was still discharging, and the site exported about 4 kW for ~10 s. fuseReliefFromSnapshot now keeps the fuse apart from the hour's import limit. The fuse and the per-phase relief act on every reading, as before. Where the hour's limit is tighter, its overage less the EV cut sent during that overage asks for relief. The EV guard notes the pool's offer on the first reading of a run over the hour's limit; the cut is that offer less this tick's offers. Our own commands only, no charger or battery reading. The cut counts while the run is younger than 20 s. A cut that reaches the meter ends the run, so a later overage gets relief at once, and a car that draws less than its offer cannot hold an overage through a second cut. The charge guard is unchanged, so a charging battery still yields to the car. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
9f0116e to
f81845f
Compare
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. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f81845f1ad
ℹ️ 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".
| if lim.overSince.IsZero() || s.now().Sub(lim.overSince) >= evSettle || !finiteFuseValue(s.EVCommandedW) { | ||
| return 0 | ||
| } | ||
| return math.Max(0, lim.overOfferW-math.Max(0, s.EVCommandedW)) |
There was a problem hiding this comment.
Track sent cuts instead of aggregate plugged offers
When multiple managed EVs are charging and one unplugs during an hourly-ceiling overage, loadpoint.CommandedEVPowerW immediately excludes that loadpoint while evLimit remains active as long as another EV is still plugged in. This subtraction therefore treats the unplugged car's entire old offer as a cut Core sent, even though the meter may already reflect the unplug, and can suppress necessary battery relief for the remainder of evSettle. Record the actual reductions commanded by the fuse guard, or reset the baseline when pool membership changes, rather than inferring commands from the aggregate offer.
AGENTS.md reference: AGENTS.md:L22-L23
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in #1713. The EV guard now restarts the run's baseline when a car leaves or joins the pool, so only cuts sent to cars still plugged count. TestUnpluggedCarIsNoCutTheBatteryWaitsFor covers two cars with one unplugged during the overage.
) Codex on #1706: with several managed chargers, unplugging one car during an overage of the hour's import limit dropped its offer from the pool. pendingEVCutW counted that drop as a cut Core sent, although the meter may already show the unplug, and held back battery relief for up to 20 s. The EV guard now notes which loadpoints are plugged. When a car leaves or joins, the run's baseline restarts at the pool's offer then, so only cuts sent to cars still plugged count. Our own commands only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
) (#1713) * fix(control): an unplugged car is no EV cut the battery waits for (#1674) Codex on #1706: with several managed chargers, unplugging one car during an overage of the hour's import limit dropped its offer from the pool. pendingEVCutW counted that drop as a cut Core sent, although the meter may already show the unplug, and held back battery relief for up to 20 s. The EV guard now notes which loadpoints are plugged. When a car leaves or joins, the run's baseline restarts at the pool's offer then, so only cuts sent to cars still plugged count. Our own commands only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com> * fix(control): keep a pending EV cut per charger when another car leaves (#1674) Codex on #1713: restarting the whole pool's baseline when a car left also dropped a cut already sent to a car that stays. The battery then covered that cut too, and the site could export when it landed. The EV guard now notes each charger's offer when a run over the hour's limit begins. pendingEVCutW sums, over chargers still plugged, the start offer less this tick's offer. A car that leaves drops out; a car that joins has no start offer. main.go hands Core this tick's offers per driver through SetEVCommanded, from loadpoint.CommandedEVPowerByDriver, which keeps a stopped car at 0 W. Our own commands only; the 20 s cap stays. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com> --------- Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Follows #1699 and #1701 (both merged). Closes the gap that #1699 left open for #1674.
Problem
On a large overage of the import ceiling, the EV guard (
stepEVMeterLimit) cuts the car by the measured overage. In the same control tick the battery's fuse guard (forceFuseDischargethroughfuseReliefFromSnapshot) discharges for the same watts. The car's cut reaches the meter 5–15 s later. The battery is still discharging, so the site swings into export.Field evidence, Göran (segran2: Easee Cloud, Pixii, 25 A), v0.147.2-beta.1, 17 Oct 2026 UTC:
Once the cut lands, the meter reads the import limit less the cut. So the site exports when the cut is larger than the limit: a car above the ceiling, which PV or battery support allows. Under the fuse alone (16.5 kW at 25 A) one car's cut never gets there. The fuse case over-corrects for a moment but does not export.
Change
fuseReliefFromSnapshotkeeps the fuse apart from the hour's import limit:pendingEVCutW.pendingEVCutWis the EV cut sent during the current overage. The EV guard already marks the first reading of a run over the hour's limit (overSince); it now also notes the pool's offer then (overOfferW). The cut is that offer less this tick's offers (EVCommandedW). It reads only our own commands, never a charger or battery reading. It counts while the run is younger than 20 s (loadpoint.CommandSettle). So:applyFuseGuard(charge scaling) is unchanged. A charging battery still yields to the car on a ceiling overage (EV first). That can over-correct, but it cannot discharge.Results (simulated, 1 s steps,
evBatterySite)TestCeilingOverageLeftToCommandedEVCutDoesNotExportfails without this change (site exported 2,053 W) and passes here.TestFuseReliefLeavesOnlyCeilingWattsToCommandedEVCutis a table for the rule: the fuse part (total and per phase) never waits, only watts the cut removes wait, and no cut, a cut sent before this overage, or an overage older than 20 s waits for nothing.A first version counted every offer of the last 20 s. An independent review found two ways it waited too long: after a cut had already landed, and when the car drew less than its offer (two cuts, 41 of 60 s over the limit). The run-based rule above fixes both;
TestCeilingOverageAfterTheCutShowsGetsBatteryReliefandTestCeilingOverageWaitsOnceWhenTheCarDrawsLessThanItsOfferfail on that first version and pass here.Limits
setCommandedForManualrecords a cut before it is sent, so a failed send still counts, for at most 20 s and only against the hour's budget.fuseSaverFromLivePowernow sees the smaller relief too. A charging battery then keeps charging until the next full tick, whereapplyFuseGuardcuts it.Overlap
ev_battery_sequence_test.goconflicted. The fuse test now steps past the instant limit (1.2 × 25 A): inside the heat budget the battery holds the fuse and the car is not cut, which fix(control): the EV guard gives the main fuse a heat budget (#1674) #1701's own tests cover. This change touches only the hour's import limit.dispatch.goandev_fuse_budget.goand needs a rebase anyway.Test plan
make verifybash .github/scripts/check-telemetry-reads.sh origin/master: none added🤖 Generated with Claude Code