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.
@@ -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>&#x2713;</p>`;
817
+ } else {
818
+ //then show a cross
819
+ loading.classList.remove("loading");
820
+ loading.innerHTML = `<p class=cross>&#x292C;</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
+ }