emhass 0.10.6__py3-none-any.whl → 0.11.0__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/web_server.py CHANGED
@@ -16,60 +16,94 @@ from emhass.command_line import forecast_model_fit, forecast_model_predict, fore
16
16
  from emhass.command_line import regressor_model_fit, regressor_model_predict
17
17
  from emhass.command_line import publish_data, continual_publish
18
18
  from emhass.utils import get_injection_dict, get_injection_dict_forecast_model_fit, \
19
- get_injection_dict_forecast_model_tune, build_params
19
+ get_injection_dict_forecast_model_tune, build_config, build_secrets, build_params, \
20
+ param_to_config, build_legacy_config_params
20
21
 
21
22
  # Define the Flask instance
22
23
  app = Flask(__name__)
24
+ emhass_conf = {}
23
25
 
24
- # Check logfile for error, anything after string match if provided
25
- def checkFileLog(refString=None):
26
+ def checkFileLog(refString=None) -> bool:
27
+ """
28
+ Check logfile for error, anything after string match if provided.
29
+
30
+ :param refString: String to reduce log area to check for errors. Use to reduce log to check anything after string match (ie. an action).
31
+ :type refString: str
32
+ :return: Boolean return if error was found in logs
33
+ :rtype: bool
34
+
35
+ """
26
36
  if (refString is not None):
27
- logArray = grabLog(refString) #grab reduced log array
37
+ logArray = grabLog(refString) #grab reduced log array (everything after string match)
28
38
  else:
29
39
  if ((emhass_conf['data_path'] / 'actionLogs.txt')).exists():
30
40
  with open(str(emhass_conf['data_path'] / 'actionLogs.txt'), "r") as fp:
31
41
  logArray = fp.readlines()
42
+ else:
43
+ app.logger.debug("Unable to obtain actionLogs.txt")
32
44
  for logString in logArray:
33
45
  if (logString.split(' ', 1)[0] == "ERROR"):
34
46
  return True
35
47
  return False
36
48
 
37
- # Find string in logs, append all lines after to return
38
- def grabLog(refString):
49
+ def grabLog(refString) -> list:
50
+ """
51
+ Find string in logs, append all lines after into list to return.
52
+
53
+ :param refString: String used to string match log.
54
+ :type refString: str
55
+ :return: List of lines in log after string match.
56
+ :rtype: list
57
+
58
+ """
39
59
  isFound = []
40
60
  output = []
41
61
  if ((emhass_conf['data_path'] / 'actionLogs.txt')).exists():
42
62
  with open(str(emhass_conf['data_path'] / 'actionLogs.txt'), "r") as fp:
43
63
  logArray = fp.readlines()
44
- for x in range(len(logArray)-1): #find all matches and log key in isFound
64
+ # Find all string matches, log key (line Number) in isFound
65
+ for x in range(len(logArray)-1):
45
66
  if (re.search(refString,logArray[x])):
46
67
  isFound.append(x)
47
68
  if len(isFound) != 0:
48
- for x in range(isFound[-1],len(logArray)): #use isFound to extract last related action logs
69
+ # Use last item in isFound to extract action logs
70
+ for x in range(isFound[-1],len(logArray)):
49
71
  output.append(logArray[x])
50
72
  return output
51
73
 
52
74
  # Clear the log file
53
- def clearFileLog():
75
+ def clearFileLog():
76
+ """
77
+ Clear the contents of the log file (actionLogs.txt)
78
+
79
+ """
54
80
  if ((emhass_conf['data_path'] / 'actionLogs.txt')).exists():
55
81
  with open(str(emhass_conf['data_path'] / 'actionLogs.txt'), "w") as fp:
56
82
  fp.truncate()
57
83
 
58
-
59
- # Initial index page render
60
84
  @app.route('/')
85
+ @app.route('/index')
61
86
  def index():
87
+ """
88
+ Render initial index page and serve to web server.
89
+ Appends plot tables saved from previous optimization into index.html, then serves.
90
+
91
+ """
62
92
  app.logger.info("EMHASS server online, serving index.html...")
63
93
  # Load HTML template
64
94
  file_loader = PackageLoader('emhass', 'templates')
65
95
  env = Environment(loader=file_loader)
96
+ #check if index.html exists
97
+ if 'index.html' not in env.list_templates():
98
+ app.logger.error("Unable to find index.html in emhass module")
99
+ return make_response(["ERROR: unable to find index.html in emhass module"],404)
66
100
  template = env.get_template('index.html')
67
- # Load cache dict
101
+ # Load cached dict (if exists), to present generated plot tables
68
102
  if (emhass_conf['data_path'] / 'injection_dict.pkl').exists():
