emhass 0.10.6__py3-none-any.whl → 0.15.5__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,956 @@
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 through 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
+ // Dynamic hiding for InfluxDB options
169
+ const use_influx_param = "use_influxdb";
170
+ const influx_related_params = [
171
+ "influxdb_host",
172
+ "influxdb_port",
173
+ "influxdb_username",
174
+ "influxdb_password",
175
+ "influxdb_database",
176
+ "influxdb_measurement",
177
+ "influxdb_retention_policy",
178
+ "influxdb_use_ssl",
179
+ "influxdb_verify_ssl"
180
+ ];
181
+
182
+ const influx_toggle_div = document.getElementById(use_influx_param);
183
+ if (influx_toggle_div) {
184
+ // The actual input is inside the div with the ID
185
+ const influx_input = influx_toggle_div.querySelector("input");
186
+ if (influx_input) {
187
+ const toggleInfluxVisibility = () => {
188
+ const isChecked = influx_input.checked;
189
+ influx_related_params.forEach(paramId => {
190
+ const paramDiv = document.getElementById(paramId);
191
+ if (paramDiv) {
192
+ paramDiv.style.display = isChecked ? "" : "none";
193
+ }
194
+ });
195
+ };
196
+
197
+ // Add listener and set initial state
198
+ influx_input.addEventListener("change", toggleInfluxVisibility);
199
+ toggleInfluxVisibility();
200
+ }
201
+ }
202
+
203
+ // ML Forecaster Visibility Logic
204
+ const forecast_method_param = "load_forecast_method";
205
+ const ml_related_params = [
206
+ "model_type",
207
+ "var_model",
208
+ "sklearn_model",
209
+ "regression_model",
210
+ "num_lags",
211
+ "split_date_delta",
212
+ "n_trials",
213
+ "perform_backtest"
214
+ ];
215
+
216
+ const forecast_method_div = document.getElementById(forecast_method_param);
217
+ if (forecast_method_div) {
218
+ const method_select = forecast_method_div.querySelector("select, input");
219
+ if (method_select) {
220
+ const toggleMLVisibility = () => {
221
+ const isML = method_select.value === "mlforecaster";
222
+ ml_related_params.forEach(paramId => {
223
+ const paramDiv = document.getElementById(paramId);
224
+ if (paramDiv) {
225
+ paramDiv.style.display = isML ? "" : "none";
226
+ }
227
+ });
228
+ };
229
+ // Add listener and set initial state
230
+ method_select.addEventListener("change", toggleMLVisibility);
231
+ method_select.addEventListener("input", toggleMLVisibility); // Handle both select and text input types
232
+ toggleMLVisibility();
233
+ }
234
+ }
235
+ }
236
+
237
+ //build sections body, containing parameter/param containers (containing parameter/param inputs)
238
+ function buildParamContainers(
239
+ section,
240
+ section_parameters_definitions,
241
+ config,
242
+ header_input_list
243
+ ) {
244
+ //get the section container element
245
+ let SectionContainer = document.getElementById(section);
246
+ //get the body container inside the section (where the parameters will be appended)
247
+ let SectionParamElement = SectionContainer.getElementsByClassName("section-body");
248
+ if (SectionContainer == null || SectionParamElement.length == 0) {
249
+ console.error("Unable to find Section container or Section Body");
250
+ return 0;
251
+ }
252
+
253
+ //loop though the sections parameters in definition file, generate and append param (div) elements for the section
254
+ for (const [
255
+ parameter_definition_name,
256
+ parameter_definition_object,
257
+ ] of Object.entries(section_parameters_definitions)) {
258
+ //check parameter definitions have the required key values
259
+ if (
260
+ !("friendly_name" in parameter_definition_object) ||
261
+ !("Description" in parameter_definition_object) ||
262
+ !("input" in parameter_definition_object) ||
263
+ !("default_value" in parameter_definition_object)
264
+ ) {
265
+ console.log(
266
+ parameter_definition_name +
267
+ " is missing some required values in the definitions file"
268
+ );
269
+ continue;
270
+ }
271
+ if (
272
+ parameter_definition_object["input"] === "select" &&
273
+ !("select_options" in parameter_definition_object)
274
+ ) {
275
+ console.log(
276
+ parameter_definition_name +
277
+ " is missing select_options values in the definitions file"
278
+ );
279
+ continue;
280
+ }
281
+
282
+ //check if param is set in the section header, if so skip building param
283
+ if (header_input_list.includes(parameter_definition_name)) {
284
+ continue;
285
+ }
286
+
287
+ //if parameter type == array.* and not in "Deferrable Loads" section, append plus and minus buttons in param div
288
+ let array_buttons = "";
289
+ if (
290
+ parameter_definition_object["input"].search("array.") > -1 &&
291
+ section != "Deferrable Loads"
292
+ ) {
293
+ array_buttons = `
294
+ <button type="button" class="input-plus ${parameter_definition_name}">+</button>
295
+ <button type="button" class="input-minus ${parameter_definition_name}">-</button>
296
+ <br>
297
+ `;
298
+ }
299
+
300
+ //generates and appends param container into section
301
+ //buildParamElement() builds the parameter input/s and returns html to append in param-input
302
+ SectionParamElement[0].innerHTML += `
303
+ <div class="param" id="${parameter_definition_name}">
304
+ <h5>${
305
+ parameter_definition_object["friendly_name"]
306
+ }:</h5> <i>${parameter_definition_name}</i> </br>
307
+ ${array_buttons}
308
+ <div class="param-input">
309
+ ${buildParamElement(
310
+ parameter_definition_object,
311
+ parameter_definition_name,
312
+ config
313
+ )}
314
+ </div>
315
+ <p>${parameter_definition_object["Description"]}</p>
316
+ </div>
317
+ `;
318
+ }
319
+
320
+ //after looping though, build and appending the parameters in the corresponding section:
321
+ //create add button (array plus) event listeners
322
+ let plus = SectionContainer.querySelectorAll(".input-plus");
323
+ plus.forEach(function (answer) {
324
+ answer.addEventListener("click", () =>
325
+ plusElements(answer.classList[1], param_definitions, section, {})
326
+ );
327
+ });
328
+
329
+ //create subtract button (array minus) event listeners
330
+ let minus = SectionContainer.querySelectorAll(".input-minus");
331
+ minus.forEach(function (answer) {
332
+ answer.addEventListener("click", () => minusElements(answer.classList[1]));
333
+ });
334
+
335
+ //check initial checkbox state, check "value" of input and match to "checked" value
336
+ let checkbox = SectionContainer.querySelectorAll("input[type='checkbox']");
337
+ checkbox.forEach(function (answer) {
338
+ let value = answer.value === "true";
339
+ answer.checked = value;
340
+ });
341
+
342
+ //loop though sections params again, check if param has a requirement, if so add a event listener to the required param input
343
+ //if required param gets changed, trigger function to check if that required parameter matches the required value for the param
344
+ //if false, add css class to param element to shadow it, to show that its unaccessible
345
+ for (const [
346
+ parameter_definition_name,
347
+ parameter_definition_object,
348
+ ] of Object.entries(section_parameters_definitions)) {
349
+ //check if param has a requirement from definitions file
350
+ if ("requires" in parameter_definition_object) {
351
+ // get param requirement element
352
+ const requirement_element = document.getElementById(
353
+ Object.keys(parameter_definition_object["requires"])[0]
354
+ );
355
+ if (requirement_element == null) {
356
+ console.debug(
357
+ "unable to find " +
358
+ Object.keys(parameter_definition_object["requires"])[0] +
359
+ " param div container element"
360
+ );
361
+ continue;
362
+ }
363
+
364
+ // get param element that has requirement
365
+ const param_element = document.getElementById(parameter_definition_name);
366
+ if (param_element == null) {
367
+ console.debug(
368
+ "unable to find " +
369
+ parameter_definition_name +
370
+ " param div container element"
371
+ );
372
+ continue;
373
+ }
374
+
375
+ //obtain required param inputs, add event listeners
376
+ let requirement_inputs =
377
+ requirement_element.getElementsByClassName("param_input");
378
+ //grab required value
379
+ const requirement_value = Object.values(
380
+ parameter_definition_object["requires"]
381
+ )[0];
382
+
383
+ //for all required inputs
384
+ for (const input of requirement_inputs) {
385
+ //if listener not already attached
386
+ if (input.getAttribute("listener") !== "true") {
387
+ //create event listener with arguments referencing the required param. param with requirement and required value
388
+ input.addEventListener("input", () =>
389
+ checkRequirements(input, param_element, requirement_value)
390
+ );
391
+ //manually run function to gain initial param element initial state
392
+ checkRequirements(input, param_element, requirement_value);
393
+ }
394
+ }
395
+ }
396
+ }
397
+ }
398
+
399
+ //create html input element/s for a param container (called by buildParamContainers)
400
+ function buildParamElement(
401
+ parameter_definition_object,
402
+ parameter_definition_name,
403
+ config
404
+ ) {
405
+ let type = "";
406
+ let inputs = "";
407
+ let type_specific_html = "";
408
+ let type_specific_html_end = "";
409
+ let placeholder = ""
410
+
411
+ //switch statement to adjust generated html according to the parameter data type (definitions in definitions file)
412
+ switch (parameter_definition_object["input"]) {
413
+ case "array.int":
414
+ //number
415
+ case "int":
416
+ type = "number";
417
+ placeholder = Number.parseInt(parameter_definition_object["default_value"]);
418
+ break;
419
+ case "array.float":
420
+ case "float":
421
+ type = "number";
422
+ placeholder = Number.parseFloat(parameter_definition_object["default_value"]);
423
+ break;
424
+ //text (string)
425
+ case "array.string":
426
+ case "string":
427
+ type = "text";
428
+ placeholder = parameter_definition_object["default_value"];
429
+ break;
430
+ case "array.time":
431
+ //time ("00:00")
432
+ case "time":
433
+ type = "time";
434
+ break;
435
+ //checkbox (boolean)
436
+ case "array.boolean":
437
+ case "boolean":
438
+ type = "checkbox";
439
+ type_specific_html = `
440
+ <label class="switch">
441
+ `;
442
+ type_specific_html_end = `
443
+ <span class="slider"></span>
444
+ </label>
445
+ `;
446
+ placeholder = parameter_definition_object["default_value"] === "true";
447
+ break;
448
+ //selects (pick)
449
+ case "select":
450
+ //format selects later
451
+ break;
452
+ }
453
+
454
+ //check default values saved in param definitions
455
+ //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)
456
+ //check if a param value is saved in the config file (if so overwrite definition default)
457
+ let value = checkConfigParam(placeholder, config, parameter_definition_name);
458
+
459
+ //generate and return param input html,
460
+ //check if param value is not an object, if so assume its a single value.
461
+ if (typeof value !== "object") {
462
+ //if select, generate and return select elements instead of input
463
+ if (parameter_definition_object["input"] == "select") {
464
+ let inputs = `<select class="param_input">`;
465
+ for (const options of parameter_definition_object["select_options"]) {
466
+ let selected = ""
467
+ //if item in select is the same as the config value, then append "selected" tag
468
+ if (options==value) {selected = `selected="selected"`}
469
+ inputs += `<option ${selected}>${options}</option>`;
470
+ }
471
+ inputs += `</select>`;
472
+ return inputs;
473
+ }
474
+ // generate param input html and return
475
+ else {
476
+ return `
477
+ ${type_specific_html}
478
+ <input class="param_input" type="${type}" placeholder=${parameter_definition_object["default_value"]} value=${value} >
479
+ ${type_specific_html_end}
480
+ `;
481
+ }
482
+ }
483
+ // else if object, loop though array of values, generate input element per value, and and return
484
+ else {
485
+ //for items such as load_peak_hour_periods (object of objects with arrays)
486
+ if (typeof Object.values(value)[0] === "object") {
487
+ for (let param of Object.values(value)) {
488
+ for (let items of Object.values(param)) {
489
+ inputs += `<input class="param_input" type="${type}" placeholder=${Object.values(items)[0]} value=${
490
+ Object.values(items)[0]
491
+ }>`;
492
+ }
493
+ inputs += `</br>`;
494
+ }
495
+ return inputs;
496
+ }
497
+ // array of values
498
+ else {
499
+ let inputs = "";
500
+ for (let param of value) {
501
+ inputs += `
502
+ ${type_specific_html}
503
+ <input class="param_input" type="${type}" placeholder=${parameter_definition_object["default_value"]} value=${param}>
504
+ ${type_specific_html_end}
505
+ `;
506
+ }
507
+ return inputs;
508
+ }
509
+ }
510
+ }
511
+
512
+ //add param inputs in param div container (for type array)
513
+ function plusElements(
514
+ parameter_definition_name,
515
+ param_definitions,
516
+ section,
517
+ config
518
+ ) {
519
+ let param_element = document.getElementById(parameter_definition_name);
520
+ if (param_element == null) {
521
+ console.log(
522
+ "Unable to find " + parameter_definition_name + " param div container"
523
+ );
524
+ return 1;
525
+ }
526
+ let param_input_container =
527
+ param_element.getElementsByClassName("param-input")[0];
528
+ // Add a copy of the param element
529
+ param_input_container.innerHTML += buildParamElement(
530
+ param_definitions[section][parameter_definition_name],
531
+ parameter_definition_name,
532
+ config
533
+ );
534
+ }
535
+
536
+ //Remove param inputs in param div container (minimum 1)
537
+ function minusElements(param) {
538
+ let param_element = document.getElementById(param);
539
+ let param_input
540
+ if (param_element == null) {
541
+ console.log(
542
+ "Unable to find " + parameter_definition_name + " param div container"
543
+ );
544
+ return 1;
545
+ }
546
+ let param_input_list = param_element.getElementsByTagName("input");
547
+ if (param_input_list.length == 0) {
548
+ console.log(
549
+ "Unable to find " + parameter_definition_name + " param input/s"
550
+ );
551
+ }
552
+
553
+ //verify if input is a boolean (if so remove parent slider/switch element with input)
554
+ if (
555
+ param_input_list[param_input_list.length - 1].parentNode.tagName === "LABEL"
556
+ ) {
557
+ param_input = param_input_list[param_input_list.length - 1].parentNode;
558
+ } else {
559
+ param_input = param_input_list[param_input_list.length - 1];
560
+ }
561
+
562
+ //if param is "load_peak_hour_periods", remove both start and end param inputs as well as the line brake tag separating the inputs
563
+ if (param == "load_peak_hour_periods") {
564
+ if (param_input_list.length > 2) {
565
+ let brs = document.getElementById(param).getElementsByTagName("br");
566
+ param_input_list[param_input_list.length - 1].remove();
567
+ param_input_list[param_input_list.length - 1].remove();
568
+ brs[brs.length - 1].remove();
569
+ }
570
+ } else if (param_input_list.length > 1) {
571
+ param_input.remove();
572
+ }
573
+ }
574
+
575
+ //check requirement_element inputs,
576
+ //if requirement_element don't match requirement_value, add .requirement-disable class to param_element
577
+ //else remove class
578
+ function checkRequirements(
579
+ requirement_element,
580
+ param_element,
581
+ requirement_value
582
+ ) {
583
+ let requirement_element_value
584
+ //get current value of required element
585
+ if (requirement_element.type == "checkbox") {
586
+ requirement_element_value = requirement_element.checked;
587
+ } else {
588
+ requirement_element_value = requirement_element.value;
589
+ }
590
+
591
+ if (requirement_element_value != requirement_value) {
592
+ if (!param_element.classList.contains("requirement-disable")) {
593
+ param_element.classList.add("requirement-disable");
594
+ }
595
+ } else if (param_element.classList.contains("requirement-disable")) {
596
+ param_element.classList.remove("requirement-disable");
597
+ }
598
+ }
599
+
600
+ //on header input change, execute accordingly
601
+ function headerElement(element, param_definitions, config) {
602
+ //obtain section body element
603
+ let section_card = element.closest(".section-card");
604
+ let param_list
605
+ let difference
606
+ if (section_card == null) {
607
+ console.log("Unable to obtain section-card");
608
+ return 1;
609
+ }
610
+ let param_container = section_card.getElementsByClassName("section-body");
611
+ if (param_container.length > 0) {
612
+ param_container = section_card.getElementsByClassName("section-body")[0];
613
+ } else {
614
+ console.log("Unable to obtain section-body");
615
+ return 1;
616
+ }
617
+
618
+ switch (element.id) {
619
+ //if set_use_battery, add or remove battery section (inc. params)
620
+ case "set_use_battery":
621
+ if (element.checked) {
622
+ param_container.innerHTML = "";
623
+ buildParamContainers("Battery", param_definitions["Battery"], config, [
624
+ "set_use_battery",
625
+ ]);
626
+ element.checked = true;
627
+ } else {
628
+ param_container.innerHTML = "";
629
+ }
630
+ break;
631
+
632
+ //if set_use_pv, add or remove PV section (inc. related params)
633
+ case "set_use_pv":
634
+ if (element.checked) {
635
+ param_container.innerHTML = "";
636
+ buildParamContainers("Solar System (PV)", param_definitions["Solar System (PV)"], config, [
637
+ "set_use_pv",
638
+ ]);
639
+ element.checked = true;
640
+ } else {
641
+ param_container.innerHTML = "";
642
+ }
643
+ break;
644
+
645
+ //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
646
+ case "number_of_deferrable_loads":
647
+ //get a list of param in section
648
+ param_list = param_container.getElementsByClassName("param");
649
+ if (param_list.length <= 0) {
650
+ console.log(
651
+ "There has been an issue counting the amount of params in number_of_deferrable_loads"
652
+ );
653
+ return 1;
654
+ }
655
+ //calculate how much off the fist parameters input elements amount to is, compering to the number_of_deferrable_loads value
656
+ difference =
657
+ Number.parseInt(element.value) -
658
+ param_container.firstElementChild.querySelectorAll("input").length;
659
+ //add elements based on how many elements are missing
660
+ if (difference > 0) {
661
+ for (let i = difference; i >= 1; i--) {
662
+ for (const param of param_list) {
663
+ //append element, do not pass config to obtain default parameter from definitions file
664
+ plusElements(param.id, param_definitions, "Deferrable Loads", {});
665
+ }
666
+ }
667
+ }
668
+ //subtract elements based how many elements its over
669
+ if (difference < 0) {
670
+ for (let i = difference; i <= -1; i++) {
671
+ for (const param of param_list) {
672
+ minusElements(param.id);
673
+ }
674
+ }
675
+ }
676
+ break;
677
+ }
678
+ }
679
+
680
+ //checks parameter value in config, updates value if exists
681
+ function checkConfigParam(value, config, parameter_definition_name) {
682
+ if (config !== null && config !== undefined) {
683
+ //check if parameter has a saved value
684
+ if (parameter_definition_name in config) {
685
+ value = config[parameter_definition_name];
686
+ }
687
+ }
688
+ return value;
689
+ }
690
+
691
+ //send all parameter input values to EMHASS, to save to config.json and param.pkl
692
+ async function saveConfiguration(param_definitions) {
693
+ //start wth none
694
+ let config = {};
695
+ let param_inputs
696
+ let param_element
697
+
698
+ //if section-cards (config sections/list) exists
699
+ let config_card = document.getElementsByClassName("section-card");
700
+ //check if page is in list or box view
701
+ let config_box_element = document.getElementById("config-box");
702
+
703
+ //if true, in list view
704
+ if (Boolean(config_card.length)) {
705
+ //retrieve params and their input/s by looping though param_definitions list
706
+ //loop through the sections
707
+ for (const [, section_object] of Object.entries(
708
+ param_definitions
709
+ )) {
710
+ //loop through parameters
711
+ for (let [
712
+ parameter_definition_name,
713
+ parameter_definition_object,
714
+ ] of Object.entries(section_object)) {
715
+ let param_values = []; //stores the obtained param input values
716
+ let param_array = false;
717
+ //get param container
718
+ param_element = document.getElementById(parameter_definition_name);
719
+ if (param_element == null) {
720
+ console.debug(
721
+ "unable to find " +
722
+ parameter_definition_name +
723
+ " param div container element, skipping this param"
724
+ );
725
+ }
726
+ //extract input/s and their value/s from param container div
727
+ else {
728
+ if (param_element.tagName !== "INPUT") {
729
+ param_inputs = param_element.getElementsByClassName("param_input");
730
+ } else {
731
+ //check if param_element is also param_input (ex. for header parameters)
732
+ param_inputs = [param_element];
733
+ }
734
+
735
+ // loop though param_inputs, extract the element/s values
736
+ for (let input of param_inputs) {
737
+ switch (input.type) {
738
+ case "number":
739
+ param_values.push(Number.parseFloat(input.value));
740
+ break;
741
+ case "checkbox":
742
+ param_values.push(input.checked);
743
+ break;
744
+ default:
745
+ param_values.push(input.value);
746
+ break;
747
+ }
748
+ }
749
+ //obtain param input type from param_definitions, check if param should be formatted as an array
750
+ param_array = Boolean(
751
+ !parameter_definition_object["input"].search("array")
752
+ );
753
+
754
+ //build parameters using values extracted from param_inputs
755
+
756
+ // If time with 2 sets (load_peak_hour_periods)
757
+ if (
758
+ parameter_definition_object["input"] == "array.time" &&
759
+ param_values.length % 2 === 0
760
+ ) {
761
+ config[parameter_definition_name] = {};
762
+ for (let i = 0; i < param_values.length; i++) {
763
+ config[parameter_definition_name][
764
+ "period_hp_" +
765
+ (Object.keys(config[parameter_definition_name]).length + 1)
766
+ ] = [{ start: param_values[i] }, { end: param_values[++i] }];
767
+ }
768
+ continue;
769
+ }
770
+
771
+ //single value
772
+ if (param_values.length && !param_array) {
773
+ config[parameter_definition_name] = param_values[0];
774
+ }
775
+
776
+ //array value
777
+ else if (param_values.length) {
778
+ config[parameter_definition_name] = param_values;
779
+ }
780
+ }
781
+ }
782
+ }
783
+ }
784
+
785
+ //if box view, extract json from box view
786
+ else if (config_box_element !== null) {
787
+ //try and parse json from box
788
+ try {
789
+ config = JSON.parse(config_box_element.value);
790
+ } catch (error) {
791
+ //if json error, show in alert box
792
+ document.getElementById("alert-text").textContent =
793
+ "\r\n" +
794
+ error +
795
+ "\r\n" +
796
+ "JSON Error: String values may not be wrapped in quotes";
797
+ document.getElementById("alert").style.display = "block";
798
+ document.getElementById("alert").style.textAlign = "center";
799
+ return 0;
800
+ }
801
+ }
802
+ // else, cant find box or list view
803
+ else {
804
+ errorAlert("There has been an error verifying box or list view");
805
+ }
806
+
807
+ //finally, send built config to emhass
808
+ const response = await fetch(`set-config`, {
809
+ method: "POST",
810
+ headers: {
811
+ "Content-Type": "application/json",
812
+ },
813
+ body: JSON.stringify(config),
814
+ });
815
+ showChangeStatus(response.status, await response.json());
816
+ }
817
+
818
+ //Toggle between box (json) and list view
819
+ async function ToggleView(param_definitions, list_html, default_reset) {
820
+ let selected = "";
821
+ config = {};
822
+
823
+ //find out if list or box view is active
824
+ let configuration_container = document.getElementById("configuration-container");
825
+ if (configuration_container == null) {
826
+ errorAlert("Unable to find Configuration Container element");
827
+ }
828
+ //get yaml button
829
+ let yaml_button = document.getElementById("yaml");
830
+ if (yaml_button == null) {
831
+ console.log("Unable to obtain yaml button");
832
+ }
833
+
834
+ // if section-cards (config sections/list) exists
835
+ let config_card = configuration_container.getElementsByClassName("section-card");
836
+ //selected view (0 = box)
837
+ let selected_view = Boolean(config_card.length);
838
+
839
+ //if default_reset is passed do not switch views, instead reinitialize view with default config as values
840
+ if (default_reset) {
841
+ selected_view = !selected_view;
842
+ //obtain default config as config (when pressing the default button)
843
+ config = await ObtainDefaultConfig();
844
+ } else {
845
+ //obtain latest config
846
+ config = await obtainConfig();
847
+ }
848
+
849
+ //if array is empty assume json box is selected
850
+ if (selected_view) {
851
+ selected = "list";
852
+ } else {
853
+ selected = "box";
854
+ }
855
+ //remove contents of current view
856
+ configuration_container.innerHTML = "";
857
+ //build new view
858
+ switch (selected) {
859
+ case "box":
860
+ //load list
861
+ loadConfigurationListView(param_definitions, config, list_html);
862
+ yaml_button.style.display = "none";
863
+ break;
864
+ case "list":
865
+ //load box
866
+ loadConfigurationBoxPage(config);
867
+ yaml_button.style.display = "block";
868
+ break;
869
+ }
870
+ }
871
+
872
+ //load box (json textarea) view
873
+ async function loadConfigurationBoxPage(config) {
874
+ //get configuration container element
875
+ let configuration_container = document.getElementById("configuration-container");
876
+ if (configuration_container == null) {
877
+ errorAlert("Unable to find Configuration Container element");
878
+ }
879
+ //append configuration container with textbox area
880
+ configuration_container.innerHTML = `
881
+ <textarea id="config-box" rows="30" placeholder="{}"></textarea>
882
+ `;
883
+ //set created textarea box with retrieved config
884
+ document.getElementById("config-box").innerHTML = JSON.stringify(
885
+ config,
886
+ null,
887
+ 2
888
+ );
889
+ }
890
+
891
+ //function in control of status icons and alert box from a fetch request
892
+ async function showChangeStatus(status, logJson) {
893
+ let loading = document.getElementById("loader"); //element showing statuses
894
+ if (loading === null) {
895
+ console.log("unable to find loader element");
896
+ return 1;
897
+ }
898
+ if (status === 200 || status === 201) {
899
+ //if status is 200 or 201, then show a tick
900
+ loading.innerHTML = `<p class=tick>&#x2713;</p>`;
901
+ } else {
902
+ //then show a cross
903
+ loading.classList.remove("loading");
904
+ loading.innerHTML = `<p class=cross>&#x292C;</p>`; //show cross icon to indicate an error
905
+ if (logJson.length != 0 && document.getElementById("alert-text") !== null) {
906
+ document.getElementById("alert-text").textContent =
907
+ "\r\n\u2022 " + logJson.join("\r\n\u2022 "); //show received log data in alert box
908
+ document.getElementById("alert").style.display = "block";
909
+ document.getElementById("alert").style.textAlign = "left";
910
+ }
911
+ }
912
+ //remove tick/cross after some time
913
+ setTimeout(() => {
914
+ loading.innerHTML = "";
915
+ }, 4000);
916
+ }
917
+
918
+ //simple function to write text to the alert box
919
+ async function errorAlert(text) {
920
+ if (
921
+ document.getElementById("alert-text") !== null &&
922
+ document.getElementById("alert") !== null
923
+ ) {
924
+ document.getElementById("alert-text").textContent = "\r\n" + text + "\r\n";
925
+ document.getElementById("alert").style.display = "block";
926
+ document.getElementById("alert").style.textAlign = "left";
927
+ }
928
+ return 0;
929
+ }
930
+
931
+ //convert yaml box into json box
932
+ async function yamlToJson() {
933
+ //get box element
934
+ let config_box_element = document.getElementById("config-box");
935
+ if (config_box_element == null) {
936
+ errorAlert("Unable to obtain config box");
937
+ } else {
938
+ const response = await fetch(`get-json`, {
939
+ method: "POST",
940
+ headers: {
941
+ "Content-Type": "application/json",
942
+ },
943
+ body: config_box_element.value,
944
+ });
945
+ let response_status = response.status; //return status
946
+ if (response_status == 201) {
947
+ showChangeStatus(response_status, {});
948
+ let blob = await response.blob(); //get data blob
949
+ config = await new Response(blob).json(); //obtain json from blob
950
+ config_box_element.value = JSON.stringify(config, null, 2);
951
+ } else {
952
+ showChangeStatus(response_status, await response.json());
953
+ }
954
+ }
955
+ return 0;
956
+ }