emhass 0.11.4__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.
@@ -4,19 +4,21 @@
4
4
  "friendly_name": "Cost function",
5
5
  "Description": "Define the type of cost function.",
6
6
  "input": "select",
7
- "select_options": [
8
- "profit",
9
- "cost",
10
- "self-consumption"
11
- ],
7
+ "select_options": ["profit", "cost", "self-consumption"],
12
8
  "default_value": "profit"
13
9
  },
14
10
  "sensor_power_photovoltaics": {
15
- "friendly_name": "Sensor power sensor",
11
+ "friendly_name": "Sensor power photovoltaic",
16
12
  "Description": "This is the name of the photovoltaic power-produced sensor in Watts from Home Assistant. For example: ‘sensor.power_photovoltaics’.",
17
13
  "input": "string",
18
14
  "default_value": "sensor.power_photovoltaics"
19
15
  },
16
+ "sensor_power_photovoltaics_forecast": {
17
+ "friendly_name": "Sensor power photovoltaic forecast",
18
+ "Description": "This is the name of the photovoltaic forecast sensor in Watts from Home Assistant. For example: ‘sensor.p_pv_forecast’.",
19
+ "input": "string",
20
+ "default_value": "sensor.p_pv_forecast"
21
+ },
20
22
  "sensor_power_load_no_var_loads": {
21
23
  "friendly_name": "Sensor power loads with no variable loads",
22
24
  "Description": "The name of the household power consumption sensor in Watts from Home Assistant. The deferrable loads that we will want to include in the optimization problem should be subtracted from this sensor in HASS. For example: ‘sensor.power_load_no_var_loads’",
@@ -35,6 +37,72 @@
35
37
  "input": "array.string",
36
38
  "default_value": "sensor.power_photovoltaics"
37
39
  },
40
+ "use_websocket": {
41
+ "friendly_name": "Use Home Assistant websocket for data retrieval",
42
+ "Description": "Enable Home Assistant websocket as data source instead of Home Assistant API. This allows for longer historical data retention and better performance for machine learning models.",
43
+ "input": "boolean",
44
+ "default_value": false
45
+ },
46
+ "use_influxdb": {
47
+ "friendly_name": "Use InfluxDB (1.x) for data retrieval",
48
+ "Description": "Enable InfluxDB as data source instead of Home Assistant API. This allows for longer historical data retention and better performance for machine learning models. Only supports version 1.x.",
49
+ "input": "boolean",
50
+ "default_value": false
51
+ },
52
+ "influxdb_host": {
53
+ "friendly_name": "InfluxDB host",
54
+ "Description": "The IP address or hostname of your InfluxDB instance. Defaults to localhost.",
55
+ "input": "string",
56
+ "default_value": "localhost"
57
+ },
58
+ "influxdb_port": {
59
+ "friendly_name": "InfluxDB port",
60
+ "Description": "The port number for your InfluxDB instance. Defaults to 8086.",
61
+ "input": "int",
62
+ "default_value": 8086
63
+ },
64
+ "influxdb_username": {
65
+ "friendly_name": "InfluxDB username",
66
+ "Description": "Username for authenticating with InfluxDB. Leave empty if no authentication is required.",
67
+ "input": "secrets.string",
68
+ "default_value": ""
69
+ },
70
+ "influxdb_password": {
71
+ "friendly_name": "InfluxDB password",
72
+ "Description": "Password for authenticating with InfluxDB. Leave empty if no authentication is required.",
73
+ "input": "secrets.string",
74
+ "default_value": ""
75
+ },
76
+ "influxdb_database": {
77
+ "friendly_name": "InfluxDB database name",
78
+ "Description": "The name of the InfluxDB database containing your Home Assistant data. Defaults to 'homeassistant'.",
79
+ "input": "string",
80
+ "default_value": "homeassistant"
81
+ },
82
+ "influxdb_measurement": {
83
+ "friendly_name": "InfluxDB measurement name",
84
+ "Description": "The measurement name where your sensor data is stored. Defaults to 'W' for the Home Assistant integration.",
85
+ "input": "string",
86
+ "default_value": "W"
87
+ },
88
+ "influxdb_retention_policy": {
89
+ "friendly_name": "InfluxDB retention policy",
90
+ "Description": "The retention policy to use for InfluxDB queries. Defaults to 'autogen'.",
91
+ "input": "string",
92
+ "default_value": "autogen"
93
+ },
94
+ "influxdb_use_ssl": {
95
+ "friendly_name": "Use HTTPS on InfluxDB connection",
96
+ "Description": "Enables HTTPS on InfluxDB connections. Defaults to 'false'.",
97
+ "input": "boolean",
98
+ "default_value": false
99
+ },
100
+ "influxdb_verify_ssl": {
101
+ "friendly_name": "Verify HTTPS on InfluxDB connection",
102
+ "Description": "Enables SSL verification of the certificate (e.g. Let's Encrypt) on InfluxDB connections. Defaults to 'false'.",
103
+ "input": "boolean",
104
+ "default_value": false
105
+ },
38
106
  "continual_publish": {
39
107
  "friendly_name": "Continually publish optimization results",
40
108
  "Description": "set to True to save entities to .json after an optimization run. Then automatically republish the saved entities (with updated current state value) every freq minutes. entity data saved to data_path/entities.",
@@ -45,12 +113,7 @@
45
113
  "friendly_name": "Logging level",
46
114
  "Description": "DEBUG provides detailed diagnostic information, INFO gives general operational messages, WARNING highlights potential issues, and ERROR indicates critical problems that may disrupt functionality.",
47
115
  "input": "select",
48
- "select_options": [
49
- "INFO",
50
- "DEBUG",
51
- "WARNING",
52
- "ERROR"
53
- ],
116
+ "select_options": ["INFO", "DEBUG", "WARNING", "ERROR"],
54
117
  "default_value": "INFO"
55
118
  }
