Skip to content

Commit

Permalink
Merge pull request #367 from fboundy/patch
Browse files Browse the repository at this point in the history
v 4.0.7
  • Loading branch information
fboundy authored Jan 11, 2025
2 parents 3114bfe + 3567e96 commit edbf3d5
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 189 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# PV Opt: Home Assistant Solar/Battery Optimiser v4.0.6
# PV Opt: Home Assistant Solar/Battery Optimiser v4.0.7


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.
Expand Down
36 changes: 18 additions & 18 deletions apps/pv_opt/config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ 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

Expand Down Expand Up @@ -159,12 +159,12 @@ pv_opt:

# octopus_import_tariff_code: E-1R-AGILE-23-12-06-G
# # octopus_export_tariff_code: E-1R-OUTGOING-LITE-FIX-12M-23-09-12-G
# octopus_export_tariff_code: E-1R-OUTGOING-FIX-12M-19-05-13-G
octopus_export_tariff_code: E-1R-OUTGOING-FIX-12M-19-05-13-G

# octopus_import_tariff_code: E-1R-FLUX-IMPORT-23-02-14-G
# octopus_export_tariff_code: E-1R-FLUX-EXPORT-23-02-14-G

# octopus_import_tariff_code: E-1R-GO-VAR-22-10-14-N
octopus_import_tariff_code: E-1R-GO-VAR-22-10-14-N
# octopus_export_tariff_code: E-1R-OUTGOING-LITE-FIX-12M-23-09-12-N

# ========================================
Expand Down Expand Up @@ -444,19 +444,19 @@ pv_opt:

# Tariff comparison
# id_daily_solar: sensor.{device_name}_power_generation_today
# id_solar_power:
# # - sensor.{device_name}_pv_power_1
# # - sensor.{device_name}_pv_power_2
# alt_tariffs:
# - name: Agile_Fix
# octopus_import_tariff_code: E-1R-AGILE-23-12-06-G
# octopus_export_tariff_code: E-1R-OUTGOING-FIX-12M-19-05-13-G

# # - name: Eco7_Fix
# # octopus_import_tariff_code: E-2R-VAR-22-11-01-G
# # octopus_export_tariff_code: E-1R-OUTGOING-FIX-12M-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
id_solar_power:
- sensor.{device_name}_pv_power_1
- sensor.{device_name}_pv_power_2
alt_tariffs:
- name: Agile_Fix
octopus_import_tariff_code: E-1R-AGILE-23-12-06-G
octopus_export_tariff_code: E-1R-OUTGOING-FIX-12M-19-05-13-G

# - name: Eco7_Fix
# octopus_import_tariff_code: E-2R-VAR-22-11-01-G
# octopus_export_tariff_code: E-1R-OUTGOING-FIX-12M-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

149 changes: 84 additions & 65 deletions apps/pv_opt/pv_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@
import pvpy as pv
from numpy import nan

VERSION = "4.0.6"
VERSION = "4.0.7"
UNITS = {
"current": "A",
"power": "W",
}


OCTOPUS_PRODUCT_URL = r"https://api.octopus.energy/v1/products/"

DEBUG = False
Expand Down Expand Up @@ -2354,67 +2355,69 @@ def optimise(self):
return

self.pv_system.static_flows = pd.concat([solcast, consumption], axis=1)
self.time_now = pd.Timestamp.utcnow()
self.time_now = pd.Timestamp.utcnow().floor("1min")

self.pv_system.static_flows = self.pv_system.static_flows[self.time_now.floor("30min") :].fillna(0)
self.pv_system.static_flows.index = [self.time_now] + list(self.pv_system.static_flows.index[1:])

soc_now = self.get_config("id_battery_soc")
soc_last_day = self.hass2df(self.config["id_battery_soc"], days=1, log=self.debug)
if self.debug and "S" in self.debug_cat:
self.log(f">>> soc_now: {soc_now}")
self.log(f">>> soc_last_day: {soc_last_day}")
self.log(
f">>> Original: {soc_last_day.loc[soc_last_day.loc[: self.pv_system.static_flows.index[0]].index[-1] :]}"
)

try:
soc_now = float(soc_now)

except:
self.log("")
self.log(
"Unable to get current SOC from HASS. Using last value from History.",
level="WARNING",
)
soc_now = soc_last_day.iloc[-1]

# x = x.astype(float)

try:
soc_last_day = pd.to_numeric(soc_last_day, errors="coerce").interpolate()
self.pv_system.initial_soc = soc_now
# soc_last_day = self.hass2df(self.config["id_battery_soc"], days=1, log=self.debug)
# if self.debug and "S" in self.debug_cat:
# self.log(f">>> soc_now: {soc_now}")
# self.log(f">>> soc_last_day: {soc_last_day}")
# self.log(
# f">>> Original: {soc_last_day.loc[soc_last_day.loc[: self.pv_system.static_flows.index[0]].index[-1] :]}"
# )

# try:
# soc_now = float(soc_now)

# except:
# self.log("")
# self.log(
# "Unable to get current SOC from HASS. Using last value from History.",
# level="WARNING",
# )
# soc_now = soc_last_day.iloc[-1]

# # x = x.astype(float)

# try:
# soc_last_day = pd.to_numeric(soc_last_day, errors="coerce").interpolate()

# soc_last_day = soc_last_day.loc[soc_last_day.loc[: self.pv_system.static_flows.index[0]].index[-1] :]
# if self.debug and "S" in self.debug_cat:
# self.log(
# f">>> Fixed : {soc_last_day.loc[soc_last_day.loc[: self.pv_system.static_flows.index[0]].index[-1] :]}"
# )

