Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 53 additions & 14 deletions apps/predbat/predbat.py
Original file line number Diff line number Diff line change
Expand Up @@ -5441,6 +5441,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
Expand All @@ -5466,6 +5467,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)
Expand All @@ -5474,18 +5476,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

Expand Down Expand Up @@ -5553,7 +5559,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
Expand Down Expand Up @@ -5655,8 +5661,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:
Expand Down Expand Up @@ -5782,7 +5790,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
"""
Expand Down Expand Up @@ -5819,6 +5827,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):
"""
Expand Down Expand Up @@ -6438,6 +6447,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 = "? &#8518;"
elif adjust_type == "future":
symbol = "? &#x2696;"
elif adjust_type == "user":
symbol = "&#61;"
elif adjust_type == "increment":
symbol = "&#177;"
elif adjust_type == "saving":
symbol = "&dollar;"
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
Expand Down Expand Up @@ -6630,21 +6665,25 @@ def publish_html_plan(self, pv_forecast_minute_step, load_minutes_step, end_reco
state += " &#8526;"

# Import and export rates -> to string
if self.rate_import_replicated.get(minute, False):
rate_str_import = "<i>%02.02f ?</i>" % (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 = "<i>%02.02f %s</i>" % (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)

if charge_window_n >= 0:
rate_str_import = "<b>" + rate_str_import + "</b>"

if self.rate_export_replicated.get(minute, False):
rate_str_export = "<i>%02.02f ?</i>" % (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 = "<i>%02.02f %s</i>" % (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)
Expand Down Expand Up @@ -10323,9 +10362,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")
Expand All @@ -10337,9 +10376,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")
Expand Down
17 changes: 17 additions & 0 deletions docs/predbat-plan-card.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,23 @@ 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):

- ? &#8518; - Rate that has been modified based on **input_number.predbat_metric_future_rate_offset_import** or **input_number.predbat_metric_future_rate_offset_export**
- ? &#x2696; - Rate that has been estimated using future rate estimation data (e.g. Nordpool)
- &#61; - Rate that has been overridden by the users apps.yaml
- &#177; - Rate that has been adjusted with a rate offset in the users apps.yaml
- &dollar; - 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:
- &rarr; - Current SOC expected to remain level
- &searr; - Current SOC expected to fall
- &nearr; - Current SOC expected to rise
Cost symbols:
- &rarr; - Current cost expected to remain level
- &searr; - Current cost expected to fall (due to export)
- &nearr; - 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.
Expand Down