diff --git a/README.md b/README.md index fa28c08..2035683 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# PV Opt: Home Assistant Solar/Battery Optimiser v3.5.2 +# PV Opt: Home Assistant Solar/Battery Optimiser v3.5.3 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/config/config.yaml b/apps/pv_opt/config/config.yaml index 7d3ef20..789deb1 100644 --- a/apps/pv_opt/config/config.yaml +++ b/apps/pv_opt/config/config.yaml @@ -95,7 +95,7 @@ pv_opt: # update_cycle_seconds: 15 # maximum_dod_percent: number.{device_name}_battery_minimum_soc - # id_consumption_today: sensor.{device_name}_house_load_today + id_consumption_today: sensor.{device_name}_consumption_today # id_grid_import_today: sensor.{device_name}_grid_import_today # id_grid_export_today: sensor.{device_name}_grid_export_today @@ -179,12 +179,12 @@ pv_opt: # Tariff comparison - id_daily_solar: sensor.{device_name}_power_generation_today - alt_tariffs: - - name: Eco7_Agile - octopus_import_tariff_code: E-2R-VAR-22-11-01-G - octopus_export_tariff_code: E-1R-AGILE-OUTGOING-19-05-13-G - - - name: Flux - octopus_import_tariff_code: E-1R-FLUX-IMPORT-23-02-14-G - octopus_export_tariff_code: E-1R-FLUX-EXPORT-23-02-14-G \ No newline at end of file + # id_daily_solar: sensor.{device_name}_power_generation_today + # alt_tariffs: + # - name: Eco7_Agile + # octopus_import_tariff_code: E-2R-VAR-22-11-01-G + # octopus_export_tariff_code: E-1R-AGILE-OUTGOING-19-05-13-G + + # - name: Flux + # octopus_import_tariff_code: E-1R-FLUX-IMPORT-23-02-14-G + # octopus_export_tariff_code: E-1R-FLUX-EXPORT-23-02-14-G \ No newline at end of file diff --git a/apps/pv_opt/pv_opt.py b/apps/pv_opt/pv_opt.py index 70f9349..653c8a2 100644 --- a/apps/pv_opt/pv_opt.py +++ b/apps/pv_opt/pv_opt.py @@ -19,7 +19,7 @@ # USE_TARIFF = True -VERSION = "3.5.2" +VERSION = "3.5.3" DEBUG = False DATE_TIME_FORMAT_LONG = "%Y-%m-%d %H:%M:%S%z" @@ -1452,20 +1452,34 @@ def optimise(self): # We aren't in a charge/discharge slot and the next one doesn't start before the # optimiser runs again - str_log = f"Next {direction} window starts in {time_to_slot_start:0.1f} minutes." + str_log = f"Next {direction} window starts in {time_to_slot_start:0.1f} minutes " # If the next slot isn't soon then just check that current status matches what we see: + did_something = False + if status["charge"]["active"]: str_log += " but inverter is charging. Disabling charge." self.log(str_log) self.inverter.control_charge(enable=False) + did_something = True + elif status["charge"]["start"] != status["charge"]["end"]: + str_log += " but charge start and end times are different." + self.log(str_log) + self.inverter.control_charge(enable=False) + did_something = True - elif status["discharge"]["active"]: + if status["discharge"]["active"]: str_log += " but inverter is discharging. Disabling discharge." self.log(str_log) self.inverter.control_discharge(enable=False) + did_something = True + elif status["discharge"]["start"] != status["discharge"]["end"]: + str_log += " but charge start and end times are different." + self.log(str_log) + self.inverter.control_charge(enable=False) + did_something = True - elif ( + if ( direction == "charge" and self.charge_start_datetime > status["discharge"]["start"] and status["discharge"]["start"] != status["discharge"]["end"] @@ -1473,6 +1487,7 @@ def optimise(self): str_log += " but inverter is has a discharge slot before then. Disabling discharge." self.log(str_log) self.inverter.control_discharge(enable=False) + did_something = True elif ( direction == "discharge" @@ -1482,16 +1497,17 @@ def optimise(self): str_log += " but inverter is has a charge slot before then. Disabling charge." self.log(str_log) self.inverter.control_charge(enable=False) + did_something = True - elif status["hold_soc"]["active"]: + if status["hold_soc"]["active"]: self.inverter.hold_soc(enable=False) str_log += " but inverter is holding SOC. Disabling." self.log(str_log) + did_something = True - else: - str_log += " Nothing to do." + if not did_something: + str_log += ". Nothing to do." self.log(str_log) - did_something = False if did_something: if self.get_config("update_cycle_seconds") is not None: diff --git a/apps/pv_opt/solis.py b/apps/pv_opt/solis.py index ecd9fe4..d14d4ce 100644 --- a/apps/pv_opt/solis.py +++ b/apps/pv_opt/solis.py @@ -221,11 +221,13 @@ def enable_timed_mode(self): ) def control_charge(self, enable, **kwargs): - self.enable_timed_mode() + if enable: + self.enable_timed_mode() self._control_charge_discharge("charge", enable, **kwargs) def control_discharge(self, enable, **kwargs): - self.enable_timed_mode() + if enable: + self.enable_timed_mode() self._control_charge_discharge("discharge", enable, **kwargs) def hold_soc(self, enable, soc=None): @@ -313,6 +315,8 @@ def _control_charge_discharge(self, direction, enable, **kwargs): self._solis_control_charge_discharge(direction, enable, **kwargs) def _solis_control_charge_discharge(self, direction, enable, **kwargs): + status = self._solis_state() + times = { "start": kwargs.get("start", None), "end": kwargs.get("end", None), @@ -332,7 +336,7 @@ def _solis_control_charge_discharge(self, direction, enable, **kwargs): # Disable by setting the times the same if (enable is not None) and (not enable): - times["start"] = pd.Timestamp.now().floor("1T") + times["start"] = status[direction]["start"] times["end"] = times["start"] # Don't span midnight