69
103
  with open(str(emhass_conf['data_path'] / 'injection_dict.pkl'), "rb") as fid:
70
104
  injection_dict = pickle.load(fid)
71
105
  else:
72
- app.logger.warning("The data container dictionary is empty... Please launch an optimization task")
106
+ app.logger.info("The data container dictionary is empty... Please launch an optimization task")
73
107
  injection_dict={}
74
108
 
75
109
  # replace {{basename}} in html template html with path root
@@ -79,35 +113,205 @@ def index():
79
113
  return make_response(template.render(injection_dict=injection_dict))
80
114
 
81
115
 
82
- #get actions
83
- @app.route('/template/<action_name>', methods=['GET'])
84
- def template_action(action_name):
116
+ @app.route('/configuration')
117
+ def configuration():
118
+ """
119
+ Configuration page actions:
120
+ Render and serve configuration page html
121
+
122
+ """
123
+ app.logger.info("serving configuration.html...")
124
+ # Load HTML template
125
+ file_loader = PackageLoader('emhass', 'templates')
126
+ env = Environment(loader=file_loader)
127
+ #check if configuration.html exists
128
+ if 'configuration.html' not in env.list_templates():
129
+ app.logger.error("Unable to find configuration.html in emhass module")
130
+ return make_response(["ERROR: unable to find configuration.html in emhass module"],404)
131
+ template = env.get_template('configuration.html')
132
+ return make_response(template.render(config=params))
133
+
134
+
135
+ @app.route('/template', methods=['GET'])
136
+ def template_action():
137
+ """
138
+ template page actions:
139
+ Render and serve template html
140
+
141
+ """
85
142
  app.logger.info(" >> Sending rendered template table data")
