66
77from fastcs .attributes .attribute import Attribute
88from fastcs .attributes .attribute_io_ref import AttributeIORefT
9+ from fastcs .attributes .util import AttributeValuePredicate , PredicateEvent
910from fastcs .datatypes import DataType , DType_T
1011from fastcs .logging import bind_logger
1112
@@ -39,6 +40,8 @@ def __init__(
3940 """Callback to update the value of the attribute with an IO to the source"""
4041 self ._on_update_callbacks : list [AttrOnUpdateCallback [DType_T ]] | None = None
4142 """Callbacks to publish changes to the value of the attribute"""
43+ self ._on_update_events : set [PredicateEvent [DType_T ]] = set ()
44+ """Events to set when the value satisifies some predicate"""
4245
4346 def get (self ) -> DType_T :
4447 """Get the cached value of the attribute."""
@@ -67,6 +70,10 @@ async def update(self, value: Any) -> None:
6770
6871 self ._value = self ._datatype .validate (value )
6972
73+ self ._on_update_events -= {
74+ e for e in self ._on_update_events if e .set (self ._value )
75+ }
76+
7077 if self ._on_update_callbacks is not None :
7178 try :
7279 await asyncio .gather (
@@ -78,6 +85,19 @@ async def update(self, value: Any) -> None:
7885 )
7986 raise
8087
88+ def _register_update_event (
89+ self , predicate : AttributeValuePredicate [DType_T ], event : asyncio .Event
90+ ):
91+ """Register an event to be set when the value satisfies a predicate
92+
93+ Args:
94+ predicate: The predicate to check - a callable that takes the attribute
95+ value and returns True if the event should be set
96+ event: The event to set
97+
98+ """
99+ self ._on_update_events .add (PredicateEvent (predicate , event ))
100+
81101 def add_on_update_callback (self , callback : AttrOnUpdateCallback [DType_T ]) -> None :
82102 """Add a callback to be called when the value of the attribute is updated
83103
@@ -115,3 +135,61 @@ async def update_attribute():
115135 raise
116136
117137 return update_attribute
138+
139+ async def wait_for_predicate (
140+ self , predicate : AttributeValuePredicate [DType_T ], * , timeout : float
141+ ):
142+ """Wait for the predicate to return True when called with the current value
143+
144+ Args:
145+ predicate: The predicate to check - a callable that takes the attribute
146+ value and returns True if the event should be set
147+ timeout: The timeout in seconds
148+
149+ """
150+ if predicate (self ._value ):
151+ self .log_event (
152+ "Predicate already satisfied" , predicate = predicate , attribute = self
153+ )
154+ return
155+
156+ self ._register_update_event (predicate , update_event := asyncio .Event ())
157+
158+ self .log_event ("Waiting for predicate" , predicate = predicate , attribute = self )
159+ try :
160+ await asyncio .wait_for (update_event .wait (), timeout )
161+ except TimeoutError :
162+ raise TimeoutError (
163+ f"Timeout waiting for predicate { predicate } . "
164+ f"Current value: { self ._value } "
165+ ) from None
166+
167+ self .log_event ("Predicate satisfied" , predicate = predicate , attribute = self )
168+
169+ async def wait_for_value (self , value : DType_T , * , timeout : float ):
170+ """Wait for value to change to the required value
171+
172+ Args:
173+ value: The value to wait for
174+ timeout: The timeout in seconds
175+
176+ Raises:
177+ TimeoutError: If the attribute does not reach the required value within the
178+ timeout
179+
180+ """
181+ if self ._value == value :
182+ self .log_event ("Value already equal" , value = value , attribute = self )
183+ return
184+
185+ def predicate (v : DType_T ) -> bool :
186+ return v == value
187+
188+ try :
189+ await self .wait_for_predicate (predicate , timeout = timeout )
190+ except TimeoutError :
191+ raise TimeoutError (
192+ f"Timeout waiting for value { value } . Current value: { self ._value } "
193+ ) from None
194+
195+ self .log_event ("Value equal" , value = value , attribute = self )
0 commit comments