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.
- emhass/command_line.py +1481 -811
- emhass/connection_manager.py +108 -0
- emhass/data/associations.csv +37 -2
- emhass/data/cec_inverters.pbz2 +0 -0
- emhass/data/cec_modules.pbz2 +0 -0
- emhass/data/config_defaults.json +53 -49
- emhass/forecast.py +1264 -731
- emhass/img/emhass_icon.png +0 -0
- emhass/machine_learning_forecaster.py +534 -281
- emhass/machine_learning_regressor.py +141 -125
- emhass/optimization.py +1173 -585
- emhass/retrieve_hass.py +958 -263
- emhass/static/advanced.html +7 -0
- emhass/static/configuration_list.html +5 -1
- emhass/static/configuration_script.js +146 -62
- emhass/static/data/param_definitions.json +215 -48
- emhass/static/script.js +58 -26
- emhass/static/style.css +6 -8
- emhass/templates/configuration.html +5 -3
- emhass/templates/index.html +8 -6
- emhass/templates/template.html +4 -5
- emhass/utils.py +1152 -403
- emhass/web_server.py +565 -379
- emhass/websocket_client.py +224 -0
- emhass-0.15.5.dist-info/METADATA +164 -0
- emhass-0.15.5.dist-info/RECORD +34 -0
- {emhass-0.11.4.dist-info → emhass-0.15.5.dist-info}/WHEEL +1 -2
- emhass-0.15.5.dist-info/entry_points.txt +2 -0
- emhass-0.11.4.dist-info/METADATA +0 -666
- emhass-0.11.4.dist-info/RECORD +0 -32
- emhass-0.11.4.dist-info/entry_points.txt +0 -2
- emhass-0.11.4.dist-info/top_level.txt +0 -1
- {emhass-0.11.4.dist-info → emhass-0.15.5.dist-info/licenses}/LICENSE +0 -0
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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 '
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
"
|
|
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
|
|
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": "
|
|
241
|
-
"Description": "The PV inverter model.
|
|
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": "
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
147
|
+
data = inputToJson();
|
|
142
148
|
} else {
|
|
143
|
-
|
|
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
|
-
|
|
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
|
-
|
|
221
|
+
let scripts = Array.from(templateDiv.getElementsByTagName("script")); //replace script tags manually
|
|
215
222
|
for (const script of scripts) {
|
|
216
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|