emhass 0.10.6__py3-none-any.whl → 0.11.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/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,204 @@ 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
+
275
+ # Save params with updated config
276
+ if os.path.exists(emhass_conf['data_path']):
277
+ with open(str(emhass_conf['data_path'] / 'params.pkl'), "wb") as fid:
278
+ pickle.dump((config_path, build_params(emhass_conf,params_secrets,config,app.logger)), fid)
279
+ else:
280
+ return make_response(["Unable to save params file, missing data_path"],500)
281
+
282
+ app.logger.info("Saved parameters from webserver")
283
+ return make_response({},201)
97
284
 
98
- #post actions
99
285
  @app.route('/action/<action_name>', methods=['POST'])
100
286
  def action_call(action_name):
287
+ """
288
+ Receive Post action, run action according to passed slug(action_name) (e.g. /action/publish-data)
289
+
290
+ :param action_name: Slug/Action string corresponding to which action to take
291
+ :type action_name: String
292
+
293
+ """
101
294
  # 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))
295
+ # Params
296
+ if (emhass_conf['data_path'] / 'params.pkl').exists():
297
+ with open(str(emhass_conf['data_path'] / 'params.pkl'), "rb") as fid:
298
+ emhass_conf['config_path'], params = pickle.load(fid)
299
+ params = json.dumps(params)
300
+ else:
301
+ app.logger.error("Unable to find params.pkl file")
302
+ return make_response(grabLog(ActionStr), 400)
303
+ # Runtime
304
+ runtimeparams = request.get_json(force=True,silent=True)
305
+ if runtimeparams is not None:
306
+ if runtimeparams != '{}':
307
+ app.logger.info("Passed runtime parameters: " + str(runtimeparams))
308
+ else:
309
+ app.logger.warning("Unable to parse runtime parameters")
310
+ runtimeparams = {}
108
311
  runtimeparams = json.dumps(runtimeparams)
109
-
110
- # Run action if weather_forecast_cache
312
+
313
+ # weather-forecast-cache (check before set_input_data_dict)
111
314
  if action_name == 'weather-forecast-cache':
112
315
  ActionStr = " >> Performing weather forecast, try to caching result"
113
316
  app.logger.info(ActionStr)
@@ -125,13 +328,15 @@ def action_call(action_name):
125
328
  return make_response(grabLog(ActionStr), 400)
126
329
 
127
330
  # 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):
331
+ if len(continual_publish_thread) == 0 and input_data_dict['retrieve_hass_conf'].get('continual_publish',False):
129
332
  # Start Thread
130
- continualLoop = threading.Thread(name="continual_publish",target=continual_publish,args=[input_data_dict,entity_path,app.logger])
333
+ continualLoop = threading.Thread(name='continual_publish',target=continual_publish,args=[input_data_dict,entity_path,app.logger])
131
334
  continualLoop.start()
132
335
  continual_publish_thread.append(continualLoop)
133
-
134
- # run action based on POST request
336
+
337
+ # Run action based on POST request
338
+ # 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)
339
+ # publish-data
135
340
  if action_name == 'publish-data':
136
341
  ActionStr = " >> Publishing data..."
137
342
  app.logger.info(ActionStr)
@@ -140,6 +345,7 @@ def action_call(action_name):
140
345
  if not checkFileLog(ActionStr):
141
346
  return make_response(msg, 201)
142
347
  return make_response(grabLog(ActionStr), 400)
348
+ # perfect-optim
143
349
  elif action_name == 'perfect-optim':
144
350
  ActionStr = " >> Performing perfect optimization..."
145
351
  app.logger.info(ActionStr)
@@ -151,6 +357,7 @@ def action_call(action_name):
151
357
  if not checkFileLog(ActionStr):
152
358
  return make_response(msg, 201)
153
359
  return make_response(grabLog(ActionStr), 400)
360
+ # dayahead-optim
154
361
  elif action_name == 'dayahead-optim':
155
362
  ActionStr = " >> Performing dayahead optimization..."
156
363
  app.logger.info(ActionStr)
@@ -162,6 +369,7 @@ def action_call(action_name):
162
369
  if not checkFileLog(ActionStr):
163
370
  return make_response(msg, 201)
164
371
  return make_response(grabLog(ActionStr), 400)
372
+ # naive-mpc-optim
165
373
  elif action_name == 'naive-mpc-optim':
166
374
  ActionStr = " >> Performing naive MPC optimization..."
167
375
  app.logger.info(ActionStr)
@@ -173,6 +381,7 @@ def action_call(action_name):
173
381
  if not checkFileLog(ActionStr):
174
382
  return make_response(msg, 201)
175
383
  return make_response(grabLog(ActionStr), 400)