56
119
  },
@@ -83,11 +146,7 @@
83
146
  "friendly_name": "Timestamp rounding method",
84
147
  "Description": "Set the method for timestamp rounding, options are: first, last and nearest.",
85
148
  "input": "select",
86
- "select_options": [
87
- "nearest",
88
- "first",
89
- "last"
90
- ],
149
+ "select_options": ["nearest", "first", "last"],
91
150
  "default_value": "nearest"
92
151
  },
93
152
  "delta_forecast_daily": {
@@ -100,12 +159,8 @@
100
159
  "friendly_name": "Load forecast method",
101
160
  "Description": "The load forecast method that will be used. The options are ‘csv’ to load a CSV file or ‘naive’ for a simple 1-day persistence model.",
102
161
  "input": "select",
103
- "select_options": [
104
- "naive",
105
- "mlforecaster",
106
- "csv"
107
- ],
108
- "default_value": "naive"
162
+ "select_options": ["typical", "naive", "mlforecaster", "csv"],
163
+ "default_value": "typical"
109
164
  },
110
165
  "set_total_pv_sell": {
111
166
  "friendly_name": "PV straight to grid",
@@ -115,13 +170,14 @@
115
170
  },
116
171
  "lp_solver": {
117
172
  "friendly_name": "Linear programming solver",
118
- "Description": "Set the name of the linear programming solver that will be used. Defaults to ‘COIN_CMD’. The options are ‘PULP_CBC_CMD’, ‘GLPK_CMD and ‘COIN_CMD’.",
173
+ "Description": "Set the name of the linear programming solver that will be used. Defaults to ‘COIN_CMD’. The options are ‘PULP_CBC_CMD’, ‘GLPK_CMD’, ‘HiGHS’, and ‘COIN_CMD’.",
119
174
  "input": "select",
120
175
  "select_options": [
121
176
  "default",
122
177
  "COIN_CMD",
123
178
  "PULP_CBC_CMD",
124
- "GLPK_CMD"
179
+ "GLPK_CMD",
180
+ "HiGHS"
125
181
  ],
126
182
  "default_value": "COIN_CMD"
127
183
  },
@@ -131,17 +187,30 @@
131
187
  "input": "text",
