fix(ev): anchor older car SoC with energy delivered since - #1500
Conversation
A car SoC was used only while under five minutes old, and each tick re-based the estimate as if the reading were current. Cloud sources such as the VW Group EU Data Act portal report every 15 minutes, so control rarely saw the car, and the per-tick re-anchor dropped the energy delivered after the reading. The loadpoint now keeps a short per-session history of delivered Wh (one sample per 30 s, two hours) and anchors a reading at the time it arrived. PickVehicleForAnchor accepts readings up to an hour old for this path only. Readings from before plug-in (beyond five minutes), or whose energy offset is unknown after a restart or trim, do not anchor. VehicleMaxAge keeps its meaning for display, MPC's direct use and goal completion. 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. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f585074c35
ℹ️ 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".
| lp.currentPowerW = powerW | ||
| lp.deliveredWhSession = deliveredWh | ||
| if pluggedIn { | ||
| lp.recordDelivered(now, deliveredWh) |
There was a problem hiding this comment.
Timestamp delivered energy at its measurement time
When an EV driver reports delayed telemetry—where EVSample.EnergyAt or PowerAt predates the control tick—this records the computed Wh at m.now() rather than when the energy was measured. For example, if a counter measured at 12:10 arrives at 12:20 while the car SoC was observed at 12:15, deliveredAt interpolates the counter increase through 12:20 and invents energy delivered after the SoC observation, overestimating the anchored SoC and potentially firing completion early. Preserve the existing energy-timeline timestamps instead of assigning every sample its receipt time.
AGENTS.md reference: AGENTS.md:L22-L23
Useful? React with 👍 / 👎.
Problem
Core used a car SoC only while it was under five minutes old (
VehicleMaxAge). The VW Group EU Data Act portal writes a file every 15 minutes, so withvag_vehiclecontrol rarely saw the car. The loadpoint ran on its plug-in estimate instead.Each tick also re-anchored the estimate as if the reading were current. While a reading stayed valid, the estimate stuck at that value and dropped the energy delivered after it.
An older SoC is fine if FTW counts the energy delivered since the reading. Say the car read 50 % half an hour ago and the charger has delivered 5 kWh since. The level is then 50 % plus 5 kWh × efficiency ÷ capacity.
Change
AnchorVehicleSoCAt(id, soc, observedAt)anchors the reading against the energy delivered atobservedAt, interpolating between samples.AnchorVehicleSoCkeeps its behaviour (observed now).telemetry.PickVehicleForAnchoruses the same gates as the loadpoint picker but accepts readings up toVehicleAnchorMaxAge(one hour). Only the per-tick anchoring loop inmain.gouses it.Unchanged:
VehicleMaxAge(five minutes) still defines "current" for the API, the MPC's direct use of the car value, wake status and goal completion. When the car value is older, MPC plans from the loadpoint'scurrent_soc, which is now based on the car reading plus delivered energy.stale=truevalues never anchor.soc_fresh=false) do not moveSoCUpdatedAt, so they re-anchor the same observation, which has no effect.Known limit
A reading is anchored at the time Core received it, not when the car measured it. For
vag_vehiclethe gap can be up to ~20 minutes, and energy delivered in that gap is not counted, the same as before this change. The Lua host has no wall clock, so the driver cannot report the measurement time yet. That follows with the plannedhost.http_request(cookie jar for srcfl/device-drivers#143), which also exposes theDateheader.Related
Validation
PickVehicleForAnchoraccepts 30 minutes and rejects past one hour and driver-marked stale.cmd/ftwtests pass.make verify: vet, test and build clean.🤖 Generated with Claude Code