384
+ # forecast-model-fit
176
385
  elif action_name == 'forecast-model-fit':
177
386
  ActionStr = " >> Performing a machine learning forecast model fit..."
178
387
  app.logger.info(ActionStr)
@@ -185,6 +394,7 @@ def action_call(action_name):
185
394
  if not checkFileLog(ActionStr):
186
395
  return make_response(msg, 201)
187
396
  return make_response(grabLog(ActionStr), 400)
397
+ # forecast-model-predict
188
398
  elif action_name == 'forecast-model-predict':
189
399
  ActionStr = " >> Performing a machine learning forecast model predict..."
190
400
  app.logger.info(ActionStr)
@@ -202,6 +412,7 @@ def action_call(action_name):
202
412
  if not checkFileLog(ActionStr):
203
413
  return make_response(msg, 201)
204
414
  return make_response(grabLog(ActionStr), 400)
415
+ # forecast-model-tune
205
416
  elif action_name == 'forecast-model-tune':
206
417
  ActionStr = " >> Performing a machine learning forecast model tune..."
207
418
  app.logger.info(ActionStr)
@@ -216,6 +427,7 @@ def action_call(action_name):
216
427
  if not checkFileLog(ActionStr):
217
428
  return make_response(msg, 201)
218
429
  return make_response(grabLog(ActionStr), 400)
430
+ # regressor-model-fit
219
431
  elif action_name == 'regressor-model-fit':
220
432
  ActionStr = " >> Performing a machine learning regressor fit..."
221
433
  app.logger.info(ActionStr)
@@ -224,6 +436,7 @@ def action_call(action_name):
224
436
  if not checkFileLog(ActionStr):
225
437
  return make_response(msg, 201)
226
438
  return make_response(grabLog(ActionStr), 400)
439
+ # regressor-model-predict
227
440
  elif action_name == 'regressor-model-predict':
228
441
  ActionStr = " >> Performing a machine learning regressor predict..."
229
442
  app.logger.info(ActionStr)
@@ -232,6 +445,7 @@ def action_call(action_name):
232
445
  if not checkFileLog(ActionStr):
233
446
  return make_response(msg, 201)
234
447
  return make_response(grabLog(ActionStr), 400)
448
+ # Else return error
235
449
  else:
236
450
  app.logger.error("ERROR: passed action is not valid")
237
451
  msg = f'EMHASS >> ERROR: Passed action is not valid... \n'
@@ -242,82 +456,74 @@ if __name__ == "__main__":
242
456
  parser = argparse.ArgumentParser()
243
457
  parser.add_argument('--url', type=str, help='The URL to your Home Assistant instance, ex the external_url in your hass configuration')
244
458
  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
459
  parser.add_argument('--no_response', type=strtobool, default='False', help='This is set if json response errors occur')
247
460
  args = parser.parse_args()
461
+
462
+ # Pre formatted config parameters
463
+ config = {}
464
+ # Secrets
465
+ params_secrets = {}
466
+ # Built parameters (formatted config + secrets)
467
+ params = None
248
468
 
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")
469
+ # Find env's, not not set defaults
262
470
  DATA_PATH = os.getenv("DATA_PATH", default="/app/data/")
263
471
  ROOT_PATH = os.getenv("ROOT_PATH", default=str(Path(__file__).parent))
264
-
265
- #options None by default
266
- options = None
472
+ CONFIG_PATH = os.getenv('CONFIG_PATH', default="/share/config.json")
473
+ OPTIONS_PATH = os.getenv('OPTIONS_PATH', default="/data/options.json")
474
+ DEFAULTS_PATH = os.getenv('DEFAULTS_PATH', default=ROOT_PATH +"/data/config_defaults.json")
475
+ ASSOCIATIONS_PATH = os.getenv('ASSOCIATIONS_PATH', default=ROOT_PATH + "/data/associations.csv")
476
+ LEGACY_CONFIG_PATH = os.getenv("LEGACY_CONFIG_PATH", default="/app/config_emhass.yaml")
267
477
 
268
478
  # 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
479
  config_path = Path(CONFIG_PATH)
480
+ options_path = Path(OPTIONS_PATH)
481
+ defaults_path = Path(DEFAULTS_PATH)
482
+ associations_path = Path(ASSOCIATIONS_PATH)
483
+ legacy_config_path = Path(LEGACY_CONFIG_PATH)
298
484
  data_path = Path(DATA_PATH)
299
485
  root_path = Path(ROOT_PATH)
300
- emhass_conf = {}
486
+ # Add paths to emhass_conf
301
487
  emhass_conf['config_path'] = config_path
