1919MAX_CHARGE_LIMITS = 16
2020PREDICT_STEP = 5
2121
22- SIMULATE = False # Debug option, when set don't write to entities but simulate each 30 min period
23- SIMULATE_LENGTH = 23 * 60 # How many periods to simulate, set to 0 for just current
22+ SIMULATE = False # Debug option, when set don't write to entities but simulate each 30 min period
23+ SIMULATE_LENGTH = 23 * 60 # How many periods to simulate, set to 0 for just current
24+ INVERTER_TEST = False # Run inverter control self test
2425
2526class Inverter ():
27+ def self_test (self ):
28+ self .base .log ("======= INVERTER CONTROL SELF TEST START - REST={} ========" .format (self .rest_api ))
29+ self .adjust_battery_target (99 )
30+ self .adjust_battery_target (100 )
31+ self .adjust_reserve (6 )
32+ self .adjust_reserve (4 )
33+ self .disable_charge_window ()
34+ timea = datetime .strptime ("23:00:00" , "%H:%M:%S" )
35+ timeb = datetime .strptime ("23:01:00" , "%H:%M:%S" )
36+ timec = datetime .strptime ("05:00:00" , "%H:%M:%S" )
37+ timed = datetime .strptime ("05:01:00" , "%H:%M:%S" )
38+ self .adjust_charge_window (timeb , timed )
39+ self .adjust_charge_window (timea , timec )
40+ self .adjust_force_discharge (False , timec , timed )
41+ self .adjust_force_discharge (True , timea , timeb )
42+ self .adjust_force_discharge (False )
43+ self .base .log ("======= INVERTER CONTROL SELF TEST END ========" )
44+
45+ if self .rest_api :
46+ self .rest_api = None
47+ self .rest_data = None
48+ self .self_test ()
49+ exit
50+
2651 def __init__ (self , base , id = 0 ):
2752 self .id = id
2853 self .base = base
@@ -182,10 +207,14 @@ def update_status(self, minutes_now):
182207 # Pre-fill best discharge enable with Off
183208 self .discharge_limits = [100.0 for i in range (0 , len (self .discharge_window ))]
184209
210+ if INVERTER_TEST :
211+ self .self_test ()
212+
185213 def adjust_reserve (self , reserve ):
186214 """
187215 Adjust the reserve target % in GivTCP
188216 """
217+
189218 if SIMULATE :
190219 current_reserve = float (self .base .sim_reserve )
191220 else :
@@ -195,6 +224,7 @@ def adjust_reserve(self, reserve):
195224 current_reserve = self .base .get_arg ('reserve' , index = self .id , default = 0.0 )
196225
197226 # Clamp to minimum
227+ reserve = int (reserve )
198228 if reserve < self .reserve_percent :
199229 reserve = self .reserve_percent
200230
@@ -206,12 +236,8 @@ def adjust_reserve(self, reserve):
206236 if self .rest_api :
207237 self .rest_setReserve (reserve )
208238 else :
209- entity_id = self .base .get_arg ('reserve' , indirect = False , index = self .id )
210- entity_soc = self .base .get_entity (entity_id )
211- if entity_soc :
212- entity_soc .call_service ("set_value" , value = reserve )
213- else :
214- self .base .log ("WARN: Inverter {} Unable to get entity to set reserve target" .format (self .id ))
239+ entity_soc = self .base .get_entity (self .base .get_arg ('reserve' , indirect = False , index = self .id ))
240+ self .write_and_poll_value ('reserve' , entity_soc , reserve )
215241 if self .base .get_arg ('set_reserve_notify' , False ):
216242 self .base .call_notify ('Predbat: Inverter {} Target Reserve has been changed to {} at {}' .format (self .id , reserve , self .base .time_now_str ()))
217243 self .base .record_status ("Inverter {} set reserve to {} at {}" .format (self .id , reserve , self .base .time_now_str ()))
@@ -241,10 +267,7 @@ def adjust_charge_rate(self, new_rate):
241267 self .rest_setChargeRate (new_rate )
242268 else :
243269 entity = self .base .get_entity (self .base .get_arg ('charge_rate' , indirect = False , index = self .id ))
244- if entity :
245- entity .call_service ("set_value" , value = new_rate )
246- else :
247- self .base .log ("WARN: Inverter {} Unable to get entity to set Charge rate target" .format (self .id ))
270+ self .write_and_poll_value ('charge_rate' , entity , new_rate , fuzzy = 100 )
248271 if self .base .get_arg ('set_soc_notify' , False ):
249272 self .base .call_notify ('Predbat: Inverter {} charge rate changes to {} at {}' .format (self .id , new_rate , self .base .time_now_str ()))
250273 self .base .record_status ("Inverter {} charge rate changed to {} at {}" .format (self .id , new_rate , self .base .time_now_str ()))
@@ -272,10 +295,7 @@ def adjust_discharge_rate(self, new_rate):
272295 self .rest_setDischargeRate (new_rate )
273296 else :
274297 entity = self .base .get_entity (self .base .get_arg ('discharge_rate' , indirect = False , index = self .id ))
275- if entity :
276- entity .call_service ("set_value" , value = new_rate )
277- else :
278- self .base .log ("WARN: Inverter {} Unable to get entity to set Charge rate target" .format (self .id ))
298+ self .write_and_poll_value ('discharge_rate' , entity , new_rate , fuzzy = 100 )
279299 if self .base .get_arg ('set_discharge_notify' , False ):
280300 self .base .call_notify ('Predbat: Inverter {} discharge rate changes to {} at {}' .format (self .id , new_rate , self .base .time_now_str ()))
281301 self .base .record_status ("Inverter {} discharge rate changed to {} at {}" .format (self .id , new_rate , self .base .time_now_str ()))
@@ -295,7 +315,7 @@ def adjust_battery_target(self, soc):
295315 if self .rest_data :
296316 current_soc = float (self .rest_data ['Control' ]['Target_SOC' ])
297317 else :
298- current_soc = self .base .get_arg ('charge_limit' , index = self .id , default = 100.0 )
318+ current_soc = self .base .get_arg ('charge_limit' , index = self .id )
299319
300320 if current_soc != soc :
301321 self .base .log ("Inverter {} Current charge Limit is {} % and new target is {} %" .format (self .id , current_soc , soc ))
@@ -306,29 +326,64 @@ def adjust_battery_target(self, soc):
306326 self .rest_setChargeTarget (soc )
307327 else :
308328 entity_soc = self .base .get_entity (self .base .get_arg ('charge_limit' , indirect = False , index = self .id ))
309- if entity_soc :
310- entity_soc .call_service ("set_value" , value = soc )
311- else :
312- self .base .log ("WARN: Inverter {} Unable to get entity to set SOC target" .format (self .id ))
329+ self .write_and_poll_value ('charge_limit' , entity_soc , soc )
330+
313331 if self .base .get_arg ('set_soc_notify' , False ):
314332 self .base .call_notify ('Predbat: Inverter {} Target SOC has been changed to {} % at {}' .format (self .id , soc , self .base .time_now_str ()))
315333 self .base .record_status ("Inverter {} set soc to {} at {}" .format (self .id , soc , self .base .time_now_str ()))
316334 else :
317335 self .base .log ("Inverter {} Current SOC is {} already at target" .format (self .id , current_soc ))
318336
337+ def write_and_poll_switch (self , name , entity , new_value ):
338+ """
339+ GivTCP Workaround, keep writing until correct
340+ """
341+ tries = 6
342+ for retry in range (0 , 6 ):
343+ if new_value :
344+ entity .call_service ('turn_on' )
345+ else :
346+ entity .call_service ('turn_off' )
347+ time .sleep (10 )
348+ old_value = entity .get_state ()
349+ if isinstance (old_value , str ):
350+ if old_value .lower () in ['on' , 'enable' , 'true' ]:
351+ old_value = True
352+ else :
353+ old_value = False
354+ if old_value == new_value :
355+ self .base .log ("Inverter {} Wrote {} to {} successfully and got {}" .format (self .id , name , new_value , entity .get_state ()))
356+ return True
357+ self .base .log ("WARN: Inverter {} Trying to write {} to {} didn't complete got {}" .format (self .id , name , new_value , entity .get_state ()))
358+ return False
359+
360+ def write_and_poll_value (self , name , entity , new_value , fuzzy = 0 ):
361+ """
362+ GivTCP Workaround, keep writing until correct
363+ """
364+ for retry in range (0 , 6 ):
365+ entity .call_service ("set_value" , value = new_value )
366+ time .sleep (10 )
367+ old_value = int (entity .get_state ())
368+ if (abs (old_value - new_value ) <= fuzzy ):
369+ self .base .log ("Inverter {} Wrote {} to {}, successfully now {}" .format (self .id , name , new_value , int (entity .get_state ())))
370+ return True
371+ self .base .log ("WARN: Inverter {} Trying to write {} to {} didn't complete got {}" .format (self .id , name , new_value , int (entity .get_state ())))
372+ return False
373+
319374 def write_and_poll_option (self , name , entity , new_value ):
320375 """
321376 GivTCP Workaround, keep writing until correct
322377 """
323- old_value = ""
324- tries = 12
325- while old_value != new_value and tries > 0 :
378+ for retry in range (0 , 6 ):
326379 entity .call_service ("select_option" , option = new_value )
327- time .sleep (5 )
380+ time .sleep (10 )
328381 old_value = entity .get_state ()
329- tries -= 1
330- if tries == 0 :
331- self .base .log ("WARN: Inverter {} Trying to write {} to {} didn't complete" .format (self .id , name , new_value ))
382+ if old_value == new_value :
383+ self .base .log ("Inverter {} Wrote {} to {} successfully" .format (self .id , name , new_value ))
384+ return True
385+ self .base .log ("WARN: Inverter {} Trying to write {} to {} didn't complete got {}" .format (self .id , name , new_value , entity .get_state ()))
386+ return False
332387
333388 def adjust_force_discharge (self , force_discharge , new_start_time = None , new_end_time = None ):
334389 """
@@ -421,15 +476,15 @@ def adjust_force_discharge(self, force_discharge, new_start_time=None, new_end_t
421476 if self .rest_api :
422477 self .rest_setBatteryMode (new_inverter_mode )
423478 else :
424- entity_inverter_mode = self .base .get_entity (self .base .get_arg ('inverter_mode' , indirect = False , index = self .id ))
425- entity_inverter_mode . call_service ( "select_option" , option = new_inverter_mode )
479+ entity = self .base .get_entity (self .base .get_arg ('inverter_mode' , indirect = False , index = self .id ))
480+ self . write_and_poll_option ( 'inverter_mode' , entity , new_inverter_mode )
426481
427482 # Notify
428483 if self .base .get_arg ('set_discharge_notify' , False ):
429484 self .base .call_notify ("Predbat: Inverter {} Force discharge set to {} at time {}" .format (self .id , force_discharge , self .base .time_now_str ()))
430485
431486 self .base .record_status ("Inverter {} Set discharge mode to {} at {}" .format (self .id , new_inverter_mode , self .base .time_now_str ()))
432- self .base .log ("Inverter {} Changing force discharge to {}" .format (self .id , force_discharge ))
487+ self .base .log ("Inverter {} set force discharge to {}" .format (self .id , force_discharge ))
433488
434489 def disable_charge_window (self ):
435490 """
@@ -449,8 +504,8 @@ def disable_charge_window(self):
449504 if self .rest_api :
450505 self .rest_enableChargeSchedule (False )
451506 else :
452- entity_start = self .base .get_entity (self .base .get_arg ('scheduled_charge_enable' , indirect = False , index = self .id ))
453- entity_start . call_service ( "turn_off" )
507+ entity = self .base .get_entity (self .base .get_arg ('scheduled_charge_enable' , indirect = False , index = self .id ))
508+ self . write_and_poll_switch ( 'scheduled_charge_enable' , entity , False )
454509 if self .base .get_arg ('set_soc_notify' , False ):
455510 self .base .call_notify ("Predbat: Inverter {} Disabled scheduled charging at {}" .format (self .id , self .base .time_now_str ()))
456511 else :
@@ -493,8 +548,8 @@ def adjust_charge_window(self, charge_start_time, charge_end_time):
493548 if self .rest_api :
494549 self .rest_enableChargeSchedule (True )
495550 else :
496- entity_start = self .base .get_entity (self .base .get_arg ('scheduled_charge_enable' , indirect = False , index = self .id ))
497- entity_start . call_service ( "turn_on" )
551+ entity = self .base .get_entity (self .base .get_arg ('scheduled_charge_enable' , indirect = False , index = self .id ))
552+ self . write_and_poll_switch ( 'scheduled_charge_enable' , entity , True )
498553 if self .base .get_arg ('set_soc_notify' , False ):
499554 self .base .call_notify ("Predbat: Inverter {} Enabling scheduled charging at {}" .format (self .id , self .base .time_now_str ()))
500555 else :
@@ -563,7 +618,7 @@ def rest_setChargeTarget(self, target):
563618 data = {"chargeToPercent" : target }
564619 for retry in range (0 , 5 ):
565620 r = requests .post (url , json = data )
566- time .sleep (5 )
621+ time .sleep (10 )
567622 self .rest_data = self .rest_runAll ()
568623 if float (self .rest_data ['Control' ]['Target_SOC' ]) == target :
569624 self .base .log ("Inverter {} charge target {} via REST successful on retry {}" .format (self .id , target , retry ))
@@ -618,7 +673,7 @@ def rest_setBatteryMode(self, inverter_mode):
618673
619674 for retry in range (0 , 5 ):
620675 r = requests .post (url , json = data )
621- time .sleep (5 )
676+ time .sleep (10 )
622677 self .rest_data = self .rest_runAll ()
623678 if inverter_mode == self .rest_data ['Control' ]['Mode' ]:
624679 self .base .log ("Set inverter {} mode {} via REST successful on retry {}" .format (self .id , inverter_mode , retry ))
@@ -636,7 +691,7 @@ def rest_setReserve(self, target):
636691 data = {"reservePercent" : target }
637692 for retry in range (0 , 5 ):
638693 r = requests .post (url , json = data )
639- time .sleep (5 )
694+ time .sleep (10 )
640695 self .rest_data = self .rest_runAll ()
641696 if float (self .rest_data ['Control' ]['Battery_Power_Reserve' ]) == target :
642697 self .base .log ("Set inverter {} reserve {} via REST successful on retry {}" .format (self .id , target , retry ))
@@ -651,11 +706,23 @@ def rest_enableChargeSchedule(self, enable):
651706 """
652707 url = self .rest_api + '/enableChargeSchedule'
653708 data = {"state" : "enable" if enable else "disable" }
654- r = requests .post (url , json = data )
655- time .sleep (5 )
656- r = requests .post (url , json = data )
657- self .base .log ("Enable charge schedule {} - {} via REST returned {}" .format (enable , data , r ))
658- return r .status_code == 200
709+
710+ for retry in range (0 , 5 ):
711+ r = requests .post (url , json = data )
712+ time .sleep (10 )
713+ self .rest_data = self .rest_runAll ()
714+ new_value = self .rest_data ['Control' ]['Enable_Charge_Schedule' ]
715+ if isinstance (new_value , str ):
716+ if new_value .lower () in ['enable' , 'on' , 'true' ]:
717+ new_value = True
718+ else :
719+ new_value = False
720+ if new_value == enable :
721+ self .base .log ("Set inverter {} charge schedule {} via REST successful on retry {}" .format (self .id , enable , retry ))
722+ return True
723+
724+ self .base .log ("WARN: Set inverter {} charge schedule {} via REST failed got {}" .format (self .id , enable , self .rest_data ['Control' ]['Enable_Charge_Schedule' ]))
725+ return False
659726
660727 def rest_setChargeSlot1 (self , start , finish ):
661728 """
@@ -666,7 +733,7 @@ def rest_setChargeSlot1(self, start, finish):
666733
667734 for retry in range (0 , 5 ):
668735 r = requests .post (url , json = data )
669- time .sleep (5 )
736+ time .sleep (10 )
670737 self .rest_data = self .rest_runAll ()
671738 if self .rest_data ['Timeslots' ]['Charge_start_time_slot_1' ] == start and self .rest_data ['Timeslots' ]['Charge_end_time_slot_1' ] == finish :
672739 self .base .log ("Inverter {} set charge slot 1 {} via REST successful after retry {}" .format (self .id , data , retry ))
@@ -684,7 +751,7 @@ def rest_setDischargeSlot1(self, start, finish):
684751
685752 for retry in range (0 , 5 ):
686753 r = requests .post (url , json = data )
687- time .sleep (5 )
754+ time .sleep (10 )
688755 self .rest_data = self .rest_runAll ()
689756 if self .rest_data ['Timeslots' ]['Discharge_start_time_slot_1' ] == start and self .rest_data ['Timeslots' ]['Discharge_end_time_slot_1' ] == finish :
690757 self .base .log ("Inverter {} Set discharge slot 1 {} via REST successful after retry {}" .format (self .id , data , retry ))
@@ -745,6 +812,7 @@ def resolve_arg(self, arg, value, default=None, indirect=True, combine=False, at
745812
746813 # Resolve indirect instance
747814 if indirect and isinstance (value , str ) and '.' in value :
815+ ovalue = value
748816 if attribute :
749817 value = self .get_state (entity_id = value , default = default , attribute = attribute )
750818 else :
@@ -2832,7 +2900,8 @@ def initialize(self):
28322900 # self.listen_state(self.state_change, "input_number")
28332901
28342902 # And then every N minutes
2835- self .run_every (self .run_time_loop , next_time , run_every , random_start = 0 , random_end = 0 )
2903+ if not INVERTER_TEST :
2904+ self .run_every (self .run_time_loop , next_time , run_every , random_start = 0 , random_end = 0 )
28362905
28372906 def run_time_loop (self , cb_args ):
28382907 """
0 commit comments