Skip to content

Commit

Permalink
Added missing support for predicted temp publish, prepared new release
Browse files Browse the repository at this point in the history
  • Loading branch information
davidusb-geek committed Jul 12, 2024
1 parent ddf0a96 commit 51c0a29
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 14 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 0.10.5 - 2024-07-12
### Improvement
- Added support for pubishing thermal load data, namely the predicted room temperature

## 0.10.4 - 2024-07-10
### Improvement
- Added a new thermal modeling, see the new section in the documentation for help to implement this of model for thermal deferrable loads
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
author = 'David HERNANDEZ'

# The full version, including alpha/beta/rc tags
release = '0.10.4'
release = '0.10.5'

# -- General configuration ---------------------------------------------------

Expand Down
146 changes: 146 additions & 0 deletions scripts/script_simple_thermal_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# -*- coding: utf-8 -*-
import pickle
import random
import numpy as np
import pandas as pd
import pathlib
import plotly.express as px
import plotly.subplots as sp
import plotly.io as pio
pio.renderers.default = 'browser'
pd.options.plotting.backend = "plotly"

from emhass.retrieve_hass import RetrieveHass
from emhass.optimization import Optimization
from emhass.forecast import Forecast
from emhass.utils import get_root, get_yaml_parse, get_days_list, get_logger

# the root folder
root = str(get_root(__file__, num_parent=2))
emhass_conf = {}
emhass_conf['config_path'] = pathlib.Path(root) / 'config_emhass.yaml'
emhass_conf['data_path'] = pathlib.Path(root) / 'data/'
emhass_conf['root_path'] = pathlib.Path(root)

# create logger
logger, ch = get_logger(__name__, emhass_conf, save_to_file=False)

if __name__ == '__main__':
get_data_from_file = True
params = None
show_figures = True
template = 'presentation'

retrieve_hass_conf, optim_conf, plant_conf = get_yaml_parse(emhass_conf, use_secrets=False)
retrieve_hass_conf, optim_conf, plant_conf = \
retrieve_hass_conf, optim_conf, plant_conf
rh = RetrieveHass(retrieve_hass_conf['hass_url'], retrieve_hass_conf['long_lived_token'],
retrieve_hass_conf['freq'], retrieve_hass_conf['time_zone'],
params, emhass_conf, logger)
if get_data_from_file:
with open(emhass_conf['data_path'] / 'test_df_final.pkl', 'rb') as inp:
rh.df_final, days_list, var_list = pickle.load(inp)
retrieve_hass_conf['var_load'] = str(var_list[0])
retrieve_hass_conf['var_PV'] = str(var_list[1])
retrieve_hass_conf['var_interp'] = [retrieve_hass_conf['var_PV'], retrieve_hass_conf['var_load']]
retrieve_hass_conf['var_replace_zero'] = [retrieve_hass_conf['var_PV']]
else:
days_list = get_days_list(retrieve_hass_conf['days_to_retrieve'])
var_list = [retrieve_hass_conf['var_load'], retrieve_hass_conf['var_PV']]
rh.get_data(days_list, var_list,
minimal_response=False, significant_changes_only=False)
rh.prepare_data(retrieve_hass_conf['var_load'], load_negative = retrieve_hass_conf['load_negative'],
set_zero_min = retrieve_hass_conf['set_zero_min'],
var_replace_zero = retrieve_hass_conf['var_replace_zero'],
var_interp = retrieve_hass_conf['var_interp'])
df_input_data = rh.df_final.copy()

fcst = Forecast(retrieve_hass_conf, optim_conf, plant_conf,
params, emhass_conf, logger, get_data_from_file=get_data_from_file)
df_weather = fcst.get_weather_forecast(method='csv')
P_PV_forecast = fcst.get_power_from_weather(df_weather)
P_load_forecast = fcst.get_load_forecast(method=optim_conf['load_forecast_method'])
df_input_data = pd.concat([P_PV_forecast, P_load_forecast], axis=1)
df_input_data.columns = ['P_PV_forecast', 'P_load_forecast']

df_input_data = fcst.get_load_cost_forecast(df_input_data)
df_input_data = fcst.get_prod_price_forecast(df_input_data)
input_data_dict = {'retrieve_hass_conf': retrieve_hass_conf}

# Set special debug cases

# Solver configurations
optim_conf.update({'lp_solver': 'PULP_CBC_CMD'}) # set the name of the linear programming solver that will be used. Options are 'PULP_CBC_CMD', 'GLPK_CMD' and 'COIN_CMD'.
optim_conf.update({'lp_solver_path': 'empty'}) # set the path to the LP solver, COIN_CMD default is /usr/bin/cbc

