Skip to content

fix(iog): take Ohme's Intelligent car slots from Octopus, and close the gaps Ohme was covering - #5685

Merged
springfall2008 merged 4 commits into
mainfrom
fix/iog-ohme-slots-from-octopus
Oct 23, 2026
Merged

springfall2008 merged 4 commits into
mainfrom
fix/iog-ohme-slots-from-octopus

Conversation

@springfall2008

@springfall2008 springfall2008 commented Oct 21, 2026 •

Copy link
Copy Markdown
Owner

Problem

On Octopus Intelligent with an Ohme charger, Predbat reads the planned car slots from Ohme's session slots (octopus_intelligent_planned_slot → binary_sensor.predbat_ohme_slot_active). Those slots carry no source, so a boost started from the Ohme app is priced as an off-peak dispatch. The plan's import column flips to the cheap rate and the house battery is charged from the grid at the full rate. The customer report showed the battery charging at ~3.4 kW from the grid at 35.99p while the car boosted, from this log line:

Octopus: Intelligent slot at 10-07 13:00:00-10-07 13:30:00, assumed price 6.6, amount 0.43, kWh location AT_HOME, source , octopus_slot_low_rate True

Octopus itself listed the same charge as type: BOOST with the device currentState: BOOSTING, so dispatch_billed_off_peak() would already have excluded it. The Octopus sensor's planned list was simply not being read for an Ohme car.

Is Ohme's schedule better than Octopus's?

We compared both sources over 4 days on 6 random production instances on IOG with an Ohme charger, with Ohme supplying the planned slots:

  • Ohme's slots are a relayed copy of Octopus's flexPlannedDispatches: the same half-hours, seconds to minutes later.
  • Ohme was worse in several cases:
    • froze for ~5 h
    • published slot sets Octopus never had
    • twice dropped a real 07:30–08:00 off-peak slot that completed 2.36 kWh
    • wrong first view after plug-in (an hour early, or fragmented)
    • showed no plan for ~4 h while Octopus had one
    • kWh 3–13% high
  • Ohme covered three gaps on the Octopus side, and they hit every IOG charger, not only Ohme:
    1. The plug-in half-hour. Octopus starts charging as soon as the car is plugged in, but never lists that half-hour as planned, only as completed about an hour later. Seen on 3 of 6 instances.
    2. Empty lists during re-plans. While re-planning a running session, Octopus answers flexPlannedDispatches: [], a valid empty list rather than an error, for 2–8 minutes, up to 12 times a night. The Octopus Intelligent: a failed planned-dispatch fetch (KT-CT-4340) is read as no dispatches and drops the car plan #5492 hold only covers a null/failed fetch, so the plan disappeared.
    3. Unplugged. Octopus kept 11.2 kWh of planned dispatches for ~12 h after the car was unplugged, with the device reporting SMART_CONTROL_NOT_AVAILABLE throughout. None of it completed. currentState is already queried but never read.

Change

Three parts. Parts 1 and 2 are in the first commit. Part 3 is the second commit, which replaces the first commit's Ohme change with a simpler one:

  1. Octopus: follow the device's currentState for planned dispatches
  2. Octopus: hold the half hour a car charges in before Octopus lists it
    • In octopus_held_update(), when the car is charging (car_charging_now), the Octopus dispatch sensor says SMART_CONTROL_IN_PROGRESS, and no planned dispatch covers the half-hour, that half-hour is held like a running dispatch.
    • It uses the existing Octopus API: a just-finished half hour of a running Intelligent dispatch is re-priced at day rate until completedDispatches lists it #5520 held mechanism, so it prices the half-hour in progress and creates no forward car slot. A slot from car_charging_now is what 8dc2471 removed, because it kept a Predbat-driven charger going.
    • BOOSTING never qualifies. Slot confirmation (octopus_intelligent_dynamic) cancelling the half-hour still wins.
    • Only the Octopus component's sensor publishes current_state, so other slot sensors are unaffected.
  3. Ohme: leave everything to Octopus when Octopus Intelligent drives the Ohme
    • Auto-detect (ohme_automatic_octopus_intelligent unset) now treats a live Ohme Intelligent device exactly like a live car or another charger: Ohme claims nothing. The Octopus component wires its own dispatch sensor, ready time and charge limit, and the Ohme supplies the car's data, power, energy and car_charging_now.
    • New octopus_drives_ohme flag: ohme_control stands down whatever octopus_intelligent_charger_follows_car says, since Octopus is driving this very charger, and a charger Predbat was still holding is handed over to Octopus rather than released. The shared mixin's charger_control_octopus_drives_charger() already reaches the same answer from the sensor's is_charger.
    • Ohme's session slots, including the fix(ohme): take completed Intelligent dispatches from Octopus, planned slots from Ohme #5443 planned/completed split, are now used only for an Intelligent tariff with no device on the account, or an explicit ohme_automatic_octopus_intelligent: true. wire_planned_slots() / clear_planned_slots() are unchanged from main.
    • Suspending smart charging still falls back to Ohme's own schedule at the tariff rate: OctopusAPI.automatic_config() clears its wiring when no active device remains.

