Skip to content

Commit 671b931

Browse files
committed
dae
1 parent 0c3ae27 commit 671b931

File tree

10 files changed

+371
-58
lines changed

10 files changed

+371
-58
lines changed

src/ibex_bluesky_core/devices/block.py

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -147,16 +147,23 @@ def __init__(self, prefix: str, name: str = "") -> None:
147147
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
148148
# When explicitly reading run control, the most obvious signal that people will be
149149
# interested in is whether the block is in range or not.
150-
self.in_range = epics_signal_r(bool, f"{prefix}INRANGE")
150+
self.in_range: SignalR[bool] = epics_signal_r(bool, f"{prefix}INRANGE")
151+
"""Whether run-control is currently in-range."""
151152

152-
self.low_limit = epics_signal_rw(float, f"{prefix}LOW")
153-
self.high_limit = epics_signal_rw(float, f"{prefix}HIGH")
153+
self.low_limit: SignalRW[float] = epics_signal_rw(float, f"{prefix}LOW")
154+
"""Run-control low limit."""
155+
self.high_limit: SignalRW[float] = epics_signal_rw(float, f"{prefix}HIGH")
156+
"""Run-control high limit."""
154157

155-
self.suspend_if_invalid = epics_signal_rw(bool, f"{prefix}SOI")
156-
self.enabled = epics_signal_rw(bool, f"{prefix}ENABLE")
158+
self.suspend_if_invalid: SignalRW[bool] = epics_signal_rw(bool, f"{prefix}SOI")
159+
"""Whether run-control should suspend data collection on invalid values."""
160+
self.enabled: SignalRW[bool] = epics_signal_rw(bool, f"{prefix}ENABLE")
161+
"""Run-control enabled."""
157162

158-
self.out_time = epics_signal_r(float, f"{prefix}OUT:TIME")
159-
self.in_time = epics_signal_r(float, f"{prefix}IN:TIME")
163+
self.out_time: SignalR[float] = epics_signal_r(float, f"{prefix}OUT:TIME")
164+
"""Run-control time outside limits."""
165+
self.in_time: SignalR[float] = epics_signal_r(float, f"{prefix}IN:TIME")
166+
"""Run-control time inside limits."""
160167

161168
super().__init__(name=name)
162169

@@ -176,9 +183,11 @@ def __init__(self, datatype: type[T], prefix: str, block_name: str) -> None:
176183
"""
177184
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
178185
self.readback: SignalR[T] = epics_signal_r(datatype, f"{prefix}CS:SB:{block_name}")
186+
"""Readback value. This is the hinted signal for a block."""
179187

180188
# Run control doesn't need to be read by default
181-
self.run_control = RunControl(f"{prefix}CS:SB:{block_name}:RC:")
189+
self.run_control: RunControl = RunControl(f"{prefix}CS:SB:{block_name}:RC:")
190+
"""Run-control settings for this block."""
182191

183192
super().__init__(name=block_name)
184193
self.readback.set_name(block_name)
@@ -243,6 +252,7 @@ def plan():
243252
self.setpoint: SignalRW[T] = epics_signal_rw(
244253
datatype, f"{prefix}CS:SB:{block_name}{sp_suffix}"
245254
)
255+
"""The setpoint for this block."""
246256

247257
self._write_config: BlockWriteConfig[T] = write_config or BlockWriteConfig()
248258

@@ -360,6 +370,7 @@ def __init__(
360370
self.setpoint_readback: SignalR[T] = epics_signal_r(
361371
datatype, f"{prefix}CS:SB:{block_name}:SP:RBV"
362372
)
373+
"""The setpoint-readback for this block."""
363374

364375
super().__init__(
365376
datatype=datatype, prefix=prefix, block_name=block_name, write_config=write_config
@@ -413,34 +424,37 @@ def __init__(
413424
) -> None:
414425
"""Create a new motor-record block.
415426
416-
The 'BlockMot' object supports motion-specific functionality such as:
427+
The ``BlockMot`` object supports motion-specific functionality such as:
417428
418-
- Stopping if a scan is aborted (supports the bluesky 'Stoppable' protocol)
419-
- Limit checking (before a move starts - supports the bluesky 'Checkable' protocol)
429+
- Stopping if a scan is aborted (supports the bluesky
430+
:py:obj:`~bluesky.protocols.Stoppable` protocol)
431+
- Limit checking (before a move starts - supports the bluesky
432+
:py:obj:`~bluesky.protocols.Checkable` protocol)
420433
- Automatic calculation of move timeouts based on motor velocity
421434
- Fly scanning
422435
423436
However, it generally relies on the underlying motor being "well-behaved". For example, a
424437
motor which does many retries may exceed the simple default timeout based on velocity (it
425438
is possible to explicitly specify a timeout on set() to override this).
426439
427-
Blocks pointing at motors do not take a BlockWriteConfiguration parameter, as these
440+
Blocks pointing at motors do not take a
441+
:py:obj:`~ibex_bluesky_core.devices.block.BlockWriteConfig` parameter, as these
428442
parameters duplicate functionality which already exists in the motor record. The mapping is:
429443
430444
use_completion_callback:
431445
Motors always use completion callbacks to check whether motion has completed. Whether to
432446
wait on that completion callback can be configured by the 'wait' keyword argument on
433447
set().
434448
set_success_func:
435-
Use .RDBD and .RTRY to control motor retries if the position has not been reached to
436-
within a specified tolerance. Note that motors which retry a lot may exceed the default
437-
motion timeout which is calculated based on velocity, distance and acceleration.
449+
Use ``.RDBD`` and ``.RTRY`` to control motor retries if the position has not been
450+
reached to within a specified tolerance. Note that motors which retry a lot may
451+
exceed the default motion timeout which is calculated based on velocity,
452+
distance and acceleration.
438453
set_timeout_s:
439454
A suitable timeout is calculated automatically based on velocity, distance and
440-
acceleration as defined on the motor record. This may be overridden by the 'timeout'
441-
keyword-argument on set().
455+
acceleration as defined on the motor record.
442456
settle_time_s:
443-
Use .DLY on the motor record to configure this.
457+
Use ``.DLY`` on the motor record to configure this.
444458
use_global_moving_flag:
445459
This is unnecessary for a single motor block, as a completion callback will always be
446460
used instead to detect when a single move has finished.

