diff --git a/apps/predbat/predbat.py b/apps/predbat/predbat.py index ff4495671..69e3adb45 100644 --- a/apps/predbat/predbat.py +++ b/apps/predbat/predbat.py @@ -32,7 +32,7 @@ import asyncio import json -THIS_VERSION = "v8.5.6" +THIS_VERSION = "v8.5.7" PREDBAT_FILES = ["predbat.py", "config.py", "prediction.py", "utils.py", "inverter.py", "ha.py", "download.py", "unit_test.py", "web.py", "predheat.py", "futurerate.py"] from download import predbat_update_move, predbat_update_download, check_install @@ -2306,6 +2306,15 @@ def run_prediction(self, charge_limit, charge_window, discharge_window, discharg "icon": "mdi:battery", }, ) + + # Compute battery value now and at end of plan + rate_min_now = self.rate_min_forward.get(self.minutes_now, self.rate_min) / self.inverter_loss / self.battery_loss + self.metric_battery_cycle + rate_min_end = self.rate_min_forward.get(end_record, self.rate_min) / self.inverter_loss / self.battery_loss + self.metric_battery_cycle + rate_export_min_now = self.rate_export_min * self.inverter_loss * self.battery_loss_discharge - self.metric_battery_cycle - rate_min_now + rate_export_min_end = self.rate_export_min * self.inverter_loss * self.battery_loss_discharge - self.metric_battery_cycle - rate_min_end + value_kwh_now = rate_min_now * self.metric_battery_value_scaling * max(rate_min_now, 1.0, rate_export_min_now) + value_kwh_end = rate_min_end * self.metric_battery_value_scaling * max(rate_min_end, 1.0, rate_export_min_end) + self.dashboard_item( self.prefix + ".soc_kw_best", state=self.dp3(final_soc), @@ -2316,6 +2325,11 @@ def run_prediction(self, charge_limit, charge_window, discharge_window, discharg "state_class": "measurement", "unit_of_measurement": "kWh", "first_charge_kwh": first_charge_soc, + "soc_now": self.dp2(self.soc_kw), + "value_per_kwh_now": self.dp2(value_kwh_now), + "value_per_kwh_end": self.dp2(value_kwh_end), + "value_now": self.dp2(self.soc_kw * value_kwh_now), + "value_end": self.dp2(final_soc * value_kwh_end), "icon": "mdi:battery", }, ) @@ -4532,6 +4546,42 @@ def today_cost(self, import_today, export_today, car_today): day_carbon_time = {} carbon_g = 0 + hour_cost = 0 + hour_cost_import = 0 + hour_cost_export = 0 + hour_cost_car = 0 + hour_energy = 0 + hour_energy_export = 0 + hour_energy_import = 0 + hour_energy_car = 0 + + for minute in range(60): + energy_import = self.get_from_incrementing(import_today, minute) + + if car_today: + energy_car = self.get_from_incrementing(car_today, minute) + else: + energy_car = 0 + + if export_today: + energy_export = self.get_from_incrementing(export_today, minute) + else: + energy_export = 0 + + hour_energy += energy_import - energy_export + hour_energy_import += energy_import + hour_energy_export += energy_export + hour_energy_car += energy_car + + if self.rate_import: + hour_cost += self.rate_import[minute] * energy_import + hour_cost_import += self.rate_import[minute] * energy_import + hour_cost_car += self.rate_import[minute] * energy_car + + if self.rate_export: + hour_cost -= self.rate_export[minute] * energy_export + hour_cost_export -= self.rate_export[minute] * energy_export + for minute in range(self.minutes_now): # Add in standing charge if (minute % (24 * 60)) == 0: @@ -4582,10 +4632,15 @@ def today_cost(self, import_today, export_today, car_today): day_cost_time_export[stamp] = self.dp2(day_cost_export) day_carbon_time[stamp] = self.dp2(carbon_g) - day_pkwh = 0 - day_car_pkwh = 0 - day_import_pkwh = 0 - day_export_pkwh = 0 + day_pkwh = self.rate_import.get(0, 0) + day_car_pkwh = self.rate_import.get(0, 0) + day_import_pkwh = self.rate_import.get(0, 0) + day_export_pkwh = self.rate_export.get(0, 0) + hour_pkwh = self.rate_import.get(0, 0) + hour_pkwh_import = self.rate_import.get(0, 0) + hour_pkwh_car = self.rate_import.get(0, 0) + hour_pkwh_export = self.rate_export.get(0, 0) + if day_energy_total > 0: day_pkwh = day_cost_nosc / day_energy_total if day_car > 0: @@ -4594,13 +4649,21 @@ def today_cost(self, import_today, export_today, car_today): day_import_pkwh = day_cost_nosc_import / day_import if day_export > 0: day_export_pkwh = day_cost_export / day_export + if hour_energy > 0: + hour_pkwh = hour_cost / hour_energy + if hour_energy_import > 0: + hour_pkwh_import = hour_cost_import / hour_energy_import + if hour_energy_export > 0: + hour_pkwh_export = hour_cost_export / hour_energy_export + if hour_energy_car > 0: + hour_pkwh_car = hour_cost_car / hour_energy_car self.dashboard_item( self.prefix + ".cost_today", state=self.dp2(day_cost), attributes={ "results": self.filtered_times(day_cost_time), - "friendly_name": "Cost so far today", + "friendly_name": "Cost so far today (since midnight)", "state_class": "measurement", "unit_of_measurement": self.currency_symbols[1], "icon": "mdi:currency-usd", @@ -4608,6 +4671,27 @@ def today_cost(self, import_today, export_today, car_today): "p/kWh": self.dp2(day_pkwh), }, ) + self.dashboard_item( + self.prefix + ".cost_hour", + state=self.dp2(hour_cost), + attributes={ + "friendly_name": "Cost in last hour", + "state_class": "measurement", + "unit_of_measurement": self.currency_symbols[1], + "icon": "mdi:currency-usd", + "energy": self.dp2(hour_energy), + "energy_import": self.dp2(hour_energy_import), + "energy_export": self.dp2(hour_energy_export), + "energy_car": self.dp2(hour_energy_car), + "cost_import": self.dp2(hour_cost_import), + "cost_export": self.dp2(hour_cost_export), + "cost_car": self.dp2(hour_cost_car), + "p/kWh": self.dp2(hour_pkwh), + "p/kWh_car": self.dp2(hour_pkwh_car), + "p/kWh_import": self.dp2(hour_pkwh_import), + "p/kWh_export": self.dp2(hour_pkwh_export), + }, + ) if self.num_cars > 0: self.dashboard_item( self.prefix + ".cost_today_car", diff --git a/apps/predbat/web.py b/apps/predbat/web.py index f8424f16a..c1579cb96 100644 --- a/apps/predbat/web.py +++ b/apps/predbat/web.py @@ -68,6 +68,7 @@ def history_update(self): self.pv_power_hist = self.history_attribute(self.base.get_history_wrapper(self.base.prefix + ".pv_power", 7)) self.pv_forecast_hist = self.history_attribute(self.base.get_history_wrapper("sensor." + self.base.prefix + "_pv_forecast_h0", 7)) self.cost_today_hist = self.history_attribute(self.base.get_history_wrapper(self.base.prefix + ".cost_today", 2), state_key="p/kWh", attributes=True) + self.cost_hour_hist = self.history_attribute(self.base.get_history_wrapper(self.base.prefix + ".cost_hour", 2), state_key="p/kWh", attributes=True) async def start(self): # Start the web server on port 5052 @@ -657,12 +658,14 @@ def get_chart(self, chart): ] text += self.render_chart(series_data, self.base.currency_symbols[1], "Home Cost Prediction", now_str) elif chart == "Rates": - cost_pkwh = self.prune_today(self.cost_today_hist, prune=False, prune_future=True) + cost_pkwh_today = self.prune_today(self.cost_today_hist, prune=False, prune_future=True) + cost_pkwh_hour = self.prune_today(self.cost_hour_hist, prune=False, prune_future=True) series_data = [ {"name": "Import", "data": rates, "opacity": "1.0", "stroke_width": "3", "stroke_curve": "stepline"}, {"name": "Export", "data": rates_export, "opacity": "0.2", "stroke_width": "2", "stroke_curve": "stepline", "chart_type": "area"}, {"name": "Gas", "data": rates_gas, "opacity": "0.2", "stroke_width": "2", "stroke_curve": "stepline", "chart_type": "area"}, - {"name": "Average p/kWh", "data": cost_pkwh, "opacity": "1.0", "stroke_width": "3", "stroke_curve": "stepline"}, + {"name": "Hourly p/kWh", "data": cost_pkwh_hour, "opacity": "1.0", "stroke_width": "2", "stroke_curve": "stepline"}, + {"name": "Today p/kWh", "data": cost_pkwh_today, "opacity": "1.0", "stroke_width": "2", "stroke_curve": "stepline"}, ] text += self.render_chart(series_data, self.base.currency_symbols[1], "Energy Rates", now_str) elif chart == "InDay":