emhass 0.13.3__tar.gz → 0.13.4__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 (32) hide show
  1. {emhass-0.13.3 → emhass-0.13.4}/PKG-INFO +45 -22
  2. {emhass-0.13.3 → emhass-0.13.4}/README.md +44 -21
  3. {emhass-0.13.3 → emhass-0.13.4}/pyproject.toml +1 -1
  4. {emhass-0.13.3 → emhass-0.13.4}/src/emhass/command_line.py +6 -2
  5. {emhass-0.13.3 → emhass-0.13.4}/src/emhass/data/associations.csv +4 -0
  6. {emhass-0.13.3 → emhass-0.13.4}/src/emhass/data/config_defaults.json +4 -0
  7. {emhass-0.13.3 → emhass-0.13.4}/src/emhass/forecast.py +52 -12
  8. {emhass-0.13.3 → emhass-0.13.4}/src/emhass/machine_learning_forecaster.py +0 -2
  9. {emhass-0.13.3 → emhass-0.13.4}/src/emhass/optimization.py +214 -84
  10. {emhass-0.13.3 → emhass-0.13.4}/src/emhass/retrieve_hass.py +7 -5
  11. {emhass-0.13.3 → emhass-0.13.4}/src/emhass/static/data/param_definitions.json +30 -0
  12. {emhass-0.13.3 → emhass-0.13.4}/src/emhass/utils.py +61 -21
  13. {emhass-0.13.3 → emhass-0.13.4}/src/emhass/web_server.py +10 -28
  14. {emhass-0.13.3 → emhass-0.13.4}/.gitignore +0 -0
  15. {emhass-0.13.3 → emhass-0.13.4}/LICENSE +0 -0
  16. {emhass-0.13.3 → emhass-0.13.4}/src/emhass/__init__.py +0 -0
  17. {emhass-0.13.3 → emhass-0.13.4}/src/emhass/data/cec_inverters.pbz2 +0 -0
  18. {emhass-0.13.3 → emhass-0.13.4}/src/emhass/data/cec_modules.pbz2 +0 -0
  19. {emhass-0.13.3 → emhass-0.13.4}/src/emhass/img/emhass_icon.png +0 -0
  20. {emhass-0.13.3 → emhass-0.13.4}/src/emhass/machine_learning_regressor.py +0 -0
  21. {emhass-0.13.3 → emhass-0.13.4}/src/emhass/static/advanced.html +0 -0
  22. {emhass-0.13.3 → emhass-0.13.4}/src/emhass/static/basic.html +0 -0
  23. {emhass-0.13.3 → emhass-0.13.4}/src/emhass/static/configuration_list.html +0 -0
  24. {emhass-0.13.3 → emhass-0.13.4}/src/emhass/static/configuration_script.js +0 -0
  25. {emhass-0.13.3 → emhass-0.13.4}/src/emhass/static/img/emhass_icon.png +0 -0
  26. {emhass-0.13.3 → emhass-0.13.4}/src/emhass/static/img/emhass_logo_short.svg +0 -0
  27. {emhass-0.13.3 → emhass-0.13.4}/src/emhass/static/img/feather-sprite.svg +0 -0
  28. {emhass-0.13.3 → emhass-0.13.4}/src/emhass/static/script.js +0 -0
  29. {emhass-0.13.3 → emhass-0.13.4}/src/emhass/static/style.css +0 -0
  30. {emhass-0.13.3 → emhass-0.13.4}/src/emhass/templates/configuration.html +0 -0
  31. {emhass-0.13.3 → emhass-0.13.4}/src/emhass/templates/index.html +0 -0
  32. {emhass-0.13.3 → emhass-0.13.4}/src/emhass/templates/template.html +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: emhass
3
- Version: 0.13.3
3
+ Version: 0.13.4
4
4
  Summary: An Energy Management System for Home Assistant
5
5
  Project-URL: Homepage, https://github.com/davidusb-geek/emhass
6
6
  Project-URL: Source, https://github.com/davidusb-geek/emhass
@@ -118,17 +118,10 @@ Description-Content-Type: text/markdown
118
118
  </div>
119
119
 
120
120
  <br>
121
- <p align="center">
122
- If you like this work please consider buying a coffee ;-)
123
- </p>
124
- <p align="center">
125
- <a href="https://www.buymeacoffee.com/davidusbgeek" target="_blank">
126
- <img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" >
127
- </a>
121
+ <p align="left">
122
+ EMHASS is a Python module designed to optimize your home energy interfacing with Home Assistant.
128
123
  </p>
129
124
 
130
- EHMASS is a Python module designed to optimize your home energy interfacing with Home Assistant.
131
-
132
125
  ## Introduction
133
126
 