86
- if action_name == 'table-template':
87
- file_loader = PackageLoader('emhass', 'templates')
88
- env = Environment(loader=file_loader)
89
- template = env.get_template('template.html')
90
- if (emhass_conf['data_path'] / 'injection_dict.pkl').exists():
91
- with open(str(emhass_conf['data_path'] / 'injection_dict.pkl'), "rb") as fid:
92
- injection_dict = pickle.load(fid)
93
- else:
94
- app.logger.warning("The data container dictionary is empty... Please launch an optimization task")
95
- injection_dict={}
96
- return make_response(template.render(injection_dict=injection_dict))
143
+ file_loader = PackageLoader('emhass', 'templates')
144
+ env = Environment(loader=file_loader)
145
+ # Check if template.html exists
146
+ if 'template.html' not in env.list_templates():
147
+ app.logger.error("Unable to find template.html in emhass module")
148
+ return make_response(["WARNING: unable to find template.html in emhass module"],404)
149
+ template = env.get_template('template.html')
150
+ if (emhass_conf['data_path'] / 'injection_dict.pkl').exists():
151
+ with open(str(emhass_conf['data_path'] / 'injection_dict.pkl'), "rb") as fid:
152
+ injection_dict = pickle.load(fid)
153
+ else:
154
+ app.logger.warning("Unable to obtain plot data from injection_dict.pkl")
155
+ app.logger.warning("Try running an launch an optimization task")
156
+ injection_dict={}
157
+ return make_response(template.render(injection_dict=injection_dict))
158
+
159
+ @app.route('/get-config', methods=['GET'])
160
+ def parameter_get():
161
+ """
162
+ Get request action that builds, formats and sends config as json (config.json format)
163
+
164
+ """
165
+ app.logger.debug("Obtaining current saved parameters as config")
166
+ # Build config from all possible sources (inc. legacy yaml config)
167
+ config = build_config(emhass_conf,app.logger,emhass_conf["defaults_path"],emhass_conf["config_path"],emhass_conf["legacy_config_path"])
168
+ if type(config) is bool and not config:
169
+ return make_response(["failed to retrieve default config file"],500)
170
+ # Format parameters in config with params (converting legacy json parameters from options.json if any)
171
+ params = build_params(emhass_conf,{},config,app.logger)
172
+ if type(params) is bool and not params:
173
+ return make_response(["Unable to obtain associations file"],500)
174
+ # Covert formatted parameters from params back into config.json format
175
+ return_config = param_to_config(params,app.logger)
176
+ # Send config
177
+ return make_response(return_config,201)
178
+
179
+
180
+ # Get default Config
181
+ @app.route('/get-config/defaults', methods=['GET'])
182
+ def config_get():
183
+ """
184
+ Get request action, retrieves and sends default configuration
185
+
186
+ """
187
+ app.logger.debug("Obtaining default parameters")
188
+ # Build config, passing only default file
189
+ config = build_config(emhass_conf,app.logger,emhass_conf["defaults_path"])
190
+ if type(config) is bool and not config:
191
+ return make_response(["failed to retrieve default config file"],500)
192
+ # Format parameters in config with params
193
+ params = build_params(emhass_conf,{},config,app.logger)
194
+ if type(params) is bool and not params:
195
+ return make_response(["Unable to obtain associations file"],500)
196
+ # Covert formatted parameters from params back into config.json format
197
+ return_config = param_to_config(params,app.logger)
198
+ # Send params
199
+ return make_response(return_config,201)
200
+
201
+
202
+ # Get YAML-to-JSON config
203
+ @app.route('/get-json', methods=['POST'])
204
+ def json_convert():
205
+ """
206
+ Post request action, receives yaml config (config_emhass.yaml or EMHASS-Add-on config page) and converts to config json format.
207
+
208
+ """
209
+ app.logger.info("Attempting to convert YAML to JSON")
210
+ data = request.get_data()
211
+ yaml_config = yaml.safe_load(data)
212
+
213
+ # If filed to Parse YAML
214
+ if yaml_config is None:
215
+ return make_response(["failed to Parse YAML from data"],400)
216
+ # Test YAML is legacy config format (from config_emhass.yaml)
217
+ test_legacy_config = build_legacy_config_params(emhass_conf,yaml_config, app.logger)
218
+ if test_legacy_config:
219
+ yaml_config = test_legacy_config
220
+ # Format YAML to params (format params. check if params match legacy option.json format)
221
+ params = build_params(emhass_conf,{},yaml_config,app.logger)
222
+ if type(params) is bool and not params:
223
+ return make_response(["Unable to obtain associations file"],500)
224
+ # Covert formatted parameters from params back into config.json format
225
+ config = param_to_config(params,app.logger)
226
+ # convert json to str
227
+ config = json.dumps(config)
228
+
229
+ # Send params
230
+ return make_response(config,201)
231
+
232
+ @app.route('/set-config', methods=['POST'])
233
+ def parameter_set():
234
+ """
235
+ Receive JSON config, and save config to file (config.json and param.pkl)
236
+
237
+ """
238
+ config = {}
239
+ if not emhass_conf['defaults_path']:
240
+ return make_response(["Unable to Obtain defaults_path from emhass_conf"],500)
241
+ if not emhass_conf['config_path']:
242
+ return make_response(["Unable to Obtain config_path from emhass_conf"],500)
243
+
244
+ # Load defaults as a reference point (for sorting) and a base to override
245
+ if os.path.exists(emhass_conf['defaults_path']) and Path(emhass_conf['defaults_path']).is_file():
246
+ with emhass_conf['defaults_path'].open('r') as data:
247
+ config = json.load(data)
248
+ else:
249
+ app.logger.warning("Unable to obtain default config. only parameters passed from request will be saved to config.json")
250
+
251
+ # Retrieve sent config json
252
+ request_data = request.get_json(force=True)
253
+
254
+ # check if data is empty
255
+ if len(request_data) == 0:
256
+ return make_response(["failed to retrieve config json"],400)
257
+
258
+ # Format config by converting to params (format params. check if params match legacy option.json format. If so format)
259
+ params = build_params(emhass_conf,params_secrets,request_data,app.logger)
260
+ if type(params) is bool and not params:
261
+ return make_response(["Unable to obtain associations file"],500)
262
+
263
+ # Covert formatted parameters from params back into config.json format.
264
+ # Overwrite existing default parameters in config
265
+ config.update(param_to_config(params,app.logger))
266
+
267
+ # Save config to config.json
268
+ if os.path.exists(emhass_conf['config_path'].parent):
269
+ with emhass_conf['config_path'].open('w') as f:
270
+ json.dump(config, f, indent=4)
271
+ else:
272
+ return make_response(["Unable to save config file"],500)
273
+ request_data
274
+ app.logger.info(params)
275
+
276
+ # Save params with updated config
277
+ if os.path.exists(emhass_conf['data_path']):
278
+ with open(str(emhass_conf['data_path'] / 'params.pkl'), "wb") as fid:
279
+ pickle.dump((config_path, build_params(emhass_conf,params_secrets,config,app.logger)), fid)
280
+ else:
281
+ return make_response(["Unable to save params file, missing data_path"],500)
282
+
283
+ app.logger.info("Saved parameters from webserver")
284
+ return make_response({},201)
97
285
 
98
- #post actions
99
286
  @app.route('/action/<action_name>', methods=['POST'])
100
287
  def action_call(action_name):
288
+ """
289
+ Receive Post action, run action according to passed slug(action_name) (e.g. /action/publish-data)
290
+
291
+ :param action_name: Slug/Action string corresponding to which action to take
292
+ :type action_name: String
293
+
294
+ """
101
295
  # Setting up parameters
