ophyd-async 0.8.0a5__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 +4 -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 +111 -136
- ophyd_async/core/_table.py +9 -4
- 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 +6 -3
- 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 +10 -5
- ophyd_async/epics/eiger/_eiger_io.py +3 -3
- ophyd_async/epics/motor.py +8 -5
- ophyd_async/epics/testing/__init__.py +24 -0
- ophyd_async/epics/testing/_example_ioc.py +107 -0
- ophyd_async/epics/testing/_utils.py +78 -0
- ophyd_async/epics/testing/test_records.db +158 -0
- ophyd_async/epics/testing/test_records_pva.db +177 -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/__init__.py +0 -43
- ophyd_async/tango/{signal → core}/__init__.py +7 -2
- ophyd_async/tango/{base_devices → core}/_base_device.py +38 -64
- ophyd_async/tango/{signal → core}/_signal.py +13 -3
- ophyd_async/tango/{base_devices → core}/_tango_readable.py +3 -4
- ophyd_async/tango/{signal → core}/_tango_transport.py +1 -1
- ophyd_async/tango/demo/_counter.py +6 -7
- ophyd_async/tango/demo/_mover.py +8 -7
- 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.0a5.dist-info → ophyd_async-0.9.0a1.dist-info}/METADATA +49 -47
- ophyd_async-0.9.0a1.dist-info/RECORD +119 -0
- {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0a1.dist-info}/WHEEL +1 -1
- ophyd_async/tango/base_devices/__init__.py +0 -4
- ophyd_async-0.8.0a5.dist-info/RECORD +0 -112
- {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0a1.dist-info}/LICENSE +0 -0
- {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0a1.dist-info}/entry_points.txt +0 -0
- {ophyd_async-0.8.0a5.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,12 @@ 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
|
-
|
|
61
|
-
assert_emitted,
|
|
62
|
-
assert_reading,
|
|
63
|
-
assert_value,
|
|
51
|
+
observe_signals_value,
|
|
64
52
|
observe_value,
|
|
65
53
|
set_and_wait_for_other_value,
|
|
66
54
|
set_and_wait_for_value,
|
|
@@ -122,14 +110,6 @@ __all__ = [
|
|
|
122
110
|
"HDFFile",
|
|
123
111
|
"config_ophyd_async_logging",
|
|
124
112
|
"MockSignalBackend",
|
|
125
|
-
"callback_on_mock_put",
|
|
126
|
-
"get_mock",
|
|
127
|
-
"get_mock_put",
|
|
128
|
-
"mock_puts_blocked",
|
|
129
|
-
"reset_mock_put_calls",
|
|
130
|
-
"set_mock_put_proceeds",
|
|
131
|
-
"set_mock_value",
|
|
132
|
-
"set_mock_values",
|
|
133
113
|
"AsyncConfigurable",
|
|
134
114
|
"AsyncReadable",
|
|
135
115
|
"AsyncStageable",
|
|
@@ -149,15 +129,13 @@ __all__ = [
|
|
|
149
129
|
"StandardReadable",
|
|
150
130
|
"StandardReadableFormat",
|
|
151
131
|
"Signal",
|
|
132
|
+
"SignalConnector",
|
|
152
133
|
"SignalR",
|
|
153
134
|
"SignalRW",
|
|
154
135
|
"SignalW",
|
|
155
136
|
"SignalX",
|
|
156
|
-
"assert_configuration",
|
|
157
|
-
"assert_emitted",
|
|
158
|
-
"assert_reading",
|
|
159
|
-
"assert_value",
|
|
160
137
|
"observe_value",
|
|
138
|
+
"observe_signals_value",
|
|
161
139
|
"set_and_wait_for_value",
|
|
162
140
|
"set_and_wait_for_other_value",
|
|
163
141
|
"soft_signal_r_and_setter",
|
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,
|
|
@@ -28,7 +28,7 @@ from ._signal_backend import (
|
|
|
28
28
|
SignalDatatypeV,
|
|
29
29
|
)
|
|
30
30
|
from ._soft_signal_backend import SoftSignalBackend
|
|
31
|
-
from ._status import AsyncStatus
|
|
31
|
+
from ._status import AsyncStatus, completed_status
|
|
32
32
|
from ._utils import (
|
|
33
33
|
CALCULATE_TIMEOUT,
|
|
34
34
|
DEFAULT_TIMEOUT,
|
|
@@ -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,137 +301,70 @@ def soft_signal_r_and_setter(
|
|
|
301
301
|
return (signal, backend.set_value)
|
|
302
302
|
|
|
303
303
|
|
|
304
|
-
def
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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.
|
|
304
|
+
async def observe_value(
|
|
305
|
+
signal: SignalR[SignalDatatypeT],
|
|
306
|
+
timeout: float | None = None,
|
|
307
|
+
done_status: Status | None = None,
|
|
308
|
+
done_timeout: float | None = None,
|
|
309
|
+
) -> AsyncGenerator[SignalDatatypeT, None]:
|
|
310
|
+
"""Subscribe to the value of a signal so it can be iterated from.
|
|
317
311
|
|
|
318
312
|
Parameters
|
|
319
313
|
----------
|
|
320
314
|
signal:
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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.
|
|
315
|
+
Call subscribe_value on this at the start, and clear_sub on it at the
|
|
316
|
+
end
|
|
317
|
+
timeout:
|
|
318
|
+
If given, how long to wait for each updated value in seconds. If an update
|
|
319
|
+
is not produced in this time then raise asyncio.TimeoutError
|
|
320
|
+
done_status:
|
|
321
|
+
If this status is complete, stop observing and make the iterator return.
|
|
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
|
|
351
327
|
|
|
352
328
|
Notes
|
|
353
329
|
-----
|
|
354
|
-
|
|
355
|
-
|
|
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.
|
|
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.
|
|
371
333
|
|
|
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
334
|
Example usage::
|
|
383
|
-
await assert_configuration(configurable configuration)
|
|
384
335
|
|
|
336
|
+
async for value in observe_value(sig):
|
|
337
|
+
do_something_with(value)
|
|
385
338
|
"""
|
|
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
339
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
340
|
+
async for _, value in observe_signals_value(
|
|
341
|
+
signal,
|
|
342
|
+
timeout=timeout,
|
|
343
|
+
done_status=done_status,
|
|
344
|
+
done_timeout=done_timeout,
|
|
345
|
+
):
|
|
346
|
+
yield value
|
|
401
347
|
|
|
402
|
-
numbers:
|
|
403
|
-
expected emission in kwarg from
|
|
404
348
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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
|
-
)
|
|
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)
|
|
422
354
|
|
|
423
355
|
|
|
424
|
-
async def
|
|
425
|
-
|
|
356
|
+
async def observe_signals_value(
|
|
357
|
+
*signals: SignalR[SignalDatatypeT],
|
|
426
358
|
timeout: float | None = None,
|
|
427
359
|
done_status: Status | None = None,
|
|
428
|
-
|
|
360
|
+
done_timeout: float | None = None,
|
|
361
|
+
) -> AsyncGenerator[tuple[SignalR[SignalDatatypeT], SignalDatatypeT], None]:
|
|
429
362
|
"""Subscribe to the value of a signal so it can be iterated from.
|
|
430
363
|
|
|
431
364
|
Parameters
|
|
432
365
|
----------
|
|
433
|
-
|
|
434
|
-
Call subscribe_value on
|
|
366
|
+
signals:
|
|
367
|
+
Call subscribe_value on all the signals at the start, and clear_sub on it at the
|
|
435
368
|
end
|
|
436
369
|
timeout:
|
|
437
370
|
If given, how long to wait for each updated value in seconds. If an update
|
|
@@ -439,36 +372,57 @@ async def observe_value(
|
|
|
439
372
|
done_status:
|
|
440
373
|
If this status is complete, stop observing and make the iterator return.
|
|
441
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
|
|
442
379
|
|
|
443
380
|
Notes
|
|
444
381
|
-----
|
|
445
382
|
Example usage::
|
|
446
383
|
|
|
447
|
-
async for value in
|
|
448
|
-
|
|
384
|
+
async for signal,value in observe_signals_values(sig1,sig2,..):
|
|
385
|
+
if signal is sig1:
|
|
386
|
+
do_something_with(value)
|
|
387
|
+
elif signal is sig2:
|
|
388
|
+
do_something_else_with(value)
|
|
449
389
|
"""
|
|
390
|
+
q: asyncio.Queue[tuple[SignalR[SignalDatatypeT], SignalDatatypeT] | Status] = (
|
|
391
|
+
asyncio.Queue()
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
cbs: dict[SignalR, Callback] = {}
|
|
395
|
+
for signal in signals:
|
|
450
396
|
|
|
451
|
-
|
|
397
|
+
def queue_value(value: SignalDatatypeT, signal=signal):
|
|
398
|
+
q.put_nowait((signal, value))
|
|
399
|
+
|
|
400
|
+
cbs[signal] = queue_value
|
|
401
|
+
signal.subscribe_value(queue_value)
|
|
452
402
|
|
|
453
403
|
if done_status is not None:
|
|
454
404
|
done_status.add_callback(q.put_nowait)
|
|
455
|
-
|
|
456
|
-
signal.subscribe_value(q.put_nowait)
|
|
405
|
+
overall_deadline = time.monotonic() + done_timeout if done_timeout else None
|
|
457
406
|
try:
|
|
458
407
|
while True:
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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)
|
|
463
416
|
if done_status and item is done_status:
|
|
464
417
|
if exc := done_status.exception():
|
|
465
418
|
raise exc
|
|
466
419
|
else:
|
|
467
420
|
break
|
|
468
421
|
else:
|
|
469
|
-
yield cast(SignalDatatypeT, item)
|
|
422
|
+
yield cast(tuple[SignalR[SignalDatatypeT], SignalDatatypeT], item)
|
|
470
423
|
finally:
|
|
471
|
-
signal.
|
|
424
|
+
for signal, cb in cbs.items():
|
|
425
|
+
signal.clear_sub(cb)
|
|
472
426
|
|
|
473
427
|
|
|
474
428
|
class _ValueChecker(Generic[SignalDatatypeT]):
|
|
@@ -533,15 +487,16 @@ async def wait_for_value(
|
|
|
533
487
|
async def set_and_wait_for_other_value(
|
|
534
488
|
set_signal: SignalW[SignalDatatypeT],
|
|
535
489
|
set_value: SignalDatatypeT,
|
|
536
|
-
|
|
537
|
-
|
|
490
|
+
match_signal: SignalR[SignalDatatypeV],
|
|
491
|
+
match_value: SignalDatatypeV | Callable[[SignalDatatypeV], bool],
|
|
538
492
|
timeout: float = DEFAULT_TIMEOUT,
|
|
539
493
|
set_timeout: float | None = None,
|
|
494
|
+
wait_for_set_completion: bool = True,
|
|
540
495
|
) -> AsyncStatus:
|
|
541
496
|
"""Set a signal and monitor another signal until it has the specified value.
|
|
542
497
|
|
|
543
498
|
This function sets a set_signal to a specified set_value and waits for
|
|
544
|
-
a
|
|
499
|
+
a match_signal to have the match_value.
|
|
545
500
|
|
|
546
501
|
Parameters
|
|
547
502
|
----------
|
|
@@ -549,14 +504,16 @@ async def set_and_wait_for_other_value(
|
|
|
549
504
|
The signal to set
|
|
550
505
|
set_value:
|
|
551
506
|
The value to set it to
|
|
552
|
-
|
|
507
|
+
match_signal:
|
|
553
508
|
The signal to monitor
|
|
554
|
-
|
|
509
|
+
match_value:
|
|
555
510
|
The value to wait for
|
|
556
511
|
timeout:
|
|
557
512
|
How long to wait for the signal to have the value
|
|
558
513
|
set_timeout:
|
|
559
514
|
How long to wait for the set to complete
|
|
515
|
+
wait_for_set_completion:
|
|
516
|
+
This will wait for set completion #More info in how-to docs
|
|
560
517
|
|
|
561
518
|
Notes
|
|
562
519
|
-----
|
|
@@ -565,7 +522,7 @@ async def set_and_wait_for_other_value(
|
|
|
565
522
|
set_and_wait_for_value(device.acquire, 1, device.acquire_rbv, 1)
|
|
566
523
|
"""
|
|
567
524
|
# Start monitoring before the set to avoid a race condition
|
|
568
|
-
values_gen = observe_value(
|
|
525
|
+
values_gen = observe_value(match_signal)
|
|
569
526
|
|
|
570
527
|
# Get the initial value from the monitor to make sure we've created it
|
|
571
528
|
current_value = await anext(values_gen)
|
|
@@ -573,28 +530,33 @@ async def set_and_wait_for_other_value(
|
|
|
573
530
|
status = set_signal.set(set_value, timeout=set_timeout)
|
|
574
531
|
|
|
575
532
|
# If the value was the same as before no need to wait for it to change
|
|
576
|
-
if current_value !=
|
|
533
|
+
if current_value != match_value:
|
|
577
534
|
|
|
578
535
|
async def _wait_for_value():
|
|
579
536
|
async for value in values_gen:
|
|
580
|
-
if value ==
|
|
537
|
+
if value == match_value:
|
|
581
538
|
break
|
|
582
539
|
|
|
583
540
|
try:
|
|
584
541
|
await asyncio.wait_for(_wait_for_value(), timeout)
|
|
542
|
+
if wait_for_set_completion:
|
|
543
|
+
await status
|
|
544
|
+
return status
|
|
585
545
|
except asyncio.TimeoutError as e:
|
|
586
546
|
raise TimeoutError(
|
|
587
|
-
f"{
|
|
547
|
+
f"{match_signal.name} didn't match {match_value} in {timeout}s"
|
|
588
548
|
) from e
|
|
589
549
|
|
|
590
|
-
return
|
|
550
|
+
return completed_status()
|
|
591
551
|
|
|
592
552
|
|
|
593
553
|
async def set_and_wait_for_value(
|
|
594
554
|
signal: SignalRW[SignalDatatypeT],
|
|
595
555
|
value: SignalDatatypeT,
|
|
556
|
+
match_value: SignalDatatypeT | Callable[[SignalDatatypeT], bool] | None = None,
|
|
596
557
|
timeout: float = DEFAULT_TIMEOUT,
|
|
597
558
|
status_timeout: float | None = None,
|
|
559
|
+
wait_for_set_completion: bool = True,
|
|
598
560
|
) -> AsyncStatus:
|
|
599
561
|
"""Set a signal and monitor it until it has that value.
|
|
600
562
|
|
|
@@ -609,10 +571,15 @@ async def set_and_wait_for_value(
|
|
|
609
571
|
The signal to set
|
|
610
572
|
value:
|
|
611
573
|
The value to set it to
|
|
574
|
+
match_value:
|
|
575
|
+
The expected value of the signal after the operation.
|
|
576
|
+
Used to verify that the set operation was successful.
|
|
612
577
|
timeout:
|
|
613
578
|
How long to wait for the signal to have the value
|
|
614
579
|
status_timeout:
|
|
615
580
|
How long the returned Status will wait for the set to complete
|
|
581
|
+
wait_for_set_completion:
|
|
582
|
+
This will wait for set completion #More info in how-to docs
|
|
616
583
|
|
|
617
584
|
Notes
|
|
618
585
|
-----
|
|
@@ -620,6 +587,14 @@ async def set_and_wait_for_value(
|
|
|
620
587
|
|
|
621
588
|
set_and_wait_for_value(device.acquire, 1)
|
|
622
589
|
"""
|
|
590
|
+
if match_value is None:
|
|
591
|
+
match_value = value
|
|
623
592
|
return await set_and_wait_for_other_value(
|
|
624
|
-
signal,
|
|
593
|
+
signal,
|
|
594
|
+
value,
|
|
595
|
+
signal,
|
|
596
|
+
match_value,
|
|
597
|
+
timeout,
|
|
598
|
+
status_timeout,
|
|
599
|
+
wait_for_set_completion,
|
|
625
600
|
)
|