Skip to content

fix(ohme): take completed Intelligent dispatches from Octopus, planned slots from Ohme - #5443

Merged
springfall2008 merged 4 commits into
mainfrom
fix/ohme-planned-octopus-completed-5413
Oct 12, 2026
Merged

springfall2008 merged 4 commits into
mainfrom
fix/ohme-planned-octopus-completed-5413

Conversation

@springfall2008

Copy link
Copy Markdown
Owner

Draft for maintainer review. Fixes #5413

Summary

On Octopus Intelligent with an Ohme charger, the car slots came only from Ohme. Ohme reports the slots of the charge session in progress and nothing else, so unplugging the car emptied the slots, and the off-peak rate for charging already done was re-priced at day rate in the plan history and cost_today. The reporter's log shows this at every unplug.

This splits the two jobs:

  • New option octopus_intelligent_planned_slot (per car, optional). When set, planned dispatches are read from it and octopus_intelligent_slot supplies the completed ones. Slots the planned sensor reports as already finished are merged in behind the slot sensor's completed list, so a slot is covered from when it ends until Octopus lists it; the existing overlap handling in load_octopus_slots() drops whatever Octopus has by then.
  • Ohme component. On Intelligent it now sets only the planned option, the ready time and the charge limit when octopus_intelligent_slot is already a sensor of Octopus's (wired by the Octopus component, or set in apps.yaml to the Octopus Energy integration's). With no such sensor it supplies everything, as before. It re-checks every poll, so the Octopus component wiring or clearing its sensor is followed without a restart.
  • car_slot_owner now covers the ready time and charge limit only. The Octopus component keeps wiring its dispatch sensor under another component's claim.
  • Leaving Intelligent (tariff change, or every device suspended): the Octopus sensor Ohme was paired with has nothing more to give, so Ohme's own schedule or Predbat-led control takes over as it did before. It is left in place when Octopus is driving another device.

ohme_control is still ignored on Intelligent in both wirings.

Testing

  • ./run_pre_commit passes, including the full quick suite ("All tests passed, 4 slow tests skipped").
  • ohme, multi_car_iog, octopus_misc and octopus_intelligent_devices fail without the source change and pass with it (source files stashed, tests kept).
  • New tests: a fetch-level regression for the issue (planned_slot_sensor_5413: the completed dispatch survives the charger reporting no slots after unplug), and four Ohme tests covering which sensors count as Octopus's, the split wiring, following the Octopus sensor coming and going, and leaving Intelligent.
  • Three existing tests asserted the old behaviour (Octopus leaving octopus_intelligent_slot alone under an Ohme claim) and were updated.

Not tested against a live Ohme or Octopus account.

Notes

  • Behaviour change for existing Ohme users on Intelligent who run the Octopus component: octopus_intelligent_slot moves from the Ohme sensor to the Octopus dispatch sensor. The car's battery size and charge rate attributes are therefore now read from Octopus's sensor, where Ohme's had none.
  • Users with no Octopus dispatch sensor keep the bug. That includes anyone who followed docs/devices.md and pointed octopus_intelligent_slot at an Ohme sensor. The Ohme component logs which source it is using. The Home Assistant Ohme integration sections of docs/devices.md and the commented template lines are not changed here.
  • A slot Ohme reports as finished that Octopus never confirms stays at the off-peak rate until the car is unplugged, the same as today.
  • Multiple cars: Ohme's planned sensor is wired to car 0, as its slot sensor was.
  • Octopus: Intelligent devices stay cached and wired to the car slots after the account leaves an Intelligent tariff #5412 (Intelligent devices staying cached after leaving the tariff) still applies after a restart: the Octopus component re-wires the stale device's sensor on its first run.
  • Setting ohme_automatic_octopus_intelligent: false with ohme_control: true on an Intelligent tariff still turns Predbat-led control on. That is not changed here.
  • Impact analysis: fetch_sensor_data_cars rates HIGH in GitNexus (it is on the main loop); the change there does nothing when the new option is unset. The newer Ohme wiring functions are not in the GitNexus index, so their callers were established by search.

🤖 Generated with Claude Code