102
- with open(str(emhass_conf['data_path'] / 'params.pkl'), "rb") as fid:
103
- emhass_conf['config_path'], params = pickle.load(fid)
104
- runtimeparams = request.get_json(force=True)
105
- params = json.dumps(params)
106
- if runtimeparams is not None and runtimeparams != '{}':
107
- app.logger.info("Passed runtime parameters: " + str(runtimeparams))
296
+ # Params
297
+ if (emhass_conf['data_path'] / 'params.pkl').exists():
298
+ with open(str(emhass_conf['data_path'] / 'params.pkl'), "rb") as fid:
299
+ emhass_conf['config_path'], params = pickle.load(fid)
300
+ params = json.dumps(params)
301
+ else:
302
+ app.logger.error("Unable to find params.pkl file")
303
+ return make_response(grabLog(ActionStr), 400)
304
+ # Runtime
305
+ runtimeparams = request.get_json(force=True,silent=True)
306
+ if runtimeparams is not None:
307
+ if runtimeparams != '{}':
308
+ app.logger.info("Passed runtime parameters: " + str(runtimeparams))
309
+ else:
310
+ app.logger.warning("Unable to parse runtime parameters")
311
+ runtimeparams = {}
108
312
  runtimeparams = json.dumps(runtimeparams)
109
-
110
- # Run action if weather_forecast_cache
313
+
314
+ # weather-forecast-cache (check before set_input_data_dict)
111
315
  if action_name == 'weather-forecast-cache':
112
316
  ActionStr = " >> Performing weather forecast, try to caching result"
113
317
  app.logger.info(ActionStr)
@@ -125,13 +329,15 @@ def action_call(action_name):
125
329
  return make_response(grabLog(ActionStr), 400)
126
330
 
127
331
  # If continual_publish is True, start thread with loop function
128
- if len(continual_publish_thread) == 0 and input_data_dict['retrieve_hass_conf'].get("continual_publish",False):
332
+ if len(continual_publish_thread) == 0 and input_data_dict['retrieve_hass_conf'].get('continual_publish',False):
129
333
  # Start Thread
130
- continualLoop = threading.Thread(name="continual_publish",target=continual_publish,args=[input_data_dict,entity_path,app.logger])
334
+ continualLoop = threading.Thread(name='continual_publish',target=continual_publish,args=[input_data_dict,entity_path,app.logger])
131
335
  continualLoop.start()
132
336
  continual_publish_thread.append(continualLoop)
133
-
134
- # run action based on POST request
337
+
338
+ # Run action based on POST request
339
+ # If error in log when running action, return actions log (list) as response. (Using ActionStr as a reference of the action start in the log)
340
+ # publish-data
135
341
  if action_name == 'publish-data':
136
342
  ActionStr = " >> Publishing data..."
137
343
  app.logger.info(ActionStr)
@@ -140,6 +346,7 @@ def action_call(action_name):
140
346
  if not checkFileLog(ActionStr):
141
347
  return make_response(msg, 201)
142
348
  return make_response(grabLog(ActionStr), 400)
349
+ # perfect-optim
143
350
  elif action_name == 'perfect-optim':
144
351
  ActionStr = " >> Performing perfect optimization..."
145
352
  app.logger.info(ActionStr)
@@ -151,6 +358,7 @@ def action_call(action_name):
151
358
  if not checkFileLog(ActionStr):
152
359
  return make_response(msg, 201)
153
360
  return make_response(grabLog(ActionStr), 400)
361
+ # dayahead-optim
154
362
  elif action_name == 'dayahead-optim':
155
363
  ActionStr = " >> Performing dayahead optimization..."
156
364
  app.logger.info(ActionStr)
@@ -162,6 +370,7 @@ def action_call(action_name):
162
370
  if not checkFileLog(ActionStr):
163
371
  return make_response(msg, 201)
164
372
  return make_response(grabLog(ActionStr), 400)
373
+ # naive-mpc-optim
165
374
  elif action_name == 'naive-mpc-optim':
166
375
  ActionStr = " >> Performing naive MPC optimization..."
167
376
  app.logger.info(ActionStr)
@@ -173,6 +382,7 @@ def action_call(action_name):
173
382
  if not checkFileLog(ActionStr):
174
383
  return make_response(msg, 201)
175
384
  return make_response(grabLog(ActionStr), 400)
385
+ # forecast-model-fit
176
386
  elif action_name == 'forecast-model-fit':
177
387
  ActionStr = " >> Performing a machine learning forecast model fit..."
178
388
  app.logger.info(ActionStr)
@@ -185,6 +395,7 @@ def action_call(action_name):
185
395
  if not checkFileLog(ActionStr):
186
396
  return make_response(msg, 201)
