ophyd-async 0.7.0__py3-none-any.whl → 0.8.0__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 +34 -9
- ophyd_async/core/_detector.py +5 -10
- ophyd_async/core/_device.py +170 -68
- ophyd_async/core/_device_filler.py +269 -0
- ophyd_async/core/_device_save_loader.py +6 -7
- ophyd_async/core/_mock_signal_backend.py +35 -40
- ophyd_async/core/_mock_signal_utils.py +25 -16
- ophyd_async/core/_protocol.py +28 -8
- ophyd_async/core/_readable.py +133 -134
- ophyd_async/core/_signal.py +219 -163
- ophyd_async/core/_signal_backend.py +131 -64
- ophyd_async/core/_soft_signal_backend.py +131 -194
- ophyd_async/core/_status.py +22 -6
- ophyd_async/core/_table.py +102 -100
- ophyd_async/core/_utils.py +143 -32
- 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/_core_logic.py +3 -1
- ophyd_async/epics/adcore/_hdf_writer.py +2 -2
- ophyd_async/epics/adcore/_single_trigger.py +6 -10
- 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 +383 -0
- ophyd_async/epics/core/_pvi_connector.py +91 -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 +7 -2
- 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/epics/testing/__init__.py +24 -0
- ophyd_async/epics/testing/_example_ioc.py +105 -0
- ophyd_async/epics/testing/_utils.py +78 -0
- ophyd_async/epics/testing/test_records.db +152 -0
- ophyd_async/epics/testing/test_records_pva.db +177 -0
- ophyd_async/fastcs/core.py +9 -0
- ophyd_async/fastcs/panda/__init__.py +4 -4
- ophyd_async/fastcs/panda/_block.py +18 -13
- ophyd_async/fastcs/panda/_control.py +3 -5
- ophyd_async/fastcs/panda/_hdf_panda.py +5 -19
- ophyd_async/fastcs/panda/_table.py +30 -52
- ophyd_async/fastcs/panda/_trigger.py +8 -8
- ophyd_async/fastcs/panda/_writer.py +2 -5
- ophyd_async/plan_stubs/_ensure_connected.py +20 -13
- 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 +0 -45
- ophyd_async/tango/{signal → core}/__init__.py +9 -6
- ophyd_async/tango/core/_base_device.py +132 -0
- ophyd_async/tango/{signal → core}/_signal.py +42 -53
- ophyd_async/tango/{base_devices → core}/_tango_readable.py +3 -4
- ophyd_async/tango/{signal → core}/_tango_transport.py +38 -40
- ophyd_async/tango/demo/_counter.py +12 -23
- ophyd_async/tango/demo/_mover.py +13 -13
- {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0.dist-info}/METADATA +52 -55
- ophyd_async-0.8.0.dist-info/RECORD +116 -0
- {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0.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/tango/base_devices/__init__.py +0 -4
- ophyd_async/tango/base_devices/_base_device.py +0 -225
- ophyd_async-0.7.0.dist-info/RECORD +0 -108
- {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0.dist-info}/LICENSE +0 -0
- {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0.dist-info}/entry_points.txt +0 -0
- {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0.dist-info}/top_level.txt +0 -0
ophyd_async/_version.py
CHANGED
ophyd_async/core/__init__.py
CHANGED
|
@@ -5,7 +5,8 @@ from ._detector import (
|
|
|
5
5
|
StandardDetector,
|
|
6
6
|
TriggerInfo,
|
|
7
7
|
)
|
|
8
|
-
from ._device import Device, DeviceCollector, DeviceVector
|
|
8
|
+
from ._device import Device, DeviceCollector, DeviceConnector, DeviceVector
|
|
9
|
+
from ._device_filler import DeviceFiller
|
|
9
10
|
from ._device_save_loader import (
|
|
10
11
|
all_at_once,
|
|
11
12
|
get_signal_values,
|
|
@@ -22,6 +23,7 @@ from ._log import config_ophyd_async_logging
|
|
|
22
23
|
from ._mock_signal_backend import MockSignalBackend
|
|
23
24
|
from ._mock_signal_utils import (
|
|
24
25
|
callback_on_mock_put,
|
|
26
|
+
get_mock,
|
|
25
27
|
get_mock_put,
|
|
26
28
|
mock_puts_blocked,
|
|
27
29
|
reset_mock_put_calls,
|
|
@@ -43,7 +45,12 @@ from ._providers import (
|
|
|
43
45
|
UUIDFilenameProvider,
|
|
44
46
|
YMDPathProvider,
|
|
45
47
|
)
|
|
46
|
-
from ._readable import
|
|
48
|
+
from ._readable import (
|
|
49
|
+
ConfigSignal,
|
|
50
|
+
HintedSignal,
|
|
51
|
+
StandardReadable,
|
|
52
|
+
StandardReadableFormat,
|
|
53
|
+
)
|
|
47
54
|
from ._signal import (
|
|
48
55
|
Signal,
|
|
49
56
|
SignalR,
|
|
@@ -54,6 +61,7 @@ from ._signal import (
|
|
|
54
61
|
assert_emitted,
|
|
55
62
|
assert_reading,
|
|
56
63
|
assert_value,
|
|
64
|
+
observe_signals_value,
|
|
57
65
|
observe_value,
|
|
58
66
|
set_and_wait_for_other_value,
|
|
59
67
|
set_and_wait_for_value,
|
|
@@ -62,9 +70,11 @@ from ._signal import (
|
|
|
62
70
|
wait_for_value,
|
|
63
71
|
)
|
|
64
72
|
from ._signal_backend import (
|
|
65
|
-
|
|
73
|
+
Array1D,
|
|
66
74
|
SignalBackend,
|
|
67
|
-
|
|
75
|
+
SignalDatatype,
|
|
76
|
+
SignalDatatypeT,
|
|
77
|
+
make_datakey,
|
|
68
78
|
)
|
|
69
79
|
from ._soft_signal_backend import SignalMetadata, SoftSignalBackend
|
|
70
80
|
from ._status import AsyncStatus, WatchableAsyncStatus, completed_status
|
|
@@ -73,14 +83,18 @@ from ._utils import (
|
|
|
73
83
|
CALCULATE_TIMEOUT,
|
|
74
84
|
DEFAULT_TIMEOUT,
|
|
75
85
|
CalculatableTimeout,
|
|
86
|
+
Callback,
|
|
87
|
+
LazyMock,
|
|
76
88
|
NotConnected,
|
|
77
|
-
|
|
89
|
+
Reference,
|
|
90
|
+
StrictEnum,
|
|
91
|
+
SubsetEnum,
|
|
78
92
|
T,
|
|
79
93
|
WatcherUpdate,
|
|
80
94
|
get_dtype,
|
|
95
|
+
get_enum_cls,
|
|
81
96
|
get_unique,
|
|
82
97
|
in_micros,
|
|
83
|
-
is_pydantic_model,
|
|
84
98
|
wait_for_connection,
|
|
85
99
|
)
|
|
86
100
|
|
|
@@ -91,8 +105,10 @@ __all__ = [
|
|
|
91
105
|
"StandardDetector",
|
|
92
106
|
"TriggerInfo",
|
|
93
107
|
"Device",
|
|
108
|
+
"DeviceConnector",
|
|
94
109
|
"DeviceCollector",
|
|
95
110
|
"DeviceVector",
|
|
111
|
+
"DeviceFiller",
|
|
96
112
|
"all_at_once",
|
|
97
113
|
"get_signal_values",
|
|
98
114
|
"load_device",
|
|
@@ -108,6 +124,7 @@ __all__ = [
|
|
|
108
124
|
"config_ophyd_async_logging",
|
|
109
125
|
"MockSignalBackend",
|
|
110
126
|
"callback_on_mock_put",
|
|
127
|
+
"get_mock",
|
|
111
128
|
"get_mock_put",
|
|
112
129
|
"mock_puts_blocked",
|
|
113
130
|
"reset_mock_put_calls",
|
|
@@ -131,6 +148,7 @@ __all__ = [
|
|
|
131
148
|
"ConfigSignal",
|
|
132
149
|
"HintedSignal",
|
|
133
150
|
"StandardReadable",
|
|
151
|
+
"StandardReadableFormat",
|
|
134
152
|
"Signal",
|
|
135
153
|
"SignalR",
|
|
136
154
|
"SignalRW",
|
|
@@ -141,30 +159,37 @@ __all__ = [
|
|
|
141
159
|
"assert_reading",
|
|
142
160
|
"assert_value",
|
|
143
161
|
"observe_value",
|
|
162
|
+
"observe_signals_value",
|
|
144
163
|
"set_and_wait_for_value",
|
|
145
164
|
"set_and_wait_for_other_value",
|
|
146
165
|
"soft_signal_r_and_setter",
|
|
147
166
|
"soft_signal_rw",
|
|
148
167
|
"wait_for_value",
|
|
149
|
-
"
|
|
168
|
+
"Array1D",
|
|
150
169
|
"SignalBackend",
|
|
170
|
+
"make_datakey",
|
|
171
|
+
"StrictEnum",
|
|
151
172
|
"SubsetEnum",
|
|
173
|
+
"SignalDatatype",
|
|
174
|
+
"SignalDatatypeT",
|
|
152
175
|
"SignalMetadata",
|
|
153
176
|
"SoftSignalBackend",
|
|
154
177
|
"AsyncStatus",
|
|
155
178
|
"WatchableAsyncStatus",
|
|
156
179
|
"DEFAULT_TIMEOUT",
|
|
157
180
|
"CalculatableTimeout",
|
|
181
|
+
"Callback",
|
|
182
|
+
"LazyMock",
|
|
158
183
|
"CALCULATE_TIMEOUT",
|
|
159
184
|
"NotConnected",
|
|
160
|
-
"
|
|
185
|
+
"Reference",
|
|
161
186
|
"Table",
|
|
162
187
|
"T",
|
|
163
188
|
"WatcherUpdate",
|
|
164
189
|
"get_dtype",
|
|
190
|
+
"get_enum_cls",
|
|
165
191
|
"get_unique",
|
|
166
192
|
"in_micros",
|
|
167
|
-
"is_pydantic_model",
|
|
168
193
|
"wait_for_connection",
|
|
169
194
|
"completed_status",
|
|
170
195
|
]
|
ophyd_async/core/_detector.py
CHANGED
|
@@ -4,11 +4,7 @@ import asyncio
|
|
|
4
4
|
import time
|
|
5
5
|
from abc import ABC, abstractmethod
|
|
6
6
|
from collections.abc import AsyncGenerator, AsyncIterator, Callable, Iterator, Sequence
|
|
7
|
-
from enum import Enum
|
|
8
7
|
from functools import cached_property
|
|
9
|
-
from typing import (
|
|
10
|
-
Generic,
|
|
11
|
-
)
|
|
12
8
|
|
|
13
9
|
from bluesky.protocols import (
|
|
14
10
|
Collectable,
|
|
@@ -23,14 +19,14 @@ from bluesky.protocols import (
|
|
|
23
19
|
from event_model import DataKey
|
|
24
20
|
from pydantic import BaseModel, Field, NonNegativeInt, computed_field
|
|
25
21
|
|
|
26
|
-
from ._device import Device
|
|
22
|
+
from ._device import Device, DeviceConnector
|
|
27
23
|
from ._protocol import AsyncConfigurable, AsyncReadable
|
|
28
24
|
from ._signal import SignalR
|
|
29
25
|
from ._status import AsyncStatus, WatchableAsyncStatus
|
|
30
|
-
from ._utils import DEFAULT_TIMEOUT,
|
|
26
|
+
from ._utils import DEFAULT_TIMEOUT, StrictEnum, WatcherUpdate, merge_gathered_dicts
|
|
31
27
|
|
|
32
28
|
|
|
33
|
-
class DetectorTrigger(
|
|
29
|
+
class DetectorTrigger(StrictEnum):
|
|
34
30
|
"""Type of mechanism for triggering a detector to take frames"""
|
|
35
31
|
|
|
36
32
|
#: Detector generates internal trigger for given rate
|
|
@@ -172,7 +168,6 @@ class StandardDetector(
|
|
|
172
168
|
Flyable,
|
|
173
169
|
Collectable,
|
|
174
170
|
WritesStreamAssets,
|
|
175
|
-
Generic[T],
|
|
176
171
|
):
|
|
177
172
|
"""
|
|
178
173
|
Useful detector base class for step and fly scanning detectors.
|
|
@@ -185,6 +180,7 @@ class StandardDetector(
|
|
|
185
180
|
writer: DetectorWriter,
|
|
186
181
|
config_sigs: Sequence[SignalR] = (),
|
|
187
182
|
name: str = "",
|
|
183
|
+
connector: DeviceConnector | None = None,
|
|
188
184
|
) -> None:
|
|
189
185
|
"""
|
|
190
186
|
Constructor
|
|
@@ -213,8 +209,7 @@ class StandardDetector(
|
|
|
213
209
|
self._completable_frames: int = 0
|
|
214
210
|
self._number_of_triggers_iter: Iterator[int] | None = None
|
|
215
211
|
self._initial_frame: int = 0
|
|
216
|
-
|
|
217
|
-
super().__init__(name)
|
|
212
|
+
super().__init__(name, connector=connector)
|
|
218
213
|
|
|
219
214
|
@property
|
|
220
215
|
def controller(self) -> DetectorController:
|
ophyd_async/core/_device.py
CHANGED
|
@@ -1,39 +1,82 @@
|
|
|
1
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import sys
|
|
5
|
-
from collections.abc import Coroutine,
|
|
5
|
+
from collections.abc import Coroutine, Iterator, Mapping, MutableMapping
|
|
6
6
|
from functools import cached_property
|
|
7
7
|
from logging import LoggerAdapter, getLogger
|
|
8
|
-
from typing import
|
|
9
|
-
Any,
|
|
10
|
-
Optional,
|
|
11
|
-
TypeVar,
|
|
12
|
-
)
|
|
8
|
+
from typing import Any, TypeVar
|
|
13
9
|
|
|
14
10
|
from bluesky.protocols import HasName
|
|
15
11
|
from bluesky.run_engine import call_in_bluesky_event_loop, in_bluesky_event_loop
|
|
16
12
|
|
|
17
|
-
from .
|
|
13
|
+
from ._protocol import Connectable
|
|
14
|
+
from ._utils import DEFAULT_TIMEOUT, LazyMock, NotConnected, wait_for_connection
|
|
18
15
|
|
|
19
16
|
|
|
20
|
-
class
|
|
21
|
-
"""
|
|
17
|
+
class DeviceConnector:
|
|
18
|
+
"""Defines how a `Device` should be connected and type hints processed."""
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
def create_children_from_annotations(self, device: Device):
|
|
21
|
+
"""Used when children can be created from introspecting the hardware.
|
|
22
|
+
|
|
23
|
+
Some control systems allow introspection of a device to determine what
|
|
24
|
+
children it has. To allow this to work nicely with typing we add these
|
|
25
|
+
hints to the Device like so::
|
|
26
|
+
|
|
27
|
+
my_signal: SignalRW[int]
|
|
28
|
+
my_device: MyDevice
|
|
29
|
+
|
|
30
|
+
This method will be run during ``Device.__init__``, and is responsible
|
|
31
|
+
for turning all of those type hints into real Signal and Device instances.
|
|
32
|
+
|
|
33
|
+
Subsequent runs of this function should do nothing, to allow it to be
|
|
34
|
+
called early in Devices that need to pass references to their children
|
|
35
|
+
during ``__init__``.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
async def connect_mock(self, device: Device, mock: LazyMock):
|
|
39
|
+
# Connect serially, no errors to gather up as in mock mode
|
|
40
|
+
exceptions: dict[str, Exception] = {}
|
|
41
|
+
for name, child_device in device.children():
|
|
42
|
+
try:
|
|
43
|
+
await child_device.connect(mock=mock.child(name))
|
|
44
|
+
except Exception as e:
|
|
45
|
+
exceptions[name] = e
|
|
46
|
+
if exceptions:
|
|
47
|
+
raise NotConnected.with_other_exceptions_logged(exceptions)
|
|
48
|
+
|
|
49
|
+
async def connect_real(self, device: Device, timeout: float, force_reconnect: bool):
|
|
50
|
+
"""Used during ``Device.connect``.
|
|
51
|
+
|
|
52
|
+
This is called when a previous connect has not been done, or has been
|
|
53
|
+
done in a different mock more. It should connect the Device and all its
|
|
54
|
+
children.
|
|
55
|
+
"""
|
|
56
|
+
# Connect in parallel, gathering up NotConnected errors
|
|
57
|
+
coros = {
|
|
58
|
+
name: child_device.connect(timeout=timeout, force_reconnect=force_reconnect)
|
|
59
|
+
for name, child_device in device.children()
|
|
60
|
+
}
|
|
61
|
+
await wait_for_connection(**coros)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class Device(HasName, Connectable):
|
|
65
|
+
"""Common base class for all Ophyd Async Devices."""
|
|
25
66
|
|
|
26
67
|
_name: str = ""
|
|
27
68
|
#: The parent Device if it exists
|
|
28
|
-
parent:
|
|
69
|
+
parent: Device | None = None
|
|
29
70
|
# None if connect hasn't started, a Task if it has
|
|
30
71
|
_connect_task: asyncio.Task | None = None
|
|
72
|
+
# The mock if we have connected in mock mode
|
|
73
|
+
_mock: LazyMock | None = None
|
|
31
74
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
75
|
+
def __init__(
|
|
76
|
+
self, name: str = "", connector: DeviceConnector | None = None
|
|
77
|
+
) -> None:
|
|
78
|
+
self._connector = connector or DeviceConnector()
|
|
79
|
+
self._connector.create_children_from_annotations(self)
|
|
37
80
|
self.set_name(name)
|
|
38
81
|
|
|
39
82
|
@property
|
|
@@ -42,16 +85,18 @@ class Device(HasName):
|
|
|
42
85
|
return self._name
|
|
43
86
|
|
|
44
87
|
@cached_property
|
|
45
|
-
def
|
|
88
|
+
def _child_devices(self) -> dict[str, Device]:
|
|
89
|
+
return {}
|
|
90
|
+
|
|
91
|
+
def children(self) -> Iterator[tuple[str, Device]]:
|
|
92
|
+
yield from self._child_devices.items()
|
|
93
|
+
|
|
94
|
+
@cached_property
|
|
95
|
+
def log(self) -> LoggerAdapter:
|
|
46
96
|
return LoggerAdapter(
|
|
47
97
|
getLogger("ophyd_async.devices"), {"ophyd_async_device_name": self.name}
|
|
48
98
|
)
|
|
49
99
|
|
|
50
|
-
def children(self) -> Iterator[tuple[str, "Device"]]:
|
|
51
|
-
for attr_name, attr in self.__dict__.items():
|
|
52
|
-
if attr_name != "parent" and isinstance(attr, Device):
|
|
53
|
-
yield attr_name, attr
|
|
54
|
-
|
|
55
100
|
def set_name(self, name: str):
|
|
56
101
|
"""Set ``self.name=name`` and each ``self.child.name=name+"-child"``.
|
|
57
102
|
|
|
@@ -60,23 +105,37 @@ class Device(HasName):
|
|
|
60
105
|
name:
|
|
61
106
|
New name to set
|
|
62
107
|
"""
|
|
63
|
-
|
|
64
|
-
# Ensure self.log is recreated after a name change
|
|
65
|
-
if hasattr(self, "log"):
|
|
66
|
-
del self.log
|
|
67
|
-
|
|
68
108
|
self._name = name
|
|
69
|
-
|
|
70
|
-
|
|
109
|
+
# Ensure logger is recreated after a name change
|
|
110
|
+
if "log" in self.__dict__:
|
|
111
|
+
del self.log
|
|
112
|
+
for child_name, child in self.children():
|
|
113
|
+
child_name = f"{self.name}-{child_name.strip('_')}" if self.name else ""
|
|
71
114
|
child.set_name(child_name)
|
|
72
|
-
|
|
115
|
+
|
|
116
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
117
|
+
# Bear in mind that this function is called *a lot*, so
|
|
118
|
+
# we need to make sure nothing expensive happens in it...
|
|
119
|
+
if name == "parent":
|
|
120
|
+
if self.parent not in (value, None):
|
|
121
|
+
raise TypeError(
|
|
122
|
+
f"Cannot set the parent of {self} to be {value}: "
|
|
123
|
+
f"it is already a child of {self.parent}"
|
|
124
|
+
)
|
|
125
|
+
# ...hence not doing an isinstance check for attributes we
|
|
126
|
+
# know not to be Devices
|
|
127
|
+
elif name not in _not_device_attrs and isinstance(value, Device):
|
|
128
|
+
value.parent = self
|
|
129
|
+
self._child_devices[name] = value
|
|
130
|
+
# ...and avoiding the super call as we know it resolves to `object`
|
|
131
|
+
return object.__setattr__(self, name, value)
|
|
73
132
|
|
|
74
133
|
async def connect(
|
|
75
134
|
self,
|
|
76
|
-
mock: bool = False,
|
|
135
|
+
mock: bool | LazyMock = False,
|
|
77
136
|
timeout: float = DEFAULT_TIMEOUT,
|
|
78
137
|
force_reconnect: bool = False,
|
|
79
|
-
):
|
|
138
|
+
) -> None:
|
|
80
139
|
"""Connect self and all child Devices.
|
|
81
140
|
|
|
82
141
|
Contains a timeout that gets propagated to child.connect methods.
|
|
@@ -88,41 +147,45 @@ class Device(HasName):
|
|
|
88
147
|
timeout:
|
|
89
148
|
Time to wait before failing with a TimeoutError.
|
|
90
149
|
"""
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
150
|
+
if mock:
|
|
151
|
+
# Always connect in mock mode serially
|
|
152
|
+
if isinstance(mock, LazyMock):
|
|
153
|
+
# Use the provided mock
|
|
154
|
+
self._mock = mock
|
|
155
|
+
elif not self._mock:
|
|
156
|
+
# Make one
|
|
157
|
+
self._mock = LazyMock()
|
|
158
|
+
await self._connector.connect_mock(self, self._mock)
|
|
159
|
+
else:
|
|
160
|
+
# Try to cache the connect in real mode
|
|
161
|
+
can_use_previous_connect = (
|
|
162
|
+
self._mock is None
|
|
163
|
+
and self._connect_task
|
|
164
|
+
and not (self._connect_task.done() and self._connect_task.exception())
|
|
100
165
|
)
|
|
101
|
-
|
|
166
|
+
if force_reconnect or not can_use_previous_connect:
|
|
167
|
+
self._mock = None
|
|
168
|
+
coro = self._connector.connect_real(self, timeout, force_reconnect)
|
|
169
|
+
self._connect_task = asyncio.create_task(coro)
|
|
170
|
+
assert self._connect_task, "Connect task not created, this shouldn't happen"
|
|
171
|
+
# Wait for it to complete
|
|
172
|
+
await self._connect_task
|
|
102
173
|
|
|
103
|
-
# If previous connect with same args has started and not errored, can use it
|
|
104
|
-
can_use_previous_connect = self._connect_task and not (
|
|
105
|
-
self._connect_task.done() and self._connect_task.exception()
|
|
106
|
-
)
|
|
107
|
-
if force_reconnect or not can_use_previous_connect:
|
|
108
|
-
# Kick off a connection
|
|
109
|
-
coros = {
|
|
110
|
-
name: child_device.connect(
|
|
111
|
-
mock, timeout=timeout, force_reconnect=force_reconnect
|
|
112
|
-
)
|
|
113
|
-
for name, child_device in self.children()
|
|
114
|
-
}
|
|
115
|
-
self._connect_task = asyncio.create_task(wait_for_connection(**coros))
|
|
116
174
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
175
|
+
_not_device_attrs = {
|
|
176
|
+
"_name",
|
|
177
|
+
"_children",
|
|
178
|
+
"_connector",
|
|
179
|
+
"_timeout",
|
|
180
|
+
"_mock",
|
|
181
|
+
"_connect_task",
|
|
182
|
+
}
|
|
120
183
|
|
|
121
184
|
|
|
122
|
-
|
|
185
|
+
DeviceT = TypeVar("DeviceT", bound=Device)
|
|
123
186
|
|
|
124
187
|
|
|
125
|
-
class DeviceVector(
|
|
188
|
+
class DeviceVector(MutableMapping[int, DeviceT], Device):
|
|
126
189
|
"""
|
|
127
190
|
Defines device components with indices.
|
|
128
191
|
|
|
@@ -131,10 +194,49 @@ class DeviceVector(dict[int, VT], Device):
|
|
|
131
194
|
:class:`~ophyd_async.epics.demo.DynamicSensorGroup`
|
|
132
195
|
"""
|
|
133
196
|
|
|
134
|
-
def
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
197
|
+
def __init__(
|
|
198
|
+
self,
|
|
199
|
+
children: Mapping[int, DeviceT],
|
|
200
|
+
name: str = "",
|
|
201
|
+
) -> None:
|
|
202
|
+
self._children: dict[int, DeviceT] = {}
|
|
203
|
+
self.update(children)
|
|
204
|
+
super().__init__(name=name)
|
|
205
|
+
|
|
206
|
+
def __setattr__(self, name: str, child: Any) -> None:
|
|
207
|
+
if name != "parent" and isinstance(child, Device):
|
|
208
|
+
raise AttributeError(
|
|
209
|
+
"DeviceVector can only have integer named children, "
|
|
210
|
+
"set via device_vector[i] = child"
|
|
211
|
+
)
|
|
212
|
+
super().__setattr__(name, child)
|
|
213
|
+
|
|
214
|
+
def __getitem__(self, key: int) -> DeviceT:
|
|
215
|
+
return self._children[key]
|
|
216
|
+
|
|
217
|
+
def __setitem__(self, key: int, value: DeviceT) -> None:
|
|
218
|
+
# Check the types on entry to dict to make sure we can't accidentally
|
|
219
|
+
# make a non-integer named child
|
|
220
|
+
assert isinstance(key, int), f"Expected int, got {key}"
|
|
221
|
+
assert isinstance(value, Device), f"Expected Device, got {value}"
|
|
222
|
+
self._children[key] = value
|
|
223
|
+
value.parent = self
|
|
224
|
+
|
|
225
|
+
def __delitem__(self, key: int) -> None:
|
|
226
|
+
del self._children[key]
|
|
227
|
+
|
|
228
|
+
def __iter__(self) -> Iterator[int]:
|
|
229
|
+
yield from self._children
|
|
230
|
+
|
|
231
|
+
def __len__(self) -> int:
|
|
232
|
+
return len(self._children)
|
|
233
|
+
|
|
234
|
+
def children(self) -> Iterator[tuple[str, Device]]:
|
|
235
|
+
for key, child in self._children.items():
|
|
236
|
+
yield str(key), child
|
|
237
|
+
|
|
238
|
+
def __hash__(self): # to allow DeviceVector to be used as dict keys and in sets
|
|
239
|
+
return hash(id(self))
|
|
138
240
|
|
|
139
241
|
|
|
140
242
|
class DeviceCollector:
|
|
@@ -195,12 +297,12 @@ class DeviceCollector:
|
|
|
195
297
|
), "No previous frame to the one with self in it, this shouldn't happen"
|
|
196
298
|
return caller_frame.f_locals
|
|
197
299
|
|
|
198
|
-
def __enter__(self) ->
|
|
300
|
+
def __enter__(self) -> DeviceCollector:
|
|
199
301
|
# Stash the names that were defined before we were called
|
|
200
302
|
self._names_on_enter = set(self._caller_locals())
|
|
201
303
|
return self
|
|
202
304
|
|
|
203
|
-
async def __aenter__(self) ->
|
|
305
|
+
async def __aenter__(self) -> DeviceCollector:
|
|
204
306
|
return self.__enter__()
|
|
205
307
|
|
|
206
308
|
async def _on_exit(self) -> None:
|