Skip to content

fix(ev): anchor older car SoC with energy delivered since - #1500

Merged
frahlg merged 2 commits into
masterfrom
vehicle-soc-max-age-20m
Oct 3, 2026
Merged

frahlg merged 2 commits into
masterfrom
vehicle-soc-max-age-20m

Conversation

@frahlg

@frahlg frahlg commented Oct 2, 2026

Copy link
Copy Markdown
Member

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 with vag_vehicle control 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

  • The loadpoint keeps a short history of delivered Wh for each session: one sample per 30 s, kept for two hours, cleared on plug-in and plug-out, and carried across a config reload.
  • AnchorVehicleSoCAt(id, soc, observedAt) anchors the reading against the energy delivered at observedAt, interpolating between samples. AnchorVehicleSoC keeps its behaviour (observed now).
  • A reading does not anchor when:
    • it is from more than five minutes before plug-in, since the car may have driven since;
    • the energy at its time is unknown: before the first sample after a restart, or after the history was trimmed.
  • telemetry.PickVehicleForAnchor uses the same gates as the loadpoint picker but accepts readings up to VehicleAnchorMaxAge (one hour). Only the per-tick anchoring loop in main.go uses it.
  • The completion event now compares the anchored estimate with the target, not the raw reading.

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's current_soc, which is now based on the car reading plus delivered energy.
  • Driver-marked stale=true values never anchor.
  • Replays (soc_fresh=false) do not move SoCUpdatedAt, 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_vehicle the 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 planned host.http_request (cookie jar for srcfl/device-drivers#143), which also exposes the Date header.

Related

Validation

  • New tests:
    • Anchoring at the observation time adds energy delivered since.
    • Repeated anchoring is stable and follows new energy.
    • Interpolation between samples.
    • Readings before plug-in: the five-minute slack and the refusal beyond it.
    • Unknown energy after a restart, and trimmed history.
    • PickVehicleForAnchor accepts 30 minutes and rejects past one hour and driver-marked stale.
  • Existing anchor, telemetry, API and cmd/ftw tests pass.
  • make verify: vet, test and build clean.
  • Not yet run on hardware with a VW car.

🤖 Generated with Claude Code

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>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 2, 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-02T13:32:54.188443Z f585074 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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@frahlg
frahlg merged commit 10e5591 into master Oct 3, 2026
15 checks passed
@frahlg
frahlg deleted the vehicle-soc-max-age-20m branch October 3, 2026 05:41
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