emhass 0.13.0__py3-none-any.whl → 0.13.2__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.
@@ -0,0 +1,888 @@
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
+ let param_definitions = await getParamDefinitions();
23
+ //obtain configuration from emhass (pull)
24
+ let 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
+ let 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
+ let response_status = 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
+ let 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
+ let response_status = 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
+ let 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
+ let blob = await response.blob(); //get data blob
113
+ let htmlTemplateData = await new Response(blob).text(); //obtain html from blob
114
+ return 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
+ let header_input_list = ["set_use_battery", "set_use_pv", "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 (let 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 (let header_input_param of header_input_list) {
142
+ if (param_definitions[section].hasOwnProperty(header_input_param)) {
143
+ //grab default from definitions file
144
+ let value = param_definitions[section][header_input_param]["default_value"];
145
+ //find input element (using the parameter name as the input element ID)
146
+ let 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
+ let SectionContainer = document.getElementById(section);
178
+ //get the body container inside the section (where the parameters will be appended)
179
+ let 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
+ let 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 = SectionContainer.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
+ let 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
+ let type = "";
338
+ let inputs = "";
339
+ let type_specific_html = "";
340
+ let type_specific_html_end = "";
341
+ let placeholder = ""
342
+
343
+ //switch statement to adjust generated html according to the parameter data type (definitions in definitions file)
344
+ switch (parameter_definition_object["input"]) {
345
+ case "array.int":
346
+ //number
347
+ case "int":
348
+ type = "number";
349
+ placeholder = parseInt(parameter_definition_object["default_value"]);
350
+ break;
351
+ case "array.float":
352
+ case "float":
353
+ type = "number";
354
+ placeholder = parseFloat(parameter_definition_object["default_value"]);
355
+ break;
356
+ //text (string)
357
+ case "array.string":
358
+ case "string":
359
+ type = "text";
360
+ placeholder = parameter_definition_object["default_value"];
361
+ break;
362
+ case "array.time":
363
+ //time ("00:00")
364
+ case "time":
365
+ type = "time";
366
+ break;
367
+ //checkbox (boolean)
368
+ case "array.boolean":
369
+ case "boolean":
370
+ type = "checkbox";
371
+ type_specific_html = `
372
+ <label class="switch">
373
+ `;
374
+ type_specific_html_end = `
375
+ <span class="slider"></span>
376
+ </label>
377
+ `;
378
+ placeholder = parameter_definition_object["default_value"] === "true";
379
+ break;
380
+ //selects (pick)
381
+ case "select":
382
+ //format selects later
383
+ break;
384
+ }
385
+
386
+ //check default values saved in param definitions
387
+ //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)
388
+ //check if a param value is saved in the config file (if so overwrite definition default)
389
+ let value = checkConfigParam(placeholder, 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
+ let 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}" placeholder=${parameter_definition_object["default_value"]} value=${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 (let param of Object.values(value)) {
420
+ for (let items of Object.values(param)) {
421
+ inputs += `<input class="param_input" type="${type}" placeholder=${Object.values(items)[0]} value=${
422
+ Object.values(items)[0]
423
+ }>`;
424
+ }
425
+ inputs += `</br>`;
426
+ }
427
+ return inputs;
428
+ }
429
+ // array of values
430
+ else {
431
+ let inputs = "";
432
+ for (let param of value) {
433
+ inputs += `
434
+ ${type_specific_html}
435
+ <input class="param_input" type="${type}" placeholder=${parameter_definition_object["default_value"]} value=${param}>
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
+ let 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
+ let 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
+ let param_element = document.getElementById(param);
471
+ let param_input
472
+ if (param_element == null) {
473
+ console.log(
474
+ "Unable to find " + parameter_definition_name + " param div container"
475
+ );
476
+ return 1;
477
+ }
478
+ let param_input_list = param_element.getElementsByTagName("input");
479
+ if (param_input_list.length == 0) {
480
+ console.log(
481
+ "Unable to find " + parameter_definition_name + " param input/s"
482
+ );
483
+ }
484
+
485
+ //verify if input is a boolean (if so remove parent slider/switch element with input)
486
+ if (
487
+ param_input_list[param_input_list.length - 1].parentNode.tagName === "LABEL"
488
+ ) {
489
+ param_input = param_input_list[param_input_list.length - 1].parentNode;
490
+ } else {
491
+ param_input = param_input_list[param_input_list.length - 1];
492
+ }
493
+
494
+ //if param is "load_peak_hour_periods", remove both start and end param inputs as well as the line brake tag separating the inputs
495
+ if (param == "load_peak_hour_periods") {
496
+ if (param_input_list.length > 2) {
497
+ let brs = document.getElementById(param).getElementsByTagName("br");
498
+ param_input_list[param_input_list.length - 1].remove();
499
+ param_input_list[param_input_list.length - 1].remove();
500
+ brs[brs.length - 1].remove();
501
+ }
502
+ } else if (param_input_list.length > 1) {
503
+ param_input.remove();
504
+ }
505
+ }
506
+
507
+ //check requirement_element inputs,
508
+ //if requirement_element don't match requirement_value, add .requirement-disable class to param_element
509
+ //else remove class
510
+ function checkRequirements(
511
+ requirement_element,
512
+ param_element,
513
+ requirement_value
514
+ ) {
515
+ let requirement_element_value
516
+ //get current value of required element
517
+ if (requirement_element.type == "checkbox") {
518
+ requirement_element_value = requirement_element.checked;
519
+ } else {
520
+ requirement_element_value = requirement_element.value;
521
+ }
522
+
523
+ if (requirement_element_value != requirement_value) {
524
+ if (!param_element.classList.contains("requirement-disable")) {
525
+ param_element.classList.add("requirement-disable");
526
+ }
527
+ } else if (param_element.classList.contains("requirement-disable")) {
528
+ param_element.classList.remove("requirement-disable");
529
+ }
530
+ }
531
+
532
+ //on header input change, execute accordingly
533
+ function headerElement(element, param_definitions, config) {
534
+ //obtain section body element
535
+ let section_card = element.closest(".section-card");
536
+ let param_list
537
+ let difference
538
+ if (section_card == null) {
539
+ console.log("Unable to obtain section-card");
540
+ return 1;
541
+ }
542
+ let param_container = section_card.getElementsByClassName("section-body");
543
+ if (param_container.length > 0) {
544
+ param_container = section_card.getElementsByClassName("section-body")[0];
545
+ } else {
546
+ console.log("Unable to obtain section-body");
547
+ return 1;
548
+ }
549
+
550
+ switch (element.id) {
551
+ //if set_use_battery, add or remove battery section (inc. params)
552
+ case "set_use_battery":
553
+ if (element.checked) {
554
+ param_container.innerHTML = "";
555
+ buildParamContainers("Battery", param_definitions["Battery"], config, [
556
+ "set_use_battery",
557
+ ]);
558
+ element.checked = true;
559
+ } else {
560
+ param_container.innerHTML = "";
561
+ }
562
+ break;
563
+
564
+ //if set_use_pv, add or remove PV section (inc. related params)
565
+ case "set_use_pv":
566
+ if (element.checked) {
567
+ param_container.innerHTML = "";
568
+ buildParamContainers("Solar System (PV)", param_definitions["Solar System (PV)"], config, [
569
+ "set_use_pv",
570
+ ]);
571
+ element.checked = true;
572
+ } else {
573
+ param_container.innerHTML = "";
574
+ }
575
+ break;
576
+
577
+ //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
578
+ case "number_of_deferrable_loads":
579
+ //get a list of param in section
580
+ param_list = param_container.getElementsByClassName("param");
581
+ if (param_list.length <= 0) {
582
+ console.log(
583
+ "There has been an issue counting the amount of params in number_of_deferrable_loads"
584
+ );
585
+ return 1;
586
+ }
587
+ //calculate how much off the fist parameters input elements amount to is, compering to the number_of_deferrable_loads value
588
+ difference =
589
+ parseInt(element.value) -
590
+ param_container.firstElementChild.querySelectorAll("input").length;
591
+ //add elements based on how many elements are missing
592
+ if (difference > 0) {
593
+ for (let i = difference; i >= 1; i--) {
594
+ for (const param of param_list) {
595
+ //append element, do not pass config to obtain default parameter from definitions file
596
+ plusElements(param.id, param_definitions, "Deferrable Loads", {});
597
+ }
598
+ }
599
+ }
600
+ //subtract elements based how many elements its over
601
+ if (difference < 0) {
602
+ for (let i = difference; i <= -1; i++) {
603
+ for (const param of param_list) {
604
+ minusElements(param.id);
605
+ }
606
+ }
607
+ }
608
+ break;
609
+ }
610
+ }
611
+
612
+ //checks parameter value in config, updates value if exists
613
+ function checkConfigParam(value, config, parameter_definition_name) {
614
+ if (config !== null && config !== undefined) {
615
+ //check if parameter has a saved value
616
+ if (parameter_definition_name in config) {
617
+ value = config[parameter_definition_name];
618
+ }
619
+ }
620
+ return value;
621
+ }
622
+
623
+ //send all parameter input values to EMHASS, to save to config.json and param.pkl
624
+ async function saveConfiguration(param_definitions) {
625
+ //start wth none
626
+ let config = {};
627
+ let param_inputs
628
+ let param_element
629
+
630
+ //if section-cards (config sections/list) exists
631
+ let config_card = document.getElementsByClassName("section-card");
632
+ //check if page is in list or box view
633
+ let config_box_element = document.getElementById("config-box");
634
+
635
+ //if true, in list view
636
+ if (Boolean(config_card.length)) {
637
+ //retrieve params and their input/s by looping though param_definitions list
638
+ //loop through the sections
639
+ for (var [section_name, section_object] of Object.entries(
640
+ param_definitions
641
+ )) {
642
+ //loop through parameters
643
+ for (let [
644
+ parameter_definition_name,
645
+ parameter_definition_object,
646
+ ] of Object.entries(section_object)) {
647
+ let param_values = []; //stores the obtained param input values
648
+ let param_array = false;
649
+ //get param container
650
+ param_element = document.getElementById(parameter_definition_name);
651
+ if (param_element == null) {
652
+ console.debug(
653
+ "unable to find " +
654
+ parameter_definition_name +
655
+ " param div container element, skipping this param"
656
+ );
657
+ }
658
+ //extract input/s and their value/s from param container div
659
+ else {
660
+ if (param_element.tagName !== "INPUT") {
661
+ param_inputs = param_element.getElementsByClassName("param_input");
662
+ } else {
663
+ //check if param_element is also param_input (ex. for header parameters)
664
+ param_inputs = [param_element];
665
+ }
666
+
667
+ // loop though param_inputs, extract the element/s values
668
+ for (let input of param_inputs) {
669
+ switch (input.type) {
670
+ case "number":
671
+ param_values.push(parseFloat(input.value));
672
+ break;
673
+ case "checkbox":
674
+ param_values.push(input.checked);
675
+ break;
676
+ default:
677
+ param_values.push(input.value);
678
+ break;
679
+ }
680
+ }
681
+ //obtain param input type from param_definitions, check if param should be formatted as an array
682
+ param_array = Boolean(
683
+ !parameter_definition_object["input"].search("array")
684
+ );
685
+
686
+ //build parameters using values extracted from param_inputs
687
+
688
+ // If time with 2 sets (load_peak_hour_periods)
689
+ if (
690
+ parameter_definition_object["input"] == "array.time" &&
691
+ param_values.length % 2 === 0
692
+ ) {
693
+ config[parameter_definition_name] = {};
694
+ for (let i = 0; i < param_values.length; i++) {
695
+ config[parameter_definition_name][
696
+ "period_hp_" +
697
+ (Object.keys(config[parameter_definition_name]).length + 1)
698
+ ] = [{ start: param_values[i] }, { end: param_values[++i] }];
699
+ }
700
+ continue;
701
+ }
702
+
703
+ //single value
704
+ if (param_values.length && !param_array) {
705
+ config[parameter_definition_name] = param_values[0];
706
+ }
707
+
708
+ //array value
709
+ else if (param_values.length) {
710
+ config[parameter_definition_name] = param_values;
711
+ }
712
+ }
713
+ }
714
+ }
715
+ }
716
+
717
+ //if box view, extract json from box view
718
+ else if (config_box_element !== null) {
719
+ //try and parse json from box
720
+ try {
721
+ config = JSON.parse(config_box_element.value);
722
+ } catch (error) {
723
+ //if json error, show in alert box
724
+ document.getElementById("alert-text").textContent =
725
+ "\r\n" +
726
+ error +
727
+ "\r\n" +
728
+ "JSON Error: String values may not be wrapped in quotes";
729
+ document.getElementById("alert").style.display = "block";
730
+ document.getElementById("alert").style.textAlign = "center";
731
+ return 0;
732
+ }
733
+ }
734
+ // else, cant find box or list view
735
+ else {
736
+ errorAlert("There has been an error verifying box or list view");
737
+ }
738
+
739
+ //finally, send built config to emhass
740
+ const response = await fetch(`set-config`, {
741
+ method: "POST",
742
+ headers: {
743
+ "Content-Type": "application/json",
744
+ },
745
+ body: JSON.stringify(config),
746
+ });
747
+ showChangeStatus(response.status, await response.json());
748
+ }
749
+
750
+ //Toggle between box (json) and list view
751
+ async function ToggleView(param_definitions, list_html, default_reset) {
752
+ let selected = "";
753
+ config = {};
754
+
755
+ //find out if list or box view is active
756
+ let configuration_container = document.getElementById("configuration-container");
757
+ if (configuration_container == null) {
758
+ errorAlert("Unable to find Configuration Container element");
759
+ }
760
+ //get yaml button
761
+ let yaml_button = document.getElementById("yaml");
762
+ if (yaml_button == null) {
763
+ console.log("Unable to obtain yaml button");
764
+ }
765
+
766
+ // if section-cards (config sections/list) exists
767
+ let config_card = configuration_container.getElementsByClassName("section-card");
768
+ //selected view (0 = box)
769
+ let selected_view = Boolean(config_card.length);
770
+
771
+ //if default_reset is passed do not switch views, instead reinitialize view with default config as values
772
+ if (default_reset) {
773
+ selected_view = !selected_view;
774
+ //obtain default config as config (when pressing the default button)
775
+ config = await ObtainDefaultConfig();
776
+ } else {
777
+ //obtain latest config
778
+ config = await obtainConfig();
779
+ }
780
+
781
+ //if array is empty assume json box is selected
782
+ if (selected_view) {
783
+ selected = "list";
784
+ } else {
785
+ selected = "box";
786
+ }
787
+ //remove contents of current view
788
+ configuration_container.innerHTML = "";
789
+ //build new view
790
+ switch (selected) {
791
+ case "box":
792
+ //load list
793
+ loadConfigurationListView(param_definitions, config, list_html);
794
+ yaml_button.style.display = "none";
795
+ break;
796
+ case "list":
797
+ //load box
798
+ loadConfigurationBoxPage(config);
799
+ yaml_button.style.display = "block";
800
+ break;
801
+ }
802
+ }
803
+
804
+ //load box (json textarea) view
805
+ async function loadConfigurationBoxPage(config) {
806
+ //get configuration container element
807
+ let configuration_container = document.getElementById("configuration-container");
808
+ if (configuration_container == null) {
809
+ errorAlert("Unable to find Configuration Container element");
810
+ }
811
+ //append configuration container with textbox area
812
+ configuration_container.innerHTML = `
813
+ <textarea id="config-box" rows="30" placeholder="{}"></textarea>
814
+ `;
815
+ //set created textarea box with retrieved config
816
+ document.getElementById("config-box").innerHTML = JSON.stringify(
817
+ config,
818
+ null,
819
+ 2
820
+ );
821
+ }
822
+
823
+ //function in control of status icons and alert box from a fetch request
824
+ async function showChangeStatus(status, logJson) {
825
+ let loading = document.getElementById("loader"); //element showing statuses
826
+ if (loading === null) {
827
+ console.log("unable to find loader element");
828
+ return 1;
829
+ }
830
+ if (status === 200 || status === 201) {
831
+ //if status is 200 or 201, then show a tick
832
+ loading.innerHTML = `<p class=tick>&#x2713;</p>`;
833
+ } else {
834
+ //then show a cross
835
+ loading.classList.remove("loading");
836
+ loading.innerHTML = `<p class=cross>&#x292C;</p>`; //show cross icon to indicate an error
837
+ if (logJson.length != 0 && document.getElementById("alert-text") !== null) {
838
+ document.getElementById("alert-text").textContent =
839
+ "\r\n\u2022 " + logJson.join("\r\n\u2022 "); //show received log data in alert box
840
+ document.getElementById("alert").style.display = "block";
841
+ document.getElementById("alert").style.textAlign = "left";
842
+ }
843
+ }
844
+ //remove tick/cross after some time
845
+ setTimeout(() => {
846
+ loading.innerHTML = "";
847
+ }, 4000);
848
+ }
849
+
850
+ //simple function to write text to the alert box
851
+ async function errorAlert(text) {
852
+ if (
853
+ document.getElementById("alert-text") !== null &&
854
+ document.getElementById("alert") !== null
855
+ ) {
856
+ document.getElementById("alert-text").textContent = "\r\n" + text + "\r\n";
857
+ document.getElementById("alert").style.display = "block";
858
+ document.getElementById("alert").style.textAlign = "left";
859
+ }
860
+ return 0;
861
+ }
862
+
863
+ //convert yaml box into json box
864
+ async function yamlToJson() {
865
+ //get box element
866
+ let config_box_element = document.getElementById("config-box");
867
+ if (config_box_element == null) {
868
+ errorAlert("Unable to obtain config box");
869
+ } else {
870
+ const response = await fetch(`get-json`, {
871
+ method: "POST",
872
+ headers: {
873
+ "Content-Type": "application/json",
874
+ },
875
+ body: config_box_element.value,
876
+ });
877
+ let response_status = response.status; //return status
878
+ if (response_status == 201) {
879
+ showChangeStatus(response_status, {});
880
+ let blob = await response.blob(); //get data blob
881
+ config = await new Response(blob).json(); //obtain json from blob
882
+ config_box_element.value = JSON.stringify(config, null, 2);
883
+ } else {
884
+ showChangeStatus(response_status, await response.json());
885
+ }
886
+ }
887
+ return 0;
888
+ }