emhass 0.12.4__py3-none-any.whl → 0.12.6__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-0.12.4.dist-info → emhass-0.12.6.dist-info}/METADATA +34 -17
- {emhass-0.12.4.dist-info → emhass-0.12.6.dist-info}/RECORD +5 -17
- emhass/__init__.py +0 -0
- emhass/command_line.py +0 -1748
- emhass/data/emhass_inverters.csv +0 -8
- emhass/data/emhass_modules.csv +0 -6
- emhass/forecast.py +0 -1348
- emhass/img/emhass_icon.png +0 -0
- emhass/machine_learning_forecaster.py +0 -397
- emhass/machine_learning_regressor.py +0 -275
- emhass/optimization.py +0 -1504
- emhass/retrieve_hass.py +0 -670
- emhass/utils.py +0 -1678
- emhass/web_server.py +0 -756
- {emhass-0.12.4.dist-info → emhass-0.12.6.dist-info}/WHEEL +0 -0
- {emhass-0.12.4.dist-info → emhass-0.12.6.dist-info}/entry_points.txt +0 -0
- {emhass-0.12.4.dist-info → emhass-0.12.6.dist-info}/licenses/LICENSE +0 -0
emhass/web_server.py
DELETED
@@ -1,756 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
# -*- coding: utf-8 -*-
|
3
|
-
|
4
|
-
import argparse
|
5
|
-
import json
|
6
|
-
import logging
|
7
|
-
import os
|
8
|
-
import pickle
|
9
|
-
import re
|
10
|
-
import threading
|
11
|
-
from importlib.metadata import PackageNotFoundError, version
|
12
|
-
from pathlib import Path
|
13
|
-
from typing import Optional
|
14
|
-
|
15
|
-
import yaml
|
16
|
-
from flask import Flask, make_response, request
|
17
|
-
from flask import logging as log
|
18
|
-
from jinja2 import Environment, PackageLoader
|
19
|
-
from waitress import serve
|
20
|
-
|
21
|
-
from emhass.command_line import (
|
22
|
-
continual_publish,
|
23
|
-
dayahead_forecast_optim,
|
24
|
-
forecast_model_fit,
|
25
|
-
forecast_model_predict,
|
26
|
-
forecast_model_tune,
|
27
|
-
naive_mpc_optim,
|
28
|
-
perfect_forecast_optim,
|
29
|
-
publish_data,
|
30
|
-
regressor_model_fit,
|
31
|
-
regressor_model_predict,
|
32
|
-
set_input_data_dict,
|
33
|
-
weather_forecast_cache,
|
34
|
-
)
|
35
|
-
from emhass.utils import (
|
36
|
-
build_config,
|
37
|
-
build_legacy_config_params,
|
38
|
-
build_params,
|
39
|
-
build_secrets,
|
40
|
-
get_injection_dict,
|
41
|
-
get_injection_dict_forecast_model_fit,
|
42
|
-
get_injection_dict_forecast_model_tune,
|
43
|
-
param_to_config,
|
44
|
-
)
|
45
|
-
|
46
|
-
# Define the Flask instance
|
47
|
-
app = Flask(__name__)
|
48
|
-
|
49
|
-
emhass_conf = {}
|
50
|
-
entity_path = Path
|
51
|
-
params_secrets = {}
|
52
|
-
continual_publish_thread = []
|
53
|
-
injection_dict = {}
|
54
|
-
|
55
|
-
|
56
|
-
def create_app(settings_override=None):
|
57
|
-
"""
|
58
|
-
Create a Flask application.
|
59
|
-
:param settings_override: Override settings
|
60
|
-
:return: Flask app
|
61
|
-
"""
|
62
|
-
global app
|
63
|
-
gunicorn_logger = logging.getLogger("gunicorn.error")
|
64
|
-
app.logger.handlers = gunicorn_logger.handlers
|
65
|
-
app.logger.setLevel(logging.INFO)
|
66
|
-
main()
|
67
|
-
return app
|
68
|
-
|
69
|
-
|
70
|
-
def checkFileLog(refString=None) -> bool:
|
71
|
-
"""
|
72
|
-
Check logfile for error, anything after string match if provided.
|
73
|
-
|
74
|
-
:param refString: String to reduce log area to check for errors. Use to reduce log to check anything after string match (ie. an action).
|
75
|
-
:type refString: str
|
76
|
-
:return: Boolean return if error was found in logs
|
77
|
-
:rtype: bool
|
78
|
-
|
79
|
-
"""
|
80
|
-
if refString is not None:
|
81
|
-
logArray = grabLog(
|
82
|
-
refString
|
83
|
-
) # grab reduced log array (everything after string match)
|
84
|
-
else:
|
85
|
-
if (emhass_conf["data_path"] / "actionLogs.txt").exists():
|
86
|
-
with open(str(emhass_conf["data_path"] / "actionLogs.txt"), "r") as fp:
|
87
|
-
logArray = fp.readlines()
|
88
|
-
else:
|
89
|
-
app.logger.debug("Unable to obtain actionLogs.txt")
|
90
|
-
for logString in logArray:
|
91
|
-
if logString.split(" ", 1)[0] == "ERROR":
|
92
|
-
return True
|
93
|
-
return False
|
94
|
-
|
95
|
-
|
96
|
-
def grabLog(refString) -> list:
|
97
|
-
"""
|
98
|
-
Find string in logs, append all lines after into list to return.
|
99
|
-
|
100
|
-
:param refString: String used to string match log.
|
101
|
-
:type refString: str
|
102
|
-
:return: List of lines in log after string match.
|
103
|
-
:rtype: list
|
104
|
-
|
105
|
-
"""
|
106
|
-
isFound = []
|
107
|
-
output = []
|
108
|
-
if (emhass_conf["data_path"] / "actionLogs.txt").exists():
|
109
|
-
with open(str(emhass_conf["data_path"] / "actionLogs.txt"), "r") as fp:
|
110
|
-
logArray = fp.readlines()
|
111
|
-
# Find all string matches, log key (line Number) in isFound
|
112
|
-
for x in range(len(logArray) - 1):
|
113
|
-
if re.search(refString, logArray[x]):
|
114
|
-
isFound.append(x)
|
115
|
-
if len(isFound) != 0:
|
116
|
-
# Use last item in isFound to extract action logs
|
117
|
-
for x in range(isFound[-1], len(logArray)):
|
118
|
-
output.append(logArray[x])
|
119
|
-
return output
|
120
|
-
|
121
|
-
|
122
|
-
# Clear the log file
|
123
|
-
def clearFileLog():
|
124
|
-
"""
|
125
|
-
Clear the contents of the log file (actionLogs.txt)
|
126
|
-
|
127
|
-
"""
|
128
|
-
if (emhass_conf["data_path"] / "actionLogs.txt").exists():
|
129
|
-
with open(str(emhass_conf["data_path"] / "actionLogs.txt"), "w") as fp:
|
130
|
-
fp.truncate()
|
131
|
-
|
132
|
-
|
133
|
-
@app.route("/")
|
134
|
-
@app.route("/index")
|
135
|
-
def index():
|
136
|
-
"""
|
137
|
-
Render initial index page and serve to web server.
|
138
|
-
Appends plot tables saved from previous optimization into index.html, then serves.
|
139
|
-
|
140
|
-
"""
|
141
|
-
app.logger.info("EMHASS server online, serving index.html...")
|
142
|
-
# Load HTML template
|
143
|
-
file_loader = PackageLoader("emhass", "templates")
|
144
|
-
env = Environment(loader=file_loader)
|
145
|
-
# check if index.html exists
|
146
|
-
if "index.html" not in env.list_templates():
|
147
|
-
app.logger.error("Unable to find index.html in emhass module")
|
148
|
-
return make_response(["ERROR: unable to find index.html in emhass module"], 404)
|
149
|
-
template = env.get_template("index.html")
|
150
|
-
# Load cached dict (if exists), to present generated plot tables
|
151
|
-
if (emhass_conf["data_path"] / "injection_dict.pkl").exists():
|
152
|
-
with open(str(emhass_conf["data_path"] / "injection_dict.pkl"), "rb") as fid:
|
153
|
-
injection_dict = pickle.load(fid)
|
154
|
-
else:
|
155
|
-
app.logger.info(
|
156
|
-
"The data container dictionary is empty... Please launch an optimization task"
|
157
|
-
)
|
158
|
-
injection_dict = {}
|
159
|
-
|
160
|
-
# replace {{basename}} in html template html with path root
|
161
|
-
# basename = request.headers.get("X-Ingress-Path", "")
|
162
|
-
# return make_response(template.render(injection_dict=injection_dict, basename=basename))
|
163
|
-
|
164
|
-
return make_response(template.render(injection_dict=injection_dict))
|
165
|
-
|
166
|
-
|
167
|
-
@app.route("/configuration")
|
168
|
-
def configuration():
|
169
|
-
"""
|
170
|
-
Configuration page actions:
|
171
|
-
Render and serve configuration page html
|
172
|
-
|
173
|
-
"""
|
174
|
-
app.logger.info("serving configuration.html...")
|
175
|
-
# get params
|
176
|
-
if (emhass_conf["data_path"] / "params.pkl").exists():
|
177
|
-
with open(str(emhass_conf["data_path"] / "params.pkl"), "rb") as fid:
|
178
|
-
emhass_conf["config_path"], params = pickle.load(fid)
|
179
|
-
# Load HTML template
|
180
|
-
file_loader = PackageLoader("emhass", "templates")
|
181
|
-
env = Environment(loader=file_loader)
|
182
|
-
# check if configuration.html exists
|
183
|
-
if "configuration.html" not in env.list_templates():
|
184
|
-
app.logger.error("Unable to find configuration.html in emhass module")
|
185
|
-
return make_response(
|
186
|
-
["ERROR: unable to find configuration.html in emhass module"], 404
|
187
|
-
)
|
188
|
-
template = env.get_template("configuration.html")
|
189
|
-
return make_response(template.render(config=params))
|
190
|
-
|
191
|
-
|
192
|
-
@app.route("/template", methods=["GET"])
|
193
|
-
def template_action():
|
194
|
-
"""
|
195
|
-
template page actions:
|
196
|
-
Render and serve template html
|
197
|
-
|
198
|
-
"""
|
199
|
-
app.logger.info(" >> Sending rendered template table data")
|
200
|
-
file_loader = PackageLoader("emhass", "templates")
|
201
|
-
env = Environment(loader=file_loader)
|
202
|
-
# Check if template.html exists
|
203
|
-
if "template.html" not in env.list_templates():
|
204
|
-
app.logger.error("Unable to find template.html in emhass module")
|
205
|
-
return make_response(
|
206
|
-
["WARNING: unable to find template.html in emhass module"], 404
|
207
|
-
)
|
208
|
-
template = env.get_template("template.html")
|
209
|
-
if (emhass_conf["data_path"] / "injection_dict.pkl").exists():
|
210
|
-
with open(str(emhass_conf["data_path"] / "injection_dict.pkl"), "rb") as fid:
|
211
|
-
injection_dict = pickle.load(fid)
|
212
|
-
else:
|
213
|
-
app.logger.warning("Unable to obtain plot data from injection_dict.pkl")
|
214
|
-
app.logger.warning("Try running an launch an optimization task")
|
215
|
-
injection_dict = {}
|
216
|
-
return make_response(template.render(injection_dict=injection_dict))
|
217
|
-
|
218
|
-
|
219
|
-
@app.route("/get-config", methods=["GET"])
|
220
|
-
def parameter_get():
|
221
|
-
"""
|
222
|
-
Get request action that builds, formats and sends config as json (config.json format)
|
223
|
-
|
224
|
-
"""
|
225
|
-
app.logger.debug("Obtaining current saved parameters as config")
|
226
|
-
# Build config from all possible sources (inc. legacy yaml config)
|
227
|
-
config = build_config(
|
228
|
-
emhass_conf,
|
229
|
-
app.logger,
|
230
|
-
emhass_conf["defaults_path"],
|
231
|
-
emhass_conf["config_path"],
|
232
|
-
emhass_conf["legacy_config_path"],
|
233
|
-
)
|
234
|
-
if type(config) is bool and not config:
|
235
|
-
return make_response(["failed to retrieve default config file"], 500)
|
236
|
-
# Format parameters in config with params (converting legacy json parameters from options.json if any)
|
237
|
-
params = build_params(emhass_conf, {}, config, app.logger)
|
238
|
-
if type(params) is bool and not params:
|
239
|
-
return make_response(["Unable to obtain associations file"], 500)
|
240
|
-
# Covert formatted parameters from params back into config.json format
|
241
|
-
return_config = param_to_config(params, app.logger)
|
242
|
-
# Send config
|
243
|
-
return make_response(return_config, 201)
|
244
|
-
|
245
|
-
|
246
|
-
# Get default Config
|
247
|
-
@app.route("/get-config/defaults", methods=["GET"])
|
248
|
-
def config_get():
|
249
|
-
"""
|
250
|
-
Get request action, retrieves and sends default configuration
|
251
|
-
|
252
|
-
"""
|
253
|
-
app.logger.debug("Obtaining default parameters")
|
254
|
-
# Build config, passing only default file
|
255
|
-
config = build_config(emhass_conf, app.logger, emhass_conf["defaults_path"])
|
256
|
-
if type(config) is bool and not config:
|
257
|
-
return make_response(["failed to retrieve default config file"], 500)
|
258
|
-
# Format parameters in config with params
|
259
|
-
params = build_params(emhass_conf, {}, config, app.logger)
|
260
|
-
if type(params) is bool and not params:
|
261
|
-
return make_response(["Unable to obtain associations file"], 500)
|
262
|
-
# Covert formatted parameters from params back into config.json format
|
263
|
-
return_config = param_to_config(params, app.logger)
|
264
|
-
# Send params
|
265
|
-
return make_response(return_config, 201)
|
266
|
-
|
267
|
-
|
268
|
-
# Get YAML-to-JSON config
|
269
|
-
@app.route("/get-json", methods=["POST"])
|
270
|
-
def json_convert():
|
271
|
-
"""
|
272
|
-
Post request action, receives yaml config (config_emhass.yaml or EMHASS-Add-on config page) and converts to config json format.
|
273
|
-
|
274
|
-
"""
|
275
|
-
app.logger.info("Attempting to convert YAML to JSON")
|
276
|
-
data = request.get_data()
|
277
|
-
yaml_config = yaml.safe_load(data)
|
278
|
-
|
279
|
-
# If filed to Parse YAML
|
280
|
-
if yaml_config is None:
|
281
|
-
return make_response(["failed to Parse YAML from data"], 400)
|
282
|
-
# Test YAML is legacy config format (from config_emhass.yaml)
|
283
|
-
test_legacy_config = build_legacy_config_params(
|
284
|
-
emhass_conf, yaml_config, app.logger
|
285
|
-
)
|
286
|
-
if test_legacy_config:
|
287
|
-
yaml_config = test_legacy_config
|
288
|
-
# Format YAML to params (format params. check if params match legacy option.json format)
|
289
|
-
params = build_params(emhass_conf, {}, yaml_config, app.logger)
|
290
|
-
if type(params) is bool and not params:
|
291
|
-
return make_response(["Unable to obtain associations file"], 500)
|
292
|
-
# Covert formatted parameters from params back into config.json format
|
293
|
-
config = param_to_config(params, app.logger)
|
294
|
-
# convert json to str
|
295
|
-
config = json.dumps(config)
|
296
|
-
|
297
|
-
# Send params
|
298
|
-
return make_response(config, 201)
|
299
|
-
|
300
|
-
|
301
|
-
@app.route("/set-config", methods=["POST"])
|
302
|
-
def parameter_set():
|
303
|
-
"""
|
304
|
-
Receive JSON config, and save config to file (config.json and param.pkl)
|
305
|
-
|
306
|
-
"""
|
307
|
-
config = {}
|
308
|
-
if not emhass_conf["defaults_path"]:
|
309
|
-
return make_response(["Unable to Obtain defaults_path from emhass_conf"], 500)
|
310
|
-
if not emhass_conf["config_path"]:
|
311
|
-
return make_response(["Unable to Obtain config_path from emhass_conf"], 500)
|
312
|
-
|
313
|
-
# Load defaults as a reference point (for sorting) and a base to override
|
314
|
-
if (
|
315
|
-
os.path.exists(emhass_conf["defaults_path"])
|
316
|
-
and Path(emhass_conf["defaults_path"]).is_file()
|
317
|
-
):
|
318
|
-
with emhass_conf["defaults_path"].open("r") as data:
|
319
|
-
config = json.load(data)
|
320
|
-
else:
|
321
|
-
app.logger.warning(
|
322
|
-
"Unable to obtain default config. only parameters passed from request will be saved to config.json"
|
323
|
-
)
|
324
|
-
|
325
|
-
# Retrieve sent config json
|
326
|
-
request_data = request.get_json(force=True)
|
327
|
-
|
328
|
-
# check if data is empty
|
329
|
-
if len(request_data) == 0:
|
330
|
-
return make_response(["failed to retrieve config json"], 400)
|
331
|
-
|
332
|
-
# Format config by converting to params (format params. check if params match legacy option.json format. If so format)
|
333
|
-
params = build_params(emhass_conf, params_secrets, request_data, app.logger)
|
334
|
-
if type(params) is bool and not params:
|
335
|
-
return make_response(["Unable to obtain associations file"], 500)
|
336
|
-
|
337
|
-
# Covert formatted parameters from params back into config.json format.
|
338
|
-
# Overwrite existing default parameters in config
|
339
|
-
config.update(param_to_config(params, app.logger))
|
340
|
-
|
341
|
-
# Save config to config.json
|
342
|
-
if os.path.exists(emhass_conf["config_path"].parent):
|
343
|
-
with emhass_conf["config_path"].open("w") as f:
|
344
|
-
json.dump(config, f, indent=4)
|
345
|
-
else:
|
346
|
-
return make_response(["Unable to save config file"], 500)
|
347
|
-
request_data
|
348
|
-
|
349
|
-
# Save params with updated config
|
350
|
-
if os.path.exists(emhass_conf["data_path"]):
|
351
|
-
with open(str(emhass_conf["data_path"] / "params.pkl"), "wb") as fid:
|
352
|
-
pickle.dump(
|
353
|
-
(
|
354
|
-
emhass_conf["config_path"],
|
355
|
-
build_params(emhass_conf, params_secrets, config, app.logger),
|
356
|
-
),
|
357
|
-
fid,
|
358
|
-
)
|
359
|
-
else:
|
360
|
-
return make_response(["Unable to save params file, missing data_path"], 500)
|
361
|
-
|
362
|
-
app.logger.info("Saved parameters from webserver")
|
363
|
-
return make_response({}, 201)
|
364
|
-
|
365
|
-
|
366
|
-
@app.route("/action/<action_name>", methods=["POST"])
|
367
|
-
def action_call(action_name):
|
368
|
-
"""
|
369
|
-
Receive Post action, run action according to passed slug(action_name) (e.g. /action/publish-data)
|
370
|
-
|
371
|
-
:param action_name: Slug/Action string corresponding to which action to take
|
372
|
-
:type action_name: String
|
373
|
-
|
374
|
-
"""
|
375
|
-
global continual_publish_thread
|
376
|
-
global injection_dict
|
377
|
-
|
378
|
-
# Setting up parameters
|
379
|
-
# Params
|
380
|
-
ActionStr = " >> Obtaining params: "
|
381
|
-
app.logger.info(ActionStr)
|
382
|
-
if (emhass_conf["data_path"] / "params.pkl").exists():
|
383
|
-
with open(str(emhass_conf["data_path"] / "params.pkl"), "rb") as fid:
|
384
|
-
emhass_conf["config_path"], params = pickle.load(fid)
|
385
|
-
# Set local costfun variable
|
386
|
-
if params.get("optim_conf", None) is not None:
|
387
|
-
costfun = params["optim_conf"].get("costfun", "profit")
|
388
|
-
params = json.dumps(params)
|
389
|
-
else:
|
390
|
-
app.logger.error("Unable to find params.pkl file")
|
391
|
-
return make_response(grabLog(ActionStr), 400)
|
392
|
-
# Runtime
|
393
|
-
runtimeparams = request.get_json(force=True, silent=True)
|
394
|
-
if runtimeparams is not None:
|
395
|
-
if runtimeparams != "{}":
|
396
|
-
app.logger.info("Passed runtime parameters: " + str(runtimeparams))
|
397
|
-
else:
|
398
|
-
app.logger.warning("Unable to parse runtime parameters")
|
399
|
-
runtimeparams = {}
|
400
|
-
runtimeparams = json.dumps(runtimeparams)
|
401
|
-
|
402
|
-
# weather-forecast-cache (check before set_input_data_dict)
|
403
|
-
if action_name == "weather-forecast-cache":
|
404
|
-
ActionStr = " >> Performing weather forecast, try to caching result"
|
405
|
-
app.logger.info(ActionStr)
|
406
|
-
weather_forecast_cache(emhass_conf, params, runtimeparams, app.logger)
|
407
|
-
msg = "EMHASS >> Weather Forecast has run and results possibly cached... \n"
|
408
|
-
if not checkFileLog(ActionStr):
|
409
|
-
return make_response(msg, 201)
|
410
|
-
return make_response(grabLog(ActionStr), 400)
|
411
|
-
|
412
|
-
ActionStr = " >> Setting input data dict"
|
413
|
-
app.logger.info(ActionStr)
|
414
|
-
input_data_dict = set_input_data_dict(
|
415
|
-
emhass_conf, costfun, params, runtimeparams, action_name, app.logger
|
416
|
-
)
|
417
|
-
if not input_data_dict:
|
418
|
-
return make_response(grabLog(ActionStr), 400)
|
419
|
-
|
420
|
-
# If continual_publish is True, start thread with loop function
|
421
|
-
if len(continual_publish_thread) == 0 and input_data_dict["retrieve_hass_conf"].get(
|
422
|
-
"continual_publish", False
|
423
|
-
):
|
424
|
-
# Start Thread
|
425
|
-
continualLoop = threading.Thread(
|
426
|
-
name="continual_publish",
|
427
|
-
target=continual_publish,
|
428
|
-
args=[input_data_dict, entity_path, app.logger],
|
429
|
-
)
|
430
|
-
continualLoop.start()
|
431
|
-
continual_publish_thread.append(continualLoop)
|
432
|
-
|
433
|
-
# Run action based on POST request
|
434
|
-
# 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)
|
435
|
-
# publish-data
|
436
|
-
if action_name == "publish-data":
|
437
|
-
ActionStr = " >> Publishing data..."
|
438
|
-
app.logger.info(ActionStr)
|
439
|
-
_ = publish_data(input_data_dict, app.logger)
|
440
|
-
msg = "EMHASS >> Action publish-data executed... \n"
|
441
|
-
if not checkFileLog(ActionStr):
|
442
|
-
return make_response(msg, 201)
|
443
|
-
return make_response(grabLog(ActionStr), 400)
|
444
|
-
# perfect-optim
|
445
|
-
elif action_name == "perfect-optim":
|
446
|
-
ActionStr = " >> Performing perfect optimization..."
|
447
|
-
app.logger.info(ActionStr)
|
448
|
-
opt_res = perfect_forecast_optim(input_data_dict, app.logger)
|
449
|
-
injection_dict = get_injection_dict(opt_res)
|
450
|
-
with open(str(emhass_conf["data_path"] / "injection_dict.pkl"), "wb") as fid:
|
451
|
-
pickle.dump(injection_dict, fid)
|
452
|
-
msg = "EMHASS >> Action perfect-optim executed... \n"
|
453
|
-
if not checkFileLog(ActionStr):
|
454
|
-
return make_response(msg, 201)
|
455
|
-
return make_response(grabLog(ActionStr), 400)
|
456
|
-
# dayahead-optim
|
457
|
-
elif action_name == "dayahead-optim":
|
458
|
-
ActionStr = " >> Performing dayahead optimization..."
|
459
|
-
app.logger.info(ActionStr)
|
460
|
-
opt_res = dayahead_forecast_optim(input_data_dict, app.logger)
|
461
|
-
injection_dict = get_injection_dict(opt_res)
|
462
|
-
with open(str(emhass_conf["data_path"] / "injection_dict.pkl"), "wb") as fid:
|
463
|
-
pickle.dump(injection_dict, fid)
|
464
|
-
msg = "EMHASS >> Action dayahead-optim executed... \n"
|
465
|
-
if not checkFileLog(ActionStr):
|
466
|
-
return make_response(msg, 201)
|
467
|
-
return make_response(grabLog(ActionStr), 400)
|
468
|
-
# naive-mpc-optim
|
469
|
-
elif action_name == "naive-mpc-optim":
|
470
|
-
ActionStr = " >> Performing naive MPC optimization..."
|
471
|
-
app.logger.info(ActionStr)
|
472
|
-
opt_res = naive_mpc_optim(input_data_dict, app.logger)
|
473
|
-
injection_dict = get_injection_dict(opt_res)
|
474
|
-
with open(str(emhass_conf["data_path"] / "injection_dict.pkl"), "wb") as fid:
|
475
|
-
pickle.dump(injection_dict, fid)
|
476
|
-
msg = "EMHASS >> Action naive-mpc-optim executed... \n"
|
477
|
-
if not checkFileLog(ActionStr):
|
478
|
-
return make_response(msg, 201)
|
479
|
-
return make_response(grabLog(ActionStr), 400)
|
480
|
-
# forecast-model-fit
|
481
|
-
elif action_name == "forecast-model-fit":
|
482
|
-
ActionStr = " >> Performing a machine learning forecast model fit..."
|
483
|
-
app.logger.info(ActionStr)
|
484
|
-
df_fit_pred, _, mlf = forecast_model_fit(input_data_dict, app.logger)
|
485
|
-
injection_dict = get_injection_dict_forecast_model_fit(df_fit_pred, mlf)
|
486
|
-
with open(str(emhass_conf["data_path"] / "injection_dict.pkl"), "wb") as fid:
|
487
|
-
pickle.dump(injection_dict, fid)
|
488
|
-
msg = "EMHASS >> Action forecast-model-fit executed... \n"
|
489
|
-
if not checkFileLog(ActionStr):
|
490
|
-
return make_response(msg, 201)
|
491
|
-
return make_response(grabLog(ActionStr), 400)
|
492
|
-
# forecast-model-predict
|
493
|
-
elif action_name == "forecast-model-predict":
|
494
|
-
ActionStr = " >> Performing a machine learning forecast model predict..."
|
495
|
-
app.logger.info(ActionStr)
|
496
|
-
df_pred = forecast_model_predict(input_data_dict, app.logger)
|
497
|
-
if df_pred is None:
|
498
|
-
return make_response(grabLog(ActionStr), 400)
|
499
|
-
table1 = df_pred.reset_index().to_html(classes="mystyle", index=False)
|
500
|
-
injection_dict = {}
|
501
|
-
injection_dict["title"] = (
|
502
|
-
"<h2>Custom machine learning forecast model predict</h2>"
|
503
|
-
)
|
504
|
-
injection_dict["subsubtitle0"] = (
|
505
|
-
"<h4>Performed a prediction using a pre-trained model</h4>"
|
506
|
-
)
|
507
|
-
injection_dict["table1"] = table1
|
508
|
-
with open(str(emhass_conf["data_path"] / "injection_dict.pkl"), "wb") as fid:
|
509
|
-
pickle.dump(injection_dict, fid)
|
510
|
-
msg = "EMHASS >> Action forecast-model-predict executed... \n"
|
511
|
-
if not checkFileLog(ActionStr):
|
512
|
-
return make_response(msg, 201)
|
513
|
-
return make_response(grabLog(ActionStr), 400)
|
514
|
-
# forecast-model-tune
|
515
|
-
elif action_name == "forecast-model-tune":
|
516
|
-
ActionStr = " >> Performing a machine learning forecast model tune..."
|
517
|
-
app.logger.info(ActionStr)
|
518
|
-
df_pred_optim, mlf = forecast_model_tune(input_data_dict, app.logger)
|
519
|
-
if df_pred_optim is None or mlf is None:
|
520
|
-
return make_response(grabLog(ActionStr), 400)
|
521
|
-
injection_dict = get_injection_dict_forecast_model_tune(df_pred_optim, mlf)
|
522
|
-
with open(str(emhass_conf["data_path"] / "injection_dict.pkl"), "wb") as fid:
|
523
|
-
pickle.dump(injection_dict, fid)
|
524
|
-
msg = "EMHASS >> Action forecast-model-tune executed... \n"
|
525
|
-
if not checkFileLog(ActionStr):
|
526
|
-
return make_response(msg, 201)
|
527
|
-
return make_response(grabLog(ActionStr), 400)
|
528
|
-
# regressor-model-fit
|
529
|
-
elif action_name == "regressor-model-fit":
|
530
|
-
ActionStr = " >> Performing a machine learning regressor fit..."
|
531
|
-
app.logger.info(ActionStr)
|
532
|
-
regressor_model_fit(input_data_dict, app.logger)
|
533
|
-
msg = "EMHASS >> Action regressor-model-fit executed... \n"
|
534
|
-
if not checkFileLog(ActionStr):
|
535
|
-
return make_response(msg, 201)
|
536
|
-
return make_response(grabLog(ActionStr), 400)
|
537
|
-
# regressor-model-predict
|
538
|
-
elif action_name == "regressor-model-predict":
|
539
|
-
ActionStr = " >> Performing a machine learning regressor predict..."
|
540
|
-
app.logger.info(ActionStr)
|
541
|
-
regressor_model_predict(input_data_dict, app.logger)
|
542
|
-
msg = "EMHASS >> Action regressor-model-predict executed... \n"
|
543
|
-
if not checkFileLog(ActionStr):
|
544
|
-
return make_response(msg, 201)
|
545
|
-
return make_response(grabLog(ActionStr), 400)
|
546
|
-
# Else return error
|
547
|
-
else:
|
548
|
-
app.logger.error("ERROR: passed action is not valid")
|
549
|
-
msg = "EMHASS >> ERROR: Passed action is not valid... \n"
|
550
|
-
return make_response(msg, 400)
|
551
|
-
|
552
|
-
|
553
|
-
def main(
|
554
|
-
args: Optional[dict] = None,
|
555
|
-
):
|
556
|
-
global continual_publish_thread
|
557
|
-
global emhass_conf
|
558
|
-
global entity_path
|
559
|
-
global injection_dict
|
560
|
-
global app
|
561
|
-
# Pre formatted config parameters
|
562
|
-
config = {}
|
563
|
-
# Secrets
|
564
|
-
global params_secrets
|
565
|
-
# Built parameters (formatted config + secrets)
|
566
|
-
params = None
|
567
|
-
|
568
|
-
# Find env's, not not set defaults
|
569
|
-
DATA_PATH = os.getenv("DATA_PATH", default="/app/data/")
|
570
|
-
ROOT_PATH = os.getenv("ROOT_PATH", default=str(Path(__file__).parent))
|
571
|
-
CONFIG_PATH = os.getenv("CONFIG_PATH", default="/share/config.json")
|
572
|
-
OPTIONS_PATH = os.getenv("OPTIONS_PATH", default="/data/options.json")
|
573
|
-
DEFAULTS_PATH = os.getenv(
|
574
|
-
"DEFAULTS_PATH", default=ROOT_PATH + "/data/config_defaults.json"
|
575
|
-
)
|
576
|
-
ASSOCIATIONS_PATH = os.getenv(
|
577
|
-
"ASSOCIATIONS_PATH", default=ROOT_PATH + "/data/associations.csv"
|
578
|
-
)
|
579
|
-
LEGACY_CONFIG_PATH = os.getenv(
|
580
|
-
"LEGACY_CONFIG_PATH", default="/app/config_emhass.yaml"
|
581
|
-
)
|
582
|
-
|
583
|
-
# Define the paths
|
584
|
-
config_path = Path(CONFIG_PATH)
|
585
|
-
options_path = Path(OPTIONS_PATH)
|
586
|
-
defaults_path = Path(DEFAULTS_PATH)
|
587
|
-
associations_path = Path(ASSOCIATIONS_PATH)
|
588
|
-
legacy_config_path = Path(LEGACY_CONFIG_PATH)
|
589
|
-
data_path = Path(DATA_PATH)
|
590
|
-
root_path = Path(ROOT_PATH)
|
591
|
-
# Add paths to emhass_conf
|
592
|
-
emhass_conf["config_path"] = config_path
|
593
|
-
emhass_conf["options_path"] = options_path
|
594
|
-
emhass_conf["defaults_path"] = defaults_path
|
595
|
-
emhass_conf["associations_path"] = associations_path
|
596
|
-
emhass_conf["legacy_config_path"] = legacy_config_path
|
597
|
-
emhass_conf["data_path"] = data_path
|
598
|
-
emhass_conf["root_path"] = root_path
|
599
|
-
|
600
|
-
# Combine parameters from configuration sources (if exists)
|
601
|
-
config.update(
|
602
|
-
build_config(
|
603
|
-
emhass_conf, app.logger, defaults_path, config_path, legacy_config_path
|
604
|
-
)
|
605
|
-
)
|
606
|
-
if type(config) is bool and not config:
|
607
|
-
raise Exception("Failed to find default config")
|
608
|
-
|
609
|
-
# Set local variables
|
610
|
-
costfun = os.getenv("LOCAL_COSTFUN", config.get("costfun", "profit"))
|
611
|
-
logging_level = os.getenv("LOGGING_LEVEL", config.get("logging_level", "INFO"))
|
612
|
-
# Temporary set logging level if debug
|
613
|
-
if logging_level == "DEBUG":
|
614
|
-
app.logger.setLevel(logging.DEBUG)
|
615
|
-
|
616
|
-
## Secrets
|
617
|
-
# Argument
|
618
|
-
argument = {}
|
619
|
-
no_response = False
|
620
|
-
if args is not None:
|
621
|
-
if args.get("url", None):
|
622
|
-
argument["url"] = args["url"]
|
623
|
-
if args.get("key", None):
|
624
|
-
argument["key"] = args["key"]
|
625
|
-
if args.get("no_response", None):
|
626
|
-
no_response = args["no_response"]
|
627
|
-
# 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)
|
628
|
-
emhass_conf, secrets = build_secrets(
|
629
|
-
emhass_conf,
|
630
|
-
app.logger,
|
631
|
-
argument,
|
632
|
-
options_path,
|
633
|
-
os.getenv("SECRETS_PATH", default="/app/secrets_emhass.yaml"),
|
634
|
-
bool(no_response),
|
635
|
-
)
|
636
|
-
params_secrets.update(secrets)
|
637
|
-
|
638
|
-
server_ip = params_secrets.get("server_ip", "0.0.0.0")
|
639
|
-
|
640
|
-
# Check if data path exists
|
641
|
-
if not os.path.isdir(emhass_conf["data_path"]):
|
642
|
-
app.logger.warning("Unable to find data_path: " + str(emhass_conf["data_path"]))
|
643
|
-
if os.path.isdir(Path("/app/data/")):
|
644
|
-
emhass_conf["data_path"] = Path("/app/data/")
|
645
|
-
else:
|
646
|
-
Path(root_path / "data/").mkdir(parents=True, exist_ok=True)
|
647
|
-
emhass_conf["data_path"] = root_path / "data/"
|
648
|
-
app.logger.info("data_path has been set to " + str(emhass_conf["data_path"]))
|
649
|
-
|
650
|
-
# Initialize this global dict
|
651
|
-
if (emhass_conf["data_path"] / "injection_dict.pkl").exists():
|
652
|
-
with open(str(emhass_conf["data_path"] / "injection_dict.pkl"), "rb") as fid:
|
653
|
-
injection_dict = pickle.load(fid)
|
654
|
-
else:
|
655
|
-
injection_dict = None
|
656
|
-
|
657
|
-
# Build params from config and param_secrets (migrate params to correct config catagories), save result to params.pkl
|
658
|
-
params = build_params(emhass_conf, params_secrets, config, app.logger)
|
659
|
-
if type(params) is bool:
|
660
|
-
raise Exception("A error has occurred while building params")
|
661
|
-
# Update params with local variables
|
662
|
-
params["optim_conf"]["costfun"] = costfun
|
663
|
-
params["optim_conf"]["logging_level"] = logging_level
|
664
|
-
|
665
|
-
# Save params to file for later reference
|
666
|
-
if os.path.exists(str(emhass_conf["data_path"])):
|
667
|
-
with open(str(emhass_conf["data_path"] / "params.pkl"), "wb") as fid:
|
668
|
-
pickle.dump((config_path, params), fid)
|
669
|
-
else:
|
670
|
-
raise Exception("missing: " + str(emhass_conf["data_path"]))
|
671
|
-
|
672
|
-
# Define loggers
|
673
|
-
formatter = logging.Formatter(
|
674
|
-
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
675
|
-
)
|
676
|
-
log.default_handler.setFormatter(formatter)
|
677
|
-
# Action file logger
|
678
|
-
fileLogger = logging.FileHandler(str(emhass_conf["data_path"] / "actionLogs.txt"))
|
679
|
-
formatter = logging.Formatter("%(levelname)s - %(name)s - %(message)s")
|
680
|
-
fileLogger.setFormatter(formatter) # add format to Handler
|
681
|
-
if logging_level == "DEBUG":
|
682
|
-
app.logger.setLevel(logging.DEBUG)
|
683
|
-
fileLogger.setLevel(logging.DEBUG)
|
684
|
-
elif logging_level == "INFO":
|
685
|
-
app.logger.setLevel(logging.INFO)
|
686
|
-
fileLogger.setLevel(logging.INFO)
|
687
|
-
elif logging_level == "WARNING":
|
688
|
-
app.logger.setLevel(logging.WARNING)
|
689
|
-
fileLogger.setLevel(logging.WARNING)
|
690
|
-
elif logging_level == "ERROR":
|
691
|
-
app.logger.setLevel(logging.ERROR)
|
692
|
-
fileLogger.setLevel(logging.ERROR)
|
693
|
-
else:
|
694
|
-
app.logger.setLevel(logging.DEBUG)
|
695
|
-
fileLogger.setLevel(logging.DEBUG)
|
696
|
-
app.logger.propagate = False
|
697
|
-
app.logger.addHandler(fileLogger)
|
698
|
-
# Clear Action File logger file, ready for new instance
|
699
|
-
clearFileLog()
|
700
|
-
|
701
|
-
# If entity_path exists, remove any entity/metadata files
|
702
|
-
entity_path = emhass_conf["data_path"] / "entities"
|
703
|
-
if os.path.exists(entity_path):
|
704
|
-
entity_pathContents = os.listdir(entity_path)
|
705
|
-
if len(entity_pathContents) > 0:
|
706
|
-
for entity in entity_pathContents:
|
707
|
-
os.remove(entity_path / entity)
|
708
|
-
|
709
|
-
# Initialise continual publish thread list
|
710
|
-
continual_publish_thread = []
|
711
|
-
|
712
|
-
# Launch server
|
713
|
-
port = int(os.environ.get("PORT", 5000))
|
714
|
-
app.logger.info(
|
715
|
-
"Launching the emhass webserver at: http://" + server_ip + ":" + str(port)
|
716
|
-
)
|
717
|
-
app.logger.info(
|
718
|
-
"Home Assistant data fetch will be performed using url: "
|
719
|
-
+ params_secrets["hass_url"]
|
720
|
-
)
|
721
|
-
app.logger.info("The data path is: " + str(emhass_conf["data_path"]))
|
722
|
-
app.logger.info("The logging is: " + str(logging_level))
|
723
|
-
try:
|
724
|
-
app.logger.info("Using core emhass version: " + version("emhass"))
|
725
|
-
except PackageNotFoundError:
|
726
|
-
app.logger.info("Using development emhass version")
|
727
|
-
|
728
|
-
return server_ip, port
|
729
|
-
|
730
|
-
|
731
|
-
if __name__ == "__main__":
|
732
|
-
# Parsing arguments
|
733
|
-
parser = argparse.ArgumentParser()
|
734
|
-
parser.add_argument(
|
735
|
-
"--url",
|
736
|
-
type=str,
|
737
|
-
help="The URL to your Home Assistant instance, ex the external_url in your hass configuration",
|
738
|
-
)
|
739
|
-
parser.add_argument(
|
740
|
-
"--key",
|
741
|
-
type=str,
|
742
|
-
help="Your access key. If using EMHASS in standalone this should be a Long-Lived Access Token",
|
743
|
-
)
|
744
|
-
parser.add_argument(
|
745
|
-
"--no_response",
|
746
|
-
type=bool,
|
747
|
-
default=False,
|
748
|
-
help="This is set if json response errors occur",
|
749
|
-
)
|
750
|
-
args = parser.parse_args()
|
751
|
-
|
752
|
-
server_ip, port = main(vars(args))
|
753
|
-
os.environ["IP"] = str(server_ip)
|
754
|
-
os.environ["PORT"] = str(port)
|
755
|
-
|
756
|
-
serve(app, host=server_ip, port=port, threads=8)
|