ophyd-async 0.7.0__py3-none-any.whl → 0.8.0a2__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.
Files changed (70) hide show
  1. ophyd_async/_version.py +2 -2
  2. ophyd_async/core/__init__.py +23 -8
  3. ophyd_async/core/_detector.py +5 -10
  4. ophyd_async/core/_device.py +139 -66
  5. ophyd_async/core/_device_filler.py +191 -0
  6. ophyd_async/core/_device_save_loader.py +6 -7
  7. ophyd_async/core/_mock_signal_backend.py +32 -40
  8. ophyd_async/core/_mock_signal_utils.py +22 -16
  9. ophyd_async/core/_protocol.py +28 -8
  10. ophyd_async/core/_readable.py +5 -5
  11. ophyd_async/core/_signal.py +140 -152
  12. ophyd_async/core/_signal_backend.py +131 -64
  13. ophyd_async/core/_soft_signal_backend.py +125 -194
  14. ophyd_async/core/_status.py +22 -6
  15. ophyd_async/core/_table.py +97 -100
  16. ophyd_async/core/_utils.py +71 -18
  17. ophyd_async/epics/adaravis/_aravis_controller.py +2 -2
  18. ophyd_async/epics/adaravis/_aravis_io.py +7 -5
  19. ophyd_async/epics/adcore/_core_io.py +4 -6
  20. ophyd_async/epics/adcore/_hdf_writer.py +2 -2
  21. ophyd_async/epics/adcore/_utils.py +15 -10
  22. ophyd_async/epics/adkinetix/__init__.py +2 -1
  23. ophyd_async/epics/adkinetix/_kinetix_controller.py +6 -3
  24. ophyd_async/epics/adkinetix/_kinetix_io.py +3 -4
  25. ophyd_async/epics/adpilatus/_pilatus_controller.py +2 -2
  26. ophyd_async/epics/adpilatus/_pilatus_io.py +2 -3
  27. ophyd_async/epics/adsimdetector/_sim_controller.py +2 -2
  28. ophyd_async/epics/advimba/__init__.py +4 -1
  29. ophyd_async/epics/advimba/_vimba_controller.py +6 -3
  30. ophyd_async/epics/advimba/_vimba_io.py +7 -8
  31. ophyd_async/epics/demo/_sensor.py +8 -4
  32. ophyd_async/epics/eiger/_eiger.py +1 -2
  33. ophyd_async/epics/eiger/_eiger_controller.py +1 -1
  34. ophyd_async/epics/eiger/_eiger_io.py +2 -4
  35. ophyd_async/epics/eiger/_odin_io.py +4 -4
  36. ophyd_async/epics/pvi/__init__.py +2 -2
  37. ophyd_async/epics/pvi/_pvi.py +56 -321
  38. ophyd_async/epics/signal/__init__.py +3 -4
  39. ophyd_async/epics/signal/_aioca.py +184 -236
  40. ophyd_async/epics/signal/_common.py +35 -49
  41. ophyd_async/epics/signal/_p4p.py +254 -387
  42. ophyd_async/epics/signal/_signal.py +63 -21
  43. ophyd_async/fastcs/core.py +9 -0
  44. ophyd_async/fastcs/panda/__init__.py +4 -4
  45. ophyd_async/fastcs/panda/_block.py +18 -13
  46. ophyd_async/fastcs/panda/_control.py +3 -5
  47. ophyd_async/fastcs/panda/_hdf_panda.py +5 -19
  48. ophyd_async/fastcs/panda/_table.py +29 -51
  49. ophyd_async/fastcs/panda/_trigger.py +8 -8
  50. ophyd_async/fastcs/panda/_writer.py +2 -5
  51. ophyd_async/plan_stubs/_ensure_connected.py +3 -1
  52. ophyd_async/plan_stubs/_fly.py +2 -2
  53. ophyd_async/plan_stubs/_nd_attributes.py +5 -4
  54. ophyd_async/py.typed +0 -0
  55. ophyd_async/sim/demo/_pattern_detector/_pattern_detector_controller.py +1 -2
  56. ophyd_async/tango/__init__.py +2 -4
  57. ophyd_async/tango/base_devices/_base_device.py +76 -143
  58. ophyd_async/tango/demo/_counter.py +2 -2
  59. ophyd_async/tango/demo/_mover.py +2 -2
  60. ophyd_async/tango/signal/__init__.py +2 -4
  61. ophyd_async/tango/signal/_signal.py +29 -50
  62. ophyd_async/tango/signal/_tango_transport.py +38 -40
  63. {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0a2.dist-info}/METADATA +8 -12
  64. ophyd_async-0.8.0a2.dist-info/RECORD +110 -0
  65. {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0a2.dist-info}/WHEEL +1 -1
  66. ophyd_async/epics/signal/_epics_transport.py +0 -34
  67. ophyd_async-0.7.0.dist-info/RECORD +0 -108
  68. {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0a2.dist-info}/LICENSE +0 -0
  69. {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0a2.dist-info}/entry_points.txt +0 -0
  70. {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0a2.dist-info}/top_level.txt +0 -0
