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/command_line.py +178 -85
- emhass/data/associations.csv +63 -0
- emhass/data/config_defaults.json +117 -0
- emhass/forecast.py +38 -36
- emhass/machine_learning_forecaster.py +2 -1
- emhass/machine_learning_regressor.py +7 -2
- emhass/optimization.py +62 -62
- emhass/retrieve_hass.py +9 -4
- emhass/static/advanced.html +2 -1
- emhass/static/basic.html +4 -2
- emhass/static/configuration_list.html +44 -0
- emhass/static/configuration_script.js +872 -0
- emhass/static/data/param_definitions.json +424 -0
- emhass/static/script.js +345 -322
- emhass/static/style.css +267 -8
- emhass/templates/configuration.html +75 -0
- emhass/templates/index.html +15 -8
- emhass/utils.py +626 -302
- emhass/web_server.py +322 -213
- {emhass-0.10.6.dist-info → emhass-0.11.1.dist-info}/METADATA +207 -169
- emhass-0.11.1.dist-info/RECORD +32 -0
- {emhass-0.10.6.dist-info → emhass-0.11.1.dist-info}/WHEEL +1 -1
- emhass-0.10.6.dist-info/RECORD +0 -26
- {emhass-0.10.6.dist-info → emhass-0.11.1.dist-info}/LICENSE +0 -0
- {emhass-0.10.6.dist-info → emhass-0.11.1.dist-info}/entry_points.txt +0 -0
- {emhass-0.10.6.dist-info → emhass-0.11.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,872 @@
|
|
1
|
+
//javascript file for dynamically processing configuration page
|
2
|
+
|
3
|
+
//used static files
|
4
|
+
//param_definitions.json : stores information about parameters (E.g. their defaults, their type, and what parameter section to be in)
|
5
|
+
//configuration_list.html : template html to act as a base for the list view. (Params get dynamically added after)
|
6
|
+
|
7
|
+
//Div layout
|
8
|
+
/* <div configuration-container>
|
9
|
+
<div class="section-card">
|
10
|
+
<div class="section-card-header"> POSSIBLE HEADER INPUT HERE WITH PARAMETER ID</div>
|
11
|
+
<div class="section-body">
|
12
|
+
<div id="PARAMETER-NAME" class="param">
|
13
|
+
<div class="param-input">input/s here</div>
|
14
|
+
</div>
|
15
|
+
</div>
|
16
|
+
</div>
|
17
|
+
</div>; */
|
18
|
+
|
19
|
+
//on page reload
|
20
|
+
window.onload = async function () {
|
21
|
+
///fetch configuration parameters from definitions json file
|
22
|
+
param_definitions = await getParamDefinitions();
|
23
|
+
//obtain configuration from emhass (pull)
|
24
|
+
config = await obtainConfig();
|
25
|
+
//obtain configuration_list.html html as a template to dynamically to render parameters in a list view (parameters as input items)
|
26
|
+
list_html = await getListHTML();
|
27
|
+
//load list parameter page (default)
|
28
|
+
loadConfigurationListView(param_definitions, config, list_html);
|
29
|
+
|
30
|
+
//add event listener to save button
|
31
|
+
document
|
32
|
+
.getElementById("save")
|
33
|
+
.addEventListener("click", () => saveConfiguration(param_definitions));
|
34
|
+
|
35
|
+
//add event listener to yaml button (convert yaml to json in box view)
|
36
|
+
document.getElementById("yaml").addEventListener("click", () => yamlToJson());
|
37
|
+
//hide yaml button by default (display in box view)
|
38
|
+
document.getElementById("yaml").style.display = "none";
|
39
|
+
|
40
|
+
//add event listener to defaults button
|
41
|
+
document
|
42
|
+
.getElementById("defaults")
|
43
|
+
.addEventListener("click", () =>
|
44
|
+
ToggleView(param_definitions, list_html, true)
|
45
|
+
);
|
46
|
+
|
47
|
+
//add event listener to json-toggle button (toggle between json box and list view)
|
48
|
+
document
|
49
|
+
.getElementById("json-toggle")
|
50
|
+
.addEventListener("click", () =>
|
51
|
+
ToggleView(param_definitions, list_html, false)
|
52
|
+
);
|
53
|
+
};
|
54
|
+
|
55
|
+
//obtain file containing information about parameters (definitions)
|
56
|
+
async function getParamDefinitions() {
|
57
|
+
const response = await fetch(`static/data/param_definitions.json`);
|
58
|
+
if (response.status !== 200 && response.status !== 201) {
|
59
|
+
//alert error in alert box
|
60
|
+
errorAlert("Unable to obtain definitions file");
|
61
|
+
return {};
|
62
|
+
}
|
63
|
+
const param_definitions = await response.json();
|
64
|
+
return await param_definitions;
|
65
|
+
}
|
66
|
+
|
67
|
+
//obtain emhass config (from saved params extracted/simplified into the config format)
|
68
|
+
async function obtainConfig() {
|
69
|
+
config = {};
|
70
|
+
const response = await fetch(`get-config`, {
|
71
|
+
method: "GET",
|
72
|
+
});
|
73
|
+
response_status = await response.status; //return status
|
74
|
+
//if request failed
|
75
|
+
if (response_status !== 200 && response_status !== 201) {
|
76
|
+
showChangeStatus(response_status, await response.json());
|
77
|
+
return {};
|
78
|
+
}
|
79
|
+
//else extract json rom data
|
80
|
+
blob = await response.blob(); //get data blob
|
81
|
+
config = await new Response(blob).json(); //obtain json from blob
|
82
|
+
showChangeStatus(response_status, {});
|
83
|
+
return config;
|
84
|
+
}
|
85
|
+
|
86
|
+
//obtain emhass default config (to present the default parameters in view)
|
87
|
+
async function ObtainDefaultConfig() {
|
88
|
+
config = {};
|
89
|
+
const response = await fetch(`get-config/defaults`, {
|
90
|
+
method: "GET",
|
91
|
+
});
|
92
|
+
//if request failed
|
93
|
+
response_status = await response.status; //return status
|
94
|
+
if (response_status !== 200 && response_status !== 201) {
|
95
|
+
showChangeStatus(response_status, await response.json());
|
96
|
+
return {};
|
97
|
+
}
|
98
|
+
//else extract json rom data
|
99
|
+
blob = await response.blob(); //get data blob
|
100
|
+
config = await new Response(blob).json(); //obtain json from blob
|
101
|
+
showChangeStatus(response_status, {});
|
102
|
+
return config;
|
103
|
+
}
|
104
|
+
|
105
|
+
//get html data from configuration_list.html (list template)
|
106
|
+
async function getListHTML() {
|
107
|
+
const response = await fetch(`static/configuration_list.html`);
|
108
|
+
if (response.status !== 200 && response.status !== 201) {
|
109
|
+
errorAlert("Unable to obtain configuration_list.html file");
|
110
|
+
return {};
|
111
|
+
}
|
112
|
+
blob = await response.blob(); //get data blob
|
113
|
+
htmlTemplateData = await new Response(blob).text(); //obtain html from blob
|
114
|
+
return await htmlTemplateData;
|
115
|
+
}
|
116
|
+
|
117
|
+
//load list configuration view
|
118
|
+
function loadConfigurationListView(param_definitions, config, list_html) {
|
119
|
+
if (list_html == null || config == null || param_definitions == null) {
|
120
|
+
return 1;
|
121
|
+
}
|
122
|
+
|
123
|
+
//list parameters used in the section headers
|
124
|
+
header_input_list = ["set_use_battery", "number_of_deferrable_loads"];
|
125
|
+
|
126
|
+
//get the main container and append list template html
|
127
|
+
document.getElementById("configuration-container").innerHTML = list_html;
|
128
|
+
|
129
|
+
//loop though configuration sections ('Local','System','Tariff','Solar System (PV)') in definitions file
|
130
|
+
for (var section in param_definitions) {
|
131
|
+
// build each section by adding parameters with their corresponding input elements
|
132
|
+
buildParamContainers(
|
133
|
+
section,
|
134
|
+
param_definitions[section],
|
135
|
+
config,
|
136
|
+
header_input_list
|
137
|
+
);
|
138
|
+
|
139
|
+
//after sections have been built, add event listeners for section header inputs
|
140
|
+
//loop though headers
|
141
|
+
for (header_input_param of header_input_list) {
|
142
|
+
if (param_definitions[section].hasOwnProperty(header_input_param)) {
|
143
|
+
//grab default from definitions file
|
144
|
+
value = param_definitions[section][header_input_param]["default_value"];
|
145
|
+
//find input element (using the parameter name as the input element ID)
|
146
|
+
header_input_element = document.getElementById(header_input_param);
|
147
|
+
if (header_input_element !== null) {
|
148
|
+
//add event listener to element (trigger on input change)
|
149
|
+
header_input_element.addEventListener("input", (e) =>
|
150
|
+
headerElement(e.target, param_definitions, config)
|
151
|
+
);
|
152
|
+
//check the EMHASS config to see if it contains a stored param value
|
153
|
+
//else keep default
|
154
|
+
value = checkConfigParam(value, config, header_input_param);
|
155
|
+
//set value of input
|
156
|
+
header_input_element.value = value;
|
157
|
+
//checkboxes (for Booleans) also set value to "checked"
|
158
|
+
if (header_input_element.type == "checkbox") {
|
159
|
+
header_input_element.checked = value;
|
160
|
+
}
|
161
|
+
//manually trigger the header parameter input event listener for setting up initial section state
|
162
|
+
headerElement(header_input_element, param_definitions, config);
|
163
|
+
}
|
164
|
+
}
|
165
|
+
}
|
166
|
+
}
|
167
|
+
}
|
168
|
+
|
169
|
+
//build sections body, containing parameter/param containers (containing parameter/param inputs)
|
170
|
+
function buildParamContainers(
|
171
|
+
section,
|
172
|
+
section_parameters_definitions,
|
173
|
+
config,
|
174
|
+
header_input_list
|
175
|
+
) {
|
176
|
+
//get the section container element
|
177
|
+
SectionContainer = document.getElementById(section);
|
178
|
+
//get the body container inside the section (where the parameters will be appended)
|
179
|
+
SectionParamElement = SectionContainer.getElementsByClassName("section-body");
|
180
|
+
if (SectionContainer == null || SectionParamElement.length == 0) {
|
181
|
+
console.error("Unable to find Section container or Section Body");
|
182
|
+
return 0;
|
183
|
+
}
|
184
|
+
|
185
|
+
//loop though the sections parameters in definition file, generate and append param (div) elements for the section
|
186
|
+
for (const [
|
187
|
+
parameter_definition_name,
|
188
|
+
parameter_definition_object,
|
189
|
+
] of Object.entries(section_parameters_definitions)) {
|
190
|
+
//check parameter definitions have the required key values
|
191
|
+
if (
|
192
|
+
!("friendly_name" in parameter_definition_object) ||
|
193
|
+
!("Description" in parameter_definition_object) ||
|
194
|
+
!("input" in parameter_definition_object) ||
|
195
|
+
!("default_value" in parameter_definition_object)
|
196
|
+
) {
|
197
|
+
console.log(
|
198
|
+
parameter_definition_name +
|
199
|
+
" is missing some required values in the definitions file"
|
200
|
+
);
|
201
|
+
continue;
|
202
|
+
}
|
203
|
+
if (
|
204
|
+
parameter_definition_object["input"] === "select" &&
|
205
|
+
!("select_options" in parameter_definition_object)
|
206
|
+
) {
|
207
|
+
console.log(
|
208
|
+
parameter_definition_name +
|
209
|
+
" is missing select_options values in the definitions file"
|
210
|
+
);
|
211
|
+
continue;
|
212
|
+
}
|
213
|
+
|
214
|
+
//check if param is set in the section header, if so skip building param
|
215
|
+
if (header_input_list.includes(parameter_definition_name)) {
|
216
|
+
continue;
|
217
|
+
}
|
218
|
+
|
219
|
+
//if parameter type == array.* and not in "Deferrable Loads" section, append plus and minus buttons in param div
|
220
|
+
array_buttons = "";
|
221
|
+
if (
|
222
|
+
parameter_definition_object["input"].search("array.") > -1 &&
|
223
|
+
section != "Deferrable Loads"
|
224
|
+
) {
|
225
|
+
array_buttons = `
|
226
|
+
<button type="button" class="input-plus ${parameter_definition_name}">+</button>
|
227
|
+
<button type="button" class="input-minus ${parameter_definition_name}">-</button>
|
228
|
+
<br>
|
229
|
+
`;
|
230
|
+
}
|
231
|
+
|
232
|
+
//generates and appends param container into section
|
233
|
+
//buildParamElement() builds the parameter input/s and returns html to append in param-input
|
234
|
+
SectionParamElement[0].innerHTML += `
|
235
|
+
<div class="param" id="${parameter_definition_name}">
|
236
|
+
<h5>${
|
237
|
+
parameter_definition_object["friendly_name"]
|
238
|
+
}:</h5> <i>${parameter_definition_name}</i> </br>
|
239
|
+
${array_buttons}
|
240
|
+
<div class="param-input">
|
241
|
+
${buildParamElement(
|
242
|
+
parameter_definition_object,
|
243
|
+
parameter_definition_name,
|
244
|
+
config
|
245
|
+
)}
|
246
|
+
</div>
|
247
|
+
<p>${parameter_definition_object["Description"]}</p>
|
248
|
+
</div>
|
249
|
+
`;
|
250
|
+
}
|
251
|
+
|
252
|
+
//after looping though, build and appending the parameters in the corresponding section:
|
253
|
+
//create add button (array plus) event listeners
|
254
|
+
let plus = SectionContainer.querySelectorAll(".input-plus");
|
255
|
+
plus.forEach(function (answer) {
|
256
|
+
answer.addEventListener("click", () =>
|
257
|
+
plusElements(answer.classList[1], param_definitions, section, {})
|
258
|
+
);
|
259
|
+
});
|
260
|
+
|
261
|
+
//create subtract button (array minus) event listeners
|
262
|
+
let minus = SectionContainer.querySelectorAll(".input-minus");
|
263
|
+
minus.forEach(function (answer) {
|
264
|
+
answer.addEventListener("click", () => minusElements(answer.classList[1]));
|
265
|
+
});
|
266
|
+
|
267
|
+
//check initial checkbox state, check "value" of input and match to "checked" value
|
268
|
+
let checkbox = document.querySelectorAll("input[type='checkbox']");
|
269
|
+
checkbox.forEach(function (answer) {
|
270
|
+
let value = answer.value === "true";
|
271
|
+
answer.checked = value;
|
272
|
+
});
|
273
|
+
|
274
|
+
//loop though sections params again, check if param has a requirement, if so add a event listener to the required param input
|
275
|
+
//if required param gets changed, trigger function to check if that required parameter matches the required value for the param
|
276
|
+
//if false, add css class to param element to shadow it, to show that its unaccessible
|
277
|
+
for (const [
|
278
|
+
parameter_definition_name,
|
279
|
+
parameter_definition_object,
|
280
|
+
] of Object.entries(section_parameters_definitions)) {
|
281
|
+
//check if param has a requirement from definitions file
|
282
|
+
if ("requires" in parameter_definition_object) {
|
283
|
+
// get param requirement element
|
284
|
+
const requirement_element = document.getElementById(
|
285
|
+
Object.keys(parameter_definition_object["requires"])[0]
|
286
|
+
);
|
287
|
+
if (requirement_element == null) {
|
288
|
+
console.debug(
|
289
|
+
"unable to find " +
|
290
|
+
Object.keys(parameter_definition_object["requires"])[0] +
|
291
|
+
" param div container element"
|
292
|
+
);
|
293
|
+
continue;
|
294
|
+
}
|
295
|
+
|
296
|
+
// get param element that has requirement
|
297
|
+
const param_element = document.getElementById(parameter_definition_name);
|
298
|
+
if (param_element == null) {
|
299
|
+
console.debug(
|
300
|
+
"unable to find " +
|
301
|
+
parameter_definition_name +
|
302
|
+
" param div container element"
|
303
|
+
);
|
304
|
+
continue;
|
305
|
+
}
|
306
|
+
|
307
|
+
//obtain required param inputs, add event listeners
|
308
|
+
requirement_inputs =
|
309
|
+
requirement_element.getElementsByClassName("param_input");
|
310
|
+
//grab required value
|
311
|
+
const requirement_value = Object.values(
|
312
|
+
parameter_definition_object["requires"]
|
313
|
+
)[0];
|
314
|
+
|
315
|
+
//for all required inputs
|
316
|
+
for (const input of requirement_inputs) {
|
317
|
+
//if listener not already attached
|
318
|
+
if (input.getAttribute("listener") !== "true") {
|
319
|
+
//create event listener with arguments referencing the required param. param with requirement and required value
|
320
|
+
input.addEventListener("input", () =>
|
321
|
+
checkRequirements(input, param_element, requirement_value)
|
322
|
+
);
|
323
|
+
//manually run function to gain initial param element initial state
|
324
|
+
checkRequirements(input, param_element, requirement_value);
|
325
|
+
}
|
326
|
+
}
|
327
|
+
}
|
328
|
+
}
|
329
|
+
}
|
330
|
+
|
331
|
+
//create html input element/s for a param container (called by buildParamContainers)
|
332
|
+
function buildParamElement(
|
333
|
+
parameter_definition_object,
|
334
|
+
parameter_definition_name,
|
335
|
+
config
|
336
|
+
) {
|
337
|
+
var type = "";
|
338
|
+
var inputs = "";
|
339
|
+
var type_specific_html = "";
|
340
|
+
var type_specific_html_end = "";
|
341
|
+
|
342
|
+
//switch statement to adjust generated html according to the parameter data type (definitions in definitions file)
|
343
|
+
switch (parameter_definition_object["input"]) {
|
344
|
+
case "array.int":
|
345
|
+
//number
|
346
|
+
case "int":
|
347
|
+
type = "number";
|
348
|
+
placeholder = parseInt(parameter_definition_object["default_value"]);
|
349
|
+
break;
|
350
|
+
case "array.float":
|
351
|
+
case "float":
|
352
|
+
type = "number";
|
353
|
+
placeholder = parseFloat(parameter_definition_object["default_value"]);
|
354
|
+
break;
|
355
|
+
//text (string)
|
356
|
+
case "array.string":
|
357
|
+
case "string":
|
358
|
+
type = "text";
|
359
|
+
placeholder = parameter_definition_object["default_value"];
|
360
|
+
break;
|
361
|
+
case "array.time":
|
362
|
+
//time ("00:00")
|
363
|
+
case "time":
|
364
|
+
type = "time";
|
365
|
+
break;
|
366
|
+
//checkbox (boolean)
|
367
|
+
case "array.boolean":
|
368
|
+
case "boolean":
|
369
|
+
type = "checkbox";
|
370
|
+
type_specific_html = `
|
371
|
+
<label class="switch">
|
372
|
+
`;
|
373
|
+
type_specific_html_end = `
|
374
|
+
<span class="slider"></span>
|
375
|
+
</label>
|
376
|
+
`;
|
377
|
+
placeholder = parameter_definition_object["default_value"] === "true";
|
378
|
+
break;
|
379
|
+
//selects (pick)
|
380
|
+
case "select":
|
381
|
+
//format selects later
|
382
|
+
break;
|
383
|
+
}
|
384
|
+
|
385
|
+
//check default values saved in param definitions
|
386
|
+
//definitions default value is used if none is found in the configs, or an array element has been added in the ui (deferrable load number increase or plus button pressed)
|
387
|
+
value = parameter_definition_object["default_value"];
|
388
|
+
//check if a param value is saved in the config file (if so overwrite definition default)
|
389
|
+
value = checkConfigParam(value, config, parameter_definition_name);
|
390
|
+
|
391
|
+
//generate and return param input html,
|
392
|
+
//check if param value is not an object, if so assume its a single value.
|
393
|
+
if (typeof value !== "object") {
|
394
|
+
//if select, generate and return select elements instead of input
|
395
|
+
if (parameter_definition_object["input"] == "select") {
|
396
|
+
let inputs = `<select class="param_input">`;
|
397
|
+
for (const options of parameter_definition_object["select_options"]) {
|
398
|
+
selected = ""
|
399
|
+
//if item in select is the same as the config value, then append "selected" tag
|
400
|
+
if (options==value) {selected = `selected="selected"`}
|
401
|
+
inputs += `<option ${selected}>${options}</option>`;
|
402
|
+
}
|
403
|
+
inputs += `</select>`;
|
404
|
+
return inputs;
|
405
|
+
}
|
406
|
+
// generate param input html and return
|
407
|
+
else {
|
408
|
+
return `
|
409
|
+
${type_specific_html}
|
410
|
+
<input class="param_input" type="${type}" value=${value} placeholder=${parameter_definition_object["default_value"]}>
|
411
|
+
${type_specific_html_end}
|
412
|
+
`;
|
413
|
+
}
|
414
|
+
}
|
415
|
+
// else if object, loop though array of values, generate input element per value, and and return
|
416
|
+
else {
|
417
|
+
//for items such as load_peak_hour_periods (object of objects with arrays)
|
418
|
+
if (typeof Object.values(value)[0] === "object") {
|
419
|
+
for (param of Object.values(value)) {
|
420
|
+
for (items of Object.values(param)) {
|
421
|
+
inputs += `<input class="param_input" type="${type}" value=${
|
422
|
+
Object.values(items)[0]
|
423
|
+
} placeholder=${Object.values(items)[0]}>`;
|
424
|
+
}
|
425
|
+
inputs += `</br>`;
|
426
|
+
}
|
427
|
+
return inputs;
|
428
|
+
}
|
429
|
+
// array of values
|
430
|
+
else {
|
431
|
+
let inputs = "";
|
432
|
+
for (param of value) {
|
433
|
+
inputs += `
|
434
|
+
${type_specific_html}
|
435
|
+
<input class="param_input" type="${type}" value=${param} placeholder=${parameter_definition_object["default_value"]}>
|
436
|
+
${type_specific_html_end}
|
437
|
+
`;
|
438
|
+
}
|
439
|
+
return inputs;
|
440
|
+
}
|
441
|
+
}
|
442
|
+
}
|
443
|
+
|
444
|
+
//add param inputs in param div container (for type array)
|
445
|
+
function plusElements(
|
446
|
+
parameter_definition_name,
|
447
|
+
param_definitions,
|
448
|
+
section,
|
449
|
+
config
|
450
|
+
) {
|
451
|
+
param_element = document.getElementById(parameter_definition_name);
|
452
|
+
if (param_element == null) {
|
453
|
+
console.log(
|
454
|
+
"Unable to find " + parameter_definition_name + " param div container"
|
455
|
+
);
|
456
|
+
return 1;
|
457
|
+
}
|
458
|
+
param_input_container =
|
459
|
+
param_element.getElementsByClassName("param-input")[0];
|
460
|
+
// Add a copy of the param element
|
461
|
+
param_input_container.innerHTML += buildParamElement(
|
462
|
+
param_definitions[section][parameter_definition_name],
|
463
|
+
parameter_definition_name,
|
464
|
+
config
|
465
|
+
);
|
466
|
+
}
|
467
|
+
|
468
|
+
//Remove param inputs in param div container (minimum 1)
|
469
|
+
function minusElements(param) {
|
470
|
+
param_element = document.getElementById(param);
|
471
|
+
if (param_element == null) {
|
472
|
+
console.log(
|
473
|
+
"Unable to find " + parameter_definition_name + " param div container"
|
474
|
+
);
|
475
|
+
return 1;
|
476
|
+
}
|
477
|
+
param_input_list = param_element.getElementsByTagName("input");
|
478
|
+
if (param_input_list.length == 0) {
|
479
|
+
console.log(
|
480
|
+
"Unable to find " + parameter_definition_name + " param input/s"
|
481
|
+
);
|
482
|
+
}
|
483
|
+
|
484
|
+
//verify if input is a boolean (if so remove parent slider/switch element with input)
|
485
|
+
if (
|
486
|
+
param_input_list[param_input_list.length - 1].parentNode.tagName === "LABEL"
|
487
|
+
) {
|
488
|
+
param_input = param_input_list[param_input_list.length - 1].parentNode;
|
489
|
+
} else {
|
490
|
+
param_input = param_input_list[param_input_list.length - 1];
|
491
|
+
}
|
492
|
+
|
493
|
+
//if param is "load_peak_hour_periods", remove both start and end param inputs as well as the line brake tag separating the inputs
|
494
|
+
if (param == "load_peak_hour_periods") {
|
495
|
+
if (param_input_list.length > 2) {
|
496
|
+
brs = document.getElementById(param).getElementsByTagName("br");
|
497
|
+
param_input_list[param_input_list.length - 1].remove();
|
498
|
+
param_input_list[param_input_list.length - 1].remove();
|
499
|
+
brs[brs.length - 1].remove();
|
500
|
+
}
|
501
|
+
} else if (param_input_list.length > 1) {
|
502
|
+
param_input.remove();
|
503
|
+
}
|
504
|
+
}
|
505
|
+
|
506
|
+
//check requirement_element inputs,
|
507
|
+
//if requirement_element don't match requirement_value, add .requirement-disable class to param_element
|
508
|
+
//else remove class
|
509
|
+
function checkRequirements(
|
510
|
+
requirement_element,
|
511
|
+
param_element,
|
512
|
+
requirement_value
|
513
|
+
) {
|
514
|
+
//get current value of required element
|
515
|
+
if (requirement_element.type == "checkbox") {
|
516
|
+
requirement_element_value = requirement_element.checked;
|
517
|
+
} else {
|
518
|
+
requirement_element_value = requirement_element.value;
|
519
|
+
}
|
520
|
+
|
521
|
+
if (requirement_element_value != requirement_value) {
|
522
|
+
if (!param_element.classList.contains("requirement-disable")) {
|
523
|
+
param_element.classList.add("requirement-disable");
|
524
|
+
}
|
525
|
+
} else {
|
526
|
+
if (param_element.classList.contains("requirement-disable")) {
|
527
|
+
param_element.classList.remove("requirement-disable");
|
528
|
+
}
|
529
|
+
}
|
530
|
+
}
|
531
|
+
|
532
|
+
//on header input change, execute accordingly
|
533
|
+
function headerElement(element, param_definitions, config) {
|
534
|
+
//obtain section body element
|
535
|
+
section_card = element.closest(".section-card");
|
536
|
+
if (section_card == null) {
|
537
|
+
console.log("Unable to obtain section-card");
|
538
|
+
return 1;
|
539
|
+
}
|
540
|
+
param_container = section_card.getElementsByClassName("section-body");
|
541
|
+
if (param_container.length > 0) {
|
542
|
+
param_container = section_card.getElementsByClassName("section-body")[0];
|
543
|
+
} else {
|
544
|
+
console.log("Unable to obtain section-body");
|
545
|
+
return 1;
|
546
|
+
}
|
547
|
+
|
548
|
+
switch (element.id) {
|
549
|
+
//if set_use_battery, add or remove battery section (inc. params)
|
550
|
+
case "set_use_battery":
|
551
|
+
if (element.checked) {
|
552
|
+
param_container.innerHTML = "";
|
553
|
+
buildParamContainers("Battery", param_definitions["Battery"], config, [
|
554
|
+
"set_use_battery",
|
555
|
+
]);
|
556
|
+
element.checked = true;
|
557
|
+
} else {
|
558
|
+
param_container.innerHTML = "";
|
559
|
+
}
|
560
|
+
break;
|
561
|
+
|
562
|
+
//if number_of_deferrable_loads, the number of inputs in the "Deferrable Loads" section should add up to number_of_deferrable_loads value in header
|
563
|
+
case "number_of_deferrable_loads":
|
564
|
+
//get a list of param in section
|
565
|
+
param_list = param_container.getElementsByClassName("param");
|
566
|
+
if (param_list.length <= 0) {
|
567
|
+
console.log(
|
568
|
+
"There has been an issue counting the amount of params in number_of_deferrable_loads"
|
569
|
+
);
|
570
|
+
return 1;
|
571
|
+
}
|
572
|
+
//calculate how much off the fist parameters input elements amount to is, compering to the number_of_deferrable_loads value
|
573
|
+
difference =
|
574
|
+
parseInt(element.value) -
|
575
|
+
param_container.firstElementChild.querySelectorAll("input").length;
|
576
|
+
//add elements based on how many elements are missing
|
577
|
+
if (difference > 0) {
|
578
|
+
for (let i = difference; i >= 1; i--) {
|
579
|
+
for (const param of param_list) {
|
580
|
+
//append element, do not pass config to obtain default parameter from definitions file
|
581
|
+
plusElements(param.id, param_definitions, "Deferrable Loads", {});
|
582
|
+
}
|
583
|
+
}
|
584
|
+
}
|
585
|
+
//subtract elements based how many elements its over
|
586
|
+
if (difference < 0) {
|
587
|
+
for (let i = difference; i <= -1; i++) {
|
588
|
+
for (const param of param_list) {
|
589
|
+
minusElements(param.id);
|
590
|
+
}
|
591
|
+
}
|
592
|
+
}
|
593
|
+
break;
|
594
|
+
}
|
595
|
+
}
|
596
|
+
|
597
|
+
//checks parameter value in config, updates value if exists
|
598
|
+
function checkConfigParam(value, config, parameter_definition_name) {
|
599
|
+
if (config !== null && config !== undefined) {
|
600
|
+
//check if parameter has a saved value
|
601
|
+
if (parameter_definition_name in config) {
|
602
|
+
value = config[parameter_definition_name];
|
603
|
+
}
|
604
|
+
}
|
605
|
+
return value;
|
606
|
+
}
|
607
|
+
|
608
|
+
//send all parameter input values to EMHASS, to save to config.json and param.pkl
|
609
|
+
async function saveConfiguration(param_definitions) {
|
610
|
+
//start wth none
|
611
|
+
config = {};
|
612
|
+
|
613
|
+
//if section-cards (config sections/list) exists
|
614
|
+
config_card = document.getElementsByClassName("section-card");
|
615
|
+
//check if page is in list or box view
|
616
|
+
config_box_element = document.getElementById("config-box");
|
617
|
+
|
618
|
+
//if true, in list view
|
619
|
+
if (Boolean(config_card.length)) {
|
620
|
+
//retrieve params and their input/s by looping though param_definitions list
|
621
|
+
//loop through the sections
|
622
|
+
for (var [section_name, section_object] of Object.entries(
|
623
|
+
param_definitions
|
624
|
+
)) {
|
625
|
+
//loop through parameters
|
626
|
+
for (var [
|
627
|
+
parameter_definition_name,
|
628
|
+
parameter_definition_object,
|
629
|
+
] of Object.entries(section_object)) {
|
630
|
+
let param_values = []; //stores the obtained param input values
|
631
|
+
let param_array = false;
|
632
|
+
//get param container
|
633
|
+
param_element = document.getElementById(parameter_definition_name);
|
634
|
+
if (param_element == null) {
|
635
|
+
console.debug(
|
636
|
+
"unable to find " +
|
637
|
+
parameter_definition_name +
|
638
|
+
" param div container element, skipping this param"
|
639
|
+
);
|
640
|
+
continue;
|
641
|
+
}
|
642
|
+
//extract input/s and their value/s from param container div
|
643
|
+
else {
|
644
|
+
if (param_element.tagName !== "INPUT") {
|
645
|
+
param_inputs = param_element.getElementsByClassName("param_input");
|
646
|
+
} else {
|
647
|
+
//check if param_element is also param_input (ex. for header parameters)
|
648
|
+
param_inputs = [param_element];
|
649
|
+
}
|
650
|
+
|
651
|
+
// loop though param_inputs, extract the element/s values
|
652
|
+
for (var input of param_inputs) {
|
653
|
+
switch (input.type) {
|
654
|
+
case "number":
|
655
|
+
param_values.push(parseFloat(input.value));
|
656
|
+
break;
|
657
|
+
case "checkbox":
|
658
|
+
param_values.push(input.checked);
|
659
|
+
break;
|
660
|
+
default:
|
661
|
+
param_values.push(input.value);
|
662
|
+
break;
|
663
|
+
}
|
664
|
+
}
|
665
|
+
//obtain param input type from param_definitions, check if param should be formatted as an array
|
666
|
+
param_array = Boolean(
|
667
|
+
!parameter_definition_object["input"].search("array")
|
668
|
+
);
|
669
|
+
|
670
|
+
//build parameters using values extracted from param_inputs
|
671
|
+
|
672
|
+
// If time with 2 sets (load_peak_hour_periods)
|
673
|
+
if (
|
674
|
+
parameter_definition_object["input"] == "array.time" &&
|
675
|
+
param_values.length % 2 === 0
|
676
|
+
) {
|
677
|
+
config[parameter_definition_name] = {};
|
678
|
+
for (let i = 0; i < param_values.length; i++) {
|
679
|
+
config[parameter_definition_name][
|
680
|
+
"period_hp_" +
|
681
|
+
(Object.keys(config[parameter_definition_name]).length + 1)
|
682
|
+
] = [{ start: param_values[i] }, { end: param_values[++i] }];
|
683
|
+
}
|
684
|
+
continue;
|
685
|
+
}
|
686
|
+
|
687
|
+
//single value
|
688
|
+
if (param_values.length && !param_array) {
|
689
|
+
config[parameter_definition_name] = param_values[0];
|
690
|
+
}
|
691
|
+
|
692
|
+
//array value
|
693
|
+
else if (param_values.length) {
|
694
|
+
config[parameter_definition_name] = param_values;
|
695
|
+
}
|
696
|
+
}
|
697
|
+
}
|
698
|
+
}
|
699
|
+
}
|
700
|
+
|
701
|
+
//if box view, extract json from box view
|
702
|
+
else if (config_box_element !== null) {
|
703
|
+
//try and parse json from box
|
704
|
+
try {
|
705
|
+
config = JSON.parse(config_box_element.value);
|
706
|
+
} catch (error) {
|
707
|
+
//if json error, show in alert box
|
708
|
+
document.getElementById("alert-text").textContent =
|
709
|
+
"\r\n" +
|
710
|
+
error +
|
711
|
+
"\r\n" +
|
712
|
+
"JSON Error: String values may not be wrapped in quotes";
|
713
|
+
document.getElementById("alert").style.display = "block";
|
714
|
+
document.getElementById("alert").style.textAlign = "center";
|
715
|
+
return 0;
|
716
|
+
}
|
717
|
+
}
|
718
|
+
// else, cant find box or list view
|
719
|
+
else {
|
720
|
+
errorAlert("There has been an error verifying box or list view");
|
721
|
+
}
|
722
|
+
|
723
|
+
//finally, send built config to emhass
|
724
|
+
const response = await fetch(`set-config`, {
|
725
|
+
method: "POST",
|
726
|
+
headers: {
|
727
|
+
"Content-Type": "application/json",
|
728
|
+
},
|
729
|
+
body: JSON.stringify(config),
|
730
|
+
});
|
731
|
+
showChangeStatus(response.status, await response.json());
|
732
|
+
}
|
733
|
+
|
734
|
+
//Toggle between box (json) and list view
|
735
|
+
async function ToggleView(param_definitions, list_html, default_reset) {
|
736
|
+
let selected = "";
|
737
|
+
config = {};
|
738
|
+
|
739
|
+
//find out if list or box view is active
|
740
|
+
configuration_container = document.getElementById("configuration-container");
|
741
|
+
if (configuration_container == null) {
|
742
|
+
errorAlert("Unable to find Configuration Container element");
|
743
|
+
}
|
744
|
+
//get yaml button
|
745
|
+
yaml_button = document.getElementById("yaml");
|
746
|
+
if (yaml_button == null) {
|
747
|
+
console.log("Unable to obtain yaml button");
|
748
|
+
}
|
749
|
+
|
750
|
+
// if section-cards (config sections/list) exists
|
751
|
+
config_card = configuration_container.getElementsByClassName("section-card");
|
752
|
+
//selected view (0 = box)
|
753
|
+
selected_view = Boolean(config_card.length);
|
754
|
+
|
755
|
+
//if default_reset is passed do not switch views, instead reinitialize view with default config as values
|
756
|
+
if (default_reset) {
|
757
|
+
selected_view = !selected_view;
|
758
|
+
//obtain default config as config (when pressing the default button)
|
759
|
+
config = await ObtainDefaultConfig();
|
760
|
+
} else {
|
761
|
+
//obtain latest config
|
762
|
+
config = await obtainConfig();
|
763
|
+
}
|
764
|
+
|
765
|
+
//if array is empty assume json box is selected
|
766
|
+
if (selected_view) {
|
767
|
+
selected = "list";
|
768
|
+
} else {
|
769
|
+
selected = "box";
|
770
|
+
}
|
771
|
+
//remove contents of current view
|
772
|
+
configuration_container.innerHTML = "";
|
773
|
+
//build new view
|
774
|
+
switch (selected) {
|
775
|
+
case "box":
|
776
|
+
//load list
|
777
|
+
loadConfigurationListView(param_definitions, config, list_html);
|
778
|
+
yaml_button.style.display = "none";
|
779
|
+
break;
|
780
|
+
case "list":
|
781
|
+
//load box
|
782
|
+
loadConfigurationBoxPage(config);
|
783
|
+
yaml_button.style.display = "block";
|
784
|
+
break;
|
785
|
+
}
|
786
|
+
}
|
787
|
+
|
788
|
+
//load box (json textarea) view
|
789
|
+
async function loadConfigurationBoxPage(config) {
|
790
|
+
//get configuration container element
|
791
|
+
configuration_container = document.getElementById("configuration-container");
|
792
|
+
if (configuration_container == null) {
|
793
|
+
errorAlert("Unable to find Configuration Container element");
|
794
|
+
}
|
795
|
+
//append configuration container with textbox area
|
796
|
+
configuration_container.innerHTML = `
|
797
|
+
<textarea id="config-box" rows="30" placeholder="{}"></textarea>
|
798
|
+
`;
|
799
|
+
//set created textarea box with retrieved config
|
800
|
+
document.getElementById("config-box").innerHTML = JSON.stringify(
|
801
|
+
config,
|
802
|
+
null,
|
803
|
+
2
|
804
|
+
);
|
805
|
+
}
|
806
|
+
|
807
|
+
//function in control of status icons and alert box from a fetch request
|
808
|
+
async function showChangeStatus(status, logJson) {
|
809
|
+
var loading = document.getElementById("loader"); //element showing statuses
|
810
|
+
if (loading === null) {
|
811
|
+
console.log("unable to find loader element");
|
812
|
+
return 1;
|
813
|
+
}
|
814
|
+
if (status === 200 || status === 201) {
|
815
|
+
//if status is 200 or 201, then show a tick
|
816
|
+
loading.innerHTML = `<p class=tick>✓</p>`;
|
817
|
+
} else {
|
818
|
+
//then show a cross
|
819
|
+
loading.classList.remove("loading");
|
820
|
+
loading.innerHTML = `<p class=cross>⤬</p>`; //show cross icon to indicate an error
|
821
|
+
if (logJson.length != 0 && document.getElementById("alert-text") !== null) {
|
822
|
+
document.getElementById("alert-text").textContent =
|
823
|
+
"\r\n\u2022 " + logJson.join("\r\n\u2022 "); //show received log data in alert box
|
824
|
+
document.getElementById("alert").style.display = "block";
|
825
|
+
document.getElementById("alert").style.textAlign = "left";
|
826
|
+
}
|
827
|
+
}
|
828
|
+
//remove tick/cross after some time
|
829
|
+
setTimeout(() => {
|
830
|
+
loading.innerHTML = "";
|
831
|
+
}, 4000);
|
832
|
+
}
|
833
|
+
|
834
|
+
//simple function to write text to the alert box
|
835
|
+
async function errorAlert(text) {
|
836
|
+
if (
|
837
|
+
document.getElementById("alert-text") !== null &&
|
838
|
+
document.getElementById("alert") !== null
|
839
|
+
) {
|
840
|
+
document.getElementById("alert-text").textContent = "\r\n" + text + "\r\n";
|
841
|
+
document.getElementById("alert").style.display = "block";
|
842
|
+
document.getElementById("alert").style.textAlign = "left";
|
843
|
+
}
|
844
|
+
return 0;
|
845
|
+
}
|
846
|
+
|
847
|
+
//convert yaml box into json box
|
848
|
+
async function yamlToJson() {
|
849
|
+
//get box element
|
850
|
+
config_box_element = document.getElementById("config-box");
|
851
|
+
if (config_box_element == null) {
|
852
|
+
errorAlert("Unable to obtain config box");
|
853
|
+
} else {
|
854
|
+
const response = await fetch(`get-json`, {
|
855
|
+
method: "POST",
|
856
|
+
headers: {
|
857
|
+
"Content-Type": "application/json",
|
858
|
+
},
|
859
|
+
body: config_box_element.value,
|
860
|
+
});
|
861
|
+
response_status = await response.status; //return status
|
862
|
+
if (response_status == 201) {
|
863
|
+
showChangeStatus(response_status, {});
|
864
|
+
blob = await response.blob(); //get data blob
|
865
|
+
config = await new Response(blob).json(); //obtain json from blob
|
866
|
+
config_box_element.value = JSON.stringify(config, null, 2);
|
867
|
+
} else {
|
868
|
+
showChangeStatus(response_status, await response.json());
|
869
|
+
}
|
870
|
+
}
|
871
|
+
return 0;
|
872
|
+
}
|