134
127
  EMHASS (Energy Management for Home Assistant) is an optimization tool designed for residential households. The package uses a Linear Programming approach to optimize energy usage while considering factors such as electricity prices, power generation from solar panels, and energy storage from batteries. EMHASS provides a high degree of configurability, making it easy to integrate with Home Assistant and other smart home systems. Whether you have solar panels, energy storage, or just a controllable load, EMHASS can provide an optimized daily schedule for your devices, allowing you to save money and minimize your environmental impact.
@@ -197,12 +190,13 @@ _Note: Both EMHASS via Docker and EMHASS-Add-on contain the same Docker image. T
197
190
 
198
191
  ### Method 2) Running EMHASS in Docker
199
192
 
200
- You can also install EMHASS using Docker as a container. This can be in the same machine as Home Assistant (if your running Home Assistant as a Docker container) or in a different distant machine. To install first pull the latest image:
193
+ You can also install EMHASS using Docker as a container. This can be in the same machine as Home Assistant (if your running Home Assistant as a Docker container) or in a different distant machine. The "share" folder is where EMHASS stores the config.json file. In the examples below adjust the "-v" volume mappings to reflect where your path to the local host directory needs to be mapped to.
194
+ To install first pull the latest image:
201
195
  ```bash
202
196
  # pull Docker image
203
197
  docker pull ghcr.io/davidusb-geek/emhass:latest
204
- # run Docker image, mounting config.json and secrets_emhass.yaml from host
205
- docker run --rm -it --restart always -p 5000:5000 --name emhass-container -v ./config.json:/share/config.json -v ./secrets_emhass.yaml:/app/secrets_emhass.yaml ghcr.io/davidusb-geek/emhass:latest
198
+ # run Docker image, mounting the dir storing config.json and secrets_emhass.yaml from host
199
+ docker run --rm -it --restart always -p 5000:5000 --name emhass-container -v /emhass/share:/share/ -v /emhass/secrets_emhass.yaml:/app/secrets_emhass.yaml ghcr.io/davidusb-geek/emhass:latest
206
200
  ```
