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 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
- retrieve_hass_conf['freq'], retrieve_hass_conf['time_zone'],
63
- params, base_path, logger, get_data_from_file=get_data_from_file)
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 sensor_power_photovoltaics or sensor_power_load_no_var_loads")
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") # The period of optimization
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=self.freq)
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=freq_scrap)
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 = pvlib.pvsystem.retrieve_sam('CECMod')
416
- cec_inverters = pvlib.pvsystem.retrieve_sam('cecinverter')
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=self.freq)
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['P_grid_max'], upBound=0,
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['P_grid_max'],
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['P_grid_max']*D[i],
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['P_grid_max']*(1-D[i]),
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 is 0:
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 + " (check your recorder settings)")
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 is 0:
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 + " (check your recorder settings)")
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=self.freq)
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(getHTMLURL() + `static/` + htmlFile);
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(getHTMLURL() + `action/${action}`, {
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(getHTMLURL() + `template/table-template`, {
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
- margin-right: 12px;
554
- filter: drop-shadow( #282928 .4px .5px);
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, .info {
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:hover {
687
- background: silver;
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, #advance, #basic, button, select, .info {
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, .input-box {
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, .alert {
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, svg.main-svg{
1000
+ figure,
1001
+ svg.main-svg {
955
1002
  -webkit-filter: invert(.82);
956
1003
  filter: invert(.82);
957
1004
  }
958
-
959
- figure, svg.main-svg{
960
- border-color: #181818;
961
- border-style: solid;
962
- border-width: 1px;
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
- .modebar-btn--logo svg{
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 td:nth-child(even),
1022
- .mystyle tr td:nth-child(even) {
1023
- background-color: #3d3d3d;
1071
+ .mystyle tr {
1072
+ background: none;
1024
1073
  }
1025
1074
 
1026
- tr:nth-child(odd),
1027
- .mystyle tr:nth-child(odd) {
1028
- background-color: #111111;
1075
+ .mystyle tr:nth-child(even) td,
1076
+ th {
1077
+ background: #282928;
1029
1078
  }
1030
1079
 
1031
- tr:nth-child(even),
1032
- .mystyle tr:nth-child(even) {
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
- tr:hover td,
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
+ }
@@ -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="{{ basename }}/static/style.css">
8
- <link rel="icon" href="{{ basename }}/static/img/emhass_logo_short.svg">
9
- <script src="{{ basename }}/static/script.js"></script>
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: .7em; cursor: pointer; z-index: 1">
18
+ <a id="basicOrAdvanced" style="margin-right: 24px; cursor: pointer; z-index: 1">
19
19
  <svg class="feather">
20
- <use href="{{ basename }}/static/img/feather-sprite.svg#tool" />
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="{{ basename }}/static/img/feather-sprite.svg#book" />
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;" class="feather">
30
- <use href="{{ basename }}/static/img/feather-sprite.svg#git-branch" />
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="{{ basename }}/static/img/emhass_icon.png" alt="">
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
 
@@ -1,4 +1,3 @@
1
- <link rel="stylesheet" type="text/css" href="{{ basename }}/static/style.css">
2
1
  {% for plot in injection_dict %} <!-- diagrams/tables elements will be added here -->
3
2
  <div class="table_div">
4
3
  {{injection_dict[plot]}}
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
- if 'pv_power_forecast' in runtimeparams.keys():
212
- if type(runtimeparams['pv_power_forecast']) == list and len(runtimeparams['pv_power_forecast']) >= len(forecast_dates):
213
- params['passed_data']['pv_power_forecast'] = runtimeparams['pv_power_forecast']
214
- optim_conf['weather_forecast_method'] = 'list'
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
- logger.error("ERROR: The passed data is either not a list or the length is not correct, length should be "+str(len(forecast_dates)))
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 plant_con
638
- params['plant_conf']['P_grid_max'] = options.get('maximum_power_from_grid',params['plant_conf']['P_grid_max'])
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
- basename = request.headers.get("X-Ingress-Path", "")
73
- return make_response(template.render(injection_dict=injection_dict, basename=basename))
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
- basename = request.headers.get("X-Ingress-Path", "")
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
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.9, <3.12
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,,
@@ -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