Repository navigation
iPhone: the plan reaches the car the moment the box replans - #73
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. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cac9fa1295
ℹ️ 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 nextWindows != windows { windows = nextWindows } | ||
| if nextShortfall != shortfallWh { shortfallWh = nextShortfall } | ||
| shortfallReadAtMs = site.scheduler.nowMs | ||
| if let stamp, lps != stamp.value { self.stamp = Stamped(lps, asOfMs: stamp.asOfMs, origin: stamp.origin) } |
There was a problem hiding this comment.
Preserve pushed cable state against delayed cloud updates
When a plan push updates a car after the last charger read, this retains the old stamp timestamp. A delayed FTW Cloud car envelope whose source time falls between that read and the push therefore passes takeCloud's freshness check and can overwrite the newer pluggedIn value from the plan. The UI then shows the old cable state until the follow-up /api/loadpoints request succeeds—and indefinitely if that request fails—so the pushed plan needs separate freshness tracking that prevents older cloud car data from superseding it.
Useful? React with 👍 / 👎.
cac9fa1 to
fec57a7
Compare
The app read the car's plan from /api/loadpoints only when the car started or stopped drawing, so a car plugged in for a night charge kept the old plan on the phone while the box's page showed the new one. srcfl/ftw#1643 pushes the session plan when the box replans, with each car's windows, cable, pending/outdated and shortfall. The app takes them at once: Now's strip, the plan view, the car view and PlanShare (the widgets and the watch) move with the push, and a cable that moved is read once for the rest of the car. A box whose plan comes without cars is read each quarter while a car is plugged in, and every 15 s while it says the plan is being made or for 30 s after FTW Cloud says a cable moved. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…de it srcfl/ftw#1643 now sends chargers/PlanCharger keyed by the loadpoint id, with an optional carId (the session's car, empty until Core keeps cars), since in the car contract (srcfl/ftw#1647) cars means real cars. The vector Core pins carries carId. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…able Review of app#73 (S1, N13) and the paired Core changes in srcfl/ftw#1643: - Each charger on the plan now carries gridDeferred and goalRev. The car view takes gridDeferred from the push; a goal revision that moves (a goal changed on the box's page or another phone) reads the car once. - While a new plan is made the push says pending and keeps the windows the box goes on acting on, so Now's strip does not blink. - A read that was on the wire while a newer push came keeps the push's cable as well as its plan. - An older box is asked every 15 s for a minute after a cable moved: it notices a cable within 10 s, or 40 s after its own replan. - The demo box says pending when its replan starts, as Core now does, and can hold an answer back (answerDelayMs) to test a stale read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fec57a7 to
24af4aa
Compare
Problem
Fredrik plugged his car in at home. The box's page showed the new charging plan; the app did not. The app read
/api/loadpoints(which carries the car's windows) only when the car started or stopped drawing power, and the box never sent its plan when it replanned. FTW Cloud's copy moved the cable but not the plan.What this changes
Pair: srcfl/ftw#1643 replans the moment a cable goes in and pushes the session
planto streaming phones when a replan starts (pending) and when it lands, withchargerson it: per charger (loadpoint id) an optionalcarId, the plug state,pending/outdated,gridDeferred,goalRev, the windows ahead, the total and the shortfall. Keyed by charger, because in the car contract (srcfl/ftw#1647, srcfl/ftw-webapp#92)carsmeans real cars.Plan.chargersandPlanChargerread it (nil from a box that sends none).PlanPushTests.thePlanWithChargersDecodesFromTheBoxsBytesdecodes the bytes Core pins inTestThePlanWithChargersVector.LoadpointsModel.takePlan()takes each charger's share the moment a plan arrives, with no read: the windows, cable, pending/outdated,gridDeferredand shortfall. While a new plan is made the windows stay those the box acts on, so Now's strip does not blink. Now's strip, the plan view, the car view andPlanShare(the widget record, which the watch gets with it) all draw from these, andPlanShare.check()takes the chargers before it writes./api/loadpointsonce./api/mpc/planfor it.Fallback for a box without the push
A box whose plan comes without chargers is an older box (known once Now's
plan.getanswers). For it, the chargers'LiveAskkey also carries the quarter while a car is plugged in, and a 15 s step while it saysplan_pendingand for a minute after a cable moved: such a box notices a cable within 10 s, or 40 s after a replan of its own, and only then says pending. Until a plan has come the app does not guess, so a box that pushes never pays for the fallback.The demo box pushes like the new Core (
pushesPlan, on): pending when a replan starts after a cable, a goal, a level or solar-only, and the plan when it is made. A hold replans nothing, as in Core.answerDelayMsholds an answer back to test a stale read.Measured
Laptop, loopback, a real
ftwclouddbuilt from srcfl/ftw-cloud#15, the relay unreachable. The box ran in process with srcfl/ftw#1643's planner (Go DP), loadpoint manager on the event bus, plug-in replan, plan feed, app uplink, cloud link and tunnel, and servedGET /api/loadpoints; Core's production reactive check stayed on (10 s, 30 s gap). The app was FTWKit'sAppModel, paired through FTW Cloud with Now's asks on. "On screen" meansLoadpointsModelhas the car plugged in with its windows andPlanShare.entryhas the charge. The harness is not in either repo.With the push: one read per plug-in, after the plan was on screen. The Go DP solved in a millisecond; on a Pi with Energyplan the plan lands when the solve ends, and the cable and "pending" still land at once. Old box: 1–3 reads per plug-in. Its worst case is the box's reactive check after a recent replan (up to 40 s) plus one 15 s ask, about 55 s; without FTW Cloud's copy, the next quarter or the car starting to draw. In the harness FTW Cloud's cable news was put in with no delay and seen at the app's next second.
Today, without these PRs, the plan reaches the phone only when the car starts drawing (23:00 in Fredrik's case) or a view opens after a minute.
Tests
PlanPushTests(FTWKit): the vector; a pushed plan moves the car's windows, PlanShare's charges and the plan with no read, keeps the windows while pending, and nothing reads the car again for a minute; a plug-in shows the cable and pending at once and reads once; a goal changed elsewhere is read once; a stale read keeps the cable a newer push gave; an older box is read once when FTW Cloud says the cable went in; an older box making its plan is asked every 15 s and stops when the plan is there. Whole FTWKit: 437 + 239 tests pass.Files
Kept out of U3's and U4's files and step 2's carrier files. Touches
SiteModel.swiftby one word:cloudCarsisinternal(set)so a test can put FTW Cloud's cars in.Loadpoint.Windowgets a public init for the demo.CLAUDE.md's read rule says what the push does.🤖 Generated with Claude Code