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.
Files changed (61) hide show
  1. {emhass-0.10.0 → emhass-0.10.2}/CHANGELOG.md +18 -1
  2. {emhass-0.10.0 → emhass-0.10.2}/PKG-INFO +16 -10
  3. {emhass-0.10.0 → emhass-0.10.2}/README.md +14 -8
  4. emhass-0.10.2/data/opt_res_latest.csv +49 -0
  5. emhass-0.10.2/data/test_df_final.pkl +0 -0
  6. {emhass-0.10.0 → emhass-0.10.2}/setup.py +2 -2
  7. {emhass-0.10.0 → emhass-0.10.2}/src/emhass/command_line.py +86 -21
  8. {emhass-0.10.0 → emhass-0.10.2}/src/emhass/forecast.py +94 -37
  9. {emhass-0.10.0 → emhass-0.10.2}/src/emhass/machine_learning_forecaster.py +20 -20
  10. {emhass-0.10.0 → emhass-0.10.2}/src/emhass/optimization.py +46 -50
  11. {emhass-0.10.0 → emhass-0.10.2}/src/emhass/utils.py +59 -34
  12. {emhass-0.10.0 → emhass-0.10.2}/src/emhass/web_server.py +14 -4
  13. {emhass-0.10.0 → emhass-0.10.2}/src/emhass.egg-info/PKG-INFO +16 -10
  14. {emhass-0.10.0 → emhass-0.10.2}/src/emhass.egg-info/requires.txt +1 -1
  15. {emhass-0.10.0 → emhass-0.10.2}/tests/test_forecast.py +8 -0
  16. {emhass-0.10.0 → emhass-0.10.2}/tests/test_retrieve_hass.py +1 -1
  17. emhass-0.10.0/data/opt_res_latest.csv +0 -11
  18. emhass-0.10.0/data/test_df_final.pkl +0 -0
  19. {emhass-0.10.0 → emhass-0.10.2}/CODE_OF_CONDUCT.md +0 -0
  20. {emhass-0.10.0 → emhass-0.10.2}/CONTRIBUTING.md +0 -0
  21. {emhass-0.10.0 → emhass-0.10.2}/LICENSE +0 -0
  22. {emhass-0.10.0 → emhass-0.10.2}/MANIFEST.in +0 -0
  23. {emhass-0.10.0 → emhass-0.10.2}/data/data_load_cost_forecast.csv +0 -0
  24. {emhass-0.10.0 → emhass-0.10.2}/data/data_load_forecast.csv +0 -0
  25. {emhass-0.10.0 → emhass-0.10.2}/data/data_prod_price_forecast.csv +0 -0
  26. {emhass-0.10.0 → emhass-0.10.2}/data/data_train_load_clustering.pkl +0 -0
  27. {emhass-0.10.0 → emhass-0.10.2}/data/data_train_load_forecast.pkl +0 -0
  28. {emhass-0.10.0 → emhass-0.10.2}/data/data_weather_forecast.csv +0 -0
  29. {emhass-0.10.0 → emhass-0.10.2}/data/heating_prediction.csv +0 -0
  30. {emhass-0.10.0 → emhass-0.10.2}/data/opt_res_perfect_optim_cost.csv +0 -0
  31. {emhass-0.10.0 → emhass-0.10.2}/data/opt_res_perfect_optim_profit.csv +0 -0
  32. {emhass-0.10.0 → emhass-0.10.2}/data/opt_res_perfect_optim_self-consumption.csv +0 -0
  33. {emhass-0.10.0 → emhass-0.10.2}/data/test_response_get_data_get_method.pbz2 +0 -0
  34. {emhass-0.10.0 → emhass-0.10.2}/data/test_response_scrapper_get_method.pbz2 +0 -0
  35. {emhass-0.10.0 → emhass-0.10.2}/data/test_response_solarforecast_get_method.pbz2 +0 -0
  36. {emhass-0.10.0 → emhass-0.10.2}/data/test_response_solcast_get_method.pbz2 +0 -0
  37. {emhass-0.10.0 → emhass-0.10.2}/pyproject.toml +0 -0
  38. {emhass-0.10.0 → emhass-0.10.2}/setup.cfg +0 -0
  39. {emhass-0.10.0 → emhass-0.10.2}/src/emhass/__init__.py +0 -0
  40. {emhass-0.10.0 → emhass-0.10.2}/src/emhass/data/cec_inverters.pbz2 +0 -0
  41. {emhass-0.10.0 → emhass-0.10.2}/src/emhass/data/cec_modules.pbz2 +0 -0
  42. {emhass-0.10.0 → emhass-0.10.2}/src/emhass/machine_learning_regressor.py +0 -0
  43. {emhass-0.10.0 → emhass-0.10.2}/src/emhass/retrieve_hass.py +0 -0
  44. {emhass-0.10.0 → emhass-0.10.2}/src/emhass/static/advanced.html +0 -0
  45. {emhass-0.10.0 → emhass-0.10.2}/src/emhass/static/basic.html +0 -0
  46. {emhass-0.10.0 → emhass-0.10.2}/src/emhass/static/img/emhass_icon.png +0 -0
  47. {emhass-0.10.0 → emhass-0.10.2}/src/emhass/static/img/emhass_logo_short.svg +0 -0
  48. {emhass-0.10.0 → emhass-0.10.2}/src/emhass/static/img/feather-sprite.svg +0 -0
  49. {emhass-0.10.0 → emhass-0.10.2}/src/emhass/static/script.js +0 -0
  50. {emhass-0.10.0 → emhass-0.10.2}/src/emhass/static/style.css +0 -0
  51. {emhass-0.10.0 → emhass-0.10.2}/src/emhass/templates/index.html +0 -0
  52. {emhass-0.10.0 → emhass-0.10.2}/src/emhass/templates/template.html +0 -0
  53. {emhass-0.10.0 → emhass-0.10.2}/src/emhass.egg-info/SOURCES.txt +0 -0
  54. {emhass-0.10.0 → emhass-0.10.2}/src/emhass.egg-info/dependency_links.txt +0 -0
  55. {emhass-0.10.0 → emhass-0.10.2}/src/emhass.egg-info/entry_points.txt +0 -0
  56. {emhass-0.10.0 → emhass-0.10.2}/src/emhass.egg-info/top_level.txt +0 -0
  57. {emhass-0.10.0 → emhass-0.10.2}/tests/test_command_line_utils.py +0 -0
  58. {emhass-0.10.0 → emhass-0.10.2}/tests/test_machine_learning_forecaster.py +0 -0
  59. {emhass-0.10.0 → emhass-0.10.2}/tests/test_machine_learning_regressor.py +0 -0
  60. {emhass-0.10.0 → emhass-0.10.2}/tests/test_optimization.py +0 -0
  61. {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.0
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.0
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
- For this you will need `virtualenv`, install it using:
182
+ Create and activate a virtual environment:
183
183
  ```bash
184
- sudo apt install python3-virtualenv
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
- For this you will need `virtualenv`, install it using:
147
+ Create and activate a virtual environment:
148
148
  ```bash
149
- sudo apt install python3-virtualenv
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.0', # Required
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.0',
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 (get_data_from_file), params=params)
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,logger,reference: Optional[str] = ""):
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
- # Additionnal argument
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 solcast API
227
- # Retrieve data from the solcast API
228
- if 'solcast_api_key' not in self.retrieve_hass_conf:
229
- self.logger.warning("The solcast_api_key parameter was not defined, using dummy values for testing")
230
- self.retrieve_hass_conf['solcast_api_key'] = "123456"
231
- if 'solcast_rooftop_id' not in self.retrieve_hass_conf:
232
- self.logger.warning("The solcast_rooftop_id parameter was not defined, using dummy values for testing")
233
- self.retrieve_hass_conf['solcast_rooftop_id'] = "123456"
234
- headers = {
235
- "Authorization": "Bearer " + self.retrieve_hass_conf['solcast_api_key'],
236
- "content-type": "application/json",
237
- }
238
- days_solcast = int(len(self.forecast_dates)*self.freq.seconds/3600)
239
- url = "https://api.solcast.com.au/rooftop_sites/"+self.retrieve_hass_conf['solcast_rooftop_id']+"/forecasts?hours="+str(days_solcast)
240
- response = get(url, headers=headers)
241
- '''import bz2 # Uncomment to save a serialized data for tests
242
- import _pickle as cPickle
243
- with bz2.BZ2File("data/test_response_solcast_get_method.pbz2", "w") as f:
244
- cPickle.dump(response, f)'''
245
- data = response.json()
246
- data_list = []
247
- for elm in data['forecasts']:
248
- data_list.append(elm['pv_estimate']*1000) # Converting kW to W
249
- # Check if the retrieved data has the correct length
250
- if len(data_list) < len(self.forecast_dates):
251
- self.logger.error("Not enough data retrived from SolCast service, try increasing the time step or use MPC")
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
- # Ensure correct length
254
- data_list = data_list[0:len(self.forecast_dates)]
255
- # Define DataFrame
256
- data_dict = {'ts':self.forecast_dates, 'yhat':data_list}
257
- data = pd.DataFrame.from_dict(data_dict)
258
- # Define index
259
- data.set_index('ts', inplace=True)
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