Skip to content

fix(loadpoint): an idle 1p or auto pool starts on room for 6 A (C6) - #1917

Merged
frahlg merged 4 commits into
masterfrom
idle-1p-pool-fuse-config
Oct 28, 2026
Merged

frahlg merged 4 commits into
masterfrom
idle-1p-pool-fuse-config

Conversation

@frahlg

@frahlg frahlg commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

Problem

C6 in the random-rig triage of 27 Oct. Charge now on a 1p or auto charger on a three-phase site never starts on a 16 A fuse. The car gets 0 W with fuse_limit, and the log repeats loadpoint fuse-clamp: below minimum step … fuse_cap_w=0 min_step_w=1380.

Rig seeds: 207534 (209 such lines, 16 A, auto), 207536 (192, 25 A, auto), 207537 (79, 16 A), 207531 (41, 16 A, passed anyway).

Cause: FuseConfigs passed control.EVFuseBudget the tick's phase mode but kept the configured min_charge_w (4,140 W, the three-phase minimum). evPoolFor takes the pool's minimum from that, so the idle-start test waited for 4,140 W of room on one phase, 18 A. On a 16 A fuse that room never exists. On 25 A it needs a worst phase below about 6 A. freezeFuseBudget and applyFuseClamp already used fuseConfig (1,380 W on one phase), so the controller and the budget disagreed about the least order.

Change

One line in FuseConfigs: configs[i] = c.fuseConfig(configs[i], mode). The budget now waits for room for the least order on the phases the command names: 6 A on one phase for 1p and auto, 6 A on each of three for 3p. A loadpoint with no command this tick, and one-phase sites, keep their configured values.

This is the same line as in open #1865. This PR lands it alone, without #1865's SnapChargeW change, which still waits for an owner decision. #1865 should drop this line once this PR lands (it will otherwise conflict on the same hunk).

Tests

go/internal/control/ev_fuse_one_phase_start_test.go runs the real EVFuseBudget against the controller, wired as main.go wires it, after a restart (fresh state, idle charger, min 4,140 W, max 11,040 W, Easee whole-amp table) under a Charge now hold of 3,680 W.

TestIdleOnePhasePoolStartsOnSixAmpsOfRoom, cases from triage section 3 item 7:

Fuse Worst phase Hold mode master this branch
16 A 4.5 A 1p 0 W after 40 s (fuse_limit) 2,070 W on one phase after 5 s
25 A 13.7 A 1p 0 W after 40 s 2,070 W on one phase after 5 s
16 A 4.5 A auto 0 W after 40 s 2,070 W on one phase after 5 s
25 A 13.7 A auto 0 W after 40 s 2,070 W on one phase after 5 s

It requires an order of at least 1,380 W within evRestartDwell + one 5 s tick, on one phase, and then 12 more ticks with no phase above the fuse rating.

TestIdleOnePhasePoolStartBoundary (added after review) pins the start point, since the cases above leave about 10 A of room. On 16 A, in 1p and auto:

  • a worst phase that leaves exactly 6.0 A under the working line (9.0 A today) starts at exactly 1,380 W on one phase, then 60 ticks stay under the fuse rating;
  • 5.7 A of room (9.3 A) waits 40 ticks (200 s) at 0 W.

The test reads the working line from EVFuseBudget itself: on a site with no house load, an idle pool's evLimit.phaseRoomA is the whole line. It does not name evPhaseMarginA, so it builds before and after #1910 and the cases move with the line.

Proof:

  • Fix line removed (master): all 4 start cases fail at 0 W with fuse_limit after 40 s, and both boundary start cases fail at 0 W.
  • A hidden margin on the one-phase minimum (phaseMinChargeW with 7*voltage instead of 6*voltage): the start test still passes, the boundary test fails in 1p and auto ("worst phase 9.0 A: offer 0 W; want 1380 W").
  • On this branch all pass. go test -race ./internal/control/ ./internal/loadpoint/ ./cmd/ftw/: ok. make verify: "vet + test + build clean".
  • At 78f357f (master 7840c33 merged in): go test -race ./internal/control/ ./internal/loadpoint/ ok, make verify clean, and the three proofs above still hold (a 7 A minimum fails the boundary start cases; no fix line fails all start cases).
  • With the line at the rating (evPhaseMarginA = 0), the probe reads 16 A and all cases pass.
  • Trial merge of fix(control): battery fuse guard reads each phase with its direction #1891 (which carries fix(control): car and battery work to the fuse rating (fuse stack #1910, #1891, #1913, #1923, #1924, #1925, #1926, #1914) #1910) into 78f357f: no conflict, go vet clean, go test ./internal/control/ ./internal/loadpoint/ ok.
  • TestSite/siterig not run here; the runner does that. siterig/short passed on 61cee08. Later commits change only tests or merge master, and 78f357f has not run on the rig.
  • CI on 78f357f first failed TestE2E_WriteMaxChargeLimit in go/cmd/sim-sungrow (expected 100, got 500). This PR touches no simulator code. The cause is a race in the simulator: its tick loop takes a snapshot, a register write lands, then RegisterBank.Refresh writes the old snapshot back over the new value. It passes 30 of 30 runs locally. I reran the failed jobs on the same head (run 40611744229); every job now passes, including go test + vet.