# Config for a single thermal model
optim_conf.update({'num_def_loads': 1})
optim_conf.update({'P_deferrable_nom': [1000.0]})
optim_conf.update({'def_total_hours': [0]})
optim_conf.update({'def_start_timestep': [0]})
optim_conf.update({'def_end_timestep': [0]})
optim_conf.update({'treat_def_as_semi_cont': [False]})
optim_conf.update({'set_def_constant': [False]})
optim_conf.update({'def_start_penalty': [0.0]})

# Thermal modeling
df_input_data['outdoor_temperature_forecast'] = [random.normalvariate(10.0, 3.0) for _ in range(48)]

runtimeparams = {
'def_load_config': [
{'thermal_config': {
'heating_rate': 5.0,
'cooling_constant': 0.1,
'overshoot_temperature': 24.0,
'start_temperature': 20,
'desired_temperatures': [21]*48,
}
}
]
}
if 'def_load_config' in runtimeparams:
optim_conf["def_load_config"] = runtimeparams['def_load_config']

costfun = 'profit'
opt = Optimization(retrieve_hass_conf, optim_conf, plant_conf,
fcst.var_load_cost, fcst.var_prod_price,
costfun, emhass_conf, logger)
P_PV_forecast.loc[:] = 0
P_load_forecast.loc[:] = 0

df_input_data.loc[df_input_data.index[25:30],'unit_load_cost'] = 2.0 # A price peak
unit_load_cost = df_input_data[opt.var_load_cost].values # €/kWh
unit_prod_price = df_input_data[opt.var_prod_price].values # €/kWh


opt_res_dayahead = opt.perform_optimization(df_input_data, P_PV_forecast.values.ravel(),
P_load_forecast.values.ravel(),
unit_load_cost, unit_prod_price, debug=True)

# Let's plot the input data
fig_inputs_dah = df_input_data.plot()
fig_inputs_dah.layout.template = template
fig_inputs_dah.update_yaxes(title_text = "Powers (W) and Costs(EUR)")
fig_inputs_dah.update_xaxes(title_text = "Time")
if show_figures:
fig_inputs_dah.show()

vars_to_plot = ['P_deferrable0', 'unit_load_cost', 'predicted_temp_heater0', 'target_temp_heater0', 'P_def_start_0']
if plant_conf['inverter_is_hybrid']:
vars_to_plot = vars_to_plot + ['P_hybrid_inverter']
if plant_conf['compute_curtailment']:
vars_to_plot = vars_to_plot + ['P_PV_curtailment']
if optim_conf['set_use_battery']:
vars_to_plot = vars_to_plot + ['P_batt'] + ['SOC_opt']
fig_res_dah = opt_res_dayahead[vars_to_plot].plot() # 'P_def_start_0', 'P_def_start_1', 'P_def_bin2_0', 'P_def_bin2_1'
fig_res_dah.layout.template = template
fig_res_dah.update_yaxes(title_text = "Powers (W)")
fig_res_dah.update_xaxes(title_text = "Time")
if show_figures:
fig_res_dah.show()

print("System with: PV, two deferrable loads, dayahead optimization, profit >> total cost function sum: "+\
str(opt_res_dayahead['cost_profit'].sum())+", Status: "+opt_res_dayahead['optim_status'].unique().item())

print(opt_res_dayahead[vars_to_plot])

2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

