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
genie_python/genie.py ADDED
@@ -0,0 +1,2462 @@
1
+ from __future__ import absolute_import, print_function
2
+
3
+ import datetime
4
+ import imp
5
+ import os
6
+ import re
7
+ import sys
8
+ import types
9
+ from builtins import FileNotFoundError, str
10
+ from io import open
11
+ from typing import Any, Callable, TypedDict
12
+
13
+ import numpy.typing as npt
14
+
15
+ from genie_python.genie_api_setup import __api as _genie_api
16
+
17
+ os.environ["EPICS_CA_MAX_ARRAY_BYTES"] = "20000000"
18
+ os.environ["FROM_IBEX"] = str(False)
19
+
20
+ # for user import this functionality so they can do g.adv and g.sim
21
+ import genie_python.genie_advanced as adv # noqa F401
22
+ import genie_python.genie_alerts as alerts # noqa F401
23
+ import genie_python.genie_simulate as sim # noqa F401
24
+ import genie_python.genie_toggle_settings as toggle # noqa F401
25
+
26
+ # Import required for g.my_pv_prefix
27
+ from genie_python.genie_api_setup import (
28
+ get_user_script_dir,
29
+ helparglist,
30
+ log_command_and_handle_exception,
31
+ my_pv_prefix, # noqa F401
32
+ set_user_script_dir,
33
+ usercommand,
34
+ )
35
+ from genie_python.genie_script_checker import ScriptChecker
36
+ from genie_python.genie_toggle_settings import ToggleSettings
37
+ from genie_python.utilities import (
38
+ EnvironmentDetails,
39
+ check_lowlimit_against_highlimit,
40
+ get_correct_filepath_existing,
41
+ get_correct_path,
42
+ )
43
+ from genie_python.version import VERSION
44
+
45
+ PVBaseValue = bool | int | float | str
46
+ PVValue = PVBaseValue | list[PVBaseValue] | npt.NDArray | None
47
+
48
+ print("\ngenie_python version " + VERSION)
49
+
50
+ SUPPORTED_PYTHON_VERSION = (3, 11, 6)
51
+ if sys.version_info[0:3] != SUPPORTED_PYTHON_VERSION[0:3]:
52
+ message = (
53
+ "WARNING: genie_python only guarantees support for "
54
+ "Python version {0[0]}.{0[1]}.{0[2]}, you are running {1}".format(
55
+ SUPPORTED_PYTHON_VERSION, sys.version
56
+ )
57
+ )
58
+ print(message, file=sys.stderr)
59
+
60
+
61
+ @log_command_and_handle_exception
62
+ def set_instrument(pv_prefix: str, import_instrument_init: bool = True) -> None:
63
+ """
64
+ Sets the instrument this session is communicating with.
65
+ Used for remote access - do not delete.
66
+
67
+ Args:
68
+ pv_prefix (string): the PV prefix
69
+ import_instrument_init (bool): if True import the instrument init
70
+ from the config area; otherwise don't
71
+ """
72
+ globs = _get_correct_globals()
73
+ _genie_api.set_instrument(pv_prefix, globs, import_instrument_init)
74
+
75
+
76
+ @log_command_and_handle_exception
77
+ def reload_current_config() -> None:
78
+ """
79
+ Reload the current configuration.
80
+ """
81
+ _genie_api.reload_current_config()
82
+
83
+
84
+ @usercommand
85
+ @helparglist("")
86
+ @log_command_and_handle_exception
87
+ def get_blocks() -> list[str]:
88
+ """
89
+ Get the names of the blocks.
90
+
91
+ Returns:
92
+ list: the blocknames
93
+ """
94
+ return _genie_api.get_block_names()
95
+
96
+
97
+ @usercommand
98
+ @helparglist("block")
99
+ @log_command_and_handle_exception
100
+ def get_block_units(block_name: str) -> str | dict | None:
101
+ """
102
+ Get the physical measurement units associated with a block name.
103
+
104
+ Args:
105
+ block_name: name of the block
106
+
107
+ Returns
108
+ string: units of the block
109
+ """
110
+ return _genie_api.get_block_units(block_name)
111
+
112
+
113
+ @usercommand
114
+ @helparglist("...")
115
+ @log_command_and_handle_exception
116
+ def cset(
117
+ *args: str,
118
+ runcontrol: bool | None = None,
119
+ lowlimit: float | None = None,
120
+ highlimit: float | None = None,
121
+ wait: bool | None = None,
122
+ verbose: bool | None = None,
123
+ **kwargs: bool | int | float | str | None,
124
+ ) -> None:
125
+ """
126
+ Sets the setpoint and runcontrol settings for blocks.
127
+
128
+ Args:
129
+ runcontrol (bool, optional): whether to set runcontrol for this block
130
+ wait (bool, optional): pause execution until setpoint is reached (one block only)
131
+ lowlimit (float, optional): the lower limit for runcontrol or waiting
132
+ highlimit (float, optional): the upper limit for runcontrol or waiting
133
+ verbose (bool, optional): report what the new block state is as a result of the command
134
+
135
+ Note: cannot use wait and runcontrol in the same command
136
+
137
+ Examples:
138
+ Setting a value for a block:
139
+
140
+ >>> cset(block1=100)
141
+
142
+ Or:
143
+
144
+ >>> cset("block1", 100)
145
+
146
+ Setting values for more than one block:
147
+
148
+ >>> cset(block1=100, block2=200, block3=300)
149
+
150
+ NOTE: the order in which the values are set is random,
151
+ e.g. block1 may or may not be set before block2 and block3
152
+
153
+ Setting runcontrol values for a block:
154
+
155
+ >>> cset(block1=100, runcontrol=True, lowlimit=99, highlimit=101)
156
+
157
+ Changing runcontrol settings for a block without changing the setpoint:
158
+
159
+ >>> cset("block1", runcontrol=False)
160
+ >>> cset(block1=None, runcontrol=False)
161
+
162
+ Wait for setpoint to be reached (one block only):
163
+
164
+ >>> cset(block1=100, wait=True)
165
+
166
+ Wait for limits to be reached - this does NOT change the runcontrol limits:
167
+
168
+ >>> cset(block1=100, wait=True, lowlimit=99, highlimit=101)
169
+ """
170
+ # Block names contain alpha-numeric and underscores only
171
+
172
+ # See if single block name was entered, i.e. cset("block1", runcontrol=True)
173
+ if len(args) > 0:
174
+ if len(args) > 2:
175
+ raise Exception(
176
+ "Too many arguments, please type: help(g.cset) for more information on the syntax"
177
+ )
178
+ blocks = [args[0]]
179
+ values = [args[1]] if len(args) == 2 else [None]
180
+ elif len(kwargs) > 0:
181
+ # Check for specifying blocks via the cset(block=value) syntax
182
+ blocks, values = zip(*kwargs.items())
183
+ else:
184
+ raise Exception(
185
+ "Incorrect syntax, please type: help(g.cset) for more information on the syntax"
186
+ )
187
+
188
+ for block in blocks:
189
+ if not _genie_api.block_exists(block):
190
+ raise Exception('No block with the name "{}" exists'.format(block))
191
+
192
+ if wait and runcontrol is not None:
193
+ raise Exception("Cannot enable or disable runcontrol at the same time as setting a wait")
194
+ if wait and values[0] is None:
195
+ raise Exception("Cannot wait as no setpoint specified. Please type: help(g.cset) for help")
196
+
197
+ # Warn if highlimit and lowlimit are round the incorrect way
198
+ check_lowlimit_against_highlimit(lowlimit, highlimit)
199
+
200
+ if len(blocks) > 1:
201
+ # Setting multiple blocks, so other settings not allowed
202
+ if not all(argument is None for argument in [runcontrol, lowlimit, highlimit, wait]):
203
+ raise Exception("Runcontrol and wait can only be changed for one block at a time")
204
+
205
+ for block, value in list(zip(blocks, values)):
206
+ # If there are multiple blocks then runcontrol etc.
207
+ # should be None anyway so pass them through
208
+
209
+ _genie_api.set_block_value(block, value, runcontrol, lowlimit, highlimit, wait)
210
+ _warn_if_block_alarm(block)
211
+
212
+ # Display what the new block state is as a result of the command if
213
+ # cset verbosity is toggled on, or the command
214
+ # was specifically called with 'verbose=True'.
215
+ # If default verbosity is True, but the command was specifically
216
+ # called with no verbose, do not display new state.
217
+ if verbose is not False and (verbose or ToggleSettings.cset_verbose):
218
+ for block, value in list(zip(blocks, values)):
219
+ waitfor_block(block=block, value=value, maxwait=5)
220
+ print("Result: ", end="")
221
+ cshow(block)
222
+
223
+
224
+ class _CgetReturn(TypedDict):
225
+ name: str
226
+ value: Any
227
+ unit: str
228
+ connected: bool
229
+ runcontrol: bool
230
+ lowlimit: Any
231
+ highlimit: Any
232
+ alarm: str
233
+
234
+
235
+ @usercommand
236
+ @helparglist("block")
237
+ @log_command_and_handle_exception
238
+ def cget(block: str) -> _CgetReturn:
239
+ """
240
+ Gets the useful values associated with a block.
241
+
242
+ The value will be None if the block is not "connected".
243
+
244
+ Args:
245
+ block (string): the name of the block
246
+
247
+ Returns
248
+ dict: details about about the block. Contains:
249
+ name - name of the block
250
+ value - value of the block
251
+ unit - physical units of the block
252
+ connected - True if connected; False otherwise
253
+ runcontrol - NO not in runcontrol, YES otherwise
254
+ lowlimit - run control low limit set
255
+ highlimit - run control high limit set
256
+ alarm - the alarm status of the block
257
+ """
258
+ ans = _genie_api.get_block_data(block)
259
+ if ans["alarm"] != "NO_ALARM":
260
+ _log_alarmed_block(block, ans["alarm"])
261
+ return ans
262
+
263
+
264
+ def _log_alarmed_block(block_name: str, alarm_state: str) -> None:
265
+ _genie_api.logger.log_info_msg("BLOCK {} IN {} ALARM".format(block_name, alarm_state))
266
+ print("Block {} is in alarm: {}".format(block_name, alarm_state), file=sys.stdout)
267
+
268
+
269
+ def _warn_if_block_alarm(block: object) -> None:
270
+ """
271
+ Checks whether a block is in an alarmed state and warn user (inc log)
272
+
273
+ Args:
274
+ block (object): The block to be checked
275
+ """
276
+ minor, major, invalid = check_alarms(block)
277
+ alarms = {"MINOR": minor, "MAJOR": major, "INVALID": invalid}
278
+ for alarm_type, alarm in alarms.items():
279
+ if alarm:
280
+ _log_alarmed_block(alarm[0], alarm_type)
281
+
282
+
283
+ def _print_from_cget(block_details: dict[str, str]) -> None:
284
+ """
285
+ Prints the values obtained through cget into a
286
+ human readable format, used for cshow.
287
+
288
+ Args:
289
+ block_details (dict): dict containing information on the block
290
+ (see return of cget)
291
+ """
292
+ format_string = (
293
+ "{name} = {value} (runcontrol = {runcontrol}, "
294
+ "lowlimit = {lowlimit}, highlimit = {highlimit}, "
295
+ "alarm = {alarm})"
296
+ )
297
+ if not block_details["connected"]:
298
+ block_details["value"] = "*** disconnected ***"
299
+ print(format_string.format(**block_details))
300
+
301
+
302
+ @log_command_and_handle_exception
303
+ def cshow(block: str | None = None) -> None:
304
+ """
305
+ Show the current settings for one block or for all blocks.
306
+
307
+ Args:
308
+ block (string, optional): the name of the block
309
+
310
+ Examples:
311
+ Showing all block values:
312
+
313
+ >>> cshow()
314
+
315
+ Showing values for one block only (name must be quoted):
316
+
317
+ >>> cshow("block1")
318
+ """
319
+ blocks_to_get = [block] if block is not None else _genie_api.get_block_names()
320
+ for block in blocks_to_get:
321
+ _print_from_cget(_genie_api.get_block_data(block, True))
322
+
323
+
324
+ @log_command_and_handle_exception
325
+ def waitfor(
326
+ block: str | None = None,
327
+ value: float | None = None,
328
+ lowlimit: float | None = None,
329
+ highlimit: float | None = None,
330
+ maxwait: float | None = None,
331
+ wait_all: bool = False,
332
+ seconds: float | None = None,
333
+ minutes: float | None = None,
334
+ hours: float | None = None,
335
+ time: str | None = None,
336
+ frames: int | None = None,
337
+ raw_frames: int | None = None,
338
+ uamps: float | None = None,
339
+ mevents: float | None = None,
340
+ early_exit: Callable[[], bool] = lambda: False,
341
+ quiet: bool = False,
342
+ **pars: bool | int | float | str | Callable[[None], bool] | None,
343
+ ) -> None:
344
+ """
345
+ Interrupts execution until certain conditions are met.
346
+
347
+ Args:
348
+ block (string, optional): the name of the block to wait for
349
+ value (float, optional): the block value to wait for
350
+ lowlimit (float, optional): wait for the block to be >= this value (numeric only)
351
+ highlimit (float, optional): wait for the block to be <= this value (numeric only)
352
+ maxwait (float, optional): wait no longer that the specified number of seconds
353
+ wait_all (bool, optional): wait for all conditions to be met
354
+ (e.g. a number of frames and an amount of uamps)
355
+ seconds (float, optional): wait for a specified number of seconds
356
+ minutes (float, optional): wait for a specified number of minutes
357
+ hours (float, optional): wait for a specified number of hours
358
+ time (string, optional): a quicker way of setting hours, minutes and seconds
359
+ (must be of format "HH:MM:SS")
360
+ frames (int, optional): wait for a total number of good frames to be collected
361
+ raw_frames (int, optional): wait for a total number of raw frames to be collected
362
+ uamps (float, optional): wait for a total number of uamps to be received
363
+ mevents (float, optional): wait for a total number of millions of events to be collected
364
+ early_exit (lambda, optional): stop waiting if the function evaluates to True
365
+ quiet (bool, optional): suppress normal output messages to the console
366
+
367
+ Examples:
368
+ Wait for a block to reach a specific value:
369
+
370
+ >>> waitfor(myblock=123)
371
+ >>> waitfor("myblock", 123)
372
+ >>> waitfor("myblock", True)
373
+ >>> waitfor("myblock", "OPEN")
374
+
375
+ Wait for a block to be between limits:
376
+
377
+ >>> waitfor("myblock", lowlimit=100, highlimit=110)
378
+
379
+ Wait for a block to reach a specific value, but no longer than 60 seconds:
380
+
381
+ >>> waitfor(myblock=123, maxwait=60)
382
+
383
+ Wait for a specified time interval:
384
+
385
+ >>> waitfor(seconds=10)
386
+ >>> waitfor(hours=1, minutes=30, seconds=15)
387
+ >>> waitfor(time="1:30:15")
388
+
389
+ Wait for a data collection condition:
390
+
391
+ >>> waitfor(frames=5000)
392
+ >>> waitfor(uamps=200)
393
+
394
+ Wait for either a number of frames OR a time interval to occur:
395
+
396
+ >>> waitfor(frames=5000, hours=2)
397
+
398
+ Wait for a number of frames AND a time interval to occur:
399
+
400
+ >>> waitfor(frames=5000, hours=2, wait_all=True)
401
+
402
+ Wait for either the block to reach a value or a condition to be met:
403
+
404
+ >>> waitfor(myblock=123, early_exit=lambda:
405
+ some_function(cget("another_block")["value"]) > 123)
406
+ """
407
+ if block is None:
408
+ # Search through the params to see if there is a block there
409
+ blks = _genie_api.get_block_names()
410
+ for k in pars:
411
+ if k in blks:
412
+ if block is not None:
413
+ raise Exception("Can set waitfor for only one block at a time")
414
+ block = k
415
+ value = pars[k]
416
+ else:
417
+ raise ValueError("Block named '{}' did not exist.".format(k))
418
+ # Check that wait_for object exists
419
+ if _genie_api.waitfor is None:
420
+ raise Exception("Cannot execute waitfor - try calling set_instrument first")
421
+ # Warn if highlimit and lowlimit are round correct way
422
+ check_lowlimit_against_highlimit(lowlimit, highlimit)
423
+ # Start_waiting checks the block exists
424
+ _genie_api.waitfor.start_waiting(
425
+ block,
426
+ value,
427
+ lowlimit,
428
+ highlimit,
429
+ maxwait,
430
+ wait_all,
431
+ seconds,
432
+ minutes,
433
+ hours,
434
+ time,
435
+ frames,
436
+ raw_frames,
437
+ uamps,
438
+ mevents,
439
+ early_exit,
440
+ quiet,
441
+ )
442
+
443
+
444
+ @usercommand
445
+ @helparglist("block[, value][, lowlimit][, highlimit][, maxwait]")
446
+ @log_command_and_handle_exception
447
+ def waitfor_block(
448
+ block: str,
449
+ value: bool | int | float | str | None = None,
450
+ lowlimit: float | None = None,
451
+ highlimit: float | None = None,
452
+ maxwait: float | None = None,
453
+ early_exit: Callable[[], bool] = lambda: False,
454
+ quiet: bool = False,
455
+ ) -> None:
456
+ """
457
+ Interrupts execution until block reaches specific value
458
+
459
+ Args:
460
+ block: the name of the block to wait for
461
+ value: the target block value
462
+ lowlimit: waits for the block to be >= this value (numeric only)
463
+ highlimit: waits for the block to be <= this value (numeric only)
464
+ maxwait: wait no longer that the specified number of seconds
465
+ early_exit: stop waiting if the exception evaluates to True
466
+ quiet (bool, optional): suppress normal output messages to the console
467
+
468
+ Examples:
469
+
470
+ >>> waitfor_block("myblock", value=123)
471
+ >>> waitfor_block("myblock", value=True, maxwait=15)
472
+ >>> waitfor_block("myblock", lowlimit=100, highlimit=110)
473
+ >>> waitfor_block("myblock", highlimit=1.0, maxwait=60)
474
+ >>> waitfor_block(
475
+ ... "myblock", value=123, early_exit=lambda: cget("myblock_limit_reached")["value"] != 0
476
+ ... )
477
+ """
478
+ if _genie_api.waitfor is None:
479
+ raise Exception("Cannot execute waitfor_block - try calling set_instrument first")
480
+ # Warn if highlimit and lowlimit are round correct way
481
+ check_lowlimit_against_highlimit(lowlimit, highlimit)
482
+ _genie_api.waitfor.start_waiting(
483
+ block=block,
484
+ value=value,
485
+ lowlimit=lowlimit,
486
+ highlimit=highlimit,
487
+ maxwait=maxwait,
488
+ early_exit=early_exit,
489
+ quiet=quiet,
490
+ )
491
+
492
+
493
+ @usercommand
494
+ @helparglist("[seconds][, minutes][, hours][, time]")
495
+ @log_command_and_handle_exception
496
+ def waitfor_time(
497
+ seconds: float | None = None,
498
+ minutes: float | None = None,
499
+ hours: float | None = None,
500
+ time: str | None = None,
501
+ quiet: bool = False,
502
+ ) -> None:
503
+ """
504
+ Interrupts execution for a specified amount of time
505
+
506
+ Args:
507
+ seconds (float, optional): wait for a specified number of seconds
508
+ minutes (float, optional): wait for a specified number of minutes
509
+ hours (float, optional): wait for a specified number of hours
510
+ time (string, optional): a quicker way of setting hours,
511
+ minutes and seconds (must be of format "HH:MM:SS")
512
+ quiet (bool, optional): suppress normal output messages to the console
513
+
514
+ Examples:
515
+
516
+ >>> waitfor_time(seconds=10)
517
+ >>> waitfor_time(hours=1, minutes=30, seconds=15)
518
+ >>> waitfor_time(time="1:30:15")
519
+ """
520
+ if all(t is None for t in (seconds, minutes, hours, time)):
521
+ raise TypeError(
522
+ "Cannot execute waitfor_time - need to set at least one parameter. "
523
+ "Type help(waitfor_time) "
524
+ "to see guidelines"
525
+ )
526
+ if any(t is not None and t < 0 for t in (seconds, minutes, hours)):
527
+ raise ValueError("Cannot execute waitfor_time - Time parameters cannot be negative")
528
+ if _genie_api.waitfor is None:
529
+ raise TypeError("Cannot execute waitfor_time - try calling set_instrument first")
530
+ _genie_api.waitfor.start_waiting(
531
+ seconds=seconds, minutes=minutes, hours=hours, time=time, quiet=quiet
532
+ )
533
+
534
+
535
+ @usercommand
536
+ @helparglist("frames")
537
+ @log_command_and_handle_exception
538
+ def waitfor_frames(frames: int, quiet: bool = False) -> None:
539
+ """
540
+ Interrupts execution to wait for number of total good frames to reach parameter value
541
+
542
+ Args:
543
+ frames (int): the number of frames to wait for
544
+ quiet (bool, optional): suppress normal output messages to the console
545
+
546
+ Example:
547
+
548
+ >>> waitfor_frames(4000)
549
+ """
550
+ if frames is None:
551
+ raise TypeError(
552
+ "Cannot execute waitfor_frames - need to set frames parameter. Type help(waitfor_frames"
553
+ )
554
+ if frames < 0:
555
+ raise ValueError("Cannot execute waitfor_frames - frames parameter cannot be negative")
556
+ if _genie_api.waitfor is None:
557
+ raise Exception("Cannot execute waitfor_frames - try calling set_instrument first")
558
+ _genie_api.waitfor.start_waiting(frames=frames, quiet=quiet)
559
+
560
+
561
+ @usercommand
562
+ @helparglist("raw_frames")
563
+ @log_command_and_handle_exception
564
+ def waitfor_raw_frames(raw_frames: int, quiet: bool = False) -> None:
565
+ """
566
+ Interrupts execution to wait for number of total raw frames to reach parameter value
567
+
568
+ Args:
569
+ raw frames (int): the number of raw frames to wait for
570
+ quiet (bool, optional): suppress normal output messages to the console
571
+
572
+ Example:
573
+
574
+ >>> waitfor_raw_frames(4000)
575
+ """
576
+ if raw_frames is None:
577
+ raise TypeError(
578
+ "Cannot execute waitfor_raw_frames - need to set raw_frames parameter. "
579
+ "Type help(waitfor_raw_frames"
580
+ )
581
+ if raw_frames < 0:
582
+ raise ValueError(
583
+ "Cannot execute waitfor_raw_frames - raw_frames parameter cannot be negative"
584
+ )
585
+ if _genie_api.waitfor is None:
586
+ raise Exception("Cannot execute waitfor_raw_frames - try calling set_instrument first")
587
+ _genie_api.waitfor.start_waiting(raw_frames=raw_frames, quiet=quiet)
588
+
589
+
590
+ @usercommand
591
+ @helparglist("uamps")
592
+ @log_command_and_handle_exception
593
+ def waitfor_uamps(uamps: float, quiet: bool = False) -> None:
594
+ """
595
+ Interrupts execution to wait for a specific total charge
596
+
597
+ Args:
598
+ uamps: the charge to wait for
599
+ quiet (bool, optional): suppress normal output messages to the console
600
+
601
+ Example:
602
+
603
+ >>> waitfor_uamps(115.5)
604
+ """
605
+ if _genie_api.waitfor is None:
606
+ raise Exception("Cannot execute waitfor_uamps - try calling set_instrument first")
607
+ _genie_api.waitfor.start_waiting(uamps=uamps, quiet=quiet)
608
+
609
+
610
+ @usercommand
611
+ @helparglist("mevents")
612
+ @log_command_and_handle_exception
613
+ def waitfor_mevents(mevents: float, quiet: bool = False) -> None:
614
+ """
615
+ Interrupts execution to wait for number of millions of events to reach parameter value
616
+
617
+ Args:
618
+ mevents (float): the number of millions of events to wait for
619
+ quiet (bool, optional): suppress normal output messages to the console
620
+
621
+ Example:
622
+
623
+ >>> waitfor_mevents(0.0004)
624
+ """
625
+ if mevents is None:
626
+ raise TypeError(
627
+ "Cannot execute waitfor_mevents - need to set mevents parameter. "
628
+ "Type help(waitfor_mevents)"
629
+ )
630
+ if mevents < 0:
631
+ raise ValueError("Cannot execute waitfor_mevents - mevents parameter cannot be negative")
632
+ if _genie_api.waitfor is None:
633
+ raise Exception("Cannot execute waitfor_mevents - try calling set_instrument first")
634
+ _genie_api.waitfor.start_waiting(mevents=mevents, quiet=quiet)
635
+
636
+
637
+ @usercommand
638
+ @helparglist("state[, maxwaitsecs][, onexit]")
639
+ @log_command_and_handle_exception
640
+ def waitfor_runstate(
641
+ state: str, maxwaitsecs: int = 3600, onexit: bool = False, quiet: bool = False
642
+ ) -> None:
643
+ """
644
+ Wait for a particular instrument run state.
645
+
646
+ Args:
647
+ state (string): the state to wait for (e.g. "paused")
648
+ maxwaitsecs (int, optional): the maximum time to wait for the state before carrying on
649
+ onexit (bool, optional): wait for runstate to change from the specified state
650
+ quiet (bool, optional): suppress normal output messages to the console
651
+
652
+ Examples:
653
+ Wait for a run to enter the paused state:
654
+
655
+ >>> waitfor_runstate("paused")
656
+
657
+ Wait for a run to exit the paused state:
658
+
659
+ >>> waitfor_runstate("paused", onexit=True)
660
+ """
661
+ # Check that wait_for object exists
662
+ if _genie_api.waitfor is None:
663
+ raise Exception("Cannot execute waitfor_runstate - try calling set_instrument first")
664
+ _genie_api.waitfor.wait_for_runstate(state, maxwaitsecs, onexit, quiet)
665
+
666
+
667
+ @usercommand
668
+ @helparglist("[block, ...][, start_timeout][, move_timeout]")
669
+ @log_command_and_handle_exception
670
+ def waitfor_move(*blocks: str | None, **kwargs: int | None) -> None:
671
+ """
672
+ Wait for all motion or specific motion to complete.
673
+
674
+ If block names are supplied then it will only wait for those to stop moving.
675
+ Otherwise, it will wait for all motion
676
+ to stop.
677
+
678
+ Args:
679
+ blocks (string, multiple, optional): the names of specific blocks to wait for
680
+ start_timeout (int, optional): the number of seconds to wait for the
681
+ movement to begin (default = 2 seconds)
682
+ move_timeout (int, optional): the maximum number of seconds to wait for motion to stop
683
+
684
+ Examples:
685
+ Wait for all motors to stop moving:
686
+
687
+ >>> waitfor_move()
688
+
689
+ Wait for all motors to stop moving with a timeout of 30 seconds:
690
+
691
+ >>> waitfor_move(move_timeout=30)
692
+
693
+ Wait for only slit1 and slit2 motors to stop moving:
694
+
695
+ >>> waitfor_move("slit1", "slit2")
696
+ """
697
+ # Sort out the parameters
698
+ # Standard parameters
699
+ if "start_timeout" in kwargs:
700
+ start_timeout = kwargs["start_timeout"]
701
+ else:
702
+ start_timeout = 2
703
+ if "move_timeout" in kwargs:
704
+ move_timeout = kwargs["move_timeout"]
705
+ else:
706
+ move_timeout = None
707
+
708
+ # Check that wait_for_move object exists
709
+ if _genie_api.wait_for_move is None:
710
+ raise Exception("Cannot execute waitfor_move - try calling set_instrument first")
711
+
712
+ if len(blocks) > 0:
713
+ # Specified blocks waitfor_move
714
+ move_blocks = list()
715
+ # Check blocks exist
716
+ for b in blocks:
717
+ if _genie_api.block_exists(b):
718
+ move_blocks.append(b)
719
+ else:
720
+ print("Block %s does not exist, so ignoring it" % b)
721
+ _genie_api.wait_for_move.wait_specific(move_blocks, start_timeout, move_timeout)
722
+ else:
723
+ # Standard waitfor_move
724
+ _genie_api.wait_for_move.wait(start_timeout, move_timeout)
725
+
726
+
727
+ @usercommand
728
+ @helparglist("name[, to_string][, is_local][, use_numpy]")
729
+ @log_command_and_handle_exception
730
+ def get_pv(
731
+ name: str, to_string: bool = False, is_local: bool = False, use_numpy: bool = False
732
+ ) -> PVValue:
733
+ """
734
+ Get the value for the specified PV.
735
+
736
+ Args:
737
+ name (string): the name of the PV to get the value for
738
+ to_string (bool, optional): whether to get the value as a string
739
+ is_local (bool, optional): whether to automatically prepend the
740
+ local inst prefix to the PV name
741
+ use_numpy (bool, optional): True use numpy to return arrays,
742
+ False return a list; None for use the default
743
+
744
+ Returns:
745
+ the current PV value
746
+ """
747
+ return _genie_api.get_pv_value(name, to_string, is_local=is_local, use_numpy=use_numpy)
748
+
749
+
750
+ @usercommand
751
+ @helparglist("name, value[, wait][, is_local]")
752
+ @log_command_and_handle_exception
753
+ def set_pv(name: str, value: PVValue, wait: bool = False, is_local: bool = False) -> None:
754
+ """
755
+ Set the value for the specified PV.
756
+
757
+ Args:
758
+ name (string): the PV name
759
+ value: the new value to set
760
+ wait (bool, optional): whether to wait until the value
761
+ has been received by the hardware
762
+ is_local (bool, optional): whether to automatically
763
+ prepend the local inst prefix to the PV name
764
+ """
765
+ _genie_api.set_pv_value(name, value, wait, is_local=is_local)
766
+
767
+
768
+ @usercommand
769
+ @helparglist("pv_list, [, is_local]")
770
+ @log_command_and_handle_exception
771
+ def connected_pvs_in_list(pv_list: list[str], is_local: bool = False) -> list[str]:
772
+ """
773
+ Check if the specified PVs are connected.
774
+
775
+ Args:
776
+ pv_list (list): the PV names
777
+ is_local (bool, optional): whether to automatically prepend the
778
+ local inst prefix to the PV names
779
+
780
+ Returns:
781
+ list: the PV names that are connected
782
+ """
783
+ return _genie_api.connected_pvs_in_list(pv_list, is_local=is_local)
784
+
785
+
786
+ @usercommand
787
+ @helparglist("...")
788
+ @log_command_and_handle_exception
789
+ def begin(
790
+ period: int = 1,
791
+ meas_id: str | None = None,
792
+ meas_type: str = "",
793
+ meas_subid: str = "",
794
+ sample_id: str = "",
795
+ delayed: bool = False,
796
+ quiet: bool = False,
797
+ paused: bool = False,
798
+ verbose: bool = False,
799
+ prepost: bool = True,
800
+ ) -> None:
801
+ """
802
+ Starts a data collection run.
803
+
804
+ Args:
805
+ period (int, optional): the period to begin data collection in
806
+ meas_id (string, optional): the measurement id
807
+ meas_type (string, optional): the type of measurement
808
+ meas_subid (string, optional): the measurement sub-id
809
+ sample_id (string, optional): the sample id
810
+ delayed (bool, optional): puts the period card to into delayed start mode
811
+ quiet (bool, optional): suppress the output to the screen
812
+ paused (bool, optional): begin in the paused state
813
+ verbose (bool, optional): show the messages from the DAE
814
+ prepost (bool, optional): run pre and post commands (default: True)
815
+ Returns:
816
+ Any: return what the begin_postcmd method returns
817
+ """
818
+ # Returns None if we should start the run or the reason why if not
819
+ pre_post_cmd_return = _genie_api.pre_post_cmd_manager.begin_precmd(quiet=quiet, prepost=prepost)
820
+ if pre_post_cmd_return is None:
821
+ _genie_api.dae.begin_run(
822
+ period, meas_id, meas_type, meas_subid, sample_id, delayed, quiet, paused, prepost
823
+ )
824
+
825
+ waitfor_runstate("SETUP", onexit=True)
826
+
827
+ _genie_api.dae.post_begin_check(verbose)
828
+ _genie_api.pre_post_cmd_manager.begin_postcmd(
829
+ run_num=_genie_api.dae.get_run_number(), quiet=quiet, prepost=prepost
830
+ )
831
+ else:
832
+ print(str(pre_post_cmd_return))
833
+
834
+
835
+ @usercommand
836
+ @helparglist("[verbose], [prepost]")
837
+ @log_command_and_handle_exception
838
+ def abort(verbose: bool = False, prepost: bool = True) -> None:
839
+ """
840
+ Abort the current run.
841
+
842
+ Args:
843
+ verbose (bool, optional): show the messages from the DAE
844
+ prepost (bool, optional): run pre and post commands (default: True)
845
+ """
846
+ _genie_api.pre_post_cmd_manager.abort_precmd(prepost=prepost)
847
+ _genie_api.dae.abort_run(prepost)
848
+ _genie_api.dae.post_abort_check(verbose)
849
+ _genie_api.pre_post_cmd_manager.abort_postcmd(prepost=prepost)
850
+
851
+
852
+ @usercommand
853
+ @helparglist("[verbose], [quiet], [immediate], [prepost]")
854
+ @log_command_and_handle_exception
855
+ def end(
856
+ verbose: bool = False, quiet: bool = False, immediate: bool = False, prepost: bool = False
857
+ ) -> None:
858
+ """
859
+ End the current run.
860
+
861
+ Args:
862
+ verbose (bool, optional): show the messages from the DAE
863
+ quiet (bool, optional): suppress the end_precmd output to the screen
864
+ immediate (bool, optional): end immediately, without waiting for
865
+ a period sequence to complete
866
+ prepost (bool, optional): run pre and post commands (default: True)
867
+ """
868
+ _genie_api.pre_post_cmd_manager.end_precmd(quiet=quiet, prepost=prepost)
869
+ _genie_api.dae.end_run(verbose=verbose, quiet=quiet, immediate=immediate, prepost=prepost)
870
+ waitfor_runstate("SETUP")
871
+ _genie_api.dae.post_end_check(verbose)
872
+ _genie_api.pre_post_cmd_manager.end_postcmd(quiet=quiet, prepost=prepost)
873
+
874
+
875
+ @usercommand
876
+ @helparglist("[verbose], [immediate], [prepost]")
877
+ @log_command_and_handle_exception
878
+ def pause(verbose: bool = False, immediate: bool = False, prepost: bool = True) -> None:
879
+ """
880
+ Pause the current run.
881
+
882
+ Args:
883
+ verbose (bool, optional): show the messages from the DAE
884
+ immediate (bool, optional): pause immediately,
885
+ without waiting for a period sequence to complete
886
+ prepost (bool, optional): run pre and post commands (default: True)
887
+ """
888
+ _genie_api.pre_post_cmd_manager.pause_precmd(prepost=prepost)
889
+ _genie_api.dae.pause_run(immediate=immediate, prepost=prepost)
890
+ _genie_api.dae.post_pause_check(verbose)
891
+ _genie_api.pre_post_cmd_manager.pause_postcmd(prepost=prepost)
892
+
893
+
894
+ @usercommand
895
+ @helparglist("[verbose], [prepost]")
896
+ @log_command_and_handle_exception
897
+ def resume(verbose: bool = False, prepost: bool = False) -> None:
898
+ """
899
+ Resume the current run after it has been paused.
900
+
901
+ Args:
902
+ verbose (bool, optional): show the messages from the DAE
903
+ prepost (bool, optional): run pre and post commands (default: True)
904
+ """
905
+ _genie_api.pre_post_cmd_manager.resume_precmd(prepost=prepost)
906
+ _genie_api.dae.resume_run(prepost)
907
+ _genie_api.dae.post_resume_check(verbose)
908
+ _genie_api.pre_post_cmd_manager.resume_postcmd(prepost=prepost)
909
+
910
+
911
+ @usercommand
912
+ @helparglist("[verbose]")
913
+ @log_command_and_handle_exception
914
+ def recover(verbose: bool = False) -> None:
915
+ """
916
+ Recovers the run if it has been aborted.
917
+ The command should be run before the next run is started.
918
+
919
+ Note: the run will be recovered in the paused state.
920
+
921
+ Args:
922
+ verbose (bool, optional): show the messages from the DAE
923
+ """
924
+ _genie_api.dae.recover_run()
925
+ waitfor_runstate("SETUP", onexit=True)
926
+ _genie_api.dae.post_recover_check(verbose)
927
+
928
+
929
+ @usercommand
930
+ @helparglist("[verbose]")
931
+ @log_command_and_handle_exception
932
+ def updatestore(verbose: bool = False) -> None:
933
+ """
934
+ Performs an update and a store operation in a combined operation.
935
+ This is more efficient than doing the commands separately.
936
+
937
+ Args:
938
+ verbose (bool, optional): show the messages from the DAE
939
+ """
940
+ _genie_api.dae.update_store_run()
941
+ waitfor_runstate("SAVING", onexit=True)
942
+ _genie_api.dae.post_update_store_check(verbose)
943
+
944
+
945
+ @usercommand
946
+ @helparglist("[pause_run], [verbose]")
947
+ @log_command_and_handle_exception
948
+ def update(pause_run: bool = True, verbose: bool = False) -> None:
949
+ """
950
+ Data is loaded from the DAE into the computer memory, but is not written to disk.
951
+
952
+ Args:
953
+ pause_run (bool, optional): whether to pause data collection first [optional]
954
+ verbose (bool, optional): show the messages from the DAE
955
+ """
956
+ if pause_run:
957
+ # Pause
958
+ pause(verbose=verbose)
959
+
960
+ # Update
961
+ _genie_api.dae.update_run()
962
+ waitfor_runstate("UPDATING", onexit=True)
963
+ _genie_api.dae.post_update_check(verbose)
964
+
965
+ if pause_run:
966
+ # Resume
967
+ resume(verbose=verbose)
968
+
969
+
970
+ @usercommand
971
+ @helparglist("[verbose]")
972
+ @log_command_and_handle_exception
973
+ def store(verbose: bool = False) -> None:
974
+ """
975
+ Data loaded into memory by a previous update command is now written to disk.
976
+
977
+ Args:
978
+ verbose (bool, optional): show the messages from the DAE
979
+ """
980
+ _genie_api.dae.store_run()
981
+ waitfor_runstate("STORING", onexit=True)
982
+ _genie_api.dae.post_store_check(verbose)
983
+
984
+
985
+ @usercommand
986
+ @helparglist("[filename], [verbose]")
987
+ @log_command_and_handle_exception
988
+ def snapshot_crpt(filename: str = "c:\\Data\\snapshot_crpt.tmp", verbose: bool = False) -> None:
989
+ """
990
+ Create a snapshot of the current data.
991
+
992
+ Args:
993
+ filename (string, optional): where to write the data file(s)
994
+ verbose (bool, optional): show the messages from the DAE
995
+
996
+ Examples:
997
+ Snapshot to a file called my_snapshot:
998
+
999
+ >>> snapshot_crpt("c:\\Data\\my_snapshot")
1000
+ """
1001
+ name = get_correct_path(filename)
1002
+ _genie_api.dae.snapshot_crpt(name)
1003
+ waitfor_runstate("STORING", onexit=True)
1004
+ _genie_api.dae.post_snapshot_check(verbose)
1005
+
1006
+
1007
+ @usercommand
1008
+ @helparglist("[period]")
1009
+ @log_command_and_handle_exception
1010
+ def get_uamps(period: bool = False) -> float:
1011
+ """
1012
+ Get the current number of micro-amp hours.
1013
+
1014
+ Args:
1015
+ period (bool, optional): whether to return the value for the current period only
1016
+
1017
+ Returns:
1018
+ float: the number of uamps
1019
+ """
1020
+ return _genie_api.dae.get_uamps(period)
1021
+
1022
+
1023
+ @usercommand
1024
+ @helparglist("[period]")
1025
+ @log_command_and_handle_exception
1026
+ def get_frames(period: bool = False) -> int:
1027
+ """
1028
+ Gets the current number of good frames.
1029
+
1030
+ Args:
1031
+ period (bool, optional): whether to return the value for the current period only
1032
+
1033
+ Returns:
1034
+ int: the number of frames
1035
+ """
1036
+ return _genie_api.dae.get_good_frames(period)
1037
+
1038
+
1039
+ @usercommand
1040
+ @helparglist("[period]")
1041
+ @log_command_and_handle_exception
1042
+ def get_raw_frames(period: bool = False) -> int:
1043
+ """
1044
+ Gets the current number of raw frames.
1045
+
1046
+ Args:
1047
+ period (bool, optional): whether to return the value for the current period only
1048
+
1049
+ Returns:
1050
+ int: the number of raw frames
1051
+ """
1052
+ return _genie_api.dae.get_raw_frames(period)
1053
+
1054
+
1055
+ @usercommand
1056
+ @helparglist("")
1057
+ @log_command_and_handle_exception
1058
+ def get_runstate() -> str:
1059
+ """
1060
+ Get the current status of the instrument as a string.
1061
+
1062
+ Note: this value can take a few seconds to update after a change of state.
1063
+
1064
+ Returns:
1065
+ string: the current run state
1066
+ """
1067
+ return _genie_api.dae.get_run_state()
1068
+
1069
+
1070
+ @usercommand
1071
+ @helparglist("")
1072
+ @log_command_and_handle_exception
1073
+ def get_time_since_begin(get_timedelta: bool = False) -> float | datetime.timedelta:
1074
+ """
1075
+ Gets the time since start of the current run in seconds or in datetime
1076
+
1077
+ Args:
1078
+ get_timedelta (bool): If true return the value as a datetime object,
1079
+ otherwise return seconds (defaults to false)
1080
+
1081
+ Returns
1082
+ integer: the time since start in seconds if get_datetime is False,
1083
+ or timedelta, the time since begin as a datetime.timedelta object
1084
+ (Year-Month-Day Hour:Minute:Second) if get_datetime is True
1085
+ """
1086
+
1087
+ return _genie_api.dae.get_time_since_begin(get_timedelta)
1088
+
1089
+
1090
+ @usercommand
1091
+ @helparglist("")
1092
+ @log_command_and_handle_exception
1093
+ def get_events() -> int:
1094
+ """
1095
+ Gets the total events for all the detectors.
1096
+
1097
+ Returns:
1098
+ int: the number of events
1099
+ """
1100
+ return _genie_api.dae.get_events()
1101
+
1102
+
1103
+ @usercommand
1104
+ @helparglist("")
1105
+ @log_command_and_handle_exception
1106
+ def get_mevents() -> float:
1107
+ """
1108
+ Gets the total millions of events for all the detectors.
1109
+
1110
+ Returns:
1111
+ float: the number of millions of events
1112
+ """
1113
+ return _genie_api.dae.get_mevents()
1114
+
1115
+
1116
+ @usercommand
1117
+ @helparglist("")
1118
+ @log_command_and_handle_exception
1119
+ def get_period() -> int:
1120
+ """
1121
+ Gets the current period number.
1122
+
1123
+ Returns:
1124
+ int: the current period
1125
+ """
1126
+ return _genie_api.dae.get_period()
1127
+
1128
+
1129
+ @usercommand
1130
+ @helparglist("")
1131
+ @log_command_and_handle_exception
1132
+ def get_number_periods() -> int:
1133
+ """
1134
+ Get the number of software periods.
1135
+
1136
+ Returns:
1137
+ int: the number of periods
1138
+ """
1139
+ return _genie_api.dae.get_num_periods()
1140
+
1141
+
1142
+ @usercommand
1143
+ @helparglist("")
1144
+ @log_command_and_handle_exception
1145
+ def get_number_timechannels() -> int:
1146
+ """
1147
+ Get the number of time channels. This is the number of bins used
1148
+ to map the time region of interest, it does not include the
1149
+ special diagnostic "bin zero" in this count but this fact is not
1150
+ normally of interest to most users.
1151
+
1152
+ Returns:
1153
+ int: the number of time channels
1154
+ """
1155
+ return _genie_api.dae.get_num_timechannels()
1156
+
1157
+
1158
+ @usercommand
1159
+ @helparglist("")
1160
+ @log_command_and_handle_exception
1161
+ def get_number_spectra() -> int:
1162
+ """
1163
+ Get the number of spectra. The diagnostic spectrum zero
1164
+ is not included in the count, so valid spectrum numbers
1165
+ are 0 to get_number_spectra()
1166
+
1167
+ Returns:
1168
+ int: the number of spectra
1169
+ """
1170
+ return _genie_api.dae.get_num_spectra()
1171
+
1172
+
1173
+ @usercommand
1174
+ @helparglist("")
1175
+ @log_command_and_handle_exception
1176
+ def get_spectrum_integrals(with_spec_zero: bool = True) -> npt.NDArray:
1177
+ """
1178
+ Get the event mode spectrum integrals as numpy ND array.
1179
+
1180
+ Args:
1181
+ with_spec_zero (bool, optional): Include or exclude diagnostic spectrum 0
1182
+ if you have 10 spectra and include spectrum zero, your array will be
1183
+ of size 11 and spectrum 5 will be at array[5]. If you exclude spectrum zero
1184
+ then spectrum 5 would be at array[4]
1185
+
1186
+ Returns:
1187
+ numpy int array: spectrum integrals numpy ND array
1188
+ this is of dimensions [periods, spectra]
1189
+ """
1190
+ data = _genie_api.dae.get_spec_integrals()
1191
+ nper = get_number_periods()
1192
+ nsp = get_number_spectra()
1193
+ # original array is nsp + 1 as has spectrum 0
1194
+ data_reshaped = data.reshape((nper, nsp + 1))
1195
+ return data_reshaped if with_spec_zero else data_reshaped[:, 1:]
1196
+
1197
+
1198
+ @usercommand
1199
+ @helparglist("")
1200
+ @log_command_and_handle_exception
1201
+ def get_spectrum_data(with_spec_zero: bool = True) -> npt.NDArray:
1202
+ """
1203
+ Get the event mode spectrum data as numpy ND array.
1204
+
1205
+ Args:
1206
+ with_spec_zero (bool, optional): Include or exclude diagnostic spectrum 0
1207
+ if you have 10 spectra and include spectrum zero, you array will be
1208
+ of size 11 and spectrum 5 will be at array[5]. If you exclude spectrum zero
1209
+ then spectrum 5 would be at array[4]
1210
+
1211
+ Returns:
1212
+ numpy int array: spectrum data ND array
1213
+ this is of dimensions [periods, spectra, time_bins]
1214
+ """
1215
+
1216
+ return adv.get_spectrum_data(with_spec_zero=with_spec_zero)
1217
+
1218
+
1219
+ @usercommand
1220
+ @helparglist("")
1221
+ @log_command_and_handle_exception
1222
+ def get_runnumber() -> str:
1223
+ """
1224
+ Get the current run-number.
1225
+
1226
+ Returns:
1227
+ string: the run-number
1228
+ """
1229
+ return _genie_api.dae.get_run_number()
1230
+
1231
+
1232
+ @usercommand
1233
+ @helparglist("")
1234
+ @log_command_and_handle_exception
1235
+ def get_totalcounts() -> int:
1236
+ """
1237
+ Get the total counts for the current run.
1238
+
1239
+ Returns:
1240
+ int: the total counts
1241
+ """
1242
+ return _genie_api.dae.get_total_counts()
1243
+
1244
+
1245
+ @usercommand
1246
+ @helparglist("")
1247
+ @log_command_and_handle_exception
1248
+ def get_title() -> str:
1249
+ """
1250
+ Returns the current title.
1251
+
1252
+ Returns:
1253
+ string: the title
1254
+ """
1255
+ return _genie_api.dae.get_title()
1256
+
1257
+
1258
+ @usercommand
1259
+ @helparglist("")
1260
+ @log_command_and_handle_exception
1261
+ def get_display_title() -> bool:
1262
+ """
1263
+ Returns the current display title status.
1264
+
1265
+ Returns:
1266
+ boolean: the display title status
1267
+ """
1268
+ return _genie_api.dae.get_display_title()
1269
+
1270
+
1271
+ @usercommand
1272
+ @helparglist("")
1273
+ @log_command_and_handle_exception
1274
+ def get_rb() -> str:
1275
+ """
1276
+ Returns the current RB number.
1277
+
1278
+ Returns:
1279
+ string: the RB number
1280
+ """
1281
+ return _genie_api.dae.get_rb_number()
1282
+
1283
+
1284
+ class _GetdashboardReturn(TypedDict):
1285
+ status: str
1286
+ run_number: str
1287
+ rb_number: str
1288
+ user: str
1289
+ title: str
1290
+ display_title: str
1291
+ run_time: int
1292
+ good_frames_total: int
1293
+ good_frames_period: int
1294
+ raw_frames_total: int
1295
+ raw_frames_period: int
1296
+ beam_current: float
1297
+ total_current: float
1298
+ spectra: int
1299
+ periods: int
1300
+ time_channels: int
1301
+ monitor_spectrum: int
1302
+ monitor_from: float
1303
+ monitor_to: float
1304
+ monitor_counts: int
1305
+
1306
+
1307
+ @usercommand
1308
+ @helparglist("")
1309
+ @log_command_and_handle_exception
1310
+ def get_dashboard() -> _GetdashboardReturn:
1311
+ """
1312
+ Get the current experiment values.
1313
+
1314
+ Returns:
1315
+ dict: the experiment values
1316
+ """
1317
+ data = dict()
1318
+ data["status"] = _genie_api.dae.get_run_state()
1319
+ data["run_number"] = _genie_api.dae.get_run_number()
1320
+ data["rb_number"] = _genie_api.dae.get_rb_number()
1321
+ data["user"] = _genie_api.dae.get_users()
1322
+ data["title"] = _genie_api.dae.get_title()
1323
+ data["display_title"] = _genie_api.dae.get_display_title()
1324
+ data["run_time"] = _genie_api.dae.get_run_duration()
1325
+ data["good_frames_total"] = _genie_api.dae.get_good_frames()
1326
+ data["good_frames_period"] = _genie_api.dae.get_good_frames(True)
1327
+ data["raw_frames_total"] = _genie_api.dae.get_raw_frames()
1328
+ data["raw_frames_period"] = _genie_api.dae.get_raw_frames(True)
1329
+ data["beam_current"] = _genie_api.dae.get_beam_current()
1330
+ data["total_current"] = _genie_api.dae.get_total_uamps()
1331
+ data["spectra"] = _genie_api.dae.get_num_spectra()
1332
+ # data["dae_memory_used"] = genie_api.dae.get_memory_used()
1333
+ # Not implemented in EPICS system
1334
+ data["periods"] = _genie_api.dae.get_num_periods()
1335
+ data["time_channels"] = _genie_api.dae.get_num_timechannels()
1336
+ data["monitor_spectrum"] = _genie_api.dae.get_monitor_spectrum()
1337
+ data["monitor_from"] = _genie_api.dae.get_monitor_from()
1338
+ data["monitor_to"] = _genie_api.dae.get_monitor_to()
1339
+ data["monitor_counts"] = _genie_api.dae.get_monitor_counts()
1340
+ return data
1341
+
1342
+
1343
+ def _get_correct_globals() -> dict[types.FrameType, int]:
1344
+ """
1345
+ This is a hack to find the frame in which to add the script function(s).
1346
+
1347
+ The frame we want is the outermost one that contains a reference to cshow().
1348
+ """
1349
+ import inspect
1350
+
1351
+ globs = dict()
1352
+
1353
+ for i in inspect.stack():
1354
+ if "cshow" in i[0].f_globals:
1355
+ globs = i[0].f_globals
1356
+ return globs
1357
+
1358
+
1359
+ def load_script(name: str, check_script: bool = True, warnings_as_error: bool = False) -> None:
1360
+ """
1361
+ Loads a user script.
1362
+
1363
+ Args:
1364
+ name (string): the name of the file to load. If this is not a
1365
+ full path the file is assumed to be in C:\\scripts
1366
+ check_script: When True run the script checker on the script;
1367
+ False otherwise (default True)
1368
+ warnings_as_error: When true throw an exception on a warning;
1369
+ False otherwise (default False)
1370
+ """
1371
+
1372
+ globs = _get_correct_globals()
1373
+
1374
+ _genie_api.logger.log_info_msg("Trying to resolve full filename for %s" % name)
1375
+
1376
+ try:
1377
+ try:
1378
+ full_name = get_correct_filepath_existing(name)
1379
+ except Exception:
1380
+ # Try with default script directory prepended
1381
+
1382
+ full_name = get_correct_filepath_existing(os.path.join(get_user_script_dir(), name))
1383
+ except Exception:
1384
+ raise Exception("Script file was not found (%s)" % get_correct_path(name))
1385
+
1386
+ _genie_api.logger.log_info_msg("Trying to load script from: %s" % full_name)
1387
+
1388
+ directory, filename = os.path.split(os.path.abspath(full_name))
1389
+
1390
+ # Add the directory to the path in case there are relative imports
1391
+ if directory not in sys.path:
1392
+ sys.path.append(directory)
1393
+
1394
+ try:
1395
+ # Now check the script details with a linter
1396
+ if check_script:
1397
+ instrument_full_name = _genie_api.get_instrument_full_name()
1398
+ sc = ScriptChecker()
1399
+ errs = sc.check_script(
1400
+ full_name, instrument_full_name, warnings_as_error=warnings_as_error
1401
+ )
1402
+ if len(errs) > 0:
1403
+ combined = "script not loaded as errors found in script: "
1404
+ for e in errs:
1405
+ combined += "\n\t" + e
1406
+ raise Exception(combined)
1407
+
1408
+ mod = __load_module(filename[0:-3], directory)
1409
+ # Safe to load
1410
+ # Read the file to get the name of the functions
1411
+ funcs = []
1412
+ file_path = os.path.join(directory, filename)
1413
+ with open(file_path) as f:
1414
+ for line in f.readlines():
1415
+ m = re.match(r"^def\s+(.+)\(", line)
1416
+ if m is not None:
1417
+ funcs.append(m.group(1))
1418
+
1419
+ scripts = []
1420
+ for att in dir(mod):
1421
+ if isinstance(mod.__dict__.get(att), types.FunctionType):
1422
+ # Check function comes from script file not an import
1423
+ if att in funcs:
1424
+ scripts.append(att)
1425
+
1426
+ if len(scripts) > 0:
1427
+ # This is where the script file is actually loaded
1428
+ with open(file_path) as f:
1429
+ file_contents = f.read()
1430
+
1431
+ # dont_inherit=True so that __future__
1432
+ # statements in this file are not propagated to user scripts
1433
+ code = compile(file_contents, file_path, "exec", dont_inherit=True)
1434
+ exec(code, globs)
1435
+
1436
+ msg = "Loaded the following script(s): "
1437
+ for script in scripts:
1438
+ msg += script + ", "
1439
+ print(msg[0:-2])
1440
+ print("From: %s" % file_path)
1441
+
1442
+ print(
1443
+ "File last modified: %s"
1444
+ % datetime.datetime.fromtimestamp(os.path.getmtime(file_path)).strftime(
1445
+ "%Y-%m-%d %H:%M:%S"
1446
+ )
1447
+ )
1448
+ else:
1449
+ raise Exception(
1450
+ "No scripts found in {} - please ensure all your code is"
1451
+ " contained within functions.".format(file_path)
1452
+ )
1453
+ except Exception as e:
1454
+ if directory in sys.path:
1455
+ sys.path.remove(directory)
1456
+ raise
1457
+
1458
+
1459
+ def __load_module(name: str, directory: str) -> types.ModuleType:
1460
+ """
1461
+ This will reload the module if it has already been loaded.
1462
+ """
1463
+ fpath = None
1464
+ try:
1465
+ fpath, pathname, description = imp.find_module(name, [directory])
1466
+ return imp.load_module(name, fpath, pathname, description)
1467
+ finally:
1468
+ # Since we may exit via an exception, close fpath explicitly.
1469
+ if fpath is not None:
1470
+ fpath.close()
1471
+
1472
+
1473
+ @log_command_and_handle_exception
1474
+ def get_script_dir() -> str:
1475
+ """
1476
+ Get the current script directory.
1477
+
1478
+ Returns:
1479
+ string: the directory
1480
+ """
1481
+ return get_user_script_dir()
1482
+
1483
+
1484
+ @log_command_and_handle_exception
1485
+ def change_script_dir(*directory: str) -> None:
1486
+ """
1487
+ Set the directory for loading user scripts from.
1488
+
1489
+ Args:
1490
+ directory (string|List(string)): the directory to load user scripts from,
1491
+ either as a single entry or as multiple arguments
1492
+ Example:
1493
+ g.change_script_dir(r"c/scrips/mydir")
1494
+ g.change_script_dir(r"c/scrips", "mydir")
1495
+
1496
+
1497
+ """
1498
+ set_user_script_dir(*directory)
1499
+
1500
+
1501
+ @usercommand
1502
+ @helparglist("")
1503
+ @log_command_and_handle_exception
1504
+ def change_start() -> None:
1505
+ """
1506
+ Start a change operation.
1507
+
1508
+ The operation is finished when change_finish is called.
1509
+
1510
+ Between these two calls a sequence of other change commands can be called.
1511
+ For example: change_tables, change_tcb etc.
1512
+ """
1513
+ _genie_api.dae.change_start()
1514
+
1515
+
1516
+ @usercommand
1517
+ @helparglist("")
1518
+ @log_command_and_handle_exception
1519
+ def change_finish() -> None:
1520
+ """
1521
+ End a change operation.
1522
+
1523
+ The operation is begun when change_start is called.
1524
+
1525
+ Between these two calls a sequence of other change commands can be called.
1526
+ For example: change_tables, change_tcb etc.
1527
+ """
1528
+ _genie_api.dae.change_finish()
1529
+
1530
+
1531
+ @usercommand
1532
+ @helparglist("spec, low, high")
1533
+ @log_command_and_handle_exception
1534
+ def change_monitor(spec: int, low: float, high: float) -> None:
1535
+ """
1536
+ Change the monitor to a specified spectrum and range.
1537
+
1538
+ Args:
1539
+ spec (int): the spectrum number
1540
+ low (float): the low end of the integral
1541
+ high (float): the high end of the integral
1542
+ """
1543
+ _genie_api.dae.change_monitor(spec, low, high)
1544
+
1545
+
1546
+ @usercommand
1547
+ @helparglist("[wiring], [detector], [spectra]")
1548
+ @log_command_and_handle_exception
1549
+ def change_tables(
1550
+ wiring: str | None = None, detector: str | None = None, spectra: str | None = None
1551
+ ) -> None:
1552
+ """
1553
+ Load the wiring, detector and/or spectra tables.
1554
+ Checks that the file paths are valid, throws exception if not.
1555
+ Args:
1556
+ wiring (string, optional): the filename of the wiring table file
1557
+ detector (string, optional): the filename of the detector table file
1558
+ spectra (string, optional): the filename of the spectra table file
1559
+ """
1560
+
1561
+ def get_table_file(path: str) -> str:
1562
+ """Assume the path is a correct path, if not try it relative to tables directory"""
1563
+ try:
1564
+ return get_correct_filepath_existing(path)
1565
+ except Exception:
1566
+ env_details = EnvironmentDetails()
1567
+ tables_dir = os.path.join(env_details.get_settings_directory(), "tables")
1568
+ return get_correct_filepath_existing(os.path.join(tables_dir, path))
1569
+
1570
+ errors = []
1571
+ if wiring is not None:
1572
+ try:
1573
+ wiring = get_table_file(wiring)
1574
+ except Exception:
1575
+ errors.append(
1576
+ "Could not find wiring table. Did you type the file name correctly? %s" % wiring
1577
+ )
1578
+ if spectra is not None:
1579
+ try:
1580
+ spectra = get_table_file(spectra)
1581
+ except Exception:
1582
+ errors.append(
1583
+ "Could not find spectra table. Did you type the file name correctly? %s" % spectra
1584
+ )
1585
+ if detector is not None:
1586
+ try:
1587
+ detector = get_table_file(detector)
1588
+ except Exception:
1589
+ errors.append(
1590
+ "Could not find detector table. Did you type the file name correctly? %s" % detector
1591
+ )
1592
+
1593
+ if errors:
1594
+ raise FileNotFoundError(" ".join(errors))
1595
+ elif not all(path is None for path in (wiring, detector, spectra)):
1596
+ _genie_api.dae.change_tables(wiring, detector, spectra)
1597
+ else:
1598
+ raise ValueError("No file paths were provided.")
1599
+
1600
+
1601
+ @usercommand
1602
+ @helparglist("source")
1603
+ @log_command_and_handle_exception
1604
+ def change_sync(source: str) -> None:
1605
+ """
1606
+ Change the source the DAE using for synchronisation.
1607
+
1608
+ Args:
1609
+ source (string): the source to use (
1610
+ 'isis',
1611
+ 'internal',
1612
+ 'smp',
1613
+ 'muon cerenkov',
1614
+ 'muon ms',
1615
+ 'isis (first ts1)'
1616
+ )
1617
+ """
1618
+ _genie_api.dae.change_sync(source)
1619
+
1620
+
1621
+ @usercommand
1622
+ @helparglist("[tcbfile], [default]")
1623
+ @log_command_and_handle_exception
1624
+ def change_tcb_file(tcbfile: str | None = None, default: bool = False) -> None:
1625
+ """
1626
+ Change the time channel boundaries.
1627
+
1628
+ Args:
1629
+ tcbfile (string, optional): the file to load
1630
+ default (bool, optional): load the default file
1631
+ """
1632
+ _genie_api.dae.change_tcb_file(tcbfile, default)
1633
+
1634
+
1635
+ @usercommand
1636
+ @helparglist("[low], [high], [step], [trange], [log], [regime]")
1637
+ @log_command_and_handle_exception
1638
+ def change_tcb(
1639
+ low: float | None = None,
1640
+ high: float | None = None,
1641
+ step: float | None = None,
1642
+ trange: int = 1,
1643
+ log: bool = False,
1644
+ regime: int = 1,
1645
+ ) -> None:
1646
+ """
1647
+ Change the time channel boundaries.
1648
+ If None is specified for low, high or step then the values are left unchanged.
1649
+
1650
+ Args
1651
+ low (float, optional): the lower limit. Default is no change from the current value.
1652
+ high (float, optional): the upper limit. Default is no change from the current value.
1653
+ step (float,optional): the step size. Default is no change from the current value.
1654
+ trange (int, optional): the time range (1 to 5). Default is 1.
1655
+ log (bool, optional): whether to use LOG binning. Default is no.
1656
+ regime (int, optional): the time regime to set (1 to 6). Default is 1.
1657
+
1658
+ Examples:
1659
+ Changes the from, to and step of the 1st range to 0, 10 and 5 respectively.
1660
+
1661
+ >>> change_tcb(0, 10, 5)
1662
+
1663
+ Changes the step size of the 2nd range to 2, leaving other parameters unchanged.
1664
+
1665
+ >>> change_tcb(step=2, trange=2)
1666
+ """
1667
+ _genie_api.dae.change_tcb(low, high, step, trange, log, regime)
1668
+
1669
+
1670
+ @usercommand
1671
+ @helparglist("trange, [regime]")
1672
+ @log_command_and_handle_exception
1673
+ def get_tcb_settings(trange: int, regime: int = 1) -> dict[str, int]:
1674
+ """
1675
+ Gets a dictionary of the time channel settings.
1676
+
1677
+ Args:
1678
+ trange (int): the time range to read (1 to 5)
1679
+ regime (int, optional): the regime to read (1 to 6). Default is 1.
1680
+
1681
+ Returns:
1682
+ dict: the low, high and step for the supplied range and regime
1683
+
1684
+ Examples:
1685
+ Get the step size for the 2nd range in the 3rd regime:
1686
+
1687
+ >>> get_tcb_settings(2, 3)["Steps"]
1688
+
1689
+ Get the step size for the 2nd range in the 3rd regime:
1690
+
1691
+ >>> get_tcb_settings(2, 3)["Steps"]
1692
+ """
1693
+ return _genie_api.dae.get_tcb_settings(trange, regime)
1694
+
1695
+
1696
+ @usercommand
1697
+ @helparglist("[...]")
1698
+ @log_command_and_handle_exception
1699
+ def change_vetos(**params: bool | None) -> None:
1700
+ """
1701
+ Change the DAE veto settings.
1702
+
1703
+ Args:
1704
+ clearall (bool, optional): remove all vetos
1705
+ smp (bool, optional): set SMP veto
1706
+ ts2 (bool, optional): set TS2 veto
1707
+ hz50 (bool, optional): set 50 hz veto
1708
+ ext0 (bool, optional): set external veto 0
1709
+ ext1 (bool, optional): set external veto 1
1710
+ ext2 (bool, optional): set external veto 2
1711
+ ext3 (bool, optional): set external veto 3
1712
+ fifo (bool, optional): set FIFO veto
1713
+
1714
+ Note: If clearall is specified then all vetos (excluding the FIFO veto) are turned off,
1715
+ but it is possible to turn other vetoes back on at the same time.
1716
+
1717
+ Note: FIFO veto is automatically enabled on run begin, but can be changed whilst running.
1718
+
1719
+ Examples:
1720
+ Turns all vetoes off then turns the SMP veto back on:
1721
+
1722
+ >>> change_vetos(clearall=True, smp=True)
1723
+
1724
+ Turn off FIFO:
1725
+
1726
+ >>> change_vetos(fifo=False)
1727
+ """
1728
+ _genie_api.dae.change_vetos(**params)
1729
+
1730
+
1731
+ @usercommand
1732
+ @helparglist("[enable], [delay], [width]")
1733
+ @log_command_and_handle_exception
1734
+ def change_fermi_veto(enable: bool | None = None, delay: float = 1.0, width: float = 1.0) -> None:
1735
+ """
1736
+ Configure the fermi chopper veto.
1737
+
1738
+ Args:
1739
+ enable (bool, optional): enable the fermi veto
1740
+ delay (float, optional): the veto delay
1741
+ width (float, optional): the veto width
1742
+ """
1743
+ _genie_api.dae.set_fermi_veto(enable, delay, width)
1744
+
1745
+
1746
+ @usercommand
1747
+ @helparglist("[nperiods]")
1748
+ @log_command_and_handle_exception
1749
+ def enable_soft_periods(nperiods: int | None = None) -> None:
1750
+ """
1751
+ Switch the DAE to software periods mode.
1752
+
1753
+ Args:
1754
+ nperiods (int, optional): the number of software periods
1755
+ """
1756
+ _genie_api.dae.set_period_mode("soft")
1757
+ if nperiods is not None:
1758
+ _genie_api.dae.set_num_soft_periods(nperiods)
1759
+
1760
+
1761
+ @usercommand
1762
+ @helparglist("mode[, ...]")
1763
+ @log_command_and_handle_exception
1764
+ def enable_hard_periods(
1765
+ mode: str,
1766
+ period_file: str | None = None,
1767
+ sequences: int | None = None,
1768
+ output_delay: int | None = None,
1769
+ period: int | None = None,
1770
+ daq: bool = False,
1771
+ dwell: bool = False,
1772
+ unused: bool = False,
1773
+ frames: int | None = None,
1774
+ output: int | None = None,
1775
+ label: str | None = None,
1776
+ ) -> None:
1777
+ """
1778
+ Sets the DAE to use hardware periods.
1779
+
1780
+ Args:
1781
+ mode (string): set the mode to internal ('int') or external ('ext')
1782
+ period_file (string, optional): the file containing the internal period settings
1783
+ (ignores any other settings)
1784
+ sequences (int, optional): the number of times to repeat the period loop (0 = infinite loop)
1785
+ output_delay (int, optional): the output delay in microseconds
1786
+ period (int, optional): the number of the period to set the following parameters for
1787
+ daq (bool, optional): the specified period is a acquisition period
1788
+ dwell (bool, optional): the specified period is a dwell period
1789
+ unused (bool, optional): the specified period is a unused period
1790
+ frames (int, optional): the number of frames to count for the specified period
1791
+ output (int, optional): the binary output the specified period
1792
+ label (string, optional): the label for the period the specified period
1793
+
1794
+ Note: if the period number is unspecified then the settings will be applied to all periods
1795
+
1796
+ Examples:
1797
+ Setting external periods:
1798
+
1799
+ >>> enable_hard_periods("ext")
1800
+
1801
+ Setting internal periods from a file:
1802
+
1803
+ >>> enable_hard_periods("int", "c:\\myperiods.txt")
1804
+ """
1805
+ _genie_api.dae.configure_hard_periods(
1806
+ mode,
1807
+ period_file,
1808
+ sequences,
1809
+ output_delay,
1810
+ period,
1811
+ daq,
1812
+ dwell,
1813
+ unused,
1814
+ frames,
1815
+ output,
1816
+ label,
1817
+ )
1818
+
1819
+
1820
+ @usercommand
1821
+ @helparglist("[...]")
1822
+ @log_command_and_handle_exception
1823
+ def configure_internal_periods(
1824
+ sequences: int | None = None,
1825
+ output_delay: int | None = None,
1826
+ period: int | None = None,
1827
+ daq: bool = False,
1828
+ dwell: bool = False,
1829
+ unused: bool = False,
1830
+ frames: int | None = None,
1831
+ output: int | None = None,
1832
+ label: str | None = None,
1833
+ ) -> None:
1834
+ """
1835
+ Configure the internal periods without switching to internal period mode.
1836
+
1837
+ Args:
1838
+ sequences (int, optional): the number of times to repeat the period loop (0 = infinite loop)
1839
+ output_delay (int, optional): the output delay in microseconds
1840
+ period (int, optional): the number of the period to set the following parameters for
1841
+ daq (bool, optional): the specified period is a acquisition period
1842
+ dwell (bool, optional): the specified period is a dwell period
1843
+ unused (bool, optional): the specified period is a unused period
1844
+ frames (int, optional): the number of frames to count for the specified period
1845
+ output (int, optional): the binary output the specified period
1846
+ label (string, optional): the label for the period the specified period
1847
+
1848
+ Note: if the period number is unspecified then the settings will be applied to all periods
1849
+ """
1850
+ _genie_api.dae.configure_internal_periods(
1851
+ sequences, output_delay, period, daq, dwell, unused, frames, output, label
1852
+ )
1853
+
1854
+
1855
+ @usercommand
1856
+ @helparglist("[...]")
1857
+ @log_command_and_handle_exception
1858
+ def define_hard_period(
1859
+ period: int | None = None,
1860
+ daq: bool = False,
1861
+ dwell: bool = False,
1862
+ unused: bool = False,
1863
+ frames: int | None = None,
1864
+ output: int | None = None,
1865
+ label: str | None = None,
1866
+ ) -> None:
1867
+ """
1868
+ Define the internal hardware periods.
1869
+
1870
+ Args:
1871
+ period (int, optional): the number of the period to set the following parameters for
1872
+ daq (bool, optional): the specified period is a acquisition period
1873
+ dwell (bool, optional): the specified period is a dwell period
1874
+ unused (bool, optional): the specified period is a unused period
1875
+ frames (int, optional): the number of frames to count for the specified period
1876
+ output (int, optional): the binary output the specified period
1877
+ label (string, optional): the label for the period the specified period
1878
+
1879
+ Note: if the period number is unspecified then the settings will be applied to all periods
1880
+ """
1881
+ configure_internal_periods(None, None, period, daq, dwell, unused, frames, output, label)
1882
+
1883
+
1884
+ @log_command_and_handle_exception
1885
+ def change(**params: str | int) -> None:
1886
+ """
1887
+ Change experiment parameters.
1888
+
1889
+ Note: it is possible to change more than one item at a time.
1890
+
1891
+ Args:
1892
+ title (string, optional): the new title
1893
+ period (int, optional): the new period (must be in a non-running state)
1894
+ nperiods (int, optional): the new number of software periods
1895
+ (must be in a non-running state)
1896
+ user (string, optional): the new user(s) as a comma-separated list
1897
+ rb (int, optional): the new RB number
1898
+
1899
+ Examples:
1900
+ Change the title:
1901
+
1902
+ >>> change(title="The new title")
1903
+
1904
+ Change the user:
1905
+
1906
+ >>> change(user="Instrument Team")
1907
+
1908
+ Set multiple users:
1909
+
1910
+ >>> change(user="Thouless, Haldane, Kosterlitz")
1911
+
1912
+ Change the RB number and the users:
1913
+
1914
+ >>> change(rb=123456, user="Smith, Jones")
1915
+ """
1916
+ for k in params:
1917
+ key = k.lower().strip()
1918
+ if key == "title":
1919
+ change_title(params[k])
1920
+ elif key == "period":
1921
+ change_period(params[k])
1922
+ elif key == "nperiods":
1923
+ change_number_soft_periods(params[k])
1924
+ elif key == "user" or key == "users":
1925
+ change_users(params[k])
1926
+ elif key == "rb":
1927
+ change_rb(params[k])
1928
+ else:
1929
+ raise KeyError("Unknown parameter supplied. Type help(change) for more information")
1930
+
1931
+
1932
+ @usercommand
1933
+ @helparglist("title")
1934
+ @log_command_and_handle_exception
1935
+ def change_title(title: str) -> None:
1936
+ """
1937
+ Sets the current title.
1938
+
1939
+ Args:
1940
+ title: the new title
1941
+ """
1942
+ _genie_api.dae.set_title(title)
1943
+
1944
+
1945
+ @usercommand
1946
+ @helparglist("display_title")
1947
+ @log_command_and_handle_exception
1948
+ def set_display_title(display_title: bool) -> None:
1949
+ """
1950
+ Sets the current display title status.
1951
+
1952
+ Args:
1953
+ display_title: the new display title status
1954
+ """
1955
+ _genie_api.dae.set_display_title(display_title)
1956
+
1957
+
1958
+ @usercommand
1959
+ @helparglist("period")
1960
+ @log_command_and_handle_exception
1961
+ def change_period(period: int) -> None:
1962
+ """
1963
+ Changes the current period number.
1964
+
1965
+ Args:
1966
+ period (int): the period to switch to
1967
+ """
1968
+ _genie_api.dae.set_period(period)
1969
+
1970
+
1971
+ @usercommand
1972
+ @helparglist("number[, enable]")
1973
+ @log_command_and_handle_exception
1974
+ def change_number_soft_periods(number: int, enable: bool = False) -> None:
1975
+ """
1976
+ Sets the number of software periods for the DAE.
1977
+
1978
+ Args:
1979
+ number (int): the number of periods to create
1980
+ enable (bool, optional): switch to soft period mode
1981
+ """
1982
+ if enable:
1983
+ _genie_api.dae.set_period_mode("soft")
1984
+ _genie_api.dae.set_num_soft_periods(number)
1985
+
1986
+
1987
+ @usercommand
1988
+ @helparglist("")
1989
+ @log_command_and_handle_exception
1990
+ def get_users() -> str:
1991
+ """
1992
+ Get the users.
1993
+
1994
+ Returns:
1995
+ str: the users.
1996
+ """
1997
+ return _genie_api.dae.get_users()
1998
+
1999
+
2000
+ @usercommand
2001
+ @helparglist("users")
2002
+ @log_command_and_handle_exception
2003
+ def change_users(users: str) -> None:
2004
+ """
2005
+ Changes the users.
2006
+
2007
+ Args:
2008
+ users: a string containing the user name(s)
2009
+
2010
+ Example:
2011
+
2012
+ >>> change_users("Emerson, Lake, Palmer")
2013
+ """
2014
+ _genie_api.dae.set_users(users)
2015
+
2016
+
2017
+ @usercommand
2018
+ @helparglist("rb")
2019
+ @log_command_and_handle_exception
2020
+ def change_rb(rb: int | str) -> None:
2021
+ """
2022
+ Changes the RB number.
2023
+
2024
+ Args:
2025
+ rb (int or string): the new RB number
2026
+ """
2027
+ if isinstance(rb, int):
2028
+ # If it is an int then that is fine, just cast to str as the PV is a string
2029
+ rb = str(rb)
2030
+ elif isinstance(rb, str):
2031
+ # Let's be kind in case they enter a string.
2032
+ # Check string contains only digits though
2033
+ if not rb.isdigit():
2034
+ raise TypeError("RB number must be a number.")
2035
+ else:
2036
+ raise TypeError("RB number must be a number.")
2037
+ _genie_api.dae.set_rb_number(rb)
2038
+
2039
+
2040
+ class _GetspectrumReturn(TypedDict):
2041
+ time: list[float]
2042
+ signal: list[float]
2043
+ sum: None
2044
+ mode: str
2045
+
2046
+
2047
+ @usercommand
2048
+ @helparglist("spectrum[, period][, dist]")
2049
+ @log_command_and_handle_exception
2050
+ def get_spectrum(spectrum: int, period: int = 1, dist: bool = True) -> _GetspectrumReturn:
2051
+ """
2052
+ Get the specified spectrum from the DAE.
2053
+
2054
+ Args:
2055
+ spectrum (int): the spectrum number
2056
+ period (int, optional): the period
2057
+ dist (bool, optional): whether to get the spectrum as a distribution. Default is True.
2058
+
2059
+ Returns:
2060
+ dict: dictionary of values
2061
+ """
2062
+ return _genie_api.dae.get_spectrum(spectrum, period, dist)
2063
+
2064
+
2065
+ @usercommand
2066
+ @helparglist("spectrum[, period][, dist]")
2067
+ @log_command_and_handle_exception
2068
+ def plot_spectrum(spectrum: int, period: int = 1, dist: bool = True) -> object:
2069
+ """
2070
+ Get the specified spectrum from the DAE and plot it. Returns the plot that was created.
2071
+
2072
+ Note: this will replace any other plots which are open.
2073
+
2074
+ Args:
2075
+ spectrum (int): the spectrum number
2076
+ period (int, optional): the period. Default is 1
2077
+ dist (bool, optional): whether to get the spectrum as a distribution. Default is True
2078
+
2079
+ Returns:
2080
+ The created plot
2081
+
2082
+ """
2083
+ # Import SpectraPlot locally as it uses matplotlib, and the user may want to change
2084
+ # some matplotlib config parameters before it is used for the first time.
2085
+ from genie_python.genie_plot import SpectraPlot
2086
+
2087
+ return SpectraPlot(_genie_api, spectrum, period, dist)
2088
+
2089
+
2090
+ @usercommand
2091
+ @helparglist("spectrum[, period][, t_min][, t_max]")
2092
+ @log_command_and_handle_exception
2093
+ def integrate_spectrum(
2094
+ spectrum: int, period: int = 1, t_min: float | None = None, t_max: float | None = None
2095
+ ) -> float | None:
2096
+ """
2097
+ Integrates the spectrum within the time period and returns neutron counts.
2098
+
2099
+ The underlying algorithm sums the counts from each bin, if a bin is split by
2100
+ the time region then a proportional fraction of the count for that bin is used.
2101
+
2102
+ Args:
2103
+ spectrum (int): the spectrum number
2104
+ period (int, optional): the period
2105
+ t_min (float, optional): time of flight to start from
2106
+ t_max (float, optional): time of flight to finish at
2107
+
2108
+ Returns:
2109
+ float: integral of the spectrum (neutron counts); None spectrum can not be read
2110
+ """
2111
+ return _genie_api.dae.integrate_spectrum(spectrum, period, t_min, t_max)
2112
+
2113
+
2114
+ class GetSampleParsReturnMEAS(TypedDict):
2115
+ ID: int
2116
+ LABEL: str
2117
+ SUBID: int
2118
+ TYPE: int
2119
+
2120
+
2121
+ class GetSampleParsReturnSCRIPT(TypedDict):
2122
+ NAME: str
2123
+
2124
+
2125
+ class _GetSampleParsReturn(TypedDict):
2126
+ AOI: float
2127
+ COMMENTS: str
2128
+ FIELD_LABEL: str
2129
+ GEOMETRY: str
2130
+ HEIGHT: float
2131
+ ID: int
2132
+ MEAS: GetSampleParsReturnMEAS
2133
+ NAME: str
2134
+ PHI: float
2135
+ SCRIPT: GetSampleParsReturnSCRIPT
2136
+ TEMP_LABEL: str
2137
+ THICK: float
2138
+ TYPE: str
2139
+ WIDTH: float
2140
+
2141
+
2142
+ @usercommand
2143
+ @helparglist("")
2144
+ @log_command_and_handle_exception
2145
+ def get_sample_pars() -> _GetSampleParsReturn:
2146
+ """
2147
+ Get the current sample parameter values.
2148
+
2149
+ Returns:
2150
+ dict: the sample parameters
2151
+ """
2152
+ names = _genie_api.get_sample_pars()
2153
+ return names
2154
+
2155
+
2156
+ @usercommand
2157
+ @helparglist("name, value")
2158
+ @log_command_and_handle_exception
2159
+ def change_sample_par(name: str, value: bool | int | float | str | None) -> None:
2160
+ """
2161
+ Set a new value for a sample parameter.
2162
+
2163
+ Args:
2164
+ name (string): the name of the parameter to change
2165
+ value: the new value
2166
+ """
2167
+ _genie_api.set_sample_par(name, value)
2168
+
2169
+
2170
+ class _GetbeamlineparsReturnBEAMSTOP(TypedDict):
2171
+ POS: str
2172
+
2173
+
2174
+ class _GetbeamlineparsReturnCHOPEN(TypedDict):
2175
+ ANG: float
2176
+
2177
+
2178
+ class _GetbeamlineparsReturnJOURNAL(TypedDict):
2179
+ BLOCKS: str
2180
+
2181
+
2182
+ class _GetbeamlineparsReturn(TypedDict):
2183
+ A1: float
2184
+ A2: float
2185
+ A3: float
2186
+ BCX: float
2187
+ BCY: float
2188
+ BEAMSTOP: _GetbeamlineparsReturnBEAMSTOP
2189
+ CHOPEN: _GetbeamlineparsReturnCHOPEN
2190
+ CURR_CONFIG: str
2191
+ FOEMIRROR: float
2192
+ GEOMETRY: str
2193
+ JOURNAL: _GetbeamlineparsReturnJOURNAL
2194
+ L1: float
2195
+ SDD: float
2196
+
2197
+
2198
+ @usercommand
2199
+ @helparglist("")
2200
+ @log_command_and_handle_exception
2201
+ def get_beamline_pars() -> _GetbeamlineparsReturn:
2202
+ """
2203
+ Get the current beamline parameter values.
2204
+
2205
+ Returns:
2206
+ dict: the beamline parameters
2207
+ """
2208
+ names = _genie_api.get_beamline_pars()
2209
+ return names
2210
+
2211
+
2212
+ @usercommand
2213
+ @helparglist("name, value")
2214
+ @log_command_and_handle_exception
2215
+ def change_beamline_par(name: str, value: bool | int | float | str | None) -> None:
2216
+ """
2217
+ Set a new value for a beamline parameter
2218
+
2219
+ Args:
2220
+ name (string): the name of the parameter to change
2221
+ value: the new value
2222
+ """
2223
+ _genie_api.set_beamline_par(name, value)
2224
+
2225
+
2226
+ @usercommand
2227
+ @helparglist("phone_num, message")
2228
+ @log_command_and_handle_exception
2229
+ def send_sms(phone_num: str, message: str) -> None:
2230
+ """
2231
+ Sends an SMS message to a phone number.
2232
+ If you are sending to messages to the same number often consider using g.alerts.send()
2233
+
2234
+ Args:
2235
+ phone_num (string): the phone number to send the SMS to
2236
+ message (string): the message to send
2237
+ """
2238
+ _genie_api.send_sms(phone_num, message)
2239
+
2240
+
2241
+ @usercommand
2242
+ @helparglist("message, inst")
2243
+ @log_command_and_handle_exception
2244
+ def send_alert(message: str, inst: str | None = None) -> None:
2245
+ """
2246
+ Sends an alert message for the specified instrument.
2247
+
2248
+ Args:
2249
+ message (string): the message to send
2250
+ inst (string, optional): the instrument to generate the alert for.
2251
+ Defaults to current instrument.
2252
+ """
2253
+ _genie_api.send_alert(message, inst)
2254
+
2255
+
2256
+ @usercommand
2257
+ @helparglist("address, message")
2258
+ @log_command_and_handle_exception
2259
+ def send_email(address: str, message: str) -> None:
2260
+ """
2261
+ Sends a message to an email address.
2262
+
2263
+ Args:
2264
+ address (string): the email address to use
2265
+ message (string): the message to send
2266
+ """
2267
+ _genie_api.send_email(address, message)
2268
+
2269
+
2270
+ @usercommand
2271
+ @helparglist("")
2272
+ @log_command_and_handle_exception
2273
+ def get_wiring_tables() -> list[str]:
2274
+ """
2275
+ Gets a list of possible wiring table choices.
2276
+
2277
+ Returns:
2278
+ list: the files
2279
+ """
2280
+ return _genie_api.dae.get_wiring_tables()
2281
+
2282
+
2283
+ @usercommand
2284
+ @helparglist("")
2285
+ @log_command_and_handle_exception
2286
+ def get_spectra_tables() -> list[str]:
2287
+ """
2288
+ Gets a list of possible spectra table choices.
2289
+
2290
+ Returns:
2291
+ list: the files
2292
+ """
2293
+ return _genie_api.dae.get_spectra_tables()
2294
+
2295
+
2296
+ @usercommand
2297
+ @helparglist("")
2298
+ @log_command_and_handle_exception
2299
+ def get_detector_tables() -> list[str]:
2300
+ """
2301
+ Gets a list of possible detector table choices.
2302
+
2303
+ Returns:
2304
+ list: the files
2305
+ """
2306
+ return _genie_api.dae.get_detector_tables()
2307
+
2308
+
2309
+ @usercommand
2310
+ @helparglist("")
2311
+ @log_command_and_handle_exception
2312
+ def get_period_files() -> list[str]:
2313
+ """
2314
+ Gets a list of possible period file choices.
2315
+
2316
+ Returns:
2317
+ list: the files
2318
+ """
2319
+ return _genie_api.dae.get_period_files()
2320
+
2321
+
2322
+ @log_command_and_handle_exception
2323
+ def check_alarms(*blocks: str) -> tuple[list[str], list[str], list[str]]:
2324
+ """
2325
+ Checks whether the specified blocks are in alarm.
2326
+
2327
+ Args:
2328
+ blocks (string, multiple): the block(s) to check
2329
+
2330
+ Returns:
2331
+ list, list: the blocks in minor alarm and major alarm respectively
2332
+
2333
+ Example:
2334
+ Check alarm state for block1 and block2:
2335
+
2336
+ >>> check_alarms("block1", "block2")
2337
+ """
2338
+ return _genie_api.check_alarms(blocks)
2339
+
2340
+
2341
+ @log_command_and_handle_exception
2342
+ def check_limit_violations(*blocks: str) -> list[str]:
2343
+ """
2344
+ Checks whether the specified blocks have soft limit violations.
2345
+
2346
+ Args:
2347
+ blocks (string, multiple): the block(s) to check
2348
+
2349
+ Returns:
2350
+ list: the blocks that have soft limit violations
2351
+
2352
+ Example:
2353
+ Check soft limit violations for block1 and block2:
2354
+
2355
+ >>> check_limit_violations("block1", "block2")
2356
+ """
2357
+ return _genie_api.check_limit_violations(blocks)
2358
+
2359
+
2360
+ @usercommand
2361
+ @helparglist("name")
2362
+ @log_command_and_handle_exception
2363
+ def prefix_pv_name(name: str) -> str:
2364
+ """
2365
+ Prepends the instrument PV prefix on to the supplied PV name
2366
+
2367
+ Args:
2368
+ name (string): The PV without the prefix.
2369
+
2370
+ Returns:
2371
+ string: The PV with the instrument prefix prepended
2372
+ """
2373
+ return _genie_api.prefix_pv_name(name)
2374
+
2375
+
2376
+ @usercommand
2377
+ @helparglist("")
2378
+ def get_version() -> str:
2379
+ """
2380
+ Tells you the version of genie_python that is used.
2381
+
2382
+ Returns:
2383
+ string: The current version number of genie python
2384
+ """
2385
+ return VERSION
2386
+
2387
+
2388
+ @usercommand
2389
+ @helparglist("mode")
2390
+ @log_command_and_handle_exception
2391
+ def set_dae_simulation_mode(mode: bool, skip_required_runstates: bool = False) -> None:
2392
+ """
2393
+ Sets the DAE into simulation mode.
2394
+
2395
+ Args:
2396
+ mode: True to set the DAE into simulated mode, False to set the DAE into
2397
+ non-simulated (hardware) mode
2398
+ skip_required_runstates: Ignore all checks, use with caution
2399
+ """
2400
+ # skip_required_runstates must be passed as a keyword argument for wrapper to catch it.
2401
+ _genie_api.dae.set_simulation_mode(mode, skip_required_runstates=skip_required_runstates)
2402
+
2403
+
2404
+ @usercommand
2405
+ @helparglist("")
2406
+ @log_command_and_handle_exception
2407
+ def get_dae_simulation_mode() -> bool:
2408
+ """
2409
+ Gets the DAE simulation mode.
2410
+ Returns:
2411
+ True if the DAE is in simulation mode, False otherwise.
2412
+ """
2413
+ return _genie_api.dae.get_simulation_mode()
2414
+
2415
+
2416
+ def load(name: str) -> None:
2417
+ """
2418
+ Informs the user that load may not be the function they want.
2419
+ Prints a message telling the user about g.loadscript and numpy.load.
2420
+
2421
+ Args:
2422
+ name (string): The script the user is trying to load.
2423
+ """
2424
+ print(
2425
+ 'This function does not load a script; you probably wanted g.load_script("{0}").'
2426
+ "If you wanted numpy load please call it directly with import numpy as numpy; "
2427
+ 'numpy.load("{0}")'.format(get_correct_path(name))
2428
+ )
2429
+ return
2430
+
2431
+
2432
+ @usercommand
2433
+ @log_command_and_handle_exception
2434
+ def get_wiring_table() -> str | None:
2435
+ """Gets the current wiring table path"
2436
+
2437
+ Returns:
2438
+ The file path of the current wiring table.
2439
+ """
2440
+ return _genie_api.dae.get_table_path("Wiring")
2441
+
2442
+
2443
+ @usercommand
2444
+ @log_command_and_handle_exception
2445
+ def get_spectra_table() -> str | None:
2446
+ """Gets the current spectra table path"
2447
+
2448
+ Returns:
2449
+ The file path of the current spectra table.
2450
+ """
2451
+ return _genie_api.dae.get_table_path("Spectra")
2452
+
2453
+
2454
+ @usercommand
2455
+ @log_command_and_handle_exception
2456
+ def get_detector_table() -> str | None:
2457
+ """Gets the current detector table path"
2458
+
2459
+ Returns:
2460
+ The file path of the current detector table.
2461
+ """
2462
+ return _genie_api.dae.get_table_path("Detector")