emhass 0.11.4__py3-none-any.whl → 0.12.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- emhass/command_line.py +49 -22
- emhass/data/associations.csv +1 -0
- emhass/data/config_defaults.json +1 -0
- emhass/forecast.py +98 -4
- emhass/optimization.py +1 -1
- emhass/retrieve_hass.py +17 -3
- emhass/static/configuration_list.html +4 -0
- emhass/static/configuration_script.js +15 -2
- emhass/static/data/param_definitions.json +8 -1
- emhass/templates/configuration.html +1 -1
- emhass/templates/index.html +1 -1
- emhass/utils.py +72 -4
- {emhass-0.11.4.dist-info → emhass-0.12.1.dist-info}/METADATA +8 -6
- emhass-0.12.1.dist-info/RECORD +32 -0
- emhass-0.11.4.dist-info/RECORD +0 -32
- {emhass-0.11.4.dist-info → emhass-0.12.1.dist-info}/LICENSE +0 -0
- {emhass-0.11.4.dist-info → emhass-0.12.1.dist-info}/WHEEL +0 -0
- {emhass-0.11.4.dist-info → emhass-0.12.1.dist-info}/entry_points.txt +0 -0
- {emhass-0.11.4.dist-info → emhass-0.12.1.dist-info}/top_level.txt +0 -0
emhass/command_line.py
CHANGED
@@ -81,7 +81,8 @@ def set_input_data_dict(
|
|
81
81
|
logger,
|
82
82
|
emhass_conf,
|
83
83
|
)
|
84
|
-
|
84
|
+
|
85
|
+
# Define the data retrieve object
|
85
86
|
rh = RetrieveHass(
|
86
87
|
retrieve_hass_conf["hass_url"],
|
87
88
|
retrieve_hass_conf["long_lived_token"],
|
@@ -92,6 +93,23 @@ def set_input_data_dict(
|
|
92
93
|
logger,
|
93
94
|
get_data_from_file=get_data_from_file,
|
94
95
|
)
|
96
|
+
|
97
|
+
# Retrieve basic configuration data from hass
|
98
|
+
if get_data_from_file:
|
99
|
+
with open(emhass_conf["data_path"] / "test_df_final.pkl", "rb") as inp:
|
100
|
+
_, _, _, rh.ha_config = pickle.load(inp)
|
101
|
+
else:
|
102
|
+
response = rh.get_ha_config()
|
103
|
+
if type(response) is bool:
|
104
|
+
return False
|
105
|
+
|
106
|
+
# Update the params dict using data from the HA configuration
|
107
|
+
params = utils.update_params_with_ha_config(
|
108
|
+
params,
|
109
|
+
rh.ha_config,
|
110
|
+
)
|
111
|
+
|
112
|
+
# Define the forecast and optimization objects
|
95
113
|
fcst = Forecast(
|
96
114
|
retrieve_hass_conf,
|
97
115
|
optim_conf,
|
@@ -111,12 +129,13 @@ def set_input_data_dict(
|
|
111
129
|
emhass_conf,
|
112
130
|
logger,
|
113
131
|
)
|
132
|
+
|
114
133
|
# Perform setup based on type of action
|
115
134
|
if set_type == "perfect-optim":
|
116
135
|
# Retrieve data from hass
|
117
136
|
if get_data_from_file:
|
118
137
|
with open(emhass_conf["data_path"] / "test_df_final.pkl", "rb") as inp:
|
119
|
-
rh.df_final, days_list, var_list = pickle.load(inp)
|
138
|
+
rh.df_final, days_list, var_list, rh.ha_config = pickle.load(inp)
|
120
139
|
retrieve_hass_conf["sensor_power_load_no_var_loads"] = str(var_list[0])
|
121
140
|
retrieve_hass_conf["sensor_power_photovoltaics"] = str(var_list[1])
|
122
141
|
retrieve_hass_conf["sensor_linear_interp"] = [
|
@@ -154,12 +173,18 @@ def set_input_data_dict(
|
|
154
173
|
P_PV_forecast, P_load_forecast, df_input_data_dayahead = None, None, None
|
155
174
|
elif set_type == "dayahead-optim":
|
156
175
|
# Get PV and load forecasts
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
176
|
+
if (
|
177
|
+
optim_conf["set_use_pv"]
|
178
|
+
or optim_conf.get("weather_forecast_method", None) == "list"
|
179
|
+
):
|
180
|
+
df_weather = fcst.get_weather_forecast(
|
181
|
+
method=optim_conf["weather_forecast_method"]
|
182
|
+
)
|
183
|
+
if isinstance(df_weather, bool) and not df_weather:
|
184
|
+
return False
|
185
|
+
P_PV_forecast = fcst.get_power_from_weather(df_weather)
|
186
|
+
else:
|
187
|
+
P_PV_forecast = pd.Series(0, index=fcst.forecast_dates)
|
163
188
|
P_load_forecast = fcst.get_load_forecast(
|
164
189
|
method=optim_conf["load_forecast_method"]
|
165
190
|
)
|
@@ -208,7 +233,7 @@ def set_input_data_dict(
|
|
208
233
|
# Retrieve data from hass
|
209
234
|
if get_data_from_file:
|
210
235
|
with open(emhass_conf["data_path"] / "test_df_final.pkl", "rb") as inp:
|
211
|
-
rh.df_final, days_list, var_list = pickle.load(inp)
|
236
|
+
rh.df_final, days_list, var_list, rh.ha_config = pickle.load(inp)
|
212
237
|
retrieve_hass_conf["sensor_power_load_no_var_loads"] = str(var_list[0])
|
213
238
|
retrieve_hass_conf["sensor_power_photovoltaics"] = str(var_list[1])
|
214
239
|
retrieve_hass_conf["sensor_linear_interp"] = [
|
@@ -241,14 +266,20 @@ def set_input_data_dict(
|
|
241
266
|
return False
|
242
267
|
df_input_data = rh.df_final.copy()
|
243
268
|
# Get PV and load forecasts
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
269
|
+
if (
|
270
|
+
optim_conf["set_use_pv"]
|
271
|
+
or optim_conf.get("weather_forecast_method", None) == "list"
|
272
|
+
):
|
273
|
+
df_weather = fcst.get_weather_forecast(
|
274
|
+
method=optim_conf["weather_forecast_method"]
|
275
|
+
)
|
276
|
+
if isinstance(df_weather, bool) and not df_weather:
|
277
|
+
return False
|
278
|
+
P_PV_forecast = fcst.get_power_from_weather(
|
279
|
+
df_weather, set_mix_forecast=True, df_now=df_input_data
|
280
|
+
)
|
281
|
+
else:
|
282
|
+
P_PV_forecast = pd.Series(0, index=fcst.forecast_dates)
|
252
283
|
P_load_forecast = fcst.get_load_forecast(
|
253
284
|
method=optim_conf["load_forecast_method"],
|
254
285
|
set_mix_forecast=True,
|
@@ -403,10 +434,8 @@ def weather_forecast_cache(
|
|
403
434
|
:rtype: bool
|
404
435
|
|
405
436
|
"""
|
406
|
-
|
407
437
|
# Parsing yaml
|
408
438
|
retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(params, logger)
|
409
|
-
|
410
439
|
# Treat runtimeparams
|
411
440
|
params, retrieve_hass_conf, optim_conf, plant_conf = utils.treat_runtimeparams(
|
412
441
|
runtimeparams,
|
@@ -417,8 +446,8 @@ def weather_forecast_cache(
|
|
417
446
|
"forecast",
|
418
447
|
logger,
|
419
448
|
emhass_conf,
|
449
|
+
{},
|
420
450
|
)
|
421
|
-
|
422
451
|
# Make sure weather_forecast_cache is true
|
423
452
|
if (params != None) and (params != "null"):
|
424
453
|
params = json.loads(params)
|
@@ -426,12 +455,10 @@ def weather_forecast_cache(
|
|
426
455
|
params = {}
|
427
456
|
params["passed_data"]["weather_forecast_cache"] = True
|
428
457
|
params = json.dumps(params)
|
429
|
-
|
430
458
|
# Create Forecast object
|
431
459
|
fcst = Forecast(
|
432
460
|
retrieve_hass_conf, optim_conf, plant_conf, params, emhass_conf, logger
|
433
461
|
)
|
434
|
-
|
435
462
|
result = fcst.get_weather_forecast(optim_conf["weather_forecast_method"])
|
436
463
|
if isinstance(result, bool) and not result:
|
437
464
|
return False
|
emhass/data/associations.csv
CHANGED
@@ -15,6 +15,7 @@ params_secrets,lon,Longitude
|
|
15
15
|
params_secrets,alt,Altitude
|
16
16
|
optim_conf,costfun,costfun
|
17
17
|
optim_conf,logging_level,logging_level
|
18
|
+
optim_conf,set_use_pv,set_use_pv
|
18
19
|
optim_conf,set_use_battery,set_use_battery
|
19
20
|
optim_conf,num_def_loads,number_of_deferrable_loads
|
20
21
|
optim_conf,P_deferrable_nom,nominal_power_of_deferrable_loads,list_nominal_power_of_deferrable_loads
|
emhass/data/config_defaults.json
CHANGED
emhass/forecast.py
CHANGED
@@ -886,10 +886,73 @@ class Forecast(object):
|
|
886
886
|
forecast_out = pd.concat([forecast_out, forecast_tp], axis=0)
|
887
887
|
return forecast_out
|
888
888
|
|
889
|
+
@staticmethod
|
890
|
+
def resample_data(data, freq, current_freq):
|
891
|
+
r"""
|
892
|
+
Resample a DataFrame with a custom frequency.
|
893
|
+
|
894
|
+
:param data: Original time series data with a DateTimeIndex.
|
895
|
+
:type data: pd.DataFrame
|
896
|
+
:param freq: Desired frequency for resampling (e.g., pd.Timedelta("10min")).
|
897
|
+
:type freq: pd.Timedelta
|
898
|
+
:return: Resampled data at the specified frequency.
|
899
|
+
:rtype: pd.DataFrame
|
900
|
+
"""
|
901
|
+
if freq > current_freq:
|
902
|
+
# Downsampling
|
903
|
+
# Use 'mean' to aggregate or choose other options ('sum', 'max', etc.)
|
904
|
+
resampled_data = data.resample(freq).mean()
|
905
|
+
elif freq < current_freq:
|
906
|
+
# Upsampling
|
907
|
+
# Use 'asfreq' to create empty slots, then interpolate
|
908
|
+
resampled_data = data.resample(freq).asfreq()
|
909
|
+
resampled_data = resampled_data.interpolate(method='time')
|
910
|
+
else:
|
911
|
+
# No resampling needed
|
912
|
+
resampled_data = data.copy()
|
913
|
+
return resampled_data
|
914
|
+
|
915
|
+
@staticmethod
|
916
|
+
def get_typical_load_forecast(data, forecast_date):
|
917
|
+
r"""
|
918
|
+
Forecast the load profile for the next day based on historic data.
|
919
|
+
|
920
|
+
:param data: A DataFrame with a DateTimeIndex containing the historic load data.
|
921
|
+
Must include a 'load' column.
|
922
|
+
:type data: pd.DataFrame
|
923
|
+
:param forecast_date: The date for which the forecast will be generated.
|
924
|
+
:type forecast_date: pd.Timestamp
|
925
|
+
:return: A Series with the forecasted load profile for the next day and a list of days used
|
926
|
+
to calculate the forecast.
|
927
|
+
:rtype: tuple (pd.Series, list)
|
928
|
+
"""
|
929
|
+
# Ensure the 'load' column exists
|
930
|
+
if 'load' not in data.columns:
|
931
|
+
raise ValueError("Data must have a 'load' column.")
|
932
|
+
# Filter historic data for the same month and day of the week
|
933
|
+
month = forecast_date.month
|
934
|
+
day_of_week = forecast_date.dayofweek
|
935
|
+
historic_data = data[(data.index.month == month) & (data.index.dayofweek == day_of_week)]
|
936
|
+
used_days = np.unique(historic_data.index.date)
|
937
|
+
# Align all historic data to the forecast day
|
938
|
+
aligned_data = []
|
939
|
+
for day in used_days:
|
940
|
+
daily_data = data[data.index.date == pd.Timestamp(day).date()]
|
941
|
+
aligned_daily_data = daily_data.copy()
|
942
|
+
aligned_daily_data.index = aligned_daily_data.index.map(
|
943
|
+
lambda x: x.replace(year=forecast_date.year, month=forecast_date.month, day=forecast_date.day)
|
944
|
+
)
|
945
|
+
aligned_data.append(aligned_daily_data)
|
946
|
+
# Combine all aligned historic data into a single DataFrame
|
947
|
+
combined_data = pd.concat(aligned_data)
|
948
|
+
# Compute the mean load for each timestamp
|
949
|
+
forecast = combined_data.groupby(combined_data.index).mean()
|
950
|
+
return forecast, used_days
|
951
|
+
|
889
952
|
def get_load_forecast(
|
890
953
|
self,
|
891
954
|
days_min_load_forecast: Optional[int] = 3,
|
892
|
-
method: Optional[str] = "
|
955
|
+
method: Optional[str] = "typical",
|
893
956
|
csv_path: Optional[str] = "data_load_forecast.csv",
|
894
957
|
set_mix_forecast: Optional[bool] = False,
|
895
958
|
df_now: Optional[pd.DataFrame] = pd.DataFrame(),
|
@@ -904,10 +967,11 @@ class Forecast(object):
|
|
904
967
|
will be used to generate a naive forecast, defaults to 3
|
905
968
|
:type days_min_load_forecast: int, optional
|
906
969
|
:param method: The method to be used to generate load forecast, the options \
|
970
|
+
are 'typical' for a typical household load consumption curve, \
|
907
971
|
are 'naive' for a persistance model, 'mlforecaster' for using a custom \
|
908
972
|
previously fitted machine learning model, 'csv' to read the forecast from \
|
909
973
|
a CSV file and 'list' to use data directly passed at runtime as a list of \
|
910
|
-
values. Defaults to '
|
974
|
+
values. Defaults to 'typical'.
|
911
975
|
:type method: str, optional
|
912
976
|
:param csv_path: The path to the CSV file used when method = 'csv', \
|
913
977
|
defaults to "/data/data_load_forecast.csv"
|
@@ -956,7 +1020,7 @@ class Forecast(object):
|
|
956
1020
|
if self.get_data_from_file:
|
957
1021
|
filename_path = self.emhass_conf["data_path"] / "test_df_final.pkl"
|
958
1022
|
with open(filename_path, "rb") as inp:
|
959
|
-
rh.df_final, days_list, var_list = pickle.load(inp)
|
1023
|
+
rh.df_final, days_list, var_list, rh.ha_config = pickle.load(inp)
|
960
1024
|
self.var_load = var_list[0]
|
961
1025
|
self.retrieve_hass_conf["sensor_power_load_no_var_loads"] = (
|
962
1026
|
self.var_load
|
@@ -977,7 +1041,37 @@ class Forecast(object):
|
|
977
1041
|
):
|
978
1042
|
return False
|
979
1043
|
df = rh.df_final.copy()[[self.var_load_new]]
|
980
|
-
if method == "
|
1044
|
+
if method == "typical": # using typical statistical data from a household power consumption
|
1045
|
+
# Loading data from history file
|
1046
|
+
model_type = "load_clustering"
|
1047
|
+
data_path = self.emhass_conf["data_path"] / str("data_train_" + model_type + ".pkl")
|
1048
|
+
with open(data_path, "rb") as fid:
|
1049
|
+
data, _ = pickle.load(fid)
|
1050
|
+
# Resample the data if needed
|
1051
|
+
current_freq = pd.Timedelta('30min')
|
1052
|
+
if self.freq != current_freq:
|
1053
|
+
data = Forecast.resample_data(data, self.freq, current_freq)
|
1054
|
+
# Generate forecast
|
1055
|
+
data_list = []
|
1056
|
+
dates_list = np.unique(self.forecast_dates.date).tolist()
|
1057
|
+
forecast = pd.DataFrame()
|
1058
|
+
for date in dates_list:
|
1059
|
+
forecast_date = pd.Timestamp(date)
|
1060
|
+
data.columns = ['load']
|
1061
|
+
forecast_tmp, used_days = Forecast.get_typical_load_forecast(data, forecast_date)
|
1062
|
+
self.logger.debug(f"Using {len(used_days)} days of data to generate the forecast.")
|
1063
|
+
# Normalize the forecast
|
1064
|
+
forecast_tmp = forecast_tmp*self.plant_conf['maximum_power_from_grid']/9000
|
1065
|
+
data_list.extend(forecast_tmp.values.ravel().tolist())
|
1066
|
+
if len(forecast) == 0:
|
1067
|
+
forecast = forecast_tmp
|
1068
|
+
else:
|
1069
|
+
forecast = pd.concat([forecast, forecast_tmp], axis=0)
|
1070
|
+
forecast.index = forecast.index.tz_convert(self.time_zone)
|
1071
|
+
forecast_out = forecast.loc[forecast.index.intersection(self.forecast_dates)]
|
1072
|
+
forecast_out.index.name = 'ts'
|
1073
|
+
forecast_out = forecast_out.rename(columns={'load': 'yhat'})
|
1074
|
+
elif method == "naive": # using a naive approach
|
981
1075
|
mask_forecast_out = (
|
982
1076
|
df.index > days_list[-1] - self.optim_conf["delta_forecast_daily"]
|
983
1077
|
)
|
emhass/optimization.py
CHANGED
emhass/retrieve_hass.py
CHANGED
@@ -90,9 +90,23 @@ class RetrieveHass:
|
|
90
90
|
"Authorization": "Bearer " + self.long_lived_token,
|
91
91
|
"content-type": "application/json",
|
92
92
|
}
|
93
|
-
|
94
|
-
|
95
|
-
|
93
|
+
if self.hass_url == "http://supervisor/core/api":
|
94
|
+
url = self.hass_url + "/config"
|
95
|
+
else:
|
96
|
+
url = self.hass_url + "api/config"
|
97
|
+
|
98
|
+
try:
|
99
|
+
response_config = get(url, headers=headers)
|
100
|
+
except Exception:
|
101
|
+
self.logger.error("Unable to access Home Assistance instance, check URL")
|
102
|
+
self.logger.error("If using addon, try setting url and token to 'empty'")
|
103
|
+
return False
|
104
|
+
|
105
|
+
try:
|
106
|
+
self.ha_config = response_config.json()
|
107
|
+
except Exception:
|
108
|
+
self.logger.error("EMHASS was unable to obtain configuration data from HA")
|
109
|
+
return False
|
96
110
|
|
97
111
|
def get_data(
|
98
112
|
self,
|
@@ -29,6 +29,10 @@
|
|
29
29
|
<div id="Solar System (PV)" class="section-card">
|
30
30
|
<div class="section-card-header">
|
31
31
|
<h4>Solar System (PV)</h4>
|
32
|
+
<label class="switch"> <!-- switch connected to set_use_pv -->
|
33
|
+
<input id="set_use_pv" type="checkbox">
|
34
|
+
<span class="slider"></span>
|
35
|
+
</label>
|
32
36
|
</div>
|
33
37
|
<div class="section-body"> </div> <!-- parameters will get generated here -->
|
34
38
|
</div>
|
@@ -121,7 +121,7 @@ function loadConfigurationListView(param_definitions, config, list_html) {
|
|
121
121
|
}
|
122
122
|
|
123
123
|
//list parameters used in the section headers
|
124
|
-
header_input_list = ["set_use_battery", "number_of_deferrable_loads"];
|
124
|
+
header_input_list = ["set_use_battery", "set_use_pv", "number_of_deferrable_loads"];
|
125
125
|
|
126
126
|
//get the main container and append list template html
|
127
127
|
document.getElementById("configuration-container").innerHTML = list_html;
|
@@ -265,7 +265,7 @@ function buildParamContainers(
|
|
265
265
|
});
|
266
266
|
|
267
267
|
//check initial checkbox state, check "value" of input and match to "checked" value
|
268
|
-
let checkbox =
|
268
|
+
let checkbox = SectionContainer.querySelectorAll("input[type='checkbox']");
|
269
269
|
checkbox.forEach(function (answer) {
|
270
270
|
let value = answer.value === "true";
|
271
271
|
answer.checked = value;
|
@@ -559,6 +559,19 @@ function headerElement(element, param_definitions, config) {
|
|
559
559
|
}
|
560
560
|
break;
|
561
561
|
|
562
|
+
//if set_use_pv, add or remove PV section (inc. related params)
|
563
|
+
case "set_use_pv":
|
564
|
+
if (element.checked) {
|
565
|
+
param_container.innerHTML = "";
|
566
|
+
buildParamContainers("Solar System (PV)", param_definitions["Solar System (PV)"], config, [
|
567
|
+
"set_use_pv",
|
568
|
+
]);
|
569
|
+
element.checked = true;
|
570
|
+
} else {
|
571
|
+
param_container.innerHTML = "";
|
572
|
+
}
|
573
|
+
break;
|
574
|
+
|
562
575
|
//if number_of_deferrable_loads, the number of inputs in the "Deferrable Loads" section should add up to number_of_deferrable_loads value in header
|
563
576
|
case "number_of_deferrable_loads":
|
564
577
|
//get a list of param in section
|
@@ -101,11 +101,12 @@
|
|
101
101
|
"Description": "The load forecast method that will be used. The options are ‘csv’ to load a CSV file or ‘naive’ for a simple 1-day persistence model.",
|
102
102
|
"input": "select",
|
103
103
|
"select_options": [
|
104
|
+
"typical",
|
104
105
|
"naive",
|
105
106
|
"mlforecaster",
|
106
107
|
"csv"
|
107
108
|
],
|
108
|
-
"default_value": "
|
109
|
+
"default_value": "typical"
|
109
110
|
},
|
110
111
|
"set_total_pv_sell": {
|
111
112
|
"friendly_name": "PV straight to grid",
|
@@ -229,6 +230,12 @@
|
|
229
230
|
}
|
230
231
|
},
|
231
232
|
"Solar System (PV)": {
|
233
|
+
"set_use_pv": {
|
234
|
+
"friendly_name": "Enable PV system",
|
235
|
+
"Description": "Set to True if we should consider an solar PV system. Defaults to False",
|
236
|
+
"input": "boolean",
|
237
|
+
"default_value": false
|
238
|
+
},
|
232
239
|
"pv_module_model": {
|
233
240
|
"friendly_name": "PV module model name",
|
234
241
|
"Description": "The PV module model. This parameter can be a list of items to enable the simulation of mixed orientation systems.",
|
@@ -66,7 +66,7 @@
|
|
66
66
|
</div>
|
67
67
|
</div>
|
68
68
|
<footer class="footer">
|
69
|
-
<p style="margin-top:10px; text-align:center;">© MIT License | Copyright (c) 2021-
|
69
|
+
<p style="margin-top:10px; text-align:center;">© MIT License | Copyright (c) 2021-2025 David
|
70
70
|
HERNANDEZ</p>
|
71
71
|
</footer>
|
72
72
|
</div>
|
emhass/templates/index.html
CHANGED
@@ -69,7 +69,7 @@
|
|
69
69
|
<div>
|
70
70
|
|
71
71
|
<footer class="footer">
|
72
|
-
<p style="margin-top:10px; text-align:center;">© MIT License | Copyright (c) 2021-
|
72
|
+
<p style="margin-top:10px; text-align:center;">© MIT License | Copyright (c) 2021-2025 David
|
73
73
|
HERNANDEZ</p>
|
74
74
|
</footer>
|
75
75
|
</body>
|
emhass/utils.py
CHANGED
@@ -138,6 +138,70 @@ def get_forecast_dates(
|
|
138
138
|
return forecast_dates
|
139
139
|
|
140
140
|
|
141
|
+
def update_params_with_ha_config(
|
142
|
+
params: str,
|
143
|
+
ha_config: dict,
|
144
|
+
) -> dict:
|
145
|
+
"""
|
146
|
+
Update the params with the Home Assistant configuration.
|
147
|
+
|
148
|
+
Parameters
|
149
|
+
----------
|
150
|
+
params : str
|
151
|
+
The serialized params.
|
152
|
+
ha_config : dict
|
153
|
+
The Home Assistant configuration.
|
154
|
+
|
155
|
+
Returns
|
156
|
+
-------
|
157
|
+
dict
|
158
|
+
The updated params.
|
159
|
+
"""
|
160
|
+
# Load serialized params
|
161
|
+
params = json.loads(params)
|
162
|
+
# Update params
|
163
|
+
currency_to_symbol = {
|
164
|
+
'EUR': '€',
|
165
|
+
'USD': '$',
|
166
|
+
'GBP': '£',
|
167
|
+
'YEN': '¥',
|
168
|
+
'JPY': '¥',
|
169
|
+
'AUD': 'A$',
|
170
|
+
'CAD': 'C$',
|
171
|
+
'CHF': 'CHF', # Swiss Franc has no special symbol
|
172
|
+
'CNY': '¥',
|
173
|
+
'INR': '₹',
|
174
|
+
# Add more as needed
|
175
|
+
}
|
176
|
+
if 'currency' in ha_config.keys():
|
177
|
+
ha_config['currency'] = currency_to_symbol.get(ha_config['currency'], 'Unknown')
|
178
|
+
else:
|
179
|
+
ha_config['currency'] = '€'
|
180
|
+
if 'unit_system' not in ha_config.keys():
|
181
|
+
ha_config['unit_system'] = {'temperature': '°C'}
|
182
|
+
|
183
|
+
for k in range(params["optim_conf"]["number_of_deferrable_loads"]):
|
184
|
+
params['passed_data']['custom_predicted_temperature_id'][k].update(
|
185
|
+
{"unit_of_measurement": ha_config['unit_system']['temperature']}
|
186
|
+
)
|
187
|
+
updated_passed_dict = {
|
188
|
+
"custom_cost_fun_id": {
|
189
|
+
"unit_of_measurement": ha_config['currency'],
|
190
|
+
},
|
191
|
+
"custom_unit_load_cost_id": {
|
192
|
+
"unit_of_measurement": f"{ha_config['currency']}/kWh",
|
193
|
+
},
|
194
|
+
"custom_unit_prod_price_id": {
|
195
|
+
"unit_of_measurement": f"{ha_config['currency']}/kWh",
|
196
|
+
},
|
197
|
+
}
|
198
|
+
for key, value in updated_passed_dict.items():
|
199
|
+
params["passed_data"][key]["unit_of_measurement"] = value["unit_of_measurement"]
|
200
|
+
# Serialize the final params
|
201
|
+
params = json.dumps(params, default=str)
|
202
|
+
return params
|
203
|
+
|
204
|
+
|
141
205
|
def treat_runtimeparams(
|
142
206
|
runtimeparams: str,
|
143
207
|
params: str,
|
@@ -183,6 +247,10 @@ def treat_runtimeparams(
|
|
183
247
|
params["optim_conf"].update(optim_conf)
|
184
248
|
params["plant_conf"].update(plant_conf)
|
185
249
|
|
250
|
+
# Check defaults on HA retrieved config
|
251
|
+
default_currency_unit = '€'
|
252
|
+
default_temperature_unit = '°C'
|
253
|
+
|
186
254
|
# Some default data needed
|
187
255
|
custom_deferrable_forecast_id = []
|
188
256
|
custom_predicted_temperature_id = []
|
@@ -197,7 +265,7 @@ def treat_runtimeparams(
|
|
197
265
|
custom_predicted_temperature_id.append(
|
198
266
|
{
|
199
267
|
"entity_id": "sensor.temp_predicted{}".format(k),
|
200
|
-
"unit_of_measurement":
|
268
|
+
"unit_of_measurement": default_temperature_unit,
|
201
269
|
"friendly_name": "Predicted temperature {}".format(k),
|
202
270
|
}
|
203
271
|
)
|
@@ -239,7 +307,7 @@ def treat_runtimeparams(
|
|
239
307
|
},
|
240
308
|
"custom_cost_fun_id": {
|
241
309
|
"entity_id": "sensor.total_cost_fun_value",
|
242
|
-
"unit_of_measurement":
|
310
|
+
"unit_of_measurement": default_currency_unit,
|
243
311
|
"friendly_name": "Total cost function value",
|
244
312
|
},
|
245
313
|
"custom_optim_status_id": {
|
@@ -249,12 +317,12 @@ def treat_runtimeparams(
|
|
249
317
|
},
|
250
318
|
"custom_unit_load_cost_id": {
|
251
319
|
"entity_id": "sensor.unit_load_cost",
|
252
|
-
"unit_of_measurement": "
|
320
|
+
"unit_of_measurement": f"{default_currency_unit}/kWh",
|
253
321
|
"friendly_name": "Unit Load Cost",
|
254
322
|
},
|
255
323
|
"custom_unit_prod_price_id": {
|
256
324
|
"entity_id": "sensor.unit_prod_price",
|
257
|
-
"unit_of_measurement": "
|
325
|
+
"unit_of_measurement": f"{default_currency_unit}/kWh",
|
258
326
|
"friendly_name": "Unit Prod Price",
|
259
327
|
},
|
260
328
|
"custom_deferrable_forecast_id": custom_deferrable_forecast_id,
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: emhass
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.12.1
|
4
4
|
Summary: An Energy Management System for Home Assistant
|
5
5
|
Author-email: David HERNANDEZ <davidusb@gmail.com>
|
6
6
|
License: MIT
|
@@ -132,11 +132,13 @@ Installation instructions and example Home Assistant automation configurations a
|
|
132
132
|
You must follow these steps to make EMHASS work properly:
|
133
133
|
|
134
134
|
1) Install and run EMHASS.
|
135
|
-
- There are multiple methods of installing and Running EMHASS. See [Installation Method](
|
135
|
+
- There are multiple methods of installing and Running EMHASS. See [Installation Method](#Installation-Methods) below to pick a method that best suits your use case.
|
136
136
|
|
137
|
-
2) Define all the parameters in the configuration file *(`config.json`)* or configuration page *(`YOURIP:5000/configuration`)*.
|
137
|
+
2) Define all the parameters in the configuration file *(`config.json`)* or configuration page *(`YOURIP:5000/configuration`)*.
|
138
|
+
- Since EMHASS v0.12.0: the default configuration does not need to retrieve any data from Home Assistant! After installing and running the add-on, EMHASS should start and it will be ready to launch an optimization.
|
138
139
|
- See the description for each parameter in the [configuration](https://emhass.readthedocs.io/en/latest/config.html) docs.
|
139
|
-
|
140
|
+
- EMHASS has a default configuration with 2 deferrable loads, no solar PV, no batteries and a basic load power forecasting method.
|
141
|
+
- If you want to consider solar PV and more advanced load power forecast methods, you will need to define the main data entering EMHASS. This will be the Home Assistant sensor/variable `sensor.power_load_no_var_loads`, for the load power of your household excluding the power of the deferrable loads that you want to optimize, and the sensor/variable `sensor.power_photovoltaics` for the name of your Home Assistant variable containing the PV produced power (if solar PV is activated).
|
140
142
|
- If you have a PV installation then this dedicated web app can be useful for finding your inverter and solar panel models: [https://emhass-pvlib-database.streamlit.app/](https://emhass-pvlib-database.streamlit.app/)
|
141
143
|
|
142
144
|
4) Launch the optimization and check the results.
|
@@ -144,10 +146,10 @@ You must follow these steps to make EMHASS work properly:
|
|
144
146
|
- Or with a `curl` command like this: `curl -i -H 'Content-Type:application/json' -X POST -d '{}' http://localhost:5000/action/dayahead-optim`.
|
145
147
|
|
146
148
|
5) If you’re satisfied with the optimization results then you can set the optimization and data publish task commands in an automation.
|
147
|
-
- You can read more about this in the [usage](
|
149
|
+
- You can read more about this in the [usage](#usage) section below.
|
148
150
|
|
149
151
|
6) The final step is to link the deferrable loads variables to real switches on your installation.
|
150
|
-
- An example code for this using automations and the shell command integration is presented below in the [usage](
|
152
|
+
- An example code for this using automations and the shell command integration is presented below in the [usage](#usage) section.
|
151
153
|
|
152
154
|
A more detailed workflow is given below:
|
153
155
|
|
@@ -0,0 +1,32 @@
|
|
1
|
+
emhass/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
emhass/command_line.py,sha256=XCqeXWJR2YvsCjN6tXg5LGc_ucKIHQqbT2SYQqw6DKA,70739
|
3
|
+
emhass/forecast.py,sha256=_Gc8k6_8Nz87WHXKyUH6iXK956Z2TGzhL8L6t-tO_sk,63496
|
4
|
+
emhass/machine_learning_forecaster.py,sha256=JErz50i_D59J5wXdbf_EUPb_FG45qRflv51iBA7ARXU,17417
|
5
|
+
emhass/machine_learning_regressor.py,sha256=yFwMvVEmlgDJUsHhBT-HpNE3j2TC24e8Gmbcn9MPfeU,10690
|
6
|
+
emhass/optimization.py,sha256=izMgRJFEP_9LHvZeX6FM1lxWyDWX3Tq2hFvu8ZP9FN4,61457
|
7
|
+
emhass/retrieve_hass.py,sha256=RIzRiGzo09TO6zfDKqIClnLzQ5n2YkV97pinXPPXWsQ,26135
|
8
|
+
emhass/utils.py,sha256=esxcCoA38VhhugOHRvu69hJ8_V_zJkAqu0jUHW26rck,68969
|
9
|
+
emhass/web_server.py,sha256=QsqT51AdlAgNCG3NV1zbm4YkBSq_0BaC3cIEzPeZvl8,28023
|
10
|
+
emhass/data/associations.csv,sha256=IpEZIIWYdFjkRoC5xa1pRHjwnVs_VH8G8ogbGFxLfGI,3679
|
11
|
+
emhass/data/cec_inverters.pbz2,sha256=tK8FvAUDW0uYez8EPttdCJwHhpPofclYV6GhhNZL0Pk,168272
|
12
|
+
emhass/data/cec_modules.pbz2,sha256=8vEaysgYffXg3KUl8XSF36Mdywzi3LpEtUN_qenjO9s,1655747
|
13
|
+
emhass/data/config_defaults.json,sha256=-mQHahDv6Z5wYgClOs4VVr5KVCP51olb3f2mEj3Beic,2777
|
14
|
+
emhass/static/advanced.html,sha256=gAhsd14elDwh1Ts4lf9wn_ZkczzzObq5qOimi_la3Ic,2067
|
15
|
+
emhass/static/basic.html,sha256=ro2WwWgJyoUhqx_nJFzKCEG8FA8863vSHLmrjGYcEgs,677
|
16
|
+
emhass/static/configuration_list.html,sha256=i4v83RVduWjdjkjPhA74e-j8NSUpFzqMGU3ixOaJLfI,1740
|
17
|
+
emhass/static/configuration_script.js,sha256=CU6CuvnFrAWhnCns8K_AyX8fAdOJMrtR7wX7pXzpnK4,31525
|
18
|
+
emhass/static/script.js,sha256=q3qTqc_pTLTK-0NPKurxFXcJ2vZLz4TctPfUgz09ygo,16291
|
19
|
+
emhass/static/style.css,sha256=a_8YlGubn1zoF5RTLJ_Qkrb8tAjUY9p7oAKxhCvJY2s,19288
|
20
|
+
emhass/static/data/param_definitions.json,sha256=W-vq1Hj5_-YDpfl00cYF7kuLAQpfpsamjKGh7eU20LY,19485
|
21
|
+
emhass/static/img/emhass_icon.png,sha256=Kyx6hXQ1huJLHAq2CaBfjYXR25H9j99PSWHI0lShkaQ,19030
|
22
|
+
emhass/static/img/emhass_logo_short.svg,sha256=yzMcqtBRCV8rH84-MwnigZh45_f9Eoqwho9P8nCodJA,66736
|
23
|
+
emhass/static/img/feather-sprite.svg,sha256=VHjMJQg88wXa9CaeYrKGhNtyK0xdd47zCqwSIa-hxo8,60319
|
24
|
+
emhass/templates/configuration.html,sha256=M-_L__juYzcdGDaryGrz6LG2mguW2f1Sx6k01YfG7Dc,2885
|
25
|
+
emhass/templates/index.html,sha256=1V44c0yyliu_z8inl0K-zmmmkhQumH3Bqk8Jj1YJPzY,3076
|
26
|
+
emhass/templates/template.html,sha256=TkGgMecQEbFUZA4ymPwMUzNjKHsENvCgroUWbPt7G4Y,158
|
27
|
+
emhass-0.12.1.dist-info/LICENSE,sha256=1X3-S1yvOCBDBeox1aK3dq00m7dA8NDtcPrpKPISzbE,1077
|
28
|
+
emhass-0.12.1.dist-info/METADATA,sha256=FMOeeklV9inxmpy26IwnnZNfZkJiyR0rhUR0J7YgPmY,49387
|
29
|
+
emhass-0.12.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
30
|
+
emhass-0.12.1.dist-info/entry_points.txt,sha256=6Bp1NFOGNv_fSTxYl1ke3K3h3aqAcBxI-bgq5yq-i1M,52
|
31
|
+
emhass-0.12.1.dist-info/top_level.txt,sha256=L7fIX4awfmxQbAePtSdVg2e6x_HhghfReHfsKSpKr9I,7
|
32
|
+
emhass-0.12.1.dist-info/RECORD,,
|
emhass-0.11.4.dist-info/RECORD
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
emhass/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
emhass/command_line.py,sha256=3fN0gUrTImxCUbmESgU8j_dXUwvkaCDMSdwcHWY0SQI,69671
|
3
|
-
emhass/forecast.py,sha256=Jr1DAobtFoTfCS9RvtPQvYRTYrvyp1Ubq-X1noqh7jA,58763
|
4
|
-
emhass/machine_learning_forecaster.py,sha256=JErz50i_D59J5wXdbf_EUPb_FG45qRflv51iBA7ARXU,17417
|
5
|
-
emhass/machine_learning_regressor.py,sha256=yFwMvVEmlgDJUsHhBT-HpNE3j2TC24e8Gmbcn9MPfeU,10690
|
6
|
-
emhass/optimization.py,sha256=-s3gWblpc85v-q0Ad9M8lXlG8ZZW0iL_s_I_9EUhzwA,61452
|
7
|
-
emhass/retrieve_hass.py,sha256=LLZBoP5Rkyg0_uSvJSG5m-6apVoWZ6MWuXPPA-j-JsI,25617
|
8
|
-
emhass/utils.py,sha256=N3qmEA_42srDr7q2cgU-LEwWeuItPMhX9VKPBBJnFy0,66887
|
9
|
-
emhass/web_server.py,sha256=QsqT51AdlAgNCG3NV1zbm4YkBSq_0BaC3cIEzPeZvl8,28023
|
10
|
-
emhass/data/associations.csv,sha256=Wv2845irZDMYm8svg__Ev0c2BgkVX88yAXoqpy1C4RM,3646
|
11
|
-
emhass/data/cec_inverters.pbz2,sha256=tK8FvAUDW0uYez8EPttdCJwHhpPofclYV6GhhNZL0Pk,168272
|
12
|
-
emhass/data/cec_modules.pbz2,sha256=8vEaysgYffXg3KUl8XSF36Mdywzi3LpEtUN_qenjO9s,1655747
|
13
|
-
emhass/data/config_defaults.json,sha256=0jyYfF1ob3QMbjP8h2rd0jlbfe2uYm68NYW0GKM5qfk,2754
|
14
|
-
emhass/static/advanced.html,sha256=gAhsd14elDwh1Ts4lf9wn_ZkczzzObq5qOimi_la3Ic,2067
|
15
|
-
emhass/static/basic.html,sha256=ro2WwWgJyoUhqx_nJFzKCEG8FA8863vSHLmrjGYcEgs,677
|
16
|
-
emhass/static/configuration_list.html,sha256=4ZAL-4YXdofnx17np-v39Yt3qW2TWbSzNBkj86bpvIg,1578
|
17
|
-
emhass/static/configuration_script.js,sha256=N95GzyQdLzzOuSNw4L78BdArdqLPYJKhU3baGEsOhZE,31098
|
18
|
-
emhass/static/script.js,sha256=q3qTqc_pTLTK-0NPKurxFXcJ2vZLz4TctPfUgz09ygo,16291
|
19
|
-
emhass/static/style.css,sha256=a_8YlGubn1zoF5RTLJ_Qkrb8tAjUY9p7oAKxhCvJY2s,19288
|
20
|
-
emhass/static/data/param_definitions.json,sha256=2z_nb94wrj-fVORy0F_hoOCHHv7CbemIoKpScCmRcPI,19243
|
21
|
-
emhass/static/img/emhass_icon.png,sha256=Kyx6hXQ1huJLHAq2CaBfjYXR25H9j99PSWHI0lShkaQ,19030
|
22
|
-
emhass/static/img/emhass_logo_short.svg,sha256=yzMcqtBRCV8rH84-MwnigZh45_f9Eoqwho9P8nCodJA,66736
|
23
|
-
emhass/static/img/feather-sprite.svg,sha256=VHjMJQg88wXa9CaeYrKGhNtyK0xdd47zCqwSIa-hxo8,60319
|
24
|
-
emhass/templates/configuration.html,sha256=yS9p730GHf99ZYK0NiZjkuaxPjH1ZFo8R6xL5c1ZZ9s,2885
|
25
|
-
emhass/templates/index.html,sha256=Ehn-hUdraIwX_5Usb5Liz1ip24NfztmCxsi0J4Tf3-A,3076
|
26
|
-
emhass/templates/template.html,sha256=TkGgMecQEbFUZA4ymPwMUzNjKHsENvCgroUWbPt7G4Y,158
|
27
|
-
emhass-0.11.4.dist-info/LICENSE,sha256=1X3-S1yvOCBDBeox1aK3dq00m7dA8NDtcPrpKPISzbE,1077
|
28
|
-
emhass-0.11.4.dist-info/METADATA,sha256=W15OQT9sMri8XG1l7KIXBbqFRUM5KsXDDppkopbpV2g,48945
|
29
|
-
emhass-0.11.4.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
30
|
-
emhass-0.11.4.dist-info/entry_points.txt,sha256=6Bp1NFOGNv_fSTxYl1ke3K3h3aqAcBxI-bgq5yq-i1M,52
|
31
|
-
emhass-0.11.4.dist-info/top_level.txt,sha256=L7fIX4awfmxQbAePtSdVg2e6x_HhghfReHfsKSpKr9I,7
|
32
|
-
emhass-0.11.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|