From 86f61ac7dd1e69f8475c15cdc612652b919f8332 Mon Sep 17 00:00:00 2001 From: Trefor Southwell <48591903+springfall2008@users.noreply.github.com> Date: Sun, 4 Feb 2024 13:51:34 +0000 Subject: [PATCH 1/5] Add symbols in the plan for different rate sources --- apps/predbat/predbat.py | 76 +++++++++++++++++++++++++++++++---------- 1 file changed, 58 insertions(+), 18 deletions(-) diff --git a/apps/predbat/predbat.py b/apps/predbat/predbat.py index 3aceb34ca..d6a572e59 100644 --- a/apps/predbat/predbat.py +++ b/apps/predbat/predbat.py @@ -4370,7 +4370,7 @@ def find_charge_window_optimised(self, charge_windows): for window_n in range(len(charge_windows)): for minute in range(charge_windows[window_n]["start"], charge_windows[window_n]["end"], PREDICT_STEP): charge_window_optimised[minute] = window_n - return charge_window_optimised + return charge_window_optimised def run_prediction(self, charge_limit, charge_window, discharge_window, discharge_limits, load_minutes_step, pv_forecast_minute_step, end_record, save=None, step=PREDICT_STEP): """ @@ -4471,6 +4471,7 @@ def run_prediction(self, charge_limit, charge_window, discharge_window, discharg if self.set_reserve_enable: reserve_expected = max(charge_limit_n, self.reserve) + # Add in standing charge, only for the final plan when we save the results if (minute_absolute % (24 * 60)) < step and (save in ["best", "base", "base10", "best10"]): metric += self.metric_standing_charge @@ -5441,6 +5442,7 @@ def rate_replicate(self, rates, rate_io={}, is_import=True, is_gas=False): # Add 48 extra hours to make sure the whole cycle repeats another day while minute < (self.forecast_minutes + 48 * 60): if minute not in rates: + adjust_type = "copy" # Take 24-hours previous if missing rate if (minute >= 24 * 60) and ((minute - 24 * 60) in rates): minute_mod = minute - 24 * 60 @@ -5466,6 +5468,7 @@ def rate_replicate(self, rates, rate_io={}, is_import=True, is_gas=False): ): prev_rate = rate_offset rate_offset = rate_offset - self.future_energy_rates_import[minute_mod] + self.future_energy_rates_import[minute] + adjust_type = "future" elif ( (not is_import) and (not is_gas) @@ -5474,18 +5477,22 @@ def rate_replicate(self, rates, rate_io={}, is_import=True, is_gas=False): and (minute_mod in self.future_energy_rates_export) ): rate_offset = max(rate_offset - self.future_energy_rates_export[minute_mod] + self.future_energy_rates_export[minute], 0) + adjust_type = "future" elif is_import: rate_offset = rate_offset + self.metric_future_rate_offset_import + if self.metric_future_rate_offset_import: + adjust_type = "offset" elif (not is_import) and (not is_gas): rate_offset = max(rate_offset + self.metric_future_rate_offset_export, 0) + if self.metric_future_rate_offset_export: + adjust_type = "offset" adjusted_rates[minute] = True rates[minute] = rate_offset - replicated_rates[minute] = True + replicated_rates[minute] = adjust_type else: rate_last = rates[minute] - replicated_rates[minute] = False minute += 1 return rates, replicated_rates @@ -5553,7 +5560,7 @@ def find_charge_window(self, rates, minute, threshold_rate, find_high): rate_low_average = self.dp2(rate_low_average / rate_low_count) return rate_low_start, rate_low_end, rate_low_average - def basic_rates(self, info, rtype, prev=None): + def basic_rates(self, info, rtype, prev=None, rate_replicate={}): """ Work out the energy rates based on user supplied time periods works on a 24-hour period only and then gets replicated later for future days @@ -5655,8 +5662,10 @@ def basic_rates(self, info, rtype, prev=None): while minute_index < max_minute: if rate_increment: rates[minute_index] = rates.get(minute_index, 0.0) + rate + rate_replicate[minute_index] = "increment" else: rates[minute_index] = rate + rate_replicate[minute_index] = "user" if load_scaling is not None: self.load_scaling_dynamic[minute_index] = load_scaling if date or not prev: @@ -5782,7 +5791,7 @@ def add_now_to_octopus_slot(self, octopus_slots, now_utc): self.log("Car is charging now - added new IO slot {}".format(slot)) return octopus_slots - def load_saving_slot(self, octopus_saving_slots, export=False): + def load_saving_slot(self, octopus_saving_slots, export=False, rate_replicate={}): """ Load octopus saving session slot """ @@ -5819,6 +5828,7 @@ def load_saving_slot(self, octopus_saving_slots, export=False): else: self.rate_import[minute] += rate self.load_scaling_dynamic[minute] = self.load_scaling_saving + rate_replicate[minute] = "saving" def load_octopus_slots(self, octopus_slots): """ @@ -6438,6 +6448,32 @@ def publish_rates_import(self): attributes={"friendly_name": "Next+1 low rate cost", "state_class": "measurement", "unit_of_measurement": "p", "icon": "mdi:currency-usd"}, ) + def adjust_symbol(self, adjust_type): + """ + Returns an HTML symbol based on the adjust rate type. + + Parameters: + - adjust_type (str): The type of adjustment. + + Returns: + - symbol (str): The symbol corresponding to the adjust_type. + """ + symbol = "" + if adjust_type: + if adjust_type == "offset": + symbol = "? ⅆ" + elif adjust_type == "future": + symbol = "? ⚖" + elif adjust_type == "user": + symbol = "=" + elif adjust_type == "increment": + symbol = "±" + elif adjust_type == "saving": + symbol = "$" + else: + symbol = "?" + return symbol + def publish_html_plan(self, pv_forecast_minute_step, load_minutes_step, end_record): """ Publish the current plan in HTML format @@ -6630,10 +6666,12 @@ def publish_html_plan(self, pv_forecast_minute_step, load_minutes_step, end_reco state += " ⅎ" # Import and export rates -> to string - if self.rate_import_replicated.get(minute, False): - rate_str_import = "%02.02f ?" % (rate_value_import) + adjust_type = self.rate_import_replicated.get(minute, None) + adjust_symbol = self.adjust_symbol(adjust_type) + if adjust_symbol: + rate_str_import = "%02.02f %s" % (rate_value_import, adjust_symbol) else: - rate_str_import = "%02.02f" % rate_value_import + rate_str_import = "%02.02f" % (rate_value_import) if plan_debug: rate_str_import += " (%02.02f)" % (rate_value_import / self.battery_loss / self.inverter_loss + self.metric_battery_cycle) @@ -6641,10 +6679,12 @@ def publish_html_plan(self, pv_forecast_minute_step, load_minutes_step, end_reco if charge_window_n >= 0: rate_str_import = "" + rate_str_import + "" - if self.rate_export_replicated.get(minute, False): - rate_str_export = "%02.02f ?" % (rate_value_export) + adjust_type = self.rate_export_replicated.get(minute, None) + adjust_symbol = self.adjust_symbol(adjust_type) + if adjust_symbol: + rate_str_export = "%02.02f %s" % (rate_value_export, adjust_symbol) else: - rate_str_export = "%2.02f" % (rate_value_export) + rate_str_export = "%02.02f" % (rate_value_export) if plan_debug: rate_str_export += " (%02.02f)" % (rate_value_export * self.battery_loss_discharge * self.inverter_loss - self.metric_battery_cycle) @@ -10323,9 +10363,9 @@ def fetch_sensor_data(self): self.rate_import = self.rate_scan(self.rate_import, print=False) self.rate_import, self.rate_import_replicated = self.rate_replicate(self.rate_import, self.io_adjusted, is_import=True) self.rate_import = self.rate_add_io_slots(self.rate_import, self.octopus_slots) - self.load_saving_slot(octopus_saving_slots, export=False) + self.load_saving_slot(octopus_saving_slots, export=False, rate_replicate=self.rate_import_replicated) if "rates_import_override" in self.args: - self.rate_import = self.basic_rates(self.get_arg("rates_import_override", [], indirect=False), "import", self.rate_import) + self.rate_import = self.basic_rates(self.get_arg("rates_import_override", [], indirect=False), "import", self.rate_import, self.rate_import_replicated) self.rate_import = self.rate_scan(self.rate_import, print=True) else: self.log("Warning: No import rate data provided") @@ -10337,9 +10377,9 @@ def fetch_sensor_data(self): self.rate_export, self.rate_export_replicated = self.rate_replicate(self.rate_export, is_import=False) # For export tariff only load the saving session if enabled if self.rate_export_max > 0: - self.load_saving_slot(octopus_saving_slots, export=True) + self.load_saving_slot(octopus_saving_slots, export=True, rate_replicate=self.rate_export_replicated) if "rates_export_override" in self.args: - self.rate_export = self.basic_rates(self.get_arg("rates_export_override", [], indirect=False), "export", self.rate_export) + self.rate_export = self.basic_rates(self.get_arg("rates_export_override", [], indirect=False), "export", self.rate_export, self.rate_export_replicated) self.rate_export = self.rate_scan_export(self.rate_export, print=True) else: self.log("Warning: No export rate data provided") @@ -11058,9 +11098,9 @@ async def select_event(self, event, data, kwargs): None Description: - This method is used to handle Home Assistant input select updates. - It extracts the necessary information from the data and performs different actions based on the selected option. - The actions include calling update service, saving and restoring settings, performing manual selection, and exposing configuration. + This method is used to handle Home Assistant input select updates. + It extracts the necessary information from the data and performs different actions based on the selected option. + The actions include calling update service, saving and restoring settings, performing manual selection, and exposing configuration. After performing the actions, it triggers an update by setting update_pending flag to True and plan_valid flag to False. """ service_data = data.get("service_data", {}) From d9bddede64f1eb96e15f542db4cb9e2d8371631b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sun, 4 Feb 2024 13:52:57 +0000 Subject: [PATCH 2/5] [pre-commit.ci lite] apply automatic fixes --- apps/predbat/predbat.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/predbat/predbat.py b/apps/predbat/predbat.py index d6a572e59..4b0efa9b9 100644 --- a/apps/predbat/predbat.py +++ b/apps/predbat/predbat.py @@ -4370,7 +4370,7 @@ def find_charge_window_optimised(self, charge_windows): for window_n in range(len(charge_windows)): for minute in range(charge_windows[window_n]["start"], charge_windows[window_n]["end"], PREDICT_STEP): charge_window_optimised[minute] = window_n - return charge_window_optimised + return charge_window_optimised def run_prediction(self, charge_limit, charge_window, discharge_window, discharge_limits, load_minutes_step, pv_forecast_minute_step, end_record, save=None, step=PREDICT_STEP): """ @@ -4471,7 +4471,6 @@ def run_prediction(self, charge_limit, charge_window, discharge_window, discharg if self.set_reserve_enable: reserve_expected = max(charge_limit_n, self.reserve) - # Add in standing charge, only for the final plan when we save the results if (minute_absolute % (24 * 60)) < step and (save in ["best", "base", "base10", "best10"]): metric += self.metric_standing_charge @@ -11098,9 +11097,9 @@ async def select_event(self, event, data, kwargs): None Description: - This method is used to handle Home Assistant input select updates. - It extracts the necessary information from the data and performs different actions based on the selected option. - The actions include calling update service, saving and restoring settings, performing manual selection, and exposing configuration. + This method is used to handle Home Assistant input select updates. + It extracts the necessary information from the data and performs different actions based on the selected option. + The actions include calling update service, saving and restoring settings, performing manual selection, and exposing configuration. After performing the actions, it triggers an update by setting update_pending flag to True and plan_valid flag to False. """ service_data = data.get("service_data", {}) From c7e85a11ac216a7caef20acdd9fad8a442f6f28c Mon Sep 17 00:00:00 2001 From: Trefor Southwell <48591903+springfall2008@users.noreply.github.com> Date: Sun, 4 Feb 2024 14:00:58 +0000 Subject: [PATCH 3/5] Update predbat-plan-card.md --- docs/predbat-plan-card.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/predbat-plan-card.md b/docs/predbat-plan-card.md index 4944b9344..3f3c7d011 100644 --- a/docs/predbat-plan-card.md +++ b/docs/predbat-plan-card.md @@ -43,6 +43,22 @@ For every half hour period (slot) that Predbat has planned for (the *forecast_ho - The forecast cost for the half hour slot - A running total cost +Rate symbols (import and export): +- ? ⅆ - Rate that has been modified based on **input_number.predbat_metric_future_rate_offset_import** or **input_number.predbat_metric_future_rate_offset_export** +- ? ⚖ - Rate that has been estimated using future rate estimation data (e.g. Nordpool) +- = - Rate that has been overriden by the users apps.yaml +- ± - Rate that has been adjusted with a rate offset in the users apps.yaml +- $ - Rate that has been adjusted for an Octopus Saving session +- ? - Rate that has not yet been defined and the previous days data was used instead +Battery SOC symbols: +- → - Current SOC expected to remain level +- ↘ - Current SOC expected to fall +- ↗ - Current SOC expected to rise +Cost symbols: +- → - Current cost expected to remain level +- ↘ - Current cost expected to fall (due to export) +- ↗ - Current cost expected to rise (due to import or standing charge) + Explaining each column in the Predbat plan in more detail: - **Time** - Predbat plans your home, solar and battery load in 30 minute slots, on the :00 and :30 minutes past each hour. From 473c4e3329fc5384bbc045ae0ce1c13c9b4258f7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sun, 4 Feb 2024 14:01:49 +0000 Subject: [PATCH 4/5] [pre-commit.ci lite] apply automatic fixes --- docs/predbat-plan-card.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/predbat-plan-card.md b/docs/predbat-plan-card.md index 3f3c7d011..03d003917 100644 --- a/docs/predbat-plan-card.md +++ b/docs/predbat-plan-card.md @@ -44,6 +44,7 @@ For every half hour period (slot) that Predbat has planned for (the *forecast_ho - A running total cost Rate symbols (import and export): + - ? ⅆ - Rate that has been modified based on **input_number.predbat_metric_future_rate_offset_import** or **input_number.predbat_metric_future_rate_offset_export** - ? ⚖ - Rate that has been estimated using future rate estimation data (e.g. Nordpool) - = - Rate that has been overriden by the users apps.yaml From 02c8142e6c0f507097bd39d1da760f31ecadc522 Mon Sep 17 00:00:00 2001 From: Trefor Southwell <48591903+springfall2008@users.noreply.github.com> Date: Sun, 4 Feb 2024 14:03:17 +0000 Subject: [PATCH 5/5] Update predbat-plan-card.md --- docs/predbat-plan-card.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/predbat-plan-card.md b/docs/predbat-plan-card.md index 03d003917..222de87ba 100644 --- a/docs/predbat-plan-card.md +++ b/docs/predbat-plan-card.md @@ -47,7 +47,7 @@ Rate symbols (import and export): - ? ⅆ - Rate that has been modified based on **input_number.predbat_metric_future_rate_offset_import** or **input_number.predbat_metric_future_rate_offset_export** - ? ⚖ - Rate that has been estimated using future rate estimation data (e.g. Nordpool) -- = - Rate that has been overriden by the users apps.yaml +- = - Rate that has been overridden by the users apps.yaml - ± - Rate that has been adjusted with a rate offset in the users apps.yaml - $ - Rate that has been adjusted for an Octopus Saving session - ? - Rate that has not yet been defined and the previous days data was used instead