Skip to content

Commit

Permalink
Updated consumption methodology
Browse files Browse the repository at this point in the history
  • Loading branch information
fboundy committed Apr 3, 2024
1 parent 5606ae0 commit 06eac09
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 56 deletions.
8 changes: 4 additions & 4 deletions apps/pv_opt/config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,10 @@ pv_opt:
# update_cycle_seconds: 15
# maximum_dod_percent: number.{device_name}_battery_minimum_soc

# id_consumption_today: sensor.{device_name}_consumption_today
# id_consumption:
# - sensor.{device_name}_house_load
# - sensor.{device_name}_bypass_load
id_consumption_today: sensor.{device_name}_consumption_today
id_consumption:
- sensor.{device_name}_house_load
- sensor.{device_name}_bypass_load

# id_grid_import_today: sensor.{device_name}_grid_import_today
# id_grid_export_today: sensor.{device_name}_grid_export_today
Expand Down
123 changes: 75 additions & 48 deletions apps/pv_opt/pv_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -1505,7 +1505,7 @@ def optimise(self):
self._status("ERROR: Baseline performance")
return

self.optimised_cost = {"Base": self.contract.net_cost(self.flows["Base"]).sum()}
self.optimised_cost = {"Base": self.contract.net_cost(self.flows["Base"])}

self.log("")
if self.get_config("use_solar", True):
Expand All @@ -1521,23 +1521,26 @@ def optimise(self):
)

cases = {
"No Export": {
"Optimised Charging": {
"export": False,
"discharge": False,
},
"Optimised Charge": {
"Optimised PV Export": {
"export": True,
"discharge": False,
},
"Optimised Discharge:" "export": True,
"discharge": True,
"Forced Discharge": {
"export": True,
"discharge": True,
},
}

if not self.get_config("use_export"):
self.selected_case = "No Export"
if not self.get_config("include_export"):
self.selected_case = "Optimised Charging"
elif not self.get_config("forced_discharge"):
self.selected_case = "Optimised Charge"
self.selected_case = "Optimised PV Export"
else:
self.selected_case = "Optimised Discharge"
self.selected_case = "Forced Discharge"

self._status("Optimising charge plan")

Expand All @@ -1547,17 +1550,21 @@ def optimise(self):
self.static,
self.contract,
solar="weighted",
export=case["export"],
discharge=case["discharge"],
export=cases[case]["export"],
discharge=cases[case]["discharge"],
log=(case == self.selected_case),
max_iters=MAX_ITERS,
)

self.optimised_cost[case] = self.contract.net_cost(self.flows[case]).sum()
self.optimised_cost[case] = self.contract.net_cost(self.flows[case])

self.log(f" {'Base cost:':30s}: {self.optimised_cost['Base']:6.2f}p")
self.ulog("Optimisation Summary")
self.log(f" {'Base cost:':40s} {self.optimised_cost['Base'].sum():6.1f}p")
for case in cases:
self.log(f" {f'Optimised cost ({case}):':30s}: {self.optimised_cost[case]:6.2f}p")
str_log = f" {f'Optimised cost ({case}):':40s} {self.optimised_cost[case].sum():6.1f}p"
if case == self.selected_case:
str_log += " <=== Current Setup"
self.log(str_log)

# self.opt = self.pv_system.optimised_force(
# self.initial_soc,
Expand All @@ -1569,13 +1576,15 @@ def optimise(self):
# )
# self.opt_cost = self.optimised_cost[selected_case]

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

self.log("")

self._create_windows()

self.log("")
self.log(
f"Plan time: {self.static.index[0].strftime('%d-%b %H:%M')} - {self.static.index[-1].strftime('%d-%b %H:%M')} Initial SOC: {self.initial_soc} Base Cost: {self.base_cost.sum():5.2f} Opt Cost: {self.optimised_cost[self.selected_case].sum():5.2f}"
f"Plan time: {self.static.index[0].strftime('%d-%b %H:%M')} - {self.static.index[-1].strftime('%d-%b %H:%M')} Initial SOC: {self.initial_soc} Base Cost: {self.optimised_cost['Base'].sum():5.1f} Opt Cost: {self.optimised_cost[self.selected_case].sum():5.1f}"
)
self.log("")
optimiser_elapsed = round((pd.Timestamp.now() - self.t0).total_seconds(), 1)
Expand Down Expand Up @@ -1993,7 +2002,7 @@ def _write_output(self):
self.write_cost(
"PV Opt Base Cost",
entity=f"sensor.{self.prefix}_base_cost",
cost=self.optimsed_cost["Base"],
cost=self.optimised_cost["Base"],
df=self.flows["Base"],
)

Expand Down Expand Up @@ -2125,15 +2134,13 @@ def _get_hass_power_from_daily_kwh(self, entity_id, start=None, end=None, days=N
log=log,
)

# self.log(df.to_string())
if df is not None:
df.index = pd.to_datetime(df.index)
df = (df.diff(-1).fillna(0).clip(upper=0).cumsum().resample("30min")).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]
x = df.diff().clip(0).fillna(0).cumsum() + df.iloc[0]
x.index = x.index.round("1s")
y = -pd.concat([x.resample("1s").interpolate().resample("30min").asfreq(), x.iloc[-1:]]).diff(-1)
dt = y.index.diff().total_seconds() / pd.Timedelta("60min").total_seconds() / 1000
df = y[1:-1] / dt[2:]

