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/_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,
|
|
@@ -62,9 +69,11 @@ from ._signal import (
|
|
|
62
69
|
wait_for_value,
|
|
63
70
|
)
|
|
64
71
|
from ._signal_backend import (
|
|
65
|
-
|
|
72
|
+
Array1D,
|
|
66
73
|
SignalBackend,
|
|
67
|
-
|
|
74
|
+
SignalDatatype,
|
|
75
|
+
SignalDatatypeT,
|
|
76
|
+
make_datakey,
|
|
68
77
|
)
|
|
69
78
|
from ._soft_signal_backend import SignalMetadata, SoftSignalBackend
|
|
70
79
|
from ._status import AsyncStatus, WatchableAsyncStatus, completed_status
|
|
@@ -73,14 +82,17 @@ from ._utils import (
|
|
|
73
82
|
CALCULATE_TIMEOUT,
|
|
74
83
|
DEFAULT_TIMEOUT,
|
|
75
84
|
CalculatableTimeout,
|
|
85
|
+
Callback,
|
|
76
86
|
NotConnected,
|
|
77
|
-
|
|
87
|
+
Reference,
|
|
88
|
+
StrictEnum,
|
|
89
|
+
SubsetEnum,
|
|
78
90
|
T,
|
|
79
91
|
WatcherUpdate,
|
|
80
92
|
get_dtype,
|
|
93
|
+
get_enum_cls,
|
|
81
94
|
get_unique,
|
|
82
95
|
in_micros,
|
|
83
|
-
is_pydantic_model,
|
|
84
96
|
wait_for_connection,
|
|
85
97
|
)
|
|
86
98
|
|
|
@@ -91,8 +103,10 @@ __all__ = [
|
|
|
91
103
|
"StandardDetector",
|
|
92
104
|
"TriggerInfo",
|
|
93
105
|
"Device",
|
|
106
|
+
"DeviceConnector",
|
|
94
107
|
"DeviceCollector",
|
|
95
108
|
"DeviceVector",
|
|
109
|
+
"DeviceFiller",
|
|
96
110
|
"all_at_once",
|
|
97
111
|
"get_signal_values",
|
|
98
112
|
"load_device",
|
|
@@ -108,6 +122,7 @@ __all__ = [
|
|
|
108
122
|
"config_ophyd_async_logging",
|
|
109
123
|
"MockSignalBackend",
|
|
110
124
|
"callback_on_mock_put",
|
|
125
|
+
"get_mock",
|
|
111
126
|
"get_mock_put",
|
|
112
127
|
"mock_puts_blocked",
|
|
113
128
|
"reset_mock_put_calls",
|
|
@@ -131,6 +146,7 @@ __all__ = [
|
|
|
131
146
|
"ConfigSignal",
|
|
132
147
|
"HintedSignal",
|
|
133
148
|
"StandardReadable",
|
|
149
|
+
"StandardReadableFormat",
|
|
134
150
|
"Signal",
|
|
135
151
|
"SignalR",
|
|
136
152
|
"SignalRW",
|
|
@@ -146,25 +162,30 @@ __all__ = [
|
|
|
146
162
|
"soft_signal_r_and_setter",
|
|
147
163
|
"soft_signal_rw",
|
|
148
164
|
"wait_for_value",
|
|
149
|
-
"
|
|
165
|
+
"Array1D",
|
|
150
166
|
"SignalBackend",
|
|
167
|
+
"make_datakey",
|
|
168
|
+
"StrictEnum",
|
|
151
169
|
"SubsetEnum",
|
|
170
|
+
"SignalDatatype",
|
|
171
|
+
"SignalDatatypeT",
|
|
152
172
|
"SignalMetadata",
|
|
153
173
|
"SoftSignalBackend",
|
|
154
174
|
"AsyncStatus",
|
|
155
175
|
"WatchableAsyncStatus",
|
|
156
176
|
"DEFAULT_TIMEOUT",
|
|
157
177
|
"CalculatableTimeout",
|
|
178
|
+
"Callback",
|
|
158
179
|
"CALCULATE_TIMEOUT",
|
|
159
180
|
"NotConnected",
|
|
160
|
-
"
|
|
181
|
+
"Reference",
|
|
161
182
|
"Table",
|
|
162
183
|
"T",
|
|
163
184
|
"WatcherUpdate",
|
|
164
185
|
"get_dtype",
|
|
186
|
+
"get_enum_cls",
|
|
165
187
|
"get_unique",
|
|
166
188
|
"in_micros",
|
|
167
|
-
"is_pydantic_model",
|
|
168
189
|
"wait_for_connection",
|
|
169
190
|
"completed_status",
|
|
170
191
|
]
|
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,81 @@
|
|
|
1
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import sys
|
|
5
|
-
from collections.abc import Coroutine,
|
|
6
|
-
from functools import cached_property
|
|
5
|
+
from collections.abc import Coroutine, Iterator, Mapping, MutableMapping
|
|
7
6
|
from logging import LoggerAdapter, getLogger
|
|
8
|
-
from typing import
|
|
9
|
-
|
|
10
|
-
Optional,
|
|
11
|
-
TypeVar,
|
|
12
|
-
)
|
|
7
|
+
from typing import Any, TypeVar
|
|
8
|
+
from unittest.mock import Mock
|
|
13
9
|
|
|
14
10
|
from bluesky.protocols import HasName
|
|
15
|
-
from bluesky.run_engine import call_in_bluesky_event_loop
|
|
11
|
+
from bluesky.run_engine import call_in_bluesky_event_loop, in_bluesky_event_loop
|
|
16
12
|
|
|
13
|
+
from ._protocol import Connectable
|
|
17
14
|
from ._utils import DEFAULT_TIMEOUT, NotConnected, wait_for_connection
|
|
18
15
|
|
|
16
|
+
_device_mocks: dict[Device, Mock] = {}
|
|
19
17
|
|
|
20
|
-
class Device(HasName):
|
|
21
|
-
"""Common base class for all Ophyd Async Devices.
|
|
22
18
|
|
|
23
|
-
|
|
24
|
-
"""
|
|
19
|
+
class DeviceConnector:
|
|
20
|
+
"""Defines how a `Device` should be connected and type hints processed."""
|
|
21
|
+
|
|
22
|
+
def create_children_from_annotations(self, device: Device):
|
|
23
|
+
"""Used when children can be created from introspecting the hardware.
|
|
24
|
+
|
|
25
|
+
Some control systems allow introspection of a device to determine what
|
|
26
|
+
children it has. To allow this to work nicely with typing we add these
|
|
27
|
+
hints to the Device like so::
|
|
28
|
+
|
|
29
|
+
my_signal: SignalRW[int]
|
|
30
|
+
my_device: MyDevice
|
|
31
|
+
|
|
32
|
+
This method will be run during ``Device.__init__``, and is responsible
|
|
33
|
+
for turning all of those type hints into real Signal and Device instances.
|
|
34
|
+
|
|
35
|
+
Subsequent runs of this function should do nothing, to allow it to be
|
|
36
|
+
called early in Devices that need to pass references to their children
|
|
37
|
+
during ``__init__``.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
async def connect(
|
|
41
|
+
self,
|
|
42
|
+
device: Device,
|
|
43
|
+
mock: bool | Mock,
|
|
44
|
+
timeout: float,
|
|
45
|
+
force_reconnect: bool,
|
|
46
|
+
):
|
|
47
|
+
"""Used during ``Device.connect``.
|
|
48
|
+
|
|
49
|
+
This is called when a previous connect has not been done, or has been
|
|
50
|
+
done in a different mock more. It should connect the Device and all its
|
|
51
|
+
children.
|
|
52
|
+
"""
|
|
53
|
+
coros = {}
|
|
54
|
+
for name, child_device in device.children():
|
|
55
|
+
child_mock = getattr(mock, name) if mock else mock # Mock() or False
|
|
56
|
+
coros[name] = child_device.connect(
|
|
57
|
+
mock=child_mock, timeout=timeout, force_reconnect=force_reconnect
|
|
58
|
+
)
|
|
59
|
+
await wait_for_connection(**coros)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class Device(HasName, Connectable):
|
|
63
|
+
"""Common base class for all Ophyd Async Devices."""
|
|
25
64
|
|
|
26
65
|
_name: str = ""
|
|
27
66
|
#: The parent Device if it exists
|
|
28
|
-
parent:
|
|
67
|
+
parent: Device | None = None
|
|
29
68
|
# None if connect hasn't started, a Task if it has
|
|
30
69
|
_connect_task: asyncio.Task | None = None
|
|
70
|
+
# If not None, then this is the mock arg of the previous connect
|
|
71
|
+
# to let us know if we can reuse an existing connection
|
|
72
|
+
_connect_mock_arg: bool | None = None
|
|
31
73
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
74
|
+
def __init__(
|
|
75
|
+
self, name: str = "", connector: DeviceConnector | None = None
|
|
76
|
+
) -> None:
|
|
77
|
+
self._connector = connector or DeviceConnector()
|
|
78
|
+
self._connector.create_children_from_annotations(self)
|
|
37
79
|
self.set_name(name)
|
|
38
80
|
|
|
39
81
|
@property
|
|
@@ -41,13 +83,7 @@ class Device(HasName):
|
|
|
41
83
|
"""Return the name of the Device"""
|
|
42
84
|
return self._name
|
|
43
85
|
|
|
44
|
-
|
|
45
|
-
def log(self):
|
|
46
|
-
return LoggerAdapter(
|
|
47
|
-
getLogger("ophyd_async.devices"), {"ophyd_async_device_name": self.name}
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
def children(self) -> Iterator[tuple[str, "Device"]]:
|
|
86
|
+
def children(self) -> Iterator[tuple[str, Device]]:
|
|
51
87
|
for attr_name, attr in self.__dict__.items():
|
|
52
88
|
if attr_name != "parent" and isinstance(attr, Device):
|
|
53
89
|
yield attr_name, attr
|
|
@@ -60,23 +96,32 @@ class Device(HasName):
|
|
|
60
96
|
name:
|
|
61
97
|
New name to set
|
|
62
98
|
"""
|
|
63
|
-
|
|
64
|
-
# Ensure self.log is recreated after a name change
|
|
65
|
-
if hasattr(self, "log"):
|
|
66
|
-
del self.log
|
|
67
|
-
|
|
68
99
|
self._name = name
|
|
69
|
-
|
|
70
|
-
|
|
100
|
+
# Ensure self.log is recreated after a name change
|
|
101
|
+
self.log = LoggerAdapter(
|
|
102
|
+
getLogger("ophyd_async.devices"), {"ophyd_async_device_name": self.name}
|
|
103
|
+
)
|
|
104
|
+
for child_name, child in self.children():
|
|
105
|
+
child_name = f"{self.name}-{child_name.strip('_')}" if self.name else ""
|
|
71
106
|
child.set_name(child_name)
|
|
72
|
-
|
|
107
|
+
|
|
108
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
109
|
+
if name == "parent":
|
|
110
|
+
if self.parent not in (value, None):
|
|
111
|
+
raise TypeError(
|
|
112
|
+
f"Cannot set the parent of {self} to be {value}: "
|
|
113
|
+
f"it is already a child of {self.parent}"
|
|
114
|
+
)
|
|
115
|
+
elif isinstance(value, Device):
|
|
116
|
+
value.parent = self
|
|
117
|
+
return super().__setattr__(name, value)
|
|
73
118
|
|
|
74
119
|
async def connect(
|
|
75
120
|
self,
|
|
76
|
-
mock: bool = False,
|
|
121
|
+
mock: bool | Mock = False,
|
|
77
122
|
timeout: float = DEFAULT_TIMEOUT,
|
|
78
123
|
force_reconnect: bool = False,
|
|
79
|
-
):
|
|
124
|
+
) -> None:
|
|
80
125
|
"""Connect self and all child Devices.
|
|
81
126
|
|
|
82
127
|
Contains a timeout that gets propagated to child.connect methods.
|
|
@@ -88,41 +133,32 @@ class Device(HasName):
|
|
|
88
133
|
timeout:
|
|
89
134
|
Time to wait before failing with a TimeoutError.
|
|
90
135
|
"""
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
and self.
|
|
95
|
-
|
|
96
|
-
raise RuntimeError(
|
|
97
|
-
f"`connect(mock={mock})` called on a `Device` where the previous "
|
|
98
|
-
f"connect was `mock={self._previous_connect_was_mock}`. Changing mock "
|
|
99
|
-
"value between connects is not permitted."
|
|
100
|
-
)
|
|
101
|
-
self._previous_connect_was_mock = mock
|
|
102
|
-
|
|
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()
|
|
136
|
+
uses_mock = bool(mock)
|
|
137
|
+
can_use_previous_connect = (
|
|
138
|
+
uses_mock is self._connect_mock_arg
|
|
139
|
+
and self._connect_task
|
|
140
|
+
and not (self._connect_task.done() and self._connect_task.exception())
|
|
106
141
|
)
|
|
142
|
+
if mock is True:
|
|
143
|
+
mock = Mock() # create a new Mock if one not provided
|
|
107
144
|
if force_reconnect or not can_use_previous_connect:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
self._connect_task = asyncio.create_task(wait_for_connection(**coros))
|
|
145
|
+
self._connect_mock_arg = uses_mock
|
|
146
|
+
if self._connect_mock_arg:
|
|
147
|
+
_device_mocks[self] = mock
|
|
148
|
+
coro = self._connector.connect(
|
|
149
|
+
device=self, mock=mock, timeout=timeout, force_reconnect=force_reconnect
|
|
150
|
+
)
|
|
151
|
+
self._connect_task = asyncio.create_task(coro)
|
|
116
152
|
|
|
117
153
|
assert self._connect_task, "Connect task not created, this shouldn't happen"
|
|
118
154
|
# Wait for it to complete
|
|
119
155
|
await self._connect_task
|
|
120
156
|
|
|
121
157
|
|
|
122
|
-
|
|
158
|
+
DeviceT = TypeVar("DeviceT", bound=Device)
|
|
123
159
|
|
|
124
160
|
|
|
125
|
-
class DeviceVector(
|
|
161
|
+
class DeviceVector(MutableMapping[int, DeviceT], Device):
|
|
126
162
|
"""
|
|
127
163
|
Defines device components with indices.
|
|
128
164
|
|
|
@@ -131,10 +167,48 @@ class DeviceVector(dict[int, VT], Device):
|
|
|
131
167
|
:class:`~ophyd_async.epics.demo.DynamicSensorGroup`
|
|
132
168
|
"""
|
|
133
169
|
|
|
134
|
-
def
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
170
|
+
def __init__(
|
|
171
|
+
self,
|
|
172
|
+
children: Mapping[int, DeviceT],
|
|
173
|
+
name: str = "",
|
|
174
|
+
) -> None:
|
|
175
|
+
self._children = dict(children)
|
|
176
|
+
super().__init__(name=name)
|
|
177
|
+
|
|
178
|
+
def __setattr__(self, name: str, child: Any) -> None:
|
|
179
|
+
if name != "parent" and isinstance(child, Device):
|
|
180
|
+
raise AttributeError(
|
|
181
|
+
"DeviceVector can only have integer named children, "
|
|
182
|
+
"set via device_vector[i] = child"
|
|
183
|
+
)
|
|
184
|
+
super().__setattr__(name, child)
|
|
185
|
+
|
|
186
|
+
def __getitem__(self, key: int) -> DeviceT:
|
|
187
|
+
return self._children[key]
|
|
188
|
+
|
|
189
|
+
def __setitem__(self, key: int, value: DeviceT) -> None:
|
|
190
|
+
# Check the types on entry to dict to make sure we can't accidentally
|
|
191
|
+
# make a non-integer named child
|
|
192
|
+
assert isinstance(key, int), f"Expected int, got {key}"
|
|
193
|
+
assert isinstance(value, Device), f"Expected Device, got {value}"
|
|
194
|
+
self._children[key] = value
|
|
195
|
+
value.parent = self
|
|
196
|
+
|
|
197
|
+
def __delitem__(self, key: int) -> None:
|
|
198
|
+
del self._children[key]
|
|
199
|
+
|
|
200
|
+
def __iter__(self) -> Iterator[int]:
|
|
201
|
+
yield from self._children
|
|
202
|
+
|
|
203
|
+
def __len__(self) -> int:
|
|
204
|
+
return len(self._children)
|
|
205
|
+
|
|
206
|
+
def children(self) -> Iterator[tuple[str, Device]]:
|
|
207
|
+
for key, child in self._children.items():
|
|
208
|
+
yield str(key), child
|
|
209
|
+
|
|
210
|
+
def __hash__(self): # to allow DeviceVector to be used as dict keys and in sets
|
|
211
|
+
return hash(id(self))
|
|
138
212
|
|
|
139
213
|
|
|
140
214
|
class DeviceCollector:
|
|
@@ -195,12 +269,12 @@ class DeviceCollector:
|
|
|
195
269
|
), "No previous frame to the one with self in it, this shouldn't happen"
|
|
196
270
|
return caller_frame.f_locals
|
|
197
271
|
|
|
198
|
-
def __enter__(self) ->
|
|
272
|
+
def __enter__(self) -> DeviceCollector:
|
|
199
273
|
# Stash the names that were defined before we were called
|
|
200
274
|
self._names_on_enter = set(self._caller_locals())
|
|
201
275
|
return self
|
|
202
276
|
|
|
203
|
-
async def __aenter__(self) ->
|
|
277
|
+
async def __aenter__(self) -> DeviceCollector:
|
|
204
278
|
return self.__enter__()
|
|
205
279
|
|
|
206
280
|
async def _on_exit(self) -> None:
|
|
@@ -224,6 +298,11 @@ class DeviceCollector:
|
|
|
224
298
|
await self._on_exit()
|
|
225
299
|
|
|
226
300
|
def __exit__(self, type_, value, traceback):
|
|
301
|
+
if in_bluesky_event_loop():
|
|
302
|
+
raise RuntimeError(
|
|
303
|
+
"Cannot use DeviceConnector inside a plan, instead use "
|
|
304
|
+
"`yield from ophyd_async.plan_stubs.ensure_connected(device)`"
|
|
305
|
+
)
|
|
227
306
|
self._objects_on_exit = self._caller_locals()
|
|
228
307
|
try:
|
|
229
308
|
fut = call_in_bluesky_event_loop(self._on_exit())
|