Risk

  • A frozen site meter (C5) can now raise a 1p or auto car on a 16 A fuse. C5 is unfixed on master: a meter that repeats one reading while FTW's orders move still counts as fresh. Before this PR a 1p or auto pool never started on 16 A, so a frozen meter could not raise it there. Now it starts and climbs while the meter never shows the car. In a unit loop (house 4.5 A on every phase, meter answering every 5 s with the house alone, 1p hold of 7,360 W) the order reaches 3,680 W on one phase, 20.5 A on a 16 A fuse. That is above 1.2 × In, and nothing cuts it. On master the same loop gives 0 W on 16 A but the same 3,680 W and 20.5 A on 25 A, so this is C5's hazard, not a new one; this PR makes 16 A sites newly exposed to it. The fix is fix(control): a site meter that repeats one reading counts as stale #1921 (C5, seed 207537): it turns a frozen meter into site_meter_stale, which stops dispatch and stands the chargers down. Trial merge of fix(control): a site meter that repeats one reading counts as stale #1921 (77bcd3d) into this branch, same loop plus the charger's own reading in telemetry and ObserveSiteMeterFreeze + SetEVCommanded each tick as main.go calls them: the car starts at 2,070 W (13.5 A) after 5 s, the meter counts as frozen at 40 s (devices_moved_w=2070, 6 samples), and the gate closes there. No phase passes the rating; the climb to 3,680 W at 190 s never comes. Do not merge this PR before fix(control): a site meter that repeats one reading counts as stale #1921. CI cannot enforce that: retargeting this PR onto fix(control): a site meter that repeats one reading counts as stale #1921's branch would stop the test workflow, which runs only on PRs to master.
  • A 3p command with a configured minimum below 4,140 W now waits for room for 4,140 W before an idle start, not the configured minimum. A three-phase order below 6 A each does not run on any IEC charger, so the old start was not a real start.
  • The budget uses no new margin. The start still needs a fresh meter, the dwell rule and the existing step of room; this PR changes only which minimum the pool needs room for. The working line itself is still SiteFuseAmps - evPhaseMarginA (1 A under the rating); fix(control): car and battery work to the fuse rating (fuse stack #1910, #1891, #1913, #1923, #1924, #1925, #1926, #1914) #1910 moves it to the rating.

Stack and merge order

🤖 Generated with Claude Code

FuseConfigs named the tick's phase mode but kept the configured
minimum, so EVFuseBudget made an idle 1p or auto pool wait for the
three-phase minimum (4,140 W) of room on one phase. On a 16 A fuse
that room never exists. FuseConfigs now applies fuseConfig, as
freezeFuseBudget and applyFuseClamp already do.

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

chatgpt-codex-connector Bot commented Oct 27, 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-27T18:37:51.650620Z 61cee08 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 and others added 3 commits October 27, 2026 20:07
On a 16 A fuse, an idle 1p or auto pool must start at exactly 1,380 W
when the worst phase leaves 6.0 A of room under the working line, and
wait at 5.7 A. The existing test left about 10 A of room, so a hidden
margin on the one-phase minimum (7 A instead of 6 A) still passed it.
The new cases are set from the working line, so they follow it when
#1910 moves it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ry test

The boundary test named evPhaseMarginA, which #1910 removes. The two
PRs merge with no text conflict, so whichever landed second would
break the control package's test build on master. The test now reads
the line from EVFuseBudget's own report on an empty site, and the
cases follow it on either side of #1910.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@frahlg
frahlg merged commit cf6a07d into master Oct 28, 2026
31 of 33 checks passed
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