132
188
  "default_value": "/usr/bin/cbc"
133
189
  },
190
+ "num_threads": {
191
+ "friendly_name": "Number of threads to use for the LP solver",
192
+ "Description": "Set the number of threads for the LP solver to use, when supported by the solver. Defaults to 0 (autodetect)",
193
+ "input": "int",
194
+ "default_value": 0
195
+ },
196
+ "lp_solver_timeout": {
197
+ "friendly_name": "Linear programming solver timeout",
198
+ "Description": "Set the maximum time (in seconds) for the LP solver. Defaults to 45.",
199
+ "input": "int",
200
+ "default_value": 45
201
+ },
134
202
  "weather_forecast_method": {
135
203
  "friendly_name": "Weather forecast method",
136
- "Description": "This will define the weather forecast method that will be used. options are 'scrapper' (ClearOutside), 'Solcast', 'solar.forecast' (forecast.solar) and 'csv' to load a CSV file. When loading a CSV file this will be directly considered as the PV power forecast in Watts.",
204
+ "Description": "This will define the weather forecast method that will be used. options are 'open-meteo', 'Solcast', 'solar.forecast' (forecast.solar) and 'csv' to load a CSV file. When loading a CSV file this will be directly considered as the PV power forecast in Watts.",
137
205
  "input": "select",
138
- "select_options": [
139
- "scrapper",
140
- "solcast",
141
- "solar.forecast",
142
- "csv"
143
- ],
144
- "default_value": "scrapper"
206
+ "select_options": ["open-meteo", "solcast", "solar.forecast", "csv"],
207
+ "default_value": "open-meteo"
208
+ },
209
+ "open_meteo_cache_max_age": {
210
+ "friendly_name": "Open-Meteo Cache Max Age",
211
+ "Description": "The maximum age, in minutes, of the cached open-meteo json response, after which a new version will be fetched from Open-Meteo. Defaults to 30.",
212
+ "input": "int",
213
+ "default_value": 30
145
214
  },
146
215
  "maximum_power_from_grid": {
147
216
  "friendly_name": "Max power from grid",
@@ -161,6 +230,42 @@
161
230
  "input": "boolean",
162
231
  "default_value": false
163
232
  },
233
+ "inverter_ac_output_max": {
234
+ "friendly_name": "Max hybrid inverter AC output power",
235
+ "Description": "Maximum hybrid inverter output power from combined PV and battery discharge.",
236
+ "input": "int",
237
+ "default_value": 0
238
+ },
239
+ "inverter_ac_input_max": {
240
+ "friendly_name": "Max hybrid inverter AC input power",
241
+ "Description": "Maximum hybrid inverter input power from grid to charge battery.",
242
+ "input": "int",
243
+ "default_value": 0
244
+ },
245
+ "inverter_efficiency_dc_ac": {
246
+ "friendly_name": "Hybrid inverter efficency DC to AC",
247
+ "Description": "Hybrid inverter efficiency from the DC bus to AC output. (percentage/100)",
248
+ "input": "float",
249
+ "default_value": 1.0
250
+ },
251
+ "inverter_efficiency_ac_dc": {
252
+ "friendly_name": "Hybrid inverter efficency AC to DC",
253
+ "Description": "Hybrid inverter efficiency when charging from the AC input to DC bus. (percentage/100)",
254
+ "input": "float",
255
+ "default_value": 1.0
256
+ },
257
+ "inverter_stress_cost": {
258
+ "friendly_name": "Hybrid Inverter Stress Cost",
259
+ "Description": "A virtual cost (Currency/kWh) applied at the inverter's nominal power to penalise high loads. Currently implemented as a quadratic curve (Cost ∝ Power²), incentivising 'low and slow' operation to reduce thermal stress and smooth power flows.",
260
+ "input": "float",
261
+ "default_value": 0.0
262
+ },
263
+ "inverter_stress_segments": {
264
+ "friendly_name": "Hybrid Inverter Stress Segments",
265
+ "Description": "The number of linear segments used to approximate the stress cost curve. Higher values increase accuracy but may slightly increase computation time.",
266
+ "input": "int",
267
+ "default_value": 10
268
+ },
164
269
  "compute_curtailment": {
165
270
  "friendly_name": "Set compute curtailment (grid export limit)",
166
271
  "Description": "Set to True to compute a special PV curtailment variable (Default False)",
@@ -173,10 +278,7 @@
173
278
  "friendly_name": "Load cost method",
174
279
  "Description": "Define the method that will be used for load cost forecast. The options are ‘hp_hc_periods’ for peak and non-peak hours contracts, and ‘csv’ to load custom cost from CSV file.",
175
280
  "input": "select",
176
- "select_options": [
177
- "hp_hc_periods",
178
- "csv"
179
- ],
281
+ "select_options": ["hp_hc_periods", "csv"],
180
282
  "default_value": "hp_hc_periods"
181
283
  },
