ophyd-async 0.7.0a1__py3-none-any.whl → 0.8.0a3__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 +30 -9
- ophyd_async/core/_detector.py +5 -10
- ophyd_async/core/_device.py +146 -67
- ophyd_async/core/_device_filler.py +269 -0
- ophyd_async/core/_device_save_loader.py +6 -7
- ophyd_async/core/_mock_signal_backend.py +32 -40
- ophyd_async/core/_mock_signal_utils.py +22 -16
- ophyd_async/core/_protocol.py +28 -8
- ophyd_async/core/_readable.py +133 -134
- ophyd_async/core/_signal.py +140 -152
- ophyd_async/core/_signal_backend.py +131 -64
- ophyd_async/core/_soft_signal_backend.py +125 -194
- ophyd_async/core/_status.py +22 -6
- ophyd_async/core/_table.py +97 -100
- ophyd_async/core/_utils.py +79 -18
- ophyd_async/epics/adaravis/_aravis_controller.py +2 -2
- ophyd_async/epics/adaravis/_aravis_io.py +8 -6
- ophyd_async/epics/adcore/_core_io.py +5 -7
- ophyd_async/epics/adcore/_hdf_writer.py +2 -2
- ophyd_async/epics/adcore/_single_trigger.py +4 -9
- ophyd_async/epics/adcore/_utils.py +15 -10
- ophyd_async/epics/adkinetix/__init__.py +2 -1
- ophyd_async/epics/adkinetix/_kinetix_controller.py +6 -3
- ophyd_async/epics/adkinetix/_kinetix_io.py +4 -5
- ophyd_async/epics/adpilatus/_pilatus_controller.py +2 -2
- ophyd_async/epics/adpilatus/_pilatus_io.py +3 -4
- ophyd_async/epics/adsimdetector/_sim_controller.py +2 -2
- ophyd_async/epics/advimba/__init__.py +4 -1
- ophyd_async/epics/advimba/_vimba_controller.py +6 -3
- ophyd_async/epics/advimba/_vimba_io.py +8 -9
- ophyd_async/epics/core/__init__.py +26 -0
- ophyd_async/epics/core/_aioca.py +323 -0
- ophyd_async/epics/core/_epics_connector.py +53 -0
- ophyd_async/epics/core/_epics_device.py +13 -0
- ophyd_async/epics/core/_p4p.py +382 -0
- ophyd_async/epics/core/_pvi_connector.py +92 -0
- ophyd_async/epics/core/_signal.py +171 -0
- ophyd_async/epics/core/_util.py +61 -0
- ophyd_async/epics/demo/_mover.py +4 -5
- ophyd_async/epics/demo/_sensor.py +14 -13
- ophyd_async/epics/eiger/_eiger.py +1 -2
- ophyd_async/epics/eiger/_eiger_controller.py +1 -1
- ophyd_async/epics/eiger/_eiger_io.py +3 -5
- ophyd_async/epics/eiger/_odin_io.py +5 -5
- ophyd_async/epics/motor.py +4 -5
- ophyd_async/epics/signal.py +11 -0
- ophyd_async/fastcs/core.py +9 -0
- ophyd_async/fastcs/panda/__init__.py +4 -4
- ophyd_async/fastcs/panda/_block.py +23 -11
- ophyd_async/fastcs/panda/_control.py +3 -5
- ophyd_async/fastcs/panda/_hdf_panda.py +5 -19
- ophyd_async/fastcs/panda/_table.py +29 -51
- ophyd_async/fastcs/panda/_trigger.py +8 -8
- ophyd_async/fastcs/panda/_writer.py +4 -7
- ophyd_async/plan_stubs/_ensure_connected.py +3 -1
- ophyd_async/plan_stubs/_fly.py +2 -2
- ophyd_async/plan_stubs/_nd_attributes.py +5 -4
- ophyd_async/py.typed +0 -0
- ophyd_async/sim/demo/_pattern_detector/_pattern_detector_controller.py +1 -2
- ophyd_async/sim/demo/_sim_motor.py +3 -4
- ophyd_async/tango/__init__.py +2 -4
- ophyd_async/tango/base_devices/_base_device.py +76 -144
- ophyd_async/tango/demo/_counter.py +8 -18
- ophyd_async/tango/demo/_mover.py +5 -6
- ophyd_async/tango/signal/__init__.py +2 -4
- ophyd_async/tango/signal/_signal.py +29 -50
- ophyd_async/tango/signal/_tango_transport.py +38 -40
- {ophyd_async-0.7.0a1.dist-info → ophyd_async-0.8.0a3.dist-info}/METADATA +8 -12
- ophyd_async-0.8.0a3.dist-info/RECORD +112 -0
- {ophyd_async-0.7.0a1.dist-info → ophyd_async-0.8.0a3.dist-info}/WHEEL +1 -1
- ophyd_async/epics/pvi/__init__.py +0 -3
- ophyd_async/epics/pvi/_pvi.py +0 -338
- ophyd_async/epics/signal/__init__.py +0 -21
- ophyd_async/epics/signal/_aioca.py +0 -378
- ophyd_async/epics/signal/_common.py +0 -57
- ophyd_async/epics/signal/_epics_transport.py +0 -34
- ophyd_async/epics/signal/_p4p.py +0 -518
- ophyd_async/epics/signal/_signal.py +0 -114
- ophyd_async-0.7.0a1.dist-info/RECORD +0 -108
- {ophyd_async-0.7.0a1.dist-info → ophyd_async-0.8.0a3.dist-info}/LICENSE +0 -0
- {ophyd_async-0.7.0a1.dist-info → ophyd_async-0.8.0a3.dist-info}/entry_points.txt +0 -0
- {ophyd_async-0.7.0a1.dist-info → ophyd_async-0.8.0a3.dist-info}/top_level.txt +0 -0
ophyd_async/core/_signal.py
CHANGED
|
@@ -2,128 +2,109 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import functools
|
|
5
|
-
from collections.abc import AsyncGenerator, Callable, Mapping
|
|
6
|
-
from typing import Any, Generic,
|
|
5
|
+
from collections.abc import AsyncGenerator, Awaitable, Callable, Mapping
|
|
6
|
+
from typing import Any, Generic, cast
|
|
7
|
+
from unittest.mock import Mock
|
|
7
8
|
|
|
8
9
|
from bluesky.protocols import (
|
|
9
10
|
Locatable,
|
|
10
11
|
Location,
|
|
11
12
|
Movable,
|
|
12
|
-
Reading,
|
|
13
13
|
Status,
|
|
14
14
|
Subscribable,
|
|
15
15
|
)
|
|
16
16
|
from event_model import DataKey
|
|
17
17
|
|
|
18
|
-
from ._device import Device
|
|
18
|
+
from ._device import Device, DeviceConnector
|
|
19
19
|
from ._mock_signal_backend import MockSignalBackend
|
|
20
|
-
from ._protocol import
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
from ._protocol import (
|
|
21
|
+
AsyncConfigurable,
|
|
22
|
+
AsyncReadable,
|
|
23
|
+
AsyncStageable,
|
|
24
|
+
Reading,
|
|
25
|
+
)
|
|
26
|
+
from ._signal_backend import (
|
|
27
|
+
SignalBackend,
|
|
28
|
+
SignalDatatypeT,
|
|
29
|
+
SignalDatatypeV,
|
|
30
|
+
)
|
|
31
|
+
from ._soft_signal_backend import SoftSignalBackend
|
|
23
32
|
from ._status import AsyncStatus
|
|
24
33
|
from ._utils import CALCULATE_TIMEOUT, DEFAULT_TIMEOUT, CalculatableTimeout, Callback, T
|
|
25
34
|
|
|
26
|
-
|
|
35
|
+
_mock_signal_backends: dict[Device, MockSignalBackend] = {}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def _wait_for(coro: Awaitable[T], timeout: float | None, source: str) -> T:
|
|
39
|
+
try:
|
|
40
|
+
return await asyncio.wait_for(coro, timeout)
|
|
41
|
+
except asyncio.TimeoutError as e:
|
|
42
|
+
raise asyncio.TimeoutError(source) from e
|
|
27
43
|
|
|
28
44
|
|
|
29
45
|
def _add_timeout(func):
|
|
30
46
|
@functools.wraps(func)
|
|
31
47
|
async def wrapper(self: Signal, *args, **kwargs):
|
|
32
|
-
return await
|
|
48
|
+
return await _wait_for(func(self, *args, **kwargs), self._timeout, self.source)
|
|
33
49
|
|
|
34
50
|
return wrapper
|
|
35
51
|
|
|
36
52
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
class DisconnectedBackend(SignalBackend):
|
|
42
|
-
source = connect = put = get_datakey = get_reading = get_value = get_setpoint = (
|
|
43
|
-
set_callback
|
|
44
|
-
) = _fail
|
|
53
|
+
class SignalConnector(DeviceConnector):
|
|
54
|
+
def __init__(self, backend: SignalBackend):
|
|
55
|
+
self.backend = self._init_backend = backend
|
|
45
56
|
|
|
46
|
-
|
|
47
|
-
|
|
57
|
+
async def connect(
|
|
58
|
+
self,
|
|
59
|
+
device: Device,
|
|
60
|
+
mock: bool | Mock,
|
|
61
|
+
timeout: float,
|
|
62
|
+
force_reconnect: bool,
|
|
63
|
+
):
|
|
64
|
+
if mock:
|
|
65
|
+
self.backend = MockSignalBackend(self._init_backend, mock)
|
|
66
|
+
_mock_signal_backends[device] = self.backend
|
|
67
|
+
else:
|
|
68
|
+
self.backend = self._init_backend
|
|
69
|
+
device.log.debug(f"Connecting to {self.backend.source(device.name, read=True)}")
|
|
70
|
+
await self.backend.connect(timeout)
|
|
48
71
|
|
|
49
72
|
|
|
50
|
-
class Signal(Device, Generic[
|
|
73
|
+
class Signal(Device, Generic[SignalDatatypeT]):
|
|
51
74
|
"""A Device with the concept of a value, with R, RW, W and X flavours"""
|
|
52
75
|
|
|
76
|
+
_connector: SignalConnector
|
|
77
|
+
|
|
53
78
|
def __init__(
|
|
54
79
|
self,
|
|
55
|
-
backend: SignalBackend[
|
|
80
|
+
backend: SignalBackend[SignalDatatypeT],
|
|
56
81
|
timeout: float | None = DEFAULT_TIMEOUT,
|
|
57
82
|
name: str = "",
|
|
58
83
|
) -> None:
|
|
84
|
+
super().__init__(name=name, connector=SignalConnector(backend))
|
|
59
85
|
self._timeout = timeout
|
|
60
|
-
self._backend = backend
|
|
61
|
-
super().__init__(name)
|
|
62
|
-
|
|
63
|
-
async def connect(
|
|
64
|
-
self,
|
|
65
|
-
mock=False,
|
|
66
|
-
timeout=DEFAULT_TIMEOUT,
|
|
67
|
-
force_reconnect: bool = False,
|
|
68
|
-
backend: SignalBackend[T] | None = None,
|
|
69
|
-
):
|
|
70
|
-
if backend:
|
|
71
|
-
if (
|
|
72
|
-
self._backend is not DISCONNECTED_BACKEND
|
|
73
|
-
and backend is not self._backend
|
|
74
|
-
):
|
|
75
|
-
raise ValueError("Backend at connection different from previous one.")
|
|
76
|
-
|
|
77
|
-
self._backend = backend
|
|
78
|
-
if (
|
|
79
|
-
self._previous_connect_was_mock is not None
|
|
80
|
-
and self._previous_connect_was_mock != mock
|
|
81
|
-
):
|
|
82
|
-
raise RuntimeError(
|
|
83
|
-
f"`connect(mock={mock})` called on a `Signal` where the previous "
|
|
84
|
-
f"connect was `mock={self._previous_connect_was_mock}`. Changing mock "
|
|
85
|
-
"value between connects is not permitted."
|
|
86
|
-
)
|
|
87
|
-
self._previous_connect_was_mock = mock
|
|
88
|
-
|
|
89
|
-
if mock and not issubclass(type(self._backend), MockSignalBackend):
|
|
90
|
-
# Using a soft backend, look to the initial value
|
|
91
|
-
self._backend = MockSignalBackend(initial_backend=self._backend)
|
|
92
|
-
|
|
93
|
-
if self._backend is None:
|
|
94
|
-
raise RuntimeError("`connect` called on signal without backend")
|
|
95
|
-
|
|
96
|
-
can_use_previous_connection: bool = self._connect_task is not None and not (
|
|
97
|
-
self._connect_task.done() and self._connect_task.exception()
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
if force_reconnect or not can_use_previous_connection:
|
|
101
|
-
self.log.debug(f"Connecting to {self.source}")
|
|
102
|
-
self._connect_task = asyncio.create_task(
|
|
103
|
-
self._backend.connect(timeout=timeout)
|
|
104
|
-
)
|
|
105
|
-
else:
|
|
106
|
-
self.log.debug(f"Reusing previous connection to {self.source}")
|
|
107
|
-
assert (
|
|
108
|
-
self._connect_task
|
|
109
|
-
), "this assert is for type analysis and will never fail"
|
|
110
|
-
await self._connect_task
|
|
111
86
|
|
|
112
87
|
@property
|
|
113
88
|
def source(self) -> str:
|
|
114
89
|
"""Like ca://PV_PREFIX:SIGNAL, or "" if not set"""
|
|
115
|
-
return self.
|
|
90
|
+
return self._connector.backend.source(self.name, read=True)
|
|
91
|
+
|
|
92
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
93
|
+
if name != "parent" and isinstance(value, Device):
|
|
94
|
+
raise AttributeError(
|
|
95
|
+
f"Cannot add Device or Signal {value} as a child of Signal {self}, "
|
|
96
|
+
"make a subclass of Device instead"
|
|
97
|
+
)
|
|
98
|
+
return super().__setattr__(name, value)
|
|
116
99
|
|
|
117
100
|
|
|
118
|
-
class _SignalCache(Generic[
|
|
119
|
-
def __init__(self, backend: SignalBackend[
|
|
101
|
+
class _SignalCache(Generic[SignalDatatypeT]):
|
|
102
|
+
def __init__(self, backend: SignalBackend[SignalDatatypeT], signal: Signal):
|
|
120
103
|
self._signal = signal
|
|
121
104
|
self._staged = False
|
|
122
105
|
self._listeners: dict[Callback, bool] = {}
|
|
123
106
|
self._valid = asyncio.Event()
|
|
124
|
-
self._reading: Reading | None = None
|
|
125
|
-
self._value: T | None = None
|
|
126
|
-
|
|
107
|
+
self._reading: Reading[SignalDatatypeT] | None = None
|
|
127
108
|
self.backend = backend
|
|
128
109
|
signal.log.debug(f"Making subscription on source {signal.source}")
|
|
129
110
|
backend.set_callback(self._callback)
|
|
@@ -132,30 +113,33 @@ class _SignalCache(Generic[T]):
|
|
|
132
113
|
self.backend.set_callback(None)
|
|
133
114
|
self._signal.log.debug(f"Closing subscription on source {self._signal.source}")
|
|
134
115
|
|
|
135
|
-
async def get_reading(self) -> Reading:
|
|
116
|
+
async def get_reading(self) -> Reading[SignalDatatypeT]:
|
|
136
117
|
await self._valid.wait()
|
|
137
118
|
assert self._reading is not None, "Monitor not working"
|
|
138
119
|
return self._reading
|
|
139
120
|
|
|
140
|
-
async def get_value(self) ->
|
|
141
|
-
await self.
|
|
142
|
-
|
|
143
|
-
return self._value
|
|
121
|
+
async def get_value(self) -> SignalDatatypeT:
|
|
122
|
+
reading = await self.get_reading()
|
|
123
|
+
return reading["value"]
|
|
144
124
|
|
|
145
|
-
def _callback(self, reading: Reading
|
|
125
|
+
def _callback(self, reading: Reading[SignalDatatypeT]):
|
|
146
126
|
self._signal.log.debug(
|
|
147
127
|
f"Updated subscription: reading of source {self._signal.source} changed"
|
|
148
128
|
f"from {self._reading} to {reading}"
|
|
149
129
|
)
|
|
150
130
|
self._reading = reading
|
|
151
|
-
self._value = value
|
|
152
131
|
self._valid.set()
|
|
153
132
|
for function, want_value in self._listeners.items():
|
|
154
133
|
self._notify(function, want_value)
|
|
155
134
|
|
|
156
|
-
def _notify(
|
|
135
|
+
def _notify(
|
|
136
|
+
self,
|
|
137
|
+
function: Callback[dict[str, Reading[SignalDatatypeT]] | SignalDatatypeT],
|
|
138
|
+
want_value: bool,
|
|
139
|
+
):
|
|
140
|
+
assert self._reading, "Monitor not working"
|
|
157
141
|
if want_value:
|
|
158
|
-
function(self.
|
|
142
|
+
function(self._reading["value"])
|
|
159
143
|
else:
|
|
160
144
|
function({self._signal.name: self._reading})
|
|
161
145
|
|
|
@@ -173,12 +157,14 @@ class _SignalCache(Generic[T]):
|
|
|
173
157
|
return self._staged or bool(self._listeners)
|
|
174
158
|
|
|
175
159
|
|
|
176
|
-
class SignalR(Signal[
|
|
160
|
+
class SignalR(Signal[SignalDatatypeT], AsyncReadable, AsyncStageable, Subscribable):
|
|
177
161
|
"""Signal that can be read from and monitored"""
|
|
178
162
|
|
|
179
163
|
_cache: _SignalCache | None = None
|
|
180
164
|
|
|
181
|
-
def _backend_or_cache(
|
|
165
|
+
def _backend_or_cache(
|
|
166
|
+
self, cached: bool | None = None
|
|
167
|
+
) -> _SignalCache | SignalBackend:
|
|
182
168
|
# If cached is None then calculate it based on whether we already have a cache
|
|
183
169
|
if cached is None:
|
|
184
170
|
cached = self._cache is not None
|
|
@@ -186,11 +172,11 @@ class SignalR(Signal[T], AsyncReadable, AsyncStageable, Subscribable):
|
|
|
186
172
|
assert self._cache, f"{self.source} not being monitored"
|
|
187
173
|
return self._cache
|
|
188
174
|
else:
|
|
189
|
-
return self.
|
|
175
|
+
return self._connector.backend
|
|
190
176
|
|
|
191
177
|
def _get_cache(self) -> _SignalCache:
|
|
192
178
|
if not self._cache:
|
|
193
|
-
self._cache = _SignalCache(self.
|
|
179
|
+
self._cache = _SignalCache(self._connector.backend, self)
|
|
194
180
|
return self._cache
|
|
195
181
|
|
|
196
182
|
def _del_cache(self, needed: bool):
|
|
@@ -206,16 +192,16 @@ class SignalR(Signal[T], AsyncReadable, AsyncStageable, Subscribable):
|
|
|
206
192
|
@_add_timeout
|
|
207
193
|
async def describe(self) -> dict[str, DataKey]:
|
|
208
194
|
"""Return a single item dict with the descriptor in it"""
|
|
209
|
-
return {self.name: await self.
|
|
195
|
+
return {self.name: await self._connector.backend.get_datakey(self.source)}
|
|
210
196
|
|
|
211
197
|
@_add_timeout
|
|
212
|
-
async def get_value(self, cached: bool | None = None) ->
|
|
198
|
+
async def get_value(self, cached: bool | None = None) -> SignalDatatypeT:
|
|
213
199
|
"""The current value"""
|
|
214
200
|
value = await self._backend_or_cache(cached).get_value()
|
|
215
201
|
self.log.debug(f"get_value() on source {self.source} returned {value}")
|
|
216
202
|
return value
|
|
217
203
|
|
|
218
|
-
def subscribe_value(self, function: Callback[
|
|
204
|
+
def subscribe_value(self, function: Callback[SignalDatatypeT]):
|
|
219
205
|
"""Subscribe to updates in value of a device"""
|
|
220
206
|
self._get_cache().subscribe(function, want_value=True)
|
|
221
207
|
|
|
@@ -238,84 +224,82 @@ class SignalR(Signal[T], AsyncReadable, AsyncStageable, Subscribable):
|
|
|
238
224
|
self._del_cache(self._get_cache().set_staged(False))
|
|
239
225
|
|
|
240
226
|
|
|
241
|
-
class SignalW(Signal[
|
|
227
|
+
class SignalW(Signal[SignalDatatypeT], Movable):
|
|
242
228
|
"""Signal that can be set"""
|
|
243
229
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
230
|
+
@AsyncStatus.wrap
|
|
231
|
+
async def set(
|
|
232
|
+
self,
|
|
233
|
+
value: SignalDatatypeT,
|
|
234
|
+
wait=True,
|
|
235
|
+
timeout: CalculatableTimeout = CALCULATE_TIMEOUT,
|
|
236
|
+
) -> None:
|
|
247
237
|
"""Set the value and return a status saying when it's done"""
|
|
248
|
-
if timeout
|
|
238
|
+
if timeout == CALCULATE_TIMEOUT:
|
|
249
239
|
timeout = self._timeout
|
|
240
|
+
source = self._connector.backend.source(self.name, read=False)
|
|
241
|
+
self.log.debug(f"Putting value {value} to backend at source {source}")
|
|
242
|
+
await _wait_for(self._connector.backend.put(value, wait=wait), timeout, source)
|
|
243
|
+
self.log.debug(f"Successfully put value {value} to backend at source {source}")
|
|
250
244
|
|
|
251
|
-
async def do_set():
|
|
252
|
-
self.log.debug(f"Putting value {value} to backend at source {self.source}")
|
|
253
|
-
await self._backend.put(value, wait=wait, timeout=timeout)
|
|
254
|
-
self.log.debug(
|
|
255
|
-
f"Successfully put value {value} to backend at source {self.source}"
|
|
256
|
-
)
|
|
257
|
-
|
|
258
|
-
return AsyncStatus(do_set())
|
|
259
245
|
|
|
260
|
-
|
|
261
|
-
class SignalRW(SignalR[T], SignalW[T], Locatable):
|
|
246
|
+
class SignalRW(SignalR[SignalDatatypeT], SignalW[SignalDatatypeT], Locatable):
|
|
262
247
|
"""Signal that can be both read and set"""
|
|
263
248
|
|
|
249
|
+
@_add_timeout
|
|
264
250
|
async def locate(self) -> Location:
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
return
|
|
251
|
+
"""Return the setpoint and readback."""
|
|
252
|
+
setpoint, readback = await asyncio.gather(
|
|
253
|
+
self._connector.backend.get_setpoint(), self._backend_or_cache().get_value()
|
|
254
|
+
)
|
|
255
|
+
return Location(setpoint=setpoint, readback=readback)
|
|
270
256
|
|
|
271
257
|
|
|
272
258
|
class SignalX(Signal):
|
|
273
259
|
"""Signal that puts the default value"""
|
|
274
260
|
|
|
275
|
-
|
|
261
|
+
@AsyncStatus.wrap
|
|
262
|
+
async def trigger(
|
|
276
263
|
self, wait=True, timeout: CalculatableTimeout = CALCULATE_TIMEOUT
|
|
277
|
-
) ->
|
|
264
|
+
) -> None:
|
|
278
265
|
"""Trigger the action and return a status saying when it's done"""
|
|
279
|
-
if timeout
|
|
266
|
+
if timeout == CALCULATE_TIMEOUT:
|
|
280
267
|
timeout = self._timeout
|
|
281
|
-
|
|
282
|
-
|
|
268
|
+
source = self._connector.backend.source(self.name, read=False)
|
|
269
|
+
self.log.debug(f"Putting default value to backend at source {source}")
|
|
270
|
+
await _wait_for(self._connector.backend.put(None, wait=wait), timeout, source)
|
|
271
|
+
self.log.debug(f"Successfully put default value to backend at source {source}")
|
|
283
272
|
|
|
284
273
|
|
|
285
274
|
def soft_signal_rw(
|
|
286
|
-
datatype: type[
|
|
287
|
-
initial_value:
|
|
275
|
+
datatype: type[SignalDatatypeT],
|
|
276
|
+
initial_value: SignalDatatypeT | None = None,
|
|
288
277
|
name: str = "",
|
|
289
278
|
units: str | None = None,
|
|
290
279
|
precision: int | None = None,
|
|
291
|
-
) -> SignalRW[
|
|
280
|
+
) -> SignalRW[SignalDatatypeT]:
|
|
292
281
|
"""Creates a read-writable Signal with a SoftSignalBackend.
|
|
293
282
|
May pass metadata, which are propagated into describe.
|
|
294
283
|
"""
|
|
295
|
-
|
|
296
|
-
signal = SignalRW(
|
|
297
|
-
SoftSignalBackend(datatype, initial_value, metadata=metadata),
|
|
298
|
-
name=name,
|
|
299
|
-
)
|
|
284
|
+
backend = SoftSignalBackend(datatype, initial_value, units, precision)
|
|
285
|
+
signal = SignalRW(backend=backend, name=name)
|
|
300
286
|
return signal
|
|
301
287
|
|
|
302
288
|
|
|
303
289
|
def soft_signal_r_and_setter(
|
|
304
|
-
datatype: type[
|
|
305
|
-
initial_value:
|
|
290
|
+
datatype: type[SignalDatatypeT],
|
|
291
|
+
initial_value: SignalDatatypeT | None = None,
|
|
306
292
|
name: str = "",
|
|
307
293
|
units: str | None = None,
|
|
308
294
|
precision: int | None = None,
|
|
309
|
-
) -> tuple[SignalR[
|
|
295
|
+
) -> tuple[SignalR[SignalDatatypeT], Callable[[SignalDatatypeT], None]]:
|
|
310
296
|
"""Returns a tuple of a read-only Signal and a callable through
|
|
311
297
|
which the signal can be internally modified within the device.
|
|
312
298
|
May pass metadata, which are propagated into describe.
|
|
313
299
|
Use soft_signal_rw if you want a device that is externally modifiable
|
|
314
300
|
"""
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
signal = SignalR(backend, name=name)
|
|
318
|
-
|
|
301
|
+
backend = SoftSignalBackend(datatype, initial_value, units, precision)
|
|
302
|
+
signal = SignalR(backend=backend, name=name)
|
|
319
303
|
return (signal, backend.set_value)
|
|
320
304
|
|
|
321
305
|
|
|
@@ -330,7 +314,7 @@ def _generate_assert_error_msg(name: str, expected_result, actual_result) -> str
|
|
|
330
314
|
)
|
|
331
315
|
|
|
332
316
|
|
|
333
|
-
async def assert_value(signal: SignalR[
|
|
317
|
+
async def assert_value(signal: SignalR[SignalDatatypeT], value: Any) -> None:
|
|
334
318
|
"""Assert a signal's value and compare it an expected signal.
|
|
335
319
|
|
|
336
320
|
Parameters
|
|
@@ -440,8 +424,10 @@ def assert_emitted(docs: Mapping[str, list[dict]], **numbers: int):
|
|
|
440
424
|
|
|
441
425
|
|
|
442
426
|
async def observe_value(
|
|
443
|
-
signal: SignalR[
|
|
444
|
-
|
|
427
|
+
signal: SignalR[SignalDatatypeT],
|
|
428
|
+
timeout: float | None = None,
|
|
429
|
+
done_status: Status | None = None,
|
|
430
|
+
) -> AsyncGenerator[SignalDatatypeT, None]:
|
|
445
431
|
"""Subscribe to the value of a signal so it can be iterated from.
|
|
446
432
|
|
|
447
433
|
Parameters
|
|
@@ -464,7 +450,7 @@ async def observe_value(
|
|
|
464
450
|
do_something_with(value)
|
|
465
451
|
"""
|
|
466
452
|
|
|
467
|
-
q: asyncio.Queue[
|
|
453
|
+
q: asyncio.Queue[SignalDatatypeT | Status] = asyncio.Queue()
|
|
468
454
|
if timeout is None:
|
|
469
455
|
get_value = q.get
|
|
470
456
|
else:
|
|
@@ -485,24 +471,26 @@ async def observe_value(
|
|
|
485
471
|
else:
|
|
486
472
|
break
|
|
487
473
|
else:
|
|
488
|
-
yield cast(
|
|
474
|
+
yield cast(SignalDatatypeT, item)
|
|
489
475
|
finally:
|
|
490
476
|
signal.clear_sub(q.put_nowait)
|
|
491
477
|
|
|
492
478
|
|
|
493
|
-
class _ValueChecker(Generic[
|
|
494
|
-
def __init__(self, matcher: Callable[[
|
|
495
|
-
self._last_value:
|
|
479
|
+
class _ValueChecker(Generic[SignalDatatypeT]):
|
|
480
|
+
def __init__(self, matcher: Callable[[SignalDatatypeT], bool], matcher_name: str):
|
|
481
|
+
self._last_value: SignalDatatypeT | None = None
|
|
496
482
|
self._matcher = matcher
|
|
497
483
|
self._matcher_name = matcher_name
|
|
498
484
|
|
|
499
|
-
async def _wait_for_value(self, signal: SignalR[
|
|
485
|
+
async def _wait_for_value(self, signal: SignalR[SignalDatatypeT]):
|
|
500
486
|
async for value in observe_value(signal):
|
|
501
487
|
self._last_value = value
|
|
502
488
|
if self._matcher(value):
|
|
503
489
|
return
|
|
504
490
|
|
|
505
|
-
async def wait_for_value(
|
|
491
|
+
async def wait_for_value(
|
|
492
|
+
self, signal: SignalR[SignalDatatypeT], timeout: float | None
|
|
493
|
+
):
|
|
506
494
|
try:
|
|
507
495
|
await asyncio.wait_for(self._wait_for_value(signal), timeout)
|
|
508
496
|
except asyncio.TimeoutError as e:
|
|
@@ -513,8 +501,8 @@ class _ValueChecker(Generic[T]):
|
|
|
513
501
|
|
|
514
502
|
|
|
515
503
|
async def wait_for_value(
|
|
516
|
-
signal: SignalR[
|
|
517
|
-
match:
|
|
504
|
+
signal: SignalR[SignalDatatypeT],
|
|
505
|
+
match: SignalDatatypeT | Callable[[SignalDatatypeT], bool],
|
|
518
506
|
timeout: float | None,
|
|
519
507
|
):
|
|
520
508
|
"""Wait for a signal to have a matching value.
|
|
@@ -548,10 +536,10 @@ async def wait_for_value(
|
|
|
548
536
|
|
|
549
537
|
|
|
550
538
|
async def set_and_wait_for_other_value(
|
|
551
|
-
set_signal: SignalW[
|
|
552
|
-
set_value:
|
|
553
|
-
read_signal: SignalR[
|
|
554
|
-
read_value:
|
|
539
|
+
set_signal: SignalW[SignalDatatypeT],
|
|
540
|
+
set_value: SignalDatatypeT,
|
|
541
|
+
read_signal: SignalR[SignalDatatypeV],
|
|
542
|
+
read_value: SignalDatatypeV,
|
|
555
543
|
timeout: float = DEFAULT_TIMEOUT,
|
|
556
544
|
set_timeout: float | None = None,
|
|
557
545
|
) -> AsyncStatus:
|
|
@@ -608,8 +596,8 @@ async def set_and_wait_for_other_value(
|
|
|
608
596
|
|
|
609
597
|
|
|
610
598
|
async def set_and_wait_for_value(
|
|
611
|
-
signal: SignalRW[
|
|
612
|
-
value:
|
|
599
|
+
signal: SignalRW[SignalDatatypeT],
|
|
600
|
+
value: SignalDatatypeT,
|
|
613
601
|
timeout: float = DEFAULT_TIMEOUT,
|
|
614
602
|
status_timeout: float | None = None,
|
|
615
603
|
) -> AsyncStatus:
|