emhass 0.10.0__tar.gz → 0.10.2__tar.gz
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-0.10.0 → emhass-0.10.2}/CHANGELOG.md +18 -1
- {emhass-0.10.0 → emhass-0.10.2}/PKG-INFO +16 -10
- {emhass-0.10.0 → emhass-0.10.2}/README.md +14 -8
- emhass-0.10.2/data/opt_res_latest.csv +49 -0
- emhass-0.10.2/data/test_df_final.pkl +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/setup.py +2 -2
- {emhass-0.10.0 → emhass-0.10.2}/src/emhass/command_line.py +86 -21
- {emhass-0.10.0 → emhass-0.10.2}/src/emhass/forecast.py +94 -37
- {emhass-0.10.0 → emhass-0.10.2}/src/emhass/machine_learning_forecaster.py +20 -20
- {emhass-0.10.0 → emhass-0.10.2}/src/emhass/optimization.py +46 -50
- {emhass-0.10.0 → emhass-0.10.2}/src/emhass/utils.py +59 -34
- {emhass-0.10.0 → emhass-0.10.2}/src/emhass/web_server.py +14 -4
- {emhass-0.10.0 → emhass-0.10.2}/src/emhass.egg-info/PKG-INFO +16 -10
- {emhass-0.10.0 → emhass-0.10.2}/src/emhass.egg-info/requires.txt +1 -1
- {emhass-0.10.0 → emhass-0.10.2}/tests/test_forecast.py +8 -0
- {emhass-0.10.0 → emhass-0.10.2}/tests/test_retrieve_hass.py +1 -1
- emhass-0.10.0/data/opt_res_latest.csv +0 -11
- emhass-0.10.0/data/test_df_final.pkl +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/CODE_OF_CONDUCT.md +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/CONTRIBUTING.md +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/LICENSE +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/MANIFEST.in +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/data/data_load_cost_forecast.csv +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/data/data_load_forecast.csv +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/data/data_prod_price_forecast.csv +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/data/data_train_load_clustering.pkl +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/data/data_train_load_forecast.pkl +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/data/data_weather_forecast.csv +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/data/heating_prediction.csv +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/data/opt_res_perfect_optim_cost.csv +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/data/opt_res_perfect_optim_profit.csv +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/data/opt_res_perfect_optim_self-consumption.csv +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/data/test_response_get_data_get_method.pbz2 +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/data/test_response_scrapper_get_method.pbz2 +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/data/test_response_solarforecast_get_method.pbz2 +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/data/test_response_solcast_get_method.pbz2 +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/pyproject.toml +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/setup.cfg +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/src/emhass/__init__.py +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/src/emhass/data/cec_inverters.pbz2 +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/src/emhass/data/cec_modules.pbz2 +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/src/emhass/machine_learning_regressor.py +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/src/emhass/retrieve_hass.py +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/src/emhass/static/advanced.html +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/src/emhass/static/basic.html +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/src/emhass/static/img/emhass_icon.png +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/src/emhass/static/img/emhass_logo_short.svg +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/src/emhass/static/img/feather-sprite.svg +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/src/emhass/static/script.js +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/src/emhass/static/style.css +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/src/emhass/templates/index.html +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/src/emhass/templates/template.html +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/src/emhass.egg-info/SOURCES.txt +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/src/emhass.egg-info/dependency_links.txt +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/src/emhass.egg-info/entry_points.txt +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/src/emhass.egg-info/top_level.txt +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/tests/test_command_line_utils.py +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/tests/test_machine_learning_forecaster.py +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/tests/test_machine_learning_regressor.py +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/tests/test_optimization.py +0 -0
- {emhass-0.10.0 → emhass-0.10.2}/tests/test_utils.py +0 -0
@@ -1,8 +1,25 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.10.2 - 2024-07-06
|
4
|
+
### Improvement
|
5
|
+
- Weather forecast caching and Solcast method fix by @GeoDerp
|
6
|
+
- Added a new configuration parameter to control wether we compute PV curtailment or not
|
7
|
+
- Added hybrid inverter to data publish
|
8
|
+
- It is now possible to pass these battery parameters at runtime: `SOCmin`, `SOCmax`, `Pd_max` and `Pc_max`
|
9
|
+
### Fix
|
10
|
+
- Fixed problem with negative PV forecast values in optimization.py, by @GeoDerp
|
11
|
+
|
12
|
+
## 0.10.1 - 2024-06-03
|
13
|
+
### Fix
|
14
|
+
- Fixed PV curtailment maximum possible value constraint
|
15
|
+
- Added PV curtailement to variable to publish to HA
|
16
|
+
|
3
17
|
## 0.10.0 - 2024-06-02
|
18
|
+
### BREAKING CHANGE
|
19
|
+
- In this new version we have added support for PV curtailment computation. While doing this the nominal PV peak power is needed. The easiest way find this information is by directly using the `inverter_model` defined in the configuration. As this is needed in the optimization to correctly compute PV curtailment, this parameter need to be properly defined for your installation. Before this chage this parameter was only needed if using the PV forecast method `scrapper`, but now it is not optional as it is directly used in the optimization.
|
20
|
+
Use the dedicated webapp to find the correct model for your inverter, if you cannot find your exact brand/model then just pick an inverter with the same nominal power as yours: [https://emhass-pvlib-database.streamlit.app/](https://emhass-pvlib-database.streamlit.app/)
|
4
21
|
### Improvement
|
5
|
-
- Added support for hybrid inverters
|
22
|
+
- Added support for hybrid inverters and PV curtailment computation
|
6
23
|
- Implemented a new `continual_publish` service that avoid the need of setting a special automation for data publish. Thanks to @GeoDerp
|
7
24
|
- Implement a deferrable load start penalty functionality. Thanks to @werdnum
|
8
25
|
- This feature also implement a `def_current_state` that can be passed at runtime to let the optimization consider that a deferrable load is currently scheduled or under operation when launching the optimization task
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: emhass
|
3
|
-
Version: 0.10.
|
3
|
+
Version: 0.10.2
|
4
4
|
Summary: An Energy Management System for Home Assistant
|
5
5
|
Home-page: https://github.com/davidusb-geek/emhass
|
6
6
|
Author: David HERNANDEZ
|
@@ -28,7 +28,7 @@ Requires-Dist: h5py==3.11.0
|
|
28
28
|
Requires-Dist: pulp>=2.4
|
29
29
|
Requires-Dist: pyyaml>=5.4.1
|
30
30
|
Requires-Dist: tables<=3.9.1
|
31
|
-
Requires-Dist: skforecast==0.12.
|
31
|
+
Requires-Dist: skforecast==0.12.1
|
32
32
|
Requires-Dist: flask>=2.0.3
|
33
33
|
Requires-Dist: waitress>=2.1.1
|
34
34
|
Requires-Dist: plotly>=5.6.0
|
@@ -179,13 +179,9 @@ docker run -it --restart always -p 5000:5000 -e TZ="Europe/Paris" -e LOCAL_COS
|
|
179
179
|
### Method 3) Legacy method using a Python virtual environment
|
180
180
|
|
181
181
|
With this method it is recommended to install on a virtual environment.
|
182
|
-
|
182
|
+
Create and activate a virtual environment:
|
183
183
|
```bash
|
184
|
-
|
185
|
-
```
|
186
|
-
Then create and activate the virtual environment:
|
187
|
-
```bash
|
188
|
-
virtualenv -p /usr/bin/python3 emhassenv
|
184
|
+
python3 -m venv emhassenv
|
189
185
|
cd emhassenv
|
190
186
|
source bin/activate
|
191
187
|
```
|
@@ -496,7 +492,7 @@ curl -i -H 'Content-Type:application/json' -X POST -d '{"publish_prefix":"all"}'
|
|
496
492
|
```
|
497
493
|
This action will publish the dayahead (_dh) and MPC (_mpc) optimization results from the optimizations above.
|
498
494
|
|
499
|
-
### Forecast data
|
495
|
+
### Forecast data at runtime
|
500
496
|
|
501
497
|
It is possible to provide EMHASS with your own forecast data. For this just add the data as list of values to a data dictionary during the call to `emhass` using the `runtimeparams` option.
|
502
498
|
|
@@ -519,7 +515,7 @@ The possible dictionary keys to pass data are:
|
|
519
515
|
|
520
516
|
- `prod_price_forecast` for the PV production selling price forecast.
|
521
517
|
|
522
|
-
### Passing other data
|
518
|
+
### Passing other data at runtime
|
523
519
|
|
524
520
|
It is possible to also pass other data during runtime in order to automate the energy management. For example, it could be useful to dynamically update the total number of hours for each deferrable load (`def_total_hours`) using for instance a correlation with the outdoor temperature (useful for water heater for example).
|
525
521
|
|
@@ -535,6 +531,8 @@ Here is the list of the other additional dictionary keys that can be passed at r
|
|
535
531
|
|
536
532
|
- `def_end_timestep` for the timestep before which each deferrable load should operate (if you don't want the deferrable load to use the whole optimization timewindow).
|
537
533
|
|
534
|
+
- `def_current_state` Pass this as a list of booleans (True/False) to indicate the current deferrable load state. This is used internally to avoid incorrectly penalizing a deferrable load start if a forecast is run when that load is already running.
|
535
|
+
|
538
536
|
- `treat_def_as_semi_cont` to define if we should treat each deferrable load as a semi-continuous variable.
|
539
537
|
|
540
538
|
- `set_def_constant` to define if we should set each deferrable load as a constant fixed value variable with just one startup for each optimization task.
|
@@ -545,8 +543,16 @@ Here is the list of the other additional dictionary keys that can be passed at r
|
|
545
543
|
|
546
544
|
- `solar_forecast_kwp` for the PV peak installed power in kW used for the solar.forecast API call.
|
547
545
|
|
546
|
+
- `SOCmin` the minimum possible SOC.
|
547
|
+
|
548
|
+
- `SOCmax` the maximum possible SOC.
|
549
|
+
|
548
550
|
- `SOCtarget` for the desired target value of initial and final SOC.
|
549
551
|
|
552
|
+
- `Pd_max` for the maximum battery discharge power.
|
553
|
+
|
554
|
+
- `Pc_max` for the maximum battery charge power.
|
555
|
+
|
550
556
|
- `publish_prefix` use this key to pass a common prefix to all published data. This will add a prefix to the sensor name but also to the forecasts attributes keys within the sensor.
|
551
557
|
|
552
558
|
## A naive Model Predictive Controller
|
@@ -144,13 +144,9 @@ docker run -it --restart always -p 5000:5000 -e TZ="Europe/Paris" -e LOCAL_COS
|
|
144
144
|
### Method 3) Legacy method using a Python virtual environment
|
145
145
|
|
146
146
|
With this method it is recommended to install on a virtual environment.
|
147
|
-
|
147
|
+
Create and activate a virtual environment:
|
148
148
|
```bash
|
149
|
-
|
150
|
-
```
|
151
|
-
Then create and activate the virtual environment:
|
152
|
-
```bash
|
153
|
-
virtualenv -p /usr/bin/python3 emhassenv
|
149
|
+
python3 -m venv emhassenv
|
154
150
|
cd emhassenv
|
155
151
|
source bin/activate
|
156
152
|
```
|
@@ -461,7 +457,7 @@ curl -i -H 'Content-Type:application/json' -X POST -d '{"publish_prefix":"all"}'
|
|
461
457
|
```
|
462
458
|
This action will publish the dayahead (_dh) and MPC (_mpc) optimization results from the optimizations above.
|
463
459
|
|
464
|
-
### Forecast data
|
460
|
+
### Forecast data at runtime
|
465
461
|
|
466
462
|
It is possible to provide EMHASS with your own forecast data. For this just add the data as list of values to a data dictionary during the call to `emhass` using the `runtimeparams` option.
|
467
463
|
|
@@ -484,7 +480,7 @@ The possible dictionary keys to pass data are:
|
|
484
480
|
|
485
481
|
- `prod_price_forecast` for the PV production selling price forecast.
|
486
482
|
|
487
|
-
### Passing other data
|
483
|
+
### Passing other data at runtime
|
488
484
|
|
489
485
|
It is possible to also pass other data during runtime in order to automate the energy management. For example, it could be useful to dynamically update the total number of hours for each deferrable load (`def_total_hours`) using for instance a correlation with the outdoor temperature (useful for water heater for example).
|
490
486
|
|
@@ -500,6 +496,8 @@ Here is the list of the other additional dictionary keys that can be passed at r
|
|
500
496
|
|
501
497
|
- `def_end_timestep` for the timestep before which each deferrable load should operate (if you don't want the deferrable load to use the whole optimization timewindow).
|
502
498
|
|
499
|
+
- `def_current_state` Pass this as a list of booleans (True/False) to indicate the current deferrable load state. This is used internally to avoid incorrectly penalizing a deferrable load start if a forecast is run when that load is already running.
|
500
|
+
|
503
501
|
- `treat_def_as_semi_cont` to define if we should treat each deferrable load as a semi-continuous variable.
|
504
502
|
|
505
503
|
- `set_def_constant` to define if we should set each deferrable load as a constant fixed value variable with just one startup for each optimization task.
|
@@ -510,8 +508,16 @@ Here is the list of the other additional dictionary keys that can be passed at r
|
|
510
508
|
|
511
509
|
- `solar_forecast_kwp` for the PV peak installed power in kW used for the solar.forecast API call.
|
512
510
|
|
511
|
+
- `SOCmin` the minimum possible SOC.
|
512
|
+
|
513
|
+
- `SOCmax` the maximum possible SOC.
|
514
|
+
|
513
515
|
- `SOCtarget` for the desired target value of initial and final SOC.
|
514
516
|
|
517
|
+
- `Pd_max` for the maximum battery discharge power.
|
518
|
+
|
519
|
+
- `Pc_max` for the maximum battery charge power.
|
520
|
+
|
515
521
|
- `publish_prefix` use this key to pass a common prefix to all published data. This will add a prefix to the sensor name but also to the forecasts attributes keys within the sensor.
|
516
522
|
|
517
523
|
## A naive Model Predictive Controller
|
@@ -0,0 +1,49 @@
|
|
1
|
+
timestamp,P_PV,P_Load,P_deferrable0,P_deferrable1,P_grid_pos,P_grid_neg,P_grid,P_PV_curtailment,unit_load_cost,unit_prod_price,cost_profit,cost_fun_profit,optim_status
|
2
|
+
2024-06-03 23:00:00+02:00,0.0,2124.8911797752808,0.0,750.0,2874.8912,0.0,2874.8912,0.0,0.1419,0.065,-0.20397353064,-0.20397353064,Optimal
|
3
|
+
2024-06-03 23:30:00+02:00,0.0,393.7693220338983,3000.0,750.0,4143.7693,0.0,4143.7693,0.0,0.1419,0.065,-0.29400043183499996,-0.29400043183499996,Optimal
|
4
|
+
2024-06-04 00:00:00+02:00,0.0,329.5656571428571,0.0,750.0,1079.5657,0.0,1079.5657,0.0,0.1419,0.065,-0.076595186415,-0.076595186415,Optimal
|
5
|
+
2024-06-04 00:30:00+02:00,0.0,214.95473988439306,0.0,750.0,964.95474,0.0,964.95474,0.0,0.1419,0.065,-0.068463538803,-0.068463538803,Optimal
|
6
|
+
2024-06-04 01:00:00+02:00,0.0,254.92180790960455,3000.0,0.0,3254.9218,0.0,3254.9218,0.0,0.1419,0.065,-0.23093670171,-0.23093670171,Optimal
|
7
|
+
2024-06-04 01:30:00+02:00,0.0,653.0385393258427,0.0,0.0,653.03854,0.0,653.03854,0.0,0.1419,0.065,-0.046333084413000006,-0.046333084413000006,Optimal
|
8
|
+
2024-06-04 02:00:00+02:00,0.0,694.8668181818182,0.0,0.0,694.86682,0.0,694.86682,0.0,0.1419,0.065,-0.049300800879,-0.049300800879,Optimal
|
9
|
+
2024-06-04 02:30:00+02:00,0.0,856.8446739130435,0.0,0.0,856.84467,0.0,856.84467,0.0,0.1419,0.065,-0.060793129336499996,-0.060793129336499996,Optimal
|
10
|
+
2024-06-04 03:00:00+02:00,0.0,914.380597826087,0.0,0.0,914.3806,0.0,914.3806,0.0,0.1907,0.065,-0.08718619021000001,-0.08718619021000001,Optimal
|
11
|
+
2024-06-04 03:30:00+02:00,0.0,599.8399421965318,0.0,0.0,599.83994,0.0,599.83994,0.0,0.1907,0.065,-0.057194738279,-0.057194738279,Optimal
|
12
|
+
2024-06-04 04:00:00+02:00,0.0,703.5027607361963,0.0,0.0,703.50276,0.0,703.50276,0.0,0.1907,0.065,-0.067078988166,-0.067078988166,Optimal
|
13
|
+
2024-06-04 04:30:00+02:00,0.0,646.7419879518072,0.0,0.0,646.74199,0.0,646.74199,0.0,0.1907,0.065,-0.061666848746500004,-0.061666848746500004,Optimal
|
14
|
+
2024-06-04 05:00:00+02:00,0.0,1009.152816091954,0.0,0.0,1009.1528,0.0,1009.1528,0.0,0.1907,0.065,-0.09622271948,-0.09622271948,Optimal
|
15
|
+
2024-06-04 05:30:00+02:00,0.0,967.1363841807911,0.0,0.0,967.13638,0.0,967.13638,0.0,0.1907,0.065,-0.092216453833,-0.092216453833,Optimal
|
16
|
+
2024-06-04 06:00:00+02:00,0.0,935.1571508379889,0.0,0.0,935.15715,0.0,935.15715,0.0,0.1907,0.065,-0.0891672342525,-0.0891672342525,Optimal
|
17
|
+
2024-06-04 06:30:00+02:00,60.0,3267.5106703910615,0.0,0.0,3207.5107,0.0,3207.5107,0.0,0.1907,0.065,-0.305836145245,-0.305836145245,Optimal
|
18
|
+
2024-06-04 07:00:00+02:00,840.0,3286.2027777777776,0.0,0.0,2446.2028,0.0,2446.2028,0.0,0.1907,0.065,-0.23324543698000003,-0.23324543698000003,Optimal
|
19
|
+
2024-06-04 07:30:00+02:00,660.0,1496.1914772727273,0.0,0.0,836.19148,0.0,836.19148,0.0,0.1907,0.065,-0.079730857618,-0.079730857618,Optimal
|
20
|
+
2024-06-04 08:00:00+02:00,620.0,794.2991620111732,0.0,0.0,174.29916,0.0,174.29916,0.0,0.1907,0.065,-0.016619424906,-0.016619424906,Optimal
|
21
|
+
2024-06-04 08:30:00+02:00,620.0,832.2424719101124,0.0,0.0,212.24247,0.0,212.24247,0.0,0.1907,0.065,-0.020237319514500002,-0.020237319514500002,Optimal
|
22
|
+
2024-06-04 09:00:00+02:00,6380.0,788.9761235955057,3000.0,750.0,0.0,-1841.0239,-1841.0239,0.0,0.1907,0.065,0.05983327675,0.05983327675,Optimal
|
23
|
+
2024-06-04 09:30:00+02:00,1095.5620000000001,781.2152298850575,0.0,750.0,435.65323,0.0,435.65323,0.0,0.1907,0.065,-0.0415395354805,-0.0415395354805,Optimal
|
24
|
+
2024-06-04 10:00:00+02:00,811.4380000000002,664.0545197740113,0.0,0.0,0.0,-147.38348,-147.38348,0.0,0.1907,0.065,0.0047899631,0.0047899631,Optimal
|
25
|
+
2024-06-04 10:30:00+02:00,681.0759999999999,666.1989265536723,0.0,0.0,0.0,-14.877073,-14.877073,0.0,0.1907,0.065,0.00048350487250000003,0.00048350487250000003,Optimal
|
26
|
+
2024-06-04 11:00:00+02:00,671.9846666666667,669.4183146067417,0.0,0.0,0.0,-2.5663521,-2.5663521,0.0,0.1907,0.065,8.340644325000001e-05,8.340644325000001e-05,Optimal
|
27
|
+
2024-06-04 11:30:00+02:00,6469.634666666666,579.2235294117647,3000.0,750.0,0.0,0.0,0.0,2140.4111,0.1907,-0.07,-0.0,-0.0,Optimal
|
28
|
+
2024-06-04 12:00:00+02:00,2992.012,642.7344318181817,3000.0,0.0,650.72243,0.0,650.72243,0.0,0.1907,-0.07,-0.06204638370050001,-0.06204638370050001,Optimal
|
29
|
+
2024-06-04 12:30:00+02:00,1867.9053333333331,637.1688636363637,0.0,750.0,0.0,0.0,0.0,480.73647,0.1907,-0.07,-0.0,-0.0,Optimal
|
30
|
+
2024-06-04 13:00:00+02:00,2067.554666666667,649.3890173410405,3000.0,0.0,1581.8344,0.0,1581.8344,0.0,0.1907,-0.07,-0.15082791004,-0.15082791004,Optimal
|
31
|
+
2024-06-04 13:30:00+02:00,622.756,509.79664739884396,0.0,0.0,0.0,0.0,0.0,112.95935,0.1907,-0.07,-0.0,-0.0,Optimal
|
32
|
+
2024-06-04 14:00:00+02:00,1518.7553333333335,500.53686046511626,0.0,750.0,0.0,-268.21847,-268.21847,0.0,0.1907,0.065,0.008717100275000002,0.008717100275000002,Optimal
|
33
|
+
2024-06-04 14:30:00+02:00,2551.502,520.944,3000.0,0.0,969.442,0.0,969.442,0.0,0.1907,0.065,-0.0924362947,-0.0924362947,Optimal
|
34
|
+
2024-06-04 15:00:00+02:00,4160.0,415.32341040462427,3000.0,750.0,5.3234104,0.0,5.3234104,0.0,0.1907,0.065,-0.00050758718164,-0.00050758718164,Optimal
|
35
|
+
2024-06-04 15:30:00+02:00,4240.0,321.6410285714286,3000.0,750.0,0.0,-168.35897,-168.35897,0.0,0.1419,0.065,0.005471666525,0.005471666525,Optimal
|
36
|
+
2024-06-04 16:00:00+02:00,560.0,3778.0933888888894,3000.0,750.0,6968.0934,0.0,6968.0934,0.0,0.1419,0.065,-0.49438622673,-0.49438622673,Optimal
|
37
|
+
2024-06-04 16:30:00+02:00,380.0,3990.816179775281,0.0,750.0,4360.8162,0.0,4360.8162,0.0,0.1419,0.065,-0.30939990939,-0.30939990939,Optimal
|
38
|
+
2024-06-04 17:00:00+02:00,0.0,3909.8039890710384,0.0,750.0,4659.804,0.0,4659.804,0.0,0.1419,0.065,-0.33061309380000004,-0.33061309380000004,Optimal
|
39
|
+
2024-06-04 17:30:00+02:00,0.0,4206.869447513812,0.0,0.0,4206.8694,0.0,4206.8694,0.0,0.1907,0.065,-0.40112499728999995,-0.40112499728999995,Optimal
|
40
|
+
2024-06-04 18:00:00+02:00,0.0,486.26584269662925,0.0,0.0,486.26584,0.0,486.26584,0.0,0.1907,0.065,-0.046365447844000006,-0.046365447844000006,Optimal
|
41
|
+
2024-06-04 18:30:00+02:00,0.0,402.43446927374305,0.0,0.0,402.43447,0.0,402.43447,0.0,0.1907,0.065,-0.0383721267145,-0.0383721267145,Optimal
|
42
|
+
2024-06-04 19:00:00+02:00,0.0,316.17875,0.0,0.0,316.17875,0.0,316.17875,0.0,0.1907,0.065,-0.0301476438125,-0.0301476438125,Optimal
|
43
|
+
2024-06-04 19:30:00+02:00,0.0,867.4,0.0,0.0,867.4,0.0,867.4,0.0,0.1907,0.065,-0.08270659000000001,-0.08270659000000001,Optimal
|
44
|
+
2024-06-04 20:00:00+02:00,0.0,340.8070760233918,0.0,0.0,340.80708,0.0,340.80708,0.0,0.1907,0.065,-0.032495955078,-0.032495955078,Optimal
|
45
|
+
2024-06-04 20:30:00+02:00,0.0,349.07406779661017,0.0,750.0,1099.0741,0.0,1099.0741,0.0,0.1419,0.065,-0.077979307395,-0.077979307395,Optimal
|
46
|
+
2024-06-04 21:00:00+02:00,0.0,1790.5224581005587,0.0,750.0,2540.5225,0.0,2540.5225,0.0,0.1419,0.065,-0.18025007137500001,-0.18025007137500001,Optimal
|
47
|
+
2024-06-04 21:30:00+02:00,0.0,2612.0882857142856,0.0,0.0,2612.0883,0.0,2612.0883,0.0,0.1419,0.065,-0.185327664885,-0.185327664885,Optimal
|
48
|
+
2024-06-04 22:00:00+02:00,0.0,2617.098882681564,0.0,0.0,2617.0989,0.0,2617.0989,0.0,0.1419,0.065,-0.185683166955,-0.185683166955,Optimal
|
49
|
+
2024-06-04 22:30:00+02:00,0.0,2254.2344375000002,0.0,0.0,2254.2344,0.0,2254.2344,0.0,0.1419,0.065,-0.15993793068,-0.15993793068,Optimal
|
Binary file
|
@@ -19,7 +19,7 @@ long_description = (here / 'README.md').read_text(encoding='utf-8')
|
|
19
19
|
|
20
20
|
setup(
|
21
21
|
name='emhass', # Required
|
22
|
-
version='0.10.
|
22
|
+
version='0.10.2', # Required
|
23
23
|
description='An Energy Management System for Home Assistant', # Optional
|
24
24
|
long_description=long_description, # Optional
|
25
25
|
long_description_content_type='text/markdown', # Optional (see note above)
|
@@ -52,7 +52,7 @@ setup(
|
|
52
52
|
'pulp>=2.4',
|
53
53
|
'pyyaml>=5.4.1',
|
54
54
|
'tables<=3.9.1',
|
55
|
-
'skforecast==0.12.
|
55
|
+
'skforecast==0.12.1',
|
56
56
|
'flask>=2.0.3',
|
57
57
|
'waitress>=2.1.1',
|
58
58
|
'plotly>=5.6.0'
|
@@ -52,7 +52,7 @@ def set_input_data_dict(emhass_conf: dict, costfun: str,
|
|
52
52
|
logger.info("Setting up needed data")
|
53
53
|
# Parsing yaml
|
54
54
|
retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(
|
55
|
-
emhass_conf, use_secrets=not
|
55
|
+
emhass_conf, use_secrets=not(get_data_from_file), params=params)
|
56
56
|
# Treat runtimeparams
|
57
57
|
params, retrieve_hass_conf, optim_conf, plant_conf = utils.treat_runtimeparams(
|
58
58
|
runtimeparams, params, retrieve_hass_conf, optim_conf, plant_conf, set_type, logger)
|
@@ -97,6 +97,8 @@ def set_input_data_dict(emhass_conf: dict, costfun: str,
|
|
97
97
|
# Get PV and load forecasts
|
98
98
|
df_weather = fcst.get_weather_forecast(
|
99
99
|
method=optim_conf["weather_forecast_method"])
|
100
|
+
if isinstance(df_weather, bool) and not df_weather:
|
101
|
+
return False
|
100
102
|
P_PV_forecast = fcst.get_power_from_weather(df_weather)
|
101
103
|
P_load_forecast = fcst.get_load_forecast(
|
102
104
|
method=optim_conf['load_forecast_method'])
|
@@ -142,6 +144,8 @@ def set_input_data_dict(emhass_conf: dict, costfun: str,
|
|
142
144
|
# Get PV and load forecasts
|
143
145
|
df_weather = fcst.get_weather_forecast(
|
144
146
|
method=optim_conf['weather_forecast_method'])
|
147
|
+
if isinstance(df_weather, bool) and not df_weather:
|
148
|
+
return False
|
145
149
|
P_PV_forecast = fcst.get_power_from_weather(
|
146
150
|
df_weather, set_mix_forecast=True, df_now=df_input_data)
|
147
151
|
P_load_forecast = fcst.get_load_forecast(
|
@@ -243,6 +247,50 @@ def set_input_data_dict(emhass_conf: dict, costfun: str,
|
|
243
247
|
}
|
244
248
|
return input_data_dict
|
245
249
|
|
250
|
+
def weather_forecast_cache(emhass_conf: dict, params: str,
|
251
|
+
runtimeparams: str, logger: logging.Logger) -> bool:
|
252
|
+
"""
|
253
|
+
Perform a call to get forecast function, intend to save results to cache.
|
254
|
+
|
255
|
+
:param emhass_conf: Dictionary containing the needed emhass paths
|
256
|
+
:type emhass_conf: dict
|
257
|
+
:param params: Configuration parameters passed from data/options.json
|
258
|
+
:type params: str
|
259
|
+
:param runtimeparams: Runtime optimization parameters passed as a dictionary
|
260
|
+
:type runtimeparams: str
|
261
|
+
:param logger: The passed logger object
|
262
|
+
:type logger: logging object
|
263
|
+
:return: A bool for function completion
|
264
|
+
:rtype: bool
|
265
|
+
|
266
|
+
"""
|
267
|
+
|
268
|
+
# Parsing yaml
|
269
|
+
retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(
|
270
|
+
emhass_conf, use_secrets=True, params=params)
|
271
|
+
|
272
|
+
# Treat runtimeparams
|
273
|
+
params, retrieve_hass_conf, optim_conf, plant_conf = utils.treat_runtimeparams(
|
274
|
+
runtimeparams, params, retrieve_hass_conf, optim_conf, plant_conf, "forecast", logger)
|
275
|
+
|
276
|
+
# Make sure weather_forecast_cache is true
|
277
|
+
if (params != None) and (params != "null"):
|
278
|
+
params = json.loads(params)
|
279
|
+
else:
|
280
|
+
params = {}
|
281
|
+
params["passed_data"]["weather_forecast_cache"] = True
|
282
|
+
params = json.dumps(params)
|
283
|
+
|
284
|
+
# Create Forecast object
|
285
|
+
fcst = Forecast(retrieve_hass_conf, optim_conf, plant_conf,
|
286
|
+
params, emhass_conf, logger)
|
287
|
+
|
288
|
+
result = fcst.get_weather_forecast(optim_conf["weather_forecast_method"])
|
289
|
+
if isinstance(result, bool) and not result:
|
290
|
+
return False
|
291
|
+
|
292
|
+
return True
|
293
|
+
|
246
294
|
|
247
295
|
def perfect_forecast_optim(input_data_dict: dict, logger: logging.Logger,
|
248
296
|
save_data_to_file: Optional[bool] = True,
|
@@ -683,8 +731,6 @@ def regressor_model_predict(input_data_dict: dict, logger: logging.Logger,
|
|
683
731
|
type_var="mlregressor")
|
684
732
|
return prediction
|
685
733
|
|
686
|
-
|
687
|
-
|
688
734
|
def publish_data(input_data_dict: dict, logger: logging.Logger,
|
689
735
|
save_data_to_file: Optional[bool] = False,
|
690
736
|
opt_res_latest: Optional[pd.DataFrame] = None,
|
@@ -708,12 +754,10 @@ def publish_data(input_data_dict: dict, logger: logging.Logger,
|
|
708
754
|
|
709
755
|
"""
|
710
756
|
logger.info("Publishing data to HASS instance")
|
711
|
-
|
712
757
|
if not isinstance(input_data_dict["params"],dict):
|
713
758
|
params = json.loads(input_data_dict["params"])
|
714
759
|
else:
|
715
760
|
params = input_data_dict["params"]
|
716
|
-
|
717
761
|
# Check if a day ahead optimization has been performed (read CSV file)
|
718
762
|
if save_data_to_file:
|
719
763
|
today = datetime.now(timezone.utc).replace(
|
@@ -726,7 +770,6 @@ def publish_data(input_data_dict: dict, logger: logging.Logger,
|
|
726
770
|
opt_res_list_names = []
|
727
771
|
publish_prefix = params["passed_data"]["publish_prefix"]
|
728
772
|
entity_path = input_data_dict['emhass_conf']['data_path'] / "entities"
|
729
|
-
|
730
773
|
# Check if items in entity_path
|
731
774
|
if os.path.exists(entity_path) and len(os.listdir(entity_path)) > 0:
|
732
775
|
# Obtain all files in entity_path
|
@@ -805,6 +848,36 @@ def publish_data(input_data_dict: dict, logger: logging.Logger,
|
|
805
848
|
dont_post=dont_post
|
806
849
|
)
|
807
850
|
cols_published = ["P_PV", "P_Load"]
|
851
|
+
# Publish PV curtailment
|
852
|
+
if input_data_dict["fcst"].plant_conf['compute_curtailment']:
|
853
|
+
custom_pv_curtailment_id = params["passed_data"]["custom_pv_curtailment_id"]
|
854
|
+
input_data_dict["rh"].post_data(
|
855
|
+
opt_res_latest["P_PV_curtailment"],
|
856
|
+
idx_closest,
|
857
|
+
custom_pv_curtailment_id["entity_id"],
|
858
|
+
custom_pv_curtailment_id["unit_of_measurement"],
|
859
|
+
custom_pv_curtailment_id["friendly_name"],
|
860
|
+
type_var="power",
|
861
|
+
publish_prefix=publish_prefix,
|
862
|
+
save_entities=entity_save,
|
863
|
+
dont_post=dont_post
|
864
|
+
)
|
865
|
+
cols_published = cols_published + ["P_PV_curtailment"]
|
866
|
+
# Publish P_hybrid_inverter
|
867
|
+
if input_data_dict["fcst"].plant_conf['inverter_is_hybrid']:
|
868
|
+
custom_hybrid_inverter_id = params["passed_data"]["custom_hybrid_inverter_id"]
|
869
|
+
input_data_dict["rh"].post_data(
|
870
|
+
opt_res_latest["P_hybrid_inverter"],
|
871
|
+
idx_closest,
|
872
|
+
custom_hybrid_inverter_id["entity_id"],
|
873
|
+
custom_hybrid_inverter_id["unit_of_measurement"],
|
874
|
+
custom_hybrid_inverter_id["friendly_name"],
|
875
|
+
type_var="power",
|
876
|
+
publish_prefix=publish_prefix,
|
877
|
+
save_entities=entity_save,
|
878
|
+
dont_post=dont_post
|
879
|
+
)
|
880
|
+
cols_published = cols_published + ["P_hybrid_inverter"]
|
808
881
|
# Publish deferrable loads
|
809
882
|
custom_deferrable_forecast_id = params["passed_data"][
|
810
883
|
"custom_deferrable_forecast_id"
|
@@ -944,7 +1017,7 @@ def publish_data(input_data_dict: dict, logger: logging.Logger,
|
|
944
1017
|
opt_res_latest.index[idx_closest]]]
|
945
1018
|
return opt_res
|
946
1019
|
|
947
|
-
def continual_publish(input_data_dict,entity_path,logger):
|
1020
|
+
def continual_publish(input_data_dict: dict, entity_path: pathlib.Path, logger: logging.Logger):
|
948
1021
|
"""
|
949
1022
|
If continual_publish is true and a entity file saved in /data_path/entities, continually publish sensor on freq rate, updating entity current state value based on timestamp
|
950
1023
|
|
@@ -959,23 +1032,22 @@ def continual_publish(input_data_dict,entity_path,logger):
|
|
959
1032
|
logger.info("Continual publish thread service started")
|
960
1033
|
freq = input_data_dict['retrieve_hass_conf'].get("freq", pd.to_timedelta(1, "minutes"))
|
961
1034
|
entity_path_contents = []
|
962
|
-
|
963
1035
|
while True:
|
964
1036
|
# Sleep for x seconds (using current time as a reference for time left)
|
965
1037
|
time.sleep(max(0,freq.total_seconds() - (datetime.now(input_data_dict["retrieve_hass_conf"]["time_zone"]).timestamp() % 60)))
|
966
|
-
|
967
1038
|
# Loop through all saved entity files
|
968
1039
|
if os.path.exists(entity_path) and len(os.listdir(entity_path)) > 0:
|
969
1040
|
entity_path_contents = os.listdir(entity_path)
|
970
1041
|
for entity in entity_path_contents:
|
971
1042
|
if entity != "metadata.json":
|
972
1043
|
# Call publish_json with entity file, build entity, and publish
|
973
|
-
publish_json(entity,input_data_dict,entity_path,logger,"continual_publish")
|
1044
|
+
publish_json(entity, input_data_dict, entity_path, logger, "continual_publish")
|
974
1045
|
pass
|
975
1046
|
# This function should never return
|
976
1047
|
return False
|
977
1048
|
|
978
|
-
def publish_json(entity,input_data_dict,entity_path
|
1049
|
+
def publish_json(entity: dict, input_data_dict: dict, entity_path: pathlib.Path,
|
1050
|
+
logger: logging.Logger, reference: Optional[str] = ""):
|
979
1051
|
"""
|
980
1052
|
Extract saved entity data from .json (in data_path/entities), build entity, post results to post_data
|
981
1053
|
|
@@ -989,9 +1061,8 @@ def publish_json(entity,input_data_dict,entity_path,logger,reference: Optional[s
|
|
989
1061
|
:type logger: logging.Logger
|
990
1062
|
:param reference: String for identifying who ran the function
|
991
1063
|
:type reference: str, optional
|
992
|
-
|
1064
|
+
|
993
1065
|
"""
|
994
|
-
|
995
1066
|
# Retrieve entity metadata from file
|
996
1067
|
if os.path.isfile(entity_path / "metadata.json"):
|
997
1068
|
with open(entity_path / "metadata.json", "r") as file:
|
@@ -1001,16 +1072,12 @@ def publish_json(entity,input_data_dict,entity_path,logger,reference: Optional[s
|
|
1001
1072
|
else:
|
1002
1073
|
logger.error("unable to located metadata.json in:" + entity_path)
|
1003
1074
|
return False
|
1004
|
-
|
1005
1075
|
# Round current timecode (now)
|
1006
1076
|
now_precise = datetime.now(input_data_dict["retrieve_hass_conf"]["time_zone"]).replace(second=0, microsecond=0)
|
1007
|
-
|
1008
1077
|
# Retrieve entity data from file
|
1009
1078
|
entity_data = pd.read_json(entity_path / entity , orient='index')
|
1010
|
-
|
1011
1079
|
# Remove ".json" from string for entity_id
|
1012
1080
|
entity_id = entity.replace(".json", "")
|
1013
|
-
|
1014
1081
|
# Adjust Dataframe from received entity json file
|
1015
1082
|
entity_data.columns = [metadata[entity_id]["name"]]
|
1016
1083
|
entity_data.index.name = "timestamp"
|
@@ -1025,15 +1092,13 @@ def publish_json(entity,input_data_dict,entity_path,logger,reference: Optional[s
|
|
1025
1092
|
idx_closest = entity_data.index.get_indexer([now_precise], method="bfill")[0]
|
1026
1093
|
if idx_closest == -1:
|
1027
1094
|
idx_closest = entity_data.index.get_indexer([now_precise], method="nearest")[0]
|
1028
|
-
|
1029
1095
|
# Call post data
|
1030
1096
|
if reference == "continual_publish":
|
1031
1097
|
logger.debug("Auto Published sensor:")
|
1032
1098
|
logger_levels = "DEBUG"
|
1033
1099
|
else:
|
1034
1100
|
logger_levels = "INFO"
|
1035
|
-
|
1036
|
-
#post/save entity
|
1101
|
+
# post/save entity
|
1037
1102
|
input_data_dict["rh"].post_data(
|
1038
1103
|
data_df=entity_data[metadata[entity_id]["name"]],
|
1039
1104
|
idx=idx_closest,
|
@@ -1125,7 +1190,7 @@ def main():
|
|
1125
1190
|
logger.error("Could not find emhass/src foulder in: " + str(root_path))
|
1126
1191
|
logger.error("Try setting emhass root path with --root")
|
1127
1192
|
return False
|
1128
|
-
#
|
1193
|
+
# Additional argument
|
1129
1194
|
try:
|
1130
1195
|
parser.add_argument(
|
1131
1196
|
"--version",
|
@@ -182,6 +182,7 @@ class Forecast(object):
|
|
182
182
|
|
183
183
|
"""
|
184
184
|
csv_path = self.emhass_conf['data_path'] / csv_path
|
185
|
+
w_forecast_cache_path = os.path.abspath(self.emhass_conf['data_path'] / "weather_forecast_data.pkl")
|
185
186
|
|
186
187
|
self.logger.info("Retrieving weather forecast data using method = "+method)
|
187
188
|
self.weather_forecast_method = method # Saving this attribute for later use to identify csv method usage
|
@@ -223,40 +224,97 @@ class Forecast(object):
|
|
223
224
|
data['relative_humidity'] = raw_data['Relative Humidity (%)']
|
224
225
|
data['precipitable_water'] = pvlib.atmosphere.gueymard94_pw(
|
225
226
|
data['temp_air'], data['relative_humidity'])
|
226
|
-
elif method == 'solcast': # using
|
227
|
-
#
|
228
|
-
if
|
229
|
-
|
230
|
-
self.
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
227
|
+
elif method == 'solcast': # using Solcast API
|
228
|
+
# Check if weather_forecast_cache is true or if forecast_data file does not exist
|
229
|
+
if self.params["passed_data"]["weather_forecast_cache"] or not os.path.isfile(w_forecast_cache_path):
|
230
|
+
# Check if weather_forecast_cache_only is true, if so produce error for not finding cache file
|
231
|
+
if not self.params["passed_data"]["weather_forecast_cache_only"]:
|
232
|
+
# Retrieve data from the Solcast API
|
233
|
+
if 'solcast_api_key' not in self.retrieve_hass_conf:
|
234
|
+
self.logger.error("The solcast_api_key parameter was not defined")
|
235
|
+
return False
|
236
|
+
if 'solcast_rooftop_id' not in self.retrieve_hass_conf:
|
237
|
+
self.logger.error("The solcast_rooftop_id parameter was not defined")
|
238
|
+
return False
|
239
|
+
headers = {
|
240
|
+
'User-Agent': 'EMHASS',
|
241
|
+
"Authorization": "Bearer " + self.retrieve_hass_conf['solcast_api_key'],
|
242
|
+
"content-type": "application/json",
|
243
|
+
}
|
244
|
+
days_solcast = int(len(self.forecast_dates)*self.freq.seconds/3600)
|
245
|
+
# If weather_forecast_cache, set request days as twice as long to avoid length issues (add a buffer)
|
246
|
+
if self.params["passed_data"]["weather_forecast_cache"]:
|
247
|
+
days_solcast = min((days_solcast * 2), 336)
|
248
|
+
url = "https://api.solcast.com.au/rooftop_sites/"+self.retrieve_hass_conf['solcast_rooftop_id']+"/forecasts?hours="+str(days_solcast)
|
249
|
+
response = get(url, headers=headers)
|
250
|
+
'''import bz2 # Uncomment to save a serialized data for tests
|
251
|
+
import _pickle as cPickle
|
252
|
+
with bz2.BZ2File("data/test_response_solcast_get_method.pbz2", "w") as f:
|
253
|
+
cPickle.dump(response, f)'''
|
254
|
+
# Verify the request passed
|
255
|
+
if int(response.status_code) == 200:
|
256
|
+
data = response.json()
|
257
|
+
elif int(response.status_code) == 402 or int(response.status_code) == 429:
|
258
|
+
self.logger.error("Solcast error: May have exceeded your subscription limit.")
|
259
|
+
return False
|
260
|
+
elif int(response.status_code) >= 400 or int(response.status_code) >= 202:
|
261
|
+
self.logger.error("Solcast error: There was a issue with the solcast request, check solcast API key and rooftop ID.")
|
262
|
+
self.logger.error("Solcast error: Check that your subscription is valid and your network can connect to Solcast.")
|
263
|
+
return False
|
264
|
+
data_list = []
|
265
|
+
for elm in data['forecasts']:
|
266
|
+
data_list.append(elm['pv_estimate']*1000) # Converting kW to W
|
267
|
+
# Check if the retrieved data has the correct length
|
268
|
+
if len(data_list) < len(self.forecast_dates):
|
269
|
+
self.logger.error("Not enough data retried from Solcast service, try increasing the time step or use MPC.")
|
270
|
+
else:
|
271
|
+
# If runtime weather_forecast_cache is true save forecast result to file as cache
|
272
|
+
if self.params["passed_data"]["weather_forecast_cache"]:
|
273
|
+
# Add x2 forecast periods for cached results. This adds a extra delta_forecast amount of days for a buffer
|
274
|
+
cached_forecast_dates = self.forecast_dates.union(pd.date_range(self.forecast_dates[-1], periods=(len(self.forecast_dates) +1), freq=self.freq)[1:])
|
275
|
+
cache_data_list = data_list[0:len(cached_forecast_dates)]
|
276
|
+
cache_data_dict = {'ts':cached_forecast_dates, 'yhat':cache_data_list}
|
277
|
+
data_cache = pd.DataFrame.from_dict(cache_data_dict)
|
278
|
+
data_cache.set_index('ts', inplace=True)
|
279
|
+
with open(w_forecast_cache_path, "wb") as file:
|
280
|
+
cPickle.dump(data_cache, file)
|
281
|
+
if not os.path.isfile(w_forecast_cache_path):
|
282
|
+
self.logger.warning("Solcast forecast data could not be saved to file.")
|
283
|
+
else:
|
284
|
+
self.logger.info("Saved the Solcast results to cache, for later reference.")
|
285
|
+
# Trim request results to forecast_dates
|
286
|
+
data_list = data_list[0:len(self.forecast_dates)]
|
287
|
+
data_dict = {'ts':self.forecast_dates, 'yhat':data_list}
|
288
|
+
# Define DataFrame
|
289
|
+
data = pd.DataFrame.from_dict(data_dict)
|
290
|
+
# Define index
|
291
|
+
data.set_index('ts', inplace=True)
|
292
|
+
# Else, notify user to update cache
|
293
|
+
else:
|
294
|
+
self.logger.error("Unable to obtain Solcast cache file.")
|
295
|
+
self.logger.error("Try running optimization again with 'weather_forecast_cache_only': false")
|
296
|
+
self.logger.error("Optionally, obtain new Solcast cache with runtime parameter 'weather_forecast_cache': true in an optimization, or run the `forecast-cache` action, to pull new data from Solcast and cache.")
|
297
|
+
return False
|
298
|
+
# Else, open stored weather_forecast_data.pkl file for previous forecast data (cached data)
|
252
299
|
else:
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
300
|
+
with open(w_forecast_cache_path, "rb") as file:
|
301
|
+
data = cPickle.load(file)
|
302
|
+
if not isinstance(data, pd.DataFrame) or len(data) < len(self.forecast_dates):
|
303
|
+
self.logger.error("There has been a error obtaining cached Solcast forecast data.")
|
304
|
+
self.logger.error("Try running optimization again with 'weather_forecast_cache': true, or run action `forecast-cache`, to pull new data from Solcast and cache.")
|
305
|
+
self.logger.warning("Removing old Solcast cache file. Next optimization will pull data from Solcast, unless 'weather_forecast_cache_only': true")
|
306
|
+
os.remove(w_forecast_cache_path)
|
307
|
+
return False
|
308
|
+
# Filter cached forecast data to match current forecast_dates start-end range (reduce forecast Dataframe size to appropriate length)
|
309
|
+
if self.forecast_dates[0] in data.index and self.forecast_dates[-1] in data.index:
|
310
|
+
data = data.loc[self.forecast_dates[0]:self.forecast_dates[-1]]
|
311
|
+
self.logger.info("Retrieved Solcast data from the previously saved cache.")
|
312
|
+
else:
|
313
|
+
self.logger.error("Unable to obtain cached Solcast forecast data within the requested timeframe range.")
|
314
|
+
self.logger.error("Try running optimization again (not using cache). Optionally, add runtime parameter 'weather_forecast_cache': true to pull new data from Solcast and cache.")
|
315
|
+
self.logger.warning("Removing old Solcast cache file. Next optimization will pull data from Solcast, unless 'weather_forecast_cache_only': true")
|
316
|
+
os.remove(w_forecast_cache_path)
|
317
|
+
return False
|
260
318
|
elif method == 'solar.forecast': # using the solar.forecast API
|
261
319
|
# Retrieve data from the solar.forecast API
|
262
320
|
if 'solar_forecast_kwp' not in self.retrieve_hass_conf:
|
@@ -423,9 +481,9 @@ class Forecast(object):
|
|
423
481
|
# Setting the main parameters of the PV plant
|
424
482
|
location = Location(latitude=self.lat, longitude=self.lon)
|
425
483
|
temp_params = TEMPERATURE_MODEL_PARAMETERS['sapm']['close_mount_glass_glass']
|
426
|
-
cec_modules = bz2.BZ2File(self.emhass_conf['root_path'] / 'data/cec_modules.pbz2', "rb")
|
484
|
+
cec_modules = bz2.BZ2File(self.emhass_conf['root_path'] / 'data' / 'cec_modules.pbz2', "rb")
|
427
485
|
cec_modules = cPickle.load(cec_modules)
|
428
|
-
cec_inverters = bz2.BZ2File(self.emhass_conf['root_path'] / 'data/cec_inverters.pbz2', "rb")
|
486
|
+
cec_inverters = bz2.BZ2File(self.emhass_conf['root_path'] / 'data' / 'cec_inverters.pbz2', "rb")
|
429
487
|
cec_inverters = cPickle.load(cec_inverters)
|
430
488
|
if type(self.plant_conf['module_model']) == list:
|
431
489
|
P_PV_forecast = pd.Series(0, index=df_weather.index)
|
@@ -838,5 +896,4 @@ class Forecast(object):
|
|
838
896
|
else:
|
839
897
|
self.logger.error("Passed method is not valid")
|
840
898
|
return False
|
841
|
-
return df_final
|
842
|
-
|
899
|
+
return df_final
|