diff --git a/.cspell/custom-dictionary-workspace.txt b/.cspell/custom-dictionary-workspace.txt index 9c112743e..02531c7d3 100644 --- a/.cspell/custom-dictionary-workspace.txt +++ b/.cspell/custom-dictionary-workspace.txt @@ -284,6 +284,7 @@ nattribute ncalls nearr newtok +NILM njpwerner nobat nocharge diff --git a/apps/predbat/config.py b/apps/predbat/config.py index b2e4adbd1..201d9d5c2 100644 --- a/apps/predbat/config.py +++ b/apps/predbat/config.py @@ -718,6 +718,13 @@ "default": False, "enable": "expert_mode", }, + { + "name": "octopus_intelligent_confirm_slots", + "friendly_name": "Only treat confirmed Intelligent dispatch slots as low rate", + "type": "switch", + "default": False, + "enable": "expert_mode", + }, { "name": "octopus_intelligent_consider_full", "friendly_name": "Consider car full as part of Intelligent plan", diff --git a/apps/predbat/config/apps.yaml b/apps/predbat/config/apps.yaml index b7fab9ade..bad842cfa 100644 --- a/apps/predbat/config/apps.yaml +++ b/apps/predbat/config/apps.yaml @@ -333,9 +333,14 @@ pred_bat: - 'waiting' - 'waiting_car' - # In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots) - # The car charging now can be set to a sensor to indicate the car is charging and to plan - # for it to charge during this 30 minute slot + # In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots). + # car_charging_now can be set to a sensor that indicates the car is charging, so Predbat plans + # for it to charge during this 30 minute slot. It's also the most reliable real-time confirmation + # that a daytime Octopus Intelligent Go dispatch slot is genuinely being used - see + # switch.predbat_octopus_intelligent_confirm_slots in docs/car-charging.md. If you're on that + # tariff's dynamic slot scheme, setting this is recommended even if slot detection already works. + # It doesn't have to be the car's own sensor - a charger-level "currently charging" sensor often + # works better, e.g. for a Hypervolt charger: car_charging_now: sensor.hypervolt_charging #car_charging_now: # - off diff --git a/apps/predbat/fetch.py b/apps/predbat/fetch.py index 4485c6841..e8a78ed99 100644 --- a/apps/predbat/fetch.py +++ b/apps/predbat/fetch.py @@ -1198,11 +1198,16 @@ def fetch_sensor_data_cars(self): self.record_status(message="Error: octopus_intelligent_slot not set correctly in apps.yaml for car {}".format(car_n), had_errors=True) # Completed and planned slots - merge from all cars + # Tag provenance (copies, not in-place mutation, since get_state_wrapper may return a + # cached/shared list): completed dispatches are Octopus's metered, confirmed record; + # planned dispatches are still Octopus's own provisional schedule, which it "routinely + # withdraws on its next re-plan" if the car never actually draws power. See + # rate_add_io_slots for how this gates low-rate treatment for out-of-window slots. if completed: - self.octopus_slots[car_n] += completed + self.octopus_slots[car_n] += [dict(slot, provisional=False) for slot in completed] if planned and (not self.octopus_intelligent_ignore_unplugged or self.car_charging_planned[car_n]): # We only count planned slots if the car is plugged in or we are ignoring unplugged cars - self.octopus_slots[car_n] += planned + self.octopus_slots[car_n] += [dict(slot, provisional=True) for slot in planned] # Extract vehicle data if we can get it size = self.get_state_wrapper(entity_id=entity_id, attribute="vehicle_battery_size_in_kwh") @@ -2390,6 +2395,19 @@ def fetch_config_options(self): self.set_window_minutes = 0 self.octopus_intelligent_charging = self.get_arg("octopus_intelligent_charging") self.octopus_intelligent_ignore_unplugged = self.get_arg("octopus_intelligent_ignore_unplugged") + self.octopus_intelligent_confirm_slots = self.get_arg("octopus_intelligent_confirm_slots") + if self.octopus_intelligent_confirm_slots and self.octopus_intelligent_charging and "car_charging_now" not in self.args: + self.log( + "Warn: switch.predbat_octopus_intelligent_confirm_slots is On but car_charging_now is not set in apps.yaml - " + "daytime Octopus Intelligent Go dispatch slots (outside the fixed 23:30-05:30 window) cannot be confirmed in " + "real time and will not be treated as low rate for the house battery. Set car_charging_now (it can point at a " + "charger-level sensor, not just the car) to restore that protection, or turn this switch Off to accept the " + "risk of a rescinded slot instead." + ) + self.record_status( + "Warn: octopus_intelligent_confirm_slots is On but car_charging_now is not set - daytime IOG slots cannot be confirmed", + had_errors=True, + ) self.octopus_intelligent_consider_full = self.get_arg("octopus_intelligent_consider_full") self.car_energy_reported_load = self.get_arg("car_energy_reported_load") self.get_car_charging_planned() diff --git a/apps/predbat/octopus.py b/apps/predbat/octopus.py index bacbc2d33..56a147498 100644 --- a/apps/predbat/octopus.py +++ b/apps/predbat/octopus.py @@ -2238,6 +2238,7 @@ def add_now_to_octopus_slot(self, car_n, octopus_slots, now_utc): slot["end"] = slot_end_date.strftime(TIME_FORMAT) slot["source"] = "car_charging_now" slot["kwh"] = self.car_charging_rate[car_n] * 30 / 60 # Scale to 30 minute slot + slot["provisional"] = False # Real-time confirmed draw, not a still-revisable Octopus plan octopus_slots.append(slot) self.log("Octopus: Car is charging now - added new IO slot {}".format(slot)) return octopus_slots @@ -2372,6 +2373,21 @@ def load_saving_slot(self, octopus_saving_slots, export=False, rate_replicate=No self.load_scaling_dynamic[minute] = self.load_scaling_saving rate_replicate[minute] = "saving" + def minute_in_iog_fixed_window(self, minute_abs): + """ + True if minute_abs (minutes-since-midnight-of-today, may be negative or beyond + forecast_minutes) falls within the fixed IOG off-peak window (23:30-05:30), which is + guaranteed cheap by the tariff itself and never subject to dispatch-slot rescission. + """ + window = OCTOPUS_NIGHT_RATE_WINDOWS["iog"] + start_minute = window["start"][0] * 60 + window["start"][1] + end_minute = window["end"][0] * 60 + window["end"][1] + minute_of_day = minute_abs % 1440 + if window["cross_midnight"]: + return minute_of_day >= start_minute or minute_of_day < end_minute + else: + return start_minute <= minute_of_day < end_minute + def decode_octopus_slot(self, car_n, slot, raw=False): """ Decode IOG slot @@ -2385,6 +2401,9 @@ def decode_octopus_slot(self, car_n, slot, raw=False): source = slot.get("source", "") location = slot.get("location", "") + # Whether this slot is still Octopus's provisional/revisable plan (True) or a confirmed + # real dispatch (False) - see rate_add_io_slots for how this gates low-rate treatment. + provisional = slot.get("provisional", False) start_minutes = minutes_to_time(start, self.midnight_utc) end_minutes = minutes_to_time(end, self.midnight_utc) @@ -2396,7 +2415,7 @@ def decode_octopus_slot(self, car_n, slot, raw=False): end_minutes = max(min(end_minutes, self.forecast_minutes + self.minutes_now), start_minutes) if start_minutes == end_minutes: - return 0, 0, 0, source, location + return 0, 0, 0, source, location, provisional cap_minutes = end_minutes - start_minutes @@ -2410,7 +2429,7 @@ def decode_octopus_slot(self, car_n, slot, raw=False): # Remove empty slots if kwh is None and location == "" and source == "": - return 0, 0, 0, source, location + return 0, 0, 0, source, location, provisional # Create kWh if missing if kwh is None: @@ -2426,7 +2445,7 @@ def decode_octopus_slot(self, car_n, slot, raw=False): else: kwh = 0 - return start_minutes, end_minutes, kwh, source, location + return start_minutes, end_minutes, kwh, source, location, provisional def load_octopus_slots(self, car_n, octopus_slots, octopus_intelligent_consider_full): """ @@ -2445,7 +2464,7 @@ def load_octopus_slots(self, car_n, octopus_slots, octopus_intelligent_consider_ # Decode the slots for slot in octopus_slots: - start_minutes, end_minutes, kwh, source, location = self.decode_octopus_slot(car_n, slot) + start_minutes, end_minutes, kwh, source, location, _ = self.decode_octopus_slot(car_n, slot) # Octopus zeros chargeKwh once it calculates the car has hit its target SoC, but the # dispatch window stays open and the charger may still draw power. Preserve active slots # with a duration-based kwh so the "Hold for car" guard in execute.py still fires. @@ -2542,11 +2561,28 @@ def load_octopus_slots(self, car_n, octopus_slots, octopus_intelligent_consider_ def rate_add_io_slots(self, car_n, rates, octopus_slots): """ - # Add in any planned octopus slots - # Octopus limits cheap slots to 6 hours (12 x 30-min slots) per 24-hour period + Add in any planned Octopus Intelligent Go dispatch slots as a low import rate. + + Octopus limits cheap slots to 6 hours (12 x 30-min slots) per 24-hour period. A daytime + dispatch slot (outside the fixed 23:30-05:30 off-peak window) is still Octopus's own + provisional/revisable plan until it's confirmed - if the car never actually draws power, + Octopus retroactively rescinds the cheap rate for that slot. When + octopus_intelligent_confirm_slots is enabled (default Off), an out-of-window slot is only + treated as low-rate once it's confirmed - either because Octopus reports it as a completed + (metered) dispatch, or car_charging_now confirms real-time draw - so the battery doesn't + commit to charging on a rate that may later be withdrawn. Slots inside the fixed window are + always trusted, since that window is guaranteed cheap by the tariff itself, not by the + dispatch mechanism. + + In practice, car_charging_now is the only one of those two confirmations fast enough to + matter for a still-live slot - a completed-dispatch confirmation may not arrive from Octopus + until after the slot has already ended. Enabling this switch without also configuring + car_charging_now means out-of-window slots will essentially never be treated as low-rate; + fetch_config_options() logs a warning for that combination. """ octopus_slot_low_rate = self.get_arg("octopus_slot_low_rate", True) octopus_slot_max = self.get_arg("octopus_slot_max", OCTOPUS_SLOT_MAX_DEFAULT) + confirm_slots_only = self.octopus_intelligent_confirm_slots # Track slots per 24-hour period (keyed by day offset from midday) # Period 0 = noon today to 11:59 tomorrow, Period -1 = noon yesterday to 11:59 today, etc. @@ -2558,9 +2594,13 @@ def rate_add_io_slots(self, car_n, rates, octopus_slots): saved_slots = set() # For logging purposes, track which slots we actually applied as low rate if octopus_slots: - # Add in IO slots + # Decode and filter all slots up front, then process confirmed slots (provisional=False) + # before still-provisional ones - so a genuine confirmation (e.g. a car_charging_now + # entry appended after the original Octopus dispatch entry) always wins a tied minute + # over a still-provisional entry for the same time, regardless of append order. + decoded_slots = [] for slot in octopus_slots: - start_minutes, end_minutes, kwh, source, location = self.decode_octopus_slot(car_n, slot, raw=True) + start_minutes, end_minutes, kwh, source, location, provisional = self.decode_octopus_slot(car_n, slot, raw=True) # Ignore bump-charge slots as their cost won't change if source != "bump-charge" and source != "BOOST" and (not location or location == "AT_HOME"): @@ -2571,49 +2611,58 @@ def rate_add_io_slots(self, car_n, rates, octopus_slots): end_minutes = ((end_minutes + plan_interval_minutes - 1) // plan_interval_minutes) * plan_interval_minutes start_minutes = max(start_minutes, -96 * 60) # Allow for previous 2 days end_minutes = min(end_minutes, self.forecast_minutes) + decoded_slots.append((start_minutes, end_minutes, kwh, source, location, provisional)) - for minute in range(start_minutes, end_minutes): - if octopus_slot_low_rate: - assumed_price = self.rate_min_base - else: - assumed_price = self.rate_import.get(start_minutes, self.rate_min) + decoded_slots.sort(key=lambda entry: entry[5]) # Stable: confirmed (False) slots first - if minute in saved_slots: - continue # Already applied a low rate slot to this minute, skip - else: - saved_slots.add(minute) - - # Calculate which day this minute belongs to (day boundary at midday) - # Period 0 = noon today (720) to 11:59 tomorrow (2159), etc. - # Python's floor division handles negative numbers correctly - day_offset = (minute - 720) // (24 * 60) - - # Initialise counter for this day if needed - if day_offset not in slots_per_day: - slots_per_day[day_offset] = 0 - - # Calculate the 30-min slot start for this minute - slot_start = (minute // 30) * 30 - - # At the start of each 30-min slot, decide if we can add it - if minute % 30 == 0: - if slots_per_day[day_offset] < octopus_slot_max: - slots_per_day[day_offset] += 1 - slots_added_set.add(slot_start) - rates[minute] = assumed_price - else: - assumed_price = self.rate_max_base + for start_minutes, end_minutes, kwh, source, location, provisional in decoded_slots: + for minute in range(start_minutes, end_minutes): + if octopus_slot_low_rate: + assumed_price = self.rate_min_base + else: + assumed_price = self.rate_import.get(start_minutes, self.rate_min) + + if minute in saved_slots: + continue # Already applied a low rate slot to this minute, skip + else: + saved_slots.add(minute) + + # Calculate which day this minute belongs to (day boundary at midday) + # Period 0 = noon today (720) to 11:59 tomorrow (2159), etc. + # Python's floor division handles negative numbers correctly + day_offset = (minute - 720) // (24 * 60) + + # Initialise counter for this day if needed + if day_offset not in slots_per_day: + slots_per_day[day_offset] = 0 + + # Calculate the 30-min slot start for this minute + slot_start = (minute // 30) * 30 + + # A still-provisional (unconfirmed) out-of-window slot doesn't get the low rate - + # Octopus can withdraw it before it's ever delivered. The fixed off-peak window + # is always trusted regardless of provenance. + confirmed = (not confirm_slots_only) or (not provisional) or self.minute_in_iog_fixed_window(slot_start) + + # At the start of each 30-min slot, decide if we can add it + if minute % 30 == 0: + if confirmed and slots_per_day[day_offset] < octopus_slot_max: + slots_per_day[day_offset] += 1 + slots_added_set.add(slot_start) + rates[minute] = assumed_price else: - # For minutes within a 30-min slot, only apply if the slot was added - if slot_start in slots_added_set: - rates[minute] = assumed_price - - if minute % 30 == 0 and start_minutes > -24 * 60: - self.log( - "Octopus: Intelligent slot at {}-{}, assumed price {}, amount {}, kWh location {}, source {}, octopus_slot_low_rate {}".format( - self.time_abs_str(start_minutes), self.time_abs_str(end_minutes), dp2(assumed_price), dp2(kwh), location, source, octopus_slot_low_rate - ) + assumed_price = self.rate_max_base + else: + # For minutes within a 30-min slot, only apply if the slot was added + if slot_start in slots_added_set: + rates[minute] = assumed_price + + if minute % 30 == 0 and start_minutes > -24 * 60: + self.log( + "Octopus: Intelligent slot at {}-{}, assumed price {}, amount {}, kWh location {}, source {}, octopus_slot_low_rate {}, provisional {}, confirmed {}".format( + self.time_abs_str(start_minutes), self.time_abs_str(end_minutes), dp2(assumed_price), dp2(kwh), location, source, octopus_slot_low_rate, provisional, confirmed ) + ) # Log daily slot counts for debugging for day_offset in sorted(slots_per_day.keys()): diff --git a/apps/predbat/predbat.py b/apps/predbat/predbat.py index ff6573fd9..2138eb161 100644 --- a/apps/predbat/predbat.py +++ b/apps/predbat/predbat.py @@ -480,6 +480,7 @@ def reset(self): self.car_charging_energy = {} self.octopus_intelligent_charging = False self.octopus_intelligent_ignore_unplugged = False + self.octopus_intelligent_confirm_slots = False self.octopus_intelligent_consider_full = False self.notify_devices = ["notify"] self.octopus_url_cache = {} diff --git a/apps/predbat/tests/test_fetch_config_options.py b/apps/predbat/tests/test_fetch_config_options.py index 0fc67bb4c..c3c930f4a 100644 --- a/apps/predbat/tests/test_fetch_config_options.py +++ b/apps/predbat/tests/test_fetch_config_options.py @@ -356,6 +356,56 @@ def mock_expose_config(key, value): print("✓ Case-insensitivity test passed") + # Test 14: octopus_intelligent_confirm_slots warns when car_charging_now isn't configured + print("\n*** Test 14: octopus_intelligent_confirm_slots warns without car_charging_now ***") + + original_log = my_predbat.log + log_messages = [] + my_predbat.log = lambda message: log_messages.append(message) + + # my_predbat.args only has the battery-curve keys set earlier in this test - car_charging_now + # is deliberately absent, mirroring a user who never uncommented it in apps.yaml. + mock_config.config["octopus_intelligent_confirm_slots"] = True + mock_config.config["octopus_intelligent_charging"] = True + + my_predbat.had_errors = False + my_predbat.fetch_config_options() + + # The detailed explanation is always logged... + detailed_warnings = [msg for msg in log_messages if "cannot be confirmed in real time and will not be treated as low rate" in msg] + assert len(detailed_warnings) == 1, "Should log the detailed warning exactly once, got {}".format(len(detailed_warnings)) + # ...and also surfaced on the status sensor, not just buried in the log + assert my_predbat.had_errors is True, "Missing car_charging_now with confirm_slots on should flag had_errors via record_status" + + # Configuring car_charging_now should silence the warning + log_messages.clear() + my_predbat.args["car_charging_now"] = "sensor.car_charging_now" + my_predbat.had_errors = False + + my_predbat.fetch_config_options() + + detailed_warnings = [msg for msg in log_messages if "cannot be confirmed in real time and will not be treated as low rate" in msg] + assert len(detailed_warnings) == 0, "Should not warn once car_charging_now is configured, got {}".format(detailed_warnings) + assert my_predbat.had_errors is False, "Should not flag had_errors once car_charging_now is configured" + + # Switch off should also silence the warning even without car_charging_now configured + del my_predbat.args["car_charging_now"] + mock_config.config["octopus_intelligent_confirm_slots"] = False + log_messages.clear() + my_predbat.had_errors = False + + my_predbat.fetch_config_options() + + detailed_warnings = [msg for msg in log_messages if "cannot be confirmed in real time and will not be treated as low rate" in msg] + assert len(detailed_warnings) == 0, "Should not warn when octopus_intelligent_confirm_slots is off, got {}".format(detailed_warnings) + assert my_predbat.had_errors is False, "Should not flag had_errors when octopus_intelligent_confirm_slots is off" + + my_predbat.log = original_log + my_predbat.had_errors = False + mock_config.config["octopus_intelligent_confirm_slots"] = False + + print("✓ octopus_intelligent_confirm_slots warning test passed") + # Restore original methods my_predbat.get_arg = original_get_arg my_predbat.manual_times = original_manual_times diff --git a/apps/predbat/tests/test_rate_add_io_slots.py b/apps/predbat/tests/test_rate_add_io_slots.py index abac7bbf4..14614d5e4 100644 --- a/apps/predbat/tests/test_rate_add_io_slots.py +++ b/apps/predbat/tests/test_rate_add_io_slots.py @@ -330,6 +330,70 @@ def run_rate_add_io_slots_tests(my_predbat): expected_rates_17[minute] = 4.0 failed |= run_rate_add_io_slots_test("test17_dup_does_not_waste_cap", my_predbat, slots_17, True, 2, expected_rates_17) + # Test 18: a daytime slot that's still Octopus's provisional/revisable plan (provisional=True) + # must NOT get the low rate while octopus_intelligent_confirm_slots is on - Octopus can withdraw + # it before it's ever delivered. + print("\n**** Test 18: Daytime provisional slot is not treated as low rate ****") + slot_start_18 = midnight_utc + timedelta(hours=14, minutes=30) # 14:30 + slot_end_18 = slot_start_18 + timedelta(minutes=30) # 15:00 + slots_18 = [{"start": slot_start_18.strftime(TIME_FORMAT), "end": slot_end_18.strftime(TIME_FORMAT), "charge_in_kwh": 2.5, "source": "smart-charge", "location": "AT_HOME", "provisional": True}] + expected_rates_18 = {} + for minute in range(870, 900): + expected_rates_18[minute] = 10.0 # Left at the normal rate, not discounted + my_predbat.octopus_intelligent_confirm_slots = True + failed |= run_rate_add_io_slots_test("test18_daytime_provisional_not_low_rate", my_predbat, slots_18, True, 12, expected_rates_18) + + # Test 19: the same slot, but confirmed (provisional=False, e.g. Octopus now reports it as a + # completed/metered dispatch) - should get the low rate. Isolates provenance as the only + # variable versus Test 18. + print("\n**** Test 19: Daytime confirmed (completed) slot is treated as low rate ****") + slots_19 = [{"start": slot_start_18.strftime(TIME_FORMAT), "end": slot_end_18.strftime(TIME_FORMAT), "charge_in_kwh": 2.5, "source": "unknown", "location": "AT_HOME", "provisional": False}] + expected_rates_19 = {} + for minute in range(870, 900): + expected_rates_19[minute] = 4.0 + my_predbat.octopus_intelligent_confirm_slots = True + failed |= run_rate_add_io_slots_test("test19_daytime_completed_is_low_rate", my_predbat, slots_19, True, 12, expected_rates_19) + + # Test 20: the same provisional dispatch, plus a car_charging_now-style confirming slot for the + # same window appended AFTER it (matching production order - add_now_to_octopus_slot always + # appends after the real dispatch entries). Should get the low rate - this specifically exercises + # the sort-before-dedup fix in rate_add_io_slots: without sorting confirmed slots first, the + # provisional entry would claim these minutes via saved_slots before the confirming entry is ever + # processed, silently losing the confirmation. + print("\n**** Test 20: car_charging_now confirmation wins over an earlier provisional entry ****") + slots_20 = [ + {"start": slot_start_18.strftime(TIME_FORMAT), "end": slot_end_18.strftime(TIME_FORMAT), "charge_in_kwh": 2.5, "source": "smart-charge", "location": "AT_HOME", "provisional": True}, + {"start": slot_start_18.strftime(TIME_FORMAT), "end": slot_end_18.strftime(TIME_FORMAT), "charge_in_kwh": 2.5, "source": "car_charging_now", "provisional": False}, + ] + expected_rates_20 = {} + for minute in range(870, 900): + expected_rates_20[minute] = 4.0 + my_predbat.octopus_intelligent_confirm_slots = True + failed |= run_rate_add_io_slots_test("test20_car_charging_now_confirms_over_provisional", my_predbat, slots_20, True, 12, expected_rates_20) + + # Test 21: a provisional slot inside the fixed 23:30-05:30 off-peak window - should always get + # the low rate regardless of provenance, since that window is guaranteed cheap by the tariff + # itself, not by the dispatch mechanism. Proves the window carve-out. + print("\n**** Test 21: Provisional slot inside the fixed IOG window is still low rate ****") + slot_start_21 = midnight_utc + timedelta(hours=2) # 02:00 - well inside 23:30-05:30 + slot_end_21 = slot_start_21 + timedelta(minutes=30) + slots_21 = [{"start": slot_start_21.strftime(TIME_FORMAT), "end": slot_end_21.strftime(TIME_FORMAT), "charge_in_kwh": 2.5, "source": "smart-charge", "location": "AT_HOME", "provisional": True}] + expected_rates_21 = {} + for minute in range(120, 150): + expected_rates_21[minute] = 4.0 + my_predbat.octopus_intelligent_confirm_slots = True + failed |= run_rate_add_io_slots_test("test21_fixed_window_always_low_rate", my_predbat, slots_21, True, 12, expected_rates_21) + + # Test 22: Test 18's fixture again, but with octopus_intelligent_confirm_slots off - should get + # the low rate, confirming the escape hatch restores the previous (unconditional) behaviour. + print("\n**** Test 22: Switch off restores unconditional low rate ****") + expected_rates_22 = {} + for minute in range(870, 900): + expected_rates_22[minute] = 4.0 + my_predbat.octopus_intelligent_confirm_slots = False + failed |= run_rate_add_io_slots_test("test22_switch_off_restores_old_behaviour", my_predbat, slots_18, True, 12, expected_rates_22) + my_predbat.octopus_intelligent_confirm_slots = False # Restore default for any subsequent tests + # Restore original forecast_minutes my_predbat.forecast_minutes = original_forecast_minutes diff --git a/docs/car-charging.md b/docs/car-charging.md index 29c924142..dd962829c 100644 --- a/docs/car-charging.md +++ b/docs/car-charging.md @@ -183,6 +183,16 @@ It is therefore recommended that you do NOT set car_charging_now unless you have **CAUTION:** It is strongly recommended to not use car_charging_now with Predbat-led charging unless you can't make it work any other way as Predbat will assume all car charging is at a low rate. +**Note:** the advice above is about using car_charging_now as a substitute for working Octopus Intelligent slot detection. It has a second, separate job: when **switch.predbat_octopus_intelligent_confirm_slots** is enabled (see below), car_charging_now is also used to confirm that a still-provisional daytime Intelligent dispatch slot (e.g. under Octopus Intelligent Go's dynamic slot scheme) is genuinely being used, before the house battery commits to charging on it. Even if your Octopus Intelligent slot detection already works fine, it's still worth configuring car_charging_now for this reason - if you removed it based on the guidance above, you may be missing out on the most reliable confirmation signal for this newer protection. + +If car_charging_now is not set, there is currently **no other reliable way for Predbat to confirm a daytime dispatch slot in real time** - this is true regardless of your car_energy_reported_load setting. In practice, if you're on a tariff with this dynamic daytime-slot scheme and want the discharge-hold/rescission protection to actually do anything for daytime slots, configuring car_charging_now is effectively required, not optional. Predbat will log a warning at startup if switch.predbat_octopus_intelligent_confirm_slots is On, Octopus Intelligent Charging is enabled, and car_charging_now is not set. + +**Why not infer it from load instead?** Predbat's dynamic_load mechanism has a car-slot cancellation check that can look like a candidate for this, but it isn't one: `load_car_slot` isn't derived from load telemetry at all, it's just "are we currently inside a car_charging_slots window" - and car_charging_slots is itself built directly from the same provisional Octopus dispatch data being confirmed, so using it as confirmation would be circular. The only thing it actually does in response to observed load is one-directional and negative - it can cancel an assumed car slot if load stays surprisingly low, but there's no symmetric path that positively confirms one from a load spike. It's also gated behind metric_dynamic_load_adjust, which defaults Off. + +Even a purpose-built load-spike heuristic would be a poor substitute here. A single-phase EV charger draws a similar amount (and for a similar duration) to an oven, electric shower, immersion heater, or tumble dryer - disambiguating "the car is charging" from "someone's cooking" out of one aggregate whole-house load signal is a genuinely hard signal-disaggregation problem (it's what NILM research exists for), not something a threshold check can do reliably. And it's worst exactly when this fix matters most: daytime, when other household loads are most active. On top of that, real EV charging isn't a flat draw - cold-battery preconditioning, BMS current tapering near full SOC, and load-balancing EVSEs can all put genuine charging well under a fixed threshold, so no single cutoff avoids both false positives from other appliances and false negatives from a slow-charging car. + +This doesn't have to come from the car's own API - if your charger is controlled directly by Octopus rather than the car (e.g. a Hypervolt charger), a charger-level "actively drawing power" sensor is often a *more* reliable source for car_charging_now than a vehicle-API-based one. + - **car_charging_now_response** - Set to the range of positive responses for car_charging_now to indicate that the car is charging. Useful if you have a sensor for your car charger that isn't binary. To make Predbat-led car charging more accurate, additionally you can configure the following items in `apps.yaml`: @@ -348,6 +358,12 @@ Predbat will still assume all Octopus charging slots are low rates even if some - The switch **switch.predbat_octopus_intelligent_ignore_unplugged** (*expert mode*) (default value is Off) can be used to prevent Predbat from assuming the car will be charging or that future extra low-rate slots apply when the car is unplugged. This will only work correctly if **car_charging_planned** is set correctly in `apps.yaml` to detect your car being plugged in +- The switch **switch.predbat_octopus_intelligent_confirm_slots** (*expert mode*) (default value is Off) protects against a daytime Octopus Intelligent dispatch slot (outside the fixed 23:30-05:30 off-peak window) being withdrawn - Octopus's own daytime dispatch schedule is provisional and can be revised before it's delivered, and if your car doesn't end up drawing power during that slot the cheap rate for it can be rescinded. +While this switch is On, a daytime slot is only treated as low rate for the house battery once it's confirmed - either because Octopus reports it as a completed dispatch, or because **car_charging_now** confirms the car (or charger) is actually drawing power. +The fixed 23:30-05:30 window is never affected by this switch, since it's guaranteed cheap by the tariff itself. +**In practice, car_charging_now is the only confirmation signal fast enough to matter for a live daytime slot** - see the note under car_charging_now above. Turning this switch On without also setting car_charging_now means daytime slots will simply never be treated as low rate (Predbat logs a warning in this situation). +It defaults Off because it's a meaningful behaviour change that depends on that extra configuration to be useful - turning it On restores nothing by itself, it only removes risk once car_charging_now is also set. Leaving it Off keeps the previous behaviour of treating every dispatch slot as low rate immediately, which is more aggressive but carries the risk of the house battery charging at what turns out to be the full rate if the slot is later withdrawn. + - Let the Octopus app control when your car charges. ### Predbat-led charging diff --git a/docs/energy-rates.md b/docs/energy-rates.md index 8c35541b5..d968e1ff0 100644 --- a/docs/energy-rates.md +++ b/docs/energy-rates.md @@ -37,7 +37,7 @@ If your energy provider prices on the basis of smaller (or larger) intervals the If your electricity supplier is Octopus Energy then the simplest way to provide Predbat with your electricity pricing information is to connect Predbat directly to Octopus. - This method will not work correctly if you have multiple import or export meters. -- A single Octopus Intelligent GO car charger or car is supported and Predbat will plan your battery charging based on iGo sessions. +- A single Octopus Intelligent GO car charger or car is supported and Predbat will plan your battery charging based on iGo sessions. Note that a daytime dispatch slot's confirmation status now factors into when the battery commits to charging on it - see **switch.predbat_octopus_intelligent_confirm_slots** in [Car charging](car-charging.md). - Saving sessions are also supported, including Predbat auto-enrolling you to them (note you must be joined to both the Octopus Octopoints and then the Saving Sessions schemes beforehand). You should first log into your Octopus account and go to the [Accounts](https://octopus.energy/dashboard/new/accounts/) section and copy your Octopus account number e.g. `A-1234567`. diff --git a/templates/enphase_cloud.yaml b/templates/enphase_cloud.yaml index 6629c740b..4526ee699 100644 --- a/templates/enphase_cloud.yaml +++ b/templates/enphase_cloud.yaml @@ -134,9 +134,14 @@ pred_bat: - 'plugged in' - 'waiting' - # In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots) - # The car charging now can be set to a sensor to indicate the car is charging and to plan - # for it to charge during this 30 minute slot + # In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots). + # car_charging_now can be set to a sensor that indicates the car is charging, so Predbat plans + # for it to charge during this 30 minute slot. It's also the most reliable real-time confirmation + # that a daytime Octopus Intelligent Go dispatch slot is genuinely being used - see + # switch.predbat_octopus_intelligent_confirm_slots in docs/car-charging.md. If you're on that + # tariff's dynamic slot scheme, setting this is recommended even if slot detection already works. + # It doesn't have to be the car's own sensor - a charger-level "currently charging" sensor often + # works better, e.g. for a Hypervolt charger: car_charging_now: sensor.hypervolt_charging #car_charging_now: # - off diff --git a/templates/fox_cloud.yaml b/templates/fox_cloud.yaml index d7cc8625d..985b4189b 100644 --- a/templates/fox_cloud.yaml +++ b/templates/fox_cloud.yaml @@ -125,9 +125,14 @@ pred_bat: - 'plugged in' - 'waiting' - # In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots) - # The car charging now can be set to a sensor to indicate the car is charging and to plan - # for it to charge during this 30 minute slot + # In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots). + # car_charging_now can be set to a sensor that indicates the car is charging, so Predbat plans + # for it to charge during this 30 minute slot. It's also the most reliable real-time confirmation + # that a daytime Octopus Intelligent Go dispatch slot is genuinely being used - see + # switch.predbat_octopus_intelligent_confirm_slots in docs/car-charging.md. If you're on that + # tariff's dynamic slot scheme, setting this is recommended even if slot detection already works. + # It doesn't have to be the car's own sensor - a charger-level "currently charging" sensor often + # works better, e.g. for a Hypervolt charger: car_charging_now: sensor.hypervolt_charging #car_charging_now: # - off diff --git a/templates/ge_cloud_octopus_standalone.yaml b/templates/ge_cloud_octopus_standalone.yaml index 90477c958..e6070403a 100644 --- a/templates/ge_cloud_octopus_standalone.yaml +++ b/templates/ge_cloud_octopus_standalone.yaml @@ -146,9 +146,14 @@ pred_bat: - 'plugged in' - 'waiting' - # In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots) - # The car charging now can be set to a sensor to indicate the car is charging and to plan - # for it to charge during this 30 minute slot + # In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots). + # car_charging_now can be set to a sensor that indicates the car is charging, so Predbat plans + # for it to charge during this 30 minute slot. It's also the most reliable real-time confirmation + # that a daytime Octopus Intelligent Go dispatch slot is genuinely being used - see + # switch.predbat_octopus_intelligent_confirm_slots in docs/car-charging.md. If you're on that + # tariff's dynamic slot scheme, setting this is recommended even if slot detection already works. + # It doesn't have to be the car's own sensor - a charger-level "currently charging" sensor often + # works better, e.g. for a Hypervolt charger: car_charging_now: sensor.hypervolt_charging #car_charging_now: # - off diff --git a/templates/givenergy_cloud.yaml b/templates/givenergy_cloud.yaml index 2f738e34c..ef26fca2b 100644 --- a/templates/givenergy_cloud.yaml +++ b/templates/givenergy_cloud.yaml @@ -241,9 +241,14 @@ pred_bat: - 'locked' - 'plugged in' - # In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots) - # The car charging now can be set to a sensor to indicate the car is charging and to plan - # for it to charge during this 30 minute slot + # In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots). + # car_charging_now can be set to a sensor that indicates the car is charging, so Predbat plans + # for it to charge during this 30 minute slot. It's also the most reliable real-time confirmation + # that a daytime Octopus Intelligent Go dispatch slot is genuinely being used - see + # switch.predbat_octopus_intelligent_confirm_slots in docs/car-charging.md. If you're on that + # tariff's dynamic slot scheme, setting this is recommended even if slot detection already works. + # It doesn't have to be the car's own sensor - a charger-level "currently charging" sensor often + # works better, e.g. for a Hypervolt charger: car_charging_now: sensor.hypervolt_charging #car_charging_now: # - off diff --git a/templates/givenergy_ems.yaml b/templates/givenergy_ems.yaml index fe58636b3..22700a3ce 100644 --- a/templates/givenergy_ems.yaml +++ b/templates/givenergy_ems.yaml @@ -155,9 +155,14 @@ pred_bat: - 'locked' - 'plugged in' - # In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots) - # The car charging now can be set to a sensor to indicate the car is charging and to plan - # for it to charge during this 30 minute slot + # In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots). + # car_charging_now can be set to a sensor that indicates the car is charging, so Predbat plans + # for it to charge during this 30 minute slot. It's also the most reliable real-time confirmation + # that a daytime Octopus Intelligent Go dispatch slot is genuinely being used - see + # switch.predbat_octopus_intelligent_confirm_slots in docs/car-charging.md. If you're on that + # tariff's dynamic slot scheme, setting this is recommended even if slot detection already works. + # It doesn't have to be the car's own sensor - a charger-level "currently charging" sensor often + # works better, e.g. for a Hypervolt charger: car_charging_now: sensor.hypervolt_charging #car_charging_now: # - off diff --git a/templates/givenergy_givtcp.yaml b/templates/givenergy_givtcp.yaml index 54a1e6291..197beab08 100644 --- a/templates/givenergy_givtcp.yaml +++ b/templates/givenergy_givtcp.yaml @@ -284,9 +284,14 @@ pred_bat: - 'locked' - 'plugged in' - # In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots) - # The car charging now can be set to a sensor to indicate the car is charging and to plan - # for it to charge during this 30 minute slot + # In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots). + # car_charging_now can be set to a sensor that indicates the car is charging, so Predbat plans + # for it to charge during this 30 minute slot. It's also the most reliable real-time confirmation + # that a daytime Octopus Intelligent Go dispatch slot is genuinely being used - see + # switch.predbat_octopus_intelligent_confirm_slots in docs/car-charging.md. If you're on that + # tariff's dynamic slot scheme, setting this is recommended even if slot detection already works. + # It doesn't have to be the car's own sensor - a charger-level "currently charging" sensor often + # works better, e.g. for a Hypervolt charger: car_charging_now: sensor.hypervolt_charging #car_charging_now: # - off diff --git a/templates/luxpower.yaml b/templates/luxpower.yaml index 48dda4c95..0bbe49543 100644 --- a/templates/luxpower.yaml +++ b/templates/luxpower.yaml @@ -250,9 +250,14 @@ pred_bat: # - 'locked' # - 'plugged in' - # In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots) - # The car charging now can be set to a sensor to indicate the car is charging and to plan - # for it to charge during this 30 minute slot + # In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots). + # car_charging_now can be set to a sensor that indicates the car is charging, so Predbat plans + # for it to charge during this 30 minute slot. It's also the most reliable real-time confirmation + # that a daytime Octopus Intelligent Go dispatch slot is genuinely being used - see + # switch.predbat_octopus_intelligent_confirm_slots in docs/car-charging.md. If you're on that + # tariff's dynamic slot scheme, setting this is recommended even if slot detection already works. + # It doesn't have to be the car's own sensor - a charger-level "currently charging" sensor often + # works better, e.g. for a Hypervolt charger: car_charging_now: sensor.hypervolt_charging #car_charging_now: # - off diff --git a/templates/sigenergy_cloud.yaml b/templates/sigenergy_cloud.yaml index fc16b2ca1..9779af340 100644 --- a/templates/sigenergy_cloud.yaml +++ b/templates/sigenergy_cloud.yaml @@ -154,9 +154,14 @@ pred_bat: - 'plugged in' - 'waiting' - # In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots) - # The car charging now can be set to a sensor to indicate the car is charging and to plan - # for it to charge during this 30 minute slot + # In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots). + # car_charging_now can be set to a sensor that indicates the car is charging, so Predbat plans + # for it to charge during this 30 minute slot. It's also the most reliable real-time confirmation + # that a daytime Octopus Intelligent Go dispatch slot is genuinely being used - see + # switch.predbat_octopus_intelligent_confirm_slots in docs/car-charging.md. If you're on that + # tariff's dynamic slot scheme, setting this is recommended even if slot detection already works. + # It doesn't have to be the car's own sensor - a charger-level "currently charging" sensor often + # works better, e.g. for a Hypervolt charger: car_charging_now: sensor.hypervolt_charging #car_charging_now: # - off diff --git a/templates/sofar.yaml b/templates/sofar.yaml index eb8116073..496e6646c 100644 --- a/templates/sofar.yaml +++ b/templates/sofar.yaml @@ -1,345 +1,350 @@ -# ------------------------------------------------------------------ -# Sofar Config -# ------------------------------------------------------------------ ---- -pred_bat: - module: predbat - class: PredBat - - # Sets the prefix for all created entities in HA - only change if you want to run more than once instance - prefix: predbat - - # Timezone to work in - timezone: Europe/London - - # XXX: Template configuration, delete this line once you have set up for your system - template: True - - # If you are using Predbat outside of HA then set the HA URL and Key (long lived access token here) - #ha_url: 'http://homeassistant.local:8123' - #ha_key: 'xxx' - - # Currency, symbol for main currency second symbol for 1/100s e.g. $ c or £ p or e c - currency_symbols: - - '£' - - 'p' - - # Number of threads to use in plan calculation - # Can be auto for automatic, 0 for off or values 1-N for a fixed number - threads: auto - - # - # Sensors, more than one can be specified and they will be summed up automatically - # - # For two inverters the load today would normally be the master load sensor only (to cover the entire house) - # If you have three phase and one inverter per phase then you would need three load sensors - # - # For pv_today if you have multiple solar inverter inputs then you should include one entry for each inverter - # - load_today: - - sensor.sofar_energy_today_consumption - import_today: - - sensor.sofar_energy_today_purchase - export_today: - - sensor.sofar_energy_today_exported - pv_today: - - sensor.sofar_energy_today_generation - - # Load forecast can be used to add to the historical load data (heat-pump) - # To link to Predheat - # Data must be in the format of 'last_updated' timestamp and 'energy' for incrementing kWh - #load_forecast: - # - predheat.heat_energy$external - # - num_inverters: 1 - inverter_type: "SF" - # - # Run balance inverters every N seconds (0=disabled) - only for multi-inverter - balance_inverters_seconds: 60 - # - battery_power: - - sensor.sofar_inverter_battery_power - pv_power: - - sensor.sofar_solar_power - load_power: - - sensor.sofar_house_consumption_power - soc_kw: - - sensor.sofar_battery_energy - soc_max: - - input_number.sofar_battery_max_charge - charge_limit: - - 100 - reserve: - - 20 - scheduled_charge_enable: - - off - scheduled_discharge_enable: - - off - charge_start_time: - - "00:00:00" - charge_end_time: - - "00:01:00" - discharge_start_time: - - "00:00:00" - discharge_end_time: - - "00:01:00" - - # Inverter max AC limit (one per inverter). E.g for a 3.6kw inverter set to 3600 - # If you have a second inverter for PV only please add the two values together - inverter_limit: - - 5000 - - # Set the maximum charge/discharge rate of the battery - battery_rate_max: - - 3000 - - # Export limit is a software limit set on your inverter that prevents exporting above a given level - # When enabled Predbat will model this limit - #export_limit: - # - 3600 - - # Some inverters don't turn off when the rate is set to 0, still charge or discharge at around 200w - # The value can be set here in watts to model this (doesn't change operation) - #inverter_battery_rate_min: - # - 200 - - # Workaround to limit the maximum reserve setting, some inverters won't allow 100% to be set - # inverter_reserve_max : 99 - - # Some batteries tail off their charge rate at high soc% - # enter the charging curve here as a % of the max charge rate for each soc percentage. - # the default is 1.0 (full power) - # The example below is from GE 9.5kwh battery with latest firmware and gen1 inverter - #battery_charge_power_curve: - # 91 : 0.91 - # 92 : 0.81 - # 93 : 0.71 - # 94 : 0.62 - # 95 : 0.52 - # 96 : 0.43 - # 97 : 0.33 - # 98 : 0.24 - # 99 : 0.24 - # 100 : 0.24 - - # Inverter clock skew in minutes, e.g. 1 means it's 1 minute fast and -1 is 1 minute slow - # Separate start and end options are applied to the start and end time windows, mostly as you want to start late (not early) and finish early (not late) - # Separate discharge skew for discharge windows only - inverter_clock_skew_start: 0 - inverter_clock_skew_end: 0 - inverter_clock_skew_discharge_start: 0 - inverter_clock_skew_discharge_end: 0 - - # Clock skew adjusts the Appdaemon time - # This is the time that Predbat takes actions like starting discharge/charging - # Only use this for workarounds if your inverter time is correct but Predbat is somehow wrong (AppDaemon issue) - # 1 means add 1 minute to AppDaemon time, -1 takes it away - clock_skew: 0 - - # Solcast cloud interface, set this or the local interface below - #solcast_host: 'https://api.solcast.com.au/' - #solcast_api_key: 'xxxx' - #solcast_poll_hours: 8 - - # Set these to match solcast sensor names if not using the cloud interface - # The regular expression (re:) makes the solcast bit optional - # If these don't match find your own names in Home Assistant - pv_forecast_today: re:(sensor.(solcast_|)(pv_forecast_|)forecast_today) - pv_forecast_tomorrow: re:(sensor.(solcast_|)(pv_forecast_|)forecast_tomorrow) - pv_forecast_d3: re:(sensor.(solcast_|)(pv_forecast_|)forecast_(day_3|d3)) - pv_forecast_d4: re:(sensor.(solcast_|)(pv_forecast_|)forecast_(day_4|d4)) - - # car_charging_energy defines an incrementing sensor which measures the charge added to your car - # is used for car_charging_hold feature to filter out car charging from the previous load data - # Automatically set to detect Wallbox and Zappi, if it doesn't match manually enter your sensor name - # Also adjust car_charging_energy_scale if it's not in kwH to fix the units - car_charging_energy: 're:(sensor.myenergi_zappi_[0-9a-z]+_charge_added_session|sensor.wallbox_portal_added_energy)' - - # Defines the number of cars modelled by the system, set to 0 for no car - num_cars: 0 - - # car_charging_planned is set to a sensor which when positive indicates the car will charged in the upcoming low rate slots - # This should not be needed if you use Intelligent Octopus slots which will take priority if enabled - # The list of possible values is in car_charging_planned_response - # Auto matches Zappi and Wallbox, or change it for your own - # One entry per car - car_charging_planned: - - 're:(sensor.wallbox_portal_status_description|sensor.myenergi_zappi_[0-9a-z]+_plug_status)' - - car_charging_planned_response: - - 'yes' - - 'on' - - 'true' - - 'connected' - - 'ev connected' - - 'charging' - - 'paused' - - 'waiting for car demand' - - 'waiting for ev' - - 'scheduled' - - 'enabled' - - 'latched' - - 'locked' - - 'plugged in' - - # In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots) - # The car charging now can be set to a sensor to indicate the car is charging and to plan - # for it to charge during this 30 minute slot - #car_charging_now: - # - off - - # Positive responses for car_charging_now - car_charging_now_response: - - 'yes' - - 'on' - - 'true' - - # To make planned car charging more accurate, either using car_charging_planned or the Octopus Energy plugin, - # specify your battery size in kwh, charge limit % and current car battery soc % sensors/values. - # If you have Intelligent Octopus the battery size and limit will be extracted from the Octopus Energy plugin directly. - # Set the car SoC% if you have it to give an accurate forecast of the cars battery levels. - # One entry per car if you have multiple cars. - #car_charging_battery_size: - # - 75 - #car_charging_limit: - # - 're:number.tsunami_charge_limit' - #car_charging_soc: - # - 're:sensor.tsunami_battery' - - # If you have Octopus Intelligent Go and are not using the Octopus Direct connection method, enable the intelligent slot information to add to pricing - # Will automatically disable if not found, or comment out to disable fully - # When enabled it overrides the 'car_charging_planned' feature and predict the car charging based on the intelligent plan (unless Octopus intelligent charging is False) - # This matches the intelligent slot from the Octopus Energy integration - octopus_intelligent_slot: 're:(binary_sensor.octopus_energy([0-9a-z_]+|)_intelligent_dispatching)' - octopus_ready_time: 're:((select|time).octopus_energy_([0-9a-z_]+|)_intelligent_target_time)' - octopus_charge_limit: 're:(number.octopus_energy([0-9a-z_]+|)_intelligent_charge_target)' - - # Example alternative configuration for Ohme integration release >=v0.6.1 - #octopus_intelligent_slot: 'binary_sensor.ohme_slot_active' - #octopus_ready_time: 'time.ohme_target_time' - #octopus_charge_limit: 'number.ohme_target_percent' - - # Set this to False if you use Octopus Intelligent slot for car planning but when on another tariff e.g. Agile - #octopus_slot_low_rate: False - - # Carbon Intensity data from National grid - # carbon_postcode: 'SW1 5NA' - # carbon_automatic: True - - # Octopus saving session points to the saving session Sensor in the Octopus plugin, when enabled saving sessions will be at the assumed - # rate configured with input_number.predbat_metric_octopus_saving_rate in-side HA - octopus_saving_session: 're:(event.octopus_energy([0-9a-z_]+|)_saving_session_event(s|))' - octopus_saving_session_octopoints_per_penny: 8 - - # Octopus free session points to the free session Sensor in the Octopus plugin - # Note: You must enable this event sensor in the Octopus Integration in Home Assistant for it to work - octopus_free_session: 're:(event.octopus_energy_([0-9a-z_]+|)_octoplus_free_electricity_session_events)' - - # Energy rates - # Please set one of these three, if multiple are set then Octopus is used first, second rates_import/rates_export and latest basic metric - - # Set import and export entity to point to the Octopus Energy plugin import and export sensors - # automatically matches your meter number assuming you have only one (no need to edit the below) - # Will be ignored if you don't have the sensor but will error if you do have one and it's incorrect - # Note: To get detailed energy rates you need to go in and manually enable the following events in HA - # event.octopus_energy_electricity_xxxxxxxx_previous_day_rates - # event.octopus_energy_electricity_xxxxxxxx_current_day_rates - # event.octopus_energy_electricity_xxxxxxxx_next_day_rates - # and if you have export enable: - # event.octopus_energy_electricity_xxxxxxxx_export_previous_day_rates - # event.octopus_energy_electricity_xxxxxxxx_export_current_day_rates - # event.octopus_energy_electricity_xxxxxxxx_export_next_day_rates - # Predbat will automatically find the event. entities from the link below to the sensors - metric_octopus_import: 're:(sensor.(octopus_energy_|)electricity_[0-9a-z]+_[0-9a-z]+_current_rate)' - # metric_octopus_export: 're:(sensor.(octopus_energy_|)electricity_[0-9a-z]+_[0-9a-z]+_export_current_rate)' - - # Standing charge in pounds, can be set to a sensor or manually entered (e.g. 0.50 is 50p) - # The default below will pick up the standing charge from the Octopus Plugin - # The standing charge only impacts the cost graphs and doesn't change the way Predbat plans - # If you don't want to show the standing charge then just delete this line or set to zero - metric_standing_charge: 're:(sensor.(octopus_energy_|)electricity_[0-9a-z]+_[0-9a-z]+_current_standing_charge)' - - # Or set your actual rates across time for import and export - # If start/end is missing it's assumed to be a fixed rate - # Gaps are filled with zero rate - #rates_import: - # - start: "00:30:00" - # end: "04:30:00" - # rate: 7.5 - # - start: "04:30:00" - # end: "00:30:00" - # rate: 40.0 - # - rates_export: - - rate: 0 - - # Can be used instead of the plugin to get import rates directly online - # Overrides metric_octopus_import and rates_import - # See the 'energy rates' part of the documentation for instructions on how to find the correct URL for your tariff and DNO region - # - # rates_import_octopus_url : "https://api.octopus.energy/v1/products/FLUX-IMPORT-23-02-14/electricity-tariffs/E-1R-FLUX-IMPORT-23-02-14-A/standard-unit-rates" - # rates_import_octopus_url : "https://api.octopus.energy/v1/products/AGILE-24-10-01/electricity-tariffs/E-1R-AGILE-24-10-01-A/standard-unit-rates" - - # Overrides metric_octopus_export and rates_export - # rates_export_octopus_url: "https://api.octopus.energy/v1/products/FLUX-EXPORT-23-02-14/electricity-tariffs/E-1R-FLUX-EXPORT-23-02-14-A/standard-unit-rates" - # rates_export_octopus_url: "https://api.octopus.energy/v1/products/AGILE-OUTGOING-19-05-13/electricity-tariffs/E-1R-AGILE-OUTGOING-19-05-13-A/standard-unit-rates/" - - # Import rates can be overridden with rate_import_override - # Export rates can be overridden with rate_export_override - # Use the same format as above, but a date can be included if it just applies for a set day (e.g. Octopus power ups) - # This will override even the Octopus plugin rates if enabled - # - #rates_import_override: - # - date: '2023-09-10' - # start: '14:00:00' - # end: '14:30:00' - # rate: 5 - - # Days previous sets how many days of load history Predbat searches when forecasting your future house load. - # By default this uses a weighted-bucket forecast that automatically accounts for day-of-week, holiday mode - # and recency (days_previous_auto) - see the documentation for other options, such as picking specific - # days/weights instead, or using Load ML. - days_previous: - - 7 - - # Number of hours forward to forecast, best left as-is unless you have specific reason - forecast_hours: 48 - - # Specify the devices that notifies are sent to, the default is 'notify' which goes to all - #notify_devices: - # - mobile_app_treforsiphone12_2 - - # Battery scaling makes the battery smaller (e.g. 0.9) or bigger than its reported - # If you have an 80% DoD battery that falsely reports it's kwh then set it to 0.8 to report the real figures - battery_scaling: 1.0 - - # Can be used to scale import and export data, used for workarounds - import_export_scaling: 1.0 - - # Export triggers: - # For each trigger give a name, the minutes of export needed and the energy required in that time - # Multiple triggers can be set at once so in total you could use too much energy if all run - # Creates an entity called 'binary_sensor.predbat_export_trigger_' which will be turned On when the condition is valid - # connect this to your automation to start whatever you want to trigger - export_triggers: - - name: 'large' - minutes: 60 - energy: 1.0 - - name: 'small' - minutes: 15 - energy: 0.25 - - # If you have a sensor that gives the energy consumed by your solar diverter then add it here - # this will make the predictions more accurate. It should be an incrementing sensor, it can reset at midnight or not - # It's assumed to be in Kwh but scaling can be applied if need be - #iboost_energy_today: 'sensor.tasmota_energy_today' - #iboost_energy_scaling: 1.0 - - # Nordpool market energy rates - #futurerate_url: 'https://dataportal-api.nordpoolgroup.com/api/DayAheadPrices?date=DATE&market=N2EX_DayAhead&deliveryArea=UK¤cy=GBP' - #futurerate_adjust_import: False - #futurerate_adjust_export: True - #futurerate_peak_start: "16:00:00" - #futurerate_peak_end: "19:00:00" - #futurerate_peak_premium_import: 14 - #futurerate_peak_premium_export: 6.5 +# ------------------------------------------------------------------ +# Sofar Config +# ------------------------------------------------------------------ +--- +pred_bat: + module: predbat + class: PredBat + + # Sets the prefix for all created entities in HA - only change if you want to run more than once instance + prefix: predbat + + # Timezone to work in + timezone: Europe/London + + # XXX: Template configuration, delete this line once you have set up for your system + template: True + + # If you are using Predbat outside of HA then set the HA URL and Key (long lived access token here) + #ha_url: 'http://homeassistant.local:8123' + #ha_key: 'xxx' + + # Currency, symbol for main currency second symbol for 1/100s e.g. $ c or £ p or e c + currency_symbols: + - '£' + - 'p' + + # Number of threads to use in plan calculation + # Can be auto for automatic, 0 for off or values 1-N for a fixed number + threads: auto + + # + # Sensors, more than one can be specified and they will be summed up automatically + # + # For two inverters the load today would normally be the master load sensor only (to cover the entire house) + # If you have three phase and one inverter per phase then you would need three load sensors + # + # For pv_today if you have multiple solar inverter inputs then you should include one entry for each inverter + # + load_today: + - sensor.sofar_energy_today_consumption + import_today: + - sensor.sofar_energy_today_purchase + export_today: + - sensor.sofar_energy_today_exported + pv_today: + - sensor.sofar_energy_today_generation + + # Load forecast can be used to add to the historical load data (heat-pump) + # To link to Predheat + # Data must be in the format of 'last_updated' timestamp and 'energy' for incrementing kWh + #load_forecast: + # - predheat.heat_energy$external + # + num_inverters: 1 + inverter_type: "SF" + # + # Run balance inverters every N seconds (0=disabled) - only for multi-inverter + balance_inverters_seconds: 60 + # + battery_power: + - sensor.sofar_inverter_battery_power + pv_power: + - sensor.sofar_solar_power + load_power: + - sensor.sofar_house_consumption_power + soc_kw: + - sensor.sofar_battery_energy + soc_max: + - input_number.sofar_battery_max_charge + charge_limit: + - 100 + reserve: + - 20 + scheduled_charge_enable: + - off + scheduled_discharge_enable: + - off + charge_start_time: + - "00:00:00" + charge_end_time: + - "00:01:00" + discharge_start_time: + - "00:00:00" + discharge_end_time: + - "00:01:00" + + # Inverter max AC limit (one per inverter). E.g for a 3.6kw inverter set to 3600 + # If you have a second inverter for PV only please add the two values together + inverter_limit: + - 5000 + + # Set the maximum charge/discharge rate of the battery + battery_rate_max: + - 3000 + + # Export limit is a software limit set on your inverter that prevents exporting above a given level + # When enabled Predbat will model this limit + #export_limit: + # - 3600 + + # Some inverters don't turn off when the rate is set to 0, still charge or discharge at around 200w + # The value can be set here in watts to model this (doesn't change operation) + #inverter_battery_rate_min: + # - 200 + + # Workaround to limit the maximum reserve setting, some inverters won't allow 100% to be set + # inverter_reserve_max : 99 + + # Some batteries tail off their charge rate at high soc% + # enter the charging curve here as a % of the max charge rate for each soc percentage. + # the default is 1.0 (full power) + # The example below is from GE 9.5kwh battery with latest firmware and gen1 inverter + #battery_charge_power_curve: + # 91 : 0.91 + # 92 : 0.81 + # 93 : 0.71 + # 94 : 0.62 + # 95 : 0.52 + # 96 : 0.43 + # 97 : 0.33 + # 98 : 0.24 + # 99 : 0.24 + # 100 : 0.24 + + # Inverter clock skew in minutes, e.g. 1 means it's 1 minute fast and -1 is 1 minute slow + # Separate start and end options are applied to the start and end time windows, mostly as you want to start late (not early) and finish early (not late) + # Separate discharge skew for discharge windows only + inverter_clock_skew_start: 0 + inverter_clock_skew_end: 0 + inverter_clock_skew_discharge_start: 0 + inverter_clock_skew_discharge_end: 0 + + # Clock skew adjusts the Appdaemon time + # This is the time that Predbat takes actions like starting discharge/charging + # Only use this for workarounds if your inverter time is correct but Predbat is somehow wrong (AppDaemon issue) + # 1 means add 1 minute to AppDaemon time, -1 takes it away + clock_skew: 0 + + # Solcast cloud interface, set this or the local interface below + #solcast_host: 'https://api.solcast.com.au/' + #solcast_api_key: 'xxxx' + #solcast_poll_hours: 8 + + # Set these to match solcast sensor names if not using the cloud interface + # The regular expression (re:) makes the solcast bit optional + # If these don't match find your own names in Home Assistant + pv_forecast_today: re:(sensor.(solcast_|)(pv_forecast_|)forecast_today) + pv_forecast_tomorrow: re:(sensor.(solcast_|)(pv_forecast_|)forecast_tomorrow) + pv_forecast_d3: re:(sensor.(solcast_|)(pv_forecast_|)forecast_(day_3|d3)) + pv_forecast_d4: re:(sensor.(solcast_|)(pv_forecast_|)forecast_(day_4|d4)) + + # car_charging_energy defines an incrementing sensor which measures the charge added to your car + # is used for car_charging_hold feature to filter out car charging from the previous load data + # Automatically set to detect Wallbox and Zappi, if it doesn't match manually enter your sensor name + # Also adjust car_charging_energy_scale if it's not in kwH to fix the units + car_charging_energy: 're:(sensor.myenergi_zappi_[0-9a-z]+_charge_added_session|sensor.wallbox_portal_added_energy)' + + # Defines the number of cars modelled by the system, set to 0 for no car + num_cars: 0 + + # car_charging_planned is set to a sensor which when positive indicates the car will charged in the upcoming low rate slots + # This should not be needed if you use Intelligent Octopus slots which will take priority if enabled + # The list of possible values is in car_charging_planned_response + # Auto matches Zappi and Wallbox, or change it for your own + # One entry per car + car_charging_planned: + - 're:(sensor.wallbox_portal_status_description|sensor.myenergi_zappi_[0-9a-z]+_plug_status)' + + car_charging_planned_response: + - 'yes' + - 'on' + - 'true' + - 'connected' + - 'ev connected' + - 'charging' + - 'paused' + - 'waiting for car demand' + - 'waiting for ev' + - 'scheduled' + - 'enabled' + - 'latched' + - 'locked' + - 'plugged in' + + # In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots). + # car_charging_now can be set to a sensor that indicates the car is charging, so Predbat plans + # for it to charge during this 30 minute slot. It's also the most reliable real-time confirmation + # that a daytime Octopus Intelligent Go dispatch slot is genuinely being used - see + # switch.predbat_octopus_intelligent_confirm_slots in docs/car-charging.md. If you're on that + # tariff's dynamic slot scheme, setting this is recommended even if slot detection already works. + # It doesn't have to be the car's own sensor - a charger-level "currently charging" sensor often + # works better, e.g. for a Hypervolt charger: car_charging_now: sensor.hypervolt_charging + #car_charging_now: + # - off + + # Positive responses for car_charging_now + car_charging_now_response: + - 'yes' + - 'on' + - 'true' + + # To make planned car charging more accurate, either using car_charging_planned or the Octopus Energy plugin, + # specify your battery size in kwh, charge limit % and current car battery soc % sensors/values. + # If you have Intelligent Octopus the battery size and limit will be extracted from the Octopus Energy plugin directly. + # Set the car SoC% if you have it to give an accurate forecast of the cars battery levels. + # One entry per car if you have multiple cars. + #car_charging_battery_size: + # - 75 + #car_charging_limit: + # - 're:number.tsunami_charge_limit' + #car_charging_soc: + # - 're:sensor.tsunami_battery' + + # If you have Octopus Intelligent Go and are not using the Octopus Direct connection method, enable the intelligent slot information to add to pricing + # Will automatically disable if not found, or comment out to disable fully + # When enabled it overrides the 'car_charging_planned' feature and predict the car charging based on the intelligent plan (unless Octopus intelligent charging is False) + # This matches the intelligent slot from the Octopus Energy integration + octopus_intelligent_slot: 're:(binary_sensor.octopus_energy([0-9a-z_]+|)_intelligent_dispatching)' + octopus_ready_time: 're:((select|time).octopus_energy_([0-9a-z_]+|)_intelligent_target_time)' + octopus_charge_limit: 're:(number.octopus_energy([0-9a-z_]+|)_intelligent_charge_target)' + + # Example alternative configuration for Ohme integration release >=v0.6.1 + #octopus_intelligent_slot: 'binary_sensor.ohme_slot_active' + #octopus_ready_time: 'time.ohme_target_time' + #octopus_charge_limit: 'number.ohme_target_percent' + + # Set this to False if you use Octopus Intelligent slot for car planning but when on another tariff e.g. Agile + #octopus_slot_low_rate: False + + # Carbon Intensity data from National grid + # carbon_postcode: 'SW1 5NA' + # carbon_automatic: True + + # Octopus saving session points to the saving session Sensor in the Octopus plugin, when enabled saving sessions will be at the assumed + # rate configured with input_number.predbat_metric_octopus_saving_rate in-side HA + octopus_saving_session: 're:(event.octopus_energy([0-9a-z_]+|)_saving_session_event(s|))' + octopus_saving_session_octopoints_per_penny: 8 + + # Octopus free session points to the free session Sensor in the Octopus plugin + # Note: You must enable this event sensor in the Octopus Integration in Home Assistant for it to work + octopus_free_session: 're:(event.octopus_energy_([0-9a-z_]+|)_octoplus_free_electricity_session_events)' + + # Energy rates + # Please set one of these three, if multiple are set then Octopus is used first, second rates_import/rates_export and latest basic metric + + # Set import and export entity to point to the Octopus Energy plugin import and export sensors + # automatically matches your meter number assuming you have only one (no need to edit the below) + # Will be ignored if you don't have the sensor but will error if you do have one and it's incorrect + # Note: To get detailed energy rates you need to go in and manually enable the following events in HA + # event.octopus_energy_electricity_xxxxxxxx_previous_day_rates + # event.octopus_energy_electricity_xxxxxxxx_current_day_rates + # event.octopus_energy_electricity_xxxxxxxx_next_day_rates + # and if you have export enable: + # event.octopus_energy_electricity_xxxxxxxx_export_previous_day_rates + # event.octopus_energy_electricity_xxxxxxxx_export_current_day_rates + # event.octopus_energy_electricity_xxxxxxxx_export_next_day_rates + # Predbat will automatically find the event. entities from the link below to the sensors + metric_octopus_import: 're:(sensor.(octopus_energy_|)electricity_[0-9a-z]+_[0-9a-z]+_current_rate)' + # metric_octopus_export: 're:(sensor.(octopus_energy_|)electricity_[0-9a-z]+_[0-9a-z]+_export_current_rate)' + + # Standing charge in pounds, can be set to a sensor or manually entered (e.g. 0.50 is 50p) + # The default below will pick up the standing charge from the Octopus Plugin + # The standing charge only impacts the cost graphs and doesn't change the way Predbat plans + # If you don't want to show the standing charge then just delete this line or set to zero + metric_standing_charge: 're:(sensor.(octopus_energy_|)electricity_[0-9a-z]+_[0-9a-z]+_current_standing_charge)' + + # Or set your actual rates across time for import and export + # If start/end is missing it's assumed to be a fixed rate + # Gaps are filled with zero rate + #rates_import: + # - start: "00:30:00" + # end: "04:30:00" + # rate: 7.5 + # - start: "04:30:00" + # end: "00:30:00" + # rate: 40.0 + # + rates_export: + - rate: 0 + + # Can be used instead of the plugin to get import rates directly online + # Overrides metric_octopus_import and rates_import + # See the 'energy rates' part of the documentation for instructions on how to find the correct URL for your tariff and DNO region + # + # rates_import_octopus_url : "https://api.octopus.energy/v1/products/FLUX-IMPORT-23-02-14/electricity-tariffs/E-1R-FLUX-IMPORT-23-02-14-A/standard-unit-rates" + # rates_import_octopus_url : "https://api.octopus.energy/v1/products/AGILE-24-10-01/electricity-tariffs/E-1R-AGILE-24-10-01-A/standard-unit-rates" + + # Overrides metric_octopus_export and rates_export + # rates_export_octopus_url: "https://api.octopus.energy/v1/products/FLUX-EXPORT-23-02-14/electricity-tariffs/E-1R-FLUX-EXPORT-23-02-14-A/standard-unit-rates" + # rates_export_octopus_url: "https://api.octopus.energy/v1/products/AGILE-OUTGOING-19-05-13/electricity-tariffs/E-1R-AGILE-OUTGOING-19-05-13-A/standard-unit-rates/" + + # Import rates can be overridden with rate_import_override + # Export rates can be overridden with rate_export_override + # Use the same format as above, but a date can be included if it just applies for a set day (e.g. Octopus power ups) + # This will override even the Octopus plugin rates if enabled + # + #rates_import_override: + # - date: '2023-09-10' + # start: '14:00:00' + # end: '14:30:00' + # rate: 5 + + # Days previous sets how many days of load history Predbat searches when forecasting your future house load. + # By default this uses a weighted-bucket forecast that automatically accounts for day-of-week, holiday mode + # and recency (days_previous_auto) - see the documentation for other options, such as picking specific + # days/weights instead, or using Load ML. + days_previous: + - 7 + + # Number of hours forward to forecast, best left as-is unless you have specific reason + forecast_hours: 48 + + # Specify the devices that notifies are sent to, the default is 'notify' which goes to all + #notify_devices: + # - mobile_app_treforsiphone12_2 + + # Battery scaling makes the battery smaller (e.g. 0.9) or bigger than its reported + # If you have an 80% DoD battery that falsely reports it's kwh then set it to 0.8 to report the real figures + battery_scaling: 1.0 + + # Can be used to scale import and export data, used for workarounds + import_export_scaling: 1.0 + + # Export triggers: + # For each trigger give a name, the minutes of export needed and the energy required in that time + # Multiple triggers can be set at once so in total you could use too much energy if all run + # Creates an entity called 'binary_sensor.predbat_export_trigger_' which will be turned On when the condition is valid + # connect this to your automation to start whatever you want to trigger + export_triggers: + - name: 'large' + minutes: 60 + energy: 1.0 + - name: 'small' + minutes: 15 + energy: 0.25 + + # If you have a sensor that gives the energy consumed by your solar diverter then add it here + # this will make the predictions more accurate. It should be an incrementing sensor, it can reset at midnight or not + # It's assumed to be in Kwh but scaling can be applied if need be + #iboost_energy_today: 'sensor.tasmota_energy_today' + #iboost_energy_scaling: 1.0 + + # Nordpool market energy rates + #futurerate_url: 'https://dataportal-api.nordpoolgroup.com/api/DayAheadPrices?date=DATE&market=N2EX_DayAhead&deliveryArea=UK¤cy=GBP' + #futurerate_adjust_import: False + #futurerate_adjust_export: True + #futurerate_peak_start: "16:00:00" + #futurerate_peak_end: "19:00:00" + #futurerate_peak_premium_import: 14 + #futurerate_peak_premium_export: 6.5 diff --git a/templates/sofar_modbus.yaml b/templates/sofar_modbus.yaml index 1039881d5..cd811035b 100644 --- a/templates/sofar_modbus.yaml +++ b/templates/sofar_modbus.yaml @@ -199,9 +199,14 @@ pred_bat: - 'locked' - 'plugged in' - # In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots) - # The car charging now can be set to a sensor to indicate the car is charging and to plan - # for it to charge during this 30 minute slot + # In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots). + # car_charging_now can be set to a sensor that indicates the car is charging, so Predbat plans + # for it to charge during this 30 minute slot. It's also the most reliable real-time confirmation + # that a daytime Octopus Intelligent Go dispatch slot is genuinely being used - see + # switch.predbat_octopus_intelligent_confirm_slots in docs/car-charging.md. If you're on that + # tariff's dynamic slot scheme, setting this is recommended even if slot detection already works. + # It doesn't have to be the car's own sensor - a charger-level "currently charging" sensor often + # works better, e.g. for a Hypervolt charger: car_charging_now: sensor.hypervolt_charging #car_charging_now: # - off diff --git a/templates/solax_cloud.yaml b/templates/solax_cloud.yaml index 8284a2754..7fea3b2e5 100644 --- a/templates/solax_cloud.yaml +++ b/templates/solax_cloud.yaml @@ -128,9 +128,14 @@ pred_bat: - 'plugged in' - 'waiting' - # In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots) - # The car charging now can be set to a sensor to indicate the car is charging and to plan - # for it to charge during this 30 minute slot + # In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots). + # car_charging_now can be set to a sensor that indicates the car is charging, so Predbat plans + # for it to charge during this 30 minute slot. It's also the most reliable real-time confirmation + # that a daytime Octopus Intelligent Go dispatch slot is genuinely being used - see + # switch.predbat_octopus_intelligent_confirm_slots in docs/car-charging.md. If you're on that + # tariff's dynamic slot scheme, setting this is recommended even if slot detection already works. + # It doesn't have to be the car's own sensor - a charger-level "currently charging" sensor often + # works better, e.g. for a Hypervolt charger: car_charging_now: sensor.hypervolt_charging #car_charging_now: # - off diff --git a/templates/solis_cloud.yaml b/templates/solis_cloud.yaml index dbe742d84..7f9799592 100644 --- a/templates/solis_cloud.yaml +++ b/templates/solis_cloud.yaml @@ -146,9 +146,14 @@ pred_bat: - 'plugged in' - 'waiting' - # In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots) - # The car charging now can be set to a sensor to indicate the car is charging and to plan - # for it to charge during this 30 minute slot + # In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots). + # car_charging_now can be set to a sensor that indicates the car is charging, so Predbat plans + # for it to charge during this 30 minute slot. It's also the most reliable real-time confirmation + # that a daytime Octopus Intelligent Go dispatch slot is genuinely being used - see + # switch.predbat_octopus_intelligent_confirm_slots in docs/car-charging.md. If you're on that + # tariff's dynamic slot scheme, setting this is recommended even if slot detection already works. + # It doesn't have to be the car's own sensor - a charger-level "currently charging" sensor often + # works better, e.g. for a Hypervolt charger: car_charging_now: sensor.hypervolt_charging #car_charging_now: # - off diff --git a/templates/sunsynk.yaml b/templates/sunsynk.yaml index dd2da3d0e..1a9e0ad76 100644 --- a/templates/sunsynk.yaml +++ b/templates/sunsynk.yaml @@ -291,9 +291,14 @@ pred_bat: # - 'locked' # - 'plugged in' - # In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots) - # The car charging now can be set to a sensor to indicate the car is charging and to plan - # for it to charge during this 30 minute slot + # In some cases car planning is difficult (e.g. Ohme with Intelligent doesn't report slots). + # car_charging_now can be set to a sensor that indicates the car is charging, so Predbat plans + # for it to charge during this 30 minute slot. It's also the most reliable real-time confirmation + # that a daytime Octopus Intelligent Go dispatch slot is genuinely being used - see + # switch.predbat_octopus_intelligent_confirm_slots in docs/car-charging.md. If you're on that + # tariff's dynamic slot scheme, setting this is recommended even if slot detection already works. + # It doesn't have to be the car's own sensor - a charger-level "currently charging" sensor often + # works better, e.g. for a Hypervolt charger: car_charging_now: sensor.hypervolt_charging #car_charging_now: # - off