ophyd-async 0.9.0a2__py3-none-any.whl → 0.10.0a1__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 +5 -8
- ophyd_async/_docs_parser.py +12 -0
- ophyd_async/_version.py +9 -4
- ophyd_async/core/__init__.py +97 -62
- ophyd_async/core/_derived_signal.py +271 -0
- ophyd_async/core/_derived_signal_backend.py +300 -0
- ophyd_async/core/_detector.py +106 -125
- ophyd_async/core/_device.py +69 -63
- ophyd_async/core/_device_filler.py +65 -1
- ophyd_async/core/_flyer.py +14 -5
- ophyd_async/core/_hdf_dataset.py +29 -22
- ophyd_async/core/_log.py +14 -23
- ophyd_async/core/_mock_signal_backend.py +11 -3
- ophyd_async/core/_protocol.py +65 -45
- ophyd_async/core/_providers.py +28 -9
- ophyd_async/core/_readable.py +44 -35
- ophyd_async/core/_settings.py +36 -27
- ophyd_async/core/_signal.py +262 -170
- ophyd_async/core/_signal_backend.py +56 -13
- ophyd_async/core/_soft_signal_backend.py +16 -11
- ophyd_async/core/_status.py +72 -24
- ophyd_async/core/_table.py +37 -8
- ophyd_async/core/_utils.py +96 -49
- ophyd_async/core/_yaml_settings.py +2 -0
- ophyd_async/epics/__init__.py +1 -0
- ophyd_async/epics/adandor/_andor.py +2 -2
- ophyd_async/epics/adandor/_andor_controller.py +4 -2
- ophyd_async/epics/adandor/_andor_io.py +2 -4
- ophyd_async/epics/adaravis/__init__.py +5 -0
- ophyd_async/epics/adaravis/_aravis.py +4 -8
- ophyd_async/epics/adaravis/_aravis_controller.py +20 -43
- ophyd_async/epics/adaravis/_aravis_io.py +13 -28
- ophyd_async/epics/adcore/__init__.py +23 -8
- ophyd_async/epics/adcore/_core_detector.py +42 -2
- ophyd_async/epics/adcore/_core_io.py +124 -99
- ophyd_async/epics/adcore/_core_logic.py +106 -27
- ophyd_async/epics/adcore/_core_writer.py +12 -8
- ophyd_async/epics/adcore/_hdf_writer.py +21 -38
- ophyd_async/epics/adcore/_single_trigger.py +2 -2
- ophyd_async/epics/adcore/_utils.py +2 -2
- ophyd_async/epics/adkinetix/__init__.py +2 -1
- ophyd_async/epics/adkinetix/_kinetix.py +3 -3
- ophyd_async/epics/adkinetix/_kinetix_controller.py +4 -2
- ophyd_async/epics/adkinetix/_kinetix_io.py +12 -13
- ophyd_async/epics/adpilatus/__init__.py +5 -0
- ophyd_async/epics/adpilatus/_pilatus.py +1 -1
- ophyd_async/epics/adpilatus/_pilatus_controller.py +5 -24
- ophyd_async/epics/adpilatus/_pilatus_io.py +11 -9
- ophyd_async/epics/adsimdetector/__init__.py +8 -1
- ophyd_async/epics/adsimdetector/_sim.py +4 -14
- ophyd_async/epics/adsimdetector/_sim_controller.py +17 -0
- ophyd_async/epics/adsimdetector/_sim_io.py +10 -0
- ophyd_async/epics/advimba/__init__.py +10 -1
- ophyd_async/epics/advimba/_vimba.py +3 -2
- ophyd_async/epics/advimba/_vimba_controller.py +4 -2
- ophyd_async/epics/advimba/_vimba_io.py +23 -28
- ophyd_async/epics/core/_aioca.py +35 -16
- ophyd_async/epics/core/_epics_connector.py +4 -0
- ophyd_async/epics/core/_epics_device.py +2 -0
- ophyd_async/epics/core/_p4p.py +10 -2
- ophyd_async/epics/core/_pvi_connector.py +65 -8
- ophyd_async/epics/core/_signal.py +51 -51
- ophyd_async/epics/core/_util.py +4 -4
- ophyd_async/epics/demo/__init__.py +16 -0
- ophyd_async/epics/demo/__main__.py +31 -0
- ophyd_async/epics/demo/_ioc.py +32 -0
- ophyd_async/epics/demo/_motor.py +82 -0
- ophyd_async/epics/demo/_point_detector.py +42 -0
- ophyd_async/epics/demo/_point_detector_channel.py +22 -0
- ophyd_async/epics/demo/_stage.py +15 -0
- ophyd_async/epics/{sim/mover.db → demo/motor.db} +2 -1
- ophyd_async/epics/demo/point_detector.db +59 -0
- ophyd_async/epics/demo/point_detector_channel.db +21 -0
- ophyd_async/epics/eiger/_eiger.py +1 -3
- ophyd_async/epics/eiger/_eiger_controller.py +11 -4
- ophyd_async/epics/eiger/_eiger_io.py +2 -0
- ophyd_async/epics/eiger/_odin_io.py +1 -2
- ophyd_async/epics/motor.py +65 -28
- ophyd_async/epics/signal.py +4 -1
- ophyd_async/epics/testing/_example_ioc.py +21 -9
- ophyd_async/epics/testing/_utils.py +3 -0
- ophyd_async/epics/testing/test_records.db +8 -0
- ophyd_async/epics/testing/test_records_pva.db +17 -16
- ophyd_async/fastcs/__init__.py +1 -0
- ophyd_async/fastcs/core.py +6 -0
- ophyd_async/fastcs/odin/__init__.py +1 -0
- ophyd_async/fastcs/panda/__init__.py +8 -6
- ophyd_async/fastcs/panda/_block.py +29 -9
- ophyd_async/fastcs/panda/_control.py +5 -0
- ophyd_async/fastcs/panda/_hdf_panda.py +2 -0
- ophyd_async/fastcs/panda/_table.py +9 -6
- ophyd_async/fastcs/panda/_trigger.py +23 -9
- ophyd_async/fastcs/panda/_writer.py +27 -30
- ophyd_async/plan_stubs/__init__.py +2 -0
- ophyd_async/plan_stubs/_ensure_connected.py +1 -0
- ophyd_async/plan_stubs/_fly.py +2 -4
- ophyd_async/plan_stubs/_nd_attributes.py +2 -0
- ophyd_async/plan_stubs/_panda.py +1 -0
- ophyd_async/plan_stubs/_settings.py +43 -16
- ophyd_async/plan_stubs/_utils.py +3 -0
- ophyd_async/plan_stubs/_wait_for_awaitable.py +1 -1
- ophyd_async/sim/__init__.py +24 -14
- ophyd_async/sim/__main__.py +43 -0
- ophyd_async/sim/_blob_detector.py +33 -0
- ophyd_async/sim/_blob_detector_controller.py +48 -0
- ophyd_async/sim/_blob_detector_writer.py +105 -0
- ophyd_async/sim/_mirror_horizontal.py +46 -0
- ophyd_async/sim/_mirror_vertical.py +74 -0
- ophyd_async/sim/_motor.py +233 -0
- ophyd_async/sim/_pattern_generator.py +124 -0
- ophyd_async/sim/_point_detector.py +86 -0
- ophyd_async/sim/_stage.py +19 -0
- ophyd_async/tango/__init__.py +1 -0
- ophyd_async/tango/core/__init__.py +6 -1
- ophyd_async/tango/core/_base_device.py +41 -33
- ophyd_async/tango/core/_converters.py +81 -0
- ophyd_async/tango/core/_signal.py +18 -32
- ophyd_async/tango/core/_tango_readable.py +2 -19
- ophyd_async/tango/core/_tango_transport.py +136 -60
- ophyd_async/tango/core/_utils.py +47 -0
- ophyd_async/tango/{sim → demo}/_counter.py +2 -0
- ophyd_async/tango/{sim → demo}/_detector.py +2 -0
- ophyd_async/tango/{sim → demo}/_mover.py +5 -4
- ophyd_async/tango/{sim → demo}/_tango/_servers.py +4 -0
- ophyd_async/tango/testing/__init__.py +6 -0
- ophyd_async/tango/testing/_one_of_everything.py +200 -0
- ophyd_async/testing/__init__.py +29 -7
- ophyd_async/testing/_assert.py +137 -81
- ophyd_async/testing/_mock_signal_utils.py +56 -70
- ophyd_async/testing/_one_of_everything.py +41 -21
- ophyd_async/testing/_single_derived.py +87 -0
- ophyd_async/testing/_utils.py +3 -0
- {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a1.dist-info}/METADATA +25 -26
- ophyd_async-0.10.0a1.dist-info/RECORD +149 -0
- {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a1.dist-info}/WHEEL +1 -1
- ophyd_async/epics/sim/__init__.py +0 -54
- ophyd_async/epics/sim/_ioc.py +0 -29
- ophyd_async/epics/sim/_mover.py +0 -101
- ophyd_async/epics/sim/_sensor.py +0 -37
- ophyd_async/epics/sim/sensor.db +0 -19
- ophyd_async/sim/_pattern_detector/__init__.py +0 -13
- ophyd_async/sim/_pattern_detector/_pattern_detector.py +0 -42
- ophyd_async/sim/_pattern_detector/_pattern_detector_controller.py +0 -69
- ophyd_async/sim/_pattern_detector/_pattern_detector_writer.py +0 -41
- ophyd_async/sim/_pattern_detector/_pattern_generator.py +0 -214
- ophyd_async/sim/_sim_motor.py +0 -107
- ophyd_async-0.9.0a2.dist-info/RECORD +0 -129
- /ophyd_async/tango/{sim → demo}/__init__.py +0 -0
- /ophyd_async/tango/{sim → demo}/_tango/__init__.py +0 -0
- {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a1.dist-info/licenses}/LICENSE +0 -0
- {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a1.dist-info}/top_level.txt +0 -0
|
@@ -16,7 +16,7 @@ from typing import (
|
|
|
16
16
|
)
|
|
17
17
|
|
|
18
18
|
from ._device import Device, DeviceConnector, DeviceVector
|
|
19
|
-
from ._signal import Signal, SignalX
|
|
19
|
+
from ._signal import Ignore, Signal, SignalX
|
|
20
20
|
from ._signal_backend import SignalBackend, SignalDatatype
|
|
21
21
|
from ._utils import get_origin_class
|
|
22
22
|
|
|
@@ -33,6 +33,7 @@ def _get_datatype(annotation: Any) -> type | None:
|
|
|
33
33
|
args = get_args(annotation)
|
|
34
34
|
if len(args) == 1 and get_origin_class(args[0]):
|
|
35
35
|
return args[0]
|
|
36
|
+
return None
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
def _logical(name: UniqueName) -> LogicalName:
|
|
@@ -53,6 +54,13 @@ class DeviceAnnotation(Protocol):
|
|
|
53
54
|
|
|
54
55
|
|
|
55
56
|
class DeviceFiller(Generic[SignalBackendT, DeviceConnectorT]):
|
|
57
|
+
"""For filling signals on introspected devices.
|
|
58
|
+
|
|
59
|
+
:param device: The device to fill.
|
|
60
|
+
:param signal_backend_factory: A callable that returns a SignalBackend.
|
|
61
|
+
:param device_connector_factory: A callable that returns a DeviceConnector.
|
|
62
|
+
"""
|
|
63
|
+
|
|
56
64
|
def __init__(
|
|
57
65
|
self,
|
|
58
66
|
device: Device,
|
|
@@ -68,6 +76,7 @@ class DeviceFiller(Generic[SignalBackendT, DeviceConnectorT]):
|
|
|
68
76
|
self._extras: dict[UniqueName, Sequence[Any]] = {}
|
|
69
77
|
self._signal_datatype: dict[LogicalName, type | None] = {}
|
|
70
78
|
self._vector_device_type: dict[LogicalName, type[Device] | None] = {}
|
|
79
|
+
self.ignored_signals: set[str] = set()
|
|
71
80
|
# Backends and Connectors stored ready for the connection phase
|
|
72
81
|
self._unfilled_backends: dict[
|
|
73
82
|
LogicalName, tuple[SignalBackendT, type[Signal]]
|
|
@@ -108,6 +117,8 @@ class DeviceFiller(Generic[SignalBackendT, DeviceConnectorT]):
|
|
|
108
117
|
# Get hints with Annotated for wrapping signals and backends
|
|
109
118
|
extra_hints = get_type_hints(cls, include_extras=True)
|
|
110
119
|
for attr_name, annotation in hints.items():
|
|
120
|
+
if annotation is Ignore:
|
|
121
|
+
self.ignored_signals.add(attr_name)
|
|
111
122
|
name = UniqueName(attr_name)
|
|
112
123
|
origin = get_origin_class(annotation)
|
|
113
124
|
if (
|
|
@@ -138,6 +149,7 @@ class DeviceFiller(Generic[SignalBackendT, DeviceConnectorT]):
|
|
|
138
149
|
self._uncreated_devices[name] = origin
|
|
139
150
|
|
|
140
151
|
def check_created(self):
|
|
152
|
+
"""Check that all Signals and Devices declared in annotations are created."""
|
|
141
153
|
uncreated = sorted(set(self._uncreated_signals).union(self._uncreated_devices))
|
|
142
154
|
if uncreated:
|
|
143
155
|
raise RuntimeError(
|
|
@@ -148,6 +160,21 @@ class DeviceFiller(Generic[SignalBackendT, DeviceConnectorT]):
|
|
|
148
160
|
self,
|
|
149
161
|
filled=True,
|
|
150
162
|
) -> Iterator[tuple[SignalBackendT, list[Any]]]:
|
|
163
|
+
"""Create all Signals from annotations.
|
|
164
|
+
|
|
165
|
+
:param filled:
|
|
166
|
+
If True then the Signals created should be considered already filled
|
|
167
|
+
with connection data. If False then `fill_child_signal` needs
|
|
168
|
+
calling at device connection time before the signal can be
|
|
169
|
+
connected.
|
|
170
|
+
:yields: `(backend, extras)`
|
|
171
|
+
The `SignalBackend` that has been created for this Signal, and the
|
|
172
|
+
list of extra annotations that could be used to customize it. For
|
|
173
|
+
example an `EpicsDeviceConnector` consumes `PvSuffix` extras to set the
|
|
174
|
+
write_pv of the backend. Any unhandled extras should be left on the
|
|
175
|
+
list so this class can handle them, e.g. `StandardReadableFormat`
|
|
176
|
+
instances.
|
|
177
|
+
"""
|
|
151
178
|
for name in list(self._uncreated_signals):
|
|
152
179
|
child_type = self._uncreated_signals.pop(name)
|
|
153
180
|
backend = self._signal_backend_factory(
|
|
@@ -167,6 +194,17 @@ class DeviceFiller(Generic[SignalBackendT, DeviceConnectorT]):
|
|
|
167
194
|
self,
|
|
168
195
|
filled=True,
|
|
169
196
|
) -> Iterator[tuple[DeviceConnectorT, list[Any]]]:
|
|
197
|
+
"""Create all Signals from annotations.
|
|
198
|
+
|
|
199
|
+
:param filled:
|
|
200
|
+
If True then the Devices created should be considered already filled
|
|
201
|
+
with connection data. If False then `fill_child_device` needs
|
|
202
|
+
calling at parent device connection time before the child Device can
|
|
203
|
+
be connected.
|
|
204
|
+
:yields: `(connector, extras)`
|
|
205
|
+
The `DeviceConnector` that has been created for this Signal, and the list of
|
|
206
|
+
extra annotations that could be used to customize it.
|
|
207
|
+
"""
|
|
170
208
|
for name in list(self._uncreated_devices):
|
|
171
209
|
child_type = self._uncreated_devices.pop(name)
|
|
172
210
|
connector = self._device_connector_factory()
|
|
@@ -181,6 +219,10 @@ class DeviceFiller(Generic[SignalBackendT, DeviceConnectorT]):
|
|
|
181
219
|
dest[_logical(name)] = connector
|
|
182
220
|
|
|
183
221
|
def create_device_vector_entries_to_mock(self, num: int):
|
|
222
|
+
"""Create num entries for each `DeviceVector`.
|
|
223
|
+
|
|
224
|
+
This is used when the Device is being connected in mock mode.
|
|
225
|
+
"""
|
|
184
226
|
for name, cls in self._vector_device_type.items():
|
|
185
227
|
if not cls:
|
|
186
228
|
msg = "Malformed device vector"
|
|
@@ -194,6 +236,11 @@ class DeviceFiller(Generic[SignalBackendT, DeviceConnectorT]):
|
|
|
194
236
|
self._raise(name, f"Can't make {cls}")
|
|
195
237
|
|
|
196
238
|
def check_filled(self, source: str):
|
|
239
|
+
"""Check that all the created Signals and Devices are filled.
|
|
240
|
+
|
|
241
|
+
:param source: The source of the data that should have done the filling, for
|
|
242
|
+
reporting as an error message
|
|
243
|
+
"""
|
|
197
244
|
unfilled = sorted(set(self._unfilled_connectors).union(self._unfilled_backends))
|
|
198
245
|
if unfilled:
|
|
199
246
|
raise RuntimeError(
|
|
@@ -216,6 +263,15 @@ class DeviceFiller(Generic[SignalBackendT, DeviceConnectorT]):
|
|
|
216
263
|
signal_type: type[Signal],
|
|
217
264
|
vector_index: int | None = None,
|
|
218
265
|
) -> SignalBackendT:
|
|
266
|
+
"""Mark a Signal as filled, and return its backend for filling.
|
|
267
|
+
|
|
268
|
+
:param name:
|
|
269
|
+
The name without trailing underscore, the name in the control system
|
|
270
|
+
:param signal_type:
|
|
271
|
+
One of the types `SignalR`, `SignalW`, `SignalRW` or `SignalX`
|
|
272
|
+
:param vector_index: If the child is in a `DeviceVector` then what index is it
|
|
273
|
+
:return: The SignalBackend for the filled Signal.
|
|
274
|
+
"""
|
|
219
275
|
name = cast(LogicalName, name)
|
|
220
276
|
if name in self._unfilled_backends:
|
|
221
277
|
# We made it above
|
|
@@ -251,6 +307,14 @@ class DeviceFiller(Generic[SignalBackendT, DeviceConnectorT]):
|
|
|
251
307
|
device_type: type[Device] = Device,
|
|
252
308
|
vector_index: int | None = None,
|
|
253
309
|
) -> DeviceConnectorT:
|
|
310
|
+
"""Mark a Device as filled, and return its connector for filling.
|
|
311
|
+
|
|
312
|
+
:param name:
|
|
313
|
+
The name without trailing underscore, the name in the control system
|
|
314
|
+
:param device_type: The `Device` subclass to be created
|
|
315
|
+
:param vector_index: If the child is in a `DeviceVector` then what index is it
|
|
316
|
+
:return: The DeviceConnector for the filled Device.
|
|
317
|
+
"""
|
|
254
318
|
name = cast(LogicalName, name)
|
|
255
319
|
if name in self._unfilled_connectors:
|
|
256
320
|
# We made it above
|
ophyd_async/core/_flyer.py
CHANGED
|
@@ -9,21 +9,26 @@ from ._utils import T
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class FlyerController(ABC, Generic[T]):
|
|
12
|
+
"""Base class for controlling 'flyable' devices.
|
|
13
|
+
|
|
14
|
+
[`bluesky.protocols.Flyable`](#bluesky.protocols.Flyable).
|
|
15
|
+
"""
|
|
16
|
+
|
|
12
17
|
@abstractmethod
|
|
13
18
|
async def prepare(self, value: T) -> Any:
|
|
14
|
-
"""Move to the start of the flyscan"""
|
|
19
|
+
"""Move to the start of the flyscan."""
|
|
15
20
|
|
|
16
21
|
@abstractmethod
|
|
17
22
|
async def kickoff(self):
|
|
18
|
-
"""Start the flyscan"""
|
|
23
|
+
"""Start the flyscan."""
|
|
19
24
|
|
|
20
25
|
@abstractmethod
|
|
21
26
|
async def complete(self):
|
|
22
|
-
"""Block until the flyscan is done"""
|
|
27
|
+
"""Block until the flyscan is done."""
|
|
23
28
|
|
|
24
29
|
@abstractmethod
|
|
25
30
|
async def stop(self):
|
|
26
|
-
"""Stop flying and wait everything to be stopped"""
|
|
31
|
+
"""Stop flying and wait everything to be stopped."""
|
|
27
32
|
|
|
28
33
|
|
|
29
34
|
class StandardFlyer(
|
|
@@ -33,6 +38,11 @@ class StandardFlyer(
|
|
|
33
38
|
Flyable,
|
|
34
39
|
Generic[T],
|
|
35
40
|
):
|
|
41
|
+
"""Base class for 'flyable' devices.
|
|
42
|
+
|
|
43
|
+
[`bluesky.protocols.Flyable`](#bluesky.protocols.Flyable).
|
|
44
|
+
"""
|
|
45
|
+
|
|
36
46
|
def __init__(
|
|
37
47
|
self,
|
|
38
48
|
trigger_logic: FlyerController[T],
|
|
@@ -54,7 +64,6 @@ class StandardFlyer(
|
|
|
54
64
|
await self._trigger_logic.stop()
|
|
55
65
|
|
|
56
66
|
def prepare(self, value: T) -> AsyncStatus:
|
|
57
|
-
"""Setup trajectories"""
|
|
58
67
|
return AsyncStatus(self._prepare(value))
|
|
59
68
|
|
|
60
69
|
async def _prepare(self, value: T) -> None:
|
ophyd_async/core/_hdf_dataset.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
from collections.abc import Iterator
|
|
2
|
-
from dataclasses import dataclass, field
|
|
1
|
+
from collections.abc import Iterator
|
|
3
2
|
from pathlib import Path
|
|
4
3
|
from urllib.parse import urlunparse
|
|
5
4
|
|
|
@@ -10,44 +9,53 @@ from event_model import (
|
|
|
10
9
|
StreamRange,
|
|
11
10
|
StreamResource,
|
|
12
11
|
)
|
|
12
|
+
from pydantic import BaseModel, Field
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
class HDFDatasetDescription(BaseModel):
|
|
16
|
+
"""A description of the type and shape of a dataset in an HDF file."""
|
|
17
|
+
|
|
17
18
|
data_key: str
|
|
19
|
+
"""The data_key that will appear in the event descriptor,
|
|
20
|
+
e.g. det or det.data"""
|
|
21
|
+
|
|
18
22
|
dataset: str
|
|
19
|
-
|
|
23
|
+
"""The dataset name within the HDF file,
|
|
24
|
+
e.g. /entry/data/data or /entry/instrument/NDAttributes/sum"""
|
|
25
|
+
|
|
26
|
+
shape: tuple[int, ...] = Field(default_factory=tuple)
|
|
27
|
+
"""The shape of a single event's data in the HDF file,
|
|
28
|
+
e.g. (1, 768, 1024) for arrays or () for scalars"""
|
|
29
|
+
|
|
20
30
|
dtype_numpy: str = ""
|
|
31
|
+
"""The numpy dtype for this field,
|
|
32
|
+
e.g. <i2 or <f8"""
|
|
33
|
+
|
|
34
|
+
chunk_shape: tuple[int, ...]
|
|
35
|
+
"""The explicit chunk size written to disk"""
|
|
36
|
+
|
|
21
37
|
multiplier: int = 1
|
|
22
|
-
|
|
23
|
-
# Represents explicit chunk size written to disk.
|
|
24
|
-
chunk_shape: tuple[int, ...] = ()
|
|
38
|
+
"""Won't be used soon."""
|
|
25
39
|
|
|
26
40
|
|
|
27
41
|
SLICE_NAME = "AD_HDF5_SWMR_SLICE"
|
|
28
42
|
|
|
29
43
|
|
|
30
|
-
class
|
|
31
|
-
"""
|
|
32
|
-
|
|
33
|
-
:param
|
|
44
|
+
class HDFDocumentComposer:
|
|
45
|
+
"""A helper class to make stream resource and datums for HDF datasets.
|
|
46
|
+
|
|
47
|
+
:param full_file_name: Absolute path to the file that has been written
|
|
48
|
+
:param datasets: Descriptions of each of the datasets that will appear in the file
|
|
34
49
|
"""
|
|
35
50
|
|
|
36
51
|
def __init__(
|
|
37
52
|
self,
|
|
38
53
|
full_file_name: Path,
|
|
39
|
-
datasets: list[
|
|
54
|
+
datasets: list[HDFDatasetDescription],
|
|
40
55
|
hostname: str = "localhost",
|
|
41
56
|
) -> None:
|
|
42
57
|
self._last_emitted = 0
|
|
43
58
|
self._hostname = hostname
|
|
44
|
-
|
|
45
|
-
if len(datasets) == 0:
|
|
46
|
-
self._bundles = []
|
|
47
|
-
return None
|
|
48
|
-
|
|
49
|
-
bundler_composer = ComposeStreamResource()
|
|
50
|
-
|
|
51
59
|
uri = urlunparse(
|
|
52
60
|
(
|
|
53
61
|
"file",
|
|
@@ -58,7 +66,7 @@ class HDFFile:
|
|
|
58
66
|
None,
|
|
59
67
|
)
|
|
60
68
|
)
|
|
61
|
-
|
|
69
|
+
bundler_composer = ComposeStreamResource()
|
|
62
70
|
self._bundles: list[ComposeStreamResourceBundle] = [
|
|
63
71
|
bundler_composer(
|
|
64
72
|
mimetype="application/x-hdf5",
|
|
@@ -66,7 +74,6 @@ class HDFFile:
|
|
|
66
74
|
data_key=ds.data_key,
|
|
67
75
|
parameters={
|
|
68
76
|
"dataset": ds.dataset,
|
|
69
|
-
"swmr": ds.swmr,
|
|
70
77
|
"multiplier": ds.multiplier,
|
|
71
78
|
"chunk_shape": ds.chunk_shape,
|
|
72
79
|
},
|
ophyd_async/core/_log.py
CHANGED
|
@@ -63,31 +63,23 @@ def config_ophyd_async_logging(
|
|
|
63
63
|
color=True,
|
|
64
64
|
level="WARNING",
|
|
65
65
|
):
|
|
66
|
-
"""
|
|
67
|
-
|
|
66
|
+
"""Set a new handler on the ``logging.getLogger('ophyd_async')`` logger.
|
|
67
|
+
|
|
68
68
|
If this is called more than once, the handler from the previous invocation
|
|
69
69
|
is removed (if still present) and replaced.
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
Returns
|
|
85
|
-
-------
|
|
86
|
-
handler : logging.Handler
|
|
87
|
-
The handler, which has already been added to the 'ophyd_async' logger.
|
|
88
|
-
|
|
89
|
-
Examples
|
|
90
|
-
--------
|
|
71
|
+
|
|
72
|
+
:param file:
|
|
73
|
+
object with ``write`` method or filename string. Default is `sys.stdout`.
|
|
74
|
+
:param fmt: str Overall logging format
|
|
75
|
+
:param datefmt: str Date format. Default is `'%H:%M:%S'`.
|
|
76
|
+
:param color: bool Use ANSI color codes. True by default.
|
|
77
|
+
:param level: str or int Python logging level, given as string or
|
|
78
|
+
corresponding integer. Default is 'WARNING'.
|
|
79
|
+
|
|
80
|
+
:returns: The handler, which has already been added to the 'ophyd_async' logger.
|
|
81
|
+
|
|
82
|
+
:examples:
|
|
91
83
|
Log to a file.
|
|
92
84
|
|
|
93
85
|
config_ophyd_async_logging(file='/tmp/what_is_happening.txt')
|
|
@@ -104,7 +96,6 @@ def config_ophyd_async_logging(
|
|
|
104
96
|
Increase verbosity: show level DEBUG or higher.
|
|
105
97
|
|
|
106
98
|
config_ophyd_async_logging(level='DEBUG')
|
|
107
|
-
|
|
108
99
|
"""
|
|
109
100
|
global current_handler
|
|
110
101
|
|
|
@@ -3,8 +3,10 @@ from collections.abc import Callable
|
|
|
3
3
|
from functools import cached_property
|
|
4
4
|
from unittest.mock import AsyncMock
|
|
5
5
|
|
|
6
|
-
from bluesky.protocols import
|
|
6
|
+
from bluesky.protocols import Reading
|
|
7
|
+
from event_model import DataKey
|
|
7
8
|
|
|
9
|
+
from ._derived_signal_backend import DerivedSignalBackend
|
|
8
10
|
from ._signal_backend import SignalBackend, SignalDatatypeT
|
|
9
11
|
from ._soft_signal_backend import SoftSignalBackend
|
|
10
12
|
from ._utils import Callback, LazyMock
|
|
@@ -23,7 +25,7 @@ class MockSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
|
23
25
|
|
|
24
26
|
self.initial_backend = initial_backend
|
|
25
27
|
|
|
26
|
-
if isinstance(self.initial_backend, SoftSignalBackend):
|
|
28
|
+
if isinstance(self.initial_backend, SoftSignalBackend | DerivedSignalBackend):
|
|
27
29
|
# Backend is already a SoftSignalBackend, so use it
|
|
28
30
|
self.soft_backend = self.initial_backend
|
|
29
31
|
else:
|
|
@@ -38,11 +40,13 @@ class MockSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
|
38
40
|
|
|
39
41
|
@cached_property
|
|
40
42
|
def put_mock(self) -> AsyncMock:
|
|
43
|
+
"""Return the mock that will track calls to `put()`."""
|
|
41
44
|
put_mock = AsyncMock(name="put", spec=Callable)
|
|
42
45
|
self.mock().attach_mock(put_mock, "put")
|
|
43
46
|
return put_mock
|
|
44
47
|
|
|
45
48
|
def set_value(self, value: SignalDatatypeT):
|
|
49
|
+
"""Set the value of the signal."""
|
|
46
50
|
self.soft_backend.set_value(value)
|
|
47
51
|
|
|
48
52
|
def source(self, name: str, read: bool) -> str:
|
|
@@ -53,6 +57,10 @@ class MockSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
|
53
57
|
|
|
54
58
|
@cached_property
|
|
55
59
|
def put_proceeds(self) -> asyncio.Event:
|
|
60
|
+
"""Return an Event that will block `put()` until set.
|
|
61
|
+
|
|
62
|
+
The Event is initially set, but can be unset to block `put()`.
|
|
63
|
+
"""
|
|
56
64
|
put_proceeds = asyncio.Event()
|
|
57
65
|
put_proceeds.set()
|
|
58
66
|
return put_proceeds
|
|
@@ -72,7 +80,7 @@ class MockSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
|
72
80
|
async def get_setpoint(self) -> SignalDatatypeT:
|
|
73
81
|
return await self.soft_backend.get_setpoint()
|
|
74
82
|
|
|
75
|
-
async def get_datakey(self, source: str) ->
|
|
83
|
+
async def get_datakey(self, source: str) -> DataKey:
|
|
76
84
|
return await self.soft_backend.get_datakey(source)
|
|
77
85
|
|
|
78
86
|
def set_callback(self, callback: Callback[Reading[SignalDatatypeT]] | None) -> None:
|
ophyd_async/core/_protocol.py
CHANGED
|
@@ -10,67 +10,66 @@ from typing import (
|
|
|
10
10
|
runtime_checkable,
|
|
11
11
|
)
|
|
12
12
|
|
|
13
|
-
from bluesky.protocols import HasName, Reading
|
|
13
|
+
from bluesky.protocols import HasName, Location, Reading, T_co
|
|
14
14
|
from event_model import DataKey
|
|
15
15
|
|
|
16
|
+
from ._utils import T
|
|
17
|
+
|
|
16
18
|
if TYPE_CHECKING:
|
|
17
19
|
from ._status import AsyncStatus
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
@runtime_checkable
|
|
21
23
|
class AsyncReadable(HasName, Protocol):
|
|
24
|
+
"""Async implementations of the sync [](#bluesky.protocols.Readable)."""
|
|
25
|
+
|
|
22
26
|
@abstractmethod
|
|
23
27
|
async def read(self) -> dict[str, Reading]:
|
|
24
|
-
"""Return
|
|
25
|
-
of values and timestamps and optional per-point metadata.
|
|
26
|
-
|
|
27
|
-
Example return value:
|
|
28
|
+
"""Return value, timestamp, optional per-point metadata for each field name.
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
For example:
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
{
|
|
33
|
+
"channel1": {"value": 5, "timestamp": 1472493713.271991},
|
|
34
|
+
"channel2": {"value": 16, "timestamp": 1472493713.539238},
|
|
35
|
+
}
|
|
35
36
|
"""
|
|
36
37
|
|
|
37
38
|
@abstractmethod
|
|
38
39
|
async def describe(self) -> dict[str, DataKey]:
|
|
39
|
-
"""Return
|
|
40
|
-
method, here mapped to per-scan metadata about each field.
|
|
40
|
+
"""Return per-scan metadata for each field name in `read()`.
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
For example:
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
'dtype': 'number',
|
|
49
|
-
'shape': []}),
|
|
50
|
-
('channel2',
|
|
51
|
-
{'source': 'XF23-ID:SOME_PV_NAME',
|
|
52
|
-
'dtype': 'number',
|
|
53
|
-
'shape': []}))
|
|
44
|
+
{
|
|
45
|
+
"channel1": {"source": "SOME_PV1", "dtype": "number", "shape": []},
|
|
46
|
+
"channel2": {"source": "SOME_PV2", "dtype": "number", "shape": []},
|
|
47
|
+
}
|
|
54
48
|
"""
|
|
55
49
|
|
|
56
50
|
|
|
57
51
|
@runtime_checkable
|
|
58
52
|
class AsyncConfigurable(HasName, Protocol):
|
|
53
|
+
"""Async implementation of the sync [](#bluesky.protocols.Configurable)."""
|
|
54
|
+
|
|
59
55
|
@abstractmethod
|
|
60
56
|
async def read_configuration(self) -> dict[str, Reading]:
|
|
61
|
-
"""
|
|
62
|
-
|
|
57
|
+
"""Return value, timestamp, optional per-point metadata for each field name.
|
|
58
|
+
|
|
59
|
+
Same API as [](#AsyncReadable.read) but for slow-changing fields related to
|
|
60
|
+
configuration. e.g., exposure time. These will typically be read only
|
|
61
|
+
once per run.
|
|
63
62
|
"""
|
|
64
63
|
|
|
65
64
|
@abstractmethod
|
|
66
65
|
async def describe_configuration(self) -> dict[str, DataKey]:
|
|
67
|
-
"""
|
|
68
|
-
``read_configuration``.
|
|
69
|
-
"""
|
|
66
|
+
"""Return per-scan metadata for each field name in `read_configuration()`."""
|
|
70
67
|
|
|
71
68
|
|
|
72
69
|
@runtime_checkable
|
|
73
70
|
class AsyncPausable(Protocol):
|
|
71
|
+
"""Async implementation of the sync [](#bluesky.protocols.Pausable)."""
|
|
72
|
+
|
|
74
73
|
@abstractmethod
|
|
75
74
|
async def pause(self) -> None:
|
|
76
75
|
"""Perform device-specific work when the RunEngine pauses."""
|
|
@@ -82,20 +81,40 @@ class AsyncPausable(Protocol):
|
|
|
82
81
|
|
|
83
82
|
@runtime_checkable
|
|
84
83
|
class AsyncStageable(Protocol):
|
|
84
|
+
"""Async implementation of the sync [](#bluesky.protocols.Stageable)."""
|
|
85
|
+
|
|
85
86
|
@abstractmethod
|
|
86
87
|
def stage(self) -> AsyncStatus:
|
|
87
|
-
"""
|
|
88
|
+
"""Set up the device for acquisition.
|
|
88
89
|
|
|
89
|
-
|
|
90
|
-
done staging.
|
|
90
|
+
:return: An `AsyncStatus` that is marked done when the device is done staging.
|
|
91
91
|
"""
|
|
92
92
|
|
|
93
93
|
@abstractmethod
|
|
94
94
|
def unstage(self) -> AsyncStatus:
|
|
95
|
-
"""
|
|
95
|
+
"""Clean up the device after acquisition.
|
|
96
|
+
|
|
97
|
+
:return: An `AsyncStatus` that is marked done when the device is done unstaging.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@runtime_checkable
|
|
102
|
+
class AsyncMovable(Protocol[T_co]):
|
|
103
|
+
@abstractmethod
|
|
104
|
+
def set(self, value: T_co) -> AsyncStatus:
|
|
105
|
+
"""Return a ``Status`` that is marked done when the device is done moving."""
|
|
96
106
|
|
|
97
|
-
|
|
98
|
-
|
|
107
|
+
|
|
108
|
+
@runtime_checkable
|
|
109
|
+
class AsyncLocatable(AsyncMovable[T], Protocol):
|
|
110
|
+
@abstractmethod
|
|
111
|
+
async def locate(self) -> Location[T]:
|
|
112
|
+
"""Return the current location of a Device.
|
|
113
|
+
|
|
114
|
+
While a ``Readable`` reports many values, a ``Movable`` will have the
|
|
115
|
+
concept of location. This is where the Device currently is, and where it
|
|
116
|
+
was last requested to move to. This protocol formalizes how to get the
|
|
117
|
+
location from a ``Movable``.
|
|
99
118
|
"""
|
|
100
119
|
|
|
101
120
|
|
|
@@ -103,16 +122,17 @@ C = TypeVar("C", contravariant=True)
|
|
|
103
122
|
|
|
104
123
|
|
|
105
124
|
class Watcher(Protocol, Generic[C]):
|
|
106
|
-
|
|
125
|
+
"""Protocol for watching changes in values."""
|
|
126
|
+
|
|
107
127
|
def __call__(
|
|
108
|
-
|
|
109
|
-
current: C,
|
|
110
|
-
initial: C,
|
|
111
|
-
target: C,
|
|
112
|
-
name: str | None,
|
|
113
|
-
unit: str | None,
|
|
114
|
-
precision:
|
|
115
|
-
fraction: float | None,
|
|
116
|
-
time_elapsed: float | None,
|
|
117
|
-
time_remaining: float | None,
|
|
128
|
+
self,
|
|
129
|
+
current: C | None = None,
|
|
130
|
+
initial: C | None = None,
|
|
131
|
+
target: C | None = None,
|
|
132
|
+
name: str | None = None,
|
|
133
|
+
unit: str | None = None,
|
|
134
|
+
precision: int | None = None,
|
|
135
|
+
fraction: float | None = None,
|
|
136
|
+
time_elapsed: float | None = None,
|
|
137
|
+
time_remaining: float | None = None,
|
|
118
138
|
) -> Any: ...
|