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,1265 @@
1
+ from __future__ import absolute_import, print_function
2
+
3
+ import inspect
4
+ import os
5
+ import xml.etree.ElementTree as ET
6
+ from builtins import object, str
7
+ from collections import OrderedDict
8
+ from typing import TYPE_CHECKING, Callable
9
+
10
+ import numpy as np
11
+ import numpy.typing as npt
12
+
13
+ from genie_python.genie_logging import GenieLogger
14
+ from genie_python.genie_pre_post_cmd_manager import PrePostCmdManager
15
+ from genie_python.utilities import require_runstate
16
+
17
+ if TYPE_CHECKING:
18
+ from genie_python.genie import PVValue, _GetspectrumReturn
19
+ from genie_python.genie_waitfor import WAITFOR_VALUE
20
+
21
+
22
+ class Waitfor(object):
23
+ def __init__(self) -> None:
24
+ pass
25
+
26
+ def start_waiting(
27
+ self,
28
+ block: str | None = None,
29
+ value: "WAITFOR_VALUE | None" = None,
30
+ lowlimit: float | None = None,
31
+ highlimit: float | None = None,
32
+ maxwait: float | None = None,
33
+ wait_all: bool = False,
34
+ seconds: float | None = None,
35
+ minutes: float | None = None,
36
+ hours: float | None = None,
37
+ time: str | None = None,
38
+ frames: int | None = None,
39
+ raw_frames: int | None = None,
40
+ uamps: float | None = None,
41
+ mevents: float | None = None,
42
+ early_exit: Callable[[], bool] = lambda: False,
43
+ quiet: bool = False,
44
+ ) -> None:
45
+ # from https://stackoverflow.com/questions/582056/getting-list-of-parameter-names-inside-python-function
46
+ frame = inspect.currentframe()
47
+ args, _, _, values = inspect.getargvalues(frame)
48
+ wait_str = [
49
+ "{}={}".format(arg, values[arg])
50
+ for arg in args
51
+ if values[arg] is not None and arg != "self"
52
+ ]
53
+ print("Waiting for {}".format(", ".join(wait_str)))
54
+
55
+ def wait_for_runstate(
56
+ self, state: str, maxwaitsecs: int = 3600, onexit: bool = False, quiet: bool = False
57
+ ) -> None:
58
+ if onexit:
59
+ print("Waiting to exit state {}".format(state))
60
+ else:
61
+ print("Waiting for state {}".format(state))
62
+
63
+
64
+ class WaitForMoveController(object):
65
+ def __init__(self) -> None:
66
+ pass
67
+
68
+ def wait(self, start_timeout: float | None = None, move_timeout: float | None = None) -> None:
69
+ pass
70
+
71
+ def wait_specific(
72
+ self,
73
+ blocks: list[str],
74
+ start_timeout: float | None = None,
75
+ move_timeout: float | None = None,
76
+ ) -> None:
77
+ pass
78
+
79
+ def wait_for_start(self, timeout: float | None, check_for_move: bool) -> None:
80
+ pass
81
+
82
+
83
+ class ChangeCache(object):
84
+ def __init__(self) -> None:
85
+ self.wiring = None
86
+ self.detector = None
87
+ self.spectra = None
88
+ self.mon_spect = None
89
+ self.mon_from = None
90
+ self.mon_to = None
91
+ self.dae_sync = None
92
+ self.tcb_file = None
93
+ self.tcb_tables = []
94
+ self.smp_veto = None
95
+ self.ts2_veto = None
96
+ self.hz50_veto = None
97
+ self.ext0_veto = None
98
+ self.ext1_veto = None
99
+ self.ext2_veto = None
100
+ self.ext3_veto = None
101
+ self.fermi_veto = None
102
+ self.fermi_delay = None
103
+ self.fermi_width = None
104
+ self.periods_soft_num = None
105
+ self.periods_type = None
106
+ self.periods_src = None
107
+ self.periods_file = None
108
+ self.periods_seq = None
109
+ self.periods_delay = None
110
+ self.periods_settings = []
111
+
112
+ def set_monitor(self, spec: int, low: float, high: float) -> None:
113
+ self.mon_spect = spec
114
+ self.mon_from = low
115
+ self.mon_to = high
116
+
117
+ def clear_vetos(self) -> None:
118
+ self.smp_veto = 0
119
+ self.ts2_veto = 0
120
+ self.hz50_veto = 0
121
+ self.ext0_veto = 0
122
+ self.ext1_veto = 0
123
+ self.ext2_veto = 0
124
+ self.ext3_veto = 0
125
+
126
+ def set_fermi(self, enable: bool, delay: float = 1.0, width: float = 1.0) -> None:
127
+ self.fermi_veto = 1
128
+ self.fermi_delay = delay
129
+ self.fermi_width = width
130
+
131
+ def change_dae_settings(self, root: ET.Element) -> bool:
132
+ changed = False
133
+ if self.wiring is not None:
134
+ self._change_xml(root, "String", "Wiring Table", self.wiring)
135
+ changed = True
136
+ if self.detector is not None:
137
+ self._change_xml(root, "String", "Detector Table", self.detector)
138
+ changed = True
139
+ if self.spectra is not None:
140
+ self._change_xml(root, "String", "Spectra Table", self.spectra)
141
+ changed = True
142
+ if self.mon_spect is not None:
143
+ self._change_xml(root, "I32", "Monitor Spectrum", self.mon_spect)
144
+ changed = True
145
+ if self.mon_from is not None:
146
+ self._change_xml(root, "DBL", "from", self.mon_from)
147
+ changed = True
148
+ if self.mon_to is not None:
149
+ self._change_xml(root, "DBL", "to", self.mon_to)
150
+ changed = True
151
+ if self.dae_sync is not None:
152
+ self._change_xml(root, "EW", "DAETimingSource", self.dae_sync)
153
+ changed = True
154
+ if self.fermi_veto is not None:
155
+ self._change_xml(root, "EW", " Fermi Chopper Veto", self.fermi_veto)
156
+ self._change_xml(root, "DBL", "FC Delay", self.fermi_delay)
157
+ self._change_xml(root, "DBL", "FC Width", self.fermi_width)
158
+ changed = True
159
+
160
+ changed = self._change_vetos(root, changed)
161
+ return changed
162
+
163
+ def _change_vetos(self, root: ET.Element, changed: bool) -> bool:
164
+ if self.smp_veto is not None:
165
+ self._change_xml(root, "EW", "SMP (Chopper) Veto", self.smp_veto)
166
+ changed = True
167
+ if self.ts2_veto is not None:
168
+ self._change_xml(root, "EW", " TS2 Pulse Veto", self.ts2_veto)
169
+ changed = True
170
+ if self.hz50_veto is not None:
171
+ self._change_xml(root, "EW", " ISIS 50Hz Veto", self.hz50_veto)
172
+ changed = True
173
+ if self.ext0_veto is not None:
174
+ self._change_xml(root, "EW", "Veto 0", self.ext0_veto)
175
+ changed = True
176
+ if self.ext1_veto is not None:
177
+ self._change_xml(root, "EW", "Veto 1", self.ext1_veto)
178
+ changed = True
179
+ if self.ext2_veto is not None:
180
+ self._change_xml(root, "EW", "Veto 2", self.ext2_veto)
181
+ changed = True
182
+ if self.ext3_veto is not None:
183
+ self._change_xml(root, "EW", "Veto 3", self.ext3_veto)
184
+ changed = True
185
+ return changed
186
+
187
+ def change_tcb_settings(self, root: ET.Element) -> bool:
188
+ changed = False
189
+ if self.tcb_file is not None:
190
+ self._change_xml(root, "String", "Time Channel File", self.tcb_file)
191
+ changed = True
192
+ changed = self._change_tcb_table(root, changed)
193
+ return changed
194
+
195
+ def _change_tcb_table(self, root: ET.Element, changed: bool) -> bool:
196
+ for row in self.tcb_tables:
197
+ regime = str(row[0])
198
+ trange = str(row[1])
199
+ self._change_xml(root, "DBL", "TR%s From %s" % (regime, trange), row[2])
200
+ self._change_xml(root, "DBL", "TR%s To %s" % (regime, trange), row[3])
201
+ self._change_xml(root, "DBL", "TR%s Steps %s" % (regime, trange), row[4])
202
+ self._change_xml(root, "U16", "TR%s In Mode %s" % (regime, trange), row[5])
203
+ changed = True
204
+ return changed
205
+
206
+ def change_period_settings(self, root: ET.Element) -> bool:
207
+ changed = False
208
+ if self.periods_type is not None:
209
+ self._change_xml(root, "EW", "Period Type", self.periods_type)
210
+ changed = True
211
+ if self.periods_soft_num is not None:
212
+ self._change_xml(root, "I32", "Number Of Software Periods", self.periods_soft_num)
213
+ changed = True
214
+ if self.periods_src is not None:
215
+ self._change_xml(root, "EW", "Period Setup Source", self.periods_src)
216
+ changed = True
217
+ if self.periods_seq is not None:
218
+ self._change_xml(root, "DBL", "Hardware Period Sequences", self.periods_seq)
219
+ changed = True
220
+ if self.periods_delay is not None:
221
+ self._change_xml(root, "DBL", "Output Delay (us)", self.periods_delay)
222
+ changed = True
223
+ if self.periods_file is not None:
224
+ self._change_xml(root, "String", "Period File", self.periods_file)
225
+ changed = True
226
+ if self.periods_settings is not None:
227
+ self._change_period_table(root, changed)
228
+ changed = True
229
+ return changed
230
+
231
+ def _change_period_table(self, root: ET.Element, changed: bool) -> bool:
232
+ for row in self.periods_settings:
233
+ period = row[0]
234
+ ptype = row[1]
235
+ frames = row[2]
236
+ output = row[3]
237
+ label = row[4]
238
+ if ptype is not None:
239
+ self._change_xml(root, "EW", "Type %s" % period, ptype)
240
+ changed = True
241
+ if frames is not None:
242
+ self._change_xml(root, "I32", "Frames %s" % period, frames)
243
+ changed = True
244
+ if output is not None:
245
+ self._change_xml(root, "U16", "Output %s" % period, output)
246
+ changed = True
247
+ if label is not None:
248
+ self._change_xml(root, "String", "Label %s" % period, label)
249
+ changed = True
250
+ return changed
251
+
252
+ def _change_xml(
253
+ self, xml: ET.Element, node: str, name: str, value: str | int | float | None
254
+ ) -> None:
255
+ for top in xml.iter(node):
256
+ n = top.find("Name")
257
+ if n is not None:
258
+ if n.text == name:
259
+ v = top.find("Val")
260
+ if v is not None:
261
+ v.text = str(value)
262
+ break
263
+
264
+
265
+ class Dae(object):
266
+ def __init__(self) -> None:
267
+ self.run_state = "SETUP"
268
+ self.run_number = "123456"
269
+ self.period_current = 1
270
+ self.num_periods = 1
271
+ self.uamps_current = 0
272
+ self.total_counts = 0
273
+ self.title_current = "Simulation"
274
+ self.display_title = True
275
+ self.rb_number = "1"
276
+ self.mevents = 1.0
277
+ self.good_frames = 1
278
+ self.users = ""
279
+ self.run_duration = 1
280
+ self.raw_frames = 1
281
+ self.beam_current = 1
282
+ self.total_uamps = 1
283
+ self.num_spectra = 1
284
+ self.num_timechannels = 1
285
+ self.monitor_spectrum = 1
286
+ self.monitor_counts = 1
287
+ self.in_change = False
288
+ self.wiring_tables = [""]
289
+ self.spectra_tables = [""]
290
+ self.detector_tables = [""]
291
+ self.tcb_tables = []
292
+ self.period_files = []
293
+ self.spectrum: "_GetspectrumReturn" = {
294
+ "time": [1.0],
295
+ "signal": [1.0],
296
+ "sum": None,
297
+ "mode": "distribution",
298
+ }
299
+ self.change_cache = ChangeCache()
300
+
301
+ @require_runstate(["SETUP"])
302
+ def begin_run(
303
+ self,
304
+ period: int | None = None,
305
+ meas_id: str | None = None,
306
+ meas_type: str | None = None,
307
+ meas_subid: str | None = None,
308
+ sample_id: str | None = None,
309
+ delayed: bool = False,
310
+ quiet: bool = False,
311
+ paused: bool = False,
312
+ prepost: bool = True,
313
+ ) -> None:
314
+ """Starts a data collection run.
315
+
316
+ Parameters:
317
+ period - the period to begin data collection in [optional]
318
+ meas_id - the measurement id [optional]
319
+ meas_type - the type of measurement [optional]
320
+ meas_subid - the measurement sub-id[optional]
321
+ sample_id - the sample id [optional]
322
+ delayed - puts the period card to into delayed start mode [optional]
323
+ quiet - suppress the output to the screen [optional]
324
+ paused - begin in the paused state [optional]
325
+ prepost - run pre and post commands [optional]
326
+ """
327
+ print("Run started")
328
+ self.run_state = "RUNNING"
329
+
330
+ def post_begin_check(self, verbose: bool = False) -> None:
331
+ pass
332
+
333
+ def post_end_check(self, verbose: bool = False) -> None:
334
+ pass
335
+
336
+ def post_abort_check(self, verbose: bool = False) -> None:
337
+ pass
338
+
339
+ def post_pause_check(self, verbose: bool = False) -> None:
340
+ pass
341
+
342
+ def post_resume_check(self, verbose: bool = False) -> None:
343
+ pass
344
+
345
+ def post_update_store_check(self, verbose: bool = False) -> None:
346
+ pass
347
+
348
+ def post_update_check(self, verbose: bool = False) -> None:
349
+ pass
350
+
351
+ def post_store_check(self, verbose: bool = False) -> None:
352
+ pass
353
+
354
+ @require_runstate(["RUNNING", "VETOING", "WAITING", "PAUSED"])
355
+ def abort_run(self, prepost: bool = True) -> None:
356
+ """
357
+ Aborts the run and sets run_state to "SETUP"
358
+ Parameters:
359
+ prepost - run pre and post commands [optional]
360
+ """
361
+ print("Run aborted")
362
+ self.run_state = "SETUP"
363
+
364
+ def get_run_state(self) -> str:
365
+ """
366
+ Returns the current run state
367
+ Returns: String
368
+ """
369
+ return self.run_state
370
+
371
+ def get_run_number(self) -> str:
372
+ """
373
+ Returns the run number
374
+ Returns: Int
375
+ """
376
+ return self.run_number
377
+
378
+ @require_runstate(["RUNNING", "VETOING", "WAITING", "PAUSED", "ENDING"])
379
+ def end_run(
380
+ self,
381
+ verbose: bool = False,
382
+ quiet: bool = False,
383
+ immediate: bool = False,
384
+ prepost: bool = True,
385
+ ) -> None:
386
+ print("Run ended")
387
+ self.run_state = "SETUP"
388
+
389
+ @require_runstate(["RUNNING", "VETOING", "WAITING", "PAUSING"])
390
+ def pause_run(self, immediate: bool = False, prepost: bool = True) -> None:
391
+ """
392
+ Parameters:
393
+ prepost - run pre and post commands [optional]
394
+ """
395
+ print("Run paused")
396
+ self.run_state = "PAUSED"
397
+
398
+ @require_runstate(["PAUSED"])
399
+ def resume_run(self, prepost: bool = True) -> None:
400
+ """
401
+ Parameters:
402
+ prepost - run pre and post commands [optional]
403
+ """
404
+ print("Run resumed")
405
+ self.run_state = "RUNNING"
406
+
407
+ def update_store_run(self) -> None:
408
+ """
409
+ Does nothing but throw error if run_state is not "RUNNING" or "PAUSED"
410
+ """
411
+ if self.run_state == "RUNNING" or self.run_state == "PAUSED":
412
+ pass
413
+ else:
414
+ raise Exception("Can only be called when RUNNING or PAUSED")
415
+
416
+ def update_run(self) -> None:
417
+ """
418
+ Does nothing but throw error if run_state is not "RUNNING" or "PAUSED"
419
+ """
420
+ if self.run_state == "RUNNING" or self.run_state == "PAUSED":
421
+ pass
422
+ else:
423
+ raise Exception("Can only be called when RUNNING or PAUSED")
424
+
425
+ @require_runstate(["RUNNING", "VETOING", "WAITING", "PAUSED"])
426
+ def store_run(self) -> None:
427
+ print("Run stored")
428
+
429
+ def recover_run(self) -> None:
430
+ if self.run_state == "SETUP":
431
+ pass
432
+ else:
433
+ raise Exception("Can only be called when SETUP")
434
+
435
+ def get_period(self) -> int:
436
+ """
437
+ returns the current period number
438
+ Returns: Int
439
+
440
+ """
441
+ return self.period_current
442
+
443
+ def get_num_periods(self) -> int:
444
+ """
445
+ returns the current number of periods
446
+ Returns: Int
447
+ """
448
+ return self.num_periods
449
+
450
+ def set_period(self, period: int) -> None:
451
+ """
452
+ sets the current period to the period parameter if it is equal to
453
+ or less than the number of periods
454
+ Args:
455
+ period: Int
456
+ """
457
+ if period <= self.num_periods:
458
+ self.period_current = period
459
+ else:
460
+ raise Exception("Cannot set period as it is higher than the number of periods")
461
+
462
+ def get_uamps(self, period: bool = False) -> float:
463
+ """Returns the current number of micro-amp hours.
464
+
465
+ Parameters:
466
+ period - whether to return the micro-amp hours for the current period [optional]
467
+ """
468
+ if period:
469
+ return self.uamps_current
470
+ else:
471
+ return self.uamps_current
472
+
473
+ def get_total_counts(self) -> int:
474
+ """Get the total counts for the current run."""
475
+ return self.total_counts
476
+
477
+ def get_title(self) -> str:
478
+ """Returns the current title
479
+
480
+ Returns: String : the current title
481
+
482
+ """
483
+ return self.title_current
484
+
485
+ def set_title(self, title: str) -> None:
486
+ """Sets the current title
487
+
488
+ Args:
489
+ title: String: the new title
490
+ """
491
+ print("Setting title to {}".format(title))
492
+ self.title_current = title
493
+
494
+ def get_display_title(self) -> bool:
495
+ """Returns the current display title status
496
+
497
+ Returns: boolean : the current display title status
498
+
499
+ """
500
+ return self.display_title
501
+
502
+ def set_display_title(self, display_title: bool) -> None:
503
+ """Sets whether to display title & users
504
+
505
+ Args:
506
+ display_title: boolean: the new display title status
507
+ """
508
+ print("Setting title display status to {}".format(display_title))
509
+ self.display_title = display_title
510
+
511
+ def get_rb_number(self) -> str:
512
+ """Returns the current RB number
513
+
514
+ Returns: String : the RB number
515
+
516
+ """
517
+ return self.rb_number
518
+
519
+ def set_rb_number(self, rb: str) -> None:
520
+ self.rb_number = rb
521
+
522
+ def get_mevents(self) -> float:
523
+ return self.mevents
524
+
525
+ def get_good_frames(self, period: bool = False) -> int:
526
+ if period:
527
+ return self.good_frames
528
+ else:
529
+ return self.good_frames
530
+
531
+ def get_users(self) -> str:
532
+ return self.users
533
+
534
+ def get_run_duration(self) -> float:
535
+ return self.run_duration
536
+
537
+ def get_raw_frames(self, period: bool = False) -> int:
538
+ return self.raw_frames
539
+
540
+ def get_beam_current(self) -> float:
541
+ return self.beam_current
542
+
543
+ def get_total_uamps(self) -> float:
544
+ return self.total_uamps
545
+
546
+ def get_num_spectra(self) -> int:
547
+ return self.num_spectra
548
+
549
+ def get_num_timechannels(self) -> int:
550
+ return self.num_timechannels
551
+
552
+ def get_monitor_spectrum(self) -> int:
553
+ return self.monitor_spectrum
554
+
555
+ def get_monitor_from(self) -> float:
556
+ return 0.0
557
+
558
+ def get_monitor_to(self) -> float:
559
+ return 1.0
560
+
561
+ def get_monitor_counts(self) -> int:
562
+ return 1
563
+
564
+ def set_users(self, users: str) -> None:
565
+ self.users = users
566
+
567
+ def change_start(self) -> None:
568
+ """Start a change operation.
569
+ The operaton is finished when change_finish is called.
570
+ Between these two calls a sequence of other change commands can be called.
571
+ For example: change_tables, change_tcb etc.
572
+ """
573
+ # Check in setup
574
+ if self.get_run_state() != "SETUP":
575
+ raise Exception("Must be in SETUP before starting change!")
576
+ if self.in_change:
577
+ raise Exception("Already in change - previous cached values will be used")
578
+ else:
579
+ self.in_change = True
580
+ self.change_cache = ChangeCache()
581
+
582
+ def change_finish(self) -> None:
583
+ if self.in_change:
584
+ self.in_change = False
585
+ self.change_cache = ChangeCache()
586
+
587
+ def get_spectrum(
588
+ self, spectrum: int, period: int = 1, dist: bool = True, use_numpy: bool | None = None
589
+ ) -> "_GetspectrumReturn":
590
+ return self.spectrum
591
+
592
+ def get_spec_data(self) -> npt.NDArray:
593
+ return np.array([1.0, 2.0, 3.0, 4.0]) # spectrum 0 and 1, time bins 0 and 1
594
+
595
+ def get_spec_integrals(self) -> npt.NDArray:
596
+ return np.array([1.0, 2.0]) # spectrum 0 and 1
597
+
598
+ def integrate_spectrum(
599
+ self, spectrum: int, period: int = 1, t_min: float | None = None, t_max: float | None = None
600
+ ) -> float:
601
+ """
602
+ Integrates the spectrum within the time period and returns neutron counts.
603
+
604
+ The underlying algorithm sums the counts from each bin, if a bin is
605
+ split by the time region then a proportional
606
+ fraction of the count for that bin is used.
607
+
608
+ Args:
609
+ spectrum (int): the spectrum number
610
+ period (int, optional): the period
611
+ t_min (float, optional): time of flight to start from
612
+ t_max (float, optional): time of flight to finish at
613
+
614
+ Returns:
615
+ float: integral of the spectrum (neutron counts) which
616
+ is 1 proporiontal to the width requested
617
+ """
618
+ return 1.0 / (t_max - t_min)
619
+
620
+ def change_monitor(self, spec: int, low: float, high: float) -> None:
621
+ """Change the monitor to a specified spectrum and range.
622
+
623
+ Parameters:
624
+ spectrum - the spectrum number (integer)
625
+ low - the low end of the integral (float)
626
+ high - the high end of the integral (float)
627
+ """
628
+ try:
629
+ spec = int(spec)
630
+ except ValueError:
631
+ raise TypeError("Spectrum number must be an integer")
632
+ try:
633
+ low = float(low)
634
+ except ValueError:
635
+ raise TypeError("Low must be a float")
636
+ try:
637
+ high = float(high)
638
+ except ValueError:
639
+ raise TypeError("High must be a float")
640
+ did_change = False
641
+ if not self.in_change:
642
+ self.change_start()
643
+ did_change = True
644
+ pass
645
+ if did_change:
646
+ self.change_finish()
647
+
648
+ def get_wiring_tables(self) -> list[str]:
649
+ return self.wiring_tables
650
+
651
+ def get_spectra_tables(self) -> list[str]:
652
+ return self.spectra_tables
653
+
654
+ def get_detector_tables(self) -> list[str]:
655
+ return self.detector_tables
656
+
657
+ def get_period_files(self) -> list[str]:
658
+ return self.period_files
659
+
660
+ def configure_hard_periods(
661
+ self,
662
+ mode: str,
663
+ period_file: str | None = None,
664
+ sequences: int | None = None,
665
+ output_delay: int | None = None,
666
+ period: int | None = None,
667
+ daq: bool = False,
668
+ dwell: bool = False,
669
+ unused: bool = False,
670
+ frames: int | None = None,
671
+ output: int | None = None,
672
+ label: str | None = None,
673
+ ) -> None:
674
+ """Configures the DAE's hardware periods.
675
+
676
+ Parameters:
677
+ mode - set the mode to internal ('int') or external ('ext')
678
+
679
+ Internal periods parameters [optional]:
680
+ period_file - the file containing the internal period
681
+ settings (ignores any other settings)
682
+ sequences - the number of period sequences
683
+ output_delay - the output delay in microseconds
684
+ period - the number of the period to set the following for:
685
+ daq - it is a aquisition period
686
+ dwell - it is a dwell period
687
+ unused - it is a unused period
688
+ frames - the number of frames to count for
689
+ output - the binary output
690
+ label - the label for the period
691
+
692
+ Note: if the period number is unspecified then the settings
693
+ will be applied to all periods.
694
+
695
+ EXAMPLE: setting external periods
696
+ enable_hardware_periods('ext')
697
+
698
+ EXAMPLE: setting internal periods from a file
699
+ enable_hardware_periods('int', 'myperiods.txt')
700
+ """
701
+ did_change = False
702
+ if not self.in_change:
703
+ self.change_start()
704
+ did_change = True
705
+ # Set the source to 'Use Parameters Below' by default
706
+ self.change_cache.periods_src = 0
707
+ if mode.strip().lower() == "int":
708
+ self.change_cache.periods_type = 1
709
+ if period_file is not None:
710
+ if not os.path.exists(period_file):
711
+ raise Exception("Period file could not be found")
712
+ self.change_cache.periods_src = 1
713
+ self.change_cache.periods_file = period_file
714
+ else:
715
+ self.configure_internal_periods(
716
+ sequences, output_delay, period, daq, dwell, unused, frames, output, label
717
+ )
718
+ elif mode.strip().lower() == "ext":
719
+ self.change_cache.periods_type = 2
720
+ else:
721
+ raise Exception('Period mode invalid, it should be "int" or "ext"')
722
+ if did_change:
723
+ self.change_finish()
724
+
725
+ def configure_internal_periods(
726
+ self,
727
+ sequences: int | None = None,
728
+ output_delay: int | None = None,
729
+ period: int | None = None,
730
+ daq: bool = False,
731
+ dwell: bool = False,
732
+ unused: bool = False,
733
+ frames: int | None = None,
734
+ output: int | None = None,
735
+ label: str | None = None,
736
+ ) -> None:
737
+ """Configure the internal periods without switching to internal period mode.
738
+
739
+ Parameters:
740
+ file - the file containing the internal period settings
741
+ (ignores any other settings) [optional]
742
+ sequences - the number of period sequences [optional]
743
+ output_delay - the output delay in microseconds [optional]
744
+ period - the number of the period to set values for [optional]
745
+ daq - the specified period is a aquisition period [optional]
746
+ dwell - the specified period is a dwell period [optional]
747
+ unused - the specified period is a unused period [optional]
748
+ frames - the number of frames to count for the specified period [optional]
749
+ output - the binary output the specified period [optional]
750
+ label - the label for the period the specified period [optional]
751
+
752
+ Note: if the period number is unspecified then the settings will be
753
+ applied to all periods.
754
+ """
755
+ did_change = False
756
+ if not self.in_change:
757
+ self.change_start()
758
+ did_change = True
759
+ if sequences is not None:
760
+ if isinstance(sequences, int):
761
+ self.change_cache.periods_seq = sequences
762
+ else:
763
+ raise Exception("Number of period sequences must be an integer")
764
+ if output_delay is not None:
765
+ if isinstance(output_delay, int):
766
+ self.change_cache.periods_delay = output_delay
767
+ else:
768
+ raise Exception("Output delay of periods must be an integer (microseconds)")
769
+ self.define_hard_period(period, daq, dwell, unused, frames, output, label)
770
+ if did_change:
771
+ self.change_finish()
772
+
773
+ def define_hard_period(
774
+ self,
775
+ period: int | None = None,
776
+ daq: bool = False,
777
+ dwell: bool = False,
778
+ unused: bool = False,
779
+ frames: int | None = None,
780
+ output: int | None = None,
781
+ label: str | None = None,
782
+ ) -> None:
783
+ """Define the hardware periods.
784
+
785
+ Parameters:
786
+ period - the number of the period to set values for [optional]
787
+ daq - the specified period is a aquisition period [optional]
788
+ dwell - the specified period is a dwell period [optional]
789
+ unused - the specified period is a unused period [optional]
790
+ frames - the number of frames to count for the specified period [optional]
791
+ output - the binary output the specified period [optional]
792
+ label - the label for the period the specified period [optional]
793
+
794
+ Note: if the period number is unspecified then the settings will be
795
+ applied to all periods.
796
+ """
797
+ did_change = False
798
+ if not self.in_change:
799
+ self.change_start()
800
+ did_change = True
801
+ if period is None:
802
+ # Do for all periods (1 to 8)
803
+ for i in range(1, 9):
804
+ self.define_hard_period(i, daq, dwell, unused, frames, output, label)
805
+ else:
806
+ if isinstance(period, int) and period > 0 and period < 9:
807
+ p_type = None # unchanged
808
+ if unused:
809
+ p_type = 0
810
+ elif daq:
811
+ p_type = 1
812
+ elif dwell:
813
+ p_type = 2
814
+ p_frames = None # unchanged
815
+ if frames is not None and isinstance(frames, int):
816
+ p_frames = frames
817
+ p_output = None # unchanged
818
+ if output is not None and isinstance(output, int):
819
+ p_output = output
820
+ p_label = None # unchanged
821
+ if label is not None:
822
+ p_label = label
823
+ self.change_cache.periods_settings.append(
824
+ (period, p_type, p_frames, p_output, p_label)
825
+ )
826
+ else:
827
+ raise Exception("Period number must be an integer from 1 to 8")
828
+ if did_change:
829
+ self.change_finish()
830
+
831
+ def change_tables(
832
+ self, wiring: str | None = None, detector: str | None = None, spectra: str | None = None
833
+ ) -> None:
834
+ """Load the wiring, detector and/or spectra tables.
835
+
836
+ Parameters:
837
+ wiring - the filename of the wiring table file [optional]
838
+ detector - the filename of the detector table file [optional]
839
+ spectra - the filename of the spectra table file [optional]
840
+ """
841
+ did_change = False
842
+ if not self.in_change:
843
+ self.change_start()
844
+ did_change = True
845
+ if wiring is not None:
846
+ self.change_cache.wiring = wiring
847
+ if detector is not None:
848
+ self.change_cache.detector = detector
849
+ if spectra is not None:
850
+ self.change_cache.spectra = spectra
851
+ if did_change:
852
+ self.change_finish()
853
+
854
+ def change_sync(self, source: str) -> None:
855
+ """Change the source the DAE using for synchronisation.
856
+
857
+ Parameters:
858
+ source - the source to use ('isis', 'internal', 'smp',
859
+ 'muon cerenkov', 'muon ms', 'isis (first ts1)')
860
+ """
861
+ did_change = False
862
+ if not self.in_change:
863
+ self.change_start()
864
+ did_change = True
865
+ source = source.strip().lower()
866
+ if source == "isis":
867
+ value = 0
868
+ elif source == "internal":
869
+ value = 1
870
+ elif source == "smp":
871
+ value = 2
872
+ elif source == "muon cerenkov":
873
+ value = 3
874
+ elif source == "muon ms":
875
+ value = 4
876
+ elif source == "isis (first ts1)":
877
+ value = 5
878
+ else:
879
+ raise Exception("Invalid timing source entered, try help(change_sync)!")
880
+ self.change_cache.dae_sync = value
881
+ if did_change:
882
+ self.change_finish()
883
+
884
+ def change_tcb_file(self, tcb_file: str | None = None, default: bool = False) -> None:
885
+ """Change the time channel boundaries.
886
+
887
+ Parameters:
888
+ tcb_file - the file to load [optional]
889
+ default - load the default file "c:\\labview modules\\dae\\tcb.dat" [optional]
890
+ """
891
+ did_change = False
892
+ if not self.in_change:
893
+ self.change_start()
894
+ did_change = True
895
+ if tcb_file is not None:
896
+ print("Reading TCB boundaries from", tcb_file)
897
+ elif default:
898
+ tcb_file = "c:\\labview modules\\dae\\tcb.dat"
899
+ else:
900
+ raise Exception("No tcb file specified")
901
+ if not os.path.exists(tcb_file):
902
+ raise Exception("Tcb file could not be found")
903
+ self.change_cache.tcb_file = tcb_file
904
+ if did_change:
905
+ self.change_finish()
906
+
907
+ def change_tcb(
908
+ self, low: float, high: float, step: float, trange: int, log: bool = False, regime: int = 1
909
+ ) -> None:
910
+ """Change the time channel boundaries.
911
+
912
+ Parameters:
913
+ low - the lower limit
914
+ high - the upper limit
915
+ step - the step size
916
+ trange - the time range (1 to 5)
917
+ log - whether to use LOG binning [optional]
918
+ regime - the time regime to set (1 to 6)[optional]
919
+ """
920
+ did_change = False
921
+ if not self.in_change:
922
+ self.change_start()
923
+ did_change = True
924
+ if log:
925
+ print("Setting TCB range", low, "to", high, "step", step, "(LOG binning)")
926
+ self.change_cache.tcb_tables.append((regime, trange, low, high, step, 2))
927
+ else:
928
+ print("Setting TCB range", low, "to", high, "step", step, "(LINEAR binning)")
929
+ self.change_cache.tcb_tables.append((regime, trange, low, high, step, 1))
930
+ if did_change:
931
+ self.change_finish()
932
+
933
+ def change_vetos(self, **params: bool) -> None:
934
+ """Change the DAE veto settings.
935
+
936
+ Parameters:
937
+ clearall - remove all vetos [optional]
938
+ smp - set SMP veto [optional]
939
+ ts2 - set TS2 veto [optional]
940
+ hz50 - set 50 hz veto [optional]
941
+ ext0 - set external veto 0 [optional]
942
+ ext1 - set external veto 1 [optional]
943
+ ext2 - set external veto 2 [optional]
944
+ ext3 - set external veto 3 [optional]
945
+
946
+ If clearall is specified then all vetos are turned off, but it is possible
947
+ to turn other vetoes back on at the same time, for example:
948
+
949
+ change_vetos(clearall=True, smp=True) #Turns all vetoes off then
950
+ #turns the SMP veto back on
951
+ """
952
+ did_change = False
953
+ if not self.in_change:
954
+ self.change_start()
955
+ did_change = True
956
+ if "clearall" in params:
957
+ if isinstance(params["clearall"], bool):
958
+ self.change_cache.clear_vetos()
959
+ if "smp" in params:
960
+ if isinstance(params["smp"], bool):
961
+ self.change_cache.smp_veto = int(params["smp"])
962
+ if "ts2" in params:
963
+ if isinstance(params["ts2"], bool):
964
+ self.change_cache.ts2_veto = int(params["ts2"])
965
+ if "hz50" in params:
966
+ if isinstance(params["hz50"], bool):
967
+ self.change_cache.hz50_veto = int(params["hz50"])
968
+ if "ext0" in params:
969
+ if isinstance(params["ext0"], bool):
970
+ self.change_cache.ext0_veto = int(params["ext0"])
971
+ if "ext1" in params:
972
+ if isinstance(params["ext1"], bool):
973
+ self.change_cache.ext1_veto = int(params["ext1"])
974
+ if "ext2" in params:
975
+ if isinstance(params["ext2"], bool):
976
+ self.change_cache.ext2_veto = int(params["ext2"])
977
+ if "ext3" in params:
978
+ if isinstance(params["ext3"], bool):
979
+ self.change_cache.ext3_veto = int(params["ext3"])
980
+ if did_change:
981
+ self.change_finish()
982
+
983
+ def set_fermi_veto(self, enable: bool = None, delay: float = 1.0, width: float = 1.0) -> None:
984
+ """Configure the fermi chopper veto.
985
+
986
+ Parameters:
987
+ enable - enable the fermi veto
988
+ delay - the veto delay
989
+ width - the veto width
990
+ """
991
+ if not isinstance(enable, bool):
992
+ raise Exception("Fermi veto: enable must be a boolean value")
993
+ if not isinstance(delay, float) and not isinstance(delay, int):
994
+ raise Exception("Fermi veto: delay must be a numeric value")
995
+ if not isinstance(width, float) and not isinstance(width, int):
996
+ raise Exception("Fermi veto: width must be a numeric value")
997
+ did_change = False
998
+ if not self.in_change:
999
+ self.change_start()
1000
+ did_change = True
1001
+ if enable:
1002
+ self.change_cache.set_fermi(1, delay, width)
1003
+ print("SET_FERMI_VETO: requested status is ON, delay:", delay, "width:", width)
1004
+ else:
1005
+ self.change_cache.set_fermi(0)
1006
+ print("SET_FERMI_VETO: requested status is OFF")
1007
+ if did_change:
1008
+ self.change_finish()
1009
+
1010
+ def set_num_soft_periods(self, number: int) -> None:
1011
+ """Sets the number of software periods for the DAE.
1012
+
1013
+ Parameters:
1014
+ number - the number of periods to create
1015
+ """
1016
+ if not isinstance(number, float) and not isinstance(number, int):
1017
+ raise Exception("Number of soft periods must be a numeric value")
1018
+ did_change = False
1019
+ if not self.in_change:
1020
+ self.change_start()
1021
+ did_change = True
1022
+ if number >= 0:
1023
+ self.num_periods = number
1024
+ if did_change:
1025
+ self.change_finish()
1026
+
1027
+ def set_period_mode(self, mode: str) -> None:
1028
+ """Sets the period mode for the DAE
1029
+
1030
+ Parameters:
1031
+ mode - the mode to switch to ('soft', 'int', 'ext')
1032
+ """
1033
+ did_change = False
1034
+ if not self.in_change:
1035
+ self.change_start()
1036
+ did_change = True
1037
+ if mode.strip().lower() == "soft":
1038
+ self.change_cache.periods_type = 0
1039
+ else:
1040
+ self.configure_hard_periods(mode)
1041
+ if did_change:
1042
+ self.change_finish()
1043
+
1044
+ def snapshot_crpt(self, name: str) -> None:
1045
+ pass
1046
+
1047
+ def post_snapshot_check(self, verbose: bool = False) -> None:
1048
+ pass
1049
+
1050
+ def set_verbose(self, verbose: bool) -> None:
1051
+ pass
1052
+
1053
+ def get_table_path(self, table_type: str) -> str:
1054
+ if table_type == "Wiring":
1055
+ return self.change_cache.wiring
1056
+ if table_type == "Detector":
1057
+ return self.change_cache.detector
1058
+ if table_type == "Spectra":
1059
+ return self.change_cache.spectra
1060
+
1061
+
1062
+ class API(object):
1063
+ def __init__(
1064
+ self, pv_prefix: str | None = None, globs: dict | None = None, strict_block: bool = True
1065
+ ) -> None:
1066
+ """
1067
+ Constructor for the simulated API.
1068
+
1069
+ Args:
1070
+ pv_prefix: used for prefixing the PV and block names
1071
+ globs: globals
1072
+ strict_block: if True will throw an exception if setting a block that
1073
+ doesn't exist, otherwise will create the block
1074
+ """
1075
+ self.inst_prefix = None
1076
+ self.pre_post_cmd_manager = PrePostCmdManager()
1077
+ self.block_dict = dict()
1078
+ self.num_periods = 1
1079
+ self.run_number = "123456"
1080
+ self.waitfor = Waitfor()
1081
+ self.wait_for_move = WaitForMoveController()
1082
+ self.dae = Dae()
1083
+ self.beamline_pars = {}
1084
+ self.sample_pars = {}
1085
+ self.strict_block = strict_block
1086
+ self.logger = GenieLogger(sim_mode=True)
1087
+
1088
+ def set_instrument(
1089
+ self, pv_prefix: str, globs: dict | None, import_instrument_init: bool
1090
+ ) -> None:
1091
+ self.inst_prefix = pv_prefix
1092
+
1093
+ def prefix_pv_name(self, name: str) -> str:
1094
+ """Adds the instrument prefix to the specified PV"""
1095
+ if self.inst_prefix is not None and not name.startswith(self.inst_prefix):
1096
+ if not self.inst_prefix.endswith(":"):
1097
+ name = ":" + name
1098
+ return self.inst_prefix + name
1099
+ return name
1100
+
1101
+ def set_pv_value(
1102
+ self, name: str, value: str, wait: bool = False, is_local: bool = False
1103
+ ) -> None:
1104
+ if is_local:
1105
+ name = self.prefix_pv_name(name)
1106
+ print(
1107
+ "set_pv_value called (name=%s value=%s wait=%s is_local=%s)"
1108
+ % (name, value, wait, is_local)
1109
+ )
1110
+
1111
+ def get_pv_value(
1112
+ self, name: str, to_string: bool = False, attempts: int = 3, is_local: bool = False
1113
+ ) -> None:
1114
+ if is_local:
1115
+ name = self.prefix_pv_name(name)
1116
+ print(
1117
+ "get_pv_value called (name=%s value=%s attempts=%s is_local=%s)"
1118
+ % (name, to_string, attempts, is_local)
1119
+ )
1120
+
1121
+ def pv_exists(self, name: str) -> bool:
1122
+ return True
1123
+
1124
+ def reload_current_config(self) -> None:
1125
+ pass
1126
+
1127
+ def correct_blockname(self, name: str, add_prefix: bool = True) -> str:
1128
+ return name
1129
+
1130
+ def get_block_names(self) -> list():
1131
+ return list(self.block_dict.keys())
1132
+
1133
+ def block_exists(self, name: str) -> bool:
1134
+ return name in self.block_dict.keys() if self.strict_block else True
1135
+
1136
+ def set_block_value(
1137
+ self,
1138
+ name: str,
1139
+ value: "PVValue" = None,
1140
+ runcontrol: bool | None = None,
1141
+ lowlimit: float | None = None,
1142
+ highlimit: float | None = None,
1143
+ wait: bool = False,
1144
+ ) -> None:
1145
+ """Sets a block's values.
1146
+ If the block already exists, update the block. Only update values
1147
+ specified in the arguments.
1148
+
1149
+ Args:
1150
+ name (string): the name of the block
1151
+ value (int): the value of the block
1152
+ runcontrol (boolean): whether to set runcontrol for this block
1153
+ lowlimit (float): the lower limit for runcontrol or waiting
1154
+ highlimit (float): the upper limit for runcontrol or waiting
1155
+ wait (boolean): pause execution until setpoint is reached (one block only)
1156
+
1157
+ """
1158
+ print("Setting {} to value {}".format(name, value))
1159
+ if name not in self.block_dict:
1160
+ self.block_dict[name] = {
1161
+ "value": value,
1162
+ "runcontrol": runcontrol,
1163
+ "lowlimit": lowlimit,
1164
+ "highlimit": highlimit,
1165
+ }
1166
+ else:
1167
+ if wait:
1168
+ self.block_dict[name]["value"] = value
1169
+ else:
1170
+ # from https://stackoverflow.com/questions/582056/
1171
+ # getting-list-of-parameter-names-inside-python-function
1172
+ frame = inspect.currentframe()
1173
+ args, _, _, values = inspect.getargvalues(frame)
1174
+ for arg in args:
1175
+ if values[arg] is not None and arg != "self":
1176
+ self.block_dict[name][arg] = values[arg]
1177
+ if wait:
1178
+ self.waitfor.start_waiting(block=name, value=value)
1179
+
1180
+ def get_block_data(self, block: str, fail_fast: bool = False) -> dict():
1181
+ ans = OrderedDict()
1182
+ ans["connected"] = True
1183
+
1184
+ ans["name"] = block
1185
+ ans["value"] = self.block_dict[block].get("value", None)
1186
+ ans["runcontrol"], ans["lowlimit"], ans["highlimit"] = self.get_runcontrol_settings(block)
1187
+
1188
+ ans["alarm"] = self.get_alarm_from_block(block)
1189
+ return ans
1190
+
1191
+ def set_multiple_blocks(self, names: list, values: list) -> None:
1192
+ temp = list(zip(names, values))
1193
+ for name, value in temp:
1194
+ if name in self.block_dict:
1195
+ self.block_dict[name]["value"] = value
1196
+ else:
1197
+ self.block_dict[name] = {
1198
+ "value": value,
1199
+ "runcontrol": None,
1200
+ "lowlimit": None,
1201
+ "highlimit": None,
1202
+ "wait": False,
1203
+ }
1204
+
1205
+ def run_pre_post_cmd(self, command: str, **pars: str) -> None:
1206
+ pass
1207
+
1208
+ def get_sample_pars(self) -> dict:
1209
+ return self.sample_pars
1210
+
1211
+ def set_sample_par(self, name: str, value: str) -> None:
1212
+ self.sample_pars[name] = value
1213
+
1214
+ def get_beamline_pars(self) -> dict:
1215
+ return self.beamline_pars
1216
+
1217
+ def set_beamline_par(self, name: str, value: str) -> None:
1218
+ self.beamline_pars[name] = value
1219
+
1220
+ def get_runcontrol_settings(self, name: str) -> tuple():
1221
+ return (
1222
+ self.block_dict[name]["runcontrol"],
1223
+ self.block_dict[name]["lowlimit"],
1224
+ self.block_dict[name]["highlimit"],
1225
+ )
1226
+
1227
+ def check_alarms(*blocks: str) -> tuple[list[str], list[str], list[str]]:
1228
+ minor = list()
1229
+ major = list()
1230
+ invalid = list()
1231
+ return (minor, major, invalid)
1232
+
1233
+ def check_limit_violations(self, blocks: str) -> list:
1234
+ return list()
1235
+
1236
+ def get_current_block_values(self) -> dict:
1237
+ """
1238
+ Values are returned for each IBEX block.
1239
+ Returns:
1240
+ dictionary of blocks each with a list of values in
1241
+
1242
+ """
1243
+ order_of_keys = ["value", "runcontrol", "lowlimit", "highlimit"]
1244
+ block_values = {}
1245
+ for key, values in self.block_dict.items():
1246
+ return_values = []
1247
+ for order_key in order_of_keys:
1248
+ return_values.append(values.get(order_key, None))
1249
+ block_values[key] = return_values
1250
+ return block_values
1251
+
1252
+ def send_sms(self, phone_num: str, message: str) -> None:
1253
+ print(('SMS "{}" sent to {}'.format(message, phone_num)))
1254
+
1255
+ def send_email(self, address: str, message: str) -> None:
1256
+ print(('Email "{}" sent to {}'.format(message, address)))
1257
+
1258
+ def send_alert(self, message: str, inst: str) -> str:
1259
+ print(('Slack message "{}" sent to {}'.format(message, inst)))
1260
+
1261
+ def get_alarm_from_block(self, block: str) -> str:
1262
+ return "NO_ALARM"
1263
+
1264
+ def get_block_units(self, block: str) -> str:
1265
+ return "mm"