Skip to content

Commit 71fffbe

Browse files
CIclaude
andcommitted
fix(ohme): treat a multi-car Octopus slot list as spent on leaving Intelligent
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>
1 parent a862f32 commit 71fffbe

2 files changed

Lines changed: 44 additions & 1 deletion

File tree

‎apps/predbat/ohme.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -748,8 +748,13 @@ def dispatch_sensor_spent(self, entity_id):
748748
every device is suspended - it is not a plan of anyone's, and left there it would keep the
749749
car on dispatches that never come. Not so while Octopus drives another device, whose
750750
dispatches that sensor is about to carry.
751+
752+
Takes octopus_intelligent_slot as it is set, so a list of several cars as well as one sensor:
753+
octopus_dispatch_sensor() reads the first of the list, and the rest are Octopus's dispatch
754+
sensors for the same account, spent along with it.
751755
"""
752-
return bool(entity_id) and entity_id == self.slot_source_applied and not self.octopus_intelligent and not self.octopus_other_device
756+
entities = entity_id if isinstance(entity_id, list) else [entity_id]
757+
return bool(self.slot_source_applied) and self.slot_source_applied in entities and not self.octopus_intelligent and not self.octopus_other_device
753758

754759
async def automatic_config_charger_slots(self):
755760
"""

‎apps/predbat/tests/test_ohme.py‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2560,6 +2560,44 @@ def start(control=False):
25602560
assert api.args["octopus_ready_time"] == "select.set_by_hand", f"Expected the hand-set ready time left, got {api.args}"
25612561
assert api.args["octopus_intelligent_slot"] == ["binary_sensor.another_charger"], f"Expected another sensor's wiring left, got {api.args}"
25622562

2563+
# Two Intelligent devices on the account, so Octopus has wired a sensor for each car. The whole
2564+
# list is spent once the tariff has gone, not only the one sensor the completed dispatches came from
2565+
second_entity = "binary_sensor.predbat_octopus_test2_intelligent_dispatch"
2566+
2567+
def start_two_cars(control=False):
2568+
"""An Ohme on Intelligent beside a second Intelligent device, each with a dispatch sensor of Octopus's"""
2569+
api = _ohme_api_with_octopus(IOG_TARIFF, {"dev1": IOG_DEVICE_OHME, "dev2": IOG_DEVICE_CAR})
2570+
api.ohme_automatic = True
2571+
api.ohme_control = control
2572+
api.args["octopus_intelligent_slot"] = [OCTOPUS_DISPATCH_ENTITY, second_entity]
2573+
_ohme_run_poll(api, first=True)
2574+
assert api.slot_source == OCTOPUS_DISPATCH_ENTITY, f"Expected the first Octopus sensor found, got {api.slot_source}"
2575+
assert api.args["octopus_intelligent_planned_slot"] == [ohme_entity], f"Expected the planned slots from Ohme, got {api.args}"
2576+
return api
2577+
2578+
api = start_two_cars()
2579+
_ohme_set_tariff(api, "E-1R-COSY-22-12-08-A")
2580+
assert _ohme_run_poll(api, seconds=120) == [True], "Expected the charger schedule once off Intelligent with two cars wired"
2581+
assert api.slot_mode == "charger_schedule" and api.charger_slots_blocked is None, f"Expected charger schedule mode, got {api.slot_mode} blocked {api.charger_slots_blocked}"
2582+
assert api.args["octopus_intelligent_slot"] == ohme_entity, f"Expected the Ohme schedule wired as the plan, got {api.args}"
2583+
assert api.args["octopus_intelligent_planned_slot"] == [], f"Expected the planned sensor cleared, got {api.args}"
2584+
2585+
api = start_two_cars(control=True)
2586+
_ohme_set_tariff(api, "E-1R-COSY-22-12-08-A")
2587+
_ohme_run_poll(api, seconds=120)
2588+
assert api.control_active is True and api.slot_mode is None, f"Expected Predbat-led control with no slot wiring, got {api.control_active} {api.slot_mode}"
2589+
for arg in ("octopus_intelligent_slot", "octopus_intelligent_planned_slot", "octopus_ready_time", "octopus_charge_limit"):
2590+
assert api.args[arg] == [], f"Expected {arg} cleared for Predbat-led control with two cars wired, got {api.args[arg]}"
2591+
2592+
# A list the completed dispatches were never read from is somebody else's wiring, and is left
2593+
api = start_two_cars()
2594+
_ohme_set_tariff(api, "E-1R-COSY-22-12-08-A")
2595+
other_list = ["binary_sensor.another_charger", "binary_sensor.another_charger_2"]
2596+
api.args["octopus_intelligent_slot"] = list(other_list)
2597+
_ohme_run_poll(api, seconds=120)
2598+
assert api.slot_mode is None, f"Expected the slots left to the other wiring, got {api.slot_mode}"
2599+
assert api.args["octopus_intelligent_slot"] == other_list, f"Expected another list's wiring left, got {api.args}"
2600+
25632601
print("PASS: leaving Intelligent cleared the planned slot wiring")
25642602
return 0
25652603

0 commit comments

Comments
 (0)