genie-python 15.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- genie_python/.pylintrc +539 -0
- genie_python/__init__.py +1 -0
- genie_python/_version.py +16 -0
- genie_python/block_names.py +123 -0
- genie_python/channel_access_exceptions.py +45 -0
- genie_python/genie.py +2462 -0
- genie_python/genie_advanced.py +418 -0
- genie_python/genie_alerts.py +195 -0
- genie_python/genie_api_setup.py +451 -0
- genie_python/genie_blockserver.py +64 -0
- genie_python/genie_cachannel_wrapper.py +551 -0
- genie_python/genie_change_cache.py +151 -0
- genie_python/genie_dae.py +2219 -0
- genie_python/genie_epics_api.py +906 -0
- genie_python/genie_experimental_data.py +186 -0
- genie_python/genie_logging.py +200 -0
- genie_python/genie_p4p_wrapper.py +203 -0
- genie_python/genie_plot.py +77 -0
- genie_python/genie_pre_post_cmd_manager.py +21 -0
- genie_python/genie_pv_connection_protocol.py +36 -0
- genie_python/genie_script_checker.py +507 -0
- genie_python/genie_script_generator.py +212 -0
- genie_python/genie_simulate.py +69 -0
- genie_python/genie_simulate_impl.py +1265 -0
- genie_python/genie_startup.py +29 -0
- genie_python/genie_toggle_settings.py +58 -0
- genie_python/genie_wait_for_move.py +154 -0
- genie_python/genie_waitfor.py +576 -0
- genie_python/matplotlib_backend/__init__.py +0 -0
- genie_python/matplotlib_backend/ibex_websocket_backend.py +366 -0
- genie_python/mysql_abstraction_layer.py +272 -0
- genie_python/scanning_instrument_pylint_plugin.py +31 -0
- genie_python/testing_utils/__init__.py +4 -0
- genie_python/testing_utils/script_checker.py +63 -0
- genie_python/typings/CaChannel/CaChannel.pyi +893 -0
- genie_python/typings/CaChannel/__init__.pyi +9 -0
- genie_python/typings/CaChannel/_version.pyi +6 -0
- genie_python/typings/CaChannel/ca.pyi +31 -0
- genie_python/utilities.py +406 -0
- genie_python/version.py +6 -0
- genie_python-15.1.0.dist-info/LICENSE +28 -0
- genie_python-15.1.0.dist-info/METADATA +84 -0
- genie_python-15.1.0.dist-info/RECORD +45 -0
- genie_python-15.1.0.dist-info/WHEEL +5 -0
- genie_python-15.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Classes allowing you to wait for states
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import absolute_import, print_function
|
|
6
|
+
|
|
7
|
+
from abc import ABCMeta, abstractmethod
|
|
8
|
+
from builtins import object, str
|
|
9
|
+
from datetime import datetime, timedelta
|
|
10
|
+
from time import sleep, strptime
|
|
11
|
+
from typing import TYPE_CHECKING, Callable, TypeAlias
|
|
12
|
+
|
|
13
|
+
from genie_python.utilities import check_break, get_time_delta
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from genie_python.genie_epics_api import API
|
|
17
|
+
|
|
18
|
+
NUMERIC_TYPE = (float, int)
|
|
19
|
+
|
|
20
|
+
WAITFOR_VALUE: TypeAlias = bool | int | float | str | None
|
|
21
|
+
|
|
22
|
+
# The time in a loop to sleep for when waiting for an event, e.g. polling a pv/block value
|
|
23
|
+
DELAY_IN_WAIT_FOR_SLEEP_LOOP = 0.1
|
|
24
|
+
|
|
25
|
+
# The time in a start_waiting to wait until notifying the user again of an error
|
|
26
|
+
SECONDS_UNTIL_RENOTIFY_EXCEPTION = 5 * 60
|
|
27
|
+
|
|
28
|
+
# Use the state pattern to keep a track of transitions to when exceptions and reconnections should
|
|
29
|
+
# be printed
|
|
30
|
+
# Exceptions should be printed at disconnection, after 5 minutes of disconnection or when the
|
|
31
|
+
# exception changes
|
|
32
|
+
# When the exception disappears pv reconnected is printed once
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class WaitForControllerState(object, metaclass=ABCMeta):
|
|
36
|
+
"""
|
|
37
|
+
The abstract class for the exception state of the
|
|
38
|
+
WaitForController when it is in the loop in the start_waiting method.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
api: "API",
|
|
44
|
+
last_notification_time: datetime,
|
|
45
|
+
last_exception: "Exception|None" = None,
|
|
46
|
+
context: "WaitForControllerExceptionContext|None" = None,
|
|
47
|
+
) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Initializes the state.
|
|
50
|
+
:param api: The api to use for logging
|
|
51
|
+
:param last_notification_time: The last time the user was notified of an exception
|
|
52
|
+
:param last_exception: The last exception the user was notified of
|
|
53
|
+
:param context: The context the state belongs to
|
|
54
|
+
"""
|
|
55
|
+
self._api = api
|
|
56
|
+
self._last_notification_time = last_notification_time
|
|
57
|
+
self._last_exception = last_exception
|
|
58
|
+
self.context = context
|
|
59
|
+
|
|
60
|
+
def process_exception(
|
|
61
|
+
self, exception: Exception | None = None
|
|
62
|
+
) -> "WaitForControllerState|None":
|
|
63
|
+
"""
|
|
64
|
+
Delegate processing of the exception to the relevant subclass method,
|
|
65
|
+
based on whether the exception argument is None or not.
|
|
66
|
+
:param exception: The exception to be processed (if no exception it is None)
|
|
67
|
+
:return: The state the context is now in (could be the same or a transition)
|
|
68
|
+
"""
|
|
69
|
+
if exception is not None:
|
|
70
|
+
return self.handle_exception_notification(exception)
|
|
71
|
+
else:
|
|
72
|
+
return self.exception_cleared()
|
|
73
|
+
|
|
74
|
+
@staticmethod
|
|
75
|
+
def _transition(new_state: "WaitForControllerState") -> "WaitForControllerState":
|
|
76
|
+
"""
|
|
77
|
+
We need to transition to a new state. Create this state, run enter on it and return it.
|
|
78
|
+
:param new_state: The state to transition to
|
|
79
|
+
:return: The new state
|
|
80
|
+
"""
|
|
81
|
+
new_state.enter()
|
|
82
|
+
return new_state
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def start(
|
|
86
|
+
api: "API", context: "WaitForControllerExceptionContext|None" = None
|
|
87
|
+
) -> "WaitForControllerConnectedState":
|
|
88
|
+
"""
|
|
89
|
+
Return the state the context starts at.
|
|
90
|
+
:param api: The api to use for logging
|
|
91
|
+
:param context: The context the state belongs to
|
|
92
|
+
:return: The state the context starts at.
|
|
93
|
+
"""
|
|
94
|
+
return WaitForControllerConnectedState(api, datetime.now(), context=context)
|
|
95
|
+
|
|
96
|
+
@abstractmethod
|
|
97
|
+
def handle_exception_notification(self, exception: Exception) -> "WaitForControllerState|None":
|
|
98
|
+
"""
|
|
99
|
+
When there is an exception handle it.
|
|
100
|
+
:param exception: The exception to handle
|
|
101
|
+
:return: The state the context is now in (could be the same or a transition)
|
|
102
|
+
"""
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
@abstractmethod
|
|
106
|
+
def exception_cleared(self) -> "WaitForControllerState|None":
|
|
107
|
+
"""
|
|
108
|
+
When there is no exception handle it.
|
|
109
|
+
:return: The state the context is now in (could be the same or a transition)
|
|
110
|
+
"""
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
@abstractmethod
|
|
114
|
+
def enter(self) -> None:
|
|
115
|
+
"""
|
|
116
|
+
When this state is first entered handle it.
|
|
117
|
+
:return: None
|
|
118
|
+
"""
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class WaitForControllerExceptionState(WaitForControllerState):
|
|
123
|
+
"""
|
|
124
|
+
The state for when the WaitForController is in an exception state
|
|
125
|
+
but should not notify the user
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
def handle_exception_notification(self, exception: Exception) -> "WaitForControllerState|None":
|
|
129
|
+
"""
|
|
130
|
+
Handle the event when an exception is occurring.
|
|
131
|
+
Transition to notifying user of an exception every 5 minutes or if the exception is
|
|
132
|
+
different to the last, else, remain in this state.
|
|
133
|
+
:param exception: The exception that has occurred.
|
|
134
|
+
:return: The new exception state if 5 minutes passed since last or if there is a new type
|
|
135
|
+
of exception. The current state if not.
|
|
136
|
+
"""
|
|
137
|
+
seconds_since_last_notification = (datetime.now() - self._last_notification_time).seconds
|
|
138
|
+
if seconds_since_last_notification >= SECONDS_UNTIL_RENOTIFY_EXCEPTION or not isinstance(
|
|
139
|
+
exception, type(self._last_exception)
|
|
140
|
+
):
|
|
141
|
+
return self._transition(
|
|
142
|
+
WaitForControllerExceptionState(self._api, self._last_notification_time, exception)
|
|
143
|
+
)
|
|
144
|
+
else:
|
|
145
|
+
return self
|
|
146
|
+
|
|
147
|
+
def exception_cleared(self) -> "WaitForControllerState|None":
|
|
148
|
+
"""
|
|
149
|
+
Handle the transition from an exception occurring to where the pv is connected.
|
|
150
|
+
:return: The new connected state
|
|
151
|
+
"""
|
|
152
|
+
return self._transition(
|
|
153
|
+
WaitForControllerConnectedState(
|
|
154
|
+
self._api, self._last_notification_time, self._last_exception
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
def print_exception(self, exception: Exception | None) -> None:
|
|
159
|
+
"""
|
|
160
|
+
Print an exception to stdout and log it to the api.
|
|
161
|
+
:param exception: The exception to print
|
|
162
|
+
:return: None
|
|
163
|
+
"""
|
|
164
|
+
message = "{}: Exception in waitfor loop: {}: {}".format(
|
|
165
|
+
str(datetime.now()), exception.__class__.__name__, exception
|
|
166
|
+
)
|
|
167
|
+
print(message)
|
|
168
|
+
self._api.logger.log_info_msg(message)
|
|
169
|
+
|
|
170
|
+
def enter(self) -> None:
|
|
171
|
+
"""
|
|
172
|
+
Print message about last exception and update notification time.
|
|
173
|
+
:return: None
|
|
174
|
+
"""
|
|
175
|
+
self.print_exception(self._last_exception)
|
|
176
|
+
self._last_notification_time = datetime.now()
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class WaitForControllerConnectedState(WaitForControllerState):
|
|
180
|
+
"""
|
|
181
|
+
The state for when the WaitForController has just reconnected after an exception state.
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
def handle_exception_notification(
|
|
185
|
+
self, exception: "Exception|None"
|
|
186
|
+
) -> "WaitForControllerState|None":
|
|
187
|
+
"""
|
|
188
|
+
Transition to the exception occurring state.
|
|
189
|
+
:param exception: The exception that has occurred.
|
|
190
|
+
:return: The new state of the context, which is an exception state.
|
|
191
|
+
"""
|
|
192
|
+
return self._transition(
|
|
193
|
+
WaitForControllerExceptionState(self._api, self._last_notification_time, exception)
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
def exception_cleared(self) -> WaitForControllerState | None:
|
|
197
|
+
"""
|
|
198
|
+
Remain in this state as we are still connected.
|
|
199
|
+
:return: The current state
|
|
200
|
+
"""
|
|
201
|
+
return self
|
|
202
|
+
|
|
203
|
+
def print_exception_cleared(self) -> None:
|
|
204
|
+
"""
|
|
205
|
+
Print to the console that the pv is connected.
|
|
206
|
+
:return: None
|
|
207
|
+
"""
|
|
208
|
+
message = "{}: Exception cleared".format(str(datetime.now()))
|
|
209
|
+
print(message)
|
|
210
|
+
self._api.logger.log_info_msg(message)
|
|
211
|
+
|
|
212
|
+
def enter(self) -> None:
|
|
213
|
+
"""
|
|
214
|
+
When first entering this state print the the pv is connected.
|
|
215
|
+
:return: None
|
|
216
|
+
"""
|
|
217
|
+
self.print_exception_cleared()
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class WaitForControllerExceptionContext(object):
|
|
221
|
+
"""
|
|
222
|
+
The exception context of the WaitForController when it is
|
|
223
|
+
in the loop in the start_waiting method
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
def __init__(self, api: "API") -> None:
|
|
227
|
+
"""
|
|
228
|
+
Set the starting state of the context.
|
|
229
|
+
:param api: The api for logging
|
|
230
|
+
"""
|
|
231
|
+
self._state = WaitForControllerState.start(api, context=self)
|
|
232
|
+
|
|
233
|
+
def process_exception(self, exception: Exception | None) -> None:
|
|
234
|
+
"""
|
|
235
|
+
Delegate the processing of the exception to the state
|
|
236
|
+
and create the new state based on what is returned.
|
|
237
|
+
:param exception: The exception to process
|
|
238
|
+
:return: None
|
|
239
|
+
"""
|
|
240
|
+
if self._state is not None:
|
|
241
|
+
self._state = self._state.process_exception(exception)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class WaitForController(object):
|
|
245
|
+
"""
|
|
246
|
+
Controller for waiting for states
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
def __init__(self, api: "API") -> None:
|
|
250
|
+
self.api = api
|
|
251
|
+
self.time_delta = None
|
|
252
|
+
self.start_time = None
|
|
253
|
+
self.block = None
|
|
254
|
+
self.low = None
|
|
255
|
+
self.high = None
|
|
256
|
+
|
|
257
|
+
def start_waiting(
|
|
258
|
+
self,
|
|
259
|
+
block: str | None = None,
|
|
260
|
+
value: WAITFOR_VALUE | None = None,
|
|
261
|
+
lowlimit: float | int | None = None,
|
|
262
|
+
highlimit: float | int | None = None,
|
|
263
|
+
maxwait: float | None = None,
|
|
264
|
+
wait_all: bool = False,
|
|
265
|
+
seconds: float | None = None,
|
|
266
|
+
minutes: float | None = None,
|
|
267
|
+
hours: float | None = None,
|
|
268
|
+
time: str | None = None,
|
|
269
|
+
frames: int | None = None,
|
|
270
|
+
raw_frames: int | None = None,
|
|
271
|
+
uamps: float | None = None,
|
|
272
|
+
mevents: float | None = None,
|
|
273
|
+
early_exit: Callable[[], bool] = lambda: False,
|
|
274
|
+
quiet: bool = False,
|
|
275
|
+
) -> None:
|
|
276
|
+
"""
|
|
277
|
+
Wait until a condition is reached. If wait_all is False then wait for one of the
|
|
278
|
+
conditions if True wait until all are reached
|
|
279
|
+
Args:
|
|
280
|
+
block: wait for a block to become a value
|
|
281
|
+
value: the value the block should become
|
|
282
|
+
lowlimit: the low limit, the value should be above this value
|
|
283
|
+
highlimit: the high limit, the value should be below this value
|
|
284
|
+
maxwait: maximum time in seconds to wait for the state to be reached
|
|
285
|
+
wait_all: True wait for all conditions to be reached; False wait for one condition
|
|
286
|
+
to be reached
|
|
287
|
+
seconds: number of seconds to wait
|
|
288
|
+
minutes: number of minutes to wait
|
|
289
|
+
hours: number of hours to wait
|
|
290
|
+
time: total time to wait (overrides seconds minutes and hours)
|
|
291
|
+
frames: number of frames to wait
|
|
292
|
+
raw_frames: number of raw frames to wait
|
|
293
|
+
uamps: number of micro amps to wait
|
|
294
|
+
mevents: number of millions of events to wait
|
|
295
|
+
early_exit: function to check if wait should exit early. Function should return true
|
|
296
|
+
to exit wait.
|
|
297
|
+
quiet: Suppress confirmation and countdown output to the console
|
|
298
|
+
(warning/error messages still sent)
|
|
299
|
+
|
|
300
|
+
Returns: nothing
|
|
301
|
+
|
|
302
|
+
"""
|
|
303
|
+
# Error checks
|
|
304
|
+
timeout_msg = ""
|
|
305
|
+
self._print_if_not_quiet(
|
|
306
|
+
"Start time: {}".format(datetime.now().time().strftime("%H:%M:%S")), quiet
|
|
307
|
+
)
|
|
308
|
+
self.api.logger.log_info_msg("WAITFOR STARTED")
|
|
309
|
+
if maxwait is not None:
|
|
310
|
+
if not isinstance(maxwait, NUMERIC_TYPE):
|
|
311
|
+
raise Exception("The value entered for maxwait was invalid, it should be numeric.")
|
|
312
|
+
else:
|
|
313
|
+
timeout_msg = "[timeout={}]".format(timedelta(seconds=maxwait).total_seconds())
|
|
314
|
+
if seconds is not None and not isinstance(seconds, NUMERIC_TYPE):
|
|
315
|
+
raise Exception("Invalid value entered for seconds")
|
|
316
|
+
if minutes is not None and not isinstance(minutes, NUMERIC_TYPE):
|
|
317
|
+
raise Exception("Invalid value entered for minutes")
|
|
318
|
+
if hours is not None and not isinstance(hours, NUMERIC_TYPE):
|
|
319
|
+
raise Exception("Invalid value entered for hours")
|
|
320
|
+
if time is not None:
|
|
321
|
+
try:
|
|
322
|
+
ans = strptime(time, "%H:%M:%S")
|
|
323
|
+
seconds = ans.tm_sec
|
|
324
|
+
minutes = ans.tm_min
|
|
325
|
+
hours = ans.tm_hour
|
|
326
|
+
except Exception:
|
|
327
|
+
raise Exception(
|
|
328
|
+
"Time string entered was invalid. It should be of the form HH:MM:SS"
|
|
329
|
+
)
|
|
330
|
+
if frames is not None:
|
|
331
|
+
if not isinstance(frames, int):
|
|
332
|
+
raise Exception("Invalid value entered for frames")
|
|
333
|
+
else:
|
|
334
|
+
self._print_if_not_quiet(
|
|
335
|
+
"Waiting for {} frames {}".format(frames, timeout_msg), quiet
|
|
336
|
+
)
|
|
337
|
+
if raw_frames is not None:
|
|
338
|
+
if not isinstance(raw_frames, int):
|
|
339
|
+
raise Exception("Invalid value entered for raw_frames")
|
|
340
|
+
else:
|
|
341
|
+
self._print_if_not_quiet(
|
|
342
|
+
"Waiting for {} raw frames {}".format(raw_frames, timeout_msg), quiet
|
|
343
|
+
)
|
|
344
|
+
if uamps is not None:
|
|
345
|
+
if not (isinstance(uamps, NUMERIC_TYPE)):
|
|
346
|
+
raise Exception("Invalid value entered for uamps")
|
|
347
|
+
else:
|
|
348
|
+
self._print_if_not_quiet(
|
|
349
|
+
"Waiting for {} uamps {}".format(uamps, timeout_msg), quiet
|
|
350
|
+
)
|
|
351
|
+
if mevents is not None:
|
|
352
|
+
if not (isinstance(mevents, NUMERIC_TYPE)):
|
|
353
|
+
raise Exception("Invalid value entered for mevents")
|
|
354
|
+
else:
|
|
355
|
+
self._print_if_not_quiet(
|
|
356
|
+
"Waiting for {} million events {}".format(mevents, timeout_msg), quiet
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
if block is not None:
|
|
360
|
+
if not self.api.block_exists(block):
|
|
361
|
+
raise NameError('No block with the name "{}" exists'.format(block))
|
|
362
|
+
block = self.api.correct_blockname(block, add_prefix=False)
|
|
363
|
+
if value is not None and (not isinstance(value, NUMERIC_TYPE + (str,))):
|
|
364
|
+
raise Exception(
|
|
365
|
+
"The value entered for the block was invalid, it should be numeric or a string."
|
|
366
|
+
)
|
|
367
|
+
if lowlimit is not None and (not isinstance(lowlimit, NUMERIC_TYPE)):
|
|
368
|
+
raise Exception("The value entered for lowlimit was invalid, it should be numeric.")
|
|
369
|
+
if highlimit is not None and (not isinstance(highlimit, NUMERIC_TYPE)):
|
|
370
|
+
raise Exception(
|
|
371
|
+
"The value entered for highlimit was invalid, it should be numeric."
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
self._init_wait_time(seconds, minutes, hours, quiet, timeout_msg)
|
|
375
|
+
self._init_wait_block(block, value, lowlimit, highlimit, quiet, timeout_msg)
|
|
376
|
+
start_time = datetime.now()
|
|
377
|
+
|
|
378
|
+
# Start with a state where the pv is considered connected and
|
|
379
|
+
# there is no need to notify of it
|
|
380
|
+
context = WaitForControllerExceptionContext(self.api)
|
|
381
|
+
|
|
382
|
+
while True:
|
|
383
|
+
if maxwait is not None:
|
|
384
|
+
if datetime.now() - start_time >= timedelta(seconds=maxwait):
|
|
385
|
+
print("Waitfor timed out after {} seconds".format(maxwait))
|
|
386
|
+
print("End time: {}".format(datetime.now().time().strftime("%H:%M:%S")))
|
|
387
|
+
self.api.logger.log_info_msg("WAITFOR TIMED OUT")
|
|
388
|
+
return
|
|
389
|
+
|
|
390
|
+
if early_exit():
|
|
391
|
+
print("Early exit handler evaluated to true in waitfor - stopping wait")
|
|
392
|
+
print("End time: {}".format(datetime.now().time().strftime("%H:%M:%S")))
|
|
393
|
+
self.api.logger.log_info_msg(
|
|
394
|
+
"EARLY EXIT HANDLER REACHED IN WAITFOR - STOPPING WAIT"
|
|
395
|
+
)
|
|
396
|
+
return
|
|
397
|
+
|
|
398
|
+
try:
|
|
399
|
+
res = []
|
|
400
|
+
if self.block is not None:
|
|
401
|
+
res.append(self._block_has_waited_for_value())
|
|
402
|
+
if self.start_time is not None and self.time_delta is not None:
|
|
403
|
+
res.append(self._waiting_for_time())
|
|
404
|
+
if frames is not None:
|
|
405
|
+
res.append(self.api.dae.get_good_frames() < frames)
|
|
406
|
+
if raw_frames is not None:
|
|
407
|
+
res.append(self.api.dae.get_raw_frames() < raw_frames)
|
|
408
|
+
if uamps is not None:
|
|
409
|
+
res.append(self.api.dae.get_uamps() < uamps)
|
|
410
|
+
if mevents is not None:
|
|
411
|
+
res.append(self.api.dae.get_mevents() < mevents)
|
|
412
|
+
# Notify the context that no exception has occurred
|
|
413
|
+
context.process_exception(None)
|
|
414
|
+
|
|
415
|
+
if len(res) == 0:
|
|
416
|
+
self.api.logger.log_error_msg("NO VALID WAITFOR CONDITIONS PROVIDED")
|
|
417
|
+
print("End time: {}".format(datetime.now().time().strftime("%H:%M:%S")))
|
|
418
|
+
return
|
|
419
|
+
elif (wait_all and True not in res) or (not wait_all and False in res):
|
|
420
|
+
self.api.logger.log_info_msg("WAITFOR EXITED NORMALLY")
|
|
421
|
+
self._print_if_not_quiet(
|
|
422
|
+
"End time: {}".format(datetime.now().time().strftime("%H:%M:%S")), quiet
|
|
423
|
+
)
|
|
424
|
+
return
|
|
425
|
+
except Exception as e:
|
|
426
|
+
# Notify the context that an exception has occurred
|
|
427
|
+
context.process_exception(e)
|
|
428
|
+
|
|
429
|
+
sleep(DELAY_IN_WAIT_FOR_SLEEP_LOOP)
|
|
430
|
+
check_break(2)
|
|
431
|
+
|
|
432
|
+
def _print_if_not_quiet(self, text: str, quiet: bool) -> None:
|
|
433
|
+
if not quiet:
|
|
434
|
+
print(text)
|
|
435
|
+
|
|
436
|
+
def wait_for_runstate(
|
|
437
|
+
self,
|
|
438
|
+
state: str,
|
|
439
|
+
maxwaitsecs: int = 3600,
|
|
440
|
+
onexit: bool = False,
|
|
441
|
+
quiet: bool = False,
|
|
442
|
+
) -> None:
|
|
443
|
+
"""
|
|
444
|
+
Wait for a given run state
|
|
445
|
+
Args:
|
|
446
|
+
state: the run state to wait for
|
|
447
|
+
maxwaitsecs: maximum time to wait
|
|
448
|
+
onexit: if True wait only as long as in transitional state;
|
|
449
|
+
False wait whatever the current state
|
|
450
|
+
quiet: This has been added in alignment with
|
|
451
|
+
genie_simulate_impl.wait_for_runstate()
|
|
452
|
+
|
|
453
|
+
Returns: nothing
|
|
454
|
+
|
|
455
|
+
"""
|
|
456
|
+
time_delta = timedelta(seconds=maxwaitsecs)
|
|
457
|
+
state = state.upper().strip()
|
|
458
|
+
start_time = datetime.now()
|
|
459
|
+
while True:
|
|
460
|
+
# The sleep is to allow run control to set state before wait_for_runstate
|
|
461
|
+
# (it polls on 0.1s loop). This is
|
|
462
|
+
# in case user does cset(x, lim=...), waitfor("RUNNING") - without a wait this
|
|
463
|
+
# might not catch the run
|
|
464
|
+
# state change from run control to waiting.
|
|
465
|
+
sleep(0.3)
|
|
466
|
+
check_break(2)
|
|
467
|
+
curr = self.api.dae.get_run_state()
|
|
468
|
+
if onexit:
|
|
469
|
+
if curr != state and not self.api.dae.in_transition():
|
|
470
|
+
self.api.logger.log_info_msg("WAITFOR_RUNSTATE ONEXIT STATE EXITED")
|
|
471
|
+
break
|
|
472
|
+
else:
|
|
473
|
+
if curr == state:
|
|
474
|
+
self.api.logger.log_info_msg("WAITFOR_RUNSTATE STATE REACHED")
|
|
475
|
+
break
|
|
476
|
+
# Check for timeout
|
|
477
|
+
if datetime.now() - start_time >= time_delta:
|
|
478
|
+
self.api.logger.log_info_msg("WAITFOR_RUNSTATE TIMED OUT")
|
|
479
|
+
break
|
|
480
|
+
|
|
481
|
+
def _init_wait_time(
|
|
482
|
+
self,
|
|
483
|
+
seconds: float | int | None,
|
|
484
|
+
minutes: float | int | None,
|
|
485
|
+
hours: float | int | None,
|
|
486
|
+
quiet: bool = False,
|
|
487
|
+
timeout_msg: str = "",
|
|
488
|
+
) -> None:
|
|
489
|
+
self.time_delta = get_time_delta(seconds, minutes, hours)
|
|
490
|
+
if self.time_delta is not None:
|
|
491
|
+
self.start_time = datetime.now()
|
|
492
|
+
self._print_if_not_quiet(
|
|
493
|
+
"Waiting for {} seconds {}".format(self.time_delta.total_seconds(), timeout_msg),
|
|
494
|
+
quiet,
|
|
495
|
+
)
|
|
496
|
+
else:
|
|
497
|
+
self.start_time = None
|
|
498
|
+
|
|
499
|
+
def _waiting_for_time(self) -> bool:
|
|
500
|
+
if (self.start_time is None) or (self.time_delta is None):
|
|
501
|
+
raise Exception("start_time and time_delta must not be None")
|
|
502
|
+
else:
|
|
503
|
+
if datetime.now() - self.start_time >= self.time_delta:
|
|
504
|
+
return False
|
|
505
|
+
else:
|
|
506
|
+
return True
|
|
507
|
+
|
|
508
|
+
def _init_wait_block(
|
|
509
|
+
self,
|
|
510
|
+
block: str | None,
|
|
511
|
+
value: WAITFOR_VALUE,
|
|
512
|
+
lowlimit: float | int | None,
|
|
513
|
+
highlimit: float | int | None,
|
|
514
|
+
quiet: bool = False,
|
|
515
|
+
timeout_msg: str = "",
|
|
516
|
+
) -> None:
|
|
517
|
+
self.block = block
|
|
518
|
+
if self.block is None:
|
|
519
|
+
return
|
|
520
|
+
self.low, self.high = self._get_block_limits(value, lowlimit, highlimit)
|
|
521
|
+
if self.low is None and self.high is None:
|
|
522
|
+
raise Exception("No limit(s) set for {0}".format(block))
|
|
523
|
+
if self.low == self.high:
|
|
524
|
+
self._print_if_not_quiet(
|
|
525
|
+
"Waiting for {0}={1}{2}".format(str(block), str(self.low), timeout_msg), quiet
|
|
526
|
+
)
|
|
527
|
+
else:
|
|
528
|
+
self._print_if_not_quiet(
|
|
529
|
+
"Waiting for {0} (lowlimit={1}, highlimit={2}){3}".format(
|
|
530
|
+
str(block), str(self.low), str(self.high), timeout_msg
|
|
531
|
+
),
|
|
532
|
+
quiet,
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
def _get_block_limits(
|
|
536
|
+
self, value: WAITFOR_VALUE, lowlimit: float | int | None, highlimit: float | int | None
|
|
537
|
+
) -> tuple[WAITFOR_VALUE | None, WAITFOR_VALUE | None]:
|
|
538
|
+
low = None
|
|
539
|
+
high = None
|
|
540
|
+
if value is not None:
|
|
541
|
+
low = high = value
|
|
542
|
+
if isinstance(lowlimit, NUMERIC_TYPE):
|
|
543
|
+
low = lowlimit
|
|
544
|
+
if isinstance(highlimit, NUMERIC_TYPE):
|
|
545
|
+
high = highlimit
|
|
546
|
+
# Check low and high are round the correct way
|
|
547
|
+
if isinstance(low, NUMERIC_TYPE) and isinstance(high, NUMERIC_TYPE) and low > high:
|
|
548
|
+
low, high = high, low
|
|
549
|
+
print(
|
|
550
|
+
"WARNING: The highlimit and lowlimit have been \
|
|
551
|
+
swapped to lowlimit({}) and highlimit({})".format(low, high)
|
|
552
|
+
)
|
|
553
|
+
return low, high
|
|
554
|
+
|
|
555
|
+
def _block_has_waited_for_value(self) -> bool:
|
|
556
|
+
"""
|
|
557
|
+
Return True if the block is above any low limit and below any high limit. In the case
|
|
558
|
+
of a string type it is if it is at the low limit.
|
|
559
|
+
|
|
560
|
+
:return: true of the block has the value that is being waited for; False otherwise
|
|
561
|
+
"""
|
|
562
|
+
flag = True
|
|
563
|
+
currval = self.api.get_block_value(self.block)
|
|
564
|
+
if not isinstance(currval, NUMERIC_TYPE):
|
|
565
|
+
# pv is a string values so just test
|
|
566
|
+
flag = currval == self.low
|
|
567
|
+
else:
|
|
568
|
+
try:
|
|
569
|
+
if self.low is not None:
|
|
570
|
+
flag = currval >= float(self.low)
|
|
571
|
+
if self.high is not None:
|
|
572
|
+
flag = currval <= float(self.high) and flag
|
|
573
|
+
except ValueError:
|
|
574
|
+
# pv is a string values so just test
|
|
575
|
+
flag = currval == self.low
|
|
576
|
+
return not flag
|
|
File without changes
|