From bc872e0a68023c633ceaeca04392cdef309a91ad Mon Sep 17 00:00:00 2001 From: fboundy Date: Fri, 16 Feb 2024 12:33:27 +0000 Subject: [PATCH 1/6] Additional logging for tariff entities --- apps/pv_opt/config/config.yaml | 6 +++--- apps/pv_opt/pv_opt.py | 10 +++++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/pv_opt/config/config.yaml b/apps/pv_opt/config/config.yaml index ac093c1..7387ab0 100644 --- a/apps/pv_opt/config/config.yaml +++ b/apps/pv_opt/config/config.yaml @@ -81,10 +81,10 @@ pv_opt: # Octopus account parameters # ======================================== - octopus_auto: False # Read tariffs from the Octopus Energy integration. If successful this over-rides the following parameters + # octopus_auto: False # Read tariffs from the Octopus Energy integration. If successful this over-rides the following parameters - octopus_account: !secret octopus_account - octopus_api_key: !secret octopus_api_key + # octopus_account: !secret octopus_account + # octopus_api_key: !secret octopus_api_key # The following Can be omitted if either of the above options is working correctly: diff --git a/apps/pv_opt/pv_opt.py b/apps/pv_opt/pv_opt.py index 1d9d22e..cd4f812 100644 --- a/apps/pv_opt/pv_opt.py +++ b/apps/pv_opt/pv_opt.py @@ -592,8 +592,16 @@ def _load_contract(self): for imp_exp in IMPEXP: for entity in entities[imp_exp]: + tariff_code = self.get_state( + entity, attribute="all" + )["attributes"][BOTTLECAP_DAVE["tariff_code"]] + average_rate = self.get_state( + entity, attribute="all" + )["attributes"]["average_rate"] + + self.rlog( - f" Found {imp_exp} entity {entity}" + f" Found {imp_exp} entity {entity}: Tariff code: {tariff_code} Average Rate: {average_rate} GBP/kWh" ) tariffs = {x: None for x in IMPEXP} From c194061c5b756015cecc4b99aa3f02c5ee690eb7 Mon Sep 17 00:00:00 2001 From: fboundy Date: Fri, 16 Feb 2024 12:37:13 +0000 Subject: [PATCH 2/6] Check history is non-zero length --- apps/pv_opt/pv_opt.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/pv_opt/pv_opt.py b/apps/pv_opt/pv_opt.py index cd4f812..f665c70 100644 --- a/apps/pv_opt/pv_opt.py +++ b/apps/pv_opt/pv_opt.py @@ -274,13 +274,18 @@ def hass2df(self, entity_id, days=2, log=False): hist = self.get_history(entity_id=entity_id, days=days) - df = pd.DataFrame(hist[0]).set_index("last_updated")["state"] - df.index = pd.to_datetime(df.index, format="ISO8601") + if len(hist) >0: + df = pd.DataFrame(hist[0]).set_index("last_updated")["state"] + df.index = pd.to_datetime(df.index, format="ISO8601") - df = df.sort_index() - df = df[df != "unavailable"] - df = df[df != "unknown"] + df = df.sort_index() + df = df[df != "unavailable"] + df = df[df != "unknown"] + else: + raise ValueError(f"No data returned from HASS entity {entity_id}") + df = None + return df def initialize(self): From 85245aa47a710da3a7bd06d8c9cc2957663cc5c8 Mon Sep 17 00:00:00 2001 From: fboundy Date: Fri, 16 Feb 2024 13:05:47 +0000 Subject: [PATCH 3/6] Additional logging --- apps/pv_opt/pv_opt.py | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/apps/pv_opt/pv_opt.py b/apps/pv_opt/pv_opt.py index f665c70..73018c9 100644 --- a/apps/pv_opt/pv_opt.py +++ b/apps/pv_opt/pv_opt.py @@ -285,7 +285,7 @@ def hass2df(self, entity_id, days=2, log=False): else: raise ValueError(f"No data returned from HASS entity {entity_id}") df = None - + return df def initialize(self): @@ -1356,11 +1356,21 @@ def optimise(self): # Load Solcast solcast = self.load_solcast() + if solcast is None: + self.log("") + self.log("Unable to optimise without Solcast data.", level="ERROR") + return + consumption = self.load_consumption( pd.Timestamp.utcnow().normalize(), pd.Timestamp.utcnow().normalize() + pd.Timedelta(days=2), ) + if consumption is None: + self.log("") + self.log("Unable to optimise without consumption data.", level="ERROR") + return + self.static = pd.concat([solcast, consumption], axis=1) self.time_now = pd.Timestamp.utcnow() @@ -1395,6 +1405,12 @@ def optimise(self): ] ).sort_index() self.initial_soc = x.interpolate().loc[self.static.index[0]] + if not isinstance(self.initial_soc, float): + self.log("") + self.log("Unable to optimise without consumption data.", level="ERROR") + self._status("ERROR: No initial SOC") + return + self.log(f"Initial SOC: {self.initial_soc}") self.log("Calculating Base flows") @@ -1402,6 +1418,12 @@ def optimise(self): self.initial_soc, self.static, solar=self.get_config("solar_forecast") ) + if len(self.base)==0: + self.log("") + self.log("Unable to calculate baseline perfoormance", level="ERROR") + self._status("ERROR: Basline performance") + return + self.base_cost = self.contract.net_cost(self.base) self.log(f"Base cost: {self.base_cost.sum():6.2f}p") self.log("") @@ -1944,10 +1966,12 @@ def load_solcast(self): df = df.fillna(0) # self.static = pd.concat([self.static, df], axis=1) self.log("Solcast forecast loaded OK") + self.log("") return df except Exception as e: self.log(f"Error loading Solcast: {e}", level="ERROR") + self.log("") return def _get_hass_power_from_daily_kwh( @@ -1994,14 +2018,19 @@ def load_consumption(self, start, end): log=self.debug, ) - actual_days = (df.index[-1] - df.index[0]).total_seconds() / 3600 / 24 + if df is None: + self._status("ERROR: No consumption history.") + return + + actual_days = int(round((df.index[-1] - df.index[0]).total_seconds() / 3600 / 24,0)) self.log( - f" - Got {actual_days:0.1f} days history from {entity_id} from {df.index[0].strftime(DATE_TIME_FORMAT_SHORT)} to {df.index[-1].strftime(DATE_TIME_FORMAT_SHORT)}" + f" - Got {actual_days} days history from {entity_id} from {df.index[0].strftime(DATE_TIME_FORMAT_SHORT)} to {df.index[-1].strftime(DATE_TIME_FORMAT_SHORT)}" ) if int(actual_days) == days: str_days = "OK" else: + self._status(f"WARNING: Consumption < {days} days.") str_days = "Potential error. <<<" self.log(f" - {days} days was expected. {str_days}") From 7f2106bf92b78b106d1df32ec23699af5bd901e8 Mon Sep 17 00:00:00 2001 From: fboundy Date: Fri, 16 Feb 2024 13:06:18 +0000 Subject: [PATCH 4/6] 3.8.8 --- README.md | 2 +- apps/pv_opt/pv_opt.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f52b5ec..d843315 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# PV Opt: Home Assistant Solar/Battery Optimiser v3.8.7 +# PV Opt: Home Assistant Solar/Battery Optimiser v3.8.8 Solar / Battery Charging Optimisation for Home Assistant. This appDaemon application attempts to optimise charging and discharging of a home solar/battery system to minimise cost electricity cost on a daily basis using freely available solar forecast data from SolCast. This is particularly beneficial for Octopus Agile but is also benefeficial for other time-of-use tariffs such as Octopus Flux or simple Economy 7. diff --git a/apps/pv_opt/pv_opt.py b/apps/pv_opt/pv_opt.py index 73018c9..cf8cfa6 100644 --- a/apps/pv_opt/pv_opt.py +++ b/apps/pv_opt/pv_opt.py @@ -20,7 +20,7 @@ # USE_TARIFF = True -VERSION = "3.8.7" +VERSION = "3.8.8" DEBUG = False DATE_TIME_FORMAT_LONG = "%Y-%m-%d %H:%M:%S%z" From 934aa3b5f65ae1907af431c26af7ff9517d5ba57 Mon Sep 17 00:00:00 2001 From: fboundy Date: Fri, 16 Feb 2024 13:08:30 +0000 Subject: [PATCH 5/6] Check average rate from OE rate sensor --- apps/pv_opt/pv_opt.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/pv_opt/pv_opt.py b/apps/pv_opt/pv_opt.py index cf8cfa6..af9399e 100644 --- a/apps/pv_opt/pv_opt.py +++ b/apps/pv_opt/pv_opt.py @@ -616,11 +616,16 @@ def _load_contract(self): entities[imp_exp][0], attribute="all" )["attributes"][BOTTLECAP_DAVE["tariff_code"]] - tariffs[imp_exp] = pv.Tariff( - tariff_code, export=(imp_exp == "export"), host=self - ) - if "AGILE" in tariff_code: - self.agile = True + average_rate = self.get_state( + entity, attribute="all" + )["attributes"]["average_rate"] + + if average_rate > 0: + tariffs[imp_exp] = pv.Tariff( + tariff_code, export=(imp_exp == "export"), host=self + ) + if "AGILE" in tariff_code: + self.agile = True self.contract = pv.Contract( "current", From dbcf9c951869636f92cf1b6481e7d753d4019de4 Mon Sep 17 00:00:00 2001 From: fboundy Date: Fri, 16 Feb 2024 13:09:51 +0000 Subject: [PATCH 6/6] 3.8.8 --- apps/pv_opt/pv_opt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/pv_opt/pv_opt.py b/apps/pv_opt/pv_opt.py index af9399e..1a41ef6 100644 --- a/apps/pv_opt/pv_opt.py +++ b/apps/pv_opt/pv_opt.py @@ -633,6 +633,7 @@ def _load_contract(self): exp=tariffs["export"], host=self, ) + self.log("") self.rlog("Contract tariffs loaded OK") except Exception as e: