Repository navigation
Octopus Intelligent: low-load test cancels a genuine dispatch while the car is charging in view of the CT #5461
Description
Activity
🤖 Automated first-pass triage (a maintainer will review before any action is taken).
Classification: bug, priority_medium. Affected cohort: Octopus Intelligent installs on defaults —
octopus_intelligent_dynamic(default on) withcar_energy_reported_load(default on), nocar_charging_nowsensor, and a car drawing ~7 kW — where a lagged or battery-charge-dipped 5-minute load figure drops under0.9 × car_charging_threshold(5.4 kW at the 6.0 kW default) while a genuine dispatch is running. Workarounds exist but each gives something up (see the end), so medium rather than low.Checked against main (v9.3.6-6-g42da8559): the classify/evidence code is identical on the v9.3.5 this report ran — the only
plan.pycommits between the two versions are #5120 and #5401, elsewhere in the file — so nothing here is version-specific.Verified:
- The quoted log line is
dynamic_load()'s (apps/predbat/plan.py:250); behind itdynamic_load_classify()(plan.py:380) classifies "low" exactly as the report describes:load_last_periodunder both0.9 × battery_rate_max_discharge(7.2 kW here) and0.9 × car_charging_threshold(5.4 kW here). With nocar_charging_nowsensor the load test (dynamic_load_car_evidence(),plan.py:413-420) turns "low" into not-charging evidence, gated only on the whole trailing 5-minute window lying inside the dispatch (plan.py:418) — so the dispatch's very first (ramp-up) window is eligible evidence, and the grace isDYNAMIC_LOAD_CAR_LOAD_MINUTES= 5 minutes (const.py:35), timed on the 5-minute cycle grid (the 15-second poll deliberately does not run the load test —plan.py:805). - One precision on the grace: a single low figure starts the grace clock (
plan.py:574) but does not itself cancel — the cancel fires when the next cycle, 5 minutes later, is also low (plan.py:575-576). So a cancellation needs two consecutive low 5-minute averages, and because the ramp-up window is eligible evidence, one lagged or under-read figure rides it into a cancel. Reading the reported 22:30 dispatch backwards from the 22:40:08 cancel: the [22:30–22:35) window must also have read low (the ramp-up), and the logged 4.80 kW [22:35–22:40) window cancelled it. The same-cycle inverter snapshot (load power 7410W) against the 4.80 kW average is the lag in one line — the instantaneous load had already reached 7.4 kW while the trailing average sat 2.6 kW lower. - The readings of 5.8–6.1 kW quoted for the battery-charge under-read sit just above the 5.4 cut-off and would classify as
baseline(slot stays trusted; the 5.4–6.0 strand never satisfies the cheap-rate confirmation either, which needs the full threshold —plan.py:565). So the 20:40 and 23:50 cancels imply those specific windows averaged under 5.4 kW, i.e. dips below the line rather than a sustained under-read. TheDynamic load last periodline prints every cycle (plan.py:250), so a retainedpredbat.logcovering the evening would pin those two figures directly, if wanted for a fix's fixtures. - The
dynamic_load_cartest module passes on main; it encodes the current designed threshold behaviour. The misfire needs real telemetry — ramp-up, lag, and the under-read while the battery grid-charges (the latter the reporter's measured Fox behaviour, not code-verifiable from here) — which the mocks do not model.
Root cause: the design of the low test itself — fixed
0.9×fractions of two thresholds evaluated on the single trailing 5-minute average, a grace of exactly one period, and no baseline, battery-charging or grid-import cross-check. All five directions in the suggestion list are still open against current main. #5318 (open, enhancement) is the adjacent request to use the charger's own energy sensor as independent evidence, which would also immunise this path against the load under-read.Workaround premise, refined: the
car_charging_thresholdhistory-stripping heuristic only runs when there is no realcar_charging_energysensor — with one configured, load history is subtracted from the sensor precisely and the threshold heuristic is skipped (fetch.py:382-389, GH#4717;load_ml_component.py:75/360is the same shape). So on an install that has a workingcar_charging_energysensor, lowering the threshold to 4.0 kW does not remove more household load from the forecast; on installs without one, the coupling the report worries about stands.Related: #5318, PRs #5229/#5248 (where the confirmation came in), #5317 (the charger-outside-the-CT shape the report already distinguishes), #5390 (the sensor-path cancel/resume flapping sibling). No duplicate found — none of those covers this cause.
- The quoted log line is
- addedBOT_TRIAGEDHas been through the triage botHas been through the triage botbugSomething isn't workingSomething isn't working
on Oct 9, 2026
Summary: On Octopus Intelligent, the low-load test in the slot confirmation can cancel a genuine dispatch while the car is charging at full power and is visible in the house load. A single 5-minute load figure under
0.9 × car_charging_thresholdis enough. Once the slots are cancelled the battery hold is released and the battery discharges into the car. The way dynamic load judges "low" probably needs adjusting to allow for this.This is not the charger-outside-the-CT case (#5317 / #5318). Here
car_energy_reported_loadis correctly On and the car shows in the load.What happened
One install on v9.3.5: Fox ESS (cloud), 16.9 kWh battery at 8 kW, a 7.4 kW car charge, no
car_charging_nowsensor,car_charging_thresholdat the default 6.0 kW. Times are BST, Thursday 8 October 2026 into Friday 9 October.car 0 is in a dispatch but not charging, cancelling its slotscar 0 slots resumedcancelling its slotsagain. Inverter load at that moment: 7410 Wslots resumedcancelling its slots - keeping the dispatch rate until 00:00slots resumedOctopus was reporting the dispatch correctly throughout (
SMART_CONTROL_IN_PROGRESS, planned dispatch present).The line that shows the cause, at 22:40:13:
Why it reads "low"
dynamic_load_classify()(apps/predbat/plan.py) returns"low"whenload_last_periodis under both0.9 × battery rateand0.9 × car_charging_threshold. With the defaults that is 5.4 kW.dynamic_load_car_evidence()then cancels afterDYNAMIC_LOAD_CAR_LOAD_MINUTES= 5, which is one period.Two things pushed a 7.4 kW car under 5.4 kW:
load_last_periodis the load energy over the lastPREDICT_STEPminutes. The dispatch started at 22:30 and the load only reached 7 kW at about 22:35, so the 22:35–22:40 figure came out at 4.80 kW. A cloud inverter whose readings arrive a few minutes late makes this worse. The existing guard only requires the 5-minute window to lie inside the dispatch; it does not allow for the car starting late or the data arriving late.So a 7 kW charger has very little margin against the default threshold, and the test fires exactly when Predbat itself starts charging the battery in the dispatch.
Suggestions
Any of these would have avoided it; they are not exclusive.
Workaround in use
car_charging_thresholdlowered from 6.0 to 4.0 kW (its minimum) on the affected install, which moves the cut-off to 3.6 kW. It works against this failure but it is the wrong tool: the same setting decides what is stripped from load history as car charging, so lowering it removes more genuine household load from the forecast. The other option is turningoctopus_intelligent_dynamicoff, which loses the confirmation altogether.Related: #5318 (other evidence sources for the same check), #5229 / #5248 (where the confirmation came in).