Skip to content

Commit 523074c

Browse files
Non REST mode now polls for all register writes
Non REST mode now polls for all register writes Add inverter test mode for internal debug
1 parent 4aff1da commit 523074c

1 file changed

Lines changed: 115 additions & 46 deletions

File tree

apps/predbat/predbat.py

Lines changed: 115 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,35 @@
1919
MAX_CHARGE_LIMITS = 16
2020
PREDICT_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

2526
class 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

Comments
 (0)