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.
Files changed (45) hide show
  1. genie_python/.pylintrc +539 -0
  2. genie_python/__init__.py +1 -0
  3. genie_python/_version.py +16 -0
  4. genie_python/block_names.py +123 -0
  5. genie_python/channel_access_exceptions.py +45 -0
  6. genie_python/genie.py +2462 -0
  7. genie_python/genie_advanced.py +418 -0
  8. genie_python/genie_alerts.py +195 -0
  9. genie_python/genie_api_setup.py +451 -0
  10. genie_python/genie_blockserver.py +64 -0
  11. genie_python/genie_cachannel_wrapper.py +551 -0
  12. genie_python/genie_change_cache.py +151 -0
  13. genie_python/genie_dae.py +2219 -0
  14. genie_python/genie_epics_api.py +906 -0
  15. genie_python/genie_experimental_data.py +186 -0
  16. genie_python/genie_logging.py +200 -0
  17. genie_python/genie_p4p_wrapper.py +203 -0
  18. genie_python/genie_plot.py +77 -0
  19. genie_python/genie_pre_post_cmd_manager.py +21 -0
  20. genie_python/genie_pv_connection_protocol.py +36 -0
  21. genie_python/genie_script_checker.py +507 -0
  22. genie_python/genie_script_generator.py +212 -0
  23. genie_python/genie_simulate.py +69 -0
  24. genie_python/genie_simulate_impl.py +1265 -0
  25. genie_python/genie_startup.py +29 -0
  26. genie_python/genie_toggle_settings.py +58 -0
  27. genie_python/genie_wait_for_move.py +154 -0
  28. genie_python/genie_waitfor.py +576 -0
  29. genie_python/matplotlib_backend/__init__.py +0 -0
  30. genie_python/matplotlib_backend/ibex_websocket_backend.py +366 -0
  31. genie_python/mysql_abstraction_layer.py +272 -0
  32. genie_python/scanning_instrument_pylint_plugin.py +31 -0
  33. genie_python/testing_utils/__init__.py +4 -0
  34. genie_python/testing_utils/script_checker.py +63 -0
  35. genie_python/typings/CaChannel/CaChannel.pyi +893 -0
  36. genie_python/typings/CaChannel/__init__.pyi +9 -0
  37. genie_python/typings/CaChannel/_version.pyi +6 -0
  38. genie_python/typings/CaChannel/ca.pyi +31 -0
  39. genie_python/utilities.py +406 -0
  40. genie_python/version.py +6 -0
  41. genie_python-15.1.0.dist-info/LICENSE +28 -0
  42. genie_python-15.1.0.dist-info/METADATA +84 -0
  43. genie_python-15.1.0.dist-info/RECORD +45 -0
  44. genie_python-15.1.0.dist-info/WHEEL +5 -0
  45. genie_python-15.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,551 @@
