ophyd-async 0.3a1__py3-none-any.whl → 0.3a3__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.
- ophyd_async/__init__.py +1 -4
- ophyd_async/_version.py +1 -1
- ophyd_async/core/__init__.py +23 -3
- ophyd_async/core/_providers.py +3 -1
- ophyd_async/core/detector.py +72 -46
- ophyd_async/core/device.py +8 -0
- ophyd_async/core/flyer.py +12 -21
- ophyd_async/core/signal.py +134 -20
- ophyd_async/core/signal_backend.py +6 -3
- ophyd_async/core/sim_signal_backend.py +32 -20
- ophyd_async/core/standard_readable.py +212 -23
- ophyd_async/core/utils.py +18 -1
- ophyd_async/epics/_backend/_aioca.py +17 -15
- ophyd_async/epics/_backend/_p4p.py +34 -25
- ophyd_async/epics/_backend/common.py +16 -11
- ophyd_async/epics/areadetector/__init__.py +8 -0
- ophyd_async/epics/areadetector/aravis.py +67 -0
- ophyd_async/epics/areadetector/controllers/__init__.py +2 -1
- ophyd_async/epics/areadetector/controllers/aravis_controller.py +73 -0
- ophyd_async/epics/areadetector/controllers/kinetix_controller.py +49 -0
- ophyd_async/epics/areadetector/controllers/pilatus_controller.py +36 -24
- ophyd_async/epics/areadetector/controllers/vimba_controller.py +66 -0
- ophyd_async/epics/areadetector/drivers/__init__.py +6 -0
- ophyd_async/epics/areadetector/drivers/aravis_driver.py +154 -0
- ophyd_async/epics/areadetector/drivers/kinetix_driver.py +24 -0
- ophyd_async/epics/areadetector/drivers/pilatus_driver.py +4 -4
- ophyd_async/epics/areadetector/drivers/vimba_driver.py +58 -0
- ophyd_async/epics/areadetector/kinetix.py +46 -0
- ophyd_async/epics/areadetector/pilatus.py +45 -0
- ophyd_async/epics/areadetector/single_trigger_det.py +14 -6
- ophyd_async/epics/areadetector/vimba.py +43 -0
- ophyd_async/epics/areadetector/writers/_hdffile.py +4 -4
- ophyd_async/epics/areadetector/writers/hdf_writer.py +12 -4
- ophyd_async/epics/areadetector/writers/nd_file_hdf.py +1 -0
- ophyd_async/epics/demo/__init__.py +45 -18
- ophyd_async/epics/motion/motor.py +24 -19
- ophyd_async/epics/pvi/__init__.py +3 -0
- ophyd_async/epics/pvi/pvi.py +318 -0
- ophyd_async/epics/signal/signal.py +26 -9
- ophyd_async/log.py +130 -0
- ophyd_async/panda/__init__.py +17 -6
- ophyd_async/panda/_common_blocks.py +49 -0
- ophyd_async/panda/_hdf_panda.py +48 -0
- ophyd_async/panda/{panda_controller.py → _panda_controller.py} +3 -7
- ophyd_async/panda/_trigger.py +39 -0
- ophyd_async/panda/writers/__init__.py +3 -0
- ophyd_async/panda/writers/_hdf_writer.py +220 -0
- ophyd_async/panda/writers/_panda_hdf_file.py +58 -0
- ophyd_async/planstubs/__init__.py +5 -0
- ophyd_async/planstubs/prepare_trigger_and_dets.py +57 -0
- ophyd_async/protocols.py +96 -0
- ophyd_async/sim/__init__.py +11 -0
- ophyd_async/sim/demo/__init__.py +3 -0
- ophyd_async/sim/demo/sim_motor.py +118 -0
- ophyd_async/sim/pattern_generator.py +318 -0
- ophyd_async/sim/sim_pattern_detector_control.py +55 -0
- ophyd_async/sim/sim_pattern_detector_writer.py +34 -0
- ophyd_async/sim/sim_pattern_generator.py +37 -0
- {ophyd_async-0.3a1.dist-info → ophyd_async-0.3a3.dist-info}/METADATA +30 -69
- ophyd_async-0.3a3.dist-info/RECORD +83 -0
- ophyd_async/epics/pvi.py +0 -70
- ophyd_async/panda/panda.py +0 -241
- ophyd_async-0.3a1.dist-info/RECORD +0 -56
- /ophyd_async/panda/{table.py → _table.py} +0 -0
- /ophyd_async/panda/{utils.py → _utils.py} +0 -0
- {ophyd_async-0.3a1.dist-info → ophyd_async-0.3a3.dist-info}/LICENSE +0 -0
- {ophyd_async-0.3a1.dist-info → ophyd_async-0.3a3.dist-info}/WHEEL +0 -0
- {ophyd_async-0.3a1.dist-info → ophyd_async-0.3a3.dist-info}/entry_points.txt +0 -0
- {ophyd_async-0.3a1.dist-info → ophyd_async-0.3a3.dist-info}/top_level.txt +0 -0
ophyd_async/__init__.py
CHANGED
ophyd_async/_version.py
CHANGED
ophyd_async/core/__init__.py
CHANGED
|
@@ -6,7 +6,13 @@ from ._providers import (
|
|
|
6
6
|
StaticDirectoryProvider,
|
|
7
7
|
)
|
|
8
8
|
from .async_status import AsyncStatus
|
|
9
|
-
from .detector import
|
|
9
|
+
from .detector import (
|
|
10
|
+
DetectorControl,
|
|
11
|
+
DetectorTrigger,
|
|
12
|
+
DetectorWriter,
|
|
13
|
+
StandardDetector,
|
|
14
|
+
TriggerInfo,
|
|
15
|
+
)
|
|
10
16
|
from .device import Device, DeviceCollector, DeviceVector
|
|
11
17
|
from .device_save_loader import (
|
|
12
18
|
get_signal_values,
|
|
@@ -17,23 +23,29 @@ from .device_save_loader import (
|
|
|
17
23
|
set_signal_values,
|
|
18
24
|
walk_rw_signals,
|
|
19
25
|
)
|
|
20
|
-
from .flyer import HardwareTriggeredFlyable,
|
|
26
|
+
from .flyer import HardwareTriggeredFlyable, TriggerLogic
|
|
21
27
|
from .signal import (
|
|
22
28
|
Signal,
|
|
23
29
|
SignalR,
|
|
24
30
|
SignalRW,
|
|
25
31
|
SignalW,
|
|
26
32
|
SignalX,
|
|
33
|
+
assert_configuration,
|
|
34
|
+
assert_emitted,
|
|
35
|
+
assert_reading,
|
|
36
|
+
assert_value,
|
|
27
37
|
observe_value,
|
|
28
38
|
set_and_wait_for_value,
|
|
29
39
|
set_sim_callback,
|
|
30
40
|
set_sim_put_proceeds,
|
|
31
41
|
set_sim_value,
|
|
42
|
+
soft_signal_r_and_backend,
|
|
43
|
+
soft_signal_rw,
|
|
32
44
|
wait_for_value,
|
|
33
45
|
)
|
|
34
46
|
from .signal_backend import SignalBackend
|
|
35
47
|
from .sim_signal_backend import SimSignalBackend
|
|
36
|
-
from .standard_readable import StandardReadable
|
|
48
|
+
from .standard_readable import ConfigSignal, HintedSignal, StandardReadable
|
|
37
49
|
from .utils import (
|
|
38
50
|
DEFAULT_TIMEOUT,
|
|
39
51
|
Callback,
|
|
@@ -61,6 +73,8 @@ __all__ = [
|
|
|
61
73
|
"SignalW",
|
|
62
74
|
"SignalRW",
|
|
63
75
|
"SignalX",
|
|
76
|
+
"soft_signal_r_and_backend",
|
|
77
|
+
"soft_signal_rw",
|
|
64
78
|
"observe_value",
|
|
65
79
|
"set_and_wait_for_value",
|
|
66
80
|
"set_sim_callback",
|
|
@@ -74,6 +88,8 @@ __all__ = [
|
|
|
74
88
|
"ShapeProvider",
|
|
75
89
|
"StaticDirectoryProvider",
|
|
76
90
|
"StandardReadable",
|
|
91
|
+
"ConfigSignal",
|
|
92
|
+
"HintedSignal",
|
|
77
93
|
"TriggerInfo",
|
|
78
94
|
"TriggerLogic",
|
|
79
95
|
"HardwareTriggeredFlyable",
|
|
@@ -93,4 +109,8 @@ __all__ = [
|
|
|
93
109
|
"walk_rw_signals",
|
|
94
110
|
"load_device",
|
|
95
111
|
"save_device",
|
|
112
|
+
"assert_reading",
|
|
113
|
+
"assert_value",
|
|
114
|
+
"assert_configuration",
|
|
115
|
+
"assert_emitted",
|
|
96
116
|
]
|
ophyd_async/core/_providers.py
CHANGED
|
@@ -39,8 +39,10 @@ class StaticDirectoryProvider(DirectoryProvider):
|
|
|
39
39
|
directory_path: Union[str, Path],
|
|
40
40
|
filename_prefix: str = "",
|
|
41
41
|
filename_suffix: str = "",
|
|
42
|
-
resource_dir: Path =
|
|
42
|
+
resource_dir: Optional[Path] = None,
|
|
43
43
|
) -> None:
|
|
44
|
+
if resource_dir is None:
|
|
45
|
+
resource_dir = Path(".")
|
|
44
46
|
if isinstance(directory_path, str):
|
|
45
47
|
directory_path = Path(directory_path)
|
|
46
48
|
self._directory_info = DirectoryInfo(
|
ophyd_async/core/detector.py
CHANGED
|
@@ -10,6 +10,7 @@ from typing import (
|
|
|
10
10
|
AsyncIterator,
|
|
11
11
|
Callable,
|
|
12
12
|
Dict,
|
|
13
|
+
Generic,
|
|
13
14
|
List,
|
|
14
15
|
Optional,
|
|
15
16
|
Sequence,
|
|
@@ -18,11 +19,9 @@ from typing import (
|
|
|
18
19
|
|
|
19
20
|
from bluesky.protocols import (
|
|
20
21
|
Collectable,
|
|
21
|
-
|
|
22
|
-
Descriptor,
|
|
22
|
+
DataKey,
|
|
23
23
|
Flyable,
|
|
24
24
|
Preparable,
|
|
25
|
-
Readable,
|
|
26
25
|
Reading,
|
|
27
26
|
Stageable,
|
|
28
27
|
StreamAsset,
|
|
@@ -30,15 +29,18 @@ from bluesky.protocols import (
|
|
|
30
29
|
WritesStreamAssets,
|
|
31
30
|
)
|
|
32
31
|
|
|
32
|
+
from ophyd_async.protocols import AsyncConfigurable, AsyncReadable
|
|
33
|
+
|
|
33
34
|
from .async_status import AsyncStatus
|
|
34
35
|
from .device import Device
|
|
35
|
-
from .signal import SignalR
|
|
36
36
|
from .utils import DEFAULT_TIMEOUT, merge_gathered_dicts
|
|
37
37
|
|
|
38
38
|
T = TypeVar("T")
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
class DetectorTrigger(str, Enum):
|
|
42
|
+
"""Type of mechanism for triggering a detector to take frames"""
|
|
43
|
+
|
|
42
44
|
#: Detector generates internal trigger for given rate
|
|
43
45
|
internal = "internal"
|
|
44
46
|
#: Expect a series of arbitrary length trigger signals
|
|
@@ -51,6 +53,8 @@ class DetectorTrigger(str, Enum):
|
|
|
51
53
|
|
|
52
54
|
@dataclass(frozen=True)
|
|
53
55
|
class TriggerInfo:
|
|
56
|
+
"""Minimal set of information required to setup triggering on a detector"""
|
|
57
|
+
|
|
54
58
|
#: Number of triggers that will be sent
|
|
55
59
|
num: int
|
|
56
60
|
#: Sort of triggers that will be sent
|
|
@@ -62,6 +66,11 @@ class TriggerInfo:
|
|
|
62
66
|
|
|
63
67
|
|
|
64
68
|
class DetectorControl(ABC):
|
|
69
|
+
"""
|
|
70
|
+
Classes implementing this interface should hold the logic for
|
|
71
|
+
arming and disarming a detector
|
|
72
|
+
"""
|
|
73
|
+
|
|
65
74
|
@abstractmethod
|
|
66
75
|
def get_deadtime(self, exposure: float) -> float:
|
|
67
76
|
"""For a given exposure, how long should the time between exposures be"""
|
|
@@ -73,19 +82,34 @@ class DetectorControl(ABC):
|
|
|
73
82
|
trigger: DetectorTrigger = DetectorTrigger.internal,
|
|
74
83
|
exposure: Optional[float] = None,
|
|
75
84
|
) -> AsyncStatus:
|
|
76
|
-
"""
|
|
85
|
+
"""
|
|
86
|
+
Arm detector, do all necessary steps to prepare detector for triggers.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
num: Expected number of frames
|
|
90
|
+
trigger: Type of trigger for which to prepare the detector. Defaults to
|
|
91
|
+
DetectorTrigger.internal.
|
|
92
|
+
exposure: Exposure time with which to set up the detector. Defaults to None
|
|
93
|
+
if not applicable or the detector is expected to use its previously-set
|
|
94
|
+
exposure time.
|
|
77
95
|
|
|
78
|
-
|
|
96
|
+
Returns:
|
|
97
|
+
AsyncStatus: Status representing the arm operation. This function returning
|
|
98
|
+
represents the start of the arm. The returned status completing means
|
|
99
|
+
the detector is now armed.
|
|
79
100
|
"""
|
|
80
101
|
|
|
81
102
|
@abstractmethod
|
|
82
103
|
async def disarm(self):
|
|
83
|
-
"""Disarm the detector"""
|
|
104
|
+
"""Disarm the detector, return detector to an idle state"""
|
|
84
105
|
|
|
85
106
|
|
|
86
107
|
class DetectorWriter(ABC):
|
|
108
|
+
"""Logic for making a detector write data to somewhere persistent
|
|
109
|
+
(e.g. an HDF5 file)"""
|
|
110
|
+
|
|
87
111
|
@abstractmethod
|
|
88
|
-
async def open(self, multiplier: int = 1) -> Dict[str,
|
|
112
|
+
async def open(self, multiplier: int = 1) -> Dict[str, DataKey]:
|
|
89
113
|
"""Open writer and wait for it to be ready for data.
|
|
90
114
|
|
|
91
115
|
Args:
|
|
@@ -100,7 +124,7 @@ class DetectorWriter(ABC):
|
|
|
100
124
|
def observe_indices_written(
|
|
101
125
|
self, timeout=DEFAULT_TIMEOUT
|
|
102
126
|
) -> AsyncGenerator[int, None]:
|
|
103
|
-
"""Yield
|
|
127
|
+
"""Yield the index of each frame (or equivalent data point) as it is written"""
|
|
104
128
|
|
|
105
129
|
@abstractmethod
|
|
106
130
|
async def get_indices_written(self) -> int:
|
|
@@ -112,50 +136,51 @@ class DetectorWriter(ABC):
|
|
|
112
136
|
|
|
113
137
|
@abstractmethod
|
|
114
138
|
async def close(self) -> None:
|
|
115
|
-
"""Close writer
|
|
139
|
+
"""Close writer, blocks until I/O is complete"""
|
|
116
140
|
|
|
117
141
|
|
|
118
142
|
class StandardDetector(
|
|
119
143
|
Device,
|
|
120
144
|
Stageable,
|
|
121
|
-
|
|
122
|
-
|
|
145
|
+
AsyncConfigurable,
|
|
146
|
+
AsyncReadable,
|
|
123
147
|
Triggerable,
|
|
124
148
|
Preparable,
|
|
125
149
|
Flyable,
|
|
126
150
|
Collectable,
|
|
127
151
|
WritesStreamAssets,
|
|
152
|
+
Generic[T],
|
|
128
153
|
):
|
|
129
|
-
"""
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
DetectorData, to dictate how the detector will be controlled (i.e. arming and
|
|
133
|
-
disarming) as well as how the detector data will be written (i.e. opening and
|
|
134
|
-
closing the writer, and handling data writing indices).
|
|
135
|
-
|
|
154
|
+
"""
|
|
155
|
+
Useful detector base class for step and fly scanning detectors.
|
|
156
|
+
Aggregates controller and writer logic together.
|
|
136
157
|
"""
|
|
137
158
|
|
|
138
159
|
def __init__(
|
|
139
160
|
self,
|
|
140
161
|
controller: DetectorControl,
|
|
141
162
|
writer: DetectorWriter,
|
|
142
|
-
config_sigs: Sequence[
|
|
163
|
+
config_sigs: Sequence[AsyncReadable] = (),
|
|
143
164
|
name: str = "",
|
|
144
165
|
writer_timeout: float = DEFAULT_TIMEOUT,
|
|
145
166
|
) -> None:
|
|
146
167
|
"""
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
168
|
+
Constructor
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
controller: Logic for arming and disarming the detector
|
|
172
|
+
writer: Logic for making the detector write persistent data
|
|
173
|
+
config_sigs: Signals to read when describe and read
|
|
174
|
+
configuration are called. Defaults to ().
|
|
175
|
+
name: Device name. Defaults to "".
|
|
176
|
+
writer_timeout: Timeout for frame writing to start, if the
|
|
177
|
+
timeout is reached, ophyd-async assumes the detector
|
|
178
|
+
has a problem and raises an error.
|
|
179
|
+
Defaults to DEFAULT_TIMEOUT.
|
|
155
180
|
"""
|
|
156
181
|
self._controller = controller
|
|
157
182
|
self._writer = writer
|
|
158
|
-
self._describe: Dict[str,
|
|
183
|
+
self._describe: Dict[str, DataKey] = {}
|
|
159
184
|
self._config_sigs = list(config_sigs)
|
|
160
185
|
self._frame_writing_timeout = writer_timeout
|
|
161
186
|
# For prepare
|
|
@@ -180,15 +205,15 @@ class StandardDetector(
|
|
|
180
205
|
|
|
181
206
|
@AsyncStatus.wrap
|
|
182
207
|
async def stage(self) -> None:
|
|
183
|
-
|
|
184
|
-
await self.
|
|
208
|
+
# Disarm the detector, stop filewriting, and open file for writing.
|
|
209
|
+
await self._check_config_sigs()
|
|
185
210
|
await asyncio.gather(self.writer.close(), self.controller.disarm())
|
|
186
211
|
self._describe = await self.writer.open()
|
|
187
212
|
|
|
188
|
-
async def
|
|
213
|
+
async def _check_config_sigs(self):
|
|
189
214
|
"""Checks configuration signals are named and connected."""
|
|
190
215
|
for signal in self._config_sigs:
|
|
191
|
-
if signal.
|
|
216
|
+
if signal.name == "":
|
|
192
217
|
raise Exception(
|
|
193
218
|
"config signal must be named before it is passed to the detector"
|
|
194
219
|
)
|
|
@@ -202,26 +227,25 @@ class StandardDetector(
|
|
|
202
227
|
|
|
203
228
|
@AsyncStatus.wrap
|
|
204
229
|
async def unstage(self) -> None:
|
|
205
|
-
|
|
230
|
+
# Stop data writing.
|
|
206
231
|
await self.writer.close()
|
|
207
232
|
|
|
208
233
|
async def read_configuration(self) -> Dict[str, Reading]:
|
|
209
234
|
return await merge_gathered_dicts(sig.read() for sig in self._config_sigs)
|
|
210
235
|
|
|
211
|
-
async def describe_configuration(self) -> Dict[str,
|
|
236
|
+
async def describe_configuration(self) -> Dict[str, DataKey]:
|
|
212
237
|
return await merge_gathered_dicts(sig.describe() for sig in self._config_sigs)
|
|
213
238
|
|
|
214
239
|
async def read(self) -> Dict[str, Reading]:
|
|
215
|
-
"""Read the detector"""
|
|
216
240
|
# All data is in StreamResources, not Events, so nothing to output here
|
|
217
241
|
return {}
|
|
218
242
|
|
|
219
|
-
def describe(self) -> Dict[str,
|
|
243
|
+
async def describe(self) -> Dict[str, DataKey]:
|
|
220
244
|
return self._describe
|
|
221
245
|
|
|
222
246
|
@AsyncStatus.wrap
|
|
223
247
|
async def trigger(self) -> None:
|
|
224
|
-
|
|
248
|
+
# Arm the detector and wait for it to finish.
|
|
225
249
|
indices_written = await self.writer.get_indices_written()
|
|
226
250
|
written_status = await self.controller.arm(
|
|
227
251
|
num=1,
|
|
@@ -240,11 +264,12 @@ class StandardDetector(
|
|
|
240
264
|
self,
|
|
241
265
|
value: T,
|
|
242
266
|
) -> AsyncStatus:
|
|
243
|
-
|
|
267
|
+
# Just arm detector for the time being
|
|
244
268
|
return AsyncStatus(self._prepare(value))
|
|
245
269
|
|
|
246
270
|
async def _prepare(self, value: T) -> None:
|
|
247
|
-
"""
|
|
271
|
+
"""
|
|
272
|
+
Arm detector.
|
|
248
273
|
|
|
249
274
|
Prepare the detector with trigger information. This is determined at and passed
|
|
250
275
|
in from the plan level.
|
|
@@ -253,6 +278,9 @@ class StandardDetector(
|
|
|
253
278
|
trigger information determined in trigger.
|
|
254
279
|
|
|
255
280
|
To do: Unify prepare to be use for both fly and step scans.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
value: TriggerInfo describing how to trigger the detector
|
|
256
284
|
"""
|
|
257
285
|
assert type(value) is TriggerInfo
|
|
258
286
|
self._trigger_info = value
|
|
@@ -301,17 +329,15 @@ class StandardDetector(
|
|
|
301
329
|
assert self._fly_status, "Kickoff not run"
|
|
302
330
|
return await self._fly_status
|
|
303
331
|
|
|
304
|
-
async def describe_collect(self) -> Dict[str,
|
|
332
|
+
async def describe_collect(self) -> Dict[str, DataKey]:
|
|
305
333
|
return self._describe
|
|
306
334
|
|
|
307
335
|
async def collect_asset_docs(
|
|
308
336
|
self, index: Optional[int] = None
|
|
309
337
|
) -> AsyncIterator[StreamAsset]:
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
retrieved for stepscans.
|
|
314
|
-
"""
|
|
338
|
+
# Collect stream datum documents for all indices written.
|
|
339
|
+
# The index is optional, and provided for fly scans, however this needs to be
|
|
340
|
+
# retrieved for step scans.
|
|
315
341
|
if not index:
|
|
316
342
|
index = await self.writer.get_indices_written()
|
|
317
343
|
async for doc in self.writer.collect_stream_docs(index):
|
ophyd_async/core/device.py
CHANGED
|
@@ -82,6 +82,14 @@ VT = TypeVar("VT", bound=Device)
|
|
|
82
82
|
|
|
83
83
|
|
|
84
84
|
class DeviceVector(Dict[int, VT], Device):
|
|
85
|
+
"""
|
|
86
|
+
Defines device components with indices.
|
|
87
|
+
|
|
88
|
+
In the below example, foos becomes a dictionary on the parent device
|
|
89
|
+
at runtime, so parent.foos[2] returns a FooDevice. For example usage see
|
|
90
|
+
:class:`~ophyd_async.epics.demo.DynamicSensorGroup`
|
|
91
|
+
"""
|
|
92
|
+
|
|
85
93
|
def children(self) -> Generator[Tuple[str, Device], None, None]:
|
|
86
94
|
for attr_name, attr in self.items():
|
|
87
95
|
if isinstance(attr, Device):
|
ophyd_async/core/flyer.py
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import Dict, Generic,
|
|
2
|
+
from typing import Dict, Generic, Sequence, TypeVar
|
|
3
3
|
|
|
4
|
-
from bluesky.protocols import
|
|
4
|
+
from bluesky.protocols import DataKey, Flyable, Preparable, Reading, Stageable
|
|
5
5
|
|
|
6
6
|
from .async_status import AsyncStatus
|
|
7
|
-
from .detector import TriggerInfo
|
|
8
7
|
from .device import Device
|
|
9
8
|
from .signal import SignalR
|
|
10
9
|
from .utils import merge_gathered_dicts
|
|
@@ -13,18 +12,18 @@ T = TypeVar("T")
|
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
class TriggerLogic(ABC, Generic[T]):
|
|
16
|
-
@abstractmethod
|
|
17
|
-
def trigger_info(self, value: T) -> TriggerInfo:
|
|
18
|
-
"""Return info about triggers that will be produced for a given value"""
|
|
19
|
-
|
|
20
15
|
@abstractmethod
|
|
21
16
|
async def prepare(self, value: T):
|
|
22
17
|
"""Move to the start of the flyscan"""
|
|
23
18
|
|
|
24
19
|
@abstractmethod
|
|
25
|
-
async def
|
|
20
|
+
async def kickoff(self):
|
|
26
21
|
"""Start the flyscan"""
|
|
27
22
|
|
|
23
|
+
@abstractmethod
|
|
24
|
+
async def complete(self):
|
|
25
|
+
"""Block until the flyscan is done"""
|
|
26
|
+
|
|
28
27
|
@abstractmethod
|
|
29
28
|
async def stop(self):
|
|
30
29
|
"""Stop flying and wait everything to be stopped"""
|
|
@@ -45,19 +44,12 @@ class HardwareTriggeredFlyable(
|
|
|
45
44
|
):
|
|
46
45
|
self._trigger_logic = trigger_logic
|
|
47
46
|
self._configuration_signals = tuple(configuration_signals)
|
|
48
|
-
self._describe: Dict[str, Descriptor] = {}
|
|
49
|
-
self._fly_status: Optional[AsyncStatus] = None
|
|
50
|
-
self._trigger_info: Optional[TriggerInfo] = None
|
|
51
47
|
super().__init__(name=name)
|
|
52
48
|
|
|
53
49
|
@property
|
|
54
50
|
def trigger_logic(self) -> TriggerLogic[T]:
|
|
55
51
|
return self._trigger_logic
|
|
56
52
|
|
|
57
|
-
@property
|
|
58
|
-
def trigger_info(self) -> Optional[TriggerInfo]:
|
|
59
|
-
return self._trigger_info
|
|
60
|
-
|
|
61
53
|
@AsyncStatus.wrap
|
|
62
54
|
async def stage(self) -> None:
|
|
63
55
|
await self.unstage()
|
|
@@ -71,19 +63,18 @@ class HardwareTriggeredFlyable(
|
|
|
71
63
|
return AsyncStatus(self._prepare(value))
|
|
72
64
|
|
|
73
65
|
async def _prepare(self, value: T) -> None:
|
|
74
|
-
self._trigger_info = self._trigger_logic.trigger_info(value)
|
|
75
66
|
# Move to start and setup the flyscan
|
|
76
67
|
await self._trigger_logic.prepare(value)
|
|
77
68
|
|
|
78
69
|
@AsyncStatus.wrap
|
|
79
70
|
async def kickoff(self) -> None:
|
|
80
|
-
|
|
71
|
+
await self._trigger_logic.kickoff()
|
|
81
72
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
73
|
+
@AsyncStatus.wrap
|
|
74
|
+
async def complete(self) -> None:
|
|
75
|
+
await self._trigger_logic.complete()
|
|
85
76
|
|
|
86
|
-
async def describe_configuration(self) -> Dict[str,
|
|
77
|
+
async def describe_configuration(self) -> Dict[str, DataKey]:
|
|
87
78
|
return await merge_gathered_dicts(
|
|
88
79
|
[sig.describe() for sig in self._configuration_signals]
|
|
89
80
|
)
|
ophyd_async/core/signal.py
CHANGED
|
@@ -2,19 +2,30 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import functools
|
|
5
|
-
from typing import
|
|
5
|
+
from typing import (
|
|
6
|
+
Any,
|
|
7
|
+
AsyncGenerator,
|
|
8
|
+
Callable,
|
|
9
|
+
Dict,
|
|
10
|
+
Generic,
|
|
11
|
+
Mapping,
|
|
12
|
+
Optional,
|
|
13
|
+
Tuple,
|
|
14
|
+
Type,
|
|
15
|
+
Union,
|
|
16
|
+
)
|
|
6
17
|
|
|
7
18
|
from bluesky.protocols import (
|
|
8
|
-
|
|
19
|
+
DataKey,
|
|
9
20
|
Locatable,
|
|
10
21
|
Location,
|
|
11
22
|
Movable,
|
|
12
|
-
Readable,
|
|
13
23
|
Reading,
|
|
14
|
-
Stageable,
|
|
15
24
|
Subscribable,
|
|
16
25
|
)
|
|
17
26
|
|
|
27
|
+
from ophyd_async.protocols import AsyncConfigurable, AsyncReadable, AsyncStageable
|
|
28
|
+
|
|
18
29
|
from .async_status import AsyncStatus
|
|
19
30
|
from .device import Device
|
|
20
31
|
from .signal_backend import SignalBackend
|
|
@@ -45,24 +56,18 @@ class Signal(Device, Generic[T]):
|
|
|
45
56
|
"""A Device with the concept of a value, with R, RW, W and X flavours"""
|
|
46
57
|
|
|
47
58
|
def __init__(
|
|
48
|
-
self,
|
|
59
|
+
self,
|
|
60
|
+
backend: SignalBackend[T],
|
|
61
|
+
timeout: Optional[float] = DEFAULT_TIMEOUT,
|
|
62
|
+
name: str = "",
|
|
49
63
|
) -> None:
|
|
50
|
-
|
|
64
|
+
super().__init__(name)
|
|
51
65
|
self._timeout = timeout
|
|
52
66
|
self._init_backend = self._backend = backend
|
|
53
67
|
|
|
54
|
-
@property
|
|
55
|
-
def name(self) -> str:
|
|
56
|
-
return self._name
|
|
57
|
-
|
|
58
|
-
def set_name(self, name: str = ""):
|
|
59
|
-
self._name = name
|
|
60
|
-
|
|
61
68
|
async def connect(self, sim=False, timeout=DEFAULT_TIMEOUT):
|
|
62
69
|
if sim:
|
|
63
|
-
self._backend = SimSignalBackend(
|
|
64
|
-
datatype=self._init_backend.datatype, source=self._init_backend.source
|
|
65
|
-
)
|
|
70
|
+
self._backend = SimSignalBackend(datatype=self._init_backend.datatype)
|
|
66
71
|
_sim_backends[self] = self._backend
|
|
67
72
|
else:
|
|
68
73
|
self._backend = self._init_backend
|
|
@@ -72,7 +77,7 @@ class Signal(Device, Generic[T]):
|
|
|
72
77
|
@property
|
|
73
78
|
def source(self) -> str:
|
|
74
79
|
"""Like ca://PV_PREFIX:SIGNAL, or "" if not set"""
|
|
75
|
-
return self._backend.source
|
|
80
|
+
return self._backend.source(self.name)
|
|
76
81
|
|
|
77
82
|
__lt__ = __le__ = __eq__ = __ge__ = __gt__ = __ne__ = _fail
|
|
78
83
|
|
|
@@ -133,7 +138,7 @@ class _SignalCache(Generic[T]):
|
|
|
133
138
|
return self._staged or bool(self._listeners)
|
|
134
139
|
|
|
135
140
|
|
|
136
|
-
class SignalR(Signal[T],
|
|
141
|
+
class SignalR(Signal[T], AsyncReadable, AsyncStageable, Subscribable):
|
|
137
142
|
"""Signal that can be read from and monitored"""
|
|
138
143
|
|
|
139
144
|
_cache: Optional[_SignalCache] = None
|
|
@@ -166,9 +171,9 @@ class SignalR(Signal[T], Readable, Stageable, Subscribable):
|
|
|
166
171
|
return {self.name: await self._backend_or_cache(cached).get_reading()}
|
|
167
172
|
|
|
168
173
|
@_add_timeout
|
|
169
|
-
async def describe(self) -> Dict[str,
|
|
174
|
+
async def describe(self) -> Dict[str, DataKey]:
|
|
170
175
|
"""Return a single item dict with the descriptor in it"""
|
|
171
|
-
return {self.name: await self._backend.
|
|
176
|
+
return {self.name: await self._backend.get_datakey(self.source)}
|
|
172
177
|
|
|
173
178
|
@_add_timeout
|
|
174
179
|
async def get_value(self, cached: Optional[bool] = None) -> T:
|
|
@@ -253,6 +258,115 @@ def set_sim_callback(signal: Signal[T], callback: ReadingValueCallback[T]) -> No
|
|
|
253
258
|
return _sim_backends[signal].set_callback(callback)
|
|
254
259
|
|
|
255
260
|
|
|
261
|
+
def soft_signal_rw(
|
|
262
|
+
datatype: Optional[Type[T]] = None,
|
|
263
|
+
initial_value: Optional[T] = None,
|
|
264
|
+
name: str = "",
|
|
265
|
+
) -> SignalRW[T]:
|
|
266
|
+
"""Creates a read-writable Signal with a SimSignalBackend"""
|
|
267
|
+
signal = SignalRW(SimSignalBackend(datatype, initial_value), name=name)
|
|
268
|
+
return signal
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def soft_signal_r_and_backend(
|
|
272
|
+
datatype: Optional[Type[T]] = None,
|
|
273
|
+
initial_value: Optional[T] = None,
|
|
274
|
+
name: str = "",
|
|
275
|
+
) -> Tuple[SignalR[T], SimSignalBackend]:
|
|
276
|
+
"""Returns a tuple of a read-only Signal and its SimSignalBackend through
|
|
277
|
+
which the signal can be internally modified within the device. Use
|
|
278
|
+
soft_signal_rw if you want a device that is externally modifiable
|
|
279
|
+
"""
|
|
280
|
+
backend = SimSignalBackend(datatype, initial_value)
|
|
281
|
+
signal = SignalR(backend, name=name)
|
|
282
|
+
return (signal, backend)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
async def assert_value(signal: SignalR[T], value: Any) -> None:
|
|
286
|
+
"""Assert a signal's value and compare it an expected signal.
|
|
287
|
+
|
|
288
|
+
Parameters
|
|
289
|
+
----------
|
|
290
|
+
signal:
|
|
291
|
+
signal with get_value.
|
|
292
|
+
value:
|
|
293
|
+
The expected value from the signal.
|
|
294
|
+
|
|
295
|
+
Notes
|
|
296
|
+
-----
|
|
297
|
+
Example usage::
|
|
298
|
+
await assert_value(signal, value)
|
|
299
|
+
|
|
300
|
+
"""
|
|
301
|
+
assert await signal.get_value() == value
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
async def assert_reading(
|
|
305
|
+
readable: AsyncReadable, reading: Mapping[str, Reading]
|
|
306
|
+
) -> None:
|
|
307
|
+
"""Assert readings from readable.
|
|
308
|
+
|
|
309
|
+
Parameters
|
|
310
|
+
----------
|
|
311
|
+
readable:
|
|
312
|
+
Callable with readable.read function that generate readings.
|
|
313
|
+
|
|
314
|
+
reading:
|
|
315
|
+
The expected readings from the readable.
|
|
316
|
+
|
|
317
|
+
Notes
|
|
318
|
+
-----
|
|
319
|
+
Example usage::
|
|
320
|
+
await assert_reading(readable, reading)
|
|
321
|
+
|
|
322
|
+
"""
|
|
323
|
+
assert await readable.read() == reading
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
async def assert_configuration(
|
|
327
|
+
configurable: AsyncConfigurable,
|
|
328
|
+
configuration: Mapping[str, Reading],
|
|
329
|
+
) -> None:
|
|
330
|
+
"""Assert readings from Configurable.
|
|
331
|
+
|
|
332
|
+
Parameters
|
|
333
|
+
----------
|
|
334
|
+
configurable:
|
|
335
|
+
Configurable with Configurable.read function that generate readings.
|
|
336
|
+
|
|
337
|
+
configuration:
|
|
338
|
+
The expected readings from configurable.
|
|
339
|
+
|
|
340
|
+
Notes
|
|
341
|
+
-----
|
|
342
|
+
Example usage::
|
|
343
|
+
await assert_configuration(configurable configuration)
|
|
344
|
+
|
|
345
|
+
"""
|
|
346
|
+
assert await configurable.read_configuration() == configuration
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def assert_emitted(docs: Mapping[str, list[dict]], **numbers: int):
|
|
350
|
+
"""Assert emitted document generated by running a Bluesky plan
|
|
351
|
+
|
|
352
|
+
Parameters
|
|
353
|
+
----------
|
|
354
|
+
Doc:
|
|
355
|
+
A dictionary
|
|
356
|
+
|
|
357
|
+
numbers:
|
|
358
|
+
expected emission in kwarg from
|
|
359
|
+
|
|
360
|
+
Notes
|
|
361
|
+
-----
|
|
362
|
+
Example usage::
|
|
363
|
+
assert_emitted(docs, start=1, descriptor=1,
|
|
364
|
+
resource=1, datum=1, event=1, stop=1)
|
|
365
|
+
"""
|
|
366
|
+
assert list(docs) == list(numbers)
|
|
367
|
+
assert {name: len(d) for name, d in docs.items()} == numbers
|
|
368
|
+
|
|
369
|
+
|
|
256
370
|
async def observe_value(signal: SignalR[T], timeout=None) -> AsyncGenerator[T, None]:
|
|
257
371
|
"""Subscribe to the value of a signal so it can be iterated from.
|
|
258
372
|
|