182
284
  "load_peak_hour_periods": {
@@ -184,7 +286,7 @@
184
286
  "Description": "A list of peak hour periods for load consumption from the grid. This is useful if you have a contract with peak and non-peak hours.",
185
287
  "input": "array.time",
186
288
  "default_value": {
187
- "period_hp_template": [{"start": "02:54"},{"end": "15:24"}]
289
+ "period_hp_template": [{ "start": "02:54" }, { "end": "15:24" }]
188
290
  },
189
291
  "requires": {
190
292
  "load_cost_forecast_method": "hp_hc_periods"
@@ -212,10 +314,7 @@
212
314
  "friendly_name": "PV power production price forecast method",
213
315
  "Description": "Define the method that will be used for PV power production price forecast. This is the price that is paid by the utility for energy injected into the grid. The options are ‘constant’ for a constant fixed value or ‘csv’ to load custom price forecasts from a CSV file.",
214
316
  "input": "select",
215
- "select_options": [
216
- "constant",
217
- "csv"
218
- ],
317
+ "select_options": ["constant", "csv"],
219
318
  "default_value": "constant"
220
319
  },
221
320
  "photovoltaic_production_sell_price": {
@@ -229,16 +328,60 @@
229
328
  }
230
329
  },
231
330
  "Solar System (PV)": {
331
+ "set_use_pv": {
332
+ "friendly_name": "Enable PV system",
333
+ "Description": "Set to True if we should consider an solar PV system. Defaults to False",
334
+ "input": "boolean",
335
+ "default_value": false
336
+ },
337
+ "set_use_adjusted_pv": {
338
+ "friendly_name": "Enable adjusted PV system",
339
+ "Description": "Set to True if we should consider an adjusted solar PV system. Defaults to False",
340
+ "input": "boolean",
341
+ "default_value": false
342
+ },
343
+ "adjusted_pv_regression_model": {
344
+ "friendly_name": "Regression model for adjusted PV",
345
+ "Description": "Set the type of regression model that will be used to adjust the solar PV production to local conditions. Available models are defined in REGRESSION_METHODS (machine_learning_regressor.py). Defaults to LassoRegression",
346
+ "input": "select",
347
+ "select_options": [
348
+ "LinearRegression",
349
+ "RidgeRegression",
350
+ "LassoRegression",
351
+ "ElasticNet",
352
+ "KNeighborsRegressor",
353
+ "DecisionTreeRegressor",
354
+ "SVR",
355
+ "RandomForestRegressor",
356
+ "ExtraTreesRegressor",
357
+ "GradientBoostingRegressor",
358
+ "AdaBoostRegressor",
359
+ "MLPRegressor"
360
+ ],
361
+ "default_value": "LassoRegression"
362
+ },
363
+ "adjusted_pv_solar_elevation_threshold": {
364
+ "friendly_name": "Solar elevation threshold",
365
+ "Description": "This is the solar elevation threshold parameter used to identify the early morning or late evening periods. Defaults to 10.",
366
+ "input": "int",
367
+ "default_value": 10
368
+ },
369
+ "adjusted_pv_model_max_age": {
370
+ "friendly_name": "Adjusted PV model maximum age",
371
+ "Description": "Maximum age in hours before the adjusted PV regression model is re-fitted. If the saved model is older than this value, a new model will be trained using fresh historical data. Set to 0 to force re-fitting on every call. Defaults to 24 hours (1 day).",
372
+ "input": "int",
373
+ "default_value": 24
374
+ },
232
375
  "pv_module_model": {
233
- "friendly_name": "PV module model name",
234
- "Description": "The PV module model. This parameter can be a list of items to enable the simulation of mixed orientation systems.",
376
+ "friendly_name": "PV module model",
377
+ "Description": "The PV module model. You can provide the model name from the CEC database or the nominal power in Watts (e.g., 300). This parameter can be a list of items to enable the simulation of mixed orientation systems.",
235
378
  "input": "array.string",
236
379
  "input_attributes": "_'s",
237
380
  "default_value": "CSUN_Eurasia_Energy_Systems_Industry_and_Trade_CSUN295_60M"
238
381
  },
239
382
  "pv_inverter_model": {
240
- "friendly_name": "The PV inverter model name",
241
- "Description": "The PV inverter model. This parameter can be a list of items to enable the simulation of mixed orientation systems.",
383
+ "friendly_name": "PV inverter model",
384
+ "Description": "The PV inverter model. You can provide the model name from the CEC database or the nominal power in Watts (e.g., 5000). This parameter can be a list of items to enable the simulation of mixed orientation systems.",
242
385
  "input": "array.string",
243
386
  "input_attributes": "_'s",
244
387
  "default_value": "Fronius_International_GmbH__Fronius_Primo_5_0_1_208_240__240V_"
@@ -281,6 +424,12 @@
281
424
  "input": "array.float",
282
425
  "default_value": 3000.0
283
426
  },
427
+ "minimum_power_of_deferrable_loads": {
428
+ "friendly_name": "Deferrable load minimum power",
429
+ "Description": "The minimum power for each deferrable load in Watts.",
430
+ "input": "array.float",
431
+ "default_value": 0.0
432
+ },
284
433
  "operating_hours_of_each_deferrable_load": {
285
434
  "friendly_name": "Deferrable load operating hours",
286
435
  "Description": "The total number of hours that each deferrable load should operate",
@@ -289,7 +438,7 @@
289
438
  },
290
439
  "treat_deferrable_load_as_semi_cont": {
291
440
  "friendly_name": "Deferrable load as semi-continuous (on/off) variable",
292
- "Description": "Semi-continuous variables (True) are variables that must take a value that can be either their maximum or minimum/zero (for example On = Maximum load, Off = 0 W). Non semi-continuous (which means continuous) variables (False) can take any values between their maximum and minimum",
441
+ "Description": "When configuring a deferrable load, set treat_deferrable_load_as_semi_cont = True if the device should only switch fully on or off (for example, a basic hot water heater), or set treat_deferrable_load_as_semi_cont = False if the device supports variable power control within its range (for example, an EV charger with adjustable charging rate).",
293
442
  "input": "array.boolean",
294
443
  "default_value": true
295
444
  },
@@ -301,7 +450,7 @@
301
450
  },
