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 CHANGED
@@ -81,7 +81,8 @@ def set_input_data_dict(
81
81
  logger,
82
82
  emhass_conf,
83
83
  )
84
- # Define main objects
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
- df_weather = fcst.get_weather_forecast(
158
- method=optim_conf["weather_forecast_method"]
159
- )
160
- if isinstance(df_weather, bool) and not df_weather:
161
- return False
162
- P_PV_forecast = fcst.get_power_from_weather(df_weather)
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
- df_weather = fcst.get_weather_forecast(
245
- method=optim_conf["weather_forecast_method"]
246
- )
247
- if isinstance(df_weather, bool) and not df_weather:
248
- return False
249
- P_PV_forecast = fcst.get_power_from_weather(
250
- df_weather, set_mix_forecast=True, df_now=df_input_data
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
@@ -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
@@ -84,6 +84,7 @@
84
84
  "photovoltaic_production_sell_price": 0.1419,
85
85
  "maximum_power_from_grid": 9000,
86
86
  "maximum_power_to_grid": 9000,
87
+ "set_use_pv": false,
87
88
  "pv_module_model": [
88
89
  "CSUN_Eurasia_Energy_Systems_Industry_and_Trade_CSUN295_60M"
89
90
  ],
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] = "naive",
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 'naive'.
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 == "naive": # using a naive approach
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
@@ -661,7 +661,7 @@ class Optimization:
661
661
  cooling_constant
662
662
  * (
663
663
  predicted_temp[I - 1]
664
- - outdoor_temperature_forecast[I - 1]
664
+ - outdoor_temperature_forecast.iloc[I - 1]
665
665
  )
666
666
  )
667
667
  )
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
- url = self.hass_url + "api/config"
94
- response_config = get(url, headers=headers)
95
- self.ha_config = response_config.json()
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 = document.querySelectorAll("input[type='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": "naive"
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;">&copy; MIT License | Copyright (c) 2021-2023 David
69
+ <p style="margin-top:10px; text-align:center;">&copy; MIT License | Copyright (c) 2021-2025 David
70
70
  HERNANDEZ</p>
71
71
  </footer>
72
72
  </div>
@@ -69,7 +69,7 @@
69
69
  <div>
70
70
 
71
71
  <footer class="footer">
72
- <p style="margin-top:10px; text-align:center;">&copy; MIT License | Copyright (c) 2021-2023 David
72
+ <p style="margin-top:10px; text-align:center;">&copy; 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": "°C",
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": "€/kWh",
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": "€/kWh",
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.11.4
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](##Installation-Methods) below to pick a method that best suits your use case.
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
- - You will most notably need to define the main data entering EMHASS. This will be the Home Assistant sensor/variable `sensor.power_photovoltaics` for the name of your Home Assistant variable containing the PV produced power, and the 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.
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](##usage) section below.
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](##usage) section.
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,,
@@ -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,,