ophyd-async 0.3a3__py3-none-any.whl → 0.3a4__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 +1 -1
- ophyd_async/core/__init__.py +24 -10
- ophyd_async/core/async_status.py +2 -0
- ophyd_async/core/device.py +22 -9
- ophyd_async/core/mock_signal_backend.py +86 -0
- ophyd_async/core/mock_signal_utils.py +149 -0
- ophyd_async/core/signal.py +38 -42
- ophyd_async/core/{sim_signal_backend.py → soft_signal_backend.py} +23 -33
- ophyd_async/epics/_backend/_aioca.py +11 -7
- ophyd_async/epics/_backend/_p4p.py +11 -7
- ophyd_async/epics/_backend/common.py +17 -17
- ophyd_async/epics/areadetector/__init__.py +0 -4
- ophyd_async/epics/areadetector/drivers/ad_base.py +12 -10
- ophyd_async/epics/areadetector/drivers/aravis_driver.py +7 -5
- ophyd_async/epics/areadetector/drivers/kinetix_driver.py +7 -4
- ophyd_async/epics/areadetector/drivers/pilatus_driver.py +5 -2
- ophyd_async/epics/areadetector/drivers/vimba_driver.py +12 -7
- ophyd_async/epics/areadetector/utils.py +2 -12
- ophyd_async/epics/areadetector/writers/nd_file_hdf.py +21 -19
- ophyd_async/epics/areadetector/writers/nd_plugin.py +6 -7
- ophyd_async/epics/motion/motor.py +5 -1
- ophyd_async/epics/pvi/pvi.py +11 -11
- ophyd_async/panda/_hdf_panda.py +3 -3
- ophyd_async/sim/demo/sim_motor.py +4 -4
- ophyd_async/sim/pattern_generator.py +5 -5
- {ophyd_async-0.3a3.dist-info → ophyd_async-0.3a4.dist-info}/METADATA +2 -2
- {ophyd_async-0.3a3.dist-info → ophyd_async-0.3a4.dist-info}/RECORD +31 -29
- {ophyd_async-0.3a3.dist-info → ophyd_async-0.3a4.dist-info}/LICENSE +0 -0
- {ophyd_async-0.3a3.dist-info → ophyd_async-0.3a4.dist-info}/WHEEL +0 -0
- {ophyd_async-0.3a3.dist-info → ophyd_async-0.3a4.dist-info}/entry_points.txt +0 -0
- {ophyd_async-0.3a3.dist-info → ophyd_async-0.3a4.dist-info}/top_level.txt +0 -0
ophyd_async/_version.py
CHANGED
ophyd_async/core/__init__.py
CHANGED
|
@@ -24,6 +24,18 @@ from .device_save_loader import (
|
|
|
24
24
|
walk_rw_signals,
|
|
25
25
|
)
|
|
26
26
|
from .flyer import HardwareTriggeredFlyable, TriggerLogic
|
|
27
|
+
from .mock_signal_backend import (
|
|
28
|
+
MockSignalBackend,
|
|
29
|
+
)
|
|
30
|
+
from .mock_signal_utils import (
|
|
31
|
+
assert_mock_put_called_with,
|
|
32
|
+
callback_on_mock_put,
|
|
33
|
+
mock_puts_blocked,
|
|
34
|
+
reset_mock_put_calls,
|
|
35
|
+
set_mock_put_proceeds,
|
|
36
|
+
set_mock_value,
|
|
37
|
+
set_mock_values,
|
|
38
|
+
)
|
|
27
39
|
from .signal import (
|
|
28
40
|
Signal,
|
|
29
41
|
SignalR,
|
|
@@ -36,15 +48,12 @@ from .signal import (
|
|
|
36
48
|
assert_value,
|
|
37
49
|
observe_value,
|
|
38
50
|
set_and_wait_for_value,
|
|
39
|
-
|
|
40
|
-
set_sim_put_proceeds,
|
|
41
|
-
set_sim_value,
|
|
42
|
-
soft_signal_r_and_backend,
|
|
51
|
+
soft_signal_r_and_setter,
|
|
43
52
|
soft_signal_rw,
|
|
44
53
|
wait_for_value,
|
|
45
54
|
)
|
|
46
55
|
from .signal_backend import SignalBackend
|
|
47
|
-
from .
|
|
56
|
+
from .soft_signal_backend import SoftSignalBackend
|
|
48
57
|
from .standard_readable import ConfigSignal, HintedSignal, StandardReadable
|
|
49
58
|
from .utils import (
|
|
50
59
|
DEFAULT_TIMEOUT,
|
|
@@ -59,9 +68,15 @@ from .utils import (
|
|
|
59
68
|
)
|
|
60
69
|
|
|
61
70
|
__all__ = [
|
|
71
|
+
"assert_mock_put_called_with",
|
|
72
|
+
"callback_on_mock_put",
|
|
73
|
+
"mock_puts_blocked",
|
|
74
|
+
"set_mock_values",
|
|
75
|
+
"reset_mock_put_calls",
|
|
62
76
|
"SignalBackend",
|
|
63
|
-
"
|
|
77
|
+
"SoftSignalBackend",
|
|
64
78
|
"DetectorControl",
|
|
79
|
+
"MockSignalBackend",
|
|
65
80
|
"DetectorTrigger",
|
|
66
81
|
"DetectorWriter",
|
|
67
82
|
"StandardDetector",
|
|
@@ -73,13 +88,12 @@ __all__ = [
|
|
|
73
88
|
"SignalW",
|
|
74
89
|
"SignalRW",
|
|
75
90
|
"SignalX",
|
|
76
|
-
"
|
|
91
|
+
"soft_signal_r_and_setter",
|
|
77
92
|
"soft_signal_rw",
|
|
78
93
|
"observe_value",
|
|
79
94
|
"set_and_wait_for_value",
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"set_sim_value",
|
|
95
|
+
"set_mock_put_proceeds",
|
|
96
|
+
"set_mock_value",
|
|
83
97
|
"wait_for_value",
|
|
84
98
|
"AsyncStatus",
|
|
85
99
|
"DirectoryInfo",
|
ophyd_async/core/async_status.py
CHANGED
|
@@ -21,7 +21,9 @@ class AsyncStatus(Status):
|
|
|
21
21
|
self.task = awaitable
|
|
22
22
|
else:
|
|
23
23
|
self.task = asyncio.create_task(awaitable) # type: ignore
|
|
24
|
+
|
|
24
25
|
self.task.add_done_callback(self._run_callbacks)
|
|
26
|
+
|
|
25
27
|
self._callbacks = cast(List[Callback[Status]], [])
|
|
26
28
|
self._watchers = watchers
|
|
27
29
|
|
ophyd_async/core/device.py
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import sys
|
|
6
|
+
from functools import cached_property
|
|
7
|
+
from logging import LoggerAdapter, getLogger
|
|
6
8
|
from typing import (
|
|
7
9
|
Any,
|
|
8
10
|
Coroutine,
|
|
@@ -39,6 +41,12 @@ class Device(HasName):
|
|
|
39
41
|
"""Return the name of the Device"""
|
|
40
42
|
return self._name
|
|
41
43
|
|
|
44
|
+
@cached_property
|
|
45
|
+
def log(self):
|
|
46
|
+
return LoggerAdapter(
|
|
47
|
+
getLogger("ophyd_async.devices"), {"ophyd_async_device_name": self.name}
|
|
48
|
+
)
|
|
49
|
+
|
|
42
50
|
def children(self) -> Iterator[Tuple[str, Device]]:
|
|
43
51
|
for attr_name, attr in self.__dict__.items():
|
|
44
52
|
if attr_name != "parent" and isinstance(attr, Device):
|
|
@@ -52,26 +60,31 @@ class Device(HasName):
|
|
|
52
60
|
name:
|
|
53
61
|
New name to set
|
|
54
62
|
"""
|
|
63
|
+
|
|
64
|
+
# Ensure self.log is recreated after a name change
|
|
65
|
+
if hasattr(self, "log"):
|
|
66
|
+
del self.log
|
|
67
|
+
|
|
55
68
|
self._name = name
|
|
56
69
|
for attr_name, child in self.children():
|
|
57
70
|
child_name = f"{name}-{attr_name.rstrip('_')}" if name else ""
|
|
58
71
|
child.set_name(child_name)
|
|
59
72
|
child.parent = self
|
|
60
73
|
|
|
61
|
-
async def connect(self,
|
|
74
|
+
async def connect(self, mock: bool = False, timeout: float = DEFAULT_TIMEOUT):
|
|
62
75
|
"""Connect self and all child Devices.
|
|
63
76
|
|
|
64
77
|
Contains a timeout that gets propagated to child.connect methods.
|
|
65
78
|
|
|
66
79
|
Parameters
|
|
67
80
|
----------
|
|
68
|
-
|
|
69
|
-
If True then
|
|
81
|
+
mock:
|
|
82
|
+
If True then use ``MockSignalBackend`` for all Signals
|
|
70
83
|
timeout:
|
|
71
84
|
Time to wait before failing with a TimeoutError.
|
|
72
85
|
"""
|
|
73
86
|
coros = {
|
|
74
|
-
name: child_device.connect(
|
|
87
|
+
name: child_device.connect(mock=mock, timeout=timeout)
|
|
75
88
|
for name, child_device in self.children()
|
|
76
89
|
}
|
|
77
90
|
if coros:
|
|
@@ -105,9 +118,9 @@ class DeviceCollector:
|
|
|
105
118
|
If True, call ``device.set_name(variable_name)`` on all collected
|
|
106
119
|
Devices
|
|
107
120
|
connect:
|
|
108
|
-
If True, call ``device.connect(
|
|
121
|
+
If True, call ``device.connect(mock)`` in parallel on all
|
|
109
122
|
collected Devices
|
|
110
|
-
|
|
123
|
+
mock:
|
|
111
124
|
If True, connect Signals in simulation mode
|
|
112
125
|
timeout:
|
|
113
126
|
How long to wait for connect before logging an exception
|
|
@@ -129,12 +142,12 @@ class DeviceCollector:
|
|
|
129
142
|
self,
|
|
130
143
|
set_name=True,
|
|
131
144
|
connect=True,
|
|
132
|
-
|
|
145
|
+
mock=False,
|
|
133
146
|
timeout: float = 10.0,
|
|
134
147
|
):
|
|
135
148
|
self._set_name = set_name
|
|
136
149
|
self._connect = connect
|
|
137
|
-
self.
|
|
150
|
+
self._mock = mock
|
|
138
151
|
self._timeout = timeout
|
|
139
152
|
self._names_on_enter: Set[str] = set()
|
|
140
153
|
self._objects_on_exit: Dict[str, Any] = {}
|
|
@@ -168,7 +181,7 @@ class DeviceCollector:
|
|
|
168
181
|
obj.set_name(name)
|
|
169
182
|
if self._connect:
|
|
170
183
|
connect_coroutines[name] = obj.connect(
|
|
171
|
-
self.
|
|
184
|
+
self._mock, timeout=self._timeout
|
|
172
185
|
)
|
|
173
186
|
|
|
174
187
|
# Connect to all the devices
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import Optional, Type
|
|
3
|
+
from unittest.mock import MagicMock
|
|
4
|
+
|
|
5
|
+
from bluesky.protocols import Descriptor, Reading
|
|
6
|
+
|
|
7
|
+
from ophyd_async.core.signal_backend import SignalBackend
|
|
8
|
+
from ophyd_async.core.soft_signal_backend import SoftSignalBackend
|
|
9
|
+
from ophyd_async.core.utils import DEFAULT_TIMEOUT, ReadingValueCallback, T
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MockSignalBackend(SignalBackend):
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
datatype: Optional[Type[T]] = None,
|
|
16
|
+
initial_backend: Optional[SignalBackend[T]] = None,
|
|
17
|
+
) -> None:
|
|
18
|
+
if isinstance(initial_backend, MockSignalBackend):
|
|
19
|
+
raise ValueError("Cannot make a MockSignalBackend for a MockSignalBackends")
|
|
20
|
+
|
|
21
|
+
self.initial_backend = initial_backend
|
|
22
|
+
|
|
23
|
+
if datatype is None:
|
|
24
|
+
assert (
|
|
25
|
+
self.initial_backend
|
|
26
|
+
), "Must supply either initial_backend or datatype"
|
|
27
|
+
datatype = self.initial_backend.datatype
|
|
28
|
+
|
|
29
|
+
self.datatype = datatype
|
|
30
|
+
|
|
31
|
+
if not isinstance(self.initial_backend, SoftSignalBackend):
|
|
32
|
+
# If the backend is a hard signal backend, or not provided,
|
|
33
|
+
# then we create a soft signal to mimick it
|
|
34
|
+
|
|
35
|
+
self.soft_backend = SoftSignalBackend(datatype=datatype)
|
|
36
|
+
else:
|
|
37
|
+
self.soft_backend = initial_backend
|
|
38
|
+
|
|
39
|
+
self.mock = MagicMock()
|
|
40
|
+
|
|
41
|
+
self.put_proceeds = asyncio.Event()
|
|
42
|
+
self.put_proceeds.set()
|
|
43
|
+
|
|
44
|
+
def source(self, name: str) -> str:
|
|
45
|
+
self.mock.source(name)
|
|
46
|
+
if self.initial_backend:
|
|
47
|
+
return f"mock+{self.initial_backend.source(name)}"
|
|
48
|
+
return f"mock+{name}"
|
|
49
|
+
|
|
50
|
+
async def connect(self, timeout: float = DEFAULT_TIMEOUT) -> None:
|
|
51
|
+
self.mock.connect(timeout=timeout)
|
|
52
|
+
|
|
53
|
+
async def put(self, value: Optional[T], wait=True, timeout=None):
|
|
54
|
+
self.mock.put(value, wait=wait, timeout=timeout)
|
|
55
|
+
await self.soft_backend.put(value, wait=wait, timeout=timeout)
|
|
56
|
+
|
|
57
|
+
if wait:
|
|
58
|
+
await asyncio.wait_for(self.put_proceeds.wait(), timeout=timeout)
|
|
59
|
+
|
|
60
|
+
def set_value(self, value: T):
|
|
61
|
+
self.mock.set_value(value)
|
|
62
|
+
self.soft_backend.set_value(value)
|
|
63
|
+
|
|
64
|
+
async def get_descriptor(self, source: str) -> Descriptor:
|
|
65
|
+
self.mock.get_descriptor(source)
|
|
66
|
+
return await self.soft_backend.get_descriptor(source)
|
|
67
|
+
|
|
68
|
+
async def get_reading(self) -> Reading:
|
|
69
|
+
self.mock.get_reading()
|
|
70
|
+
return await self.soft_backend.get_reading()
|
|
71
|
+
|
|
72
|
+
async def get_value(self) -> T:
|
|
73
|
+
self.mock.get_value()
|
|
74
|
+
return await self.soft_backend.get_value()
|
|
75
|
+
|
|
76
|
+
async def get_setpoint(self) -> T:
|
|
77
|
+
"""For a soft signal, the setpoint and readback values are the same."""
|
|
78
|
+
self.mock.get_setpoint()
|
|
79
|
+
return await self.soft_backend.get_setpoint()
|
|
80
|
+
|
|
81
|
+
async def get_datakey(self, source: str) -> Descriptor:
|
|
82
|
+
return await self.soft_backend.get_datakey(source)
|
|
83
|
+
|
|
84
|
+
def set_callback(self, callback: Optional[ReadingValueCallback[T]]) -> None:
|
|
85
|
+
self.mock.set_callback(callback)
|
|
86
|
+
self.soft_backend.set_callback(callback)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from contextlib import asynccontextmanager, contextmanager
|
|
2
|
+
from typing import Any, Callable, Generator, Iterable, Iterator, List
|
|
3
|
+
from unittest.mock import ANY
|
|
4
|
+
|
|
5
|
+
from ophyd_async.core.signal import Signal
|
|
6
|
+
from ophyd_async.core.utils import T
|
|
7
|
+
|
|
8
|
+
from .mock_signal_backend import MockSignalBackend
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _get_mock_signal_backend(signal: Signal) -> MockSignalBackend:
|
|
12
|
+
assert isinstance(signal._backend, MockSignalBackend), (
|
|
13
|
+
"Expected to receive a `MockSignalBackend`, instead "
|
|
14
|
+
f" received {type(signal._backend)}. "
|
|
15
|
+
)
|
|
16
|
+
return signal._backend
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def set_mock_value(signal: Signal[T], value: T):
|
|
20
|
+
"""Set the value of a signal that is in mock mode."""
|
|
21
|
+
backend = _get_mock_signal_backend(signal)
|
|
22
|
+
backend.set_value(value)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def set_mock_put_proceeds(signal: Signal[T], proceeds: bool):
|
|
26
|
+
"""Allow or block a put with wait=True from proceeding"""
|
|
27
|
+
backend = _get_mock_signal_backend(signal)
|
|
28
|
+
|
|
29
|
+
if proceeds:
|
|
30
|
+
backend.put_proceeds.set()
|
|
31
|
+
else:
|
|
32
|
+
backend.put_proceeds.clear()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@asynccontextmanager
|
|
36
|
+
async def mock_puts_blocked(*signals: List[Signal]):
|
|
37
|
+
for signal in signals:
|
|
38
|
+
set_mock_put_proceeds(signal, False)
|
|
39
|
+
yield
|
|
40
|
+
for signal in signals:
|
|
41
|
+
set_mock_put_proceeds(signal, True)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def assert_mock_put_called_with(signal: Signal, value: Any, wait=ANY, timeout=ANY):
|
|
45
|
+
backend = _get_mock_signal_backend(signal)
|
|
46
|
+
backend.mock.put.assert_called_with(value, wait=wait, timeout=timeout)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def reset_mock_put_calls(signal: Signal):
|
|
50
|
+
backend = _get_mock_signal_backend(signal)
|
|
51
|
+
backend.mock.put.reset_mock()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class _SetValuesIterator:
|
|
55
|
+
# Garbage collected by the time __del__ is called unless we put it as a
|
|
56
|
+
# global attrbute here.
|
|
57
|
+
require_all_consumed: bool = False
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
signal: Signal,
|
|
62
|
+
values: Iterable[Any],
|
|
63
|
+
require_all_consumed: bool = False,
|
|
64
|
+
):
|
|
65
|
+
self.signal = signal
|
|
66
|
+
self.values = values
|
|
67
|
+
self.require_all_consumed = require_all_consumed
|
|
68
|
+
self.index = 0
|
|
69
|
+
|
|
70
|
+
self.iterator = enumerate(values, start=1)
|
|
71
|
+
|
|
72
|
+
def __iter__(self):
|
|
73
|
+
return self
|
|
74
|
+
|
|
75
|
+
def __next__(self):
|
|
76
|
+
# Will propogate StopIteration
|
|
77
|
+
self.index, next_value = next(self.iterator)
|
|
78
|
+
set_mock_value(self.signal, next_value)
|
|
79
|
+
return next_value
|
|
80
|
+
|
|
81
|
+
def __del__(self):
|
|
82
|
+
if self.require_all_consumed and self.index != len(self.values):
|
|
83
|
+
raise AssertionError("Not all values have been consumed.")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def set_mock_values(
|
|
87
|
+
signal: Signal,
|
|
88
|
+
values: Iterable[Any],
|
|
89
|
+
require_all_consumed: bool = False,
|
|
90
|
+
) -> Iterator[Any]:
|
|
91
|
+
"""Iterator to set a signal to a sequence of values, optionally repeating the
|
|
92
|
+
sequence.
|
|
93
|
+
|
|
94
|
+
Parameters
|
|
95
|
+
----------
|
|
96
|
+
signal:
|
|
97
|
+
A signal with a `MockSignalBackend` backend.
|
|
98
|
+
values:
|
|
99
|
+
An iterable of the values to set the signal to, on each iteration
|
|
100
|
+
the value will be set.
|
|
101
|
+
require_all_consumed:
|
|
102
|
+
If True, an AssertionError will be raised if the iterator is deleted before
|
|
103
|
+
all values have been consumed.
|
|
104
|
+
|
|
105
|
+
Notes
|
|
106
|
+
-----
|
|
107
|
+
Example usage::
|
|
108
|
+
|
|
109
|
+
for value_set in set_mock_values(signal, [1, 2, 3]):
|
|
110
|
+
# do something
|
|
111
|
+
|
|
112
|
+
cm = set_mock_values(signal, 1, 2, 3, require_all_consumed=True):
|
|
113
|
+
next(cm)
|
|
114
|
+
# do something
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
return _SetValuesIterator(
|
|
118
|
+
signal,
|
|
119
|
+
values,
|
|
120
|
+
require_all_consumed=require_all_consumed,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@contextmanager
|
|
125
|
+
def _unset_side_effect_cm(mock):
|
|
126
|
+
yield
|
|
127
|
+
mock.put.side_effect = None
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# linting isn't smart enought to realize @contextmanager will give use a
|
|
131
|
+
# ContextManager[None]
|
|
132
|
+
def callback_on_mock_put(
|
|
133
|
+
signal: Signal, callback: Callable[[T], None]
|
|
134
|
+
) -> Generator[None, None, None]:
|
|
135
|
+
"""For setting a callback when a backend is put to.
|
|
136
|
+
|
|
137
|
+
Can either be used in a context, with the callback being
|
|
138
|
+
unset on exit, or as an ordinary function.
|
|
139
|
+
|
|
140
|
+
Parameters
|
|
141
|
+
----------
|
|
142
|
+
signal:
|
|
143
|
+
A signal with a `MockSignalBackend` backend.
|
|
144
|
+
callback:
|
|
145
|
+
The callback to call when the backend is put to during the context.
|
|
146
|
+
"""
|
|
147
|
+
backend = _get_mock_signal_backend(signal)
|
|
148
|
+
backend.mock.put.side_effect = callback
|
|
149
|
+
return _unset_side_effect_cm(backend.mock)
|
ophyd_async/core/signal.py
CHANGED
|
@@ -24,15 +24,14 @@ from bluesky.protocols import (
|
|
|
24
24
|
Subscribable,
|
|
25
25
|
)
|
|
26
26
|
|
|
27
|
+
from ophyd_async.core.mock_signal_backend import MockSignalBackend
|
|
27
28
|
from ophyd_async.protocols import AsyncConfigurable, AsyncReadable, AsyncStageable
|
|
28
29
|
|
|
29
30
|
from .async_status import AsyncStatus
|
|
30
31
|
from .device import Device
|
|
31
32
|
from .signal_backend import SignalBackend
|
|
32
|
-
from .
|
|
33
|
-
from .utils import DEFAULT_TIMEOUT, Callback,
|
|
34
|
-
|
|
35
|
-
_sim_backends: Dict[Signal, SimSignalBackend] = {}
|
|
33
|
+
from .soft_signal_backend import SoftSignalBackend
|
|
34
|
+
from .utils import DEFAULT_TIMEOUT, Callback, T
|
|
36
35
|
|
|
37
36
|
|
|
38
37
|
def _add_timeout(func):
|
|
@@ -61,17 +60,17 @@ class Signal(Device, Generic[T]):
|
|
|
61
60
|
timeout: Optional[float] = DEFAULT_TIMEOUT,
|
|
62
61
|
name: str = "",
|
|
63
62
|
) -> None:
|
|
64
|
-
super().__init__(name)
|
|
65
63
|
self._timeout = timeout
|
|
66
|
-
self.
|
|
64
|
+
self._initial_backend = self._backend = backend
|
|
65
|
+
super().__init__(name)
|
|
67
66
|
|
|
68
|
-
async def connect(self,
|
|
69
|
-
if
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
67
|
+
async def connect(self, mock=False, timeout=DEFAULT_TIMEOUT):
|
|
68
|
+
if mock and not isinstance(self._backend, MockSignalBackend):
|
|
69
|
+
# Using a soft backend, look to the initial value
|
|
70
|
+
self._backend = MockSignalBackend(
|
|
71
|
+
initial_backend=self._initial_backend,
|
|
72
|
+
)
|
|
73
|
+
self.log.debug(f"Connecting to {self.source}")
|
|
75
74
|
await self._backend.connect(timeout=timeout)
|
|
76
75
|
|
|
77
76
|
@property
|
|
@@ -96,10 +95,12 @@ class _SignalCache(Generic[T]):
|
|
|
96
95
|
self._value: Optional[T] = None
|
|
97
96
|
|
|
98
97
|
self.backend = backend
|
|
98
|
+
signal.log.debug(f"Making subscription on source {signal.source}")
|
|
99
99
|
backend.set_callback(self._callback)
|
|
100
100
|
|
|
101
101
|
def close(self):
|
|
102
102
|
self.backend.set_callback(None)
|
|
103
|
+
self._signal.log.debug(f"Closing subscription on source {self._signal.source}")
|
|
103
104
|
|
|
104
105
|
async def get_reading(self) -> Reading:
|
|
105
106
|
await self._valid.wait()
|
|
@@ -112,6 +113,10 @@ class _SignalCache(Generic[T]):
|
|
|
112
113
|
return self._value
|
|
113
114
|
|
|
114
115
|
def _callback(self, reading: Reading, value: T):
|
|
116
|
+
self._signal.log.debug(
|
|
117
|
+
f"Updated subscription: reading of source {self._signal.source} changed"
|
|
118
|
+
f"from {self._reading} to {reading}"
|
|
119
|
+
)
|
|
115
120
|
self._reading = reading
|
|
116
121
|
self._value = value
|
|
117
122
|
self._valid.set()
|
|
@@ -178,7 +183,9 @@ class SignalR(Signal[T], AsyncReadable, AsyncStageable, Subscribable):
|
|
|
178
183
|
@_add_timeout
|
|
179
184
|
async def get_value(self, cached: Optional[bool] = None) -> T:
|
|
180
185
|
"""The current value"""
|
|
181
|
-
|
|
186
|
+
value = await self._backend_or_cache(cached).get_value()
|
|
187
|
+
self.log.debug(f"get_value() on source {self.source} returned {value}")
|
|
188
|
+
return value
|
|
182
189
|
|
|
183
190
|
def subscribe_value(self, function: Callback[T]):
|
|
184
191
|
"""Subscribe to updates in value of a device"""
|
|
@@ -213,8 +220,15 @@ class SignalW(Signal[T], Movable):
|
|
|
213
220
|
"""Set the value and return a status saying when it's done"""
|
|
214
221
|
if timeout is USE_DEFAULT_TIMEOUT:
|
|
215
222
|
timeout = self._timeout
|
|
216
|
-
|
|
217
|
-
|
|
223
|
+
|
|
224
|
+
async def do_set():
|
|
225
|
+
self.log.debug(f"Putting value {value} to backend at source {self.source}")
|
|
226
|
+
await self._backend.put(value, wait=wait, timeout=timeout)
|
|
227
|
+
self.log.debug(
|
|
228
|
+
f"Successfully put value {value} to backend at source {self.source}"
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
return AsyncStatus(do_set())
|
|
218
232
|
|
|
219
233
|
|
|
220
234
|
class SignalRW(SignalR[T], SignalW[T], Locatable):
|
|
@@ -239,47 +253,29 @@ class SignalX(Signal):
|
|
|
239
253
|
return AsyncStatus(coro)
|
|
240
254
|
|
|
241
255
|
|
|
242
|
-
def set_sim_value(signal: Signal[T], value: T):
|
|
243
|
-
"""Set the value of a signal that is in sim mode."""
|
|
244
|
-
_sim_backends[signal]._set_value(value)
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
def set_sim_put_proceeds(signal: Signal[T], proceeds: bool):
|
|
248
|
-
"""Allow or block a put with wait=True from proceeding"""
|
|
249
|
-
event = _sim_backends[signal].put_proceeds
|
|
250
|
-
if proceeds:
|
|
251
|
-
event.set()
|
|
252
|
-
else:
|
|
253
|
-
event.clear()
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
def set_sim_callback(signal: Signal[T], callback: ReadingValueCallback[T]) -> None:
|
|
257
|
-
"""Monitor the value of a signal that is in sim mode"""
|
|
258
|
-
return _sim_backends[signal].set_callback(callback)
|
|
259
|
-
|
|
260
|
-
|
|
261
256
|
def soft_signal_rw(
|
|
262
257
|
datatype: Optional[Type[T]] = None,
|
|
263
258
|
initial_value: Optional[T] = None,
|
|
264
259
|
name: str = "",
|
|
265
260
|
) -> SignalRW[T]:
|
|
266
|
-
"""Creates a read-writable Signal with a
|
|
267
|
-
signal = SignalRW(
|
|
261
|
+
"""Creates a read-writable Signal with a SoftSignalBackend"""
|
|
262
|
+
signal = SignalRW(SoftSignalBackend(datatype, initial_value), name=name)
|
|
268
263
|
return signal
|
|
269
264
|
|
|
270
265
|
|
|
271
|
-
def
|
|
266
|
+
def soft_signal_r_and_setter(
|
|
272
267
|
datatype: Optional[Type[T]] = None,
|
|
273
268
|
initial_value: Optional[T] = None,
|
|
274
269
|
name: str = "",
|
|
275
|
-
) -> Tuple[SignalR[T],
|
|
276
|
-
"""Returns a tuple of a read-only Signal and
|
|
270
|
+
) -> Tuple[SignalR[T], Callable[[T]]]:
|
|
271
|
+
"""Returns a tuple of a read-only Signal and a callable through
|
|
277
272
|
which the signal can be internally modified within the device. Use
|
|
278
273
|
soft_signal_rw if you want a device that is externally modifiable
|
|
279
274
|
"""
|
|
280
|
-
backend =
|
|
275
|
+
backend = SoftSignalBackend(datatype, initial_value)
|
|
281
276
|
signal = SignalR(backend, name=name)
|
|
282
|
-
|
|
277
|
+
|
|
278
|
+
return (signal, backend.set_value)
|
|
283
279
|
|
|
284
280
|
|
|
285
281
|
async def assert_value(signal: SignalR[T], value: Any) -> None:
|