187
397
  return make_response(grabLog(ActionStr), 400)
398
+ # forecast-model-predict
188
399
  elif action_name == 'forecast-model-predict':
189
400
  ActionStr = " >> Performing a machine learning forecast model predict..."
190
401
  app.logger.info(ActionStr)
@@ -202,6 +413,7 @@ def action_call(action_name):
202
413
  if not checkFileLog(ActionStr):
203
414
  return make_response(msg, 201)
204
415
  return make_response(grabLog(ActionStr), 400)
416
+ # forecast-model-tune
205
417
  elif action_name == 'forecast-model-tune':
206
418
  ActionStr = " >> Performing a machine learning forecast model tune..."
207
419
  app.logger.info(ActionStr)
@@ -216,6 +428,7 @@ def action_call(action_name):
216
428
  if not checkFileLog(ActionStr):
217
429
  return make_response(msg, 201)
218
430
  return make_response(grabLog(ActionStr), 400)
431
+ # regressor-model-fit
219
432
  elif action_name == 'regressor-model-fit':
220
433
  ActionStr = " >> Performing a machine learning regressor fit..."
221
434
  app.logger.info(ActionStr)
@@ -224,6 +437,7 @@ def action_call(action_name):
224
437
  if not checkFileLog(ActionStr):
225
438
  return make_response(msg, 201)
226
439
  return make_response(grabLog(ActionStr), 400)
440
+ # regressor-model-predict
227
441
  elif action_name == 'regressor-model-predict':
228
442
  ActionStr = " >> Performing a machine learning regressor predict..."
229
443
  app.logger.info(ActionStr)
@@ -232,6 +446,7 @@ def action_call(action_name):
232
446
  if not checkFileLog(ActionStr):
233
447
  return make_response(msg, 201)
234
448
  return make_response(grabLog(ActionStr), 400)
449
+ # Else return error
235
450
  else:
236
451
  app.logger.error("ERROR: passed action is not valid")
237
452
  msg = f'EMHASS >> ERROR: Passed action is not valid... \n'
@@ -242,82 +457,74 @@ if __name__ == "__main__":
242
457
  parser = argparse.ArgumentParser()
243
458
  parser.add_argument('--url', type=str, help='The URL to your Home Assistant instance, ex the external_url in your hass configuration')
244
459
  parser.add_argument('--key', type=str, help='Your access key. If using EMHASS in standalone this should be a Long-Lived Access Token')
245
- parser.add_argument('--addon', type=strtobool, default='False', help='Define if we are usinng EMHASS with the add-on or in standalone mode')
246
460
  parser.add_argument('--no_response', type=strtobool, default='False', help='This is set if json response errors occur')
247
461
  args = parser.parse_args()
462
+
463
+ # Pre formatted config parameters
464
+ config = {}
465
+ # Secrets
466
+ params_secrets = {}
467
+ # Built parameters (formatted config + secrets)
468
+ params = None
248
469
 
249
- #Obtain url and key from ENV or ARG (if any)
250
- hass_url = os.getenv("EMHASS_URL", default=args.url)
251
- key = os.getenv("SUPERVISOR_TOKEN", default=args.key)
252
- if hass_url != "http://supervisor/core/api":
253
- key = os.getenv("EMHASS_KEY", key)
254
- #If url or key is None, Set as empty string to reduce NoneType errors bellow
255
- if key is None: key = ""
256
- if hass_url is None: hass_url = ""
257
-
258
- #find env's, not not set defaults
259
- use_options = os.getenv('USE_OPTIONS', default=False)
260
- CONFIG_PATH = os.getenv("CONFIG_PATH", default="/app/config_emhass.yaml")
261
- OPTIONS_PATH = os.getenv('OPTIONS_PATH', default="/app/options.json")
470
+ # Find env's, not not set defaults
262
471
  DATA_PATH = os.getenv("DATA_PATH", default="/app/data/")
263
472
  ROOT_PATH = os.getenv("ROOT_PATH", default=str(Path(__file__).parent))
264
-
265
- #options None by default
266
- options = None
473
+ CONFIG_PATH = os.getenv('CONFIG_PATH', default="/share/config.json")
474
+ OPTIONS_PATH = os.getenv('OPTIONS_PATH', default="/data/options.json")
475
+ DEFAULTS_PATH = os.getenv('DEFAULTS_PATH', default=ROOT_PATH +"/data/config_defaults.json")
476
+ ASSOCIATIONS_PATH = os.getenv('ASSOCIATIONS_PATH', default=ROOT_PATH + "/data/associations.csv")
477
+ LEGACY_CONFIG_PATH = os.getenv("LEGACY_CONFIG_PATH", default="/app/config_emhass.yaml")
267
478
 
