Skip to content

Commit

Permalink
Merge pull request #142 from fboundy/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
fboundy authored Feb 26, 2024
2 parents bec4854 + 73ca020 commit ba32cc0
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 21 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 v3.9.3
# PV Opt: Home Assistant Solar/Battery Optimiser v3.9.4

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
43 changes: 28 additions & 15 deletions apps/pv_opt/pv_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

USE_TARIFF = True

VERSION = "3.9.3"
VERSION = "3.9.4"
DEBUG = False

DATE_TIME_FORMAT_LONG = "%Y-%m-%d %H:%M:%S%z"
Expand All @@ -39,6 +39,7 @@
HOLD_TOLERANCE = 3
MAX_ITERS = 10
MAX_INVERTER_UPDATES = 2
MAX_HASS_HISTORY_CALLS = 5

BOTTLECAP_DAVE = {
"domain": "event",
Expand Down Expand Up @@ -270,7 +271,14 @@ def hass2df(self, entity_id, days=2, log=False):
self.log(f">>> Getting {days} days' history for {entity_id}")
self.log(f">>> Entity exits: {self.entity_exists(entity_id)}")

hist = self.get_history(entity_id=entity_id, days=days)
hist = None

i = 0
while (hist is None) and (i < MAX_HASS_HISTORY_CALLS):
hist = self.get_history(entity_id=entity_id, days=days)
if hist is None:
time.sleep(1)
i += 1

if (hist is not None) and (len(hist) >0):
df = pd.DataFrame(hist[0]).set_index("last_updated")["state"]
Expand Down Expand Up @@ -1515,9 +1523,10 @@ def optimise(self):
self.charge_end_datetime - pd.Timestamp.now(self.tz)
).total_seconds() / 60

# if len(self.windows) > 0:
if (time_to_slot_start > 0) and (
time_to_slot_start < self.get_config("optimise_frequency_minutes")
):
) and (len(self.windows) > 0):
# Next slot starts before the next optimiser run. This implies we are not currently in
# a charge or discharge slot

Expand Down Expand Up @@ -2019,16 +2028,18 @@ def _get_hass_power_from_daily_kwh(
log=log,
)

df.index = pd.to_datetime(df.index)
df = pd.to_numeric(df, errors="coerce")
df = (
df.diff(-1).fillna(0).clip(upper=0).cumsum().resample("30T")
).ffill().fillna(0).diff(-1) * 2000
df = df.fillna(0)
if start is not None:
df = df.loc[start:]
if end is not None:
df = df.loc[:end]
if df is not None:
df.index = pd.to_datetime(df.index)
df = pd.to_numeric(df, errors="coerce")
df = (
df.diff(-1).fillna(0).clip(upper=0).cumsum().resample("30T")
).ffill().fillna(0).diff(-1) * 2000
df = df.fillna(0)
if start is not None:
df = df.loc[start:]
if end is not None:
df = df.loc[:end]

return df

def load_consumption(self, start, end):
Expand Down Expand Up @@ -2245,8 +2256,10 @@ def _get_solar(self, start, end):

days = (pd.Timestamp.now(tz="UTC") - start).days + 1
df = self.hass2df(entity_id, days=days).astype(float).resample("30T").ffill()
df.index = pd.to_datetime(df.index)
df = -df.loc[dt[0] : dt[-1]].diff(-1).clip(upper=0).iloc[:-1] * 2000
if df is not None:
df.index = pd.to_datetime(df.index)
df = -df.loc[dt[0] : dt[-1]].diff(-1).clip(upper=0).iloc[:-1] * 2000

return df

def _check_tariffs_vs_bottlecap(self):
Expand Down
11 changes: 6 additions & 5 deletions apps/pv_opt/pvpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,12 @@ def to_df(self, start=None, end=None, **kwargs):
# if it is after 11 but we don't have new Agile prices yet, check for a day-ahead forecast
if self.day_ahead is None:
self.day_ahead = self.get_day_ahead(df.index[0])
self.day_ahead = self.day_ahead.sort_index()
self.log("")
self.log(
f"Retrieved day ahead forecast for period {self.day_ahead.index[0].strftime(TIME_FORMAT)} - {self.day_ahead.index[-1].strftime(TIME_FORMAT)} for tariff {self.name}"
)
if self.day_ahead is not None:
self.day_ahead = self.day_ahead.sort_index()
self.log("")
self.log(
f"Retrieved day ahead forecast for period {self.day_ahead.index[0].strftime(TIME_FORMAT)} - {self.day_ahead.index[-1].strftime(TIME_FORMAT)} for tariff {self.name}"
)

if self.day_ahead is not None:
mask = (self.day_ahead.index.hour >= 16) & (
Expand Down

0 comments on commit ba32cc0

Please sign in to comment.