Tests

  • octopus_intelligent_devices: new test_planned_dispatches_follow_device_state covers the not-dispatching states, the empty-list hold (kept at 2 and 8 min, cleared at 12, restarted after a planned list), the state kept through a failed query, and an empty list clearing outside a smart charge. Test 15's empty-list case now runs with SMART_CONTROL_CAPABLE.
  • octopus_dispatch_hold: Tests 14–17 cover the plug-in half-hour being held and cheap with no forward car slot, and nothing held for None / SMART_CONTROL_CAPABLE / BOOSTING / SMART_CONTROL_NOT_AVAILABLE, a car not charging, or a cancelled half-hour.
  • rate_add_io_slots: Test 24b shows a boost in progress (source: BOOST, AT_HOME) at the day rate while the same slot with no source is off-peak. That is the mechanism of the bug.
  • ohme:
  • ./run_all --quick: all tests pass (4 slow tests skipped)

Notes for review

  • 10 minutes for the empty-list hold is from observed gaps of 2–8 min. Happy to tune it.
  • SMART_CONTROL_OFF is included alongside NOT_AVAILABLE because of a past case where a VW returned planned dispatches while smart control was off and they were priced cheap. Drop it if you'd rather keep this to the observed unplug case.
  • The 6-hour cap filling up from completed dispatches, so genuine slots get priced at peak, was seen on 4 of the 6 instances. It is independent of this change and left alone.

🤖 Generated with Claude Code

…he gaps Ohme was covering

On Octopus Intelligent with an Ohme charger the planned car slots were read from Ohme's
session slots, which carry no source - so a boost started from the Ohme app was priced as
an off-peak dispatch and the house battery was grid-charged at the full rate while it ran.
Octopus lists the same charge as type BOOST, which dispatch_billed_off_peak() excludes.

Compared over 4 days on 6 IOG + Ohme instances, Ohme's slots are a relayed copy of
Octopus's flexPlannedDispatches, minutes behind and at times stale or missing a slot. What
Ohme did cover were three gaps on the Octopus side, which this closes for every charger:

- octopus: read the device's currentState, already queried but unused. Planned dispatches
  are dropped while SMART_CONTROL_NOT_AVAILABLE or SMART_CONTROL_OFF (a plan was seen kept
  for 12 hours after unplug), and an empty planned list while SMART_CONTROL_IN_PROGRESS -
  Octopus re-planning a running session - keeps the last known plan for
  OCTOPUS_PLANNED_EMPTY_HOLD_MINUTES. current_state is published on the dispatch sensor.
- octopus: hold the half hour a car charges in during a smart charge with no dispatch listed
  (the plug-in charge, which Octopus only lists once completed), through the #5520 held
  mechanism, so it prices the half hour in progress and adds no forward car slot.
- ohme: with an Octopus dispatch sensor in octopus_intelligent_slot, leave the car slots,
  planned and completed, to it and wire only the ready time and charge limit to Ohme.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the BOT_REVIEW Trigger an autotriage label Oct 21, 2026

@springfall2008 springfall2008 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Automated comment from the triage bot.

Reviewed at head commit 483dba7 — 2 findings, 1 posted inline and 1 in this body. Both minor; the core behaviour checks out (details below).