268
479
  # Define the paths
269
- if args.addon==1:
270
- options_json = Path(OPTIONS_PATH)
271
- # Read options info
272
- if options_json.exists():
273
- with options_json.open('r') as data:
274
- options = json.load(data)
275
- else:
276
- app.logger.error("options.json does not exist")
277
- raise Exception("options.json does not exist in path: "+str(options_json))
278
- else:
279
- if use_options:
280
- options_json = Path(OPTIONS_PATH)
281
- # Read options info
282
- if options_json.exists():
283
- with options_json.open('r') as data:
284
- options = json.load(data)
285
- else:
286
- app.logger.error("options.json does not exist")
287
- raise Exception("options.json does not exist in path: "+str(options_json))
288
- else:
289
- options = None
290
-
291
- #if data path specified by options.json
292
- if options is not None:
293
- if options.get('data_path', None) != None and options.get('data_path', None) != "default":
294
- DATA_PATH = options.get('data_path', None);
295
-
296
- #save paths to dictionary
297
480
  config_path = Path(CONFIG_PATH)
481
+ options_path = Path(OPTIONS_PATH)
482
+ defaults_path = Path(DEFAULTS_PATH)
483
+ associations_path = Path(ASSOCIATIONS_PATH)
484
+ legacy_config_path = Path(LEGACY_CONFIG_PATH)
298
485
  data_path = Path(DATA_PATH)
299
486
  root_path = Path(ROOT_PATH)
300
- emhass_conf = {}
487
+ # Add paths to emhass_conf
301
488
  emhass_conf['config_path'] = config_path
489
+ emhass_conf['options_path'] = options_path
490
+ emhass_conf['defaults_path'] = defaults_path
491
+ emhass_conf['associations_path'] = associations_path
492
+ emhass_conf['legacy_config_path'] = legacy_config_path
302
493
  emhass_conf['data_path'] = data_path
303
494
  emhass_conf['root_path'] = root_path
304
-
305
- # Read the example default config file
306
- if config_path.exists():
307
- with open(config_path, 'r') as file:
308
- config = yaml.load(file, Loader=yaml.FullLoader)
309
- retrieve_hass_conf = config['retrieve_hass_conf']
310
- optim_conf = config['optim_conf']
311
- plant_conf = config['plant_conf']
312
- else:
313
- app.logger.error("Unable to open the default configuration yaml file")
314
- raise Exception("Failed to open config file, config_path: "+str(config_path))
315
495
 
316
- params = {}
317
- params['retrieve_hass_conf'] = retrieve_hass_conf
318
- params['optim_conf'] = optim_conf
319
- params['plant_conf'] = plant_conf
320
- web_ui_url = '0.0.0.0'
496
+ # Combine parameters from configuration sources (if exists)
497
+ config.update(build_config(emhass_conf,app.logger,defaults_path,config_path,legacy_config_path))
498
+ if type(config) is bool and not config:
499
+ raise Exception("Failed to find default config")
500
+
501
+ costfun = os.getenv('LOCAL_COSTFUN', config.get('costfun', 'profit'))
502
+ logging_level = os.getenv('LOGGING_LEVEL', config.get('logging_level','INFO'))
503
+ # Temporary set logging level if debug
504
+ if logging_level == "DEBUG":
505
+ app.logger.setLevel(logging.DEBUG)
506
+
507
+ ## Secrets
508
+ argument = {}
509
+ if args.url:
510
+ argument['url'] = args.url
511
+ if args.key:
512
+ argument['key'] = args.key
513
+ # Combine secrets from ENV, Arguments/ARG, Secrets file (secrets_emhass.yaml), options (options.json from addon configuration file) and/or Home Assistant Standalone API (if exist)
514
+ emhass_conf, secrets = build_secrets(emhass_conf,app.logger,argument,options_path,os.getenv('SECRETS_PATH', default='/app/secrets_emhass.yaml'), bool(args.no_response))
515
+ params_secrets.update(secrets)
516
+
517
+ server_ip = params_secrets.get("server_ip","0.0.0.0")
518
+
519
+ # Check if data path exists
520
+ if not os.path.isdir(emhass_conf['data_path']):
521
+ app.logger.warning("Unable to find data_path: " + str(emhass_conf['data_path']))
522
+ if os.path.isdir(Path("/app/data/")):
523
+ emhass_conf['data_path'] = Path("/app/data/")
524
+ else:
525
+ Path(root_path / "data/").mkdir(parents=True, exist_ok=True)
526
+ emhass_conf['data_path'] = root_path / "data/"
527
+ app.logger.info("data_path has been set to " + str(emhass_conf['data_path']))
321
528
 
322
529
  # Initialize this global dict