207
201
  *Note it is not recommended to install the latest EMHASS image with `:latest` *(as you would likely want to control when you update EMHASS version)*. Instead, find the [latest version tag](https://github.com/davidusb-geek/emhass/pkgs/container/emhass) (E.g: `v0.2.1`) and replace `latest`*
208
202
 
@@ -216,7 +210,7 @@ cd emhass
216
210
  # may need to set architecture tag (docker build --build-arg TARGETARCH=amd64 -t emhass-local .)
217
211
  docker build -t emhass-local .
218
212
  # run built Docker image, mounting config.json and secrets_emhass.yaml from host
219
- docker run --rm -it -p 5000:5000 --name emhass-container -v ./config.json:/share/config.json -v ./secrets_emhass.yaml:/app/secrets_emhass.yaml emhass-local
213
+ docker run --rm -it -p 5000:5000 --name emhass-container -v /emhass/share:/share -v /emhass/secrets_emhass.yaml:/app/secrets_emhass.yaml emhass-local
220
214
  ```
221
215
 
222
216
  Before running the docker container, make sure you have a designated folder for emhass on your host device and a `secrets_emhass.yaml` file. You can get a example of the secrets file from [`secrets_emhass(example).yaml`](https://github.com/davidusb-geek/emhass/blob/master/secrets_emhass(example).yaml) file on this repository.
@@ -232,23 +226,23 @@ Latitude: 45.83
232
226
  Longitude: 6.86
233
227
  Altitude: 4807.8
234
228
  EOT
235
- docker run --rm -it --restart always -p 5000:5000 --name emhass-container -v ./config.json:/share/config.json -v ./secrets_emhass.yaml:/app/secrets_emhass.yaml ghcr.io/davidusb-geek/emhass:latest
229
+ docker run --rm -it --restart always -p 5000:5000 --name emhass-container -v /emhass/share:/share -v /emhass/secrets_emhass.yaml:/app/secrets_emhass.yaml ghcr.io/davidusb-geek/emhass:latest
236
230
  ```
237
231
 
238
232
  #### Docker, things to note
239
233
 
240
- - You can create a `config.json` file prior to running emhass. *(obtain a example from: [config_defaults.json](https://github.com/davidusb-geek/emhass/blob/enhass-standalone-addon-merge/src/emhass/data/config_defaults.json)* Alteratively, you can insert your parameters into the configuration page on the EMHASS web server. (for EMHASS to auto create a config.json) With either option, the volume mount `-v ./config.json:/share/config.json` should be applied to make sure your config is stored on the host device. (to be not deleted when the EMHASS container gets removed/image updated)*
234
+ - You can create a `config.json` file prior to running emhass. *(obtain a example from: [config_defaults.json](https://github.com/davidusb-geek/emhass/blob/enhass-standalone-addon-merge/src/emhass/data/config_defaults.json)* Alteratively, you can insert your parameters into the configuration page on the EMHASS web server. (for EMHASS to auto create a config.json) With either option, the volume mount `-v /emhass/share:/share` should be applied to make sure your config is stored on the host device. (to be not deleted when the EMHASS container gets removed/image updated)*
241
235
 
242
236
  - If you wish to keep a local, semi-persistent copy of the EMHASS-generated data, create a local folder on your device, then mount said folder inside the container.
243
237
  ```bash
244
238
  #create data folder
245
239
  mkdir -p ~/emhass/data
246
- docker run -it --restart always -p 5000:5000 -e LOCAL_COSTFUN="profit" -v ~/emhass/config.json:/app/config.json -v ~/emhass/data:/data -v ~/emhass/secrets_emhass.yaml:/app/secrets_emhass.yaml --name DockerEMHASS <REPOSITORY:TAG>
240
+ docker run -it --restart always -p 5000:5000 -e LOCAL_COSTFUN="profit" -v /emhass/share:/share -v /emhass/data:/data -v /emhass/secrets_emhass.yaml:/app/secrets_emhass.yaml --name DockerEMHASS <REPOSITORY:TAG>
247
241
  ```
248
242
 
249
243
  - If you wish to set the web_server's homepage optimization diagrams to a timezone other than UTC, set `TZ` environment variable on docker run:
250
244
  ```bash
251
- docker run -it --restart always -p 5000:5000 -e TZ="Europe/Paris" -v ~/emhass/config.json:/app/config.json -v ~/emhass/secrets_emhass.yaml:/app/secrets_emhass.yaml --name DockerEMHASS <REPOSITORY:TAG>
245
+ docker run -it --restart always -p 5000:5000 -e TZ="Europe/Paris" -v /emhass/share:/share -v /emhass/secrets_emhass.yaml:/app/secrets_emhass.yaml --name DockerEMHASS <REPOSITORY:TAG>
252
246
  ```
253
247
  ### Method 3) Legacy method using a Python virtual environment *(Legacy CLI)*
254
248
  If you wish to run EMHASS optimizations with cli commands. *(no persistent web server session)* you can run EMHASS via the python package alone *(not wrapped in a Docker container)*.
@@ -320,12 +314,22 @@ Additional optimization strategies were developed later, that can be used in com
320
314
 
321
315
  ### Dayahead Optimization - Method 1) Add-on and docker standalone
322
316
 
323
- In `configuration.yaml`:
317
+ We can use the `shell_command` integration in `configuration.yaml`:
324
318
  ```yaml
325
319
  shell_command:
326
320
  dayahead_optim: "curl -i -H \"Content-Type:application/json\" -X POST -d '{}' http://localhost:5000/action/dayahead-optim"
327
321
  publish_data: "curl -i -H \"Content-Type:application/json\" -X POST -d '{}' http://localhost:5000/action/publish-data"
328
322
  ```
323
+ An alternative that will be useful when passing data at runtime (see dedicated section), we can use the the `rest_command` instead:
324
+ ```yaml
325
+ rest_command:
326
+ url: http://127.0.0.1:5000/action/dayahead-optim
327
+ method: POST
328
+ headers:
329
+ content-type: application/json
330
+ payload: >-
331
+ {}
332
+ ```
329
333
  ### Dayahead Optimization - Method 2) Legacy method using a Python virtual environment
330
334
 
331
335
  In `configuration.yaml`:
@@ -388,8 +392,8 @@ In `automations.yaml`:
388
392
  ```
389
393
  in configuration page/`config.json`
390
394
  ```json
391
- 'method_ts_round': "first"
392
- 'continual_publish': true
395
+ "method_ts_round": "first"
396
+ "continual_publish": true
393
397
  ```
394
398
  In this automation, the day-ahead optimization is performed once a day, every day at 5:30am.
395
399
  If the `optimization_time_step` parameter is set to `30` *(default)* in the configuration, the results of the day-ahead optimization will generate 48 values *(for each entity)*, a value for every 30 minutes in a day *(i.e. 24 hrs x 2)*.
@@ -543,7 +547,7 @@ For users who wish to have full control of exactly when they would like to run a
543
547
 
544
548
  in configuration page/`config.json` :
545
549
  ```json
546
- 'continual_publish': false
550
+ "continual_publish": false
547
551
  ```
548
552
  POST action :
549
553
  ```bash
@@ -667,6 +671,25 @@ curl -i -H 'Content-Type:application/json' -X POST -d '{"pv_power_forecast":[0,
667
671
  curl -i -H 'Content-Type:application/json' -X POST -d '{"pv_power_forecast":[0, 70, 141.22, 246.18, 513.5, 753.27, 1049.89, 1797.93, 1697.3, 3078.93], "prediction_horizon":10, "soc_init":0.5,"soc_final":0.6,"operating_hours_of_each_deferrable_load":[1,3],"start_timesteps_of_each_deferrable_load":[0,3],"end_timesteps_of_each_deferrable_load":[0,6]}' http://localhost:5000/action/naive-mpc-optim
668
672
  ```
669
673
 
674
+ For a more readable option we can use the `rest_command` integration:
675
+ ```yaml
676
+ rest_command:
677
+ url: http://127.0.0.1:5000/action/dayahead-optim
678
+ method: POST
679
+ headers:
680
+ content-type: application/json
681
+ payload: >-
682
+ {
683
+ "pv_power_forecast": [0, 70, 141.22, 246.18, 513.5, 753.27, 1049.89, 1797.93, 1697.3, 3078.93],
684
+ "prediction_horizon":10,
685
+ "soc_init":0.5,
686
+ "soc_final":0.6,
687
+ "operating_hours_of_each_deferrable_load":[1,3],
688
+ "start_timesteps_of_each_deferrable_load":[0,3],
689
+ "end_timesteps_of_each_deferrable_load":[0,6]
690
+ }
691
+ ```
692
+
670
693
  ## A machine learning forecaster
671
694
 
672
695
  Starting in v0.4.0 a new machine learning forecaster class was introduced.
@@ -62,17 +62,10 @@
62
62
  </div>
63
63
 
64
64
  <br>
65
- <p align="center">
66
- If you like this work please consider buying a coffee ;-)
67
- </p>
68
- <p align="center">
69
- <a href="https://www.buymeacoffee.com/davidusbgeek" target="_blank">
70
- <img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" >
71
- </a>
65
+ <p align="left">
66
+ EMHASS is a Python module designed to optimize your home energy interfacing with Home Assistant.
72
67
  </p>
73
68
 
74
- EHMASS is a Python module designed to optimize your home energy interfacing with Home Assistant.
75
-
76
69
  ## Introduction
77
70
 
78
71
  EMHASS (Energy Management for Home Assistant) is an optimization tool designed for residential households. The package uses a Linear Programming approach to optimize energy usage while considering factors such as electricity prices, power generation from solar panels, and energy storage from batteries. EMHASS provides a high degree of configurability, making it easy to integrate with Home Assistant and other smart home systems. Whether you have solar panels, energy storage, or just a controllable load, EMHASS can provide an optimized daily schedule for your devices, allowing you to save money and minimize your environmental impact.
@@ -141,12 +134,13 @@ _Note: Both EMHASS via Docker and EMHASS-Add-on contain the same Docker image. T
141
134
 
142
135
  ### Method 2) Running EMHASS in Docker
143
136
 
144
- You can also install EMHASS using Docker as a container. This can be in the same machine as Home Assistant (if your running Home Assistant as a Docker container) or in a different distant machine. To install first pull the latest image:
137
+ You can also install EMHASS using Docker as a container. This can be in the same machine as Home Assistant (if your running Home Assistant as a Docker container) or in a different distant machine. The "share" folder is where EMHASS stores the config.json file. In the examples below adjust the "-v" volume mappings to reflect where your path to the local host directory needs to be mapped to.
138
+ To install first pull the latest image:
145
139
  ```bash
146
140
  # pull Docker image
147
141
  docker pull ghcr.io/davidusb-geek/emhass:latest
148
- # run Docker image, mounting config.json and secrets_emhass.yaml from host
149
- docker run --rm -it --restart always -p 5000:5000 --name emhass-container -v ./config.json:/share/config.json -v ./secrets_emhass.yaml:/app/secrets_emhass.yaml ghcr.io/davidusb-geek/emhass:latest
142
+ # run Docker image, mounting the dir storing config.json and secrets_emhass.yaml from host
143
+ docker run --rm -it --restart always -p 5000:5000 --name emhass-container -v /emhass/share:/share/ -v /emhass/secrets_emhass.yaml:/app/secrets_emhass.yaml ghcr.io/davidusb-geek/emhass:latest
150
144
  ```
151
145
  *Note it is not recommended to install the latest EMHASS image with `:latest` *(as you would likely want to control when you update EMHASS version)*. Instead, find the [latest version tag](https://github.com/davidusb-geek/emhass/pkgs/container/emhass) (E.g: `v0.2.1`) and replace `latest`*
152
146
 
@@ -160,7 +154,7 @@ cd emhass
160
154
  # may need to set architecture tag (docker build --build-arg TARGETARCH=amd64 -t emhass-local .)
161
155
  docker build -t emhass-local .
162
156
  # run built Docker image, mounting config.json and secrets_emhass.yaml from host
163
- docker run --rm -it -p 5000:5000 --name emhass-container -v ./config.json:/share/config.json -v ./secrets_emhass.yaml:/app/secrets_emhass.yaml emhass-local
157
+ docker run --rm -it -p 5000:5000 --name emhass-container -v /emhass/share:/share -v /emhass/secrets_emhass.yaml:/app/secrets_emhass.yaml emhass-local
164
158
  ```
165
159
 
166
160
  Before running the docker container, make sure you have a designated folder for emhass on your host device and a `secrets_emhass.yaml` file. You can get a example of the secrets file from [`secrets_emhass(example).yaml`](https://github.com/davidusb-geek/emhass/blob/master/secrets_emhass(example).yaml) file on this repository.
@@ -176,23 +170,23 @@ Latitude: 45.83
176
170
  Longitude: 6.86
177
171
  Altitude: 4807.8
178
172
  EOT
179
- docker run --rm -it --restart always -p 5000:5000 --name emhass-container -v ./config.json:/share/config.json -v ./secrets_emhass.yaml:/app/secrets_emhass.yaml ghcr.io/davidusb-geek/emhass:latest
173
+ docker run --rm -it --restart always -p 5000:5000 --name emhass-container -v /emhass/share:/share -v /emhass/secrets_emhass.yaml:/app/secrets_emhass.yaml ghcr.io/davidusb-geek/emhass:latest
180
174
  ```
181
175
 
182
176
  #### Docker, things to note
183
177
 
184
- - You can create a `config.json` file prior to running emhass. *(obtain a example from: [config_defaults.json](https://github.com/davidusb-geek/emhass/blob/enhass-standalone-addon-merge/src/emhass/data/config_defaults.json)* Alteratively, you can insert your parameters into the configuration page on the EMHASS web server. (for EMHASS to auto create a config.json) With either option, the volume mount `-v ./config.json:/share/config.json` should be applied to make sure your config is stored on the host device. (to be not deleted when the EMHASS container gets removed/image updated)*
178
+ - You can create a `config.json` file prior to running emhass. *(obtain a example from: [config_defaults.json](https://github.com/davidusb-geek/emhass/blob/enhass-standalone-addon-merge/src/emhass/data/config_defaults.json)* Alteratively, you can insert your parameters into the configuration page on the EMHASS web server. (for EMHASS to auto create a config.json) With either option, the volume mount `-v /emhass/share:/share` should be applied to make sure your config is stored on the host device. (to be not deleted when the EMHASS container gets removed/image updated)*
185
179
 
186
180
  - If you wish to keep a local, semi-persistent copy of the EMHASS-generated data, create a local folder on your device, then mount said folder inside the container.
187
181
  ```bash
188
182
  #create data folder
189
183
  mkdir -p ~/emhass/data
190
- docker run -it --restart always -p 5000:5000 -e LOCAL_COSTFUN="profit" -v ~/emhass/config.json:/app/config.json -v ~/emhass/data:/data -v ~/emhass/secrets_emhass.yaml:/app/secrets_emhass.yaml --name DockerEMHASS <REPOSITORY:TAG>
184
+ docker run -it --restart always -p 5000:5000 -e LOCAL_COSTFUN="profit" -v /emhass/share:/share -v /emhass/data:/data -v /emhass/secrets_emhass.yaml:/app/secrets_emhass.yaml --name DockerEMHASS <REPOSITORY:TAG>
191
185
  ```
192
186
 
193
187
  - If you wish to set the web_server's homepage optimization diagrams to a timezone other than UTC, set `TZ` environment variable on docker run:
194
188
  ```bash
195
- docker run -it --restart always -p 5000:5000 -e TZ="Europe/Paris" -v ~/emhass/config.json:/app/config.json -v ~/emhass/secrets_emhass.yaml:/app/secrets_emhass.yaml --name DockerEMHASS <REPOSITORY:TAG>
189
+ docker run -it --restart always -p 5000:5000 -e TZ="Europe/Paris" -v /emhass/share:/share -v /emhass/secrets_emhass.yaml:/app/secrets_emhass.yaml --name DockerEMHASS <REPOSITORY:TAG>
196
190
  ```
197
191
  ### Method 3) Legacy method using a Python virtual environment *(Legacy CLI)*
198
192
  If you wish to run EMHASS optimizations with cli commands. *(no persistent web server session)* you can run EMHASS via the python package alone *(not wrapped in a Docker container)*.
@@ -264,12 +258,22 @@ Additional optimization strategies were developed later, that can be used in com
264
258
 
265
259
  ### Dayahead Optimization - Method 1) Add-on and docker standalone
266
260
 
267
- In `configuration.yaml`:
261
+ We can use the `shell_command` integration in `configuration.yaml`:
268
262
  ```yaml
269
263
  shell_command:
270
264
  dayahead_optim: "curl -i -H \"Content-Type:application/json\" -X POST -d '{}' http://localhost:5000/action/dayahead-optim"
271
265
  publish_data: "curl -i -H \"Content-Type:application/json\" -X POST -d '{}' http://localhost:5000/action/publish-data"
272
266
  ```
267
+ An alternative that will be useful when passing data at runtime (see dedicated section), we can use the the `rest_command` instead:
268
+ ```yaml
269
+ rest_command:
270
+ url: http://127.0.0.1:5000/action/dayahead-optim
271
+ method: POST
272
+ headers:
273
+ content-type: application/json
274
+ payload: >-
275
+ {}
276
+ ```
273
277
  ### Dayahead Optimization - Method 2) Legacy method using a Python virtual environment
274
278
 
275
279
  In `configuration.yaml`:
@@ -332,8 +336,8 @@ In `automations.yaml`:
332
336
  ```
333
337
  in configuration page/`config.json`
334
338
  ```json
335
- 'method_ts_round': "first"
336
- 'continual_publish': true
339
+ "method_ts_round": "first"
340
+ "continual_publish": true
337
341
  ```
338
342
  In this automation, the day-ahead optimization is performed once a day, every day at 5:30am.
339
343
  If the `optimization_time_step` parameter is set to `30` *(default)* in the configuration, the results of the day-ahead optimization will generate 48 values *(for each entity)*, a value for every 30 minutes in a day *(i.e. 24 hrs x 2)*.
@@ -487,7 +491,7 @@ For users who wish to have full control of exactly when they would like to run a
487
491
 
488
492
  in configuration page/`config.json` :
489
493
  ```json
490
- 'continual_publish': false
494
+ "continual_publish": false
491
495
  ```
492
496
  POST action :
493
497
  ```bash
@@ -611,6 +615,25 @@ curl -i -H 'Content-Type:application/json' -X POST -d '{"pv_power_forecast":[0,
611
615
  curl -i -H 'Content-Type:application/json' -X POST -d '{"pv_power_forecast":[0, 70, 141.22, 246.18, 513.5, 753.27, 1049.89, 1797.93, 1697.3, 3078.93], "prediction_horizon":10, "soc_init":0.5,"soc_final":0.6,"operating_hours_of_each_deferrable_load":[1,3],"start_timesteps_of_each_deferrable_load":[0,3],"end_timesteps_of_each_deferrable_load":[0,6]}' http://localhost:5000/action/naive-mpc-optim
612
616
  ```
613
617
 
618
+ For a more readable option we can use the `rest_command` integration:
619
+ ```yaml
620
+ rest_command:
621
+ url: http://127.0.0.1:5000/action/dayahead-optim
622
+ method: POST
623
+ headers:
624
+ content-type: application/json
625
+ payload: >-
626
+ {
627
+ "pv_power_forecast": [0, 70, 141.22, 246.18, 513.5, 753.27, 1049.89, 1797.93, 1697.3, 3078.93],
628
+ "prediction_horizon":10,
629
+ "soc_init":0.5,
630
+ "soc_final":0.6,
631
+ "operating_hours_of_each_deferrable_load":[1,3],
632
+ "start_timesteps_of_each_deferrable_load":[0,3],
633
+ "end_timesteps_of_each_deferrable_load":[0,6]
634
+ }
635
+ ```
636
+
614
637
  ## A machine learning forecaster
615
638
 
616
639
  Starting in v0.4.0 a new machine learning forecaster class was introduced.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "emhass"
3
- version = "0.13.3"
3
+ version = "0.13.4"
4
4
  description = "An Energy Management System for Home Assistant"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10, <3.13"
@@ -72,7 +72,9 @@ def retrieve_home_assistant_data(
72
72
  if optim_conf.get("set_use_pv", True):
73
73
  var_list.append(retrieve_hass_conf["sensor_power_photovoltaics"])
74
74
  if optim_conf.get("set_use_adjusted_pv", True):
75
- var_list.append(retrieve_hass_conf["sensor_power_photovoltaics_forecast"])
75
+ var_list.append(
76
+ retrieve_hass_conf["sensor_power_photovoltaics_forecast"]
77
+ )
76
78
  if not rh.get_data(
77
79
  days_list, var_list, minimal_response=False, significant_changes_only=False
78
80
  ):
@@ -302,7 +304,8 @@ def set_input_data_dict(
302
304
  else:
303
305
  P_PV_forecast = pd.Series(0, index=fcst.forecast_dates)
304
306
  P_load_forecast = fcst.get_load_forecast(
305
- method=optim_conf["load_forecast_method"]
307
+ days_min_load_forecast=optim_conf["delta_forecast_daily"].days,
308
+ method=optim_conf["load_forecast_method"],
306
309
  )
307
310
  if isinstance(P_load_forecast, bool) and not P_load_forecast:
308
311
  logger.error(
@@ -400,6 +403,7 @@ def set_input_data_dict(
400
403
  else:
401
404
  P_PV_forecast = pd.Series(0, index=fcst.forecast_dates)
402
405
  P_load_forecast = fcst.get_load_forecast(
406
+ days_min_load_forecast=optim_conf["delta_forecast_daily"].days,
403
407
  method=optim_conf["load_forecast_method"],
404
408
  set_mix_forecast=set_mix_forecast,
405
409
  df_now=df_input_data,
@@ -61,6 +61,10 @@ plant_conf,surface_azimuth,surface_azimuth,list_surface_azimuth
61
61
  plant_conf,modules_per_string,modules_per_string,list_modules_per_string
62
62
  plant_conf,strings_per_inverter,strings_per_inverter,list_strings_per_inverter
63
63
  plant_conf,inverter_is_hybrid,inverter_is_hybrid
64
+ plant_conf,inverter_ac_output_max,inverter_ac_output_max
65
+ plant_conf,inverter_ac_input_max,inverter_ac_input_max
66
+ plant_conf,inverter_efficiency_dc_ac,inverter_efficiency_dc_ac
67
+ plant_conf,inverter_efficiency_ac_dc,inverter_efficiency_ac_dc
64
68
  plant_conf,compute_curtailment,compute_curtailment
65
69
  plant_conf,Pd_max,battery_discharge_power_max
66
70
  plant_conf,Pc_max,battery_charge_power_max
@@ -116,6 +116,10 @@
116
116
  1
117
117
  ],
118
118
  "inverter_is_hybrid": false,
119
+ "inverter_ac_output_max": 1000,
120
+ "inverter_ac_input_max": 1000,
121
+ "inverter_efficiency_dc_ac": 1.0,
122
+ "inverter_efficiency_ac_dc": 1.0,
119
123
  "compute_curtailment": false,
120
124
  "set_use_battery": false,
121
125
  "battery_discharge_power_max": 1000,
@@ -1,5 +1,3 @@
1
- #!/usr/bin/env python3
2
-
3
1
  import bz2
4
2
  import copy
5
3
  import json
@@ -216,8 +214,7 @@ class Forecast:
216
214
  ]
217
215
 
218
216
  def get_cached_open_meteo_forecast_json(
219
- self,
220
- max_age: int | None = 30,
217
+ self, max_age: int | None = 30, forecast_days: int = 3
221
218
  ) -> dict:
222
219
  r"""
223
220
  Get weather forecast json from Open-Meteo and cache it for re-use.
@@ -235,10 +232,30 @@ class Forecast:
235
232
  before it is discarded and a new version fetched from Open-Meteo.
236
233
  Defaults to 30 minutes.
237
234
  :type max_age: int, optional
235
+ :param forecast_days: The number of days of forecast data required from Open-Meteo.
236
+ One additional day is always fetched from Open-Meteo so there is an extra data in the cache.
237
+ Defaults to 2 days (3 days fetched) to match the prior default.
238
+ :type forecast_days: int, optional
238
239
  :return: The json containing the Open-Meteo forecast data
239
240
  :rtype: dict
240
241
 
241
242
  """
243
+
244
+ # Ensure at least 3 weather forecast days (and 1 more than requested)
245
+ if forecast_days is None:
246
+ self.logger.warning(
247
+ "Open-Meteo forecast_days is missing so defaulting to 3 days"
248
+ )
249
+ forecast_days = 3
250
+ elif forecast_days < 3:
251
+ self.logger.warning(
252
+ "Open-Meteo forecast_days is too low (%s) so defaulting to 3 days",
253
+ forecast_days,
254
+ )
255
+ forecast_days = 3
256
+ else:
257
+ forecast_days = forecast_days + 1
258
+
242
259
  json_path = os.path.abspath(
243
260
  self.emhass_conf["data_path"] / "cached-open-meteo-forecast.json"
244
261
  )
@@ -287,10 +304,13 @@ class Forecast:
287
304
  + "shortwave_radiation_instant,"
288
305
  + "diffuse_radiation_instant,"
289
306
  + "direct_normal_irradiance_instant"
307
+ + "&forecast_days="
308
+ + str(forecast_days)
290
309
  + "&timezone="
291
310
  + quote(str(self.time_zone), safe="")
292
311
  )
293
312
  try:
313
+ self.logger.debug("Fetching data from Open-Meteo using URL: %s", url)
294
314
  response = get(url, headers=headers)
295
315
  self.logger.debug("Returned HTTP status code: %s", response.status_code)
296
316
  response.raise_for_status()
@@ -349,7 +369,8 @@ class Forecast:
349
369
  ): # The scrapper option is being left here for backward compatibility
350
370
  if not os.path.isfile(w_forecast_cache_path):
351
371
  data_raw = self.get_cached_open_meteo_forecast_json(
352
- self.optim_conf["open_meteo_cache_max_age"]
372
+ self.optim_conf["open_meteo_cache_max_age"],
373
+ self.optim_conf["delta_forecast_daily"].days,
353
374
  )
354
375
  data_15min = pd.DataFrame.from_dict(data_raw["minutely_15"])
355
376
  data_15min["time"] = pd.to_datetime(data_15min["time"])
@@ -671,6 +692,7 @@ class Forecast:
671
692
  alpha: float,
672
693
  beta: float,
673
694
  col: str,
695
+ ignore_pv_feedback: bool = False,
674
696
  ) -> pd.DataFrame:
675
697
  """A simple correction method for forecasted data using the current real values of a variable.
676
698
 
@@ -684,9 +706,15 @@ class Forecast:
684
706
  :type beta: float
685
707
  :param col: The column variable name
686
708
  :type col: str
709
+ :param ignore_pv_feedback: If True, bypass mixing and return original forecast (used during curtailment)
710
+ :type ignore_pv_feedback: bool
687
711
  :return: The output DataFrame with the corrected values
688
712
  :rtype: pd.DataFrame
689
713
  """
714
+ # If ignoring PV feedback (e.g., during curtailment), return original forecast
715
+ if ignore_pv_feedback:
716
+ return df_forecast
717
+
690
718
  first_fcst = alpha * df_forecast.iloc[0] + beta * df_now[col].iloc[-1]
691
719
  df_forecast.iloc[0] = int(round(first_fcst))
692
720
  return df_forecast
@@ -787,12 +815,14 @@ class Forecast:
787
815
  # Extracting results for AC power
788
816
  P_PV_forecast = mc.results.ac
789
817
  if set_mix_forecast:
818
+ ignore_pv_feedback = self.params["passed_data"].get("ignore_pv_feedback_during_curtailment", False)
790
819
  P_PV_forecast = Forecast.get_mix_forecast(
791
820
  df_now,
792
821
  P_PV_forecast,
793
822
  self.params["passed_data"]["alpha"],
794
823
  self.params["passed_data"]["beta"],
795
824
  self.var_PV,
825
+ ignore_pv_feedback,
796
826
  )
797
827
  P_PV_forecast[P_PV_forecast < 0] = 0 # replace any negative PV values with zero
798
828
  self.logger.debug("get_power_from_weather returning:\n%s", P_PV_forecast)
@@ -1410,14 +1440,22 @@ class Forecast:
1410
1440
  forecast_out.index.name = "ts"
1411
1441
  forecast_out = forecast_out.rename(columns={"load": "yhat"})
1412
1442
  elif method == "naive": # using a naive approach
1413
- mask_forecast_out = (
1414
- df.index > days_list[-1] - self.optim_conf["delta_forecast_daily"]
1443
+ # Old code logic (shifted timestamp problem)
1444
+ # mask_forecast_out = (
1445
+ # df.index > days_list[-1] - self.optim_conf["delta_forecast_daily"]
1446
+ # )
1447
+ # forecast_out = df.copy().loc[mask_forecast_out]
1448
+ # forecast_out = forecast_out.rename(columns={self.var_load_new: "yhat"})
1449
+ # forecast_out = forecast_out.iloc[0 : len(self.forecast_dates)]
1450
+ # forecast_out.index = self.forecast_dates
1451
+ # New code logic
1452
+ forecast_horizon = len(self.forecast_dates)
1453
+ historical_values = df.iloc[-forecast_horizon:]
1454
+ forecast_out = pd.DataFrame(
1455
+ historical_values.values,
1456
+ index=self.forecast_dates,
1457
+ columns=["yhat"]
1415
1458
  )
1416
- forecast_out = df.copy().loc[mask_forecast_out]
1417
- forecast_out = forecast_out.rename(columns={self.var_load_new: "yhat"})
1418
- # Force forecast_out length to avoid mismatches
1419
- forecast_out = forecast_out.iloc[0 : len(self.forecast_dates)]
1420
- forecast_out.index = self.forecast_dates
1421
1459
  elif (
1422
1460
  method == "mlforecaster"
1423
1461
  ): # using a custom forecast model with machine learning
@@ -1506,12 +1544,14 @@ class Forecast:
1506
1544
  return False
1507
1545
  P_Load_forecast = copy.deepcopy(forecast_out["yhat"])
1508
1546
  if set_mix_forecast:
1547
+ # Load forecasts don't need curtailment protection - always use feedback
1509
1548
  P_Load_forecast = Forecast.get_mix_forecast(
1510
1549
  df_now,
1511
1550
  P_Load_forecast,
1512
1551
  self.params["passed_data"]["alpha"],
1513
1552
  self.params["passed_data"]["beta"],
1514
1553
  self.var_load_new,
1554
+ False, # Never ignore feedback for load forecasts
1515
1555
  )
1516
1556
  self.logger.debug("get_load_forecast returning:\n%s", P_Load_forecast)
1517
1557
  return P_Load_forecast
@@ -1,5 +1,3 @@
1
- #!/usr/bin/env python3
2
-
3
1
  import logging
4
2
  import time
5
3
  import warnings