@@ -151,6 +151,8 @@ async def wait_until(
151
151
event_trigger = None ,
152
152
timeout = None ,
153
153
state_hold = None ,
154
+ state_hold_false = None ,
155
+ __test_handshake__ = None ,
154
156
):
155
157
"""Wait for zero or more triggers, until an optional timeout."""
156
158
if state_trigger is None and time_trigger is None and event_trigger is None :
@@ -169,6 +171,12 @@ async def wait_until(
169
171
state_trig_waiting = False
170
172
state_trig_notify_info = [None , None ]
171
173
174
+ #
175
+ # at startup we start our state_hold_false window,
176
+ # although it could get updated if state_check_now is set.
177
+ #
178
+ state_false_time = time .monotonic ()
179
+
172
180
if state_trigger is not None :
173
181
state_trig = []
174
182
if isinstance (state_trigger , str ):
@@ -212,7 +220,13 @@ async def wait_until(
212
220
exc = state_trig_eval .get_exception_obj ()
213
221
if exc is not None :
214
222
raise exc
215
- if state_hold is not None and state_trig_ok :
223
+ if state_hold_false is not None :
224
+ #
225
+ # if state_trig_ok we wait until it is false;
226
+ # otherwise we consider now to be the start of the false hold time
227
+ #
228
+ state_false_time = None if state_trig_ok else time .monotonic ()
229
+ elif state_hold is not None and state_trig_ok :
216
230
state_trig_waiting = True
217
231
state_trig_notify_info = [None , {"trigger_type" : "state" }]
218
232
last_state_trig_time = time .monotonic ()
@@ -248,6 +262,14 @@ async def wait_until(
248
262
Event .notify_add (event_trigger [0 ], notify_q )
249
263
time0 = time .monotonic ()
250
264
265
+ if __test_handshake__ :
266
+ #
267
+ # used for testing to avoid race conditions
268
+ # we use this as a handshake that we are about to
269
+ # listen to the queue
270
+ #
271
+ State .set (__test_handshake__ [0 ], __test_handshake__ [1 ])
272
+
251
273
while True :
252
274
ret = None
253
275
this_timeout = None
@@ -319,6 +341,26 @@ async def wait_until(
319
341
if exc is not None :
320
342
break
321
343
344
+ if state_hold_false is not None :
345
+ if state_false_time is None :
346
+ if state_trig_ok :
347
+ #
348
+ # wasn't False, so ignore
349
+ #
350
+ continue
351
+ #
352
+ # first False, so remember when it is
353
+ #
354
+ state_false_time = time .monotonic ()
355
+ elif state_trig_ok :
356
+ too_soon = time .monotonic () - state_false_time < state_hold_false
357
+ state_false_time = None
358
+ if too_soon :
359
+ #
360
+ # was False but not for long enough, so start over
361
+ #
362
+ continue
363
+
322
364
if state_hold is not None :
323
365
if state_trig_ok :
324
366
if not state_trig_waiting :
@@ -594,7 +636,8 @@ def __init__(
594
636
self .trig_cfg = trig_cfg
595
637
self .state_trigger = trig_cfg .get ("state_trigger" , {}).get ("args" , None )
596
638
self .state_trigger_kwargs = trig_cfg .get ("state_trigger" , {}).get ("kwargs" , {})
597
- self .state_hold_dur = self .state_trigger_kwargs .get ("state_hold" , None )
639
+ self .state_hold = self .state_trigger_kwargs .get ("state_hold" , None )
640
+ self .state_hold_false = self .state_trigger_kwargs .get ("state_hold_false" , None )
598
641
self .state_check_now = self .state_trigger_kwargs .get ("state_check_now" , False )
599
642
self .time_trigger = trig_cfg .get ("time_trigger" , {}).get ("args" , None )
600
643
self .event_trigger = trig_cfg .get ("event_trigger" , {}).get ("args" , None )
@@ -727,6 +770,11 @@ async def trigger_watch(self):
727
770
last_state_trig_time = None
728
771
state_trig_waiting = False
729
772
state_trig_notify_info = [None , None ]
773
+ #
774
+ # at startup we start our state_hold_false window,
775
+ # although it could get updated if state_check_now is set.
776
+ #
777
+ state_false_time = time .monotonic ()
730
778
731
779
while True :
732
780
timeout = None
@@ -761,7 +809,7 @@ async def trigger_watch(self):
761
809
if time_next is not None :
762
810
timeout = (time_next - now ).total_seconds ()
763
811
if state_trig_waiting :
764
- time_left = last_state_trig_time + self .state_hold_dur - time .monotonic ()
812
+ time_left = last_state_trig_time + self .state_hold - time .monotonic ()
765
813
if timeout is None or time_left < timeout :
766
814
timeout = time_left
767
815
state_trig_timeout = True
@@ -798,7 +846,9 @@ async def trigger_watch(self):
798
846
new_vars , func_args = notify_info
799
847
800
848
if not ident_any_values_changed (func_args , self .state_trig_ident_any ):
849
+ #
801
850
# if var_name not in func_args we are state_check_now
851
+ #
802
852
if "var_name" in func_args and not ident_values_changed (
803
853
func_args , self .state_trig_ident
804
854
):
@@ -810,10 +860,38 @@ async def trigger_watch(self):
810
860
if exc is not None :
811
861
self .state_trig_eval .get_logger ().error (exc )
812
862
trig_ok = False
863
+
864
+ if self .state_hold_false is not None :
865
+ if "var_name" not in func_args :
866
+ #
867
+ # this is state_check_now check
868
+ # if immediately true, force wait until False
869
+ # otherwise start False wait now
870
+ #
871
+ state_false_time = None if trig_ok else time .monotonic ()
872
+ continue
873
+ if state_false_time is None :
874
+ if trig_ok :
875
+ #
876
+ # wasn't False, so ignore
877
+ #
878
+ continue
879
+ #
880
+ # first False, so remember when it is
881
+ #
882
+ state_false_time = time .monotonic ()
883
+ elif trig_ok :
884
+ too_soon = time .monotonic () - state_false_time < self .state_hold_false
885
+ state_false_time = None
886
+ if too_soon :
887
+ #
888
+ # was False but not for long enough, so start over
889
+ #
890
+ continue
813
891
else :
814
892
trig_ok = False
815
893
816
- if self .state_hold_dur is not None :
894
+ if self .state_hold is not None :
817
895
if trig_ok :
818
896
if not state_trig_waiting :
819
897
state_trig_waiting = True
@@ -823,14 +901,14 @@ async def trigger_watch(self):
823
901
"trigger %s got %s trigger; now waiting for state_hold of %g seconds" ,
824
902
notify_type ,
825
903
self .name ,
826
- self .state_hold_dur ,
904
+ self .state_hold ,
827
905
)
828
906
else :
829
907
_LOGGER .debug (
830
908
"trigger %s got %s trigger; still waiting for state_hold of %g seconds" ,
831
909
notify_type ,
832
910
self .name ,
833
- self .state_hold_dur ,
911
+ self .state_hold ,
834
912
)
835
913
continue
836
914
if state_trig_waiting :
0 commit comments