setup(
name='emhass', # Required
version='0.10.4', # Required
version='0.10.5', # Required
description='An Energy Management System for Home Assistant', # Optional
long_description=long_description, # Optional
long_description_content_type='text/markdown', # Optional (see note above)
Expand Down
44 changes: 32 additions & 12 deletions src/emhass/command_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,25 @@ def publish_data(input_data_dict: dict, logger: logging.Logger,
dont_post=dont_post
)
cols_published = cols_published + ["P_deferrable{}".format(k)]
# Publish thermal model data (predicted temperature)
custom_predicted_temperature_id = params["passed_data"][
"custom_predicted_temperature_id"
]
for k in range(input_data_dict["opt"].optim_conf["num_def_loads"]):
if "def_load_config" in input_data_dict["opt"].optim_conf.keys():
if "thermal_config" in input_data_dict["opt"].optim_conf["def_load_config"][k]:
input_data_dict["rh"].post_data(
opt_res_latest["P_deferrable{}".format(k)],
idx_closest,
custom_predicted_temperature_id[k]["entity_id"],
custom_predicted_temperature_id[k]["unit_of_measurement"],
custom_predicted_temperature_id[k]["friendly_name"],
type_var="temperature",
publish_prefix=publish_prefix,
save_entities=entity_save,
dont_post=dont_post
)
cols_published = cols_published + ["predicted_temp_heater{}".format(k)]
# Publish battery power
if input_data_dict["opt"].optim_conf["set_use_battery"]:
if "P_batt" not in opt_res_latest.columns:
Expand Down Expand Up @@ -967,18 +986,19 @@ def publish_data(input_data_dict: dict, logger: logging.Logger,
logger.warning(
"no optim_status in opt_res_latest, run an optimization task first",
)
input_data_dict["rh"].post_data(
opt_res_latest["optim_status"],
idx_closest,
custom_cost_fun_id["entity_id"],
custom_cost_fun_id["unit_of_measurement"],
custom_cost_fun_id["friendly_name"],
type_var="optim_status",
publish_prefix=publish_prefix,
save_entities=entity_save,
dont_post=dont_post
)
cols_published = cols_published + ["optim_status"]
else:
input_data_dict["rh"].post_data(
opt_res_latest["optim_status"],
idx_closest,
custom_cost_fun_id["entity_id"],
custom_cost_fun_id["unit_of_measurement"],
custom_cost_fun_id["friendly_name"],
type_var="optim_status",
publish_prefix=publish_prefix,
save_entities=entity_save,
dont_post=dont_post
)
cols_published = cols_published + ["optim_status"]
# Publish unit_load_cost
custom_unit_load_cost_id = params["passed_data"]["custom_unit_load_cost_id"]
input_data_dict["rh"].post_data(
Expand Down
3 changes: 3 additions & 0 deletions src/emhass/retrieve_hass.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,9 @@ def post_data(self, data_df: pd.DataFrame, idx: int, entity_id: str, unit_of_mea
elif type_var == "deferrable":
data = RetrieveHass.get_attr_data_dict(data_df, idx, entity_id, unit_of_measurement,
friendly_name, "deferrables_schedule", state)
elif type_var == "temperature":
data = RetrieveHass.get_attr_data_dict(data_df, idx, entity_id, unit_of_measurement,
friendly_name, "predicted_temperatures", state)
elif type_var == "batt":
data = RetrieveHass.get_attr_data_dict(data_df, idx, entity_id, unit_of_measurement,
friendly_name, "battery_scheduled_power", state)
Expand Down
13 changes: 13 additions & 0 deletions src/emhass/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ def treat_runtimeparams(runtimeparams: str, params: str, retrieve_hass_conf: dic
params = {}
# Some default data needed
custom_deferrable_forecast_id = []
custom_predicted_temperature_id = []
for k in range(optim_conf["num_def_loads"]):
custom_deferrable_forecast_id.append(
{
Expand All @@ -151,6 +152,13 @@ def treat_runtimeparams(runtimeparams: str, params: str, retrieve_hass_conf: dic
"friendly_name": "Deferrable Load {}".format(k),
}
)
custom_predicted_temperature_id.append(
{
"entity_id": "sensor.temp_predicted{}".format(k),
"unit_of_measurement": "°C",
"friendly_name": "Predicted temperature {}".format(k),
}
)
default_passed_dict = {
"custom_pv_forecast_id": {
"entity_id": "sensor.p_pv_forecast",
Expand Down Expand Up @@ -208,6 +216,7 @@ def treat_runtimeparams(runtimeparams: str, params: str, retrieve_hass_conf: dic
"friendly_name": "Unit Prod Price",
},
"custom_deferrable_forecast_id": custom_deferrable_forecast_id,
"custom_predicted_temperature_id": custom_predicted_temperature_id,
"publish_prefix": "",
}
if "passed_data" in params.keys():
Expand Down Expand Up @@ -521,6 +530,10 @@ def treat_runtimeparams(runtimeparams: str, params: str, retrieve_hass_conf: dic
params["passed_data"]["custom_deferrable_forecast_id"] = runtimeparams[
"custom_deferrable_forecast_id"
]
if "custom_predicted_temperature_id" in runtimeparams.keys():
params["passed_data"]["custom_predicted_temperature_id"] = runtimeparams[
"custom_predicted_temperature_id"
]
# A condition to put a prefix on all published data, or check for saved data under prefix name
if "publish_prefix" not in runtimeparams.keys():
publish_prefix = ""
Expand Down

0 comments on commit 51c0a29

Please sign in to comment.