302
451
  "set_deferrable_startup_penalty": {
303
452
  "friendly_name": "Set deferrable startup penalty",
304
- "Description": "For penalty P, each time the deferrable load turns on will incur an additional cost of P * number_of_deferrable_loads * cost_of_electricity at that time",
453
+ "Description": "For penalty P, each time the deferrable load turns on will incur an additional cost of nominal_power (kW) * cost_of_electricity * time step at that time",
305
454
  "input": "array.float",
306
455
  "default_value": 0.0
307
456
  },
@@ -420,6 +569,24 @@
420
569
  "Description": "The desired battery state of charge at the end of each optimization cycle. (percentage/100)",
421
570
  "input": "float",
422
571
  "default_value": 0.6
572
+ },
573
+ "battery_stress_cost": {
574
+ "friendly_name": "Battery Stress Cost",
575
+ "Description": "A virtual cost (Currency/kWh) applied at the battery's nominal power to penalise high loads. Currently implemented as a quadratic curve (Cost ∝ Power²), incentivising 'low and slow' operation to reduce thermal stress and smooth power flows.",
576
+ "input": "float",
577
+ "default_value": 0.0
578
+ },
579
+ "battery_stress_segments": {
580
+ "friendly_name": "Battery Stress Segments",
581
+ "Description": "The number of linear segments used to approximate the stress cost curve. Higher values increase accuracy but may slightly increase computation time.",
582
+ "input": "int",
583
+ "default_value": 10
584
+ },
585
+ "ignore_pv_feedback_during_curtailment": {
586
+ "friendly_name": "Ignore PV feedback during curtailment",
587
+ "Description": "When set to true, prevents PV forecast from being updated with real PV data, avoiding flip-flop behavior during curtailment operations",
588
+ "input": "bool",
589
+ "default_value": false
423
590
  }
