@@ -63,21 +63,15 @@ def minutes_since_yesterday(self, now):
6363 def dp2 (self , value ):
6464 return math .ceil (value * 100 )/ 100
6565
66- def run_prediction (self , now , charge_limit , load_minutes , pv_forecast_minute , save , save_best , short ):
66+ def run_prediction (self , now , charge_limit , load_minutes , pv_forecast_minute , save , save_best ):
6767
6868 six_days = 24 * 60 * (self .days_previous - 1 )
6969
7070 # Offset by 6 (configurable) days to get to last week
7171 load_yesterday = load_minutes [self .difference_minutes + six_days ]
7272 load_yesterday_now = load_minutes [24 * 60 + six_days ]
73- self .log ("Minutes since yesterday " + str (self .difference_minutes ) + " load past day " + str (load_yesterday ) + " load past day now " + str (load_yesterday_now ))
7473
7574 forecast_minutes = self .forecast_hours * 60
76-
77- # For the SOC calculation we need to stop at the second charge window to avoid confusing multiple days out
78- if short :
79- forecast_minutes = min (forecast_minutes , self .charge_start_time_minutes + 24 * 60 - self .minutes_now )
80-
8175 predict_soc = {}
8276 predict_soc_time = {}
8377 minute = 0
@@ -88,9 +82,19 @@ def run_prediction(self, now, charge_limit, load_minutes, pv_forecast_minute, sa
8882 import_kwh_house = 0
8983 import_kwh_battery = 0
9084
85+ # For the SOC calculation we need to stop at the second charge window to avoid confusing multiple days out
86+ end_record = min (forecast_minutes , self .charge_start_time_minutes + 24 * 60 - self .minutes_now )
87+ record = True
88+
89+ self .log ("Minutes since yesterday " + str (self .difference_minutes ) + " load past day " + str (load_yesterday ) + " load past day now " + str (load_yesterday_now ) + " end record " + str (end_record ))
90+
9191 # Simulate each forward minute
9292 while minute < forecast_minutes :
9393
94+ # Outside the recording window?
95+ if minute >= end_record :
96+ record = False
97+
9498 minute_yesterday = 24 * 60 - minute + six_days
9599 # Average previous load over 10 minutes due to sampling accuracy
96100 load_yesterday = (load_minutes [minute_yesterday ] - load_minutes [minute_yesterday + 10 ]) / 10.0
@@ -121,8 +125,9 @@ def run_prediction(self, now, charge_limit, load_minutes, pv_forecast_minute, sa
121125
122126 # Apply battery loss to computed charging energy
123127 # For now we ignore PV in this as it's probably not a major factor when mains charging is enabled
124- import_kwh += max (0 , soc - old_soc - pv_now ) / self .battery_loss
125- import_kwh_battery += max (0 , soc - old_soc - pv_now ) / self .battery_loss
128+ if record :
129+ import_kwh += max (0 , soc - old_soc - pv_now ) / self .battery_loss
130+ import_kwh_battery += max (0 , soc - old_soc - pv_now ) / self .battery_loss
126131
127132 if self .debug_enable and minute % 60 == 0 :
128133 self .log ("Hour %s battery charging target soc %s" % (minute / 60 , charge_limit ))
@@ -135,18 +140,21 @@ def run_prediction(self, now, charge_limit, load_minutes, pv_forecast_minute, sa
135140
136141 if diff > self .discharge_rate :
137142 soc -= self .discharge_rate
138- import_kwh += (diff - self .discharge_rate )
139- import_kwh_house += (diff - self .discharge_rate )
143+ if record :
144+ import_kwh += (diff - self .discharge_rate )
145+ import_kwh_house += (diff - self .discharge_rate )
140146 else :
141147 soc -= diff
142148
143149 if soc < self .reserve :
144- import_kwh += self .reserve - soc
145- import_kwh_house += self .reserve - soc
150+ if record :
151+ import_kwh += self .reserve - soc
152+ import_kwh_house += self .reserve - soc
146153 soc = self .reserve
147154
148155 if soc > self .soc_max :
149- export_kwh += soc - self .soc_max
156+ if record :
157+ export_kwh += soc - self .soc_max
150158 soc = self .soc_max
151159
152160 if self .debug_enable and minute % 60 == 0 :
@@ -158,10 +166,15 @@ def run_prediction(self, now, charge_limit, load_minutes, pv_forecast_minute, sa
158166 if minute % 10 == 0 :
159167 predict_soc_time [str (minute_timestamp )] = self .dp2 (soc )
160168
161- # Store the worst caste
162- if soc <= self .reserve :
169+ # Store the number of minutes until the battery runs out
170+ if record and soc <= self .reserve :
163171 if minute_left > minute :
164172 minute_left = minute
173+
174+ # Record final soc
175+ if record :
176+ final_soc = soc
177+
165178 minute += 1
166179
167180 #self.log("load yesterday " + str(load_minutes))
@@ -175,17 +188,18 @@ def run_prediction(self, now, charge_limit, load_minutes, pv_forecast_minute, sa
175188
176189 if save :
177190 self .set_state ("predbat.battery_hours_left" , state = self .dp2 (hours_left ), attributes = {'friendly_name' : 'Battery Hours left' , 'state_class' : 'measurement' , 'unit_of_measurement' : 'hours' , 'step' : 0.5 })
178- self .set_state ("predbat.soc_kw" , state = self .dp2 (soc ), attributes = {'results' : predict_soc_time , 'friendly_name' : 'Battery SOC kwh' , 'state_class' : 'measurement' , 'unit_of_measurement' : 'kwh' , 'step' : 0.5 })
191+ self .set_state ("predbat.soc_kw" , state = self .dp2 (final_soc ), attributes = {'results' : predict_soc_time , 'friendly_name' : 'Battery SOC kwh' , 'state_class' : 'measurement' , 'unit_of_measurement' : 'kwh' , 'step' : 0.5 })
179192 self .set_state ("predbat.export_energy" , state = self .dp2 (export_kwh ), attributes = {'friendly_name' : 'Predicted exports' , 'state_class' : 'measurement' , 'unit_of_measurement' : 'kwh' })
180193 self .set_state ("predbat.import_energy" , state = self .dp2 (import_kwh ), attributes = {'friendly_name' : 'Predicted imports' , 'state_class' : 'measurement' , 'unit_of_measurement' : 'kwh' })
181194 self .set_state ("predbat.import_energy_battery" , state = self .dp2 (import_kwh_battery ), attributes = {'friendly_name' : 'Predicted import to battery' , 'state_class' : 'measurement' , 'unit_of_measurement' : 'kwh' })
182195 self .set_state ("predbat.import_energy_house" , state = self .dp2 (import_kwh_house ), attributes = {'friendly_name' : 'Predicted import to house' , 'state_class' : 'measurement' , 'unit_of_measurement' : 'kwh' })
183196 self .log ("Battery has " + str (hours_left ) + " hours left - now at " + str (self .soc_kw ))
184197 self .set_state ("predbat.metric" , state = self .dp2 (metric ), attributes = {'friendly_name' : 'Predicted metric (cost)' , 'state_class' : 'measurement' , 'unit_of_measurement' : 'p' })
198+ self .set_state ("predbat.duration" , state = self .dp2 (end_record / 60 ), attributes = {'friendly_name' : 'Predicted duration' , 'state_class' : 'measurement' , 'unit_of_measurement' : 'hours' })
185199
186200 if save_best :
187201 self .log ('Saving best data with charge_limit %s' % charge_limit )
188- self .set_state ("predbat.soc_kw_best" , state = self .dp2 (soc ), attributes = {'results' : predict_soc_time , 'friendly_name' : 'Battery SOC kwh best' , 'state_class' : 'measurement' , 'unit_of_measurement' : 'kwh' , 'step' : 0.5 })
202+ self .set_state ("predbat.soc_kw_best" , state = self .dp2 (final_soc ), attributes = {'results' : predict_soc_time , 'friendly_name' : 'Battery SOC kwh best' , 'state_class' : 'measurement' , 'unit_of_measurement' : 'kwh' , 'step' : 0.5 })
189203 self .set_state ("predbat.best_charge_limit_kw" , state = self .dp2 (charge_limit ), attributes = {'friendly_name' : 'Predicted charge limit kwh best' , 'state_class' : 'measurement' , 'unit_of_measurement' : 'kwh' })
190204 self .set_state ("predbat.best_charge_limit" , state = charge_limit_percent , attributes = {'friendly_name' : 'Predicted charge limit best' , 'state_class' : 'measurement' , 'unit_of_measurement' : '%' })
191205 self .set_state ("predbat.best_export_energy" , state = self .dp2 (export_kwh ), attributes = {'friendly_name' : 'Predicted exports best' , 'state_class' : 'measurement' , 'unit_of_measurement' : 'kwh' })
@@ -292,7 +306,7 @@ def update_pred(self):
292306 while try_soc > self .reserve :
293307 was_debug = self .debug_enable
294308 self .debug_enable = False
295- metric , charge_limit_percent , import_kwh_battery , import_kwh_house , export_kwh = self .run_prediction (now , try_soc , load_minutes , pv_forecast_minute , False , False , True )
309+ metric , charge_limit_percent , import_kwh_battery , import_kwh_house , export_kwh = self .run_prediction (now , try_soc , load_minutes , pv_forecast_minute , False , False )
296310 self .debug_true = was_debug
297311 if self .debug_enable :
298312 self .log ("Trying soc %s gives import battery %s house %s export %s metric %s (%s + %s - %s)" %
@@ -311,7 +325,7 @@ def update_pred(self):
311325 best_soc = max (self .best_soc_min , best_soc )
312326 best_soc = min (best_soc , self .soc_max )
313327 self .log ("Best soc calculated at %s (margin added %s and min %s) with metric %s" % (best_soc , self .best_soc_margin , self .best_soc_min , best_metric ))
314- best_metric , charge_limit_percent , import_kwh_battery , import_kwh_house , export_kwh = self .run_prediction (now , best_soc , load_minutes , pv_forecast_minute , False , True , True )
328+ best_metric , charge_limit_percent , import_kwh_battery , import_kwh_house , export_kwh = self .run_prediction (now , best_soc , load_minutes , pv_forecast_minute , False , True )
315329 self .log ("Best soc %s gives import battery %s house %s export %s metric %s (%s + %s - %s)" %
316330 (best_soc , import_kwh_battery , import_kwh_house , export_kwh , metric ,
317331 import_kwh_house * self .metric_house , import_kwh_battery * self .metric_battery , export_kwh * self .metric_export ))
@@ -324,7 +338,7 @@ def update_pred(self):
324338 self .log ("Not setting charging SOC as we are not within the window (now %s target set_soc_minutes %s charge start time %s" % (self .minutes_now ,self .set_soc_minutes , self .charge_start_time_minutes ))
325339
326340 # Simulate current settings
327- metric , charge_limit_percent , import_kwh_battery , import_kwh_house , export_kwh = self .run_prediction (now , self .charge_limit , load_minutes , pv_forecast_minute , True , False , False )
341+ metric , charge_limit_percent , import_kwh_battery , import_kwh_house , export_kwh = self .run_prediction (now , self .charge_limit , load_minutes , pv_forecast_minute , True , False )
328342
329343 def initialize (self ):
330344 self .log ("Startup" )
0 commit comments