488
+ emhass_conf['options_path'] = options_path
489
+ emhass_conf['defaults_path'] = defaults_path
490
+ emhass_conf['associations_path'] = associations_path
491
+ emhass_conf['legacy_config_path'] = legacy_config_path
302
492
  emhass_conf['data_path'] = data_path
303
493
  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
494
 
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'
495
+ # Combine parameters from configuration sources (if exists)
496
+ config.update(build_config(emhass_conf,app.logger,defaults_path,config_path,legacy_config_path))
497
+ if type(config) is bool and not config:
498
+ raise Exception("Failed to find default config")
499
+
500
+ costfun = os.getenv('LOCAL_COSTFUN', config.get('costfun', 'profit'))
501
+ logging_level = os.getenv('LOGGING_LEVEL', config.get('logging_level','INFO'))
502
+ # Temporary set logging level if debug
503
+ if logging_level == "DEBUG":
504
+ app.logger.setLevel(logging.DEBUG)
505
+
506
+ ## Secrets
507
+ argument = {}
508
+ if args.url:
509
+ argument['url'] = args.url
510
+ if args.key:
511
+ argument['key'] = args.key
512
+ # 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)
513
+ 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))
514
+ params_secrets.update(secrets)
515
+
516
+ server_ip = params_secrets.get("server_ip","0.0.0.0")
517
+
518
+ # Check if data path exists
519
+ if not os.path.isdir(emhass_conf['data_path']):
520
+ app.logger.warning("Unable to find data_path: " + str(emhass_conf['data_path']))
521
+ if os.path.isdir(Path("/app/data/")):
522
+ emhass_conf['data_path'] = Path("/app/data/")
523
+ else:
524
+ Path(root_path / "data/").mkdir(parents=True, exist_ok=True)
525
+ emhass_conf['data_path'] = root_path / "data/"
526
+ app.logger.info("data_path has been set to " + str(emhass_conf['data_path']))
321
527
 
322
528
  # Initialize this global dict
323
529
  if (emhass_conf['data_path'] / 'injection_dict.pkl').exists():
@@ -325,123 +531,23 @@ if __name__ == "__main__":
325
531
  injection_dict = pickle.load(fid)
326
532
  else:
327
533
  injection_dict = None
534
+
535
+ # Build params from config and param_secrets (migrate params to correct config catagories), save result to params.pkl
536
+ params = build_params(emhass_conf, params_secrets, config, app.logger)
537
+ if type(params) is bool:
538
+ raise Exception("A error has occurred while building params")
328
539
 
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
540
  if os.path.exists(str(emhass_conf['data_path'])):
434
541
  with open(str(emhass_conf['data_path'] / 'params.pkl'), "wb") as fid:
435
542
  pickle.dump((config_path, params), fid)
436
543
  else:
437
544
  raise Exception("missing: " + str(emhass_conf['data_path']))
438
545
 
439
- # Define logger
440
- #stream logger
546
+ # Define loggers
441
547
  ch = logging.StreamHandler()
442
548
  formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
443
549
  ch.setFormatter(formatter)
444
- #Action File logger
550
+ # Action file logger
445
551
  fileLogger = logging.FileHandler(str(emhass_conf['data_path'] / 'actionLogs.txt'))
446
552
  formatter = logging.Formatter('%(levelname)s - %(name)s - %(message)s')
447
553
  fileLogger.setFormatter(formatter) # add format to Handler
@@ -468,7 +574,8 @@ if __name__ == "__main__":
468
574
  app.logger.propagate = False
469
575
  app.logger.addHandler(ch)
470
576
  app.logger.addHandler(fileLogger)
471
- clearFileLog() #Clear Action File logger file, ready for new instance
577
+ # Clear Action File logger file, ready for new instance
578
+ clearFileLog()
472
579
 
473
580
  # If entity_path exists, remove any entity/metadata files
474
581
  entity_path = emhass_conf['data_path'] / "entities"
@@ -483,11 +590,13 @@ if __name__ == "__main__":
483
590
 
484
591
  # Launch server
485
592
  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)
593
+ app.logger.info("Launching the emhass webserver at: http://"+server_ip+":"+str(port))
594
+ app.logger.info("Home Assistant data fetch will be performed using url: "+params_secrets['hass_url'])
488
595
  app.logger.info("The data path is: "+str(emhass_conf['data_path']))
596
+ app.logger.info("The logging is: "+str(logging_level))
489
597
  try:
490
598
  app.logger.info("Using core emhass version: "+version('emhass'))
491
599
  except PackageNotFoundError:
492
600
  app.logger.info("Using development emhass version")
493
- serve(app, host=web_ui_url, port=port, threads=8)
601
+ serve(app, host=server_ip, port=port, threads=8)
602
+