# soc_last_day = pd.concat(
# [
# soc_last_day,
# pd.Series(
# data=[soc_now, nan],
# index=[self.time_now, self.pv_system.static_flows.index[0]],
# ),
# ]
# ).sort_index()
# self.pv_system.initial_soc = soc_last_day.interpolate().loc[self.pv_system.static_flows.index[0]]
# except:
# self.pv_system.initial_soc = None

# if not isinstance(self.pv_system.initial_soc, float):
# self.log("")
# self.log(
# "Unable to retrieve initial SOC - assuming it is the same as current SOC",
# level="WARNING",
# )
# self.pv_system.initial_soc = soc_now

# self.pv_system.soc_now = (self.time_now, soc_now)

soc_last_day = soc_last_day.loc[soc_last_day.loc[: self.pv_system.static_flows.index[0]].index[-1] :]
if self.debug and "S" in self.debug_cat:
self.log(
f">>> Fixed : {soc_last_day.loc[soc_last_day.loc[: self.pv_system.static_flows.index[0]].index[-1] :]}"
)

soc_last_day = pd.concat(
[
soc_last_day,
pd.Series(
data=[soc_now, nan],
index=[self.time_now, self.pv_system.static_flows.index[0]],
),
]
).sort_index()
self.pv_system.initial_soc = soc_last_day.interpolate().loc[self.pv_system.static_flows.index[0]]
except:
self.pv_system.initial_soc = None

if not isinstance(self.pv_system.initial_soc, float):
self.log("")
self.log(
"Unable to retrieve initial SOC - assuming it is the same as current SOC",
level="WARNING",
)
self.pv_system.initial_soc = soc_now

self.pv_system.soc_now = (self.time_now, soc_now)

self.log("")
self.log(f"Initial SOC: {self.pv_system.initial_soc}")
self.log(f"Current SOC: {self.pv_system.soc_now}")
# self.log("")
# self.log(f"Initial SOC: {self.pv_system.initial_soc}")
# self.log(f"Current SOC: {self.pv_system.soc_now}")

self.pv_system.calculate_flows()
self.flows = {"Base": self.pv_system.flows}
Expand Down Expand Up @@ -2507,6 +2510,8 @@ def optimise(self):

self.opt = self.flows[self.selected_case]

self.base = self.flows["Base"]

# SVB debug logging
# self.log("")
# self.log("Returned from .flows. self.opt is........")
Expand Down Expand Up @@ -3987,10 +3992,19 @@ def _compare_tariffs(self):
return

consumption = self.load_consumption(start, end)
static = pd.concat([solar, consumption], axis=1).set_axis(["solar", "consumption"], axis=1)
self.pv_system.static_flows = pd.concat([solar, consumption], axis=1).set_axis(
["solar", "consumption"], axis=1
)

initial_soc_df = self.hass2df(self.config["id_battery_soc"], days=2, freq="30min")
initial_soc = initial_soc_df.loc[start]
self.pv_system.initial_soc = initial_soc_df.loc[start]

# Not sure about the next lines, but calculate_flows requires soc_now
soc_now = self.get_config("id_battery_soc")
self.time_now = pd.Timestamp.utcnow()
self.pv_system.soc_now = (self.time_now, soc_now)

# self.pv_system.soc_now = initial_soc_df.loc[start]

self.pv_system.calculate_flows()
base = self.pv_system.flows
Expand All @@ -4000,12 +4014,12 @@ def _compare_tariffs(self):
self.log("")
self.log(f"Start: {start.strftime(DATE_TIME_FORMAT_SHORT):>15s}")
self.log(f"End: {end.strftime(DATE_TIME_FORMAT_SHORT):>15s}")
self.log(f"Initial SOC: {initial_soc:>15.1f}%")
self.log(f"Consumption: {static['consumption'].sum()/2000:15.1f} kWh")
self.log(f"Solar: {static['solar'].sum()/2000:15.1f} kWh")
self.log(f"Initial SOC: {self.pv_system.initial_soc:>15.1f}%")
self.log(f"Consumption: {self.pv_system.static_flows['consumption'].sum()/2000:15.1f} kWh")
self.log(f"Solar: {self.pv_system.static_flows['solar'].sum()/2000:15.1f} kWh")

if self.debug and "T" in self.debug_cat:
self.log(f">>> Yesterday's data:\n{static.to_string()}")
self.log(f">>> Yesterday's data:\n{self.pv_system.static_flows.to_string()}")

for tariff_set in self.config["alt_tariffs"]:
code = {}
Expand All @@ -4025,7 +4039,9 @@ def _compare_tariffs(self):
)

actual = self._cost_actual(start=start, end=end - pd.Timedelta(30, "minutes"))
static["period_start"] = static.index.tz_convert(self.tz).strftime("%Y-%m-%dT%H:%M:%S%z").str[:-2] + ":00"
self.pv_system.static_flows["period_start"] = (
self.pv_system.static_flows.index.tz_convert(self.tz).strftime("%Y-%m-%dT%H:%M:%S%z").str[:-2] + ":00"
)
entity_id = f"sensor.{self.prefix}_opt_cost_actual"
self.set_state(
state=round(actual.sum() / 100, 2),
Expand All @@ -4036,7 +4052,10 @@ def _compare_tariffs(self):
"unit_of_measurement": "GBP",
"friendly_name": f"PV Opt Comparison Actual",
}
| {col: static[["period_start", col]].to_dict("records") for col in ["solar", "consumption"]},
| {
col: self.pv_system.static_flows[["period_start", col]].to_dict("records")
for col in ["solar", "consumption"]
},
)

self.ulog("Net Cost comparison:", underline=None)
Expand Down
Loading

0 comments on commit edbf3d5

Please sign in to comment.