fix(ev): retain charging progress and restore the matching plan - #1240
Conversation
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. |
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ab4c77d1c4
ℹ️ 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".
| if !sample.Connected || sample.ConnectionUnknown || sample.PowerUnavailable || math.IsNaN(sample.PowerW) || math.IsInf(sample.PowerW, 0) { | ||
| delete(c.energySamples, cfg.ID) | ||
| return |
There was a problem hiding this comment.
Stop duty dispatch when power measurement is unavailable
When a source-aged power reading sets PowerUnavailable, this deletes all delivery history, but tickOne still proceeds to computeCommand; for plans with LoadpointMaxPowerW, the unmeasured-time reservation leaves a positive remainingWh until slot end and lines 2797-2803 therefore command peak power throughout. For example, a 100 Wh budget in a 15-minute slot can become a full 2.75 kWh at 11 kW whenever both power and the delayed counter remain unavailable, overshooting the operator's planned charge instead of stopping or conservatively bounding dispatch.
AGENTS.md reference: AGENTS.md:L22-L23
Useful? React with 👍 / 👎.
| watts := a.LoadpointPowerW[lp.ID] | ||
| if len(a.LoadpointPowerW) == 0 && len(previous) == 1 { | ||
| watts = a.LoadpointW | ||
| } | ||
| expected += max(0, watts) * float64(elapsed) / 3600000 * efficiency / initial.CapacityWh |
There was a problem hiding this comment.
Compare SoC against the duty-shaped execution curve
For actions carrying LoadpointMaxPowerW, LoadpointPowerW is the slot-average energy budget, while the controller deliberately runs at peak power until that budget is spent. Integrating the average uniformly therefore reports normal execution as divergence: in a one-hour 60 kWh EV action averaging 5.5 kW with an 11 kW peak, after 15 minutes the measured estimate has risen about 4.1 percentage points while this calculation expects only about 2.1, crossing the 2-point threshold and triggering replans every cooldown. The expected range needs to reflect the actual duty dispatch rather than conflating the planned average with measured progress.
AGENTS.md reference: AGENTS.md:L22-L23
Useful? React with 👍 / 👎.
| // Save each change inferred from power: the cloud counter can remain | ||
| // behind through an arbitrary restart. Unchanged estimates do not write. | ||
| saveProgress := lp.socConfirmed && lp.sessionID != "" && lp.energy.counterKnown && | ||
| (lp.socRetention != "session" || (lp.energy.source == "power" && lp.deliveredWhSession != lp.lastSavedEnergyWh)) | ||
| m.mu.Unlock() | ||
| if saveProgress { | ||
| m.persistSession(id) |
There was a problem hiding this comment.
Debounce inferred-progress persistence
During confirmed charging with a delayed counter, deliveredWhSession changes on every control observation and energy.source remains power, so this condition calls persistSession for every tick. The default two-second control interval consequently performs roughly 43,200 SQLite upserts per charging day against state.db, adding continuous I/O and flash wear on the Raspberry Pi target merely to reduce restart loss by another two seconds; persist on a bounded interval or meaningful energy delta instead.
Useful? React with 👍 / 👎.
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
A delayed charger counter could freeze the EV battery estimate while power was flowing. Restarting before the counter caught up then lost progress, and a restored level could leave the planner using the old assumed level. Preserve source times, reconcile counter and power energy without overlap, and retain the anchor under the verified hardware/session identity.
Core uses the same energy calculation for battery progress and charge budgets. Missing or older counters do not reset the anchor; an actual session change or newer counter reset still invalidates it. Checkpoints bound unsaved progress to 30 seconds or 30 Wh, with immediate saves on stop, user correction and failed-save retry. At 75 kWh, 30 Wh is 0.036 percentage points after charging loss; counter catch-up restores the full progress after a crash between checkpoints.
Unavailable charger power now causes an explicit zero command without erasing the request or spent pulse energy. Recovery cannot repeat a spent budget. Restored-level replanning accounts for the possible placement of a duty pulse, so normal peak-power execution does not trigger false replans. The UI explains reached goals and unavailable measurements, and distinguishes an estimate from a level reported by the car.
Paired driver PR: srcfl/device-drivers#116. Core pins d44fda113f171b5824ecbe178073731dfcf1c031. Easee keeps the canonical session through pauses, carries observation timestamps, requires fresh session proof after offline/unreadable data, and spreads failed lookups across the hour. The bounded 180-second source cadence does not extend Core's separate driver watchdog.
Validation:
This is a development candidate, not a beta release. The battery level is an estimate; the latest BMS value has not been independently confirmed. Full backup/storage reliability in #1234 remains unresolved. Human review of the rendered UI remains required before merge.
Closes #1235
Closes #1236
Closes #1239
Closes #1241
Closes #1242
Closes #1243
Closes #1244
Closes #1245