ophyd-async 0.8.0a6__py3-none-any.whl → 0.9.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/_version.py +2 -2
- ophyd_async/core/__init__.py +2 -26
- ophyd_async/core/_detector.py +9 -9
- ophyd_async/core/_device.py +27 -8
- ophyd_async/core/_protocol.py +0 -28
- ophyd_async/core/_signal.py +38 -136
- ophyd_async/core/_utils.py +11 -2
- ophyd_async/epics/adaravis/_aravis_controller.py +8 -8
- ophyd_async/epics/adaravis/_aravis_io.py +4 -4
- ophyd_async/epics/adcore/_core_io.py +21 -21
- ophyd_async/epics/adcore/_core_logic.py +3 -2
- ophyd_async/epics/adcore/_hdf_writer.py +6 -3
- ophyd_async/epics/adcore/_single_trigger.py +1 -1
- ophyd_async/epics/adcore/_utils.py +35 -35
- ophyd_async/epics/adkinetix/_kinetix_controller.py +7 -7
- ophyd_async/epics/adkinetix/_kinetix_io.py +7 -7
- ophyd_async/epics/adpilatus/_pilatus.py +3 -3
- ophyd_async/epics/adpilatus/_pilatus_controller.py +4 -4
- ophyd_async/epics/adpilatus/_pilatus_io.py +5 -5
- ophyd_async/epics/adsimdetector/_sim_controller.py +2 -2
- ophyd_async/epics/advimba/_vimba_controller.py +14 -14
- ophyd_async/epics/advimba/_vimba_io.py +23 -23
- ophyd_async/epics/core/_p4p.py +19 -0
- ophyd_async/epics/core/_pvi_connector.py +4 -2
- ophyd_async/epics/core/_signal.py +9 -2
- ophyd_async/epics/core/_util.py +9 -0
- ophyd_async/epics/demo/_mover.py +2 -2
- ophyd_async/epics/demo/_sensor.py +2 -2
- ophyd_async/epics/eiger/_eiger_controller.py +4 -4
- ophyd_async/epics/eiger/_eiger_io.py +3 -3
- ophyd_async/epics/motor.py +8 -5
- ophyd_async/epics/testing/_example_ioc.py +5 -3
- ophyd_async/epics/testing/test_records.db +6 -0
- ophyd_async/fastcs/core.py +2 -2
- ophyd_async/fastcs/panda/_block.py +9 -9
- ophyd_async/fastcs/panda/_control.py +2 -2
- ophyd_async/fastcs/panda/_hdf_panda.py +4 -1
- ophyd_async/fastcs/panda/_trigger.py +7 -7
- ophyd_async/plan_stubs/_fly.py +1 -1
- ophyd_async/sim/demo/_sim_motor.py +34 -32
- ophyd_async/tango/core/_tango_transport.py +1 -1
- ophyd_async/testing/__init__.py +33 -0
- ophyd_async/testing/_assert.py +128 -0
- ophyd_async/{core → testing}/_mock_signal_utils.py +12 -8
- ophyd_async/testing/_wait_for_pending.py +22 -0
- {ophyd_async-0.8.0a6.dist-info → ophyd_async-0.9.0a1.dist-info}/METADATA +3 -1
- {ophyd_async-0.8.0a6.dist-info → ophyd_async-0.9.0a1.dist-info}/RECORD +51 -48
- {ophyd_async-0.8.0a6.dist-info → ophyd_async-0.9.0a1.dist-info}/LICENSE +0 -0
- {ophyd_async-0.8.0a6.dist-info → ophyd_async-0.9.0a1.dist-info}/WHEEL +0 -0
- {ophyd_async-0.8.0a6.dist-info → ophyd_async-0.9.0a1.dist-info}/entry_points.txt +0 -0
- {ophyd_async-0.8.0a6.dist-info → ophyd_async-0.9.0a1.dist-info}/top_level.txt +0 -0
ophyd_async/_version.py
CHANGED
ophyd_async/core/__init__.py
CHANGED
|
@@ -21,16 +21,6 @@ from ._flyer import FlyerController, StandardFlyer
|
|
|
21
21
|
from ._hdf_dataset import HDFDataset, HDFFile
|
|
22
22
|
from ._log import config_ophyd_async_logging
|
|
23
23
|
from ._mock_signal_backend import MockSignalBackend
|
|
24
|
-
from ._mock_signal_utils import (
|
|
25
|
-
callback_on_mock_put,
|
|
26
|
-
get_mock,
|
|
27
|
-
get_mock_put,
|
|
28
|
-
mock_puts_blocked,
|
|
29
|
-
reset_mock_put_calls,
|
|
30
|
-
set_mock_put_proceeds,
|
|
31
|
-
set_mock_value,
|
|
32
|
-
set_mock_values,
|
|
33
|
-
)
|
|
34
24
|
from ._protocol import AsyncConfigurable, AsyncReadable, AsyncStageable
|
|
35
25
|
from ._providers import (
|
|
36
26
|
AutoIncrementFilenameProvider,
|
|
@@ -53,14 +43,11 @@ from ._readable import (
|
|
|
53
43
|
)
|
|
54
44
|
from ._signal import (
|
|
55
45
|
Signal,
|
|
46
|
+
SignalConnector,
|
|
56
47
|
SignalR,
|
|
57
48
|
SignalRW,
|
|
58
49
|
SignalW,
|
|
59
50
|
SignalX,
|
|
60
|
-
assert_configuration,
|
|
61
|
-
assert_emitted,
|
|
62
|
-
assert_reading,
|
|
63
|
-
assert_value,
|
|
64
51
|
observe_signals_value,
|
|
65
52
|
observe_value,
|
|
66
53
|
set_and_wait_for_other_value,
|
|
@@ -123,14 +110,6 @@ __all__ = [
|
|
|
123
110
|
"HDFFile",
|
|
124
111
|
"config_ophyd_async_logging",
|
|
125
112
|
"MockSignalBackend",
|
|
126
|
-
"callback_on_mock_put",
|
|
127
|
-
"get_mock",
|
|
128
|
-
"get_mock_put",
|
|
129
|
-
"mock_puts_blocked",
|
|
130
|
-
"reset_mock_put_calls",
|
|
131
|
-
"set_mock_put_proceeds",
|
|
132
|
-
"set_mock_value",
|
|
133
|
-
"set_mock_values",
|
|
134
113
|
"AsyncConfigurable",
|
|
135
114
|
"AsyncReadable",
|
|
136
115
|
"AsyncStageable",
|
|
@@ -150,14 +129,11 @@ __all__ = [
|
|
|
150
129
|
"StandardReadable",
|
|
151
130
|
"StandardReadableFormat",
|
|
152
131
|
"Signal",
|
|
132
|
+
"SignalConnector",
|
|
153
133
|
"SignalR",
|
|
154
134
|
"SignalRW",
|
|
155
135
|
"SignalW",
|
|
156
136
|
"SignalX",
|
|
157
|
-
"assert_configuration",
|
|
158
|
-
"assert_emitted",
|
|
159
|
-
"assert_reading",
|
|
160
|
-
"assert_value",
|
|
161
137
|
"observe_value",
|
|
162
138
|
"observe_signals_value",
|
|
163
139
|
"set_and_wait_for_value",
|
ophyd_async/core/_detector.py
CHANGED
|
@@ -30,13 +30,13 @@ class DetectorTrigger(StrictEnum):
|
|
|
30
30
|
"""Type of mechanism for triggering a detector to take frames"""
|
|
31
31
|
|
|
32
32
|
#: Detector generates internal trigger for given rate
|
|
33
|
-
|
|
33
|
+
INTERNAL = "internal"
|
|
34
34
|
#: Expect a series of arbitrary length trigger signals
|
|
35
|
-
|
|
35
|
+
EDGE_TRIGGER = "edge_trigger"
|
|
36
36
|
#: Expect a series of constant width external gate signals
|
|
37
|
-
|
|
37
|
+
CONSTANT_GATE = "constant_gate"
|
|
38
38
|
#: Expect a series of variable width external gate signals
|
|
39
|
-
|
|
39
|
+
VARIABLE_GATE = "variable_gate"
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
class TriggerInfo(BaseModel):
|
|
@@ -53,7 +53,7 @@ class TriggerInfo(BaseModel):
|
|
|
53
53
|
#: - 3 times for final flat field images
|
|
54
54
|
number_of_triggers: NonNegativeInt | list[NonNegativeInt]
|
|
55
55
|
#: Sort of triggers that will be sent
|
|
56
|
-
trigger: DetectorTrigger = Field(default=DetectorTrigger.
|
|
56
|
+
trigger: DetectorTrigger = Field(default=DetectorTrigger.INTERNAL)
|
|
57
57
|
#: What is the minimum deadtime between triggers
|
|
58
58
|
deadtime: float | None = Field(default=None, ge=0)
|
|
59
59
|
#: What is the maximum high time of the triggers
|
|
@@ -265,14 +265,14 @@ class StandardDetector(
|
|
|
265
265
|
await self.prepare(
|
|
266
266
|
TriggerInfo(
|
|
267
267
|
number_of_triggers=1,
|
|
268
|
-
trigger=DetectorTrigger.
|
|
268
|
+
trigger=DetectorTrigger.INTERNAL,
|
|
269
269
|
deadtime=None,
|
|
270
270
|
livetime=None,
|
|
271
271
|
frame_timeout=None,
|
|
272
272
|
)
|
|
273
273
|
)
|
|
274
274
|
assert self._trigger_info
|
|
275
|
-
assert self._trigger_info.trigger is DetectorTrigger.
|
|
275
|
+
assert self._trigger_info.trigger is DetectorTrigger.INTERNAL
|
|
276
276
|
# Arm the detector and wait for it to finish.
|
|
277
277
|
indices_written = await self.writer.get_indices_written()
|
|
278
278
|
await self.controller.arm()
|
|
@@ -303,7 +303,7 @@ class StandardDetector(
|
|
|
303
303
|
Args:
|
|
304
304
|
value: TriggerInfo describing how to trigger the detector
|
|
305
305
|
"""
|
|
306
|
-
if value.trigger != DetectorTrigger.
|
|
306
|
+
if value.trigger != DetectorTrigger.INTERNAL:
|
|
307
307
|
assert (
|
|
308
308
|
value.deadtime
|
|
309
309
|
), "Deadtime must be supplied when in externally triggered mode"
|
|
@@ -323,7 +323,7 @@ class StandardDetector(
|
|
|
323
323
|
self._describe, _ = await asyncio.gather(
|
|
324
324
|
self.writer.open(value.multiplier), self.controller.prepare(value)
|
|
325
325
|
)
|
|
326
|
-
if value.trigger != DetectorTrigger.
|
|
326
|
+
if value.trigger != DetectorTrigger.INTERNAL:
|
|
327
327
|
await self.controller.arm()
|
|
328
328
|
self._fly_start = time.monotonic()
|
|
329
329
|
|
ophyd_async/core/_device.py
CHANGED
|
@@ -10,7 +10,6 @@ from typing import Any, TypeVar
|
|
|
10
10
|
from bluesky.protocols import HasName
|
|
11
11
|
from bluesky.run_engine import call_in_bluesky_event_loop, in_bluesky_event_loop
|
|
12
12
|
|
|
13
|
-
from ._protocol import Connectable
|
|
14
13
|
from ._utils import DEFAULT_TIMEOUT, LazyMock, NotConnected, wait_for_connection
|
|
15
14
|
|
|
16
15
|
|
|
@@ -61,7 +60,7 @@ class DeviceConnector:
|
|
|
61
60
|
await wait_for_connection(**coros)
|
|
62
61
|
|
|
63
62
|
|
|
64
|
-
class Device(HasName
|
|
63
|
+
class Device(HasName):
|
|
65
64
|
"""Common base class for all Ophyd Async Devices."""
|
|
66
65
|
|
|
67
66
|
_name: str = ""
|
|
@@ -71,13 +70,16 @@ class Device(HasName, Connectable):
|
|
|
71
70
|
_connect_task: asyncio.Task | None = None
|
|
72
71
|
# The mock if we have connected in mock mode
|
|
73
72
|
_mock: LazyMock | None = None
|
|
73
|
+
# The separator to use when making child names
|
|
74
|
+
_child_name_separator: str = "-"
|
|
74
75
|
|
|
75
76
|
def __init__(
|
|
76
77
|
self, name: str = "", connector: DeviceConnector | None = None
|
|
77
78
|
) -> None:
|
|
78
79
|
self._connector = connector or DeviceConnector()
|
|
79
80
|
self._connector.create_children_from_annotations(self)
|
|
80
|
-
|
|
81
|
+
if name:
|
|
82
|
+
self.set_name(name)
|
|
81
83
|
|
|
82
84
|
@property
|
|
83
85
|
def name(self) -> str:
|
|
@@ -97,21 +99,30 @@ class Device(HasName, Connectable):
|
|
|
97
99
|
getLogger("ophyd_async.devices"), {"ophyd_async_device_name": self.name}
|
|
98
100
|
)
|
|
99
101
|
|
|
100
|
-
def set_name(self, name: str):
|
|
102
|
+
def set_name(self, name: str, *, child_name_separator: str | None = None) -> None:
|
|
101
103
|
"""Set ``self.name=name`` and each ``self.child.name=name+"-child"``.
|
|
102
104
|
|
|
103
105
|
Parameters
|
|
104
106
|
----------
|
|
105
107
|
name:
|
|
106
108
|
New name to set
|
|
109
|
+
child_name_separator:
|
|
110
|
+
Use this as a separator instead of "-". Use "_" instead to make the same
|
|
111
|
+
names as the equivalent ophyd sync device.
|
|
107
112
|
"""
|
|
108
113
|
self._name = name
|
|
114
|
+
if child_name_separator:
|
|
115
|
+
self._child_name_separator = child_name_separator
|
|
109
116
|
# Ensure logger is recreated after a name change
|
|
110
117
|
if "log" in self.__dict__:
|
|
111
118
|
del self.log
|
|
112
|
-
for
|
|
113
|
-
child_name =
|
|
114
|
-
|
|
119
|
+
for attr_name, child in self.children():
|
|
120
|
+
child_name = (
|
|
121
|
+
f"{self.name}{self._child_name_separator}{attr_name}"
|
|
122
|
+
if self.name
|
|
123
|
+
else ""
|
|
124
|
+
)
|
|
125
|
+
child.set_name(child_name, child_name_separator=self._child_name_separator)
|
|
115
126
|
|
|
116
127
|
def __setattr__(self, name: str, value: Any) -> None:
|
|
117
128
|
# Bear in mind that this function is called *a lot*, so
|
|
@@ -147,6 +158,10 @@ class Device(HasName, Connectable):
|
|
|
147
158
|
timeout:
|
|
148
159
|
Time to wait before failing with a TimeoutError.
|
|
149
160
|
"""
|
|
161
|
+
assert hasattr(self, "_connector"), (
|
|
162
|
+
f"{self}: doesn't have attribute `_connector`,"
|
|
163
|
+
" did you call `super().__init__` in your `__init__` method?"
|
|
164
|
+
)
|
|
150
165
|
if mock:
|
|
151
166
|
# Always connect in mock mode serially
|
|
152
167
|
if isinstance(mock, LazyMock):
|
|
@@ -247,6 +262,8 @@ class DeviceCollector:
|
|
|
247
262
|
set_name:
|
|
248
263
|
If True, call ``device.set_name(variable_name)`` on all collected
|
|
249
264
|
Devices
|
|
265
|
+
child_name_separator:
|
|
266
|
+
Use this as a separator if we call ``set_name``.
|
|
250
267
|
connect:
|
|
251
268
|
If True, call ``device.connect(mock)`` in parallel on all
|
|
252
269
|
collected Devices
|
|
@@ -271,11 +288,13 @@ class DeviceCollector:
|
|
|
271
288
|
def __init__(
|
|
272
289
|
self,
|
|
273
290
|
set_name=True,
|
|
291
|
+
child_name_separator: str = "-",
|
|
274
292
|
connect=True,
|
|
275
293
|
mock=False,
|
|
276
294
|
timeout: float = 10.0,
|
|
277
295
|
):
|
|
278
296
|
self._set_name = set_name
|
|
297
|
+
self._child_name_separator = child_name_separator
|
|
279
298
|
self._connect = connect
|
|
280
299
|
self._mock = mock
|
|
281
300
|
self._timeout = timeout
|
|
@@ -311,7 +330,7 @@ class DeviceCollector:
|
|
|
311
330
|
for name, obj in self._objects_on_exit.items():
|
|
312
331
|
if name not in self._names_on_enter and isinstance(obj, Device):
|
|
313
332
|
if self._set_name and not obj.name:
|
|
314
|
-
obj.set_name(name)
|
|
333
|
+
obj.set_name(name, child_name_separator=self._child_name_separator)
|
|
315
334
|
if self._connect:
|
|
316
335
|
connect_coroutines[name] = obj.connect(
|
|
317
336
|
self._mock, timeout=self._timeout
|
ophyd_async/core/_protocol.py
CHANGED
|
@@ -13,38 +13,10 @@ from typing import (
|
|
|
13
13
|
from bluesky.protocols import HasName, Reading
|
|
14
14
|
from event_model import DataKey
|
|
15
15
|
|
|
16
|
-
from ._utils import DEFAULT_TIMEOUT
|
|
17
|
-
|
|
18
16
|
if TYPE_CHECKING:
|
|
19
|
-
from unittest.mock import Mock
|
|
20
|
-
|
|
21
17
|
from ._status import AsyncStatus
|
|
22
18
|
|
|
23
19
|
|
|
24
|
-
@runtime_checkable
|
|
25
|
-
class Connectable(Protocol):
|
|
26
|
-
@abstractmethod
|
|
27
|
-
async def connect(
|
|
28
|
-
self,
|
|
29
|
-
mock: bool | Mock = False,
|
|
30
|
-
timeout: float = DEFAULT_TIMEOUT,
|
|
31
|
-
force_reconnect: bool = False,
|
|
32
|
-
):
|
|
33
|
-
"""Connect self and all child Devices.
|
|
34
|
-
|
|
35
|
-
Contains a timeout that gets propagated to child.connect methods.
|
|
36
|
-
|
|
37
|
-
Parameters
|
|
38
|
-
----------
|
|
39
|
-
mock:
|
|
40
|
-
If True then use ``MockSignalBackend`` for all Signals
|
|
41
|
-
timeout:
|
|
42
|
-
Time to wait before failing with a TimeoutError.
|
|
43
|
-
force_reconnect:
|
|
44
|
-
Reconnect even if previous connect was successful.
|
|
45
|
-
"""
|
|
46
|
-
|
|
47
|
-
|
|
48
20
|
@runtime_checkable
|
|
49
21
|
class AsyncReadable(HasName, Protocol):
|
|
50
22
|
@abstractmethod
|
ophyd_async/core/_signal.py
CHANGED
|
@@ -2,8 +2,9 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import functools
|
|
5
|
-
|
|
6
|
-
from
|
|
5
|
+
import time
|
|
6
|
+
from collections.abc import AsyncGenerator, Awaitable, Callable
|
|
7
|
+
from typing import Generic, cast
|
|
7
8
|
|
|
8
9
|
from bluesky.protocols import (
|
|
9
10
|
Locatable,
|
|
@@ -17,7 +18,6 @@ from event_model import DataKey
|
|
|
17
18
|
from ._device import Device, DeviceConnector
|
|
18
19
|
from ._mock_signal_backend import MockSignalBackend
|
|
19
20
|
from ._protocol import (
|
|
20
|
-
AsyncConfigurable,
|
|
21
21
|
AsyncReadable,
|
|
22
22
|
AsyncStageable,
|
|
23
23
|
Reading,
|
|
@@ -122,7 +122,7 @@ class _SignalCache(Generic[SignalDatatypeT]):
|
|
|
122
122
|
|
|
123
123
|
def _callback(self, reading: Reading[SignalDatatypeT]):
|
|
124
124
|
self._signal.log.debug(
|
|
125
|
-
f"Updated subscription: reading of source {self._signal.source} changed"
|
|
125
|
+
f"Updated subscription: reading of source {self._signal.source} changed "
|
|
126
126
|
f"from {self._reading} to {reading}"
|
|
127
127
|
)
|
|
128
128
|
self._reading = reading
|
|
@@ -301,130 +301,11 @@ def soft_signal_r_and_setter(
|
|
|
301
301
|
return (signal, backend.set_value)
|
|
302
302
|
|
|
303
303
|
|
|
304
|
-
def _generate_assert_error_msg(name: str, expected_result, actual_result) -> str:
|
|
305
|
-
WARNING = "\033[93m"
|
|
306
|
-
FAIL = "\033[91m"
|
|
307
|
-
ENDC = "\033[0m"
|
|
308
|
-
return (
|
|
309
|
-
f"Expected {WARNING}{name}{ENDC} to produce"
|
|
310
|
-
+ f"\n{FAIL}{expected_result}{ENDC}"
|
|
311
|
-
+ f"\nbut actually got \n{FAIL}{actual_result}{ENDC}"
|
|
312
|
-
)
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
async def assert_value(signal: SignalR[SignalDatatypeT], value: Any) -> None:
|
|
316
|
-
"""Assert a signal's value and compare it an expected signal.
|
|
317
|
-
|
|
318
|
-
Parameters
|
|
319
|
-
----------
|
|
320
|
-
signal:
|
|
321
|
-
signal with get_value.
|
|
322
|
-
value:
|
|
323
|
-
The expected value from the signal.
|
|
324
|
-
|
|
325
|
-
Notes
|
|
326
|
-
-----
|
|
327
|
-
Example usage::
|
|
328
|
-
await assert_value(signal, value)
|
|
329
|
-
|
|
330
|
-
"""
|
|
331
|
-
actual_value = await signal.get_value()
|
|
332
|
-
assert actual_value == value, _generate_assert_error_msg(
|
|
333
|
-
name=signal.name,
|
|
334
|
-
expected_result=value,
|
|
335
|
-
actual_result=actual_value,
|
|
336
|
-
)
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
async def assert_reading(
|
|
340
|
-
readable: AsyncReadable, expected_reading: Mapping[str, Reading]
|
|
341
|
-
) -> None:
|
|
342
|
-
"""Assert readings from readable.
|
|
343
|
-
|
|
344
|
-
Parameters
|
|
345
|
-
----------
|
|
346
|
-
readable:
|
|
347
|
-
Callable with readable.read function that generate readings.
|
|
348
|
-
|
|
349
|
-
reading:
|
|
350
|
-
The expected readings from the readable.
|
|
351
|
-
|
|
352
|
-
Notes
|
|
353
|
-
-----
|
|
354
|
-
Example usage::
|
|
355
|
-
await assert_reading(readable, reading)
|
|
356
|
-
|
|
357
|
-
"""
|
|
358
|
-
actual_reading = await readable.read()
|
|
359
|
-
assert expected_reading == actual_reading, _generate_assert_error_msg(
|
|
360
|
-
name=readable.name,
|
|
361
|
-
expected_result=expected_reading,
|
|
362
|
-
actual_result=actual_reading,
|
|
363
|
-
)
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
async def assert_configuration(
|
|
367
|
-
configurable: AsyncConfigurable,
|
|
368
|
-
configuration: Mapping[str, Reading],
|
|
369
|
-
) -> None:
|
|
370
|
-
"""Assert readings from Configurable.
|
|
371
|
-
|
|
372
|
-
Parameters
|
|
373
|
-
----------
|
|
374
|
-
configurable:
|
|
375
|
-
Configurable with Configurable.read function that generate readings.
|
|
376
|
-
|
|
377
|
-
configuration:
|
|
378
|
-
The expected readings from configurable.
|
|
379
|
-
|
|
380
|
-
Notes
|
|
381
|
-
-----
|
|
382
|
-
Example usage::
|
|
383
|
-
await assert_configuration(configurable configuration)
|
|
384
|
-
|
|
385
|
-
"""
|
|
386
|
-
actual_configurable = await configurable.read_configuration()
|
|
387
|
-
assert configuration == actual_configurable, _generate_assert_error_msg(
|
|
388
|
-
name=configurable.name,
|
|
389
|
-
expected_result=configuration,
|
|
390
|
-
actual_result=actual_configurable,
|
|
391
|
-
)
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
def assert_emitted(docs: Mapping[str, list[dict]], **numbers: int):
|
|
395
|
-
"""Assert emitted document generated by running a Bluesky plan
|
|
396
|
-
|
|
397
|
-
Parameters
|
|
398
|
-
----------
|
|
399
|
-
Doc:
|
|
400
|
-
A dictionary
|
|
401
|
-
|
|
402
|
-
numbers:
|
|
403
|
-
expected emission in kwarg from
|
|
404
|
-
|
|
405
|
-
Notes
|
|
406
|
-
-----
|
|
407
|
-
Example usage::
|
|
408
|
-
assert_emitted(docs, start=1, descriptor=1,
|
|
409
|
-
resource=1, datum=1, event=1, stop=1)
|
|
410
|
-
"""
|
|
411
|
-
assert list(docs) == list(numbers), _generate_assert_error_msg(
|
|
412
|
-
name="documents",
|
|
413
|
-
expected_result=list(numbers),
|
|
414
|
-
actual_result=list(docs),
|
|
415
|
-
)
|
|
416
|
-
actual_numbers = {name: len(d) for name, d in docs.items()}
|
|
417
|
-
assert actual_numbers == numbers, _generate_assert_error_msg(
|
|
418
|
-
name="emitted",
|
|
419
|
-
expected_result=numbers,
|
|
420
|
-
actual_result=actual_numbers,
|
|
421
|
-
)
|
|
422
|
-
|
|
423
|
-
|
|
424
304
|
async def observe_value(
|
|
425
305
|
signal: SignalR[SignalDatatypeT],
|
|
426
306
|
timeout: float | None = None,
|
|
427
307
|
done_status: Status | None = None,
|
|
308
|
+
done_timeout: float | None = None,
|
|
428
309
|
) -> AsyncGenerator[SignalDatatypeT, None]:
|
|
429
310
|
"""Subscribe to the value of a signal so it can be iterated from.
|
|
430
311
|
|
|
@@ -439,9 +320,17 @@ async def observe_value(
|
|
|
439
320
|
done_status:
|
|
440
321
|
If this status is complete, stop observing and make the iterator return.
|
|
441
322
|
If it raises an exception then this exception will be raised by the iterator.
|
|
323
|
+
done_timeout:
|
|
324
|
+
If given, the maximum time to watch a signal, in seconds. If the loop is still
|
|
325
|
+
being watched after this length, raise asyncio.TimeoutError. This should be used
|
|
326
|
+
instead of on an 'asyncio.wait_for' timeout
|
|
442
327
|
|
|
443
328
|
Notes
|
|
444
329
|
-----
|
|
330
|
+
Due to a rare condition with busy signals, it is not recommended to use this
|
|
331
|
+
function with asyncio.timeout, including in an 'asyncio.wait_for' loop. Instead,
|
|
332
|
+
this timeout should be given to the done_timeout parameter.
|
|
333
|
+
|
|
445
334
|
Example usage::
|
|
446
335
|
|
|
447
336
|
async for value in observe_value(sig):
|
|
@@ -449,15 +338,26 @@ async def observe_value(
|
|
|
449
338
|
"""
|
|
450
339
|
|
|
451
340
|
async for _, value in observe_signals_value(
|
|
452
|
-
signal,
|
|
341
|
+
signal,
|
|
342
|
+
timeout=timeout,
|
|
343
|
+
done_status=done_status,
|
|
344
|
+
done_timeout=done_timeout,
|
|
453
345
|
):
|
|
454
346
|
yield value
|
|
455
347
|
|
|
456
348
|
|
|
349
|
+
def _get_iteration_timeout(
|
|
350
|
+
timeout: float | None, overall_deadline: float | None
|
|
351
|
+
) -> float | None:
|
|
352
|
+
overall_deadline = overall_deadline - time.monotonic() if overall_deadline else None
|
|
353
|
+
return min([x for x in [overall_deadline, timeout] if x is not None], default=None)
|
|
354
|
+
|
|
355
|
+
|
|
457
356
|
async def observe_signals_value(
|
|
458
357
|
*signals: SignalR[SignalDatatypeT],
|
|
459
358
|
timeout: float | None = None,
|
|
460
359
|
done_status: Status | None = None,
|
|
360
|
+
done_timeout: float | None = None,
|
|
461
361
|
) -> AsyncGenerator[tuple[SignalR[SignalDatatypeT], SignalDatatypeT], None]:
|
|
462
362
|
"""Subscribe to the value of a signal so it can be iterated from.
|
|
463
363
|
|
|
@@ -472,6 +372,10 @@ async def observe_signals_value(
|
|
|
472
372
|
done_status:
|
|
473
373
|
If this status is complete, stop observing and make the iterator return.
|
|
474
374
|
If it raises an exception then this exception will be raised by the iterator.
|
|
375
|
+
done_timeout:
|
|
376
|
+
If given, the maximum time to watch a signal, in seconds. If the loop is still
|
|
377
|
+
being watched after this length, raise asyncio.TimeoutError. This should be used
|
|
378
|
+
instead of on an 'asyncio.wait_for' timeout
|
|
475
379
|
|
|
476
380
|
Notes
|
|
477
381
|
-----
|
|
@@ -486,12 +390,6 @@ async def observe_signals_value(
|
|
|
486
390
|
q: asyncio.Queue[tuple[SignalR[SignalDatatypeT], SignalDatatypeT] | Status] = (
|
|
487
391
|
asyncio.Queue()
|
|
488
392
|
)
|
|
489
|
-
if timeout is None:
|
|
490
|
-
get_value = q.get
|
|
491
|
-
else:
|
|
492
|
-
|
|
493
|
-
async def get_value():
|
|
494
|
-
return await asyncio.wait_for(q.get(), timeout)
|
|
495
393
|
|
|
496
394
|
cbs: dict[SignalR, Callback] = {}
|
|
497
395
|
for signal in signals:
|
|
@@ -504,13 +402,17 @@ async def observe_signals_value(
|
|
|
504
402
|
|
|
505
403
|
if done_status is not None:
|
|
506
404
|
done_status.add_callback(q.put_nowait)
|
|
507
|
-
|
|
405
|
+
overall_deadline = time.monotonic() + done_timeout if done_timeout else None
|
|
508
406
|
try:
|
|
509
407
|
while True:
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
408
|
+
if overall_deadline and time.monotonic() >= overall_deadline:
|
|
409
|
+
raise asyncio.TimeoutError(
|
|
410
|
+
f"observe_value was still observing signals "
|
|
411
|
+
f"{[signal.source for signal in signals]} after "
|
|
412
|
+
f"timeout {done_timeout}s"
|
|
413
|
+
)
|
|
414
|
+
iteration_timeout = _get_iteration_timeout(timeout, overall_deadline)
|
|
415
|
+
item = await asyncio.wait_for(q.get(), iteration_timeout)
|
|
514
416
|
if done_status and item is done_status:
|
|
515
417
|
if exc := done_status.exception():
|
|
516
418
|
raise exc
|
ophyd_async/core/_utils.py
CHANGED
|
@@ -17,11 +17,20 @@ DEFAULT_TIMEOUT = 10.0
|
|
|
17
17
|
ErrorText = str | Mapping[str, Exception]
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
class
|
|
20
|
+
class StrictEnumMeta(EnumMeta):
|
|
21
|
+
def __new__(metacls, *args, **kwargs):
|
|
22
|
+
ret = super().__new__(metacls, *args, **kwargs)
|
|
23
|
+
lowercase_names = [x.name for x in ret if not x.name.isupper()] # type: ignore
|
|
24
|
+
if lowercase_names:
|
|
25
|
+
raise TypeError(f"Names {lowercase_names} should be uppercase")
|
|
26
|
+
return ret
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class StrictEnum(str, Enum, metaclass=StrictEnumMeta):
|
|
21
30
|
"""All members should exist in the Backend, and there will be no extras"""
|
|
22
31
|
|
|
23
32
|
|
|
24
|
-
class SubsetEnumMeta(
|
|
33
|
+
class SubsetEnumMeta(StrictEnumMeta):
|
|
25
34
|
def __call__(self, value, *args, **kwargs): # type: ignore
|
|
26
35
|
if isinstance(value, str) and not isinstance(value, self):
|
|
27
36
|
return value
|
|
@@ -31,9 +31,9 @@ class AravisController(DetectorController):
|
|
|
31
31
|
|
|
32
32
|
async def prepare(self, trigger_info: TriggerInfo):
|
|
33
33
|
if trigger_info.total_number_of_triggers == 0:
|
|
34
|
-
image_mode = adcore.ImageMode.
|
|
34
|
+
image_mode = adcore.ImageMode.CONTINUOUS
|
|
35
35
|
else:
|
|
36
|
-
image_mode = adcore.ImageMode.
|
|
36
|
+
image_mode = adcore.ImageMode.MULTIPLE
|
|
37
37
|
if (exposure := trigger_info.livetime) is not None:
|
|
38
38
|
await self._drv.acquire_time.set(exposure)
|
|
39
39
|
|
|
@@ -58,9 +58,9 @@ class AravisController(DetectorController):
|
|
|
58
58
|
self, trigger: DetectorTrigger
|
|
59
59
|
) -> tuple[AravisTriggerMode, AravisTriggerSource]:
|
|
60
60
|
supported_trigger_types = (
|
|
61
|
-
DetectorTrigger.
|
|
62
|
-
DetectorTrigger.
|
|
63
|
-
DetectorTrigger.
|
|
61
|
+
DetectorTrigger.CONSTANT_GATE,
|
|
62
|
+
DetectorTrigger.EDGE_TRIGGER,
|
|
63
|
+
DetectorTrigger.INTERNAL,
|
|
64
64
|
)
|
|
65
65
|
if trigger not in supported_trigger_types:
|
|
66
66
|
raise ValueError(
|
|
@@ -68,10 +68,10 @@ class AravisController(DetectorController):
|
|
|
68
68
|
f"types: {supported_trigger_types} but was asked to "
|
|
69
69
|
f"use {trigger}"
|
|
70
70
|
)
|
|
71
|
-
if trigger == DetectorTrigger.
|
|
72
|
-
return AravisTriggerMode.
|
|
71
|
+
if trigger == DetectorTrigger.INTERNAL:
|
|
72
|
+
return AravisTriggerMode.OFF, AravisTriggerSource.FREERUN
|
|
73
73
|
else:
|
|
74
|
-
return (AravisTriggerMode.
|
|
74
|
+
return (AravisTriggerMode.ON, f"Line{self.gpio_number}") # type: ignore
|
|
75
75
|
|
|
76
76
|
async def disarm(self):
|
|
77
77
|
await adcore.stop_busy_record(self._drv.acquire, False, timeout=1)
|
|
@@ -6,8 +6,8 @@ from ophyd_async.epics.core import epics_signal_rw_rbv
|
|
|
6
6
|
class AravisTriggerMode(StrictEnum):
|
|
7
7
|
"""GigEVision GenICAM standard: on=externally triggered"""
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
ON = "On"
|
|
10
|
+
OFF = "Off"
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
"""A minimal set of TriggerSources that must be supported by the underlying record.
|
|
@@ -20,8 +20,8 @@ class AravisTriggerMode(StrictEnum):
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class AravisTriggerSource(SubsetEnum):
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
FREERUN = "Freerun"
|
|
24
|
+
LINE1 = "Line1"
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
class AravisDriverIO(adcore.ADBaseIO):
|
|
@@ -9,8 +9,8 @@ from ._utils import ADBaseDataType, FileWriteMode, ImageMode
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class Callback(StrictEnum):
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
ENABLE = "Enable"
|
|
13
|
+
DISABLE = "Disable"
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class NDArrayBaseIO(Device):
|
|
@@ -72,17 +72,17 @@ class DetectorState(StrictEnum):
|
|
|
72
72
|
See definition in ADApp/ADSrc/ADDriver.h in https://github.com/areaDetector/ADCore
|
|
73
73
|
"""
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
75
|
+
IDLE = "Idle"
|
|
76
|
+
ACQUIRE = "Acquire"
|
|
77
|
+
READOUT = "Readout"
|
|
78
|
+
CORRECT = "Correct"
|
|
79
|
+
SAVING = "Saving"
|
|
80
|
+
ABORTING = "Aborting"
|
|
81
|
+
ERROR = "Error"
|
|
82
|
+
WAITING = "Waiting"
|
|
83
|
+
INITIALIZING = "Initializing"
|
|
84
|
+
DISCONNECTED = "Disconnected"
|
|
85
|
+
ABORTED = "Aborted"
|
|
86
86
|
|
|
87
87
|
|
|
88
88
|
class ADBaseIO(NDArrayBaseIO):
|
|
@@ -99,14 +99,14 @@ class ADBaseIO(NDArrayBaseIO):
|
|
|
99
99
|
|
|
100
100
|
|
|
101
101
|
class Compression(StrictEnum):
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
102
|
+
NONE = "None"
|
|
103
|
+
NBIT = "N-bit"
|
|
104
|
+
SZIP = "szip"
|
|
105
|
+
ZLIB = "zlib"
|
|
106
|
+
BLOSC = "Blosc"
|
|
107
|
+
BSLZ4 = "BSLZ4"
|
|
108
|
+
LZ4 = "LZ4"
|
|
109
|
+
JPEG = "JPEG"
|
|
110
110
|
|
|
111
111
|
|
|
112
112
|
class NDFileHDFIO(NDPluginBaseIO):
|