apps/predbat/ohme.py:702 — automatic_config_octopus_intelligent() still documents the old wiring. Its docstring opens "Automatically set the predbat entities to take the Intelligent car slots from Ohme" and ends "...and Ohme supplies only the planned ones - see octopus_dispatch_sensor()". This PR inverts exactly that (planned and completed slots both come from Octopus's sensor where one is wired, and only the ready time and charge limit are Ohme's), and it updated the neighbouring docstrings and log text (reset() in predbat.py, charger_control_slots_claimed(), the update_slot_mode() log line, octopus_dispatch_sensor() and wire_planned_slots()) — but missed the docstring of the very function that calls wire_planned_slots(). After this PR a reader of that function gets the pre-#5685 behaviour presented as the truth, contradicting both the code directly below it and the new docs table in car-charging.md.

Checks that came back clean, for the record: the new charging-unlisted held source passes dispatch_billed_off_peak() (held=True, end at/behind now) so the plug-in half hour is priced off-peak, while BOOST/bump-charge/charger-schedule stay excluded — Test 24b exercises exactly that split; the held dispatch ends at now so load_octopus_slots() plans no forward car slot from it and "Hold for car" cannot keep it going; dispatch_slots_signature() excludes the drifting start of an in-progress slot, so the re-trimmed kept plan through an empty answer does not request a replan every 2-minute poll; slot confirmation cancelling the half hour wins over the unlisted hold (the strip_from gate precedes it, Test 17); the state-driven drop and the 10-minute empty-hold compose correctly with the failed-query keep from #5492, and the state machine covers CAPABLE/BOOSTING/IN_PROGRESS/NOT_AVAILABLE/empty as documented; devices[].status.currentState really is in intelligent_dispatches_query already, so no API change is being assumed beyond what the PR observed; the new tests restore my_predbat.args and state fields in finally blocks, and car-charging.md covers all three parts including the fallback's limits.

Comment thread apps/predbat/octopus.py
@springfall2008 springfall2008 removed the BOT_REVIEW Trigger an autotriage label Oct 21, 2026
CI and others added 3 commits October 22, 2026 08:30
…s the Ohme

Simpler than splitting the wiring: when the Ohme is a live Intelligent device, Ohme no longer
claims the car slots at all, the same as when Octopus drives the car or another charger. The
Octopus component wires its own dispatch sensor, ready time and charge limit, and the Ohme is
used for the car's data, power and energy. ohme_control stands down whatever
octopus_intelligent_charger_follows_car says, as Octopus is driving this very charger, and a
charger Predbat was still holding is handed over to Octopus.

This reverts the earlier wire_planned_slots()/clear_planned_slots() change: the #5443 split,
Ohme's planned slots beside Octopus's completed ones, is again only for an Intelligent tariff
with no device on the account or an explicit ohme_automatic_octopus_intelligent: true.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts own slot sensor

octopus_charging_unlisted() read octopus_intelligent_slot at the car's index for every car charging,
wired or not. A list too short for the car logged "Out of range index" every cycle and 15 second poll
for the length of the charge, and a single entity resolved to car 0's dispatch sensor at any index, so
a second car charging while car 0 was in a smart charge got a charging-unlisted half hour held (saved
to storage, though never priced, as only a wired car's held half hours are read). It now returns False
for a car fetch_sensor_data_cars() recorded no planned dispatches for, which is only ever a wired car.

Test 18 covers both shapes: nothing held for car 1 and no warning logged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@springfall2008

Copy link
Copy Markdown
Owner Author

Automated reply from the triage bot.

On the review-body finding about automatic_config_octopus_intelligent()'s docstring in ohme.py: left unchanged, because 71bdf8d (pushed after the review at 483dba7) made it accurate again. That commit reverted the split: wire_planned_slots() once more wires only octopus_intelligent_planned_slot to Ohme and leaves octopus_intelligent_slot on Octopus's sensor. That is what the docstring says ("Ohme supplies only the planned ones"). The case this PR set out to fix, where Octopus drives the Ohme itself, now takes the octopus_drives_ohme path, and that path never calls this function.

The inline finding on octopus_charging_unlisted() is fixed in 519e082. Details are in that thread. The branch is also merged up to current main.

@springfall2008
springfall2008 merged commit 5d619ba into main Oct 23, 2026
2 checks passed
@springfall2008
springfall2008 deleted the fix/iog-ohme-slots-from-octopus branch October 23, 2026 08:04
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