Skip to content

fix(control): reserve EV demand until offered power is delivered - #1763

Draft
segran2 wants to merge 3 commits into
srcfl:masterfrom
segran2:test/ev-priority-offer-reservation
Draft

segran2 wants to merge 3 commits into
srcfl:masterfrom
segran2:test/ev-priority-offer-reservation

Conversation

@segran2

@segran2 segran2 commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

An accepted, stable EV current offer could be counted as if the car already consumed that power. When the car drew less, optional battery charging could retain the space the EV still needed. For example, a 10.35 kW offer with only 4.155 kW delivered let Pixii keep 9.383 kW despite a projected busiest phase of 29.5 A on a 25 A site.

This change keeps the missing EV demand reserved before optional battery charging, including Charge now. It fixes allocation separately from the diagnostic wording in #1762 / #1761.

Change

clampEVBatteryHeadroom credits only the smaller of the lowest recent EV offer and qualified measured EV delivery. A stable offer alone no longer proves that its load is inside the site meter.

  • Credit is bounded per demanded driver, using healthy EV readings with known, finite, nonnegative power.
  • Both the reading receipt and power observation must be current (at most 15 seconds, and no older than the driver's declared observation validity). EV power must be within 10 seconds of the site-meter sample. Future, stale, unavailable, unaligned or faulty readings receive no credit, retaining the full demand reservation.
  • The recent-command bound remains: a newly raised offer cannot immediately release battery headroom.
  • This only reduces optional positive battery targets. It changes neither EV fuse admission nor start/rise logic; a requested battery reduction does not fund an EV increase until the site meter observes the relief. Battery discharge and required measured export relief retain their existing handling.
  • Add a patch changeset.

The existing phase guard keeps its 1 A working margin where tighter than the configured margin. No fuse settings or user priorities are changed.

Evidence and tests

The original regression commit reproduces two captured-household cases on master 94c04d385d2a0dd11f05547454d29509f942ef50: 10.35 kW offered / 4.155 kW delivered, and 4.14 kW offered / zero delivered. Both were red before the fix and now pass. A fully realized-offer control preserves battery allocation.

Additional tests cover fresh, missing, stale, future, misaligned, faulted, offline, unavailable and cached-source power, negative/excessive readings, multiple chargers, full reservation without delivery, and independence from battery-device readings. Existing command-pending, unseen battery reduction, asymmetric phase, export relief and joined-EV recovery/control-loop tests remain in the control suite.

The allocation replay assumes balanced three-phase EV/battery power at configured 230 V and unchanged other loads. It is not an Easee hardware simulator.

Hardware validation — 21 October 2026

Installed the PR head 387b7d7 as a local backport in the affected ARM64 installation, running a locally packaged Core v0.154.1-beta.2. This is not a deployment of the PR branch verbatim: the package also retains earlier local diagnostics and a separate vehicle-SoC replanning correction. The later hardware runs used a separate, locally patched Easee driver 1.3.11. Neither that driver correction nor the SoC correction is part of this PR.

Manual EV priority and battery backoff/recovery

During ordinary planned battery charging on 21 October, Europe/Stockholm:

  • Pixii was charging at approximately 4.66 kW before a 120-second manual EV request of 10.35 kW.
  • Core initially limited the EV offer to 8.97 kW. The battery command then fell to approximately 1.05 kW and subsequently zero; measured battery charging fell from approximately 4.65 kW through 929 W to zero.
  • The captured sequence showed the EV offer rising to 9.66 and 10.35 kW after the measured battery reduction. This supports waiting for observed relief in this run, rather than treating a reduction command as already-realized headroom.
  • Actual EV delivery rose from approximately 8.77 to 9.45 kW, below the requested 10.35 kW. Battery charging resumed within the remaining space. Highest recorded grid phase current was 20.98 A against the configured 25 A fuse limit.
  • On automatic expiry of the EV hold, the charger returned to the inactive plan with no manual override and measured 0 W. Battery charging recovered to approximately 4.99 kW, close to the plan's approximately 4.95 kW target.

The capture contains 61 samples, roughly five seconds apart, with vendor source timestamps plus FTW command, power and phase-current observations. Cloud and Modbus samples are not exactly simultaneous. These observations support manual EV priority/backoff/recovery on this installation; they do not prove every transient or internal reservation invariant.

Separate charger-start finding

An earlier 6 A / 4.14 kW test reached approximately 10.86 kW. A subsequent source-timestamped capture showed the charger briefly reporting a 32 A dynamic limit and 16 A physical-pilot observation around resume, before settling to 6 A. This is a separate charger-start/current-control issue, not evidence that the allocation correction itself resolves Easee start behavior.

A local Easee-driver candidate reasserts the intended current before/after resume and on a later poll. With that candidate, a clean 180-second 6 A test held approximately 4.145 kW for over two minutes, maximum observed pilot 6 A and maximum grid phase current 8.308 A, then expired to 0 W under the plan. Another attempt needed a second resume before charging started. The complete capture corrected an earlier preliminary interpretation of that attempt as no-start; delayed cloud observations showed delivery before the stop completed. Do not infer universal charger-start acceptance from the successful run.

Scheduled charging — overnight read-only follow-up

All times below are Europe/Stockholm on 21 October 2026. These observations come from the locally modified installation described above, not an unchanged PR checkout.

Window Observed EV behavior Battery response and highest recorded phase current
02:30–02:45 0 W throughout the sampled window. Three resume acknowledgements at 02:31–02:32 were followed by a no-charge warning at 02:33:01. The original plan requested 10.35 kW; a scheduled replan at 02:33:53 removed this slot and moved the next start to 04:00. Battery charging initially fell to about 0.8 kW, then rose to about 9.63 kW; about 9.15 kW after the window. Phase maxima through 02:47: 21.94 / 16.67 / 15.56 A.
04:00–04:30, extended to 04:45 First positive minute aggregate ended at 04:11:56, followed by about 8.24–8.31 kW. Delivery remained below the 10.35 kW request; feedback reported power_below_target. The plan extended charging through 04:45. Battery charging fell close to zero, then rose to about 1 kW while EV charging continued. Phase maxima through 04:34: 21.15 / 18.67 / 14.73 A.
04:35–05:05 follow-up About 8.31 kW through the minute ending 04:44:56, then 0 W, then about 4.16 kW in the minute aggregates ending 04:46:56–04:49:56. Driver logs show resume attempts at 04:45:56 and 04:46:26. EV was 0 W from the minute ending 04:50:56 through the check. The 05:00 start again produced three acknowledgements without observed power; warning at 05:03:11. At 05:05 the request was 10.35 kW / 15 A, actual 0 W, feedback no_power_response. Battery charging increased to about 9.27 kW after 04:45, then fell near zero for the 05:00 slot. Phase maxima: 23.62 / 22.19 / 20.30 A.

At the final check, the plan contained charging through 05:30, 06:00–06:15 and 07:00–07:30; the final end time had moved from 07:15 to 07:30. These replans mean the originally scheduled windows cannot be treated as fixed acceptance scenarios.

Evidence: FTW minute history, Pixii phase-current series (10-second aggregates retaining maxima), loadpoint command/feedback state, incident planning snapshots and Easee driver logs. All reads succeeded. Repeated unchanged vendor power values are not independent physical measurements; site-meter load and session-energy integration support delivery, but no independent EV energy meter was used. Minute aggregates and asynchronous sources limit transition timing. No recorded phase current exceeded the configured 25 A limit; this does not exclude unobserved faster transients. The overnight checks changed no control, settings, goals or driver and initiated no vendor login. Both one-shot monitors have completed and are disabled.

Remaining acceptance

Keep this PR draft. Manual battery backoff/recovery and one stable 6 A run are supported by the captures, and scheduled EV delivery was observed with a substantial delay. Reliable scheduled starts, full requested 15 A delivery, and clean stop/return-to-plan behavior are not accepted. The cause of acknowledged starts with no power, the approximately 8.3 kW delivery under a 10.35 kW request, and the brief post-04:45 restart remain unresolved. The evidence does not yet separate charger/vehicle behavior, driver resume handling and plan/deadline decisions.

Battery discharge-to-idle behavior, fresh vehicle-SoC-driven replanning and broader hardware coverage remain outstanding. The vehicle's own 64% reading remained stale; the later approximately 69.1% planning SoC was inferred, not a fresh vehicle observation. The allocation regression tests remain valid evidence for the scoped code correction, but these mixed-environment hardware runs do not establish complete end-to-end acceptance.

Validation of the fix

  • make verify: passed on commit 387b7d7; Docker-context validation skipped because no Docker daemon was available.
  • go test -race ./internal/control: passed.
  • Focused incident regressions, freshness/health/source-observation tests and TestJoinedEVWaitsForMeasuredBatteryBackoffThenResumes: passed.
  • The PR itself contains no hardware settings, driver, database or history changes. The local backport is now deployed; the separately modified hardware-test environment and remaining acceptance are described above.
  • Additional local-package checks: make verify GO_TEST_TIMEOUT=30m passed on the Pi using RAM-backed test data; this is not production SD-card performance evidence. Race tests for internal/cars, internal/telemetry and internal/control passed on macOS after the Pi race runtime could not start (unsupported VMA range). These checks cover the local package and must not be presented as testing an unchanged PR checkout.

Repository hygiene follow-up

EV power qualification now lives in telemetry.Store.OnlineEVControlPower, including the health and source-age checks; control adds only site/EV time alignment and per-demand reservation bounds. No telemetry-read exception or CI waiver was added. The exact check-telemetry-reads.sh check passes locally and both Ubuntu/macOS GitHub jobs are green. make verify passes on the updated head. This describes the earlier validation; current CI status must be checked independently.

Signed-off-by: Segran <goran@andersson-stehag.se>
Signed-off-by: Segran <goran@andersson-stehag.se>
@segran2 segran2 changed the title test(control): reproduce lost EV priority when stable offers are not delivered fix(control): reserve EV demand until offered power is delivered Oct 20, 2026
Signed-off-by: Segran <goran@andersson-stehag.se>

segran2 commented Oct 20, 2026

Copy link
Copy Markdown
Contributor Author

Field-test report — 2026-10-20 (partial pass)

Tested commit 387b7d7f9c6d2b351afe27377b50d510d6220c20 in FTW v0.154.2-beta.1 on Raspberry Pi (aarch64), with Easee and Pixii.

Passed / observed

  • FTW service started and remained operational during the observed test window; no errors appeared in the filtered logs supplied.
  • Easee/FTW reported measured EV power of 0 W while the vehicle was connected but not charging.
  • Relevant control and telemetry tests passed: go test ./internal/control ./internal/telemetry -run 'TestEVBattery|TestOnlineEVControlPower' -count=1 -v. This includes unrealized offers, stale/offline/faulted telemetry, reservation and phase/headroom scenarios.
  • Reviewed control path: SetEVFuseDemands(...) precedes ComputeDispatch(...), whose target processing calls clampEVBatteryHeadroom(...).
  • Pixii continued receiving stable discharge setpoints (~−1.2 kW in EMS sign convention). A 60-second scoped POST /api/battery/manual_hold requesting charge, power_w=2000, driver=pixii was accepted at 16:29:06. Pixii's EMS setpoint transitioned from approximately −1.3 kW before the hold to +982 W at 16:30:05, consistent with gradual ramping and successful positive charge dispatch.

Not yet demonstrated in the field

  • No direct observation of a positive evFuseDemands snapshot and a before/after reduction specifically attributable to clampEVBatteryHeadroom. The sub-2 kW Pixii target during the 60-second hold cannot be attributed to EV reservation rather than slew/other limits. Therefore, the EV-specific battery-charge clamp is unit-tested but not conclusively field-verified.
  • Setpoint logs show commands, not independent confirmation of actual Pixii measured power.

Outcome: Partial field-test pass; no regression observed in the tested conditions. No code changes proposed from this test. A conclusive EV-headroom field check would require diagnostic visibility into EV demand, qualified realized EV power, and battery targets before/after the clamp.

segran2 commented Oct 22, 2026

Copy link
Copy Markdown
Contributor Author

Hardware follow-up — 22 October 2026: distinguish Core admission from Easee startup

Test environment: released Core v0.155.0-beta.1 with local Easee driver 1.3.12. This is not a test of PR head 387b7d7 and does not establish acceptance of this PR's allocation change. Times below are Europe/Stockholm.

  • A seven-minute direct 6 A / 4140 W request from confirmed idle eventually started without any higher-current offer. Resume acknowledgements were logged at 01:24:10, 01:24:40 and 01:25:15. A no-power warning followed at 01:25:45. Easee first reported a positive 6 A pilot at source time 01:29:32, 5 min 22 s after the initial resume. Positive power was received at 01:29:40 and subsequently reached approximately 4.184 kW; the site meter also confirmed the load increase. No new resume appears in the captured log at the actual start. Earlier three-minute no-start runs therefore do not establish that 6 A cannot start.
  • A later repeated 7 A start followed by reduction to 6 A succeeded: approximately 4.764 kW at 7 A, then a source-timestamped 6 A pilot at 03:28:07 and approximately 4.130 kW. The car was first paused from scheduled charging and confirmed at 0 W / 0 A.
  • Importantly, Core initially admitted 0 W with reason fuse_limit during that 7 A request, before later sending 4830 W. One captured interval had approximately 1.83 kW grid import and phase currents 6.257 / 1.817 / 1.422 A, with Pixii at 0 W. This is an observation requiring investigation, not proof that the fuse guard was incorrect: internal historical demand/reservation state was not captured. End-to-end wait time must be split into Core admission time and charger response time.

The late 6 A start is consistent with an Easee firmware/allocation delay, but the internal cause and the effect of repeated resume commands remain unproven. A single-resume long-duration comparison remains useful. The early no-power warning preceded real delivery and should be reviewed separately from allocation correctness. No automatic over-request startup boost or safety-limit change was introduced.

Temporary commands were removed after the tests and ordinary planning resumed. These observations do not close the outstanding EV-priority/headroom acceptance criteria.

segran2 commented Oct 22, 2026

Copy link
Copy Markdown
Contributor Author

Consolidated hardware and Core investigation — 21–22 October 2026

This consolidates the later results, including the follow-up to the earlier comment. Partial hardware success; outstanding faults remain. Keep acceptance open. #1763 is the primary location because allocation/admission is central; the feedback findings below are relevant to #1762 but are not claims about its unmerged implementation.

Environment and evidence boundary

The later runs used released Core v0.155.0-beta.1 and locally modified Easee 1.3.12, not this PR's head and not the older v0.154.1-beta.2 backport described above. Consequently, these results neither validate nor disprove this PR's exact implementation. Configured main fuse: 25 A, three-phase. No raw vendor/Pixii control bypass was used. Temporary holds were removed or expired and ordinary planning resumed. Times below are Europe/Stockholm.

Observed successes

Scenario Result
Natural scheduled start, 21 Oct 23:45 15 A pilot at 23:45:08; first positive vendor power source time 23:45:14; approximately 10.375 kW by receipt 23:45:35. Initial recorded phase maximum 18.35 A. Subsequent natural stop was observed at 0 W after midnight.
Import ceiling 7500 W, five-minute request Core offered 8 A / 5520 W with import_limit; approximately 5472 W delivered. No positive-to-zero cycling during the hold. Recorded grid maximum 6988 W and phase maximum 12.75 A. Ceiling restored to disabled afterward. This ceiling controls hourly average import, not a strict instantaneous 7500 W cap.
EV starts while Pixii already charges Pixii measured 6024 W before the EV request. EV reached approximately 8307 W; battery charging fell through approximately 5022 / 4300 / 3615 W to zero, then recovered. Recorded phase maximum 24.28 A.
7 A start followed by 6 A Repeated successfully: approximately 4.76–4.81 kW at 7 A, then approximately 4.13–4.23 kW with a reported 6 A pilot.

EV priority remains incomplete: in the battery-first run, 15 A was requested but Core later offered only 13 A with fuse_limit while Pixii recovered toward 6 kW. The last changed pilot observation was 12 A and delivery approximately 8.3 kW. Full 15 A priority and command-to-pilot propagation were not established.

Direct 6 A start: delayed, not demonstrated impossible

Earlier three-minute runs ended without power; longer observation changed the conclusion:

  • Idle start: resume acknowledged at 01:24:10, repeated at 01:24:40 and 01:25:15. A no-power warning appeared at 01:25:45. Positive 6 A pilot source time was 01:29:32, 5 min 22 s after the initial resume; positive power received at 01:29:40, subsequently approximately 4184 W.
  • Explicit pause/restart: verified 0 A / 0 W, then requested 4140 W at approximately 03:31:35. Core initially sent zero with fuse_limit. First captured admission was 03:34:29.94, resume acknowledged 03:34:30.28. Positive 6 A pilot source time was 03:40:09, 5 min 39 s after resume; delivery subsequently approximately 4146 W.
  • No higher-current startup offer was issued in either direct 6 A run. Site-meter changes support real delivery.

These runs separate Core admission delay from a subsequent charger-side pilot delay. They support delayed 6 A startup, but do not establish the charger's internal mechanism or exclude influence from repeated resume commands. A single-resume long observation is still outstanding.

Core admission delay reproduced in software

Source review found that recordEVOffers uses the driver's declared power freshness interval as a command-settling interval. Easee declares power_max_age_s=180. Recent command rises can therefore remain reserved as potentially unobserved load after a zero-power pause. The admission calculation combines that reservation with measured/thermal phase loading and a 35-second restart dwell.

A controlled test using the released source's existing evMeterSite fixture reproduced a long admission delay. Physical application delay was 5 s, telemetry delay 10 s and house load 900 W in both cases. Sequence: 25 s at a 10350 W request, 25 s pause, then 4140 W request.

Only changed parameter Restart admission wait
Declared power max age 180 s 167 s / 2 min 47 s
Declared power max age 15 s 12 s

Both simulations reached a 4140 W offer; pre-start measured phases were approximately 1.30 A. This demonstrates sensitivity to the freshness/settling coupling and closely explains the long field admission delay, but is not an exact tick-level household replay. The restart dwell may accrue during the pause, so it is not necessarily an additional 35 s after the request.

No production timeout was shortened. Review separate telemetry-validity and command-propagation semantics. Any correction needs protection against delayed execution, stale/silent meters, rebound and multiple chargers; command acknowledgement alone must not release reserved headroom.

Feedback inconsistency relevant to #1762

The owner's FTW screenshot at 03:37:34 shows:

  • “Not following” and “The charger has not said why”;
  • simultaneously “Waiting for the car to start drawing” and “limited by charger max current”;
  • “Continues until the car stops drawing” although delivery had not begun.

This screenshot predates the 03:40:09 pilot transition and is consistent with the measured waiting period. It is not evidence of stale app rendering after startup. The reported current-limit reason is an observation, not a proven explanation of the delay. Review contradictory messaging and distinguish waiting, observed limits and confirmed cause.

Remaining acceptance

  1. Safety regressions before changing Core's freshness/settling coupling, then hardware pause/restart validation.
  2. Single-resume 6 A start to separate retry effects from charger delay.
  3. Full EV priority/backoff/recovery with realized 15 A delivery; investigate later power/pilot shortfalls.
  4. Repeated import-limit cycles, load steps and hour-boundary/budget-exhaustion scenarios (EV charging cycles to zero under import ceiling despite low phase currents #1674).
  5. Real export/daylight phase-current verification (fix(control): phase current takes its direction from the phase's power #1782).
  6. Stale-plan startup ordering (EV briefly resumes on Core startup before restored plan is rejected as stale #1718): separately reproduced on this version; after a correction, repeat restart acceptance.
  7. Consistent status wording and transition timing.

Evidence retained locally: source-timestamped JSONL captures, Core/driver logs, plans and per-test reports. Approximately 3-second capture intervals and asynchronous vendor/site observations cannot exclude faster transients; unchanged vendor values are not independent new measurements. No recorded phase current exceeded 25 A in the cited runs. The controlled replay and field observations are separate evidence, not a complete hardware safety proof.

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