src/ibex_bluesky_core/devices/dae/__init__.py

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ class DaeCheckingSignal(StandardReadable, Movable[T], Generic[T]):
8383
def __init__(self, datatype: type[T], prefix: str) -> None:
8484
"""Device that wraps a signal and checks the result of a set.
8585
86+
This ensures that the DAE has accepted a configuration change, ensuring that
87+
runs are not accidentally started in an unwanted configuration if the DAE does
88+
not accept new settings.
89+
8690
Args:
8791
datatype: The datatype of the signal.
8892
prefix: The PV address of the signal.
@@ -95,11 +99,14 @@ def __init__(self, datatype: type[T], prefix: str) -> None:
9599

96100
@AsyncStatus.wrap
97101
async def set(self, value: T) -> None:
98-
"""Check a signal when it is set. Raises if not set.
102+
"""Set and check a signal.
99103
100104
Args:
101105
value: the value to set.
102106
107+
Raises:
108+
OSError: If the signal failed to be set to the specified value.
109+
103110
"""
104111
await self.signal.set(value, wait=True, timeout=None)
105112
actual_value = await self.signal.get_value()
@@ -113,20 +120,35 @@ class RunstateEnum(StrictEnum):
113120
"""The run state."""
114121

115122
PROCESSING = "PROCESSING"
123+
"""Processing state."""
116124
SETUP = "SETUP"
125+
"""Setup (idle) state."""
117126
RUNNING = "RUNNING"
127+
"""Running state."""
118128
PAUSED = "PAUSED"
129+
"""Paused state."""
119130
WAITING = "WAITING"
131+
"""Waiting state (running blocked by run control)."""
120132
VETOING = "VETOING"
133+
"""Vetoing state (running blocked by hardware signal)."""
121134
ENDING = "ENDING"
135+
"""Ending state."""
122136
SAVING = "SAVING"
137+
"""Saving state."""
123138
RESUMING = "RESUMING"
139+
"""Resuming state."""
124140
PAUSING = "PAUSING"
141+
"""Pausing state."""
125142
BEGINNING = "BEGINNING"
143+
"""Beginning state."""
126144
ABORTING = "ABORTING"
145+
"""Aborting state."""
127146
UPDATING = "UPDATING"
147+
"""Updating state."""
128148
STORING = "STORING"
149+
"""Storing state."""
129150
CHANGING = "CHANGING"
151+
"""Changing state."""
130152

131153
def __str__(self) -> str:
132154
"""Return a string representation of the enum value."""
@@ -137,7 +159,16 @@ class Dae(StandardReadable):
137159
"""Device representing the ISIS data acquisition electronics."""
138160

