Repository navigation
fix(control): the EV guard gives the main fuse a heat budget (#1674) - #1701
Merged
Merged
Conversation
frahlg
force-pushed
the
1674-fuse-thermal-budget
branch
from
October 17, 2026 15:09
f3381bb to
2d8d76d
Compare
Since #1695 the EV hard limit cut on every one-second reading over the fuse less 1 A on a phase, and on any reading where total import passed the fuse less its margin. A heat pump start or an oven switching on cut the car, which then climbed back 1 A per confirmed step. Each phase of the main fuse is now a first-order low-pass on I² from the site meter, tau 60 s. A phase cuts once its filtered current passes the fuse less 1 A, or at once above 1.2 x the fuse. The cut takes off what the meter shows over the limit, so one cut stops the heat rising. Rises and restarts still read the instantaneous worst phase. Where the meter reports phase currents, they replace total import as the fuse check; a tariff ceiling still cuts. The heat holds through a meter gap and steps over the real time when the meter returns. After a Core restart it starts at the first reading. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Repeated house peaks on a 16 A fuse (17 A for 10 s, 14 A between) cut the car, which climbed back in the quiet between peaks and was cut again: 4, 7 and 10 cuts in 30 peaks. Rises and starts now read each phase at the higher of the reading and its heat, so each pattern cuts once and settles where the peaks fit. Adds Fredrik's 16 A case as tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
…EV (#1674) Göran's site, 14:20:34Z on v0.147.2-beta.1: one Pixii reading gave 19,174 W while its worst phase was 5.7 A, and the total against the fuse cut the car from 10,350 W to 8,081 W. With phase currents the phases judge the fuse and the offer holds; without them the same total still cuts to 8,081 W. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
frahlg
force-pushed
the
1674-fuse-thermal-budget
branch
from
October 17, 2026 15:22
2d8d76d to
db9717c
Compare
frahlg
marked this pull request as ready for review
October 17, 2026 15:22
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: db9717cf2e
ℹ️ 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".
3 of 4 tasks
…en (#1674) The heat model applied each new reading's current to the whole time since the last one. After a meter gap, one high reading counted as the whole gap at that current and could cut the car at once, and a low one erased an overload from before the gap. A reading now stands for the time until the next: the model integrates the last current seen over that time, then holds the new one. Cut times, counted from the first reading at the new current, are unchanged. The 30 s pattern on a 16 A fuse peaks just over 15 A only as a peak ends, when the meter shows 14 A, so it is not cut. Raised by the Codex review on #1701. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
This was referenced Oct 17, 2026
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.
Part of #1674. Follows #1695 (v0.147.2-beta.1) and #1699 (the import ceiling as an hourly mean, merged as 404a754).
Problem
Since #1695 the EV hard limit cuts on every one-second reading where a phase is over the fuse less 1 A, or where total import is over the fuse less its margin. A fuse or breaker trips on heat, not on one reading. A heat pump start or an oven switching on cuts the car, which then climbs back 1 A per confirmed step.
Göran's site on v0.147.2-beta.1, 14:20:34Z: one Pixii reading gave 19,174 W of total import while its worst phase was 5.7 A on a 25 A fuse. The total against 16,905 W (25 A × 230 V × 3 − 345 W) cut the car from 10,350 W to 8,081 W, and it took about a minute to climb back:
Four seconds later the meter read 15,334 W with phases 12.3 / 9.5 / 7.9 A, about 6.8 kW. The Pixii's total power and its phase currents can disagree within one reading. We have not found why.
With the old rule in the test harness (25 A, car at 15 A beside 5 A of house load per phase):
Change
fuse_heat.go: each phase of the main fuse is a first-order low-pass on I² from the site meter's phase currents: θ += (I² − θ)·(1 − e^(−dt/τ)), τ = 60 s, where I is the last reading's current, held until the next reading. It steps on every new reading, whether or not a car is plugged in.SiteFuseSafetyA0.5). The clock hour's import limit from fix(control): the import ceiling caps each clock hour's mean import (#1674) #1699 keeps its own rule on total import. Without phase currents, total import against the fuse cuts as before.Cut times
25 A fuse, worst phase at 20 A, readings every second. Times run from the first reading at the new current; the charger then needs its own delay, 5–15 s for Easee Cloud, to apply the cut:
Fredrik's 16 A case
A steady 17 A on a 16 A fuse must come down; 10 s peaks to 17 A with 14 A between them may be fine in total. Working limit 15 A, 30 peaks from 14 A, the car at 10 A:
Boundary rule: a phase cuts when its filtered current is strictly over the limit; exactly on it is inside, as for a reading. The 30 s pattern goes 0.011 A over, so either rule counts it as over; it is not cut because the meter shows 14 A by then, and there is nothing to take off. Each cut takes off what the meter shows over the limit at that second, which is 2 A at a 17 A peak; the car then rises 1 A once the heat has room. The heat never passes 15.2 A.
A conflict in the brief, kept on the safe side
The brief asks for an instant cut above 1.2 × the fuse and also says a 10 s spike to 32 A should not cut (filtered 22.3 A). On 25 A, 1.2 × the fuse is 30 A, so 32 A cuts at once. I kept 1.2 × In: it stays below the 1.25 × In that a gG fuse carries for an hour.
TestFuseHeatSpendsBudgetFrom20Achecks the filter alone (22.3 A after 10 s at 32 A);TestEVHardLimitCutsAtOnceAboveInstantLimitchecks that 32 A cuts at once. If spikes above 30 A should pass, raisefuseInstantFactor(for example to 1.45, a breaker's conventional tripping current). That is Fredrik's call.Cut size: meter overage, not filtered overage
The brief says to size the cut from the filtered overage. When the budget runs out, √θ has only just passed the limit, so the filtered overage is close to zero. A cut by it leaves the phase over the limit, θ keeps rising, and the guard cuts again every settle time. The same harness over 10 minutes:
A cut by what the meter shows over the limit brings the phase to the limit in one step, so θ stops rising. The cost: until θ falls under the limit, a spike on that phase cuts by its full size. When the cut leaves the phase below the limit, that takes about 20 s; when it leaves the phase exactly on the limit, θ only approaches it, so it lasts until the house load falls.
Fuse type
FTW does not know whether a site has a gG fuse or a miniature circuit breaker.
Tests
TestFuseHeatSpendsBudgetFrom20A: 26 A → 61 s, 28 A → 37 s, 30 A → 26 s; 10 s at 32 A → 22.3 A.TestFuseHeatCountsGapAtLastCurrentSeen: a spike just after a 30 s gap and an overload just before one;TestFuseHeatStartsAtFirstReadingAfterRestart.TestEVHardLimitPhaseCutWaitsForHeatBudget: the cut times above end to end, with the cut bringing L1 to 24 A or below.TestEVHardLimitHeatBudgetCarriesHouseSpikes: a three-phase heat pump start past the total fuse ceiling, and 10 s at 29.5 A on L1, leave the offer alone.TestEVHardLimitSustainedOvenCutsOnceAfterBudget: one cut after 73 s, then holds.TestEVHardLimitCutsAtOnceAboveInstantLimit: 31 A and 32 A cut on the first reading.TestEVHardLimitHeatBudgetAcrossMeterGap: a house step during 30 s of silence is cut 61 s after the meter returns.TestEVHardLimitRestartStartsHeatAtFirstReading.TestEVHardLimitHoldsWhenTotalImportDisagreesWithPhases: Göran's 19,174 W reading with the worst phase at 5.7 A holds the 10,350 W offer; with no phase currents the same total still cuts to 8,081 W, as in the field log. With the total fuse cut put back, the first part fails with 8,081 W.TestFuseHeatSixteenAmpPeaks: the model values in the 16 A table.TestEVHardLimitSixteenAmpPeaksCutOnceAndSettle: the 16 A table with the car; it fails (4, 7, 10 cuts) when rises read only the reading.TestEVFirstHouseStepWhileBatteryAndEVChargenow allows the car not to be cut at all: the battery takes the 2 kW step within a tick, inside the heat budget.make verify: green on master 404a754 plus these three commits (76 Go packages, vet, build).check-telemetry-reads.sh origin/master: none added.Not done
dispatch.gostill reacts to each reading. It is not part of this change.🤖 Generated with Claude Code