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