424
591
  }
425
592
  }
emhass/static/script.js CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  //on page reload get saved data
9
9
  window.onload = async function () {
10
- pageSelected = await loadBasicOrAdvanced();
10
+ await loadBasicOrAdvanced();
11
11
 
12
12
  //add listener for basic and advanced html switch
13
13
  document
@@ -16,7 +16,7 @@ window.onload = async function () {
16
16
  };
17
17
 
18
18
  //add listeners to buttons (based on page)
19
- function loadButtons(page) {
19
+ async function loadButtons(page) {
20
20
  switch (page) {
21
21
  case "advanced":
22
22
  [
@@ -26,6 +26,7 @@ function loadButtons(page) {
26
26
  "forecast-model-tune",
27
27
  "regressor-model-fit",
28
28
  "regressor-model-predict",
29
+ "export-influxdb-to-csv",
29
30
  "perfect-optim",
30
31
  "publish-data",
31
32
  "naive-mpc-optim",
@@ -45,6 +46,9 @@ function loadButtons(page) {
45
46
  document
46
47
  .getElementById("input-clear")
47
48
  .addEventListener("click", () => ClearInputData());
49
+
50
+ // Check if InfluxDB is enabled and show/hide export section
51
+ await checkInfluxDBAndShowExport();
48
52
  break;
49
53
  case "basic":
50
54
  document
@@ -58,7 +62,8 @@ function loadButtons(page) {
58
62
  async function loadBasicOrAdvanced(RequestedPage) {
59
63
  let basicFile = "basic.html";
60
64
  let advencedFile = "advanced.html";
61
- var formContainer = document.getElementById("TabSelection"); //container element to house basic or advanced data
65
+ let formContainer = document.getElementById("TabSelection"); //container element to house basic or advanced data
66
+ let htmlData
62
67
  //first check any function arg
63
68
  if (arguments.length == 1) {
64
69
  switch (RequestedPage) {
@@ -94,7 +99,7 @@ async function loadBasicOrAdvanced(RequestedPage) {
94
99
  localStorage.getItem("TabSelection") === "advanced"
95
100
  ) {
96
101
  //if advance
97
- htmlData = await getHTMLData(advencedFile);
102
+ let htmlData = await getHTMLData(advencedFile);
98
103
  formContainer.innerHTML = htmlData;
99
104
  loadButtons("advanced");
100
105
  getSavedData();
@@ -117,7 +122,7 @@ async function loadBasicOrAdvanced(RequestedPage) {
117
122
 
118
123
  //on button press, check current displayed page data and switch
119
124
  function SwitchBasicOrAdvanced() {
120
- var formContainerChildID =
125
+ let formContainerChildID =
121
126
  document.getElementById("TabSelection").firstElementChild.id;
122
127
  if (formContainerChildID === "basic") {
123
128
  loadBasicOrAdvanced("advanced");
@@ -129,18 +134,19 @@ function SwitchBasicOrAdvanced() {
129
134
  //get html data from basic.html or advanced.html
130
135
  async function getHTMLData(htmlFile) {
131
136
  const response = await fetch(`static/` + htmlFile);
132
- blob = await response.blob(); //get data blob
133
- htmlTemplateData = await new Response(blob).text(); //obtain html from blob
134
- return await htmlTemplateData;
137
+ let blob = await response.blob(); //get data blob
138
+ let htmlTemplateData = await new Response(blob).text(); //obtain html from blob
139
+ return htmlTemplateData;
135
140
  }
136
141
 
137
142
  //function pushing data via post, triggered by button action
138
143
  async function formAction(action, page) {
144
+ let data = {}
139
145
  if (page !== "basic") {
140
146
  //dont try to get input data in basic mode
141
- var data = inputToJson(page);
147
+ data = inputToJson();
142
148
  } else {
143
- var data = {};
149
+ data = {};
144
150
  } //send no data
145
151
 
146
152
  if (data !== 0) {
@@ -151,6 +157,7 @@ async function formAction(action, page) {
151
157
  method: "POST",
152
158
  headers: {
153
159
  "Content-Type": "application/json",
160
+ 'Transfer-Encoding': 'chunked'
154
161
  },
155
162
  body: JSON.stringify(data), //note that post can only send data via strings
156
163
  });
@@ -173,7 +180,7 @@ async function formAction(action, page) {
173
180
 
174
181
  //function in control of status icons of post above
175
182
  async function showChangeStatus(status, logJson) {
176
- var loading = document.getElementById("loader"); //element showing statuses
183
+ let loading = document.getElementById("loader"); //element showing statuses
177
184
  if (status === "remove") {
178
185
  //remove all
179
186
  loading.innerHTML = "";
@@ -204,16 +211,16 @@ async function showChangeStatus(status, logJson) {
204
211
  async function getTemplate() {
205
212
  //fetch data from webserver.py
206
213
  let htmlTemplateData = "";
207
- response = await fetch(`template`, {
214
+ let response = await fetch(`template`, {
208
215
  method: "GET",
209
216
  });
210
- blob = await response.blob(); //get data blob
217
+ let blob = await response.blob(); //get data blob
211
218
  htmlTemplateData = await new Response(blob).text(); //obtain html from blob
212
- templateDiv = document.getElementById("template"); //get template container element to override
219
+ let templateDiv = document.getElementById("template"); //get template container element to override
213
220
  templateDiv.innerHTML = htmlTemplateData; //override container inner html with new data
214
- var scripts = Array.from(templateDiv.getElementsByTagName("script")); //replace script tags manually
221
+ let scripts = Array.from(templateDiv.getElementsByTagName("script")); //replace script tags manually
215
222
  for (const script of scripts) {
216
- var TempScript = document.createElement("script");
223
+ let TempScript = document.createElement("script");
217
224
  TempScript.innerHTML = script.innerHTML;
218
225
  script.parentElement.appendChild(TempScript);
219
226
  }
@@ -228,7 +235,6 @@ function testStorage() {
228
235
  } catch (error) {
229
236
  return false;
230
237
  }
231
- return false;
232
238
  }
233
239
 
234
240
  //function gets saved data (if any)
@@ -237,7 +243,7 @@ function getSavedData() {
237
243
  if (testStorage()) {
238
244
  //if local storage exists and works
239
245
  let selectElement = document.getElementById("input-select"); // select button element
240
- var input_container = document.getElementById("input-container"); // container div containing all dynamic input elements (Box/List)
246
+ let input_container = document.getElementById("input-container"); // container div containing all dynamic input elements (Box/List)
241
247
  if (
242
248
  localStorage.getItem("input_container_content") &&
243
249
  localStorage.getItem("input_container_content") !== "{}"
@@ -251,12 +257,11 @@ function getSavedData() {
251
257
  }
252
258
  if (selectElement.value == "List") {
253
259
  //if List is selected, show saved json data into box
254
- storedJson = JSON.parse(
260
+ let storedJson = JSON.parse(
255
261
  localStorage.getItem("input_container_content")
256
262
  );
257
263
  if (Object.keys(storedJson).length > 0) {
258
264
  input_container.innerHTML = "";
259
- i = 1;
260
265
  for (const ikey in storedJson) {
261
266
  input_container.appendChild(
262
267
  createInputListDiv(ikey, JSON.stringify(storedJson[ikey]))
@@ -270,7 +275,7 @@ function getSavedData() {
270
275
 
271
276
  //using localStorage, store json data from input-list(List)/text-area(from input-box) elements for saved state save on page refresh (will save state on successful post)
272
277
  function saveStorage() {
273
- var data = JSON.stringify(inputToJson());
278
+ let data = JSON.stringify(inputToJson());
274
279
  if (testStorage() && data != "{}") {
275
280
  //don't bother saving if empty and/or storage don't exist
276
281
  localStorage.setItem("input_container_content", data);
@@ -279,12 +284,12 @@ function saveStorage() {
279
284
 
280
285
  //function gets values from input-list/text-area(from input-box) elements and return json dict object
281
286
  function inputToJson() {
282
- var input_container = document.getElementById("input-container"); //container
287
+ let input_container = document.getElementById("input-container"); //container
283
288
  let inputListArr = document.getElementsByClassName("input-list"); //list
284
289
  let inputTextArea = document.getElementById("text-area"); //box
285
290
  let input_container_child = null;
286
291
  input_container_child = input_container.firstElementChild; //work out which element is first inside container div
287
- var jsonReturnData = {};
292
+ let jsonReturnData = {};
288
293
 
289
294
  if (input_container_child == null) {
290
295
  //if no elements in container then return empty
@@ -300,7 +305,7 @@ function inputToJson() {
300
305
  let jsonTempData = "{";
301
306
  for (let i = 0; i < inputListArr.length; i++) {
302
307
  let key = inputListArr[i].getElementsByClassName("input-key")[0].value;
303
- var value =
308
+ let value =
304
309
  inputListArr[i].getElementsByClassName("input-value")[0].value;
305
310
  //curate a string with list elements to parse into json later
306
311
  if (key !== "") {
@@ -365,7 +370,7 @@ function createInputListDiv(ikey, ivalue) {
365
370
 
366
371
  //function assigned to control (add and remove) input (Box and List) elements
367
372
  function dictInputs(action) {
368
- var input_container = document.getElementById("input-container"); // container div containing all dynamic input elements
373
+ let input_container = document.getElementById("input-container"); // container div containing all dynamic input elements
369
374
  let selectElement = document.getElementById("input-select"); // select button
370
375
  let input_container_child = null;
371
376
  let input_container_child_name = null;
@@ -424,7 +429,7 @@ async function ClearInputData(id) {
424
429
  //clear input elements
425
430
  async function ClearInputElements() {
426
431
  let selectElement = document.getElementById("input-select");
427
- var input_container = document.getElementById("input-container");
432
+ let input_container = document.getElementById("input-container");
428
433
  if (selectElement.value == "Box") {
429
434
  document.getElementById("text-area").value = "{}";
430
435
  }
@@ -440,3 +445,30 @@ async function ClearInputElements() {
440
445
  // formAction("publish-data", "basic")
441
446
  // }
442
447
  //}
448
+
449
+ // Check if InfluxDB is configured and show/hide export section
450
+ async function checkInfluxDBAndShowExport() {
451
+ try {
452
+ const response = await fetch("/get-config");
453
+ if (response.ok) {
454
+ const config = await response.json();
455
+ const exportSection = document.getElementById("export-influxdb-section");
456
+ if (exportSection) {
457
+ // Show export if InfluxDB is configured (has host and port)
458
+ // Users can export from InfluxDB even if use_influxdb is false for optimization
459
+ const isInfluxDBConfigured =
460
+ config.influxdb_host &&
461
+ config.influxdb_host !== "" &&
462
+ config.influxdb_port;
463
+
464
+ if (isInfluxDBConfigured) {
465
+ exportSection.style.display = "block";
466
+ } else {
467
+ exportSection.style.display = "none";
468
+ }
469
+ }
470
+ }
471
+ } catch (error) {
472
+ console.error("Error checking InfluxDB configuration:", error);
473
+ }
474
+ }