emhass 0.8.3__py3-none-any.whl → 0.8.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- emhass/command_line.py +3 -3
- emhass/data/cec_inverters.pbz2 +0 -0
- emhass/data/cec_modules.pbz2 +0 -0
- emhass/forecast.py +11 -7
- emhass/optimization.py +4 -4
- emhass/retrieve_hass.py +5 -5
- emhass/static/script.js +4 -12
- emhass/static/style.css +86 -45
- emhass/templates/index.html +11 -11
- emhass/templates/template.html +0 -1
- emhass/utils.py +21 -64
- emhass/web_server.py +8 -4
- {emhass-0.8.3.dist-info → emhass-0.8.5.dist-info}/METADATA +2 -4
- emhass-0.8.5.dist-info/RECORD +25 -0
- emhass-0.8.3.dist-info/RECORD +0 -23
- {emhass-0.8.3.dist-info → emhass-0.8.5.dist-info}/LICENSE +0 -0
- {emhass-0.8.3.dist-info → emhass-0.8.5.dist-info}/WHEEL +0 -0
- {emhass-0.8.3.dist-info → emhass-0.8.5.dist-info}/entry_points.txt +0 -0
- {emhass-0.8.3.dist-info → emhass-0.8.5.dist-info}/top_level.txt +0 -0
emhass/command_line.py
CHANGED
@@ -59,8 +59,8 @@ def set_input_data_dict(config_path: pathlib.Path, base_path: str, costfun: str,
|
|
59
59
|
optim_conf, plant_conf, set_type, logger)
|
60
60
|
# Define main objects
|
61
61
|
rh = RetrieveHass(retrieve_hass_conf['hass_url'], retrieve_hass_conf['long_lived_token'],
|
62
|
-
|
63
|
-
|
62
|
+
retrieve_hass_conf['freq'], retrieve_hass_conf['time_zone'],
|
63
|
+
params, base_path, logger, get_data_from_file=get_data_from_file)
|
64
64
|
fcst = Forecast(retrieve_hass_conf, optim_conf, plant_conf,
|
65
65
|
params, base_path, logger, get_data_from_file=get_data_from_file)
|
66
66
|
opt = Optimization(retrieve_hass_conf, optim_conf, plant_conf,
|
@@ -92,7 +92,7 @@ def set_input_data_dict(config_path: pathlib.Path, base_path: str, costfun: str,
|
|
92
92
|
P_PV_forecast = fcst.get_power_from_weather(df_weather)
|
93
93
|
P_load_forecast = fcst.get_load_forecast(method=optim_conf['load_forecast_method'])
|
94
94
|
if isinstance(P_load_forecast,bool) and not P_load_forecast:
|
95
|
-
logger.error("Unable to get
|
95
|
+
logger.error("Unable to get sensor power photovoltaics, or sensor power load no var loads. Check HA sensors and their daily data")
|
96
96
|
return False
|
97
97
|
df_input_data_dayahead = pd.DataFrame(np.transpose(np.vstack([P_PV_forecast.values,P_load_forecast.values])),
|
98
98
|
index=P_PV_forecast.index,
|
Binary file
|
Binary file
|
emhass/forecast.py
CHANGED
@@ -7,6 +7,8 @@ import copy
|
|
7
7
|
import logging
|
8
8
|
import json
|
9
9
|
from typing import Optional
|
10
|
+
import bz2
|
11
|
+
import pickle as cPickle
|
10
12
|
import pandas as pd
|
11
13
|
import numpy as np
|
12
14
|
from datetime import datetime, timedelta
|
@@ -21,7 +23,7 @@ from pvlib.irradiance import disc
|
|
21
23
|
|
22
24
|
from emhass.retrieve_hass import RetrieveHass
|
23
25
|
from emhass.machine_learning_forecaster import MLForecaster
|
24
|
-
from emhass.utils import get_days_list
|
26
|
+
from emhass.utils import get_days_list, get_root
|
25
27
|
|
26
28
|
|
27
29
|
class Forecast(object):
|
@@ -133,7 +135,7 @@ class Forecast(object):
|
|
133
135
|
self.time_zone = self.retrieve_hass_conf['time_zone']
|
134
136
|
self.method_ts_round = self.retrieve_hass_conf['method_ts_round']
|
135
137
|
self.timeStep = self.freq.seconds/3600 # in hours
|
136
|
-
self.time_delta = pd.to_timedelta(opt_time_delta, "hours")
|
138
|
+
self.time_delta = pd.to_timedelta(opt_time_delta, "hours")
|
137
139
|
self.var_PV = self.retrieve_hass_conf['var_PV']
|
138
140
|
self.var_load = self.retrieve_hass_conf['var_load']
|
139
141
|
self.var_load_new = self.var_load+'_positive'
|
@@ -159,7 +161,7 @@ class Forecast(object):
|
|
159
161
|
self.end_forecast = (self.start_forecast + self.optim_conf['delta_forecast']).replace(microsecond=0)
|
160
162
|
self.forecast_dates = pd.date_range(start=self.start_forecast,
|
161
163
|
end=self.end_forecast-self.freq,
|
162
|
-
freq=self.freq).round(self.freq, ambiguous='infer', nonexistent=
|
164
|
+
freq=self.freq).round(self.freq, ambiguous='infer', nonexistent='shift_forward')
|
163
165
|
if params is not None:
|
164
166
|
if 'prediction_horizon' in list(self.params['passed_data'].keys()):
|
165
167
|
if self.params['passed_data']['prediction_horizon'] is not None:
|
@@ -184,7 +186,7 @@ class Forecast(object):
|
|
184
186
|
freq_scrap = pd.to_timedelta(60, "minutes") # The scrapping time step is 60min
|
185
187
|
forecast_dates_scrap = pd.date_range(start=self.start_forecast,
|
186
188
|
end=self.end_forecast-freq_scrap,
|
187
|
-
freq=freq_scrap).round(freq_scrap, ambiguous='infer', nonexistent=
|
189
|
+
freq=freq_scrap).round(freq_scrap, ambiguous='infer', nonexistent='shift_forward')
|
188
190
|
# Using the clearoutside webpage
|
189
191
|
response = get("https://clearoutside.com/forecast/"+str(round(self.lat, 2))+"/"+str(round(self.lon, 2))+"?desktop=true")
|
190
192
|
'''import bz2 # Uncomment to save a serialized data for tests
|
@@ -412,8 +414,10 @@ class Forecast(object):
|
|
412
414
|
# Setting the main parameters of the PV plant
|
413
415
|
location = Location(latitude=self.lat, longitude=self.lon)
|
414
416
|
temp_params = TEMPERATURE_MODEL_PARAMETERS['sapm']['close_mount_glass_glass']
|
415
|
-
cec_modules =
|
416
|
-
|
417
|
+
cec_modules = bz2.BZ2File(get_root(__file__, num_parent=2) / 'emhass/data/cec_modules.pbz2', "rb")
|
418
|
+
cec_modules = cPickle.load(cec_modules)
|
419
|
+
cec_inverters = bz2.BZ2File(get_root(__file__, num_parent=2) / 'emhass/data/cec_inverters.pbz2', "rb")
|
420
|
+
cec_inverters = cPickle.load(cec_inverters)
|
417
421
|
if type(self.plant_conf['module_model']) == list:
|
418
422
|
P_PV_forecast = pd.Series(0, index=df_weather.index)
|
419
423
|
for i in range(len(self.plant_conf['module_model'])):
|
@@ -476,7 +480,7 @@ class Forecast(object):
|
|
476
480
|
end_forecast_csv = (start_forecast_csv + self.optim_conf['delta_forecast']).replace(microsecond=0)
|
477
481
|
forecast_dates_csv = pd.date_range(start=start_forecast_csv,
|
478
482
|
end=end_forecast_csv+timedelta(days=timedelta_days)-self.freq,
|
479
|
-
freq=self.freq).round(self.freq, ambiguous='infer', nonexistent=
|
483
|
+
freq=self.freq).round(self.freq, ambiguous='infer', nonexistent='shift_forward')
|
480
484
|
if self.params is not None:
|
481
485
|
if 'prediction_horizon' in list(self.params['passed_data'].keys()):
|
482
486
|
if self.params['passed_data']['prediction_horizon'] is not None:
|
emhass/optimization.py
CHANGED
@@ -162,10 +162,10 @@ class Optimization:
|
|
162
162
|
|
163
163
|
## Add decision variables
|
164
164
|
P_grid_neg = {(i):plp.LpVariable(cat='Continuous',
|
165
|
-
lowBound=-self.plant_conf['
|
165
|
+
lowBound=-self.plant_conf['P_to_grid_max'], upBound=0,
|
166
166
|
name="P_grid_neg{}".format(i)) for i in set_I}
|
167
167
|
P_grid_pos = {(i):plp.LpVariable(cat='Continuous',
|
168
|
-
lowBound=0, upBound=self.plant_conf['
|
168
|
+
lowBound=0, upBound=self.plant_conf['P_from_grid_max'],
|
169
169
|
name="P_grid_pos{}".format(i)) for i in set_I}
|
170
170
|
P_deferrable = []
|
171
171
|
P_def_bin1 = []
|
@@ -267,13 +267,13 @@ class Optimization:
|
|
267
267
|
# Avoid injecting and consuming from grid at the same time
|
268
268
|
constraints.update({"constraint_pgridpos_{}".format(i) :
|
269
269
|
plp.LpConstraint(
|
270
|
-
e = P_grid_pos[i] - self.plant_conf['
|
270
|
+
e = P_grid_pos[i] - self.plant_conf['P_from_grid_max']*D[i],
|
271
271
|
sense = plp.LpConstraintLE,
|
272
272
|
rhs = 0)
|
273
273
|
for i in set_I})
|
274
274
|
constraints.update({"constraint_pgridneg_{}".format(i) :
|
275
275
|
plp.LpConstraint(
|
276
|
-
e = -P_grid_neg[i] - self.plant_conf['
|
276
|
+
e = -P_grid_neg[i] - self.plant_conf['P_to_grid_max']*(1-D[i]),
|
277
277
|
sense = plp.LpConstraintLE,
|
278
278
|
rhs = 0)
|
279
279
|
for i in set_I})
|
emhass/retrieve_hass.py
CHANGED
@@ -133,23 +133,23 @@ class RetrieveHass:
|
|
133
133
|
try: # Sometimes when there are connection problems we need to catch empty retrieved json
|
134
134
|
data = response.json()[0]
|
135
135
|
except IndexError:
|
136
|
-
if x
|
136
|
+
if x == 0:
|
137
137
|
self.logger.error("The retrieved JSON is empty, A sensor:" + var + " may have 0 days of history or passed sensor may not be correct")
|
138
138
|
else:
|
139
|
-
self.logger.error("The retrieved JSON is empty, days_to_retrieve may be larger than the recorded history of sensor:" + var + "
|
139
|
+
self.logger.error("The retrieved JSON is empty for day:"+ str(day) +", days_to_retrieve may be larger than the recorded history of sensor:" + var + " (check your recorder settings)")
|
140
140
|
return False
|
141
141
|
df_raw = pd.DataFrame.from_dict(data)
|
142
142
|
if len(df_raw) == 0:
|
143
|
-
if x
|
143
|
+
if x == 0:
|
144
144
|
self.logger.error("The retrieved Dataframe is empty, A sensor:" + var + " may have 0 days of history or passed sensor may not be correct")
|
145
145
|
else:
|
146
|
-
self.logger.error("Retrieved empty Dataframe, days_to_retrieve may be larger than the recorded history of sensor:" + var + "
|
146
|
+
self.logger.error("Retrieved empty Dataframe for day:"+ str(day) +", days_to_retrieve may be larger than the recorded history of sensor:" + var + " (check your recorder settings)")
|
147
147
|
return False
|
148
148
|
if i == 0: # Defining the DataFrame container
|
149
149
|
from_date = pd.to_datetime(df_raw['last_changed'], format="ISO8601").min()
|
150
150
|
to_date = pd.to_datetime(df_raw['last_changed'], format="ISO8601").max()
|
151
151
|
ts = pd.to_datetime(pd.date_range(start=from_date, end=to_date, freq=self.freq),
|
152
|
-
format='%Y-%d-%m %H:%M').round(self.freq, ambiguous='infer', nonexistent=
|
152
|
+
format='%Y-%d-%m %H:%M').round(self.freq, ambiguous='infer', nonexistent='shift_forward')
|
153
153
|
df_day = pd.DataFrame(index = ts)
|
154
154
|
# Caution with undefined string data: unknown, unavailable, etc.
|
155
155
|
df_tp = df_raw.copy()[['state']].replace(
|
emhass/static/script.js
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
//on page reload get saved data
|
2
2
|
window.onload = async function () {
|
3
|
+
|
3
4
|
pageSelected = await loadBasicOrAdvanced();
|
4
5
|
|
5
6
|
//add listener for basic and advanced html switch
|
@@ -97,19 +98,10 @@ function SwitchBasicOrAdvanced() {
|
|
97
98
|
}
|
98
99
|
}
|
99
100
|
|
100
|
-
//set current url
|
101
|
-
function getHTMLURL() {
|
102
|
-
var currentUrl
|
103
|
-
if (window.location) {
|
104
|
-
currentUrl = window.location.href; //get current url to append
|
105
|
-
}
|
106
|
-
else { currentUrl = "" }
|
107
|
-
return currentUrl
|
108
|
-
}
|
109
101
|
|
110
102
|
//get html data from basic.html or advanced.html
|
111
103
|
async function getHTMLData(htmlFile) {
|
112
|
-
const response = await fetch(
|
104
|
+
const response = await fetch(`static/` + htmlFile);
|
113
105
|
blob = await response.blob(); //get data blob
|
114
106
|
htmlTemplateData = await new Response(blob).text(); //obtain html from blob
|
115
107
|
return await htmlTemplateData;
|
@@ -125,7 +117,7 @@ async function formAction(action, page) {
|
|
125
117
|
|
126
118
|
if (data !== 0) { //don't run if there is an error in the input (box/list) Json data
|
127
119
|
showChangeStatus("loading", {}); // show loading div for status
|
128
|
-
const response = await fetch(
|
120
|
+
const response = await fetch(`action/` + action, {
|
129
121
|
//fetch data from webserver.py
|
130
122
|
method: "POST",
|
131
123
|
headers: {
|
@@ -183,7 +175,7 @@ async function showChangeStatus(status, logJson) {
|
|
183
175
|
async function getTemplate() {
|
184
176
|
//fetch data from webserver.py
|
185
177
|
let htmlTemplateData = "";
|
186
|
-
response = await fetch(
|
178
|
+
response = await fetch(`template/table-template`, {
|
187
179
|
method: "GET",
|
188
180
|
});
|
189
181
|
blob = await response.blob(); //get data blob
|
emhass/static/style.css
CHANGED
@@ -543,17 +543,23 @@ template {
|
|
543
543
|
/* feather icons css
|
544
544
|
https://github.com/feathericons/feather */
|
545
545
|
.feather {
|
546
|
-
width: 27px;
|
547
|
-
height: 27px;
|
548
|
-
stroke: currentColor;
|
549
|
-
stroke-width: 2;
|
550
|
-
stroke-linecap: round;
|
551
|
-
stroke-linejoin: round;
|
552
|
-
fill: none;
|
553
|
-
|
554
|
-
|
546
|
+
width: 27px !important;
|
547
|
+
height: 27px !important;
|
548
|
+
stroke: currentColor !important;
|
549
|
+
stroke-width: 2 !important;
|
550
|
+
stroke-linecap: round !important;
|
551
|
+
stroke-linejoin: round !important;
|
552
|
+
fill: none !important;
|
553
|
+
filter: drop-shadow(#282928 .2px .2px) !important;
|
554
|
+
-webkit-text-size-adjust: none !important;
|
555
|
+
-ms-text-size-adjust: none !important;
|
556
|
+
|
555
557
|
}
|
556
558
|
|
559
|
+
/* feather icons no background color */
|
560
|
+
#top-links a {
|
561
|
+
background: none !important;
|
562
|
+
}
|
557
563
|
|
558
564
|
/* -------------- */
|
559
565
|
|
@@ -580,7 +586,8 @@ select {
|
|
580
586
|
}
|
581
587
|
|
582
588
|
|
583
|
-
.alert,
|
589
|
+
.alert,
|
590
|
+
.info {
|
584
591
|
max-width: 50%;
|
585
592
|
}
|
586
593
|
|
@@ -594,7 +601,7 @@ h2 {
|
|
594
601
|
margin-bottom: .3em;
|
595
602
|
}
|
596
603
|
|
597
|
-
.table_div h4{
|
604
|
+
.table_div h4 {
|
598
605
|
margin-top: .5em;
|
599
606
|
}
|
600
607
|
|
@@ -677,17 +684,49 @@ button {
|
|
677
684
|
|
678
685
|
th {
|
679
686
|
padding: 5px 7.77px;
|
687
|
+
text-align: center;
|
680
688
|
}
|
681
689
|
|
682
|
-
.mystyle tr:nth-child(even)
|
690
|
+
.mystyle tr:nth-child(even) td,
|
691
|
+
th {
|
683
692
|
background: #e1e1e1;
|
684
693
|
}
|
685
694
|
|
686
|
-
.mystyle tr:
|
687
|
-
background:
|
695
|
+
.mystyle tr:nth-child(odd) td {
|
696
|
+
background: white;
|
697
|
+
}
|
698
|
+
|
699
|
+
.mystyle tr:hover td {
|
700
|
+
background-color: silver;
|
688
701
|
cursor: pointer;
|
689
702
|
}
|
690
703
|
|
704
|
+
th:last-child {
|
705
|
+
border-top-right-radius: 7px;
|
706
|
+
}
|
707
|
+
|
708
|
+
th:first-child {
|
709
|
+
border-top-left-radius: 7px;
|
710
|
+
}
|
711
|
+
|
712
|
+
tr:last-child td:first-child {
|
713
|
+
border-bottom-left-radius: 7px;
|
714
|
+
}
|
715
|
+
|
716
|
+
tr:last-child td:last-child {
|
717
|
+
border-bottom-right-radius: 7px;
|
718
|
+
}
|
719
|
+
|
720
|
+
tr:hover td:first-child {
|
721
|
+
border-top-left-radius: 7px;
|
722
|
+
border-bottom-left-radius: 7px;
|
723
|
+
}
|
724
|
+
|
725
|
+
tr:hover td:last-child {
|
726
|
+
border-top-right-radius: 7px;
|
727
|
+
border-bottom-right-radius: 7px;
|
728
|
+
}
|
729
|
+
|
691
730
|
#top-links {
|
692
731
|
display: flex;
|
693
732
|
position: absolute;
|
@@ -789,14 +828,20 @@ th {
|
|
789
828
|
}
|
790
829
|
|
791
830
|
/* Basic and Advanced fade transitions */
|
792
|
-
.TabSelection,
|
831
|
+
.TabSelection,
|
832
|
+
#advance,
|
833
|
+
#basic,
|
834
|
+
button,
|
835
|
+
select,
|
836
|
+
.info {
|
793
837
|
animation-name: fadeInOpacity;
|
794
838
|
animation-iteration-count: 1;
|
795
839
|
animation-timing-function: ease-in-out;
|
796
840
|
animation-duration: .3s;
|
797
841
|
}
|
798
842
|
|
799
|
-
.input-list,
|
843
|
+
.input-list,
|
844
|
+
.input-box {
|
800
845
|
animation-name: fadeInOpacity;
|
801
846
|
animation-iteration-count: 1;
|
802
847
|
animation-timing-function: ease-in-out;
|
@@ -938,7 +983,8 @@ th {
|
|
938
983
|
display: none !important;
|
939
984
|
}
|
940
985
|
|
941
|
-
.info,
|
986
|
+
.info,
|
987
|
+
.alert {
|
942
988
|
max-width: 100%;
|
943
989
|
}
|
944
990
|
}
|
@@ -951,15 +997,17 @@ th {
|
|
951
997
|
}
|
952
998
|
|
953
999
|
img,
|
954
|
-
figure,
|
1000
|
+
figure,
|
1001
|
+
svg.main-svg {
|
955
1002
|
-webkit-filter: invert(.82);
|
956
1003
|
filter: invert(.82);
|
957
1004
|
}
|
958
|
-
|
959
|
-
figure,
|
960
|
-
|
961
|
-
|
962
|
-
|
1005
|
+
|
1006
|
+
figure,
|
1007
|
+
svg.main-svg {
|
1008
|
+
border-color: #181818;
|
1009
|
+
border-style: solid;
|
1010
|
+
border-width: 1px;
|
963
1011
|
}
|
964
1012
|
|
965
1013
|
button,
|
@@ -1002,10 +1050,12 @@ th {
|
|
1002
1050
|
.modebar-btn svg path {
|
1003
1051
|
fill: #111 !important;
|
1004
1052
|
}
|
1053
|
+
|
1005
1054
|
.modebar-btn svg {
|
1006
|
-
filter:invert(100%) sepia(64%) saturate(2%) hue-rotate(294deg) brightness(85%) contrast(93%) !important
|
1055
|
+
filter: invert(100%) sepia(64%) saturate(2%) hue-rotate(294deg) brightness(85%) contrast(93%) !important
|
1007
1056
|
}
|
1008
|
-
|
1057
|
+
|
1058
|
+
.modebar-btn--logo svg {
|
1009
1059
|
filter: None !important;
|
1010
1060
|
/* filter: invert(100%) saturate(100%) brightness(87%) contrast(100%) !important */
|
1011
1061
|
}
|
@@ -1018,37 +1068,28 @@ th {
|
|
1018
1068
|
color: #e1e1e1;
|
1019
1069
|
}
|
1020
1070
|
|
1021
|
-
tr
|
1022
|
-
|
1023
|
-
background-color: #3d3d3d;
|
1071
|
+
.mystyle tr {
|
1072
|
+
background: none;
|
1024
1073
|
}
|
1025
1074
|
|
1026
|
-
tr:nth-child(
|
1027
|
-
|
1028
|
-
background
|
1075
|
+
.mystyle tr:nth-child(even) td,
|
1076
|
+
th {
|
1077
|
+
background: #282928;
|
1029
1078
|
}
|
1030
1079
|
|
1031
|
-
tr:nth-child(
|
1032
|
-
|
1033
|
-
background-color: #181818;
|
1080
|
+
.mystyle tr:nth-child(odd) td {
|
1081
|
+
background: #111111;
|
1034
1082
|
}
|
1035
1083
|
|
1036
|
-
tr:hover td
|
1037
|
-
th {
|
1084
|
+
.mystyle tr:hover td {
|
1038
1085
|
background-color: #3f3f3f;
|
1039
1086
|
}
|
1040
1087
|
|
1041
|
-
|
1042
|
-
th, td:first-child, td:last-child
|
1043
|
-
{
|
1044
|
-
border-radius: 7px
|
1045
|
-
}
|
1046
|
-
|
1047
|
-
.modebar-group{
|
1088
|
+
.modebar-group {
|
1048
1089
|
background-color: #0000 !important;
|
1049
1090
|
}
|
1050
1091
|
|
1051
|
-
.modebar-btn{
|
1092
|
+
.modebar-btn {
|
1052
1093
|
background: #3f3f3f;
|
1053
1094
|
}
|
1054
1095
|
|
@@ -1070,4 +1111,4 @@ th {
|
|
1070
1111
|
}
|
1071
1112
|
|
1072
1113
|
|
1073
|
-
}
|
1114
|
+
}
|
emhass/templates/index.html
CHANGED
@@ -4,9 +4,9 @@
|
|
4
4
|
<head>
|
5
5
|
<title>EMHASS: Energy Management Optimization for Home Assistant</title>
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
7
|
-
<link rel="stylesheet" type="text/css" href="
|
8
|
-
<link rel="icon" href="
|
9
|
-
<script src="
|
7
|
+
<link rel="stylesheet" type="text/css" href="static/style.css?version=1"> <!-- change version on stylesheet changes -->
|
8
|
+
<link rel="icon" type="image/x-icon" href="static/img/emhass_logo_short.svg">
|
9
|
+
<script src="static/script.js"></script>
|
10
10
|
</head>
|
11
11
|
|
12
12
|
<body style="margin: auto; align-items:center; text-align:center;">
|
@@ -15,24 +15,24 @@
|
|
15
15
|
<!-- Icons by feather https://github.com/feathericons/feather -->
|
16
16
|
<div id="top-links">
|
17
17
|
<!-- advanced or basic page switch -->
|
18
|
-
<a id="basicOrAdvanced" style="margin-right:
|
18
|
+
<a id="basicOrAdvanced" style="margin-right: 24px; cursor: pointer; z-index: 1">
|
19
19
|
<svg class="feather">
|
20
|
-
<use href="
|
20
|
+
<use class="feather" href="static/img/feather-sprite.svg#tool" />
|
21
21
|
</svg>
|
22
22
|
</a>
|
23
23
|
<a href="https://emhass.readthedocs.io/en/latest/">
|
24
|
-
<svg class="feather"
|
25
|
-
<use href="
|
24
|
+
<svg class="feather" style="margin-right: 12px;";>
|
25
|
+
<use class="feather" href="static/img/feather-sprite.svg#book" />
|
26
26
|
</svg>
|
27
27
|
</a>
|
28
|
-
<a href="https://github.com/davidusb-geek/emhass">
|
29
|
-
<svg style="margin-right: 0px;"
|
30
|
-
<use href="
|
28
|
+
<a href="https://github.com/davidusb-geek/emhass" target="_blank" rel="noopener noreferrer">
|
29
|
+
<svg class="feather" style="margin-right: 0px;" >
|
30
|
+
<use class="feather" href="static/img/feather-sprite.svg#git-branch" />
|
31
31
|
</svg>
|
32
32
|
</a>
|
33
33
|
</div>
|
34
34
|
<!-- Title -->
|
35
|
-
<img src="
|
35
|
+
<img src="static/img/emhass_icon.png" alt="">
|
36
36
|
<h2>EMHASS: Energy Management Optimization for Home Assistant</h2>
|
37
37
|
</div>
|
38
38
|
|
emhass/templates/template.html
CHANGED
emhass/utils.py
CHANGED
@@ -96,7 +96,7 @@ def get_forecast_dates(freq: int, delta_forecast: int,
|
|
96
96
|
end_forecast = (start_forecast + pd.Timedelta(days=delta_forecast)).replace(microsecond=0)
|
97
97
|
forecast_dates = pd.date_range(start=start_forecast,
|
98
98
|
end=end_forecast+timedelta(days=timedelta_days)-freq,
|
99
|
-
freq=freq).round(freq)
|
99
|
+
freq=freq).round(freq, ambiguous='infer', nonexistent='shift_forward')
|
100
100
|
return forecast_dates
|
101
101
|
|
102
102
|
def treat_runtimeparams(runtimeparams: str, params: str, retrieve_hass_conf: dict, optim_conf: dict, plant_conf: dict,
|
@@ -208,62 +208,23 @@ def treat_runtimeparams(runtimeparams: str, params: str, retrieve_hass_conf: dic
|
|
208
208
|
params['passed_data']['alpha'] = None
|
209
209
|
params['passed_data']['beta'] = None
|
210
210
|
# Treat passed forecast data lists
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
211
|
+
list_forecast_key = ['pv_power_forecast', 'load_power_forecast', 'load_cost_forecast', 'prod_price_forecast']
|
212
|
+
forecast_methods = ['weather_forecast_method', 'load_forecast_method', 'load_cost_forecast_method', 'prod_price_forecast_method']
|
213
|
+
for method, forecast_key in enumerate(list_forecast_key):
|
214
|
+
if forecast_key in runtimeparams.keys():
|
215
|
+
if type(runtimeparams[forecast_key]) == list and len(runtimeparams[forecast_key]) >= len(forecast_dates):
|
216
|
+
params['passed_data'][forecast_key] = runtimeparams[forecast_key]
|
217
|
+
optim_conf[forecast_methods[method]] = 'list'
|
218
|
+
else:
|
219
|
+
logger.error(f"ERROR: The passed data is either not a list or the length is not correct, length should be {str(len(forecast_dates))}")
|
220
|
+
logger.error(f"Passed type is {str(type(runtimeparams[forecast_key]))} and length is {str(len(runtimeparams[forecast_key]))}")
|
221
|
+
list_non_digits = [x for x in runtimeparams[forecast_key] if not (isinstance(x, int) or isinstance(x, float))]
|
222
|
+
if len(list_non_digits) > 0:
|
223
|
+
logger.warning(f"There are non numeric values on the passed data for {forecast_key}, check for missing values (nans, null, etc)")
|
224
|
+
for x in list_non_digits:
|
225
|
+
logger.warning(f"This value in {forecast_key} was detected as non digits: {str(x)}")
|
215
226
|
else:
|
216
|
-
|
217
|
-
logger.error("Passed type is "+str(type(runtimeparams['pv_power_forecast']))+" and length is "+str(len(runtimeparams['pv_power_forecast'])))
|
218
|
-
list_non_digits = [x for x in runtimeparams['pv_power_forecast'] if not (isinstance(x, int) or isinstance(x, float))]
|
219
|
-
if len(list_non_digits) > 0:
|
220
|
-
logger.warning("There are non numeric values on the passed data for pv_power_forecast, check for missing values (nans, null, etc)")
|
221
|
-
for x in list_non_digits:
|
222
|
-
logger.warning("This value in pv_power_forecast was detected as non digits: "+str(x))
|
223
|
-
else:
|
224
|
-
params['passed_data']['pv_power_forecast'] = None
|
225
|
-
if 'load_power_forecast' in runtimeparams.keys():
|
226
|
-
if type(runtimeparams['load_power_forecast']) == list and len(runtimeparams['load_power_forecast']) >= len(forecast_dates):
|
227
|
-
params['passed_data']['load_power_forecast'] = runtimeparams['load_power_forecast']
|
228
|
-
optim_conf['load_forecast_method'] = 'list'
|
229
|
-
else:
|
230
|
-
logger.error("ERROR: The passed data is either not a list or the length is not correct, length should be "+str(len(forecast_dates)))
|
231
|
-
logger.error("Passed type is "+str(type(runtimeparams['load_power_forecast']))+" and length is "+str(len(runtimeparams['load_power_forecast'])))
|
232
|
-
list_non_digits = [x for x in runtimeparams['load_power_forecast'] if not (isinstance(x, int) or isinstance(x, float))]
|
233
|
-
if len(list_non_digits) > 0:
|
234
|
-
logger.warning("There are non numeric values on the passed data for load_power_forecast, check for missing values (nans, null, etc)")
|
235
|
-
for x in list_non_digits:
|
236
|
-
logger.warning("This value in load_power_forecast was detected as non digits: "+str(x))
|
237
|
-
else:
|
238
|
-
params['passed_data']['load_power_forecast'] = None
|
239
|
-
if 'load_cost_forecast' in runtimeparams.keys():
|
240
|
-
if type(runtimeparams['load_cost_forecast']) == list and len(runtimeparams['load_cost_forecast']) >= len(forecast_dates):
|
241
|
-
params['passed_data']['load_cost_forecast'] = runtimeparams['load_cost_forecast']
|
242
|
-
optim_conf['load_cost_forecast_method'] = 'list'
|
243
|
-
else:
|
244
|
-
logger.error("ERROR: The passed data is either not a list or the length is not correct, length should be "+str(len(forecast_dates)))
|
245
|
-
logger.error("Passed type is "+str(type(runtimeparams['load_cost_forecast']))+" and length is "+str(len(runtimeparams['load_cost_forecast'])))
|
246
|
-
list_non_digits = [x for x in runtimeparams['load_cost_forecast'] if not (isinstance(x, int) or isinstance(x, float))]
|
247
|
-
if len(list_non_digits) > 0:
|
248
|
-
logger.warning("There are non numeric values on the passed data or load_cost_forecast, check for missing values (nans, null, etc)")
|
249
|
-
for x in list_non_digits:
|
250
|
-
logger.warning("This value in load_cost_forecast was detected as non digits: "+str(x))
|
251
|
-
else:
|
252
|
-
params['passed_data']['load_cost_forecast'] = None
|
253
|
-
if 'prod_price_forecast' in runtimeparams.keys():
|
254
|
-
if type(runtimeparams['prod_price_forecast']) == list and len(runtimeparams['prod_price_forecast']) >= len(forecast_dates):
|
255
|
-
params['passed_data']['prod_price_forecast'] = runtimeparams['prod_price_forecast']
|
256
|
-
optim_conf['prod_price_forecast_method'] = 'list'
|
257
|
-
else:
|
258
|
-
logger.error("ERROR: The passed data is either not a list or the length is not correct, length should be "+str(len(forecast_dates)))
|
259
|
-
logger.error("Passed type is "+str(type(runtimeparams['prod_price_forecast']))+" and length is "+str(len(runtimeparams['prod_price_forecast'])))
|
260
|
-
list_non_digits = [x for x in runtimeparams['prod_price_forecast'] if not (isinstance(x, int) or isinstance(x, float))]
|
261
|
-
if len(list_non_digits) > 0:
|
262
|
-
logger.warning("There are non numeric values on the passed data for prod_price_forecast, check for missing values (nans, null, etc)")
|
263
|
-
for x in list_non_digits:
|
264
|
-
logger.warning("This value in prod_price_forecast was detected as non digits: "+str(x))
|
265
|
-
else:
|
266
|
-
params['passed_data']['prod_price_forecast'] = None
|
227
|
+
params['passed_data'][forecast_key] = None
|
267
228
|
# Treat passed data for forecast model fit/predict/tune at runtime
|
268
229
|
if 'days_to_retrieve' not in runtimeparams.keys():
|
269
230
|
days_to_retrieve = 9
|
@@ -634,8 +595,9 @@ def build_params(params: dict, params_secrets: dict, options: dict, addon: int,
|
|
634
595
|
params['optim_conf']['def_start_timestep'] = [i['start_timesteps_of_each_deferrable_load'] for i in options.get('list_start_timesteps_of_each_deferrable_load')]
|
635
596
|
if options.get('list_end_timesteps_of_each_deferrable_load',None) != None:
|
636
597
|
params['optim_conf']['def_end_timestep'] = [i['end_timesteps_of_each_deferrable_load'] for i in options.get('list_end_timesteps_of_each_deferrable_load')]
|
637
|
-
# Updating variables in
|
638
|
-
|
598
|
+
# Updating variables in plant_conf
|
599
|
+
params['plant_conf']['P_from_grid_max'] = options.get('maximum_power_from_grid',params['plant_conf']['P_from_grid_max'])
|
600
|
+
params['plant_conf']['P_to_grid_max'] = options.get('maximum_power_to_grid',params['plant_conf']['P_to_grid_max'])
|
639
601
|
if options.get('list_pv_module_model',None) != None:
|
640
602
|
params['plant_conf']['module_model'] = [i['pv_module_model'] for i in options.get('list_pv_module_model')]
|
641
603
|
if options.get('list_pv_inverter_model',None) != None:
|
@@ -656,8 +618,7 @@ def build_params(params: dict, params_secrets: dict, options: dict, addon: int,
|
|
656
618
|
params['plant_conf']['SOCmin'] = options.get('battery_minimum_state_of_charge',params['plant_conf']['SOCmin'])
|
657
619
|
params['plant_conf']['SOCmax'] = options.get('battery_maximum_state_of_charge',params['plant_conf']['SOCmax'])
|
658
620
|
params['plant_conf']['SOCtarget'] = options.get('battery_target_state_of_charge',params['plant_conf']['SOCtarget'])
|
659
|
-
|
660
|
-
# Check parameter lists have the same amounts as deferrable loads
|
621
|
+
# Check parameter lists have the same amounts as deferrable loads
|
661
622
|
# If not, set defaults it fill in gaps
|
662
623
|
if params['optim_conf']['num_def_loads'] is not len(params['optim_conf']['def_start_timestep']):
|
663
624
|
logger.warning("def_start_timestep / list_start_timesteps_of_each_deferrable_load does not match number in num_def_loads, adding default values to parameter")
|
@@ -683,10 +644,6 @@ def build_params(params: dict, params_secrets: dict, options: dict, addon: int,
|
|
683
644
|
logger.warning("P_deferrable_nom / list_nominal_power_of_deferrable_loads does not match number in num_def_loads, adding default values to parameter")
|
684
645
|
for x in range(len(params['optim_conf']['P_deferrable_nom']), params['optim_conf']['num_def_loads']):
|
685
646
|
params['optim_conf']['P_deferrable_nom'].append(0)
|
686
|
-
if params['optim_conf']['num_def_loads'] is not len(params['optim_conf']['list_hp_periods']):
|
687
|
-
logger.warning("list_hp_periods / list_peak_hours_periods_(start&end)_hours does not match number in num_def_loads, adding default values to parameter")
|
688
|
-
for x in range(len(params['optim_conf']['list_hp_periods']), params['optim_conf']['num_def_loads']):
|
689
|
-
params['optim_conf']['list_hp_periods'].append({'period_hp_'+str(x+1):[{'start':'02:54'},{'end':'20:24'}]})
|
690
647
|
# days_to_retrieve should be no less then 2
|
691
648
|
if params['retrieve_hass_conf']['days_to_retrieve'] < 2:
|
692
649
|
params['retrieve_hass_conf']['days_to_retrieve'] = 2
|
emhass/web_server.py
CHANGED
@@ -69,8 +69,13 @@ def index():
|
|
69
69
|
else:
|
70
70
|
app.logger.warning("The data container dictionary is empty... Please launch an optimization task")
|
71
71
|
injection_dict={}
|
72
|
-
|
73
|
-
|
72
|
+
|
73
|
+
# replace {{basename}} in html template html with path root
|
74
|
+
# basename = request.headers.get("X-Ingress-Path", "")
|
75
|
+
# return make_response(template.render(injection_dict=injection_dict, basename=basename))
|
76
|
+
|
77
|
+
return make_response(template.render(injection_dict=injection_dict))
|
78
|
+
|
74
79
|
|
75
80
|
#get actions
|
76
81
|
@app.route('/template/<action_name>', methods=['GET'])
|
@@ -86,8 +91,7 @@ def template_action(action_name):
|
|
86
91
|
else:
|
87
92
|
app.logger.warning("The data container dictionary is empty... Please launch an optimization task")
|
88
93
|
injection_dict={}
|
89
|
-
|
90
|
-
return make_response(template.render(injection_dict=injection_dict, basename=basename))
|
94
|
+
return make_response(template.render(injection_dict=injection_dict))
|
91
95
|
|
92
96
|
#post actions
|
93
97
|
@app.route('/action/<action_name>', methods=['POST'])
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: emhass
|
3
|
-
Version: 0.8.
|
3
|
+
Version: 0.8.5
|
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
|
@@ -12,7 +12,7 @@ Classifier: Topic :: Software Development :: Build Tools
|
|
12
12
|
Classifier: License :: OSI Approved :: MIT License
|
13
13
|
Classifier: Programming Language :: Python :: 3.11
|
14
14
|
Classifier: Operating System :: OS Independent
|
15
|
-
Requires-Python: >=3.
|
15
|
+
Requires-Python: >=3.10, <3.12
|
16
16
|
Description-Content-Type: text/markdown
|
17
17
|
License-File: LICENSE
|
18
18
|
Requires-Dist: wheel
|
@@ -466,8 +466,6 @@ curl -i -H 'Content-Type:application/json' -X POST -d '{"pv_power_forecast":[0,
|
|
466
466
|
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,"def_total_hours":[1,3],"def_start_timestep":[0,3],"def_end_timestep":[0,6]}' http://localhost:5000/action/naive-mpc-optim
|
467
467
|
```
|
468
468
|
|
469
|
-
|
470
|
-
|
471
469
|
## A machine learning forecaster
|
472
470
|
|
473
471
|
Starting in v0.4.0 a new machine learning forecaster class was introduced.
|
@@ -0,0 +1,25 @@
|
|
1
|
+
emhass/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
emhass/command_line.py,sha256=pfkGRAyWsYDIVP9WRxb2PZlwhH0KpcFH7uMpDH6gMqc,37462
|
3
|
+
emhass/forecast.py,sha256=8sA9r70SfmxKx1BEU7NuJWkN70-0agoh-2GPoDaimWQ,43796
|
4
|
+
emhass/machine_learning_forecaster.py,sha256=8Rm0-pltsjIYqLv01zCeO_Ij_n2HKC62dv_kCno7UsU,15640
|
5
|
+
emhass/optimization.py,sha256=WcUJSDSBK7wgx0jaX25mhco7ZfqG1g066Ebh6ACyruQ,37197
|
6
|
+
emhass/retrieve_hass.py,sha256=COf8LD6B0arFI-P71PXyLT7snB7_Wg5c3bMhRdVMdI4,18406
|
7
|
+
emhass/utils.py,sha256=zj1rzpzsRpifgDcmeqRZUcM6WL6GGCcUAdStmGoXlJE,42394
|
8
|
+
emhass/web_server.py,sha256=FFdIZio-QGFH3t-p-Le2Q1o6_cqjfBSdoEXdExJ21nY,21541
|
9
|
+
emhass/data/cec_inverters.pbz2,sha256=tK8FvAUDW0uYez8EPttdCJwHhpPofclYV6GhhNZL0Pk,168272
|
10
|
+
emhass/data/cec_modules.pbz2,sha256=8vEaysgYffXg3KUl8XSF36Mdywzi3LpEtUN_qenjO9s,1655747
|
11
|
+
emhass/static/advanced.html,sha256=AsT3lMD0AjvAqzAYvUPmslyOYk2C3LA-VfoSB2PwnYA,1747
|
12
|
+
emhass/static/basic.html,sha256=hJ4EgCXVNHL5nMQWkIHWjsTm_bJb0N_ZN4zFUjhxEzU,608
|
13
|
+
emhass/static/script.js,sha256=bj3Pksm97sM4lUTpp3IkStx8fMwee39GnDWhrWjvV_A,17250
|
14
|
+
emhass/static/style.css,sha256=xSihd06G-AeMKtFGPCJAnTaGVQXKuPM7kvVvDuQDuxU,15557
|
15
|
+
emhass/static/img/emhass_icon.png,sha256=Kyx6hXQ1huJLHAq2CaBfjYXR25H9j99PSWHI0lShkaQ,19030
|
16
|
+
emhass/static/img/emhass_logo_short.svg,sha256=yzMcqtBRCV8rH84-MwnigZh45_f9Eoqwho9P8nCodJA,66736
|
17
|
+
emhass/static/img/feather-sprite.svg,sha256=VHjMJQg88wXa9CaeYrKGhNtyK0xdd47zCqwSIa-hxo8,60319
|
18
|
+
emhass/templates/index.html,sha256=_BsvUJ981uSQkx5H9tq_3es__x4WdPiOy7FjNoNYU9w,2744
|
19
|
+
emhass/templates/template.html,sha256=TkGgMecQEbFUZA4ymPwMUzNjKHsENvCgroUWbPt7G4Y,158
|
20
|
+
emhass-0.8.5.dist-info/LICENSE,sha256=1X3-S1yvOCBDBeox1aK3dq00m7dA8NDtcPrpKPISzbE,1077
|
21
|
+
emhass-0.8.5.dist-info/METADATA,sha256=yrzLpT7uN4YHmgR9Unjbp_ypd5qSgR_fG48_RRypRHg,34758
|
22
|
+
emhass-0.8.5.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
23
|
+
emhass-0.8.5.dist-info/entry_points.txt,sha256=6Bp1NFOGNv_fSTxYl1ke3K3h3aqAcBxI-bgq5yq-i1M,52
|
24
|
+
emhass-0.8.5.dist-info/top_level.txt,sha256=L7fIX4awfmxQbAePtSdVg2e6x_HhghfReHfsKSpKr9I,7
|
25
|
+
emhass-0.8.5.dist-info/RECORD,,
|
emhass-0.8.3.dist-info/RECORD
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
emhass/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
emhass/command_line.py,sha256=f8u1gNo5NhXF1dYMTFPDc3En34yH0sW4lFu3dqTJNf0,37424
|
3
|
-
emhass/forecast.py,sha256=2_Dm03_XyEVPYnKnJzA5TABYgt3tCe9_zRDGOOfFtEM,43555
|
4
|
-
emhass/machine_learning_forecaster.py,sha256=8Rm0-pltsjIYqLv01zCeO_Ij_n2HKC62dv_kCno7UsU,15640
|
5
|
-
emhass/optimization.py,sha256=M9BlbJ4f38APoIsHKLY_pfKszVWA61cPv_QnmtazkRA,37181
|
6
|
-
emhass/retrieve_hass.py,sha256=0v-CRFvhJgtT27AlDh0QL8f3PlZYNNMfw1fxGVwobg4,18356
|
7
|
-
emhass/utils.py,sha256=Slme0gYyXE9LwWO-8yOALZua7civhdhCoiv0h89ivdA,46114
|
8
|
-
emhass/web_server.py,sha256=U8aqg0udqxChBMxr83b0bE84HZLnuLQyb6SQaJmKtzM,21475
|
9
|
-
emhass/static/advanced.html,sha256=AsT3lMD0AjvAqzAYvUPmslyOYk2C3LA-VfoSB2PwnYA,1747
|
10
|
-
emhass/static/basic.html,sha256=hJ4EgCXVNHL5nMQWkIHWjsTm_bJb0N_ZN4zFUjhxEzU,608
|
11
|
-
emhass/static/script.js,sha256=k3srZCNWLAduZzHzEjSyS2qttjKXpiI1WAh8CrJpQKY,17512
|
12
|
-
emhass/static/style.css,sha256=2sGD6OF33tx3-sAoZQvDvVcF67RkGNf3Xi0PMoRB34A,14952
|
13
|
-
emhass/static/img/emhass_icon.png,sha256=Kyx6hXQ1huJLHAq2CaBfjYXR25H9j99PSWHI0lShkaQ,19030
|
14
|
-
emhass/static/img/emhass_logo_short.svg,sha256=yzMcqtBRCV8rH84-MwnigZh45_f9Eoqwho9P8nCodJA,66736
|
15
|
-
emhass/static/img/feather-sprite.svg,sha256=VHjMJQg88wXa9CaeYrKGhNtyK0xdd47zCqwSIa-hxo8,60319
|
16
|
-
emhass/templates/index.html,sha256=OwmgZW8a4Powuzz9KOL-F3XAwSK5i0oi8RF4vJ8ptnQ,2652
|
17
|
-
emhass/templates/template.html,sha256=MXQsd1a1UcrwBUXEly7bq0jlS7Yj4kzvrLYEtjKhwtQ,237
|
18
|
-
emhass-0.8.3.dist-info/LICENSE,sha256=1X3-S1yvOCBDBeox1aK3dq00m7dA8NDtcPrpKPISzbE,1077
|
19
|
-
emhass-0.8.3.dist-info/METADATA,sha256=zAdXl-POayaLtHGOmx-ozbR8zdMyDhb87S_BL4HNiLk,34759
|
20
|
-
emhass-0.8.3.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
21
|
-
emhass-0.8.3.dist-info/entry_points.txt,sha256=6Bp1NFOGNv_fSTxYl1ke3K3h3aqAcBxI-bgq5yq-i1M,52
|
22
|
-
emhass-0.8.3.dist-info/top_level.txt,sha256=L7fIX4awfmxQbAePtSdVg2e6x_HhghfReHfsKSpKr9I,7
|
23
|
-
emhass-0.8.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|