emhass 0.11.0__tar.gz → 0.11.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.11.0 → emhass-0.11.2}/CHANGELOG.md +13 -0
- {emhass-0.11.0 → emhass-0.11.2}/PKG-INFO +3 -6
- {emhass-0.11.0 → emhass-0.11.2}/README.md +2 -5
- {emhass-0.11.0 → emhass-0.11.2}/setup.py +1 -1
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass/data/associations.csv +2 -0
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass/data/config_defaults.json +1 -2
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass/retrieve_hass.py +20 -11
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass/static/configuration_script.js +6 -5
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass/utils.py +124 -65
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass/web_server.py +0 -1
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass.egg-info/PKG-INFO +3 -6
- {emhass-0.11.0 → emhass-0.11.2}/tests/test_retrieve_hass.py +12 -6
- {emhass-0.11.0 → emhass-0.11.2}/tests/test_utils.py +1 -1
- {emhass-0.11.0 → emhass-0.11.2}/CODE_OF_CONDUCT.md +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/CONTRIBUTING.md +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/LICENSE +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/MANIFEST.in +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/data/data_load_cost_forecast.csv +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/data/data_load_forecast.csv +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/data/data_prod_price_forecast.csv +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/data/data_train_load_clustering.pkl +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/data/data_train_load_forecast.pkl +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/data/data_weather_forecast.csv +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/data/heating_prediction.csv +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/data/opt_res_latest.csv +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/data/opt_res_perfect_optim_cost.csv +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/data/opt_res_perfect_optim_profit.csv +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/data/opt_res_perfect_optim_self-consumption.csv +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/data/test_df_final.pkl +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/data/test_response_get_data_get_method.pbz2 +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/data/test_response_scrapper_get_method.pbz2 +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/data/test_response_solarforecast_get_method.pbz2 +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/data/test_response_solcast_get_method.pbz2 +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/pyproject.toml +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/setup.cfg +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass/__init__.py +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass/command_line.py +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass/data/cec_inverters.pbz2 +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass/data/cec_modules.pbz2 +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass/forecast.py +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass/machine_learning_forecaster.py +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass/machine_learning_regressor.py +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass/optimization.py +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass/static/advanced.html +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass/static/basic.html +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass/static/configuration_list.html +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass/static/data/param_definitions.json +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass/static/img/emhass_icon.png +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass/static/img/emhass_logo_short.svg +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass/static/img/feather-sprite.svg +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass/static/script.js +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass/static/style.css +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass/templates/configuration.html +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass/templates/index.html +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass/templates/template.html +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass.egg-info/SOURCES.txt +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass.egg-info/dependency_links.txt +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass.egg-info/entry_points.txt +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass.egg-info/requires.txt +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/src/emhass.egg-info/top_level.txt +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/tests/test_command_line_utils.py +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/tests/test_forecast.py +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/tests/test_machine_learning_forecaster.py +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/tests/test_machine_learning_regressor.py +0 -0
- {emhass-0.11.0 → emhass-0.11.2}/tests/test_optimization.py +0 -0
@@ -1,5 +1,18 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.11.2 - 2024-10-31
|
4
|
+
### Improvement
|
5
|
+
- Added support to retrieve HA configuration. This will be used in the future to automatically retrieve some parameters as the currency
|
6
|
+
### Fix
|
7
|
+
- utils fix runtime parameter merge bugs
|
8
|
+
- configuration_script.js fix placeholder value bug
|
9
|
+
|
10
|
+
## 0.11.1 - 2024-10-29
|
11
|
+
### Fix
|
12
|
+
- Fix parameter saving and duplicate battery bugs
|
13
|
+
- utils.py add more specific logging information for config
|
14
|
+
- Fix issue where thermal runtime parameters were not being propagated into optim_conf
|
15
|
+
|
3
16
|
## 0.11.0 - 2024-10-25
|
4
17
|
|
5
18
|
This version marks huge improvement works by @GeoDerp aiming to simplfy the intial and normal setup of EMHASS. The workflow for setting the EMHASS configuration regardless of the installation method has now been centralized on the single `config.json` file. The webserver has now a configuration tab that can be used to to modify and save the `config.json` file.
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: emhass
|
3
|
-
Version: 0.11.
|
3
|
+
Version: 0.11.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
|
@@ -47,9 +47,6 @@ Requires-Dist: plotly>=5.6.0
|
|
47
47
|
</a>
|
48
48
|
<a style="text-decoration:none" href="https://github.com/davidusb-geek/emhass/actions">
|
49
49
|
<img alt="EMHASS GitHub Workflow Status" src="https://github.com/davidusb-geek/emhass/actions/workflows/publish_docker.yaml/badge.svg?event=release">
|
50
|
-
</a>
|
51
|
-
<a style="text-decoration:none" href="https://github.com/davidusb-geek/emhass-add-on/actions">
|
52
|
-
<img alt="EMHASS-Add-on GitHub Workflow Status" src="https://github.com/davidusb-geek/emhass-add-on/actions/workflows/publish_docker.yaml/badge.svg?event=release">
|
53
50
|
</a>
|
54
51
|
<a hstyle="text-decoration:none" ref="https://codecov.io/github/davidusb-geek/emhass" >
|
55
52
|
<img src="https://codecov.io/github/davidusb-geek/emhass/branch/master/graph/badge.svg?token=BW7KSCHN90"/>
|
@@ -164,7 +161,7 @@ _Note: Both EMHASS via Docker and EMHASS-Add-on contain the same Docker image. T
|
|
164
161
|
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:
|
165
162
|
```bash
|
166
163
|
# pull Docker image
|
167
|
-
docker
|
164
|
+
docker pull ghcr.io/davidusb-geek/emhass:latest
|
168
165
|
# run Docker image, mounting config.json and secrets_emhass.yaml from host
|
169
166
|
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
|
170
167
|
```
|
@@ -173,7 +170,7 @@ docker run --rm -it --restart always -p 5000:5000 --name emhass-container -v ./
|
|
173
170
|
You can also build your image locally. For this clone this repository, and build the image from the Dockerfile:
|
174
171
|
```bash
|
175
172
|
# git clone EMHASS repo
|
176
|
-
git clone
|
173
|
+
git clone https://github.com/davidusb-geek/emhass.git
|
177
174
|
# move to EMHASS directory
|
178
175
|
cd emhass
|
179
176
|
# build Docker image
|
@@ -12,9 +12,6 @@
|
|
12
12
|
</a>
|
13
13
|
<a style="text-decoration:none" href="https://github.com/davidusb-geek/emhass/actions">
|
14
14
|
<img alt="EMHASS GitHub Workflow Status" src="https://github.com/davidusb-geek/emhass/actions/workflows/publish_docker.yaml/badge.svg?event=release">
|
15
|
-
</a>
|
16
|
-
<a style="text-decoration:none" href="https://github.com/davidusb-geek/emhass-add-on/actions">
|
17
|
-
<img alt="EMHASS-Add-on GitHub Workflow Status" src="https://github.com/davidusb-geek/emhass-add-on/actions/workflows/publish_docker.yaml/badge.svg?event=release">
|
18
15
|
</a>
|
19
16
|
<a hstyle="text-decoration:none" ref="https://codecov.io/github/davidusb-geek/emhass" >
|
20
17
|
<img src="https://codecov.io/github/davidusb-geek/emhass/branch/master/graph/badge.svg?token=BW7KSCHN90"/>
|
@@ -129,7 +126,7 @@ _Note: Both EMHASS via Docker and EMHASS-Add-on contain the same Docker image. T
|
|
129
126
|
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:
|
130
127
|
```bash
|
131
128
|
# pull Docker image
|
132
|
-
docker
|
129
|
+
docker pull ghcr.io/davidusb-geek/emhass:latest
|
133
130
|
# run Docker image, mounting config.json and secrets_emhass.yaml from host
|
134
131
|
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
|
135
132
|
```
|
@@ -138,7 +135,7 @@ docker run --rm -it --restart always -p 5000:5000 --name emhass-container -v ./
|
|
138
135
|
You can also build your image locally. For this clone this repository, and build the image from the Dockerfile:
|
139
136
|
```bash
|
140
137
|
# git clone EMHASS repo
|
141
|
-
git clone
|
138
|
+
git clone https://github.com/davidusb-geek/emhass.git
|
142
139
|
# move to EMHASS directory
|
143
140
|
cd emhass
|
144
141
|
# build Docker image
|
@@ -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.11.
|
22
|
+
version='0.11.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)
|
@@ -1,4 +1,5 @@
|
|
1
1
|
config_categorie,legacy_parameter_name,parameter,list_name
|
2
|
+
retrieve_hass_conf,logging_level,logging_level
|
2
3
|
retrieve_hass_conf,freq,optimization_time_step
|
3
4
|
retrieve_hass_conf,days_to_retrieve,historic_days_to_retrieve
|
4
5
|
retrieve_hass_conf,var_PV,sensor_power_photovoltaics
|
@@ -13,6 +14,7 @@ params_secrets,time_zone,time_zone
|
|
13
14
|
params_secrets,lat,Latitude
|
14
15
|
params_secrets,lon,Longitude
|
15
16
|
params_secrets,alt,Altitude
|
17
|
+
optim_conf,costfun,costfun
|
16
18
|
optim_conf,set_use_battery,set_use_battery
|
17
19
|
optim_conf,num_def_loads,number_of_deferrable_loads
|
18
20
|
optim_conf,P_deferrable_nom,nominal_power_of_deferrable_loads,list_nominal_power_of_deferrable_loads
|
@@ -19,8 +19,7 @@
|
|
19
19
|
"sensor_power_photovoltaics": "sensor.power_photovoltaics",
|
20
20
|
"sensor_power_load_no_var_loads": "sensor.power_load_no_var_loads",
|
21
21
|
"sensor_replace_zero": [
|
22
|
-
"sensor.power_photovoltaics"
|
23
|
-
"sensor.power_load_no_var_loads"
|
22
|
+
"sensor.power_photovoltaics"
|
24
23
|
],
|
25
24
|
"sensor_linear_interp": [
|
26
25
|
"sensor.power_photovoltaics",
|
@@ -72,6 +72,19 @@ class RetrieveHass:
|
|
72
72
|
self.logger = logger
|
73
73
|
self.get_data_from_file = get_data_from_file
|
74
74
|
|
75
|
+
def get_ha_config(self):
|
76
|
+
"""
|
77
|
+
Extract some configuration data from HA.
|
78
|
+
|
79
|
+
"""
|
80
|
+
headers = {
|
81
|
+
"Authorization": "Bearer " + self.long_lived_token,
|
82
|
+
"content-type": "application/json"
|
83
|
+
}
|
84
|
+
url = self.hass_url+"api/config"
|
85
|
+
response_config = get(url, headers=headers)
|
86
|
+
self.ha_config = response_config.json()
|
87
|
+
|
75
88
|
def get_data(self, days_list: pd.date_range, var_list: list,
|
76
89
|
minimal_response: Optional[bool] = False, significant_changes_only: Optional[bool] = False,
|
77
90
|
test_url: Optional[str] = "empty") -> None:
|
@@ -98,15 +111,17 @@ class RetrieveHass:
|
|
98
111
|
are experimental
|
99
112
|
"""
|
100
113
|
self.logger.info("Retrieve hass get data method initiated...")
|
114
|
+
headers = {
|
115
|
+
"Authorization": "Bearer " + self.long_lived_token,
|
116
|
+
"content-type": "application/json"
|
117
|
+
}
|
118
|
+
# Looping on each day from days list
|
101
119
|
self.df_final = pd.DataFrame()
|
102
120
|
x = 0 # iterate based on days
|
103
|
-
# Looping on each day from days list
|
104
121
|
for day in days_list:
|
105
122
|
for i, var in enumerate(var_list):
|
106
123
|
if test_url == "empty":
|
107
|
-
if (
|
108
|
-
self.hass_url == "http://supervisor/core/api"
|
109
|
-
): # If we are using the supervisor API
|
124
|
+
if (self.hass_url == "http://supervisor/core/api"): # If we are using the supervisor API
|
110
125
|
url = (
|
111
126
|
self.hass_url
|
112
127
|
+ "/history/period/"
|
@@ -124,16 +139,10 @@ class RetrieveHass:
|
|
124
139
|
)
|
125
140
|
if minimal_response: # A support for minimal response
|
126
141
|
url = url + "?minimal_response"
|
127
|
-
if (
|
128
|
-
significant_changes_only
|
129
|
-
): # And for signicant changes only (check the HASS restful API for more info)
|
142
|
+
if (significant_changes_only): # And for signicant changes only (check the HASS restful API for more info)
|
130
143
|
url = url + "?significant_changes_only"
|
131
144
|
else:
|
132
145
|
url = test_url
|
133
|
-
headers = {
|
134
|
-
"Authorization": "Bearer " + self.long_lived_token,
|
135
|
-
"content-type": "application/json",
|
136
|
-
}
|
137
146
|
try:
|
138
147
|
response = get(url, headers=headers)
|
139
148
|
except Exception:
|
@@ -249,7 +249,7 @@ function buildParamContainers(
|
|
249
249
|
`;
|
250
250
|
}
|
251
251
|
|
252
|
-
//
|
252
|
+
//after looping though, build and appending the parameters in the corresponding section:
|
253
253
|
//create add button (array plus) event listeners
|
254
254
|
let plus = SectionContainer.querySelectorAll(".input-plus");
|
255
255
|
plus.forEach(function (answer) {
|
@@ -407,7 +407,7 @@ function buildParamElement(
|
|
407
407
|
else {
|
408
408
|
return `
|
409
409
|
${type_specific_html}
|
410
|
-
<input class="param_input" type="${type}"
|
410
|
+
<input class="param_input" type="${type}" placeholder=${parameter_definition_object["default_value"]} value=${value} >
|
411
411
|
${type_specific_html_end}
|
412
412
|
`;
|
413
413
|
}
|
@@ -418,9 +418,9 @@ function buildParamElement(
|
|
418
418
|
if (typeof Object.values(value)[0] === "object") {
|
419
419
|
for (param of Object.values(value)) {
|
420
420
|
for (items of Object.values(param)) {
|
421
|
-
inputs += `<input class="param_input" type="${type}" value=${
|
421
|
+
inputs += `<input class="param_input" type="${type}" placeholder=${Object.values(items)[0]} value=${
|
422
422
|
Object.values(items)[0]
|
423
|
-
}
|
423
|
+
}>`;
|
424
424
|
}
|
425
425
|
inputs += `</br>`;
|
426
426
|
}
|
@@ -432,7 +432,7 @@ function buildParamElement(
|
|
432
432
|
for (param of value) {
|
433
433
|
inputs += `
|
434
434
|
${type_specific_html}
|
435
|
-
<input class="param_input" type="${type}"
|
435
|
+
<input class="param_input" type="${type}" placeholder=${parameter_definition_object["default_value"]} value=${param}>
|
436
436
|
${type_specific_html_end}
|
437
437
|
`;
|
438
438
|
}
|
@@ -549,6 +549,7 @@ function headerElement(element, param_definitions, config) {
|
|
549
549
|
//if set_use_battery, add or remove battery section (inc. params)
|
550
550
|
case "set_use_battery":
|
551
551
|
if (element.checked) {
|
552
|
+
param_container.innerHTML = "";
|
552
553
|
buildParamContainers("Battery", param_definitions["Battery"], config, [
|
553
554
|
"set_use_battery",
|
554
555
|
]);
|
@@ -288,7 +288,7 @@ def treat_runtimeparams(runtimeparams: str, params: str, retrieve_hass_conf: dic
|
|
288
288
|
else:
|
289
289
|
prediction_horizon = runtimeparams["prediction_horizon"]
|
290
290
|
params["passed_data"]["prediction_horizon"] = prediction_horizon
|
291
|
-
if
|
291
|
+
if 'soc_init' not in runtimeparams.keys():
|
292
292
|
soc_init = plant_conf['battery_target_state_of_charge']
|
293
293
|
else:
|
294
294
|
soc_init = runtimeparams["soc_init"]
|
@@ -298,25 +298,33 @@ def treat_runtimeparams(runtimeparams: str, params: str, retrieve_hass_conf: dic
|
|
298
298
|
else:
|
299
299
|
soc_final = runtimeparams["soc_final"]
|
300
300
|
params["passed_data"]["soc_final"] = soc_final
|
301
|
-
if 'operating_hours_of_each_deferrable_load' not in runtimeparams.keys():
|
302
|
-
def_total_hours = optim_conf
|
301
|
+
if 'operating_hours_of_each_deferrable_load' not in runtimeparams.keys() and 'def_total_hours' not in runtimeparams.keys():
|
302
|
+
def_total_hours = optim_conf.get('operating_hours_of_each_deferrable_load')
|
303
303
|
else:
|
304
|
-
def_total_hours = runtimeparams
|
304
|
+
def_total_hours = runtimeparams.get(
|
305
|
+
'operating_hours_of_each_deferrable_load', runtimeparams.get('def_total_hours'))
|
305
306
|
params["passed_data"]['operating_hours_of_each_deferrable_load'] = def_total_hours
|
306
|
-
if 'start_timesteps_of_each_deferrable_load' in runtimeparams.keys():
|
307
|
-
def_start_timestep =
|
308
|
-
else:
|
307
|
+
if 'start_timesteps_of_each_deferrable_load' not in runtimeparams.keys() and 'def_start_timestep' in runtimeparams.keys():
|
308
|
+
def_start_timestep = optim_conf.get('start_timesteps_of_each_deferrable_load')
|
309
|
+
else:
|
309
310
|
def_start_timestep = runtimeparams.get(
|
310
|
-
'
|
311
|
+
'start_timesteps_of_each_deferrable_load', runtimeparams.get('def_start_timestep'))
|
311
312
|
params["passed_data"]['start_timesteps_of_each_deferrable_load'] = def_start_timestep
|
312
|
-
if 'end_timesteps_of_each_deferrable_load' in runtimeparams.keys():
|
313
|
-
def_end_timestep =
|
313
|
+
if 'end_timesteps_of_each_deferrable_load' not in runtimeparams.keys() and 'def_end_timestep' not in runtimeparams.keys():
|
314
|
+
def_end_timestep = optim_conf.get('end_timesteps_of_each_deferrable_load')
|
314
315
|
else:
|
315
316
|
def_end_timestep = runtimeparams.get(
|
316
|
-
'
|
317
|
+
'end_timesteps_of_each_deferrable_load', runtimeparams.get('def_end_timestep'))
|
317
318
|
params["passed_data"]['end_timesteps_of_each_deferrable_load'] = def_end_timestep
|
318
|
-
forecast_dates = copy.deepcopy(forecast_dates)[
|
319
|
-
|
319
|
+
forecast_dates = copy.deepcopy(forecast_dates)[0:prediction_horizon]
|
320
|
+
# Load the default config
|
321
|
+
if "def_load_config" in optim_conf:
|
322
|
+
for k in range(len(optim_conf["def_load_config"])):
|
323
|
+
if "thermal_config" in optim_conf["def_load_config"][k]:
|
324
|
+
if ("heater_desired_temperatures" in runtimeparams and len(runtimeparams["heater_desired_temperatures"]) > k):
|
325
|
+
optim_conf["def_load_config"][k]["thermal_config"]["desired_temperatures"] = runtimeparams["heater_desired_temperatures"][k]
|
326
|
+
if ("heater_start_temperatures" in runtimeparams and len(runtimeparams["heater_start_temperatures"]) > k):
|
327
|
+
optim_conf["def_load_config"][k]["thermal_config"]["start_temperature"] = runtimeparams["heater_start_temperatures"][k]
|
320
328
|
else:
|
321
329
|
params["passed_data"]["prediction_horizon"] = None
|
322
330
|
params["passed_data"]["soc_init"] = None
|
@@ -357,11 +365,16 @@ def treat_runtimeparams(runtimeparams: str, params: str, retrieve_hass_conf: dic
|
|
357
365
|
params['passed_data'][forecast_key] = None
|
358
366
|
|
359
367
|
# Treat passed data for forecast model fit/predict/tune at runtime
|
360
|
-
if 'historic_days_to_retrieve' in runtimeparams.keys():
|
361
|
-
|
368
|
+
if 'historic_days_to_retrieve' not in runtimeparams.keys() and 'days_to_retrieve' not in runtimeparams.keys():
|
369
|
+
historic_days_to_retrieve = retrieve_hass_conf.get('historic_days_to_retrieve')
|
362
370
|
else:
|
363
|
-
|
364
|
-
|
371
|
+
historic_days_to_retrieve = runtimeparams.get(
|
372
|
+
'historic_days_to_retrieve', runtimeparams.get('days_to_retrieve'))
|
373
|
+
if historic_days_to_retrieve < 9:
|
374
|
+
logger.warning("warning `days_to_retrieve` is set to a value less than 9, this could cause an error with the fit")
|
375
|
+
logger.warning("setting`passed_data:days_to_retrieve` to 9 for fit/predict/tune")
|
376
|
+
historic_days_to_retrieve = 9
|
377
|
+
params["passed_data"]['historic_days_to_retrieve'] = historic_days_to_retrieve
|
365
378
|
if "model_type" not in runtimeparams.keys():
|
366
379
|
model_type = "load_forecast"
|
367
380
|
else:
|
@@ -472,50 +485,35 @@ def treat_runtimeparams(runtimeparams: str, params: str, retrieve_hass_conf: dic
|
|
472
485
|
params["passed_data"]["publish_prefix"] = publish_prefix
|
473
486
|
|
474
487
|
# Treat optimization (optim_conf) configuration parameters passed at runtime
|
475
|
-
if 'number_of_deferrable_loads' in runtimeparams.keys():
|
476
|
-
optim_conf['number_of_deferrable_loads'] = runtimeparams
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
if '
|
482
|
-
optim_conf['
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
if '
|
488
|
-
optim_conf['
|
489
|
-
|
490
|
-
optim_conf['end_timesteps_of_each_deferrable_load'] = runtimeparams['end_timesteps_of_each_deferrable_load']
|
488
|
+
if 'number_of_deferrable_loads' in runtimeparams.keys() or 'num_def_loads' in runtimeparams.keys():
|
489
|
+
optim_conf['number_of_deferrable_loads'] = runtimeparams.get(
|
490
|
+
'number_of_deferrable_loads', runtimeparams.get('num_def_loads'))
|
491
|
+
if 'nominal_power_of_deferrable_loads' in runtimeparams.keys() or 'P_deferrable_nom' in runtimeparams.keys():
|
492
|
+
optim_conf['nominal_power_of_deferrable_loads'] = runtimeparams.get(
|
493
|
+
'nominal_power_of_deferrable_loads', runtimeparams.get('P_deferrable_nom'))
|
494
|
+
if 'operating_hours_of_each_deferrable_load' in runtimeparams.keys() or 'def_total_hours' in runtimeparams.keys():
|
495
|
+
optim_conf['operating_hours_of_each_deferrable_load'] = runtimeparams.get(
|
496
|
+
'operating_hours_of_each_deferrable_load', runtimeparams.get('def_total_hours'))
|
497
|
+
if 'start_timesteps_of_each_deferrable_load' in runtimeparams.keys() or 'def_start_timestep' in runtimeparams.keys():
|
498
|
+
optim_conf['start_timesteps_of_each_deferrable_load'] = runtimeparams.get(
|
499
|
+
'start_timesteps_of_each_deferrable_load', runtimeparams.get('def_start_timestep'))
|
500
|
+
if 'end_timesteps_of_each_deferrable_load' in runtimeparams.keys() or 'def_end_timestep' in runtimeparams.keys():
|
501
|
+
optim_conf['end_timesteps_of_each_deferrable_load'] = runtimeparams.get(
|
502
|
+
'end_timesteps_of_each_deferrable_load', runtimeparams.get('def_end_timestep'))
|
491
503
|
if "def_current_state" in runtimeparams.keys():
|
492
504
|
optim_conf["def_current_state"] = [
|
493
505
|
bool(s) for s in runtimeparams["def_current_state"]]
|
494
|
-
if 'treat_deferrable_load_as_semi_cont' in runtimeparams.keys():
|
495
|
-
optim_conf['treat_deferrable_load_as_semi_cont'] = [
|
496
|
-
ast.literal_eval(str(k).capitalize())
|
497
|
-
for k in runtimeparams['treat_deferrable_load_as_semi_cont']
|
498
|
-
]
|
499
|
-
if 'treat_def_as_semi_cont' in runtimeparams.keys():
|
506
|
+
if 'treat_deferrable_load_as_semi_cont' in runtimeparams.keys() or 'treat_def_as_semi_cont' in runtimeparams.keys():
|
500
507
|
optim_conf['treat_deferrable_load_as_semi_cont'] = [
|
501
|
-
ast.literal_eval(str(k).capitalize())
|
502
|
-
|
503
|
-
|
504
|
-
if 'set_deferrable_load_single_constant' in runtimeparams.keys():
|
508
|
+
ast.literal_eval(str(k).capitalize()) for k in runtimeparams.get('treat_deferrable_load_as_semi_cont',runtimeparams.get('treat_def_as_semi_cont'))
|
509
|
+
]
|
510
|
+
if 'set_deferrable_load_single_constant' in runtimeparams.keys() or 'set_def_constant' in runtimeparams.keys():
|
505
511
|
optim_conf['set_deferrable_load_single_constant'] = [
|
506
|
-
ast.literal_eval(str(k).capitalize()) for k in runtimeparams
|
512
|
+
ast.literal_eval(str(k).capitalize()) for k in runtimeparams.get('set_deferrable_load_single_constant',runtimeparams.get('set_def_constant'))
|
507
513
|
]
|
508
|
-
if '
|
509
|
-
optim_conf['set_deferrable_load_single_constant'] = [
|
510
|
-
ast.literal_eval(str(k).capitalize()) for k in runtimeparams['set_def_constant']
|
511
|
-
]
|
512
|
-
if 'set_deferrable_startup_penalty' in runtimeparams.keys():
|
513
|
-
optim_conf['set_deferrable_startup_penalty'] = [
|
514
|
-
ast.literal_eval(str(k).capitalize()) for k in runtimeparams['set_deferrable_startup_penalty']
|
515
|
-
]
|
516
|
-
if 'def_start_penalty' in runtimeparams.keys():
|
514
|
+
if 'set_deferrable_startup_penalty' in runtimeparams.keys() or 'def_start_penalty' in runtimeparams.keys():
|
517
515
|
optim_conf['set_deferrable_startup_penalty'] = [
|
518
|
-
ast.literal_eval(str(k).capitalize()) for k in runtimeparams
|
516
|
+
ast.literal_eval(str(k).capitalize()) for k in runtimeparams.get('set_deferrable_startup_penalty',runtimeparams.get('def_start_penalty'))
|
519
517
|
]
|
520
518
|
if 'def_load_config' in runtimeparams:
|
521
519
|
optim_conf["def_load_config"] = runtimeparams['def_load_config']
|
@@ -527,9 +525,9 @@ def treat_runtimeparams(runtimeparams: str, params: str, retrieve_hass_conf: dic
|
|
527
525
|
optim_conf['weight_battery_charge'] = runtimeparams['weight_battery_charge']
|
528
526
|
|
529
527
|
# Treat retrieve data from Home Assistant (retrieve_hass_conf) configuration parameters passed at runtime
|
530
|
-
if 'optimization_time_step' in runtimeparams.keys():
|
531
|
-
retrieve_hass_conf['optimization_time_step'] = pd.to_timedelta(
|
532
|
-
|
528
|
+
if 'optimization_time_step' in runtimeparams.keys() or 'freq' in runtimeparams.keys():
|
529
|
+
retrieve_hass_conf['optimization_time_step'] = pd.to_timedelta(runtimeparams.get(
|
530
|
+
'optimization_time_step', runtimeparams.get('freq')), "minutes")
|
533
531
|
if 'continual_publish' in runtimeparams.keys():
|
534
532
|
retrieve_hass_conf['continual_publish'] = bool(
|
535
533
|
runtimeparams['continual_publish'])
|
@@ -646,9 +644,7 @@ def get_yaml_parse(params: str, logger: logging.Logger) -> Tuple[dict, dict, dic
|
|
646
644
|
return False, False, False
|
647
645
|
|
648
646
|
optim_conf = input_conf.get("optim_conf", {})
|
649
|
-
|
650
647
|
retrieve_hass_conf = input_conf.get("retrieve_hass_conf", {})
|
651
|
-
|
652
648
|
plant_conf = input_conf.get("plant_conf", {})
|
653
649
|
|
654
650
|
# Format time parameters
|
@@ -661,6 +657,70 @@ def get_yaml_parse(params: str, logger: logging.Logger) -> Tuple[dict, dict, dic
|
|
661
657
|
|
662
658
|
return retrieve_hass_conf, optim_conf, plant_conf
|
663
659
|
|
660
|
+
def get_legacy_yaml_parse(emhass_conf: dict, use_secrets: Optional[bool] = True,
|
661
|
+
params: Optional[str] = None) -> Tuple[dict, dict, dict]:
|
662
|
+
"""
|
663
|
+
Perform parsing of the config.yaml file.
|
664
|
+
|
665
|
+
:param emhass_conf: Dictionary containing the needed emhass paths
|
666
|
+
:type emhass_conf: dict
|
667
|
+
:param use_secrets: Indicate if we should use a secrets file or not.
|
668
|
+
Set to False for unit tests.
|
669
|
+
:type use_secrets: bool, optional
|
670
|
+
:param params: Configuration parameters passed from data/options.json
|
671
|
+
:type params: str
|
672
|
+
:return: A tuple with the dictionaries containing the parsed data
|
673
|
+
:rtype: tuple(dict)
|
674
|
+
|
675
|
+
"""
|
676
|
+
if params is None:
|
677
|
+
with open(emhass_conf["config_path"], 'r') as file:
|
678
|
+
input_conf = yaml.load(file, Loader=yaml.FullLoader)
|
679
|
+
else:
|
680
|
+
input_conf = json.loads(params)
|
681
|
+
if use_secrets:
|
682
|
+
if params is None:
|
683
|
+
with open(emhass_conf["config_path"].parent / 'secrets_emhass.yaml', 'r') as file: # Assume secrets and config file paths are the same
|
684
|
+
input_secrets = yaml.load(file, Loader=yaml.FullLoader)
|
685
|
+
else:
|
686
|
+
input_secrets = input_conf.pop("params_secrets", None)
|
687
|
+
|
688
|
+
if type(input_conf["retrieve_hass_conf"]) == list: # if using old config version
|
689
|
+
retrieve_hass_conf = dict(
|
690
|
+
{key: d[key] for d in input_conf["retrieve_hass_conf"] for key in d}
|
691
|
+
)
|
692
|
+
else:
|
693
|
+
retrieve_hass_conf = input_conf.get("retrieve_hass_conf", {})
|
694
|
+
|
695
|
+
if use_secrets:
|
696
|
+
retrieve_hass_conf.update(input_secrets)
|
697
|
+
else:
|
698
|
+
retrieve_hass_conf["hass_url"] = "http://supervisor/core/api"
|
699
|
+
retrieve_hass_conf["long_lived_token"] = "${SUPERVISOR_TOKEN}"
|
700
|
+
retrieve_hass_conf["time_zone"] = "Europe/Paris"
|
701
|
+
retrieve_hass_conf["lat"] = 45.83
|
702
|
+
retrieve_hass_conf["lon"] = 6.86
|
703
|
+
retrieve_hass_conf["alt"] = 4807.8
|
704
|
+
retrieve_hass_conf["freq"] = pd.to_timedelta(retrieve_hass_conf["freq"], "minutes")
|
705
|
+
retrieve_hass_conf["time_zone"] = pytz.timezone(retrieve_hass_conf["time_zone"])
|
706
|
+
|
707
|
+
if type(input_conf["optim_conf"]) == list:
|
708
|
+
optim_conf = dict({key: d[key] for d in input_conf["optim_conf"] for key in d})
|
709
|
+
else:
|
710
|
+
optim_conf = input_conf.get("optim_conf", {})
|
711
|
+
|
712
|
+
optim_conf["list_hp_periods"] = dict(
|
713
|
+
(key, d[key]) for d in optim_conf["list_hp_periods"] for key in d
|
714
|
+
)
|
715
|
+
optim_conf["delta_forecast"] = pd.Timedelta(days=optim_conf["delta_forecast"])
|
716
|
+
|
717
|
+
if type(input_conf["plant_conf"]) == list:
|
718
|
+
plant_conf = dict({key: d[key] for d in input_conf["plant_conf"] for key in d})
|
719
|
+
else:
|
720
|
+
plant_conf = input_conf.get("plant_conf", {})
|
721
|
+
|
722
|
+
return retrieve_hass_conf, optim_conf, plant_conf
|
723
|
+
|
664
724
|
|
665
725
|
def get_injection_dict(df: pd.DataFrame, plot_size: Optional[int] = 1366) -> dict:
|
666
726
|
"""
|
@@ -843,7 +903,7 @@ def build_config(emhass_conf: dict, logger: logging.Logger, defaults_path: str,
|
|
843
903
|
logger.info("Obtaining parameters from config.json:")
|
844
904
|
config.update(json.load(data))
|
845
905
|
else:
|
846
|
-
logger.info("config.json does not exist, or has not been passed")
|
906
|
+
logger.info("config.json does not exist, or has not been passed. config parameters may default to config_defaults.json")
|
847
907
|
logger.info("you may like to generate the config.json file on the configuration page")
|
848
908
|
|
849
909
|
# Check to see if legacy config_emhass.yaml was provided (default /app/config_emhass.yaml)
|
@@ -853,14 +913,14 @@ def build_config(emhass_conf: dict, logger: logging.Logger, defaults_path: str,
|
|
853
913
|
legacy_config = yaml.load(data, Loader=yaml.FullLoader)
|
854
914
|
legacy_config_parameters = build_legacy_config_params(emhass_conf,legacy_config,logger)
|
855
915
|
if type(legacy_config_parameters) is not bool:
|
856
|
-
logger.info("Obtaining parameters from config_emhass.yaml:")
|
916
|
+
logger.info("Obtaining parameters from config_emhass.yaml: (will overwrite config parameters)")
|
857
917
|
config.update(legacy_config_parameters)
|
858
918
|
|
859
919
|
return config
|
860
920
|
|
861
921
|
|
862
922
|
def build_legacy_config_params(emhass_conf: dict, legacy_config: dict,
|
863
|
-
|
923
|
+
logger: logging.Logger) -> dict:
|
864
924
|
"""
|
865
925
|
Build a config dictionary with legacy config_emhass.yaml file.
|
866
926
|
Uses the associations file to convert parameter naming conventions (to config.json/config_defaults.json).
|
@@ -911,8 +971,7 @@ def build_legacy_config_params(emhass_conf: dict, legacy_config: dict,
|
|
911
971
|
return config
|
912
972
|
# params['associations_dict'] = associations_dict
|
913
973
|
|
914
|
-
def param_to_config(param: dict,
|
915
|
-
logger: logging.Logger) -> dict:
|
974
|
+
def param_to_config(param: dict, logger: logging.Logger) -> dict:
|
916
975
|
"""
|
917
976
|
A function that extracts the parameters from param back to the config.json format.
|
918
977
|
Extracts parameters from config catagories.
|
@@ -942,7 +1001,7 @@ def param_to_config(param: dict,
|
|
942
1001
|
return return_config
|
943
1002
|
|
944
1003
|
def build_secrets(emhass_conf: dict, logger: logging.Logger, argument: Optional[dict] = {}, options_path: Optional[str] = None,
|
945
|
-
|
1004
|
+
secrets_path: Optional[str] = None, no_response: Optional[bool] = False) -> Tuple[dict, dict]:
|
946
1005
|
"""
|
947
1006
|
Retrieve and build parameters from secrets locations (ENV, ARG, Secrets file (secrets_emhass.yaml/options.json) and/or Home Assistant (via API))
|
948
1007
|
priority order (lwo to high) = Defaults (written in function), ENV, Options json file, Home Assistant API, Secrets yaml file, Arguments
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: emhass
|
3
|
-
Version: 0.11.
|
3
|
+
Version: 0.11.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
|
@@ -47,9 +47,6 @@ Requires-Dist: plotly>=5.6.0
|
|
47
47
|
</a>
|
48
48
|
<a style="text-decoration:none" href="https://github.com/davidusb-geek/emhass/actions">
|
49
49
|
<img alt="EMHASS GitHub Workflow Status" src="https://github.com/davidusb-geek/emhass/actions/workflows/publish_docker.yaml/badge.svg?event=release">
|
50
|
-
</a>
|
51
|
-
<a style="text-decoration:none" href="https://github.com/davidusb-geek/emhass-add-on/actions">
|
52
|
-
<img alt="EMHASS-Add-on GitHub Workflow Status" src="https://github.com/davidusb-geek/emhass-add-on/actions/workflows/publish_docker.yaml/badge.svg?event=release">
|
53
50
|
</a>
|
54
51
|
<a hstyle="text-decoration:none" ref="https://codecov.io/github/davidusb-geek/emhass" >
|
55
52
|
<img src="https://codecov.io/github/davidusb-geek/emhass/branch/master/graph/badge.svg?token=BW7KSCHN90"/>
|
@@ -164,7 +161,7 @@ _Note: Both EMHASS via Docker and EMHASS-Add-on contain the same Docker image. T
|
|
164
161
|
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:
|
165
162
|
```bash
|
166
163
|
# pull Docker image
|
167
|
-
docker
|
164
|
+
docker pull ghcr.io/davidusb-geek/emhass:latest
|
168
165
|
# run Docker image, mounting config.json and secrets_emhass.yaml from host
|
169
166
|
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
|
170
167
|
```
|
@@ -173,7 +170,7 @@ docker run --rm -it --restart always -p 5000:5000 --name emhass-container -v ./
|
|
173
170
|
You can also build your image locally. For this clone this repository, and build the image from the Dockerfile:
|
174
171
|
```bash
|
175
172
|
# git clone EMHASS repo
|
176
|
-
git clone
|
173
|
+
git clone https://github.com/davidusb-geek/emhass.git
|
177
174
|
# move to EMHASS directory
|
178
175
|
cd emhass
|
179
176
|
# build Docker image
|
@@ -34,10 +34,16 @@ class TestRetrieveHass(unittest.TestCase):
|
|
34
34
|
save_data_to_file = False
|
35
35
|
|
36
36
|
# Build params with default secrets (no config)
|
37
|
-
if emhass_conf['defaults_path'].exists():
|
38
|
-
|
39
|
-
|
40
|
-
|
37
|
+
if emhass_conf['defaults_path'].exists():
|
38
|
+
if get_data_from_file:
|
39
|
+
_,secrets = utils.build_secrets(emhass_conf,logger,no_response=True)
|
40
|
+
params = utils.build_params(emhass_conf,secrets,{},logger)
|
41
|
+
retrieve_hass_conf, _, _ = get_yaml_parse(params,logger)
|
42
|
+
else:
|
43
|
+
emhass_conf['secrets_path'] = root / 'secrets_emhass.yaml'
|
44
|
+
emhass_conf['config_path'] = pathlib.Path(root) / 'config_emhass.yaml'
|
45
|
+
retrieve_hass_conf, _, _ = utils.get_legacy_yaml_parse(emhass_conf)
|
46
|
+
params = None
|
41
47
|
else:
|
42
48
|
raise Exception("config_defaults. does not exist in path: "+str(emhass_conf['defaults_path'] ))
|
43
49
|
|
@@ -60,8 +66,8 @@ class TestRetrieveHass(unittest.TestCase):
|
|
60
66
|
self.rh.df_final, self.days_list, self.var_list = pickle.load(inp)
|
61
67
|
# Else obtain sensor values from HA
|
62
68
|
else:
|
63
|
-
self.days_list = get_days_list(self.retrieve_hass_conf['
|
64
|
-
self.var_list = [self.retrieve_hass_conf['
|
69
|
+
self.days_list = get_days_list(self.retrieve_hass_conf['days_to_retrieve'])
|
70
|
+
self.var_list = [self.retrieve_hass_conf['var_PV'], self.retrieve_hass_conf['var_load']]
|
65
71
|
self.rh.get_data(self.days_list, self.var_list,
|
66
72
|
minimal_response=False, significant_changes_only=False)
|
67
73
|
# Check to save updated data to file
|
@@ -67,7 +67,7 @@ class TestCommandLineUtils(unittest.TestCase):
|
|
67
67
|
self.assertTrue(params['optim_conf']['lp_solver'] == "default")
|
68
68
|
self.assertTrue(params['optim_conf']['lp_solver_path'] == "empty")
|
69
69
|
self.assertTrue(config['load_peak_hour_periods'] == {'period_hp_1': [{'start': '02:54'}, {'end': '15:24'}], 'period_hp_2': [{'start': '17:24'}, {'end': '20:24'}]})
|
70
|
-
self.assertTrue(params['retrieve_hass_conf']['sensor_replace_zero'] == ['sensor.power_photovoltaics'
|
70
|
+
self.assertTrue(params['retrieve_hass_conf']['sensor_replace_zero'] == ['sensor.power_photovoltaics'])
|
71
71
|
# Test with config.json
|
72
72
|
config = utils.build_config(emhass_conf,logger,emhass_conf['defaults_path'],emhass_conf['config_path'])
|
73
73
|
params = utils.build_params(emhass_conf,{},config,logger)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|