@@ -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, TypeVar, cast
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 AsyncConfigurable, AsyncReadable, AsyncStageable
21
- from ._signal_backend import SignalBackend
22
- from ._soft_signal_backend import SignalMetadata, SoftSignalBackend
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
- S = TypeVar("S")
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 asyncio.wait_for(func(self, *args, **kwargs), self._timeout)
48
+ return await _wait_for(func(self, *args, **kwargs), self._timeout, self.source)
33
49
 
34
50
  return wrapper
35
51
 
36
52
 
37
- def _fail(*args, **kwargs):
38
- raise RuntimeError("Signal has not been supplied a backend yet")
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
- DISCONNECTED_BACKEND = DisconnectedBackend()
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[T]):
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[T] = DISCONNECTED_BACKEND,
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._backend.source(self.name)
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[T]):
119
- def __init__(self, backend: SignalBackend[T], signal: Signal):
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) -> T:
141
- await self._valid.wait()
142
- assert self._value is not None, "Monitor not working"
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, value: T):
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(self, function: Callback, want_value: bool):
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._value)
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[T], AsyncReadable, AsyncStageable, Subscribable):
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(self, cached: bool | None) -> _SignalCache | SignalBackend:
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._backend
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._backend, 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._backend.get_datakey(self.source)}
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) -> T:
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[T]):
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[T], Movable):
227
+ class SignalW(Signal[SignalDatatypeT], Movable):
242
228
  """Signal that can be set"""
243
229
 
244
- def set(
245
- self, value: T, wait=True, timeout: CalculatableTimeout = CALCULATE_TIMEOUT
246
- ) -> AsyncStatus:
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 is CALCULATE_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
- location: Location = {
266
- "setpoint": await self._backend.get_setpoint(),
267
- "readback": await self.get_value(),
268
- }
269
- return location
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
- def trigger(
261
+ @AsyncStatus.wrap
262
+ async def trigger(
276
263
  self, wait=True, timeout: CalculatableTimeout = CALCULATE_TIMEOUT
277
- ) -> AsyncStatus:
264
+ ) -> None:
278
265
  """Trigger the action and return a status saying when it's done"""
279
- if timeout is CALCULATE_TIMEOUT:
266
+ if timeout == CALCULATE_TIMEOUT:
280
267
  timeout = self._timeout
281
- coro = self._backend.put(None, wait=wait, timeout=timeout)
282
- return AsyncStatus(coro)
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[T] | None = None,
287
- initial_value: T | None = None,
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[T]:
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
- metadata = SignalMetadata(units=units, precision=precision)
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[T] | None = None,
305
- initial_value: T | None = None,
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[T], Callable[[T], None]]:
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
- metadata = SignalMetadata(units=units, precision=precision)
316
- backend = SoftSignalBackend(datatype, initial_value, metadata=metadata)
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[T], value: Any) -> None:
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[T], timeout: float | None = None, done_status: Status | None = None
444
- ) -> AsyncGenerator[T, None]:
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[T | Status] = 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(T, item)
474
+ yield cast(SignalDatatypeT, item)
489
475
  finally:
490
476
  signal.clear_sub(q.put_nowait)
491
477
 
492
478
 
493
- class _ValueChecker(Generic[T]):
494
- def __init__(self, matcher: Callable[[T], bool], matcher_name: str):
495
- self._last_value: T | None = None
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[T]):
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(self, signal: SignalR[T], timeout: float | None):
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[T],
517
- match: T | Callable[[T], bool],
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[T],
552
- set_value: T,
553
- read_signal: SignalR[S],
554
- read_value: S,
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[T],
612
- value: T,
599
+ signal: SignalRW[SignalDatatypeT],
600
+ value: SignalDatatypeT,
613
601
  timeout: float = DEFAULT_TIMEOUT,
614
602
  status_timeout: float | None = None,
615
603
  ) -> AsyncStatus: