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