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
ophyd_async/core/_signal.py
CHANGED
|
@@ -2,14 +2,17 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import functools
|
|
5
|
+
import inspect
|
|
5
6
|
import time
|
|
6
7
|
from collections.abc import AsyncGenerator, Awaitable, Callable
|
|
7
|
-
from typing import Any, Generic, cast
|
|
8
|
+
from typing import Any, Generic, TypeVar, cast
|
|
8
9
|
|
|
9
10
|
from bluesky.protocols import (
|
|
11
|
+
Configurable,
|
|
10
12
|
Locatable,
|
|
11
13
|
Location,
|
|
12
14
|
Movable,
|
|
15
|
+
Reading,
|
|
13
16
|
Status,
|
|
14
17
|
Subscribable,
|
|
15
18
|
)
|
|
@@ -17,18 +20,10 @@ from event_model import DataKey
|
|
|
17
20
|
|
|
18
21
|
from ._device import Device, DeviceConnector
|
|
19
22
|
from ._mock_signal_backend import MockSignalBackend
|
|
20
|
-
from ._protocol import
|
|
21
|
-
|
|
22
|
-
AsyncStageable,
|
|
23
|
-
Reading,
|
|
24
|
-
)
|
|
25
|
-
from ._signal_backend import (
|
|
26
|
-
SignalBackend,
|
|
27
|
-
SignalDatatypeT,
|
|
28
|
-
SignalDatatypeV,
|
|
29
|
-
)
|
|
23
|
+
from ._protocol import AsyncReadable, AsyncStageable
|
|
24
|
+
from ._signal_backend import SignalBackend, SignalDatatypeT, SignalDatatypeV
|
|
30
25
|
from ._soft_signal_backend import SoftSignalBackend
|
|
31
|
-
from ._status import AsyncStatus
|
|
26
|
+
from ._status import AsyncStatus
|
|
32
27
|
from ._utils import (
|
|
33
28
|
CALCULATE_TIMEOUT,
|
|
34
29
|
DEFAULT_TIMEOUT,
|
|
@@ -55,6 +50,8 @@ def _add_timeout(func):
|
|
|
55
50
|
|
|
56
51
|
|
|
57
52
|
class SignalConnector(DeviceConnector):
|
|
53
|
+
"""Used for connecting signals with a given backend."""
|
|
54
|
+
|
|
58
55
|
def __init__(self, backend: SignalBackend):
|
|
59
56
|
self.backend = self._init_backend = backend
|
|
60
57
|
|
|
@@ -69,14 +66,19 @@ class SignalConnector(DeviceConnector):
|
|
|
69
66
|
|
|
70
67
|
class _ChildrenNotAllowed(dict[str, Device]):
|
|
71
68
|
def __setitem__(self, key: str, value: Device) -> None:
|
|
72
|
-
raise
|
|
69
|
+
raise KeyError(
|
|
73
70
|
f"Cannot add Device or Signal child {key}={value} of Signal, "
|
|
74
71
|
"make a subclass of Device instead"
|
|
75
72
|
)
|
|
76
73
|
|
|
77
74
|
|
|
78
75
|
class Signal(Device, Generic[SignalDatatypeT]):
|
|
79
|
-
"""A Device with the concept of a value, with R, RW, W and X flavours
|
|
76
|
+
"""A Device with the concept of a value, with R, RW, W and X flavours.
|
|
77
|
+
|
|
78
|
+
:param backend: The backend for providing Signal values.
|
|
79
|
+
:param timeout: The default timeout for operations on the Signal.
|
|
80
|
+
:param name: The name of the signal.
|
|
81
|
+
"""
|
|
80
82
|
|
|
81
83
|
_connector: SignalConnector
|
|
82
84
|
_child_devices = _ChildrenNotAllowed() # type: ignore
|
|
@@ -92,10 +94,16 @@ class Signal(Device, Generic[SignalDatatypeT]):
|
|
|
92
94
|
|
|
93
95
|
@property
|
|
94
96
|
def source(self) -> str:
|
|
95
|
-
"""
|
|
97
|
+
"""Returns the source of the signal.
|
|
98
|
+
|
|
99
|
+
E.g. "ca://PV_PREFIX:SIGNAL", or "" if not available until connection.
|
|
100
|
+
"""
|
|
96
101
|
return self._connector.backend.source(self.name, read=True)
|
|
97
102
|
|
|
98
103
|
|
|
104
|
+
SignalT = TypeVar("SignalT", bound=Signal)
|
|
105
|
+
|
|
106
|
+
|
|
99
107
|
class _SignalCache(Generic[SignalDatatypeT]):
|
|
100
108
|
def __init__(self, backend: SignalBackend[SignalDatatypeT], signal: Signal) -> None:
|
|
101
109
|
self._signal: Signal[Any] = signal
|
|
@@ -159,7 +167,7 @@ class _SignalCache(Generic[SignalDatatypeT]):
|
|
|
159
167
|
|
|
160
168
|
|
|
161
169
|
class SignalR(Signal[SignalDatatypeT], AsyncReadable, AsyncStageable, Subscribable):
|
|
162
|
-
"""Signal that can be read from and monitored"""
|
|
170
|
+
"""Signal that can be read from and monitored."""
|
|
163
171
|
|
|
164
172
|
_cache: _SignalCache | None = None
|
|
165
173
|
|
|
@@ -190,46 +198,71 @@ class SignalR(Signal[SignalDatatypeT], AsyncReadable, AsyncStageable, Subscribab
|
|
|
190
198
|
|
|
191
199
|
@_add_timeout
|
|
192
200
|
async def read(self, cached: bool | None = None) -> dict[str, Reading]:
|
|
193
|
-
"""Return a single item dict with the reading in it
|
|
201
|
+
"""Return a single item dict with the reading in it.
|
|
202
|
+
|
|
203
|
+
:param cached:
|
|
204
|
+
Whether to use the cached monitored value:
|
|
205
|
+
- If None, use the cache if it exists.
|
|
206
|
+
- If False, do an explicit get.
|
|
207
|
+
- If True, explicitly use the cache and raise an error if it doesn't exist.
|
|
208
|
+
"""
|
|
194
209
|
return {self.name: await self._backend_or_cache(cached).get_reading()}
|
|
195
210
|
|
|
196
211
|
@_add_timeout
|
|
197
212
|
async def describe(self) -> dict[str, DataKey]:
|
|
198
|
-
"""Return a single item dict
|
|
213
|
+
"""Return a single item dict describing the signal value."""
|
|
199
214
|
return {self.name: await self._connector.backend.get_datakey(self.source)}
|
|
200
215
|
|
|
201
216
|
@_add_timeout
|
|
202
217
|
async def get_value(self, cached: bool | None = None) -> SignalDatatypeT:
|
|
203
|
-
"""
|
|
218
|
+
"""Return the current value.
|
|
219
|
+
|
|
220
|
+
:param cached:
|
|
221
|
+
Whether to use the cached monitored value:
|
|
222
|
+
- If None, use the cache if it exists.
|
|
223
|
+
- If False, do an explicit get.
|
|
224
|
+
- If True, explicitly use the cache and raise an error if it doesn't exist.
|
|
225
|
+
"""
|
|
204
226
|
value = await self._backend_or_cache(cached).get_value()
|
|
205
227
|
self.log.debug(f"get_value() on source {self.source} returned {value}")
|
|
206
228
|
return value
|
|
207
229
|
|
|
208
230
|
def subscribe_value(self, function: Callback[SignalDatatypeT]):
|
|
209
|
-
"""Subscribe to updates in value of a device
|
|
231
|
+
"""Subscribe to updates in value of a device.
|
|
232
|
+
|
|
233
|
+
:param function: The callback function to call when the value changes.
|
|
234
|
+
"""
|
|
210
235
|
self._get_cache().subscribe(function, want_value=True)
|
|
211
236
|
|
|
212
|
-
def subscribe(
|
|
213
|
-
|
|
237
|
+
def subscribe(
|
|
238
|
+
self, function: Callback[dict[str, Reading[SignalDatatypeT]]]
|
|
239
|
+
) -> None:
|
|
240
|
+
"""Subscribe to updates in the reading.
|
|
241
|
+
|
|
242
|
+
:param function: The callback function to call when the reading changes.
|
|
243
|
+
"""
|
|
214
244
|
self._get_cache().subscribe(function, want_value=False)
|
|
215
245
|
|
|
216
246
|
def clear_sub(self, function: Callback) -> None:
|
|
217
|
-
"""Remove a subscription
|
|
247
|
+
"""Remove a subscription passed to `subscribe` or `subscribe_value`.
|
|
248
|
+
|
|
249
|
+
:param function: The callback function to remove.
|
|
250
|
+
"""
|
|
218
251
|
self._del_cache(self._get_cache().unsubscribe(function))
|
|
219
252
|
|
|
220
253
|
@AsyncStatus.wrap
|
|
221
254
|
async def stage(self) -> None:
|
|
222
|
-
"""Start caching this signal"""
|
|
255
|
+
"""Start caching this signal."""
|
|
223
256
|
self._get_cache().set_staged(True)
|
|
224
257
|
|
|
225
258
|
@AsyncStatus.wrap
|
|
226
259
|
async def unstage(self) -> None:
|
|
227
|
-
"""Stop caching this signal"""
|
|
260
|
+
"""Stop caching this signal."""
|
|
228
261
|
self._del_cache(self._get_cache().set_staged(False))
|
|
229
262
|
|
|
230
263
|
|
|
231
264
|
class SignalW(Signal[SignalDatatypeT], Movable):
|
|
232
|
-
"""Signal that can be set"""
|
|
265
|
+
"""Signal that can be set."""
|
|
233
266
|
|
|
234
267
|
@AsyncStatus.wrap
|
|
235
268
|
async def set(
|
|
@@ -238,7 +271,12 @@ class SignalW(Signal[SignalDatatypeT], Movable):
|
|
|
238
271
|
wait=True,
|
|
239
272
|
timeout: CalculatableTimeout = CALCULATE_TIMEOUT,
|
|
240
273
|
) -> None:
|
|
241
|
-
"""Set the value and return a status saying when it's done
|
|
274
|
+
"""Set the value and return a status saying when it's done.
|
|
275
|
+
|
|
276
|
+
:param value: The value to set.
|
|
277
|
+
:param wait: If True, wait for the set to complete.
|
|
278
|
+
:param timeout: The timeout for the set.
|
|
279
|
+
"""
|
|
242
280
|
if timeout == CALCULATE_TIMEOUT:
|
|
243
281
|
timeout = self._timeout
|
|
244
282
|
source = self._connector.backend.source(self.name, read=False)
|
|
@@ -248,7 +286,7 @@ class SignalW(Signal[SignalDatatypeT], Movable):
|
|
|
248
286
|
|
|
249
287
|
|
|
250
288
|
class SignalRW(SignalR[SignalDatatypeT], SignalW[SignalDatatypeT], Locatable):
|
|
251
|
-
"""Signal that can be both read and set"""
|
|
289
|
+
"""Signal that can be both read and set."""
|
|
252
290
|
|
|
253
291
|
@_add_timeout
|
|
254
292
|
async def locate(self) -> Location:
|
|
@@ -260,13 +298,17 @@ class SignalRW(SignalR[SignalDatatypeT], SignalW[SignalDatatypeT], Locatable):
|
|
|
260
298
|
|
|
261
299
|
|
|
262
300
|
class SignalX(Signal):
|
|
263
|
-
"""Signal that puts the default value"""
|
|
301
|
+
"""Signal that puts the default value."""
|
|
264
302
|
|
|
265
303
|
@AsyncStatus.wrap
|
|
266
304
|
async def trigger(
|
|
267
305
|
self, wait=True, timeout: CalculatableTimeout = CALCULATE_TIMEOUT
|
|
268
306
|
) -> None:
|
|
269
|
-
"""Trigger the action and return a status saying when it's done
|
|
307
|
+
"""Trigger the action and return a status saying when it's done.
|
|
308
|
+
|
|
309
|
+
:param wait: If True, wait for the trigger to complete.
|
|
310
|
+
:param timeout: The timeout for the trigger.
|
|
311
|
+
"""
|
|
270
312
|
if timeout == CALCULATE_TIMEOUT:
|
|
271
313
|
timeout = self._timeout
|
|
272
314
|
source = self._connector.backend.source(self.name, read=False)
|
|
@@ -282,8 +324,15 @@ def soft_signal_rw(
|
|
|
282
324
|
units: str | None = None,
|
|
283
325
|
precision: int | None = None,
|
|
284
326
|
) -> SignalRW[SignalDatatypeT]:
|
|
285
|
-
"""
|
|
327
|
+
"""Create a read-writable Signal with a [](#SoftSignalBackend).
|
|
328
|
+
|
|
286
329
|
May pass metadata, which are propagated into describe.
|
|
330
|
+
|
|
331
|
+
:param datatype: The datatype of the signal.
|
|
332
|
+
:param initial_value: The initial value of the signal.
|
|
333
|
+
:param name: The name of the signal.
|
|
334
|
+
:param units: The units of the signal.
|
|
335
|
+
:param precision: The precision of the signal.
|
|
287
336
|
"""
|
|
288
337
|
backend = SoftSignalBackend(datatype, initial_value, units, precision)
|
|
289
338
|
signal = SignalRW(backend=backend, name=name)
|
|
@@ -297,10 +346,17 @@ def soft_signal_r_and_setter(
|
|
|
297
346
|
units: str | None = None,
|
|
298
347
|
precision: int | None = None,
|
|
299
348
|
) -> tuple[SignalR[SignalDatatypeT], Callable[[SignalDatatypeT], None]]:
|
|
300
|
-
"""
|
|
301
|
-
|
|
349
|
+
"""Create a read-only Signal with a [](#SoftSignalBackend).
|
|
350
|
+
|
|
302
351
|
May pass metadata, which are propagated into describe.
|
|
303
|
-
Use soft_signal_rw if you want a device that is externally modifiable
|
|
352
|
+
Use soft_signal_rw if you want a device that is externally modifiable.
|
|
353
|
+
|
|
354
|
+
:param datatype: The datatype of the signal.
|
|
355
|
+
:param initial_value: The initial value of the signal.
|
|
356
|
+
:param name: The name of the signal.
|
|
357
|
+
:param units: The units of the signal.
|
|
358
|
+
:param precision: The precision of the signal.
|
|
359
|
+
:return: A tuple of the created SignalR and a callable to set its value.
|
|
304
360
|
"""
|
|
305
361
|
backend = SoftSignalBackend(datatype, initial_value, units, precision)
|
|
306
362
|
signal = SignalR(backend=backend, name=name)
|
|
@@ -315,34 +371,34 @@ async def observe_value(
|
|
|
315
371
|
) -> AsyncGenerator[SignalDatatypeT, None]:
|
|
316
372
|
"""Subscribe to the value of a signal so it can be iterated from.
|
|
317
373
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
Call subscribe_value on this at the start, and clear_sub on it at the
|
|
322
|
-
end
|
|
323
|
-
timeout:
|
|
324
|
-
If given, how long to wait for each updated value in seconds. If an update
|
|
325
|
-
is not produced in this time then raise asyncio.TimeoutError
|
|
326
|
-
done_status:
|
|
327
|
-
If this status is complete, stop observing and make the iterator return.
|
|
328
|
-
If it raises an exception then this exception will be raised by the iterator.
|
|
329
|
-
done_timeout:
|
|
330
|
-
If given, the maximum time to watch a signal, in seconds. If the loop is still
|
|
331
|
-
being watched after this length, raise asyncio.TimeoutError. This should be used
|
|
332
|
-
instead of on an 'asyncio.wait_for' timeout
|
|
333
|
-
|
|
334
|
-
Notes
|
|
335
|
-
-----
|
|
336
|
-
Due to a rare condition with busy signals, it is not recommended to use this
|
|
337
|
-
function with asyncio.timeout, including in an 'asyncio.wait_for' loop. Instead,
|
|
338
|
-
this timeout should be given to the done_timeout parameter.
|
|
374
|
+
The first value yielded in the iterator will be the current value of the
|
|
375
|
+
Signal, and subsequent updates from the control system will result in that
|
|
376
|
+
value being yielded, even if it is the same as the previous value.
|
|
339
377
|
|
|
340
|
-
|
|
378
|
+
:param signal:
|
|
379
|
+
Call subscribe_value on this at the start, and clear_sub on it at the end.
|
|
380
|
+
:param timeout:
|
|
381
|
+
If given, how long to wait for each updated value in seconds. If an
|
|
382
|
+
update is not produced in this time then raise asyncio.TimeoutError.
|
|
383
|
+
:param done_status:
|
|
384
|
+
If this status is complete, stop observing and make the iterator return.
|
|
385
|
+
If it raises an exception then this exception will be raised by the
|
|
386
|
+
iterator.
|
|
387
|
+
:param done_timeout:
|
|
388
|
+
If given, the maximum time to watch a signal, in seconds. If the loop is
|
|
389
|
+
still being watched after this length, raise asyncio.TimeoutError. This
|
|
390
|
+
should be used instead of on an 'asyncio.wait_for' timeout.
|
|
341
391
|
|
|
342
|
-
|
|
343
|
-
|
|
392
|
+
Due to a rare condition with busy signals, it is not recommended to use this
|
|
393
|
+
function with asyncio.timeout, including in an `asyncio.wait_for` loop.
|
|
394
|
+
Instead, this timeout should be given to the done_timeout parameter.
|
|
395
|
+
|
|
396
|
+
:example:
|
|
397
|
+
```python
|
|
398
|
+
async for value in observe_value(sig):
|
|
399
|
+
do_something_with(value)
|
|
400
|
+
```
|
|
344
401
|
"""
|
|
345
|
-
|
|
346
402
|
async for _, value in observe_signals_value(
|
|
347
403
|
signal,
|
|
348
404
|
timeout=timeout,
|
|
@@ -365,33 +421,35 @@ async def observe_signals_value(
|
|
|
365
421
|
done_status: Status | None = None,
|
|
366
422
|
done_timeout: float | None = None,
|
|
367
423
|
) -> AsyncGenerator[tuple[SignalR[SignalDatatypeT], SignalDatatypeT], None]:
|
|
368
|
-
"""Subscribe to
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
424
|
+
"""Subscribe to a set of signals so they can be iterated from.
|
|
425
|
+
|
|
426
|
+
The first values yielded in the iterator will be the current values of the
|
|
427
|
+
Signals, and subsequent updates from the control system will result in that
|
|
428
|
+
value being yielded, even if it is the same as the previous value.
|
|
429
|
+
|
|
430
|
+
:param signals:
|
|
431
|
+
Call subscribe_value on all the signals at the start, and clear_sub on
|
|
432
|
+
it at the end.
|
|
433
|
+
:param timeout:
|
|
434
|
+
If given, how long to wait for each updated value in seconds. If an
|
|
435
|
+
update is not produced in this time then raise asyncio.TimeoutError.
|
|
436
|
+
:param done_status:
|
|
379
437
|
If this status is complete, stop observing and make the iterator return.
|
|
380
|
-
If it raises an exception then this exception will be raised by the
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
438
|
+
If it raises an exception then this exception will be raised by the
|
|
439
|
+
iterator.
|
|
440
|
+
:param done_timeout:
|
|
441
|
+
If given, the maximum time to watch a signal, in seconds. If the loop is
|
|
442
|
+
still being watched after this length, raise asyncio.TimeoutError. This
|
|
443
|
+
should be used instead of on an `asyncio.wait_for` timeout.
|
|
444
|
+
|
|
445
|
+
:example:
|
|
446
|
+
```python
|
|
447
|
+
async for signal, value in observe_signals_values(sig1, sig2, ..):
|
|
448
|
+
if signal is sig1:
|
|
449
|
+
do_something_with(value)
|
|
450
|
+
elif signal is sig2:
|
|
451
|
+
do_something_else_with(value)
|
|
452
|
+
```
|
|
395
453
|
"""
|
|
396
454
|
q: asyncio.Queue[tuple[SignalR[SignalDatatypeT], SignalDatatypeT] | Status] = (
|
|
397
455
|
asyncio.Queue()
|
|
@@ -459,29 +517,23 @@ async def wait_for_value(
|
|
|
459
517
|
signal: SignalR[SignalDatatypeT],
|
|
460
518
|
match: SignalDatatypeT | Callable[[SignalDatatypeT], bool],
|
|
461
519
|
timeout: float | None,
|
|
462
|
-
):
|
|
520
|
+
) -> None:
|
|
463
521
|
"""Wait for a signal to have a matching value.
|
|
464
522
|
|
|
465
|
-
|
|
466
|
-
----------
|
|
467
|
-
signal:
|
|
523
|
+
:param signal:
|
|
468
524
|
Call subscribe_value on this at the start, and clear_sub on it at the
|
|
469
|
-
end
|
|
470
|
-
match:
|
|
525
|
+
end.
|
|
526
|
+
:param match:
|
|
471
527
|
If a callable, it should return True if the value matches. If not
|
|
472
528
|
callable then value will be checked for equality with match.
|
|
473
|
-
timeout:
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
Or::
|
|
483
|
-
|
|
484
|
-
wait_for_value(device.num_captured, lambda v: v > 45, timeout=1)
|
|
529
|
+
:param timeout: How long to wait for the value to match.
|
|
530
|
+
|
|
531
|
+
:example:
|
|
532
|
+
```python
|
|
533
|
+
await wait_for_value(device.acquiring, 1, timeout=1)
|
|
534
|
+
# or
|
|
535
|
+
await wait_for_value(device.num_captured, lambda v: v > 45, timeout=1)
|
|
536
|
+
```
|
|
485
537
|
"""
|
|
486
538
|
if callable(match):
|
|
487
539
|
checker = _ValueChecker(match, match.__name__) # type: ignore
|
|
@@ -504,28 +556,25 @@ async def set_and_wait_for_other_value(
|
|
|
504
556
|
This function sets a set_signal to a specified set_value and waits for
|
|
505
557
|
a match_signal to have the match_value.
|
|
506
558
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
signal
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
Example usage::
|
|
527
|
-
|
|
528
|
-
set_and_wait_for_value(device.acquire, 1, device.acquire_rbv, 1)
|
|
559
|
+
:param set_signal: The signal to set.
|
|
560
|
+
:param set_value: The value to set it to.
|
|
561
|
+
:param match_signal: The signal to monitor.
|
|
562
|
+
:param match_value:
|
|
563
|
+
The value (or callable that says if the value matches) to wait for.
|
|
564
|
+
:param timeout: How long to wait for the signal to have the value.
|
|
565
|
+
:param set_timeout: How long to wait for the set to complete.
|
|
566
|
+
:param wait_for_set_completion:
|
|
567
|
+
If False then return as soon as the match_signal matches match_value. If
|
|
568
|
+
True then also wait for the set operation to complete before returning.
|
|
569
|
+
|
|
570
|
+
:seealso:
|
|
571
|
+
[](#interact-with-signals)
|
|
572
|
+
|
|
573
|
+
:example:
|
|
574
|
+
To set the setpoint and wait for the readback to match:
|
|
575
|
+
```python
|
|
576
|
+
await set_and_wait_for_value(device.setpoint, 1, device.readback, 1)
|
|
577
|
+
```
|
|
529
578
|
"""
|
|
530
579
|
# Start monitoring before the set to avoid a race condition
|
|
531
580
|
values_gen = observe_value(match_signal)
|
|
@@ -535,25 +584,34 @@ async def set_and_wait_for_other_value(
|
|
|
535
584
|
|
|
536
585
|
status = set_signal.set(set_value, timeout=set_timeout)
|
|
537
586
|
|
|
587
|
+
if callable(match_value):
|
|
588
|
+
matcher: Callable[[SignalDatatypeV], bool] = match_value # type: ignore
|
|
589
|
+
else:
|
|
590
|
+
|
|
591
|
+
def matcher(value):
|
|
592
|
+
return value == match_value
|
|
593
|
+
|
|
594
|
+
matcher.__name__ = f"equals_{match_value}"
|
|
595
|
+
|
|
538
596
|
# If the value was the same as before no need to wait for it to change
|
|
539
|
-
if current_value
|
|
597
|
+
if not matcher(current_value):
|
|
540
598
|
|
|
541
599
|
async def _wait_for_value():
|
|
542
600
|
async for value in values_gen:
|
|
543
|
-
if value
|
|
601
|
+
if matcher(value):
|
|
544
602
|
break
|
|
545
603
|
|
|
546
604
|
try:
|
|
547
605
|
await asyncio.wait_for(_wait_for_value(), timeout)
|
|
548
606
|
if wait_for_set_completion:
|
|
549
607
|
await status
|
|
550
|
-
return status
|
|
551
608
|
except asyncio.TimeoutError as e:
|
|
552
|
-
raise TimeoutError(
|
|
553
|
-
f"{match_signal.name} didn't match
|
|
609
|
+
raise asyncio.TimeoutError(
|
|
610
|
+
f"{match_signal.name} value didn't match value from"
|
|
611
|
+
f" {matcher.__name__}() in {timeout}s"
|
|
554
612
|
) from e
|
|
555
613
|
|
|
556
|
-
return
|
|
614
|
+
return status
|
|
557
615
|
|
|
558
616
|
|
|
559
617
|
async def set_and_wait_for_value(
|
|
@@ -561,37 +619,44 @@ async def set_and_wait_for_value(
|
|
|
561
619
|
value: SignalDatatypeT,
|
|
562
620
|
match_value: SignalDatatypeT | Callable[[SignalDatatypeT], bool] | None = None,
|
|
563
621
|
timeout: float = DEFAULT_TIMEOUT,
|
|
564
|
-
|
|
622
|
+
set_timeout: float | None = None,
|
|
565
623
|
wait_for_set_completion: bool = True,
|
|
566
624
|
) -> AsyncStatus:
|
|
567
|
-
"""Set a signal and monitor
|
|
625
|
+
"""Set a signal and monitor that same signal until it has the specified value.
|
|
626
|
+
|
|
627
|
+
This function sets a set_signal to a specified set_value and waits for
|
|
628
|
+
a match_signal to have the match_value.
|
|
568
629
|
|
|
569
|
-
|
|
570
|
-
|
|
630
|
+
:param signal: The signal to set.
|
|
631
|
+
:param value: The value to set it to.
|
|
632
|
+
:param match_value:
|
|
633
|
+
The value (or callable that says if the value matches) to wait for.
|
|
634
|
+
:param timeout: How long to wait for the signal to have the value.
|
|
635
|
+
:param set_timeout: How long to wait for the set to complete.
|
|
636
|
+
:param wait_for_set_completion:
|
|
637
|
+
If False then return as soon as the match_signal matches match_value. If
|
|
638
|
+
True then also wait for the set operation to complete before returning.
|
|
639
|
+
|
|
640
|
+
:seealso:
|
|
641
|
+
[](#interact-with-signals)
|
|
642
|
+
|
|
643
|
+
:examples:
|
|
644
|
+
To set a parameter and wait for it's value to change:
|
|
645
|
+
```python
|
|
646
|
+
await set_and_wait_for_value(device.parameter, 1)
|
|
647
|
+
```
|
|
648
|
+
For busy record, or other Signals with pattern:
|
|
649
|
+
- Set Signal with `wait=True` and stash the Status
|
|
571
650
|
- Read the same Signal to check the operation has started
|
|
572
651
|
- Return the Status so calling code can wait for operation to complete
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
The expected value of the signal after the operation.
|
|
582
|
-
Used to verify that the set operation was successful.
|
|
583
|
-
timeout:
|
|
584
|
-
How long to wait for the signal to have the value
|
|
585
|
-
status_timeout:
|
|
586
|
-
How long the returned Status will wait for the set to complete
|
|
587
|
-
wait_for_set_completion:
|
|
588
|
-
This will wait for set completion #More info in how-to docs
|
|
589
|
-
|
|
590
|
-
Notes
|
|
591
|
-
-----
|
|
592
|
-
Example usage::
|
|
593
|
-
|
|
594
|
-
set_and_wait_for_value(device.acquire, 1)
|
|
652
|
+
```python
|
|
653
|
+
status = await set_and_wait_for_value(
|
|
654
|
+
device.acquire, 1, wait_for_set_completion=False
|
|
655
|
+
)
|
|
656
|
+
# device is now acquiring
|
|
657
|
+
await status
|
|
658
|
+
# device has finished acquiring
|
|
659
|
+
```
|
|
595
660
|
"""
|
|
596
661
|
if match_value is None:
|
|
597
662
|
match_value = value
|
|
@@ -601,7 +666,7 @@ async def set_and_wait_for_value(
|
|
|
601
666
|
signal,
|
|
602
667
|
match_value,
|
|
603
668
|
timeout,
|
|
604
|
-
|
|
669
|
+
set_timeout,
|
|
605
670
|
wait_for_set_completion,
|
|
606
671
|
)
|
|
607
672
|
|
|
@@ -612,20 +677,11 @@ def walk_rw_signals(device: Device, path_prefix: str = "") -> dict[str, SignalRW
|
|
|
612
677
|
Stores retrieved signals with their dotted attribute paths in a dictionary. Used as
|
|
613
678
|
part of saving and loading a device.
|
|
614
679
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
Ophyd device to retrieve read-write signals from.
|
|
619
|
-
|
|
620
|
-
path_prefix : str
|
|
621
|
-
For internal use, leave blank when calling the method.
|
|
622
|
-
|
|
623
|
-
Returns
|
|
624
|
-
-------
|
|
625
|
-
SignalRWs : dict
|
|
680
|
+
:param device: Device to retrieve read-write signals from.
|
|
681
|
+
:param path_prefix: For internal use, leave blank when calling the method.
|
|
682
|
+
:return:
|
|
626
683
|
A dictionary matching the string attribute path of a SignalRW with the
|
|
627
684
|
signal itself.
|
|
628
|
-
|
|
629
685
|
"""
|
|
630
686
|
signals: dict[str, SignalRW[Any]] = {}
|
|
631
687
|
|
|
@@ -636,3 +692,39 @@ def walk_rw_signals(device: Device, path_prefix: str = "") -> dict[str, SignalRW
|
|
|
636
692
|
attr_signals = walk_rw_signals(attr, path_prefix=dot_path + ".")
|
|
637
693
|
signals.update(attr_signals)
|
|
638
694
|
return signals
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
async def walk_config_signals(
|
|
698
|
+
device: Device, path_prefix: str = ""
|
|
699
|
+
) -> dict[str, SignalRW[Any]]:
|
|
700
|
+
"""Retrieve all configuration signals from a device.
|
|
701
|
+
|
|
702
|
+
Stores retrieved signals with their dotted attribute paths in a dictionary. Used as
|
|
703
|
+
part of saving and loading a device.
|
|
704
|
+
|
|
705
|
+
:param device: Device to retrieve configuration signals from.
|
|
706
|
+
:param path_prefix: For internal use, leave blank when calling the method.
|
|
707
|
+
:return:
|
|
708
|
+
A dictionary matching the string attribute path of a SignalRW with the
|
|
709
|
+
signal itself.
|
|
710
|
+
"""
|
|
711
|
+
signals: dict[str, SignalRW[Any]] = {}
|
|
712
|
+
config_names: list[str] = []
|
|
713
|
+
if isinstance(device, Configurable):
|
|
714
|
+
configuration = device.read_configuration()
|
|
715
|
+
if inspect.isawaitable(configuration):
|
|
716
|
+
configuration = await configuration
|
|
717
|
+
config_names = list(configuration.keys())
|
|
718
|
+
for attr_name, attr in device.children():
|
|
719
|
+
dot_path = f"{path_prefix}{attr_name}"
|
|
720
|
+
if isinstance(attr, SignalRW) and attr.name in config_names:
|
|
721
|
+
signals[dot_path] = attr
|
|
722
|
+
signals.update(await walk_config_signals(attr, path_prefix=dot_path + "."))
|
|
723
|
+
|
|
724
|
+
return signals
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
class Ignore:
|
|
728
|
+
"""Annotation to ignore a signal when connecting a device."""
|
|
729
|
+
|
|
730
|
+
pass
|