…d slots from Ohme (#5413)

Ohme only reports the slots of the charge session in progress, so unplugging the
car emptied the car slots and the off-peak rate already charged at was re-priced
at day rate in the plan history and today's cost.

Add octopus_intelligent_planned_slot: a per-car sensor the planned dispatches are
read from, leaving octopus_intelligent_slot as the record of the completed ones.
On Octopus Intelligent the Ohme component now sets only that, the ready time and
the charge limit when octopus_intelligent_slot is already a sensor of Octopus's
(wired by the Octopus component or set in apps.yaml), and falls back to supplying
everything when there is no such sensor. The car_slot_owner claim narrows to the
ready time and charge limit, so the Octopus component keeps its dispatch sensor
wired under it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@springfall2008 springfall2008 self-assigned this Oct 8, 2026
@springfall2008
springfall2008 marked this pull request as ready for review October 9, 2026 18:46
Copilot AI balanced review requested due to automatic review settings October 9, 2026 18:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Multi-car configurations can retain stale dispatch wiring and lose per-car ready-time and charge-limit targets.

1 open finding
What changed in this PR

Splits completed Octopus Intelligent dispatches from Ohme-planned slots to preserve historical off-peak pricing.

Changes:

  • Adds octopus_intelligent_planned_slot.
  • Updates Ohme/Octopus wiring ownership.
  • Adds documentation and regression tests.
File Description
apps/​predbat/​config.py Defines the new option.
apps/​predbat/​fetch.py Merges completed and planned dispatch sources.
apps/​predbat/​ohme.py Implements split sensor wiring.
apps/​predbat/​octopus.py Retains Octopus dispatch wiring under Ohme ownership.
apps/​predbat/​predbat.py Updates ownership semantics.
apps/​predbat/​tests/​test_multi_car_iog.py Tests split dispatch fetching.
apps/​predbat/​tests/​test_octopus_intelligent_devices.py Updates ownership tests.
apps/​predbat/​tests/​test_octopus_misc.py Tests retained dispatch wiring.
apps/​predbat/​tests/​test_ohme.py Adds Ohme transition and source tests.
docs/​apps-yaml.md Documents the option.
docs/​car-charging.md Explains split Ohme/Octopus behavior.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread apps/predbat/ohme.py Outdated
CI and others added 3 commits October 11, 2026 22:10
…telligent

octopus_dispatch_sensor() records the first entry of octopus_intelligent_slot
as the source of the completed dispatches, but dispatch_sensor_spent() compared
that against the whole arg. With two Intelligent devices wired the list never
matched, so leaving the tariff left the stale Octopus sensors in place and
refused to take the car plan from Ohme's schedule.

Addresses the review finding on #5443.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@springfall2008
springfall2008 merged commit 6b1889d into main Oct 12, 2026
2 checks passed
@springfall2008
springfall2008 deleted the fix/ohme-planned-octopus-completed-5413 branch October 12, 2026 07:22
@gorstj

gorstj commented Oct 12, 2026

Copy link
Copy Markdown

What are the recommended Ohme/Predbat settings when installing this release?

chalfontchubby added a commit that referenced this pull request Oct 12, 2026
Conflict in apps/predbat/ohme.py (OhmeAPI.__init__): main's #5443 added
slot_source/slot_source_applied (Octopus dispatch sensor read beside Ohme's
planned slots); this branch added follows_car (Predbat drives the charger
while Octopus drives the car). Independent state - kept both. The rest of
update_slot_mode() and run() auto-merged with both changes intact.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
chalfontchubby added a commit that referenced this pull request Oct 12, 2026
…ests

The tests main added in #5443 read OhmeAPI.control_active, which this branch
renamed to charger_control_active when control moved into the shared
CarChargerControl mixin. Only the attribute name changes; the assertions
are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
chalfontchubby added a commit that referenced this pull request Oct 12, 2026
… an Ohme claim

Since #5443 the Octopus component keeps wiring its whole dispatch-sensor list
into octopus_intelligent_slot while Ohme holds car_slot_owner (the claim now
covers only the ready time and charge limit). The car_slot_owner shortcut in
charger_control_octopus_drives_charger()/_drives_car() predates that and
applied to every car, so with an Ohme on car 0 and a second car whose
Intelligent device is the car itself, a Zappi or GE EVC on car 1 was left to
Octopus: follows_car ignored, and a held charger handed over stopped.

The sensor's own is_charger now decides; the claim only stands in when the
sensor does not say (the Ohme's own slot sensor). drives_car no longer reads
an "unknown" is_charger as "the car". Regression test fails on the merge
without this change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
chalfontchubby added a commit that referenced this pull request Oct 12, 2026
…he merge

The previous commit let car 0's own dispatch sensor override an Ohme
car_slot_owner claim. That changed behaviour the merge never touched: with
ohme_automatic_octopus_intelligent forced on, or Octopus's device ids sorting
the car's sensor first, a charger on car 0 that used to be left to Octopus
would be released or driven. Car 0 goes back to the claim alone; only cars
after it, which had no slot at all under the claim before #5443, are judged
on their own sensor - and one whose sensor has lost its value is "cannot
tell" rather than handed to Octopus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
chalfontchubby added a commit that referenced this pull request Oct 12, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
chalfontchubby added a commit that referenced this pull request Oct 12, 2026
…-planned-slots-5339

Conflicts resolved by keeping both sides:
- config.py: APPS_SCHEMA lists both octopus_intelligent_smart_control (this PR)
  and octopus_intelligent_planned_slot (#5443).
- fetch.py: keeps octopus_smart_control_off() and main's car_planned_slot_entity().
- octopus.py: imports take OCTOPUS_MANUAL_DISPATCH_SOURCES as well as main's
  REPEAT_FULL_LOG_SECONDS/RepeatLogGate. automatic_config() wires the Smart
  Control switch alongside the dispatch sensor, which the Octopus component
  still owns, while main's other_owner check (#5443) guards only the ready time
  and charge limit.
- docs/apps-yaml.md, docs/car-charging.md: both new items documented.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
chalfontchubby added a commit that referenced this pull request Oct 12, 2026
…_planned_slot while Smart Control is off (#5339)

Since the merge with main, a car's planned dispatches can come from
octopus_intelligent_planned_slot (#5443) rather than the Octopus dispatch
sensor. Those are the charger's own plan, not the plan Octopus keeps
reporting after Smart Control is switched off, so the Smart Control filter
no longer applies to them. Before this, a charger whose sensor does not tag
its slots as the charger's schedule lost every planned slot while the
switch was off. The Ohme component already tags its slots that way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
chalfontchubby added a commit that referenced this pull request Oct 12, 2026
… Smart Control is off (#5339)

The previous commit kept the planned slots from
octopus_intelligent_planned_slot while Smart Control is off, but did not
change their source. While the Ohme component follows Octopus's dispatches
it leaves its slots untagged, so they were still costed as off-peak
Intelligent dispatches. Octopus is not dispatching anything at that point.
They are now re-tagged as a charger schedule: still car load, but never
off-peak. Manual bump/boost slots keep their own source.

Two further fixes:
- If the planned sensor cannot be read, `planned` still holds the slot
  sensor's Octopus plan. That plan is now filtered as usual, rather than
  being let through because a planned sensor was configured.
- The Smart Control state is now read every fetch again, so the log line
  for its changes stays on time while a planned sensor is set.

Tests:
- t10c checks the source, the off-peak test and the switch-on case.
- t10d covers an unreadable planned sensor.
- t7b pins that automatic_config() still wires the switch with the
  dispatch sensor while another component owns the ready time and
  charge limit (#5443).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
springfall2008 pushed a commit that referenced this pull request Oct 12, 2026
…e and guest charging for GE, Ohme and Zappi (folds #5284 + #5311) (#5448)

* feat(car): add a shared Predbat-led EV charger control mixin

Ohme, myenergi and GivEnergy EVC each carry their own copy of the same
control loop. CarChargerControl owns it once: read the per-car plan,
charge inside a window and stop outside one, re-apply on drift, and
release on read only or when the control switch is turned off, with
the switch persisted across restarts. Components supply only the
charger-specific send, release, connected and drift checks.

Nothing uses it yet; the components move onto it in following commits.

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

* refactor(gecloud): drive EV chargers through the shared charger control

GivEnergy EVC control moves onto CarChargerControl. The component keeps
what is GivEnergy's own: which chargers exist and in what order, whether
a car is connected, and the start-charge / stop-charge commands.

Behaviour is unchanged: the switch keeps its storage key, chargers
without a car are still left alone, and release still only restarts a
charger Predbat had stopped. The EVC read only check now falls back to
the set_read_only arg, like the other chargers, rather than the switch
entity; the fallback only applies before Predbat sets the attribute at
startup. The test double's get_arg answers set_read_only from the same
flag as its switch, as Predbat does.

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

* refactor(myenergi): drive Zappis through the shared charger control

Zappi control moves onto CarChargerControl. The component keeps what is
myenergi's own: Fast inside a window and Stopped outside one, drift
detected from the polled mode, and release back to the saved mode or
Eco+.

One deliberate change: a Zappi with no car to follow (more Zappis than
num_cars, briefly true while auto-config's num_cars reaches the base) is
now left alone rather than Stopped, matching what GivEnergy EVC control
already did.

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

* refactor(ohme): drive the charger through the shared charger control

Ohme control moves onto CarChargerControl. The component keeps what is
Ohme's own: max charge inside a window and pause outside one, drift
detected from the session mode, and release back to Ohme's smart
schedule with the user's target percent restored. Ohme has no control
switch, so read only stays the only release.

Its inline plan parser goes too, in favour of the shared one. That
fixes a New Year case the copy missed: read just after midnight on
1 January, a window that started on 31 December parsed as the following
December, so a charge running across midnight was paused.

A release Ohme refuses is now retried on the next control cycle rather
than attempted once, so a paused car is not stranded by one failed
call. The control tick is wrapped the way myenergi's already is: a
refused command logs a warning instead of failing the whole run, which
would otherwise repeat every minute while read only stays on.

The mixin now checks for a switch before touching storage, so a
component with no switch never needs a storage attribute.

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

* fix(car): address #5284 review - release chargers whose car has gone, retry refused GE commands

- A charger Predbat holds that falls beyond the cars it follows (num_cars
  dropped) was never commanded again, left charging or stopped with
  nobody in control. apply() now releases it.
- GivEnergy's async_send_evc_command() reports a refusal by returning
  None, so a refused release was taken as done and a stopped charger
  stranded - the failure the Ohme and myenergi retries already close.
  GE charger control now raises EVCCommandFailed on a refusal, so the
  command is not recorded and is tried again next cycle; GE's run loop
  catches it as a warning, as Ohme's and myenergi's already do. A
  refused start or stop is retried the same way rather than recorded as
  sent.
- Docstring for a nested test helper.

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

* fix(car): one charger refusing no longer blocks the others, and a released Zappi forgets its mode

Found by a later review, in code from this PR:

- Releasing, releasing a charger whose car has gone, and driving from the plan ran as
  one loop, so the first charger to refuse a command stopped every charger after it -
  and each cycle retried in the same order, so they stayed blocked. With two Zappis
  held Stopped and read only on, a refusing Zappi 1 left Zappi 2 Stopped for good. Each
  charger is now handled on its own; the first failure is still raised after the rest
  have had their turn, for the run loop to log and retry. A charger is forgotten as
  soon as its own release goes through, so a retry re-sends only to the one that
  refused.
- A Zappi released on its own (its car gone when num_cars dropped) kept its saved
  mode, so taking it back later saved nothing new and the next release restored the
  stale mode. The saved mode is now dropped once that Zappi's release goes through.

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

* feat(car): leave a charger to Octopus only when Octopus drives the charger

Charger control now decides per car whether Octopus Intelligent is in
charge of the charger. Where Octopus delivers its dispatches by driving
the charger itself, Predbat leaves it alone - releasing it if it was
holding it - so the two never fight. Where Octopus drives the car, or
there is no Intelligent car, Predbat drives the charger from the car
slot sensor, which then carries the Octopus dispatches, so the car
cannot charge outside them on its own timers.

Octopus car N is the Nth wired Intelligent device, and the Octopus
component already records whether each is a charge point. When that
cannot be told - slots from outside the Octopus component, or before
it has discovered its devices - the charger is left alone, unless the
component's control setting is explicitly true.

Nothing turns control on by default yet; that follows.

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

* feat(gecloud): turn EV charger control on with ge_cloud_automatic_evc

ge_cloud_evc_control no longer defaults to false. Left unset, control follows
ge_cloud_automatic_evc: setting that is the user asking Predbat to plan for the car,
and it is what maps each charger to its car. An explicit false keeps control off,
and only an explicit true warns when a prerequisite is missing.

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

* feat(ohme): turn charger control on with ohme_automatic

ohme_control no longer defaults to false. Left unset, control follows ohme_automatic,
which registers the car. Where the Intelligent slots come from the Ohme, Octopus
drives the charger and Predbat still leaves it alone - logged as Info for an unset
control, and as a warning only when ohme_control was explicitly set true.

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

* feat(myenergi): turn Zappi control on when the user wrote myenergi_automatic

myenergi_zappi_control no longer defaults to false. Left unset, control turns on
only when the user wrote myenergi_automatic or myenergi_automatic_zappi into
apps.yaml themselves. Both default on, so following their values alone - the rule
GE and Ohme use - would switch every Zappi user to Predbat control on upgrade, and a
Zappi held off between windows can no longer divert solar in Eco/Eco+. The options
considered are recorded in enable_control()'s docstring for review.

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

* docs(car): charger control turns on with automatic setup, and Octopus Intelligent

A new "Predbat starting and stopping your charger" section in car-charging.md sets
out when each charger's control turns on while its setting is unset, the upgrade
change for existing automatic users, and what happens on Octopus Intelligent
depending on whether Octopus drives the charger or the car. The per-setting entries
in apps-yaml.md and components.md now describe the unset default instead of false.

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

* fix(car): judge Octopus per car from its own dispatch sensor, and never start a charge handing over

Found by review before opening the stage 2 PR:

- Read is_charger from the car's own wired octopus_intelligent_slot sensor instead of
  pairing car N with the Nth sorted Octopus device. Both the Octopus and Kraken
  components publish it there, so E.ON/EDF SmartFlex users are covered and a slot list
  in a different order can no longer pair a car with another car's device.
- Only wait for Octopus discovery when the Octopus component runs its automatic setup.
  With octopus_automatic off it never wires the slots, so control never turned on.
  "Cannot tell yet" also gets its own log line rather than claiming the car is on IOG.
- Handing a charger to Octopus no longer starts it. A normal release undoes a stop so
  a car is never stranded, but here Octopus decides when to charge: a GE charger
  Predbat stopped is left for Octopus, an Ohme has max charge turned off before it is
  resumed into its smart schedule, and a Zappi returns to its own mode, Eco+ in place
  of Fast.
- The control settings have no default, which also skipped get_arg()'s boolean
  conversion, so a quoted "false" or a 0 read as set. parse_control_setting()
  normalises them to None, True or False in each component.

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

* feat(ohme): only turn control on by default once the car's size and limit are set

Predbat drives an Ohme with max charge, which overrides the target set in the Ohme
app, so car_charging_battery_size and car_charging_limit are all that stop a charge.
Left at their defaults (100 kWh, 100%) the car would be charged to full. Unset
ohme_control now waits for both to be in apps.yaml and logs why; an explicit
ohme_control: true is still the user's call.

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

* docs(car): Ohme size and limit guard, Kraken, and handing over to Octopus

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

* fix(car): a charger Octopus drives stays with Octopus, and a car Octopus drives follows its dispatch sensor

- switch.predbat_octopus_intelligent_charging only changes Predbat's own planning:
  Octopus goes on switching a charger that is its Intelligent device whatever the
  switch says. The device is now judged first, so turning the switch off no longer
  has Predbat fight Octopus for that charger. For a car Octopus drives, the switch
  still decides whether the charger follows the dispatches or Predbat's own plan.
- Where Octopus drives the car, the charger also runs while the car's dispatch sensor
  is on. The plan carries the dispatches but is only republished every 5 minutes and
  leaves out a dispatch Octopus has not given any energy yet, so the charger held the
  car off for the first minutes of a new dispatch.

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

* feat(car): guest charging switch for chargers Predbat drives

While Predbat drives a charger it holds it off outside the planned windows, so a
visitor's car could not charge - and stage 2 turns control on by default. Each
component (GE, myenergi, Ohme) now publishes switch.predbat_<component>_guest_charging
beside its control. On, Predbat releases the charger as it does for read only mode, so
it runs on its own settings; how the guest charges is chosen on the charger itself.

It turns itself off when a connected car is unplugged, on chargers that can tell
(GE), and otherwise after GUEST_CHARGING_MAX_HOURS (12). It is deliberately not
persisted, so a restart puts Predbat back in charge. The guest's charging is still
counted as the car's.

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

* feat(car): end guest charging when the car is unplugged from a Zappi or Ohme

Both report whether a car is plugged in - the Zappi's plug status, the Ohme's
UNPLUGGED status - so guest charging now ends on unplug for every charger, not just
GE. A new charger_control_car_plugged() hook carries it, separate from
charger_control_connected(), so how Predbat drives an empty Zappi or Ohme is
unchanged. An unknown plug state reads as plugged in, leaving the 12 hour limit.

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

* fix(car): guest charging survives the owner unplugging, and dispatch ends are judged by the clock

Found by review before opening the stage 2 PR:

- Guest charging ended on any unplug, including the owner's car being unplugged to make
  way for the guest. Only a car plugged in after the switch went on now ends it
  (empty, plugged in, empty again); a guest who plugged in first is left to the 12 hour
  limit or a manual off.
- A GE status blip (offline, unavailable, unknown) read as an unplug and ended guest
  charging. Only "available" and "idle" count as empty now, as the Zappi and Ohme
  already treat unknown states as plugged in.
- The charger followed the dispatch sensor's on/off state, which only refreshes every
  couple of minutes and so ran the charger past the end of a dispatch. The dispatch
  times on the sensor are now judged against the clock, in both the Octopus and the
  Kraken formats, falling back to the state for a sensor with no times.
- A toggle on the guest switch now flips it rather than turning it off.

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

* feat(car): warn when Predbat cannot tell whether Octopus drives a charger

A dispatch sensor that does not say which device Octopus drives (the Octopus Energy
integration, for one) leaves the charger uncontrolled for good, and was only an Info
line. It is now a warning naming the component's own setting to set true. Waiting for
the Octopus component to discover its devices, which normally clears within a cycle
or two, stays Info. Each reason is logged once, and again only when it changes.

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

* fix(car): wait for Kraken to wire its SmartFlex devices before driving a charger (#5311 review)

Only the Octopus component was waited for. After a restart a Kraken (E.ON/EDF
SmartFlex) user's charger could be driven before Kraken had wired
octopus_intelligent_slot - stopping a GE charger mid-dispatch, which the hand-over on
the next cycle then left stopped. Kraken wires its devices in its first successful run,
from cache or fresh discovery, so until it has started the charger is left alone.

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

* fix(car): release a held charger when Predbat cannot tell who drives it, and stop waiting on discovery after 15 minutes (#5311 review)

- Cannot tell whether Octopus drives a charger: a charger Predbat had stopped
  was let go without being started, as if Octopus were taking it over. With
  nobody known to take it over, a GE charger was left stopped indefinitely.
  It is now released as usual; the non-starting hand-over is kept for a
  charger Octopus is known to drive.
- Waiting for Octopus or Kraken discovery is bounded at 15 minutes. A Kraken
  component whose first run never succeeds (a failed login) otherwise left
  every unwired car's charger uncontrolled for good. The log line now names
  both components.
- The apps.yaml default's literal "re:" string, before its regex matches, is
  no longer read as a dispatch sensor - which on every restart warned users
  with no Intelligent sensor to set a control override they do not need.

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

* fix(car): leave a held charger as it is while discovery runs, and treat a discovery that never finishes as cannot tell (#5311 review)

Releasing during discovery started a charge Predbat had stopped whenever the
Octopus or Kraken component restarted, and driving the charger once the wait
ran out could fight Kraken if it was the one driving it. While discovery is
under way the charger is now left as it is and still held, so it is settled
either way once discovery finishes. After 15 minutes Predbat still cannot
tell, so it warns once and hands back a charger it holds, as for a dispatch
sensor that does not say.

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

* fix(car): start each wait for discovery afresh once a charger's driver is known (#5311 review)

The wait's start was only cleared when discovery was asked about with nothing
discovering, which stops happening once every car's driver is known. A later
component restart could then find the old start and count the wait as
already over.

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

* feat(ohme): Predbat drives the Ohme only with ohme_control: true, so its own schedule stays the car plan otherwise

Main's #5401 now takes the car plan from Ohme's own schedule when Predbat is
not controlling the charger, so a default user already gets a plan that matches
what the charger will do. Turning control on by default would override that
schedule - one the user may have set up deliberately - with max charge, which
also overrides the Ohme app's target. ohme_control goes back to a false default;
GE and Zappi keep their automatic defaults. The size and limit guard for an
unset ohme_control goes, as there is no unset default any more.

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

* feat(car): octopus_intelligent_charger_follows_car - leave a charger alone when Octopus drives the car, unless asked to follow it

When the car itself is the Octopus Intelligent device, the car starts and stops
its own charge. Predbat now leaves the charger alone by default, handing over a
charger it held without starting a charge, as it does when Octopus drives the
charger. The new expert-mode switch, default off, has Predbat drive the charger
to the car's dispatches instead, so the car cannot also charge on its own
timers.

The Ohme follows the same switch: with it on, ohme_control can run while
Octopus drives the car, where #5405 otherwise stands it down. A change of the
switch is picked up on the next Ohme poll.

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

* fix(car): release a held charger when Octopus drives the car, keep following Predbat's own plan with Intelligent charging off, and log only real Ohme changes (#5311 review)

- Octopus driving the car means nobody drives the charger, so a charger
  Predbat had stopped is released as usual (GE started, Zappi back to its own
  mode including Fast) rather than handed over without starting, which left it
  stopped through every dispatch.
- With switch.predbat_octopus_intelligent_charging off Predbat plans the car
  itself, so it still drives the charger to that plan whatever the new switch
  says.
- An Ohme poll that sees only the new switch change logs that change, and only
  re-decides control while Octopus drives another device - not the Octopus
  tariff change it used to report.
- The log points at expert mode for the switch, and the class docstring says
  the charger is left alone by default.

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

* fix(ohme): drive the Ohme to Predbat's own plan with Intelligent charging off while Octopus drives the car (#5311 review)

The shared charger rule drives a charger for a car Octopus drives when
switch.predbat_octopus_intelligent_charging is off, as Predbat then plans the
car itself; the Ohme stood control down there regardless. It now follows the
same rule, and re-decides when either switch changes.

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

* fix(car): read a dispatch time with no offset as UTC, and a lost is_charger value as cannot tell (local review)

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

* fix(car): hand an Ohme to Octopus without starting a charge, and warn when Storage refuses to save the control switch (#5448 review)

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

* refactor(car): share the hand-over to Octopus, and only cancel a max charge Predbat started (#5448 review)

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

* fix(car): time each car's wait for Octopus discovery on its own (local review)

The discovery wait was one clock shared by every car, and drive_one reset it
whenever any car's driver was known. With two cars - one wired, one still
waiting on a Kraken login that keeps failing - the wired car reset the clock
every cycle, so the waiting car never reached "cannot tell" and its charger
was held as it was indefinitely, with no warning. The clock is now kept per
car, and still cleared for every car once discovery has finished.

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

* docs(debug-journal): restore control_active for the Sunsynk/Deye persistence clause (#5448 review)

The myenergi rename to charger_control_active caught the Sunsynk/Deye
sentence too; those components still persist control_active.

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

* fix(car): leave a charger alone while its own car's plan is unpublished (#5448 review)

charger_control_refresh_windows() only waited for any car's plan, so with
car 0's slot sensor published and car 1's not, car 1 read as an empty plan
and its charger was stopped. A car missing from charger_control_windows is
now unknown: its charger is left as it is until its plan appears or an
Octopus dispatch says to charge.

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

* fix(car): keep driving a held charger whose car's plan is unpublished (local review)

The unknown-plan skip came before the drift and last-state checks, so a
charger a dispatch had started for a car with no published plan was frozen
on once the dispatch ended. The skip now applies only to a charger Predbat
does not hold.

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

* fix(car): keep a car's last plan when its slot sensor briefly loses it (local review)

A held charger whose car's planned attribute went missing for a cycle was
driven from an empty window list and stopped mid-charge. The windows are
absolute times, so the last ones read are carried forward instead.

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

* fix(ohme): a refused hand-back no longer fails the whole poll (local review)

update_slot_mode() handed the charger back outside any handler, so an
ApiException from Ohme skipped the session fetch, publish and slot wiring
for every poll until Ohme accepted the command. It is now a warning, as in
the control tick; the charger stays held and the next poll retries.

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

* fix(myenergi): save a Zappi's mode only once Predbat's command has gone through (local review)

The mode was snapshotted ahead of set_mode, so a refused command left a
snapshot for a Zappi Predbat never held. Release paths only run for held
Zappis, so it could outlive the moment and be restored much later in place
of what the user had set by then.

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

* fix(output): publish each car's slot sensor with only its own windows

publish_car_plan() built one plan list for every car, and handed the same
list object to each slot sensor, so with two or more cars every sensor's
'planned' attribute held the windows of all of them. Pre-existing on main,
but the shared charger control drives charger N from car N's 'planned', so
a charger would start in another car's window.

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

* Revert "fix(myenergi): save a Zappi's mode only once Predbat's command has gone through (local review)"

This reverts commit 3ff61b7. Snapshotting after set_mode traded one hazard
for a worse one: a command that timed out but was applied is retried with
the Zappi already Stopped, and Stopped was then saved as the user's mode, so
a later release left the car unable to charge. Saving ahead of the command
gets the mode the user really had; a snapshot left by a refused command is
the lesser risk, and a full release still clears it.

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

* fix(myenergi): never release or hand over a Zappi back to Stopped (local review)

A saved Stopped is most likely Predbat's own hold: after a restart, or a
Stop that timed out but was applied, the first snapshot is taken with the
Zappi already Stopped. Restoring it left the car unable to charge, the very
outcome a release exists to prevent. It now falls back to Eco+, as for no
saved mode.

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

* test(ohme): use charger_control_active in the #5443 dispatch-sensor tests

The tests main added in #5443 read OhmeAPI.control_active, which this branch
renamed to charger_control_active when control moved into the shared
CarChargerControl mixin. Only the attribute name changes; the assertions
are unchanged.

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

* fix(charger-control): judge each car on its own dispatch sensor under an Ohme claim

Since #5443 the Octopus component keeps wiring its whole dispatch-sensor list
into octopus_intelligent_slot while Ohme holds car_slot_owner (the claim now
covers only the ready time and charge limit). The car_slot_owner shortcut in
charger_control_octopus_drives_charger()/_drives_car() predates that and
applied to every car, so with an Ohme on car 0 and a second car whose
Intelligent device is the car itself, a Zappi or GE EVC on car 1 was left to
Octopus: follows_car ignored, and a held charger handed over stopped.

The sensor's own is_charger now decides; the claim only stands in when the
sensor does not say (the Ohme's own slot sensor). drives_car no longer reads
an "unknown" is_charger as "the car". Regression test fails on the merge
without this change.

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

* fix(charger-control): keep the Ohme claim deciding car 0, as before the merge

The previous commit let car 0's own dispatch sensor override an Ohme
car_slot_owner claim. That changed behaviour the merge never touched: with
ohme_automatic_octopus_intelligent forced on, or Octopus's device ids sorting
the car's sensor first, a charger on car 0 that used to be left to Octopus
would be released or driven. Car 0 goes back to the claim alone; only cars
after it, which had no slot at all under the claim before #5443, are judged
on their own sensor - and one whose sensor has lost its value is "cannot
tell" rather than handed to Octopus.

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

* docs(charger-control): say what an Ohme claim covers since #5443

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

* fix(car): charger control stays off unless set, and a Zappi's own mode survives a restart

Review changes from Trefor on #5448.

ge_cloud_evc_control and myenergi_zappi_control default to false again, so an
upgrade never starts Predbat driving a charger nobody asked it to drive. Control
runs only with an explicit true, and still stands down where Octopus Intelligent
drives the charger or the car. MyEnergiAPI.automatic_set_by_user() is removed.

The mode a Zappi had before Predbat took over is now saved to storage and
restored on startup, so a restart no longer snapshots Predbat's own Stopped as
the mode to release to. With that fixed, a saved Stopped is restored like any
other mode, on release and on the hand-over to Octopus (which still never uses
Fast). Modes left in storage are dropped when control is off at startup.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: CI <ci@example.com>
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.

IOG extra car charging slots still not holding the off-peak rate

3 participants