323
530
  if (emhass_conf['data_path'] / 'injection_dict.pkl').exists():
@@ -325,123 +532,23 @@ if __name__ == "__main__":
325
532
  injection_dict = pickle.load(fid)
326
533
  else:
327
534
  injection_dict = None
535
+
536
+ # Build params from config and param_secrets (migrate params to correct config catagories), save result to params.pkl
537
+ params = build_params(emhass_conf, params_secrets, config, app.logger)
538
+ if type(params) is bool:
539
+ raise Exception("A error has occurred while building params")
328
540
 
329
- if args.addon==1:
330
- # The cost function
331
- costfun = options.get('costfun', 'profit')
332
- # Some data from options
333
- logging_level = options.get('logging_level','INFO')
334
- url_from_options = options.get('hass_url', 'empty')
335
- if url_from_options == 'empty' or url_from_options == '' or url_from_options == "http://supervisor/core/api":
336
- url = "http://supervisor/core/api/config"
337
- else:
338
- hass_url = url_from_options
339
- url = hass_url+"api/config"
340
- token_from_options = options.get('long_lived_token', 'empty')
341
- if token_from_options == 'empty' or token_from_options == '':
342
- long_lived_token = key
343
- else:
344
- long_lived_token = token_from_options
345
- headers = {
346
- "Authorization": "Bearer " + long_lived_token,
347
- "content-type": "application/json"
348
- }
349
- if not args.no_response==1:
350
- response = get(url, headers=headers)
351
- config_hass = response.json()
352
- params_secrets = {
353
- 'hass_url': hass_url,
354
- 'long_lived_token': long_lived_token,
355
- 'time_zone': config_hass['time_zone'],
356
- 'lat': config_hass['latitude'],
357
- 'lon': config_hass['longitude'],
358
- 'alt': config_hass['elevation']
359
- }
360
- else: #if no_response is set to true
361
- costfun = os.getenv('LOCAL_COSTFUN', default='profit')
362
- logging_level = os.getenv('LOGGING_LEVEL', default='INFO')
363
- # check if secrets file exists
364
- if Path(os.getenv('SECRETS_PATH', default='/app/secrets_emhass.yaml')).is_file():
365
- with open(os.getenv('SECRETS_PATH', default='/app/secrets_emhass.yaml'), 'r') as file:
366
- params_secrets = yaml.load(file, Loader=yaml.FullLoader)
367
- app.logger.debug("Obtained secrets from secrets file")
368
- #If cant find secrets_emhass file, use env
369
- else:
370
- app.logger.debug("Failed to find secrets file: "+str(os.getenv('SECRETS_PATH', default='/app/secrets_emhass.yaml')))
371
- app.logger.debug("Setting location defaults")
372
- params_secrets = {}
373
- #If no secrets file try args, else set some defaults
374
- params_secrets['time_zone'] = os.getenv("TIME_ZONE", default="Europe/Paris")
375
- params_secrets['lat'] = float(os.getenv("LAT", default="45.83"))
376
- params_secrets['lon'] = float(os.getenv("LON", default="6.86"))
377
- params_secrets['alt'] = float(os.getenv("ALT", default="4807.8"))
378
- #If ARG/ENV specify url and key, then override secrets file
379
- if hass_url != "":
380
- params_secrets['hass_url'] = hass_url
381
- app.logger.debug("Using URL obtained from ARG/ENV")
382
- else:
383
- hass_url = params_secrets.get('hass_url',"http://localhost:8123/")
384
- if long_lived_token != "":
385
- params_secrets['long_lived_token'] = long_lived_token
386
- app.logger.debug("Using Key obtained from ARG/ENV")
387
- else: #If addon is false
388
- costfun = os.getenv('LOCAL_COSTFUN', default='profit')
389
- logging_level = os.getenv('LOGGING_LEVEL', default='INFO')
390
- if Path(os.getenv('SECRETS_PATH', default='/app/secrets_emhass.yaml')).is_file():
391
- with open(os.getenv('SECRETS_PATH', default='/app/secrets_emhass.yaml'), 'r') as file:
392
- params_secrets = yaml.load(file, Loader=yaml.FullLoader)
393
- #Check if URL and KEY are provided by file. If not attempt using values from ARG/ENV
394
- if params_secrets.get("hass_url", "empty") == "empty" or params_secrets['hass_url'] == "":
395
- app.logger.info("No specified Home Assistant URL in secrets_emhass.yaml. Attempting to get from ARG/ENV")
396
- if hass_url != "":
397
- params_secrets['hass_url'] = hass_url
398
- else:
399
- app.logger.error("Can not find Home Assistant URL from secrets_emhass.yaml or ARG/ENV")
400
- raise Exception("Can not find Home Assistant URL from secrets_emhass.yaml or ARG/ENV")
401
- else:
402
- hass_url = params_secrets['hass_url']
403
- if params_secrets.get("long_lived_token", "empty") == "empty" or params_secrets['long_lived_token'] == "":
404
- app.logger.info("No specified Home Assistant KEY in secrets_emhass.yaml. Attempting to get from ARG/ENV")
405
- if key != "":
406
- params_secrets['long_lived_token'] = key
407
- else:
408
- app.logger.error("Can not find Home Assistant KEY from secrets_emhass.yaml or ARG/ENV")
409
- raise Exception("Can not find Home Assistant KEY from secrets_emhass.yaml or ARG/ENV")
410
- else: #If no secrets file try args, else set some defaults
411
- app.logger.info("Failed to find secrets_emhass.yaml in directory:" + os.getenv('SECRETS_PATH', default='/app/secrets_emhass.yaml') )
412
- app.logger.info("Attempting to use secrets from arguments or environment variables")
413
- params_secrets = {}
414
- params_secrets['time_zone'] = os.getenv("TIME_ZONE", default="Europe/Paris")
415
- params_secrets['lat'] = float(os.getenv("LAT", default="45.83"))
416
- params_secrets['lon'] = float(os.getenv("LON", default="6.86"))
417
- params_secrets['alt'] = float(os.getenv("ALT", default="4807.8"))
418
- if hass_url != "":
419
- params_secrets['hass_url'] = hass_url
420
- else: #If cant find secrets_emhass and passed url ENV/ARG, then send error
421
- app.logger.error("No specified Home Assistant URL")
422
- raise Exception("Can not find Home Assistant URL from secrets_emhass.yaml or ARG/ENV")
423
- if key != "":
424
- params_secrets['long_lived_token'] = key
425
- else: #If cant find secrets_emhass and passed key ENV/ARG, then send error
426
- app.logger.error("No specified Home Assistant KEY")
427
- raise Exception("Can not find Home Assistant KEY from secrets_emhass.yaml or ARG/ENV")
428
- # Build params
429
- if use_options:
430
- params = build_params(params, params_secrets, options, 1, app.logger)
431
- else:
432
- params = build_params(params, params_secrets, options, args.addon, app.logger)
433
541
  if os.path.exists(str(emhass_conf['data_path'])):