139161
def __init__(self, prefix: str, name: str = "DAE") -> None:
140-
"""Create a new Dae ophyd-async device."""
162+
"""Device representing the full interface to the ISIS data acquisition electronics.
163+
164+
.. warning::
165+
166+
This class exposes underlying DAE functionality behaviour to bluesky, but has no
167+
configured behaviour. A raw ``Dae`` object is therefore unsuitable for use in a scan.
168+
169+
To use the DAE in a scan, use a subclass such as
170+
:py:obj:`~ibex_bluesky_core.devices.simpledae.SimpleDae`.
171+
"""
141172
dae_prefix = f"{prefix}DAE:"
142173
self._prefix = prefix
143174
self.good_uah: SignalR[float] = epics_signal_r(float, f"{dae_prefix}GOODUAH")
@@ -244,7 +275,6 @@ async def trigger_and_get_specdata(
244275
settings. The returned array will not include the "junk" time-channel 0.
245276
246277
Args:
247-
dae: The SimpleDae instance
248278
detectors: a numpy array or slice describing detectors to get data from.
249279
Default is all detectors.
250280
Pass np.array([1]) to select detector 1.

src/ibex_bluesky_core/devices/dae/_controls.py

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,43 +11,92 @@
1111

1212

1313
class DaeControls(StandardReadable):
14-
"""Subdevice for the DAE run controls."""
14+
"""DAE run control signals."""
1515

1616
def __init__(self, dae_prefix: str, name: str = "") -> None:
17-
"""Set up write-only signals for DAE controls."""
17+
"""DAE run controls, for example to begin and end runs."""
1818
self.begin_run: SignalX = epics_signal_x(f"{dae_prefix}BEGINRUN")
19+
"""
20+
Begin a run.
21+
"""
1922
self.begin_run_ex: BeginRunEx = BeginRunEx(dae_prefix)
23+
"""
24+
Begin a run, with options.
25+
"""
2026
self.end_run: SignalX = epics_signal_x(f"{dae_prefix}ENDRUN")
27+
"""
28+
End a run.
29+
"""
2130
self.pause_run: SignalX = epics_signal_x(f"{dae_prefix}PAUSERUN")
31+
"""
32+
Pause the current run.
33+
"""
2234
self.resume_run: SignalX = epics_signal_x(f"{dae_prefix}RESUMERUN")
35+
"""
36+
Resume the current run.
37+
"""
2338
self.abort_run: SignalX = epics_signal_x(f"{dae_prefix}ABORTRUN")
39+
"""
40+
Abort the current run (does not save data files).
41+
"""
2442
self.recover_run: SignalX = epics_signal_x(f"{dae_prefix}RECOVERRUN")
43+
"""
44+
Recover a previously-aborted run.
45+
"""
2546
self.save_run: SignalX = epics_signal_x(f"{dae_prefix}SAVERUN")
47+
"""
48+
Save a data file for the current run (equivalent to update & store).
49+
"""
2650
self.update_run: SignalX = epics_signal_x(f"{dae_prefix}UPDATERUN")
51+
"""
52+
Ensure that data in the DAE has been downloaded to the ICP.
53+
"""
2754
self.store_run: SignalX = epics_signal_x(f"{dae_prefix}STORERUN")
55+
"""
56+
Write data currently in-memory in the ICP to a data file.
57+
"""
2858

2959
super().__init__(name=name)
3060

3161

3262
class BeginRunExBits(IntFlag):
33-
"""Bits for BEGINRUNEX."""
63+
"""Bit-flags for :py:obj:`BeginRunEx`.
64+
65+
These flags control behaviour such as beginning in 'paused' mode.
66+
"""
3467

3568
NONE = 0
69+
"""
70+
Begin a run in the standard way.
71+
"""
72+
3673
BEGIN_PAUSED = 1
74+
"""
75+
Begin a run in the 'paused' state.
76+
"""
77+
3778
BEGIN_DELAYED = 2
79+
"""
80+
Allow 'delayed' begin commands.
81+
"""
3882

3983

4084
class BeginRunEx(StandardReadable, Movable[BeginRunExBits]):
41-
"""Subdevice for the BEGINRUNEX signal to begin a run."""
85+
"""Subdevice for the ``BEGINRUNEX`` signal to begin a run."""
4286

4387
def __init__(self, dae_prefix: str, name: str = "") -> None:
44-
"""Set up write-only signal for BEGINRUNEX."""
88+
"""Set up write-only signal for ``BEGINRUNEX``."""
4589
self._raw_begin_run_ex: SignalW[int] = epics_signal_w(int, f"{dae_prefix}BEGINRUNEX")
4690
super().__init__(name=name)
4791

4892
@AsyncStatus.wrap
4993
async def set(self, value: BeginRunExBits) -> None:
50-
"""Start a run with the specified bits - See BeginRunExBits."""
94+
"""Start a run with the specified behaviour flags.
95+
96+
See Also:
97+
:py:obj:`BeginRunExBits` for a description of the behaviour flags.
98+
99+
"""
51100
logger.info("starting run with options %s", value)
52101
await self._raw_begin_run_ex.set(value, wait=True, timeout=None)
53102
logger.info("start run complete")

src/ibex_bluesky_core/devices/dae/_event_mode.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,24 @@ class DaeEventMode(StandardReadable):
88
"""Subdevice for event mode statistics."""
99

1010
def __init__(self, dae_prefix: str, name: str = "") -> None:
11-
"""Set up signals for DAE event mode statistics."""
11+
"""DAE event mode statistics."""
1212
self.fraction: SignalR[float] = epics_signal_r(float, f"{dae_prefix}EVENTMODEFRACTION")
13+
"""
14+
Event mode fraction.
15+
16+
1 is full event mode, 0 is full histogram mode. Values between 0 and 1 imply that
17+
some spectra are in event mode and others are in histogram mode.
18+
"""
1319
self.buf_used: SignalR[float] = epics_signal_r(float, f"{dae_prefix}EVENTMODEBUFUSED")
20+
"""
21+
Event mode buffer used fraction.
22+
"""
1423
self.file_size: SignalR[float] = epics_signal_r(float, f"{dae_prefix}EVENTMODEFILEMB")
24+
"""
25+
Event mode file size (MB).
26+
"""
1527
self.data_rate: SignalR[float] = epics_signal_r(float, f"{dae_prefix}EVENTMODEDATARATE")
28+
"""
29+
Event mode data rate (MB/s).
30+
"""
1631
super().__init__(name=name)

src/ibex_bluesky_core/devices/dae/_monitor.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@ class DaeMonitor(StandardReadable):
88
"""Subdevice for the current monitor."""
99

1010
def __init__(self, dae_prefix: str, name: str = "") -> None:
11-
"""Set up signals for the current DAE monitor."""
12-
self.spectrum: SignalR[int] = epics_signal_r(int, f"{dae_prefix}MONITORCOUNTS")
13-
self.counts: SignalR[int] = epics_signal_r(int, f"{dae_prefix}MONITORSPECTRUM")
11+
"""DAE monitor statistics and diagnostics."""
12+
self.spectrum: SignalR[int] = epics_signal_r(int, f"{dae_prefix}MONITORSPECTRUM")
13+
"""Monitor spectrum."""
14+
self.counts: SignalR[int] = epics_signal_r(int, f"{dae_prefix}MONITORCOUNTS")
15+
"""Monitor counts."""
1416
self.to: SignalR[float] = epics_signal_r(float, f"{dae_prefix}MONITORTO")
17+
"""Monitor integration upper bound (us)."""
1518
self.from_: SignalR[float] = epics_signal_r(float, f"{dae_prefix}MONITORFROM")
19+
"""Monitor integration lower bound (us)."""
1620

1721
super().__init__(name=name)

src/ibex_bluesky_core/devices/dae/_period.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,18 @@ class DaePeriod(StandardReadable):
88
"""Subdevice for the current DAE period."""
99

1010
def __init__(self, dae_prefix: str, name: str = "") -> None:
11-
"""Set up signals for the current DAE period."""
11+
"""DAE statistics and diagnostics for the current DAE period."""
1212
self.run_duration: SignalR[int] = epics_signal_r(int, f"{dae_prefix}RUNDURATION_PD")
13+
"""Run duration (seconds) in the current DAE period."""
1314
self.good_frames: SignalR[int] = epics_signal_r(int, f"{dae_prefix}GOODFRAMES_PD")
15+
"""DAE good frames in the current DAE period."""
1416
self.raw_frames: SignalR[int] = epics_signal_r(int, f"{dae_prefix}RAWFRAMES_PD")
17+
"""DAE raw frames in the current DAE period."""
1518
self.good_uah: SignalR[float] = epics_signal_r(float, f"{dae_prefix}GOODUAH_PD")
19+
"""DAE good uAh in the current DAE period."""
1620
self.type: SignalR[str] = epics_signal_r(str, f"{dae_prefix}PERIODTYPE")
21+
"""DAE period type."""
1722
self.sequence: SignalR[int] = epics_signal_r(int, f"{dae_prefix}PERIODSEQ")
23+
"""DAE period sequence."""
1824

1925
super().__init__(name=name)

0 commit comments

Comments
 (0)