return df

Expand All @@ -2148,34 +2155,52 @@ def load_consumption(self, start, end):

index = pd.date_range(start, end, inclusive="left", freq="30min")
consumption = pd.DataFrame(index=index, data={"consumption": 0})
df = None

if self.get_config("use_consumption_history"):
entity_ids = []
entity_id = None

if self.get_config("use_consumption_history"):
days = int(self.get_config("consumption_history_days"))

# if not isinstance(entity_ids, list):
# entity_ids = [entity_ids]
if "id_consumption" in self.config:
entity_ids = self.config["id_consumption"]
if not isinstance(entity_ids, list):
entity_ids = [entity_ids]

entity_ids = [entity_id for entity_id in entity_ids if self.entity_exists(entity_id)]

if (
(len(entity_ids) == 0)
and ("id_consumption_today" in self.config)
and self.entity_exists(self.config["id_consumption_today"])
):
entity_id = self.config["id_consumption_today"]

# for entity_id in entity_ids:
if (start < time_now) and (end < time_now):
# consumption["consumption"] = self._get_hass_power_from_daily_kwh(
# entity_id,
# start=start,
# end=end,
# log=self.debug,
# )

consumption["consumption"] = 0
for entity_id in self.config["id_consumption"]:
for entity_id in entity_ids:
power = self.hass2df(entity_id=entity_id, days=days).loc[
start.floor("30min") : end.ceil("30min") + pd.Timedelta("30min")
]

power = self.riemann_avg(power)
consumption["consumption"] += power
if power is not None:
power = self.riemann_avg(power)
consumption["consumption"] += power

if consumption["consumption"].sum() == 0:
consumption["consumption"] = self._get_hass_power_from_daily_kwh(
entity_id,
start=start,
end=end,
log=self.debug,
)

if consumption["consumption"].sum() == 0:
self._status("ERROR: No consumption history.")
return

else:
df = None
for entity_id in self.config["id_consumption"]:
for entity_id in entity_ids:
power = self.hass2df(entity_id=entity_id, days=days)

power = self.riemann_avg(power)
Expand All @@ -2184,12 +2209,13 @@ def load_consumption(self, start, end):
else:
df += power

# entity_id = self.config["id_consumption_today"]
# df = self._get_hass_power_from_daily_kwh(
# entity_id,
# days=days,
# log=self.debug,
# )
if df is None:
self.log("Getting consumpo")
df = self._get_hass_power_from_daily_kwh(
entity_id,
days=days,
log=self.debug,
)

if df is None:
self._status("ERROR: No consumption history.")
Expand Down Expand Up @@ -2362,9 +2388,10 @@ def _compare_tariffs(self):
static,
contract,
solar="solar",
discharge=self.get_config("forced_discharge"),
export=True,
discharge=True,
max_iters=MAX_ITERS,
log=self.debug,
log=False,
)

# opt["period_start"] = opt.index.tz_convert(self.tz).strftime("%Y-%m-%dT%H:%M:%S%z").str[:-2] + ":00"
Expand Down
2 changes: 1 addition & 1 deletion apps/pv_opt/pvpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ def get_day_ahead(self, start):
else:
if len(i["Name"]) > 8:
try:
self.log(time, i["Name"], i["Value"])
# self.log(time, i["Name"], i["Value"])
data.append(float(i["Value"].replace(",", ".")))
index.append(
pd.Timestamp(
Expand Down
6 changes: 5 additions & 1 deletion apps/pv_opt/solax.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ def control_charge(self, enable, **kwargs):
end = kwargs.get("end", time_now).ceil("30min").strftime(TIMEFORMAT)
self.host.set_select("charge_start_time_1", start)
self.host.set_select("charge_end_time_1", end)
self.host.set_select("charge_start_time_2", start)
self.host.set_select("charge_end_time_2", end)

power = self.kwargs.get("power")
if power is not None:
Expand Down Expand Up @@ -140,10 +142,12 @@ def control_charge(self, enable, **kwargs):
else:
self.host.set_select("use_mode", "Self Use Mode")
time_now = pd.Timestamp.now(tz=self.tz)
start = kwargs.get("start", time_now).floor("15min").strftime(TIMEFORMAT)
start = kwargs.get("start", time_now).normalize().strftime(TIMEFORMAT)
end = start
self.host.set_select("charge_start_time_1", start)
self.host.set_select("charge_end_time_1", end)
self.host.set_select("charge_start_time_2", start)
self.host.set_select("charge_end_time_2", end)

else:
self._unknown_inverter()
Expand Down
4 changes: 2 additions & 2 deletions apps/pv_opt/solis.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@
"id_battery_soc": "sensor.{device_name}_battery_soc",
"id_consumption_today": "sensor.{device_name}_house_load_today",
"id_consumption": [
"sensor.{device_name}_house_load",
"sensor.{device_name}_bypass_load",
"sensor.{device_name}_house_load_x",
"sensor.{device_name}_bypass_load_x",
],
"id_grid_import_today": "sensor.{device_name}_grid_import_today",
"id_grid_export_today": "sensor.{device_name}_grid_export_today",
Expand Down

0 comments on commit 06eac09

Please sign in to comment.