434
542
  with open(str(emhass_conf['data_path'] / 'params.pkl'), "wb") as fid:
435
543
  pickle.dump((config_path, params), fid)
436
544
  else:
437
545
  raise Exception("missing: " + str(emhass_conf['data_path']))
438
546
 
439
- # Define logger
440
- #stream logger
547
+ # Define loggers
441
548
  ch = logging.StreamHandler()
442
549
  formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
443
550
  ch.setFormatter(formatter)
444
- #Action File logger
551
+ # Action file logger
445
552
  fileLogger = logging.FileHandler(str(emhass_conf['data_path'] / 'actionLogs.txt'))
446
553
  formatter = logging.Formatter('%(levelname)s - %(name)s - %(message)s')
447
554
  fileLogger.setFormatter(formatter) # add format to Handler
@@ -468,7 +575,8 @@ if __name__ == "__main__":
468
575
  app.logger.propagate = False
469
576
  app.logger.addHandler(ch)
470
577
  app.logger.addHandler(fileLogger)
471
- clearFileLog() #Clear Action File logger file, ready for new instance
578
+ # Clear Action File logger file, ready for new instance
579
+ clearFileLog()
472
580
 
473
581
  # If entity_path exists, remove any entity/metadata files
474
582
  entity_path = emhass_conf['data_path'] / "entities"
@@ -483,11 +591,13 @@ if __name__ == "__main__":
483
591
 
484
592
  # Launch server
485
593
  port = int(os.environ.get('PORT', 5000))
486
- app.logger.info("Launching the emhass webserver at: http://"+web_ui_url+":"+str(port))
487
- app.logger.info("Home Assistant data fetch will be performed using url: "+hass_url)
594
+ app.logger.info("Launching the emhass webserver at: http://"+server_ip+":"+str(port))
595
+ app.logger.info("Home Assistant data fetch will be performed using url: "+params_secrets['hass_url'])
488
596
  app.logger.info("The data path is: "+str(emhass_conf['data_path']))
597
+ app.logger.info("The logging is: "+str(logging_level))
489
598
  try:
490
599
  app.logger.info("Using core emhass version: "+version('emhass'))
491
600
  except PackageNotFoundError:
492
601
  app.logger.info("Using development emhass version")
493
- serve(app, host=web_ui_url, port=port, threads=8)
602
+ serve(app, host=server_ip, port=port, threads=8)
603
+