1
+ """
2
+ Wrapping of channel access in genie_python
3
+ """
4
+
5
+ from __future__ import absolute_import, print_function
6
+
7
+ import os
8
+ import threading
9
+ from builtins import object
10
+ from collections.abc import Callable
11
+ from threading import Event
12
+ from typing import TYPE_CHECKING, Optional, Tuple, TypeVar
13
+
14
+ from CaChannel import CaChannel, CaChannelException, ca
15
+
16
+ try:
17
+ from CaChannel._ca import (
18
+ AlarmCondition,
19
+ AlarmSeverity,
20
+ dbf_type_to_DBR_STS,
21
+ dbf_type_to_DBR_TIME,
22
+ )
23
+ except ImportError:
24
+ # Note: caffi dynamically added to dependencies by CaChannel if not using built backend.
25
+ from caffi.ca import ( # type: ignore[reportMissingImports]
26
+ AlarmCondition,
27
+ AlarmSeverity,
28
+ dbf_type_to_DBR_STS,
29
+ dbf_type_to_DBR_TIME,
30
+ )
31
+
32
+ if TYPE_CHECKING:
33
+ from genie_python.genie import PVValue
34
+
35
+ from .channel_access_exceptions import (
36
+ InvalidEnumStringException,
37
+ ReadAccessException,
38
+ UnableToConnectToPVException,
39
+ WriteAccessException,
40
+ )
41
+ from .utilities import waveform_to_string
42
+
43
+ TIMEOUT = 15 # Default timeout for PV set/get
44
+ EXIST_TIMEOUT = 3 # Separate smaller timeout for pv_exists() and searchw() operations
45
+ CACHE = threading.local()
46
+ CACHE_LOCK = threading.local()
47
+ T = TypeVar("T")
48
+
49
+
50
+ class CaChannelWrapper(object):
51
+ """
52
+ Wrap CA Channel access to give utilities methods for access in one place
53
+ """
54
+
55
+ error_log_func: Optional[Callable[[str], None]] = None
56
+
57
+ # noinspection PyPep8Naming
58
+ @staticmethod
59
+ def logError(message: str): # noqa N802
60
+ """
61
+ Log an error
62
+ Args:
63
+ message: message to log
64
+ """
65
+ if CaChannelWrapper.error_log_func is not None:
66
+ try:
67
+ CaChannelWrapper.error_log_func(message)
68
+ except Exception:
69
+ pass
70
+ else:
71
+ print("CAERROR: {}".format(message))
72
+
73
+ # noinspection PyPep8Naming
74
+ @staticmethod
75
+ def printfHandler(message: str, user_args: Tuple[T, ...]) -> None: # noqa N802
76
+ """
77
+ Callback used for CA printing messages.
78
+
79
+ Args:
80
+ message (string): Contains the results of the action.
81
+ user_args (tuple): Contains any extra arguments supplied to the call.
82
+
83
+ Returns:
84
+ None.
85
+ """
86
+ CaChannelWrapper.logError("CAMessage: {}".format(message))
87
+
88
+ # noinspection PyPep8Naming
89
+ @staticmethod
90
+ def CAExceptionHandler(epics_args: dict[str, str], user_args: Tuple[None]) -> None: # noqa N802
91
+ """
92
+ Callback used for CA exception messages.
93
+
94
+ Args:
95
+ epics_args (dict): Contains the results of the action - see C struct
96
+ "exception_handler_args"
97
+ Available ones are: chid, type, count, state, op, ctx, file, lineNo
98
+
99
+ user_args (dict): Contains any extra arguments supplied to the call.
100
+
101
+ Returns:
102
+ None.
103
+ """
104
+ CaChannelWrapper.logError(
105
+ "CAException: ctx={} type={} state={} op={} file={} lineNo={}".format(
106
+ epics_args["ctx"],
107
+ epics_args["type"],
108
+ epics_args["state"],
109
+ epics_args["op"],
110
+ epics_args["file"],
111
+ epics_args["lineNo"],
112
+ )
113
+ )
114
+
115
+ # noinspection PyPep8Naming
116
+ @staticmethod
117
+ def installHandlers(chan: CaChannel) -> None: # noqa N802
118
+ """
119
+ Installs callbacks for printf and exceptions.
120
+
121
+ Args:
122
+ chan: CaChannel instance
123
+
124
+ Returns:
125
+ None.
126
+ """
127
+ # We do a poll() so ca_context_create() gets called with arguments to enable preemptive
128
+ # callbacks CaChannel itself delays creation of the context, so if we just installed the
129
+ # handlers now we would get a default non-preemptive CA context created.
130
+ chan.poll()
131
+ try:
132
+ chan.replace_printf_handler(CaChannelWrapper.printfHandler)
133
+ except AttributeError:
134
+ # If we can't replace the printf handler, ignore that error - it is not crucial.
135
+ # It probably means we are using default CaChannel, as opposed to ISIS' special build.
136
+ # Cope with both cases.
137
+ pass
138
+ chan.add_exception_event(CaChannelWrapper.CAExceptionHandler)
139
+
140
+ # noinspection PyPep8Naming
141
+ @staticmethod
142
+ def putCB(epics_args: Tuple[str, int, int, int], user_args: Tuple[Event, ...]) -> None: # noqa N802
143
+ """
144
+ Callback used for setting PV values.
145
+
146
+ Args:
147
+ epics_args (tuple): Contains the results of the action.
148
+ user_args (tuple): Contains any extra arguments supplied to the call.
149
+
150
+ Returns:
151
+ None.
152
+ """
153
+ user_args[0].set()
154
+
155
+ @staticmethod
156
+ def set_pv_value(
157
+ name: str,
158
+ value: "PVValue",
159
+ wait: bool = False,
160
+ timeout: float = TIMEOUT,
161
+ safe_not_quick: bool = True,
162
+ ) -> None:
163
+ """
164
+ Set the PV to a value.
165
+
166
+ When getting a PV value this call should be used, unless there is a special requirement.
167
+
168
+ Args:
169
+ name (string): The PV name.
170
+ value: The value to set.
171
+ wait (bool, optional): Wait for the value to be set before returning.
172
+ timeout (optional): How long to wait for the PV to connect etc.
173
+ safe_not_quick (bool): True run all checks while setting the pv, False don't run checks
174
+ just write the value, e.g. disp check
175
+
176
+ Returns:
177
+ None.
178
+
179
+ Raises:
180
+ UnableToConnectToPVException: If cannot connect to PV.
181
+ WriteAccessException: If write access is denied.
182
+ InvalidEnumStringException: If the PV is an enum and the string value supplied is not a
183
+ valid enum value.
184
+ """
185
+ chan = CaChannelWrapper.get_chan(name)
186
+ chan.setTimeout(timeout)
187
+
188
+ # Validate user input and format accordingly for mbbi/bi records
189
+ value = CaChannelWrapper.check_for_enum_value(value, chan, name)
190
+
191
+ if not chan.write_access():
192
+ raise WriteAccessException(name)
193
+ if safe_not_quick:
194
+ CaChannelWrapper._check_for_disp(name)
195
+ if wait:
196
+ ftype = chan.field_type()
197
+ ecount = chan.element_count()
198
+ event = Event()
199
+ chan.array_put_callback(value, ftype, ecount, CaChannelWrapper.putCB, event)
200
+ CaChannelWrapper._wait_for_pend_event(chan, event, timeout=None)
201
+ else:
202
+ # putw() flushes send buffer, but doesn't wait for a CA completion callback
203
+ # Write value to PV, or produce error
204
+ chan.putw(value)
205
+
206
+ @staticmethod
207
+ def _check_for_disp(name: str) -> None:
208
+ """
209
+ Check if DISP is set on a PV. If passed a field instead of a PV, do nothing.
210
+ Only check DISP if it exists.
211
+ """
212
+ if (
213
+ ".DISP" not in name
214
+ ): # Do not check for DISP if it's already in the name of the PV to check
215
+ if "." in name: # If given a field on a PV, check the PV itself if DISP is set
216
+ name = name.split(".")[0]
217
+ _disp_name = "{}.DISP".format(name)
218
+ if (
219
+ CaChannelWrapper.pv_exists(_disp_name, 0)
220
+ and CaChannelWrapper.get_pv_value(_disp_name) != "0"
221
+ ):
222
+ raise WriteAccessException("{} (DISP is set)".format(name))
223
+
224
+ @staticmethod
225
+ def get_chan(name: str, timeout: float = EXIST_TIMEOUT) -> CaChannel:
226
+ """
227
+ Gets a channel based on a channel name, from the cache if it exists.
228
+
229
+ Args:
230
+ name: the name of the channel to get
231
+ timeout: timeout to set on channel
232
+
233
+ Returns:
234
+ CaChannel object representing the channel
235
+
236
+ Raises:
237
+ UnableToConnectToPVException if it was unable to connect to the channel
238
+ """
239
+
240
+ try:
241
+ lock = CACHE_LOCK.lock
242
+ except AttributeError:
243
+ lock = CACHE_LOCK.lock = threading.RLock()
244
+
245
+ with lock:
246
+ try:
247
+ pv_map = CACHE.map
248
+ except AttributeError:
249
+ pv_map = CACHE.map = {}
250
+
251
+ if name in list(pv_map.keys()) and pv_map[name].state() == ca.cs_conn:
252
+ chan = pv_map[name]
253
+ else:
254
+ chan = CaChannel(name)
255
+ # noinspection PyTypeChecker
256
+ CaChannelWrapper.installHandlers(chan)
257
+ chan.setTimeout(timeout)
258
+ # Try to connect - throws if cannot
259
+ CaChannelWrapper.connect_to_pv(chan)
260
+ pv_map[name] = chan
261
+ return chan
262
+
263
+ @staticmethod
264
+ def clear_monitor(name: str, timeout: float = EXIST_TIMEOUT) -> None:
265
+ channel = CaChannelWrapper.get_chan(name, timeout)
266
+ channel.clear_channel()
267
+
268
+ @staticmethod
269
+ def get_pv_value(
270
+ name: str, to_string: bool = False, timeout: float = TIMEOUT, use_numpy: bool | None = None
271
+ ) -> "PVValue":
272
+ """
273
+ Get the current value of the PV.
274
+
275
+ Args:
276
+ name (name): The PV.
277
+ to_string (bool, optional): Whether to convert the value to a string.
278
+ timeout (optional): How long to wait for the PV to connect etc.
279
+ use_numpy (None|boolean): True use numpy to return arrays, False return a list;
280
+ None for use the default
281
+
282
+ Returns:
283
+ The PV value.
284
+
285
+ Raises:
286
+ UnableToConnectToPVException: If cannot connect to PV.
287
+ ReadAccessException: If read access is denied.
288
+ """
289
+ chan = CaChannelWrapper.get_chan(name)
290
+ chan.setTimeout(timeout)
291
+ if not chan.read_access():
292
+ raise ReadAccessException(name)
293
+ ftype = chan.field_type()
294
+ if ca.dbr_type_is_ENUM(ftype) or ca.dbr_type_is_CHAR(ftype) or ca.dbr_type_is_STRING(ftype):
295
+ to_string = True
296
+ if to_string:
297
+ if ca.dbr_type_is_ENUM(ftype) or ca.dbr_type_is_STRING(ftype):
298
+ value = chan.getw(ca.DBR_STRING)
299
+ else:
300
+ # If we get a numeric using ca.DBR_CHAR the value still comes back as a numeric
301
+ # In other words, it does not get cast to char
302
+ value = chan.getw(ca.DBR_CHAR)
303
+ # Could see if the element count is > 1 instead
304
+ if isinstance(value, list):
305
+ return waveform_to_string(value)
306
+ else:
307
+ return str(value)
308
+ else:
309
+ if use_numpy is None:
310
+ output = chan.getw()
311
+ else:
312
+ output = chan.getw(use_numpy=use_numpy)
313
+ assert not isinstance(output, dict)
314
+ return output
315
+
316
+ @staticmethod
317
+ def get_pv_timestamp(name: str, timeout: float = TIMEOUT) -> Tuple[int, int]:
318
+ """
319
+ Get the timestamp of when the PV was last processed.
320
+
321
+ Args:
322
+ name (name): The PV.
323
+ timeout (optional): How long to wait for the PV to connect etc.
324
+
325
+ Returns:
326
+ tuple of: (seconds, nanoseconds)
327
+
328
+ Raises:
329
+ UnableToConnectToPVException: If cannot connect to PV.
330
+ ReadAccessException: If read access is denied.
331
+ """
332
+ chan = CaChannelWrapper.get_chan(name)
333
+ chan.setTimeout(timeout)
334
+ if not chan.read_access():
335
+ raise ReadAccessException(name)
336
+ ftype = chan.field_type()
337
+ info = chan.getw(dbf_type_to_DBR_TIME(ftype))
338
+ assert isinstance(info, dict)
339
+ return info["pv_seconds"], info["pv_nseconds"]
340
+
341
+ @staticmethod
342
+ def pv_exists(name: str, timeout: float = EXIST_TIMEOUT) -> bool:
343
+ """
344
+ See if the PV exists.
345
+
346
+ Args:
347
+ name (string): The PV name.
348
+ timeout(optional): How long to wait for the PV to "appear".
349
+
350
+ Returns:
351
+ True if exists, otherwise False.
352
+ """
353
+ try:
354
+ chan = CaChannelWrapper.get_chan(name, timeout)
355
+ CaChannelWrapper.connect_to_pv(chan)
356
+ return True
357
+ except UnableToConnectToPVException:
358
+ return False
359
+
360
+ @staticmethod
361
+ def connect_to_pv(ca_channel: CaChannel) -> None:
362
+ """
363
+ Connects to the PV.
364
+
365
+ Args:
366
+ ca_channel (CaChannel): The channel to connect to.
367
+
368
+ Returns:
369
+ None.
370
+
371
+ Raises:
372
+ UnableToConnectToPVException: If cannot connect to PV.
373
+ """
374
+ if os.getenv("GITHUB_ACTIONS"):
375
+ # genie_python does some PV accesses on import. To avoid them timing out and making CI
376
+ # builds really slow, shortcut every PV to "non-existent" here.
377
+ raise UnableToConnectToPVException("", "In CI")
378
+
379
+ event = Event()
380
+ try:
381
+ ca_channel.search_and_connect(None, CaChannelWrapper.putCB, event)
382
+ except CaChannelException as e:
383
+ raise UnableToConnectToPVException(ca_channel.name(), e)
384
+
385
+ ca_channel.flush_io()
386
+
387
+ # we do not need to call pend_event / poll as we are using preemptive callbacks
388
+ time_elapsed = 0.0
389
+ interval = 0.1
390
+ while True:
391
+ time_elapsed += interval
392
+ if event.wait(interval) or time_elapsed >= ca_channel.getTimeout():
393
+ break
394
+
395
+ if not event.is_set():
396
+ raise UnableToConnectToPVException(ca_channel.name(), "Connection timeout (event)")
397
+
398
+ if ca_channel.state() != ca.cs_conn:
399
+ raise UnableToConnectToPVException(ca_channel.name(), "Connection timeout (state)")
400
+
401
+ @staticmethod
402
+ def check_for_enum_value(value: "PVValue", chan: CaChannel, name: str) -> "PVValue":
403
+ """
404
+ Check for string input for MBBI/BI records and replace with the equivalent index value.
405
+
406
+ Args:
407
+ value: The PV value.
408
+ chan (CaChannel): The channel access channel.
409
+ name (string): The name of the channel.
410
+
411
+ Returns:
412
+ Index value of enum, if the record is mbbi/bi. Otherwise, returns unmodified value.
413
+
414
+ Raises:
415
+ InvalidEnumStringException: If the string supplied is not a valid enum value.
416
+ """
417
+ # If PV is MBBI/BI type, search list of enum values and iterate to find a match
418
+ if ca.dbr_type_is_ENUM(chan.field_type()) and isinstance(value, str):
419
+ chan.array_get(ca.DBR_CTRL_ENUM)
420
+ chan.pend_io()
421
+ channel_properties = chan.getValue()
422
+ for index, enum_value in enumerate(channel_properties["pv_statestrings"]):
423
+ if enum_value.lower() == value.lower():
424
+ # Replace user input with enum index value
425
+ return index
426
+ # If the string entered isn't valid then throw
427
+ raise InvalidEnumStringException(name, channel_properties["pv_statestrings"])
428
+
429
+ return value
430
+
431
+ @staticmethod
432
+ def add_monitor(
433
+ name: str,
434
+ call_back_function: "Callable[[PVValue, str, str], None]",
435
+ link_alarm_on_disconnect: bool = True,
436
+ to_string: bool = False,
437
+ use_numpy: bool | None = None,
438
+ ) -> Callable[[], None]:
439
+ """
440
+ Add a callback to a pv which responds on a monitor (i.e. value change).
441
+ This currently only tested for numbers.
442
+ Args:
443
+ name: name of the pv
444
+ call_back_function: the callback function, arguments are value, alarm severity
445
+ (CaChannel._ca.AlarmSeverity), alarm status (CaChannel._ca.AlarmCondition)
446
+ link_alarm_on_disconnect: if set to True, a link alarm is sent with the last value
447
+ when the pv disconnects
448
+ use_numpy (bool, optional): True use numpy to return arrays,
449
+ False return a list; None for use the default
450
+ Returns:
451
+ unsubscribe event function
452
+ """
453
+ from CaChannel import USE_NUMPY
454
+
455
+ if use_numpy is None:
456
+ use_numpy = USE_NUMPY
457
+ chan = CaChannelWrapper.get_chan(name)
458
+ if not chan.read_access():
459
+ raise ReadAccessException(name)
460
+ field_type = chan.field_type()
461
+ # if this is an enum field return the monitor as a string (not an int)
462
+ if ca.dbr_type_is_ENUM(field_type):
463
+ field_type = ca.DBR_STRING
464
+ # Modify the field type from monitor the value to includes the alarm severity and status
465
+ field_type_with_status = dbf_type_to_DBR_STS(field_type)
466
+
467
+ def _process_call_back(epics_args: dict[str, str], _: dict[str, str]) -> None:
468
+ value = epics_args.get("pv_value", None)
469
+
470
+ if to_string:
471
+ # Could see if the element count is > 1 instead
472
+ if isinstance(value, list):
473
+ value = waveform_to_string(value)
474
+ else:
475
+ value = str(value)
476
+
477
+ chan.last_value = value
478
+ call_back_function(
479
+ value,
480
+ epics_args.get("pv_severity", AlarmSeverity.No),
481
+ epics_args.get("pv_status", AlarmCondition.No),
482
+ )
483
+
484
+ def _connection_callback(epics_args: Tuple[T, ...], _: dict[str, str]) -> None:
485
+ if epics_args[1] == ca.CA_OP_CONN_DOWN:
486
+ call_back_function(chan.last_value, AlarmSeverity.Invalid, AlarmCondition.Link)
487
+
488
+ chan.add_masked_array_event(
489
+ field_type_with_status,
490
+ count=None,
491
+ mask=None,
492
+ callback=_process_call_back,
493
+ use_numpy=use_numpy,
494
+ )
495
+ if link_alarm_on_disconnect:
496
+ chan.change_connection_event(_connection_callback)
497
+
498
+ return chan.clear_event
499
+
500
+ @staticmethod
501
+ def poll() -> None:
502
+ """
503
+ Flush the send buffer and execute any outstanding background activity for all connected pvs.
504
+ NB Connected pv is one which is in the cache
505
+ """
506
+ # pick first channel and perform flush on it.
507
+ try:
508
+ for key, value in CACHE.map.items():
509
+ value.poll()
510
+ break
511
+ except AttributeError:
512
+ # There are no channels so we do not need to poll them
513
+ pass
514
+
515
+ @staticmethod
516
+ def _wait_for_pend_event(
517
+ chan: CaChannel, event: Event, timeout: Optional[float] = None, interval: float = 0.1
518
+ ) -> None:
519
+ """
520
+ Wait for a pending event to occur in short intervals to allow for keyboard interrupt;
521
+ has possible timeout for maximum time to wait. This should be used for put operation
522
+ callbacks.
523
+
524
+ Args:
525
+ chan: channel to use
526
+ event: the event posted by the callback to wait for
527
+ timeout: maximum time to wait for the event, None means wait forever.
528
+ interval: time to poll channel access
529
+ """
530
+
531
+ time_elapsed = 0
532
+
533
+ while True:
534
+ # Should use overall timeout somehow? need to make sure it is long enough for
535
+ # all requests to complete did try flush_io() followed by event.wait(1.0) inside the
536
+ # loop for set pv, but a send got missed (this is what util/caput in CaChannel does with
537
+ # its wait is set to True)So looks like pend_event() / pend_io() / poll() is needed
538
+ # CaChannel example uses pend_event, pyepics seems to do both pend_io and pend_event
539
+ # According to docs, if using preemptive callbacks then only an initial flush_io()
540
+ # should be needed
541
+
542
+ status = chan.poll() # equivalent to pend_event() with a small timeout
543
+ if status != ca.ECA_TIMEOUT:
544
+ raise CaChannelException(status)
545
+
546
+ time_elapsed += interval
547
+ if event.wait(interval) or (timeout is not None and time_elapsed >= timeout):
548
+ break
549
+
550
+ if not event.is_set():
551
+ raise UnableToConnectToPVException(chan.name(), "Pend event timeout")
@@ -0,0 +1,151 @@
1
+ from builtins import object, str
2
+
3
+
4
+ class ChangeCache(object):
5
+ def __init__(self):
6
+ self.wiring = None
7
+ self.detector = None
8
+ self.spectra = None
9
+ self.mon_spect = None
10
+ self.mon_from = None
11
+ self.mon_to = None
12
+ self.dae_sync = None
13
+ self.tcb_file = None
14
+ self.tcb_tables = []
15
+ self.tcb_calculation_method = None
16
+ self.smp_veto = None
17
+ self.ts2_veto = None
18
+ self.hz50_veto = None
19
+ self.ext0_veto = None
20
+ self.ext1_veto = None
21
+ self.ext2_veto = None
22
+ self.ext3_veto = None
23
+ self.fermi_veto = None
24
+ self.fermi_delay = None
25
+ self.fermi_width = None
26
+ self.periods_soft_num = None
27
+ self.periods_type = None
28
+ self.periods_src = None
29
+ self.periods_file = None
30
+ self.periods_seq = None
31
+ self.periods_delay = None
32
+ self.periods_settings = []
33
+
34
+ def set_monitor(self, spec, low, high):
35
+ self.mon_spect = spec
36
+ self.mon_from = low
37
+ self.mon_to = high
38
+
39
+ def clear_vetos(self):
40
+ self.smp_veto = 0
41
+ self.ts2_veto = 0
42
+ self.hz50_veto = 0
43
+ self.ext0_veto = 0
44
+ self.ext1_veto = 0
45
+ self.ext2_veto = 0
46
+ self.ext3_veto = 0
47
+
48
+ def set_fermi(self, enable, delay=1.0, width=1.0):
49
+ self.fermi_veto = 1 if enable else 0
50
+ self.fermi_delay = delay
51
+ self.fermi_width = width
52
+
53
+ def change_dae_settings(self, root):
54
+ changed = self._change_xml(root, "String", "Wiring Table", self.wiring)
55
+ changed |= self._change_xml(root, "String", "Detector Table", self.detector)
56
+ changed |= self._change_xml(root, "String", "Spectra Table", self.spectra)
57
+ changed |= self._change_xml(root, "I32", "Monitor Spectrum", self.mon_spect)
58
+ changed |= self._change_xml(root, "DBL", "from", self.mon_from)
59
+ changed |= self._change_xml(root, "DBL", "to", self.mon_to)
60
+ changed |= self._change_xml(root, "EW", "DAETimingSource", self.dae_sync)
61
+
62
+ if self.fermi_veto is not None:
63
+ self._change_xml(root, "EW", " Fermi Chopper Veto", self.fermi_veto)
64
+ self._change_xml(root, "DBL", "FC Delay", self.fermi_delay)
65
+ self._change_xml(root, "DBL", "FC Width", self.fermi_width)
66
+ changed |= True
67
+
68
+ changed |= self._change_vetos(root)
69
+ return changed
70
+
71
+ def _change_vetos(self, root):
72
+ changed = self._change_xml(root, "EW", "SMP (Chopper) Veto", self.smp_veto)
73
+ changed |= self._change_xml(root, "EW", " TS2 Pulse Veto", self.ts2_veto)
74
+ changed |= self._change_xml(root, "EW", " ISIS 50Hz Veto", self.hz50_veto)
75
+ changed |= self._change_xml(root, "EW", "Veto 0", self.ext0_veto)
76
+ changed |= self._change_xml(root, "EW", "Veto 1", self.ext1_veto)
77
+ changed |= self._change_xml(root, "EW", "Veto 2", self.ext2_veto)
78
+ changed |= self._change_xml(root, "EW", "Veto 3", self.ext3_veto)
79
+ return changed
80
+
81
+ def change_tcb_calculation_method(self, root):
82
+ changed = self._change_xml(root, "U16", "Calculation Method", self.tcb_calculation_method)
83
+ return changed
84
+
85
+ def change_tcb_settings(self, root):
86
+ changed = self._change_xml(root, "String", "Time Channel File", self.tcb_file)
87
+ changed |= self.change_tcb_calculation_method(root)
88
+ changed |= self._change_tcb_table(root)
89
+ return changed
90
+
91
+ def _change_tcb_table(self, root):
92
+ changed = False
93
+ for row in self.tcb_tables:
94
+ regime = str(row[0])
95
+ trange = str(row[1])
96
+ changed |= self._change_xml(root, "DBL", "TR%s From %s" % (regime, trange), row[2])
97
+ changed |= self._change_xml(root, "DBL", "TR%s To %s" % (regime, trange), row[3])
98
+ changed |= self._change_xml(root, "DBL", "TR%s Steps %s" % (regime, trange), row[4])
99
+ changed |= self._change_xml(root, "U16", "TR%s In Mode %s" % (regime, trange), row[5])
100
+
101
+ changed |= self.change_tcb_calculation_method(root)
102
+ return changed
103
+
104
+ def change_period_settings(self, root):
105
+ changed = self._change_xml(root, "EW", "Period Type", self.periods_type)
106
+ changed |= self._change_xml(
107
+ root, "I32", "Number Of Software Periods", self.periods_soft_num
108
+ )
109
+ changed |= self._change_xml(root, "EW", "Period Setup Source", self.periods_src)
110
+ changed |= self._change_xml(root, "DBL", "Hardware Period Sequences", self.periods_seq)
111
+ changed |= self._change_xml(root, "DBL", "Output Delay (us)", self.periods_delay)
112
+ changed |= self._change_xml(root, "String", "Period File", self.periods_file)
113
+ changed |= self._change_period_table(root)
114
+ return changed
115
+
116
+ def _change_period_table(self, root):
117
+ changed = False
118
+ for row in self.periods_settings:
119
+ period = row[0]
120
+ ptype = row[1]
121
+ frames = row[2]
122
+ output = row[3]
123
+ label = row[4]
124
+ changed |= self._change_xml(root, "EW", "Type %s" % period, ptype)
125
+ changed |= self._change_xml(root, "I32", "Frames %s" % period, frames)
126
+ changed |= self._change_xml(root, "U16", "Output %s" % period, output)
127
+ changed |= self._change_xml(root, "String", "Label %s" % period, label)
128
+ return changed
129
+
130
+ def _change_xml(self, xml, node, name, value):
131
+ """
132
+ Helper func to change the xml.
133
+ Will not be set if the input is None.
134
+
135
+ Args:
136
+ xml: The root of the xml
137
+ node: The node type
138
+ name: The name of the node
139
+ value: The new value to set
140
+
141
+ Returns:
142
+ bool: True if the xml has been changed
143
+ """
144
+ if value is not None:
145
+ for top in xml.iter(node):
146
+ n = top.find("Name")
147
+ if n.text == name:
148
+ v = top.find("Val")
149
+ v.text = str(value)
150
+ return True
151
+ return False