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.
- ophyd_async/_version.py +2 -2
- ophyd_async/core/__init__.py +23 -8
- ophyd_async/core/_detector.py +5 -10
- ophyd_async/core/_device.py +139 -66
- ophyd_async/core/_device_filler.py +191 -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 +5 -5
- 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 +71 -18
- ophyd_async/epics/adaravis/_aravis_controller.py +2 -2
- ophyd_async/epics/adaravis/_aravis_io.py +7 -5
- ophyd_async/epics/adcore/_core_io.py +4 -6
- ophyd_async/epics/adcore/_hdf_writer.py +2 -2
- 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 +3 -4
- ophyd_async/epics/adpilatus/_pilatus_controller.py +2 -2
- ophyd_async/epics/adpilatus/_pilatus_io.py +2 -3
- 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 +7 -8
- ophyd_async/epics/demo/_sensor.py +8 -4
- ophyd_async/epics/eiger/_eiger.py +1 -2
- ophyd_async/epics/eiger/_eiger_controller.py +1 -1
- ophyd_async/epics/eiger/_eiger_io.py +2 -4
- ophyd_async/epics/eiger/_odin_io.py +4 -4
- ophyd_async/epics/pvi/__init__.py +2 -2
- ophyd_async/epics/pvi/_pvi.py +56 -321
- ophyd_async/epics/signal/__init__.py +3 -4
- ophyd_async/epics/signal/_aioca.py +184 -236
- ophyd_async/epics/signal/_common.py +35 -49
- ophyd_async/epics/signal/_p4p.py +254 -387
- ophyd_async/epics/signal/_signal.py +63 -21
- 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 +29 -51
- ophyd_async/fastcs/panda/_trigger.py +8 -8
- ophyd_async/fastcs/panda/_writer.py +2 -5
- 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/tango/__init__.py +2 -4
- ophyd_async/tango/base_devices/_base_device.py +76 -143
- ophyd_async/tango/demo/_counter.py +2 -2
- ophyd_async/tango/demo/_mover.py +2 -2
- 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.0.dist-info → ophyd_async-0.8.0a2.dist-info}/METADATA +8 -12
- ophyd_async-0.8.0a2.dist-info/RECORD +110 -0
- {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0a2.dist-info}/WHEEL +1 -1
- ophyd_async/epics/signal/_epics_transport.py +0 -34
- ophyd_async-0.7.0.dist-info/RECORD +0 -108
- {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0a2.dist-info}/LICENSE +0 -0
- {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0a2.dist-info}/entry_points.txt +0 -0
- {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0a2.dist-info}/top_level.txt +0 -0
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
1
|
+
from ophyd_async.core import StrictEnum
|
|
3
2
|
from ophyd_async.epics import adcore
|
|
4
3
|
from ophyd_async.epics.signal import epics_signal_rw_rbv
|
|
5
4
|
|
|
6
5
|
|
|
7
|
-
class VimbaPixelFormat(
|
|
6
|
+
class VimbaPixelFormat(StrictEnum):
|
|
8
7
|
internal = "Mono8"
|
|
9
8
|
ext_enable = "Mono12"
|
|
10
9
|
ext_trigger = "Ext. Trigger"
|
|
@@ -12,7 +11,7 @@ class VimbaPixelFormat(str, Enum):
|
|
|
12
11
|
alignment = "Alignment"
|
|
13
12
|
|
|
14
13
|
|
|
15
|
-
class VimbaConvertFormat(
|
|
14
|
+
class VimbaConvertFormat(StrictEnum):
|
|
16
15
|
none = "None"
|
|
17
16
|
mono8 = "Mono8"
|
|
18
17
|
mono16 = "Mono16"
|
|
@@ -20,7 +19,7 @@ class VimbaConvertFormat(str, Enum):
|
|
|
20
19
|
rgb16 = "RGB16"
|
|
21
20
|
|
|
22
21
|
|
|
23
|
-
class VimbaTriggerSource(
|
|
22
|
+
class VimbaTriggerSource(StrictEnum):
|
|
24
23
|
freerun = "Freerun"
|
|
25
24
|
line1 = "Line1"
|
|
26
25
|
line2 = "Line2"
|
|
@@ -30,17 +29,17 @@ class VimbaTriggerSource(str, Enum):
|
|
|
30
29
|
action1 = "Action1"
|
|
31
30
|
|
|
32
31
|
|
|
33
|
-
class VimbaOverlap(
|
|
32
|
+
class VimbaOverlap(StrictEnum):
|
|
34
33
|
off = "Off"
|
|
35
34
|
prev_frame = "PreviousFrame"
|
|
36
35
|
|
|
37
36
|
|
|
38
|
-
class VimbaOnOff(
|
|
37
|
+
class VimbaOnOff(StrictEnum):
|
|
39
38
|
on = "On"
|
|
40
39
|
off = "Off"
|
|
41
40
|
|
|
42
41
|
|
|
43
|
-
class VimbaExposeOutMode(
|
|
42
|
+
class VimbaExposeOutMode(StrictEnum):
|
|
44
43
|
timed = "Timed" # Use ExposureTime PV
|
|
45
44
|
trigger_width = "TriggerWidth" # Expose for length of high signal
|
|
46
45
|
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
from ophyd_async.core import (
|
|
2
|
+
ConfigSignal,
|
|
3
|
+
DeviceVector,
|
|
4
|
+
HintedSignal,
|
|
5
|
+
StandardReadable,
|
|
6
|
+
StrictEnum,
|
|
7
|
+
)
|
|
4
8
|
from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw
|
|
5
9
|
|
|
6
10
|
|
|
7
|
-
class EnergyMode(
|
|
11
|
+
class EnergyMode(StrictEnum):
|
|
8
12
|
"""Energy mode for `Sensor`"""
|
|
9
13
|
|
|
10
14
|
#: Low energy mode
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from pydantic import Field
|
|
2
2
|
|
|
3
|
-
from ophyd_async.core import AsyncStatus, PathProvider, StandardDetector
|
|
4
|
-
from ophyd_async.core._detector import TriggerInfo
|
|
3
|
+
from ophyd_async.core import AsyncStatus, PathProvider, StandardDetector, TriggerInfo
|
|
5
4
|
|
|
6
5
|
from ._eiger_controller import EigerController
|
|
7
6
|
from ._eiger_io import EigerDriverIO
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
from ophyd_async.core import Device
|
|
1
|
+
from ophyd_async.core import Device, StrictEnum
|
|
4
2
|
from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw_rbv, epics_signal_w
|
|
5
3
|
|
|
6
4
|
|
|
7
|
-
class EigerTriggerMode(
|
|
5
|
+
class EigerTriggerMode(StrictEnum):
|
|
8
6
|
internal = "ints"
|
|
9
7
|
edge = "exts"
|
|
10
8
|
gate = "exte"
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from collections.abc import AsyncGenerator, AsyncIterator
|
|
3
|
-
from enum import Enum
|
|
4
3
|
|
|
5
4
|
from bluesky.protocols import StreamAsset
|
|
6
5
|
from event_model import DataKey
|
|
@@ -12,6 +11,7 @@ from ophyd_async.core import (
|
|
|
12
11
|
DeviceVector,
|
|
13
12
|
NameProvider,
|
|
14
13
|
PathProvider,
|
|
14
|
+
StrictEnum,
|
|
15
15
|
observe_value,
|
|
16
16
|
set_and_wait_for_value,
|
|
17
17
|
)
|
|
@@ -22,7 +22,7 @@ from ophyd_async.epics.signal import (
|
|
|
22
22
|
)
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
class Writing(
|
|
25
|
+
class Writing(StrictEnum):
|
|
26
26
|
ON = "ON"
|
|
27
27
|
OFF = "OFF"
|
|
28
28
|
|
|
@@ -101,10 +101,10 @@ class OdinWriter(DetectorWriter):
|
|
|
101
101
|
return {
|
|
102
102
|
"data": DataKey(
|
|
103
103
|
source=self._drv.file_name.source,
|
|
104
|
-
shape=data_shape,
|
|
104
|
+
shape=list(data_shape),
|
|
105
105
|
dtype="array",
|
|
106
106
|
# TODO: Use correct type based on eiger https://github.com/bluesky/ophyd-async/issues/529
|
|
107
|
-
dtype_numpy="<u2",
|
|
107
|
+
dtype_numpy="<u2",
|
|
108
108
|
external="STREAM:",
|
|
109
109
|
)
|
|
110
110
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
from ._pvi import
|
|
1
|
+
from ._pvi import PviDeviceConnector
|
|
2
2
|
|
|
3
|
-
__all__ = ["
|
|
3
|
+
__all__ = ["PviDeviceConnector"]
|
ophyd_async/epics/pvi/_pvi.py
CHANGED
|
@@ -1,338 +1,73 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
from
|
|
4
|
-
from dataclasses import dataclass
|
|
5
|
-
from inspect import isclass
|
|
6
|
-
from typing import (
|
|
7
|
-
Any,
|
|
8
|
-
Literal,
|
|
9
|
-
Union,
|
|
10
|
-
get_args,
|
|
11
|
-
get_origin,
|
|
12
|
-
get_type_hints,
|
|
13
|
-
)
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from unittest.mock import Mock
|
|
14
4
|
|
|
15
5
|
from ophyd_async.core import (
|
|
16
|
-
DEFAULT_TIMEOUT,
|
|
17
6
|
Device,
|
|
18
|
-
|
|
7
|
+
DeviceConnector,
|
|
8
|
+
DeviceFiller,
|
|
19
9
|
Signal,
|
|
20
|
-
|
|
21
|
-
|
|
10
|
+
SignalR,
|
|
11
|
+
SignalRW,
|
|
12
|
+
SignalX,
|
|
22
13
|
)
|
|
23
14
|
from ophyd_async.epics.signal import (
|
|
24
15
|
PvaSignalBackend,
|
|
25
|
-
|
|
26
|
-
epics_signal_rw,
|
|
27
|
-
epics_signal_w,
|
|
28
|
-
epics_signal_x,
|
|
16
|
+
pvget_with_timeout,
|
|
29
17
|
)
|
|
30
18
|
|
|
31
|
-
Access = frozenset[
|
|
32
|
-
Literal["r"] | Literal["w"] | Literal["rw"] | Literal["x"] | Literal["d"]
|
|
33
|
-
]
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def _strip_number_from_string(string: str) -> tuple[str, int | None]:
|
|
37
|
-
match = re.match(r"(.*?)(\d*)$", string)
|
|
38
|
-
assert match
|
|
39
|
-
|
|
40
|
-
name = match.group(1)
|
|
41
|
-
number = match.group(2) or None
|
|
42
|
-
if number is None:
|
|
43
|
-
return name, None
|
|
44
|
-
else:
|
|
45
|
-
return name, int(number)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def _split_subscript(tp: T) -> tuple[Any, tuple[Any]] | tuple[T, None]:
|
|
49
|
-
"""Split a subscripted type into the its origin and args.
|
|
50
|
-
|
|
51
|
-
If `tp` is not a subscripted type, then just return the type and None as args.
|
|
52
|
-
|
|
53
|
-
"""
|
|
54
|
-
if get_origin(tp) is not None:
|
|
55
|
-
return get_origin(tp), get_args(tp)
|
|
56
|
-
|
|
57
|
-
return tp, None
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def _strip_union(field: T | T) -> tuple[T, bool]:
|
|
61
|
-
if get_origin(field) in [Union, types.UnionType]:
|
|
62
|
-
args = get_args(field)
|
|
63
|
-
is_optional = type(None) in args
|
|
64
|
-
for arg in args:
|
|
65
|
-
if arg is not type(None):
|
|
66
|
-
return arg, is_optional
|
|
67
|
-
return field, False
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def _strip_device_vector(field: type[Device]) -> tuple[bool, type[Device]]:
|
|
71
|
-
if get_origin(field) is DeviceVector:
|
|
72
|
-
return True, get_args(field)[0]
|
|
73
|
-
return False, field
|
|
74
|
-
|
|
75
19
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
20
|
+
def _get_signal_details(entry: dict[str, str]) -> tuple[type[Signal], str, str]:
|
|
21
|
+
match entry:
|
|
22
|
+
case {"r": read_pv}:
|
|
23
|
+
return SignalR, read_pv, read_pv
|
|
24
|
+
case {"r": read_pv, "w": write_pv}:
|
|
25
|
+
return SignalRW, read_pv, write_pv
|
|
26
|
+
case {"rw": read_write_pv}:
|
|
27
|
+
return SignalRW, read_write_pv, read_write_pv
|
|
28
|
+
case {"x": execute_pv}:
|
|
29
|
+
return SignalX, execute_pv, execute_pv
|
|
30
|
+
case _:
|
|
31
|
+
raise TypeError(f"Can't process entry {entry}")
|
|
82
32
|
|
|
83
|
-
sub_entries: dict[str, Union[dict[int, "_PVIEntry"], "_PVIEntry"]]
|
|
84
|
-
pvi_pv: str | None = None
|
|
85
|
-
device: Device | None = None
|
|
86
|
-
common_device_type: type[Device] | None = None
|
|
87
33
|
|
|
34
|
+
class PviDeviceConnector(DeviceConnector):
|
|
35
|
+
def __init__(self, pvi_pv: str = "") -> None:
|
|
36
|
+
self.pvi_pv = pvi_pv
|
|
88
37
|
|
|
89
|
-
def
|
|
90
|
-
|
|
91
|
-
return
|
|
92
|
-
common_sub_devices = get_type_hints(common_device)
|
|
93
|
-
for sub_name, sub_device in common_sub_devices.items():
|
|
94
|
-
if sub_name.startswith("_") or sub_name == "parent":
|
|
95
|
-
continue
|
|
96
|
-
assert entry.sub_entries
|
|
97
|
-
device_t, is_optional = _strip_union(sub_device)
|
|
98
|
-
if sub_name not in entry.sub_entries and not is_optional:
|
|
99
|
-
raise RuntimeError(
|
|
100
|
-
f"sub device `{sub_name}:{type(sub_device)}` " "was not provided by pvi"
|
|
101
|
-
)
|
|
102
|
-
if isinstance(entry.sub_entries[sub_name], dict):
|
|
103
|
-
for sub_sub_entry in entry.sub_entries[sub_name].values(): # type: ignore
|
|
104
|
-
_verify_common_blocks(sub_sub_entry, sub_device) # type: ignore
|
|
105
|
-
else:
|
|
106
|
-
_verify_common_blocks(
|
|
107
|
-
entry.sub_entries[sub_name], # type: ignore
|
|
108
|
-
sub_device, # type: ignore
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
_pvi_mapping: dict[frozenset[str], Callable[..., Signal]] = {
|
|
113
|
-
frozenset({"r", "w"}): lambda dtype, read_pv, write_pv: epics_signal_rw(
|
|
114
|
-
dtype, "pva://" + read_pv, "pva://" + write_pv
|
|
115
|
-
),
|
|
116
|
-
frozenset({"rw"}): lambda dtype, read_write_pv: epics_signal_rw(
|
|
117
|
-
dtype, "pva://" + read_write_pv, write_pv="pva://" + read_write_pv
|
|
118
|
-
),
|
|
119
|
-
frozenset({"r"}): lambda dtype, read_pv: epics_signal_r(dtype, "pva://" + read_pv),
|
|
120
|
-
frozenset({"w"}): lambda dtype, write_pv: epics_signal_w(
|
|
121
|
-
dtype, "pva://" + write_pv
|
|
122
|
-
),
|
|
123
|
-
frozenset({"x"}): lambda _, write_pv: epics_signal_x("pva://" + write_pv),
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
def _parse_type(
|
|
128
|
-
is_pvi_table: bool,
|
|
129
|
-
number_suffix: int | None,
|
|
130
|
-
common_device_type: type[Device] | None,
|
|
131
|
-
):
|
|
132
|
-
if common_device_type:
|
|
133
|
-
# pre-defined type
|
|
134
|
-
device_cls, _ = _strip_union(common_device_type)
|
|
135
|
-
is_device_vector, device_cls = _strip_device_vector(device_cls)
|
|
136
|
-
device_cls, device_args = _split_subscript(device_cls)
|
|
137
|
-
assert issubclass(device_cls, Device)
|
|
138
|
-
|
|
139
|
-
is_signal = issubclass(device_cls, Signal)
|
|
140
|
-
signal_dtype = device_args[0] if device_args is not None else None
|
|
141
|
-
|
|
142
|
-
elif is_pvi_table:
|
|
143
|
-
# is a block, we can make it a DeviceVector if it ends in a number
|
|
144
|
-
is_device_vector = number_suffix is not None
|
|
145
|
-
is_signal = False
|
|
146
|
-
signal_dtype = None
|
|
147
|
-
device_cls = Device
|
|
148
|
-
else:
|
|
149
|
-
# is a signal, signals aren't stored in DeviceVectors unless
|
|
150
|
-
# they're defined as such in the common_device_type
|
|
151
|
-
is_device_vector = False
|
|
152
|
-
is_signal = True
|
|
153
|
-
signal_dtype = None
|
|
154
|
-
device_cls = Signal
|
|
155
|
-
|
|
156
|
-
return is_device_vector, is_signal, signal_dtype, device_cls
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
def _mock_common_blocks(device: Device, stripped_type: type | None = None):
|
|
160
|
-
device_t = stripped_type or type(device)
|
|
161
|
-
sub_devices = (
|
|
162
|
-
(field, field_type)
|
|
163
|
-
for field, field_type in get_type_hints(device_t).items()
|
|
164
|
-
if not field.startswith("_") and field != "parent"
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
for device_name, device_cls in sub_devices:
|
|
168
|
-
device_cls, _ = _strip_union(device_cls)
|
|
169
|
-
is_device_vector, device_cls = _strip_device_vector(device_cls)
|
|
170
|
-
device_cls, device_args = _split_subscript(device_cls)
|
|
171
|
-
assert issubclass(device_cls, Device)
|
|
172
|
-
|
|
173
|
-
signal_dtype = device_args[0] if device_args is not None else None
|
|
174
|
-
|
|
175
|
-
if is_device_vector:
|
|
176
|
-
if issubclass(device_cls, Signal):
|
|
177
|
-
sub_device_1 = device_cls(SoftSignalBackend(signal_dtype))
|
|
178
|
-
sub_device_2 = device_cls(SoftSignalBackend(signal_dtype))
|
|
179
|
-
sub_device = DeviceVector({1: sub_device_1, 2: sub_device_2})
|
|
180
|
-
else:
|
|
181
|
-
if hasattr(device, device_name):
|
|
182
|
-
sub_device = getattr(device, device_name)
|
|
183
|
-
else:
|
|
184
|
-
sub_device = DeviceVector(
|
|
185
|
-
{
|
|
186
|
-
1: device_cls(),
|
|
187
|
-
2: device_cls(),
|
|
188
|
-
}
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
for sub_device_in_vector in sub_device.values():
|
|
192
|
-
_mock_common_blocks(sub_device_in_vector, stripped_type=device_cls)
|
|
193
|
-
|
|
194
|
-
for value in sub_device.values():
|
|
195
|
-
value.parent = sub_device
|
|
196
|
-
else:
|
|
197
|
-
if issubclass(device_cls, Signal):
|
|
198
|
-
sub_device = device_cls(SoftSignalBackend(signal_dtype))
|
|
199
|
-
else:
|
|
200
|
-
sub_device = getattr(device, device_name, device_cls())
|
|
201
|
-
_mock_common_blocks(sub_device, stripped_type=device_cls)
|
|
202
|
-
|
|
203
|
-
setattr(device, device_name, sub_device)
|
|
204
|
-
sub_device.parent = device
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
async def _get_pvi_entries(entry: _PVIEntry, timeout=DEFAULT_TIMEOUT):
|
|
208
|
-
if not entry.pvi_pv or not entry.pvi_pv.endswith(":PVI"):
|
|
209
|
-
raise RuntimeError("Top level entry must be a pvi table")
|
|
210
|
-
|
|
211
|
-
pvi_table_signal_backend: PvaSignalBackend = PvaSignalBackend(
|
|
212
|
-
None, entry.pvi_pv, entry.pvi_pv
|
|
213
|
-
)
|
|
214
|
-
await pvi_table_signal_backend.connect(
|
|
215
|
-
timeout=timeout
|
|
216
|
-
) # create table signal backend
|
|
217
|
-
|
|
218
|
-
pva_table = (await pvi_table_signal_backend.get_value())["pvi"]
|
|
219
|
-
common_device_type_hints = (
|
|
220
|
-
get_type_hints(entry.common_device_type) if entry.common_device_type else {}
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
for sub_name, pva_entries in pva_table.items():
|
|
224
|
-
pvs = list(pva_entries.values())
|
|
225
|
-
is_pvi_table = len(pvs) == 1 and pvs[0].endswith(":PVI")
|
|
226
|
-
sub_name_split, sub_number_split = _strip_number_from_string(sub_name)
|
|
227
|
-
is_device_vector, is_signal, signal_dtype, device_type = _parse_type(
|
|
228
|
-
is_pvi_table,
|
|
229
|
-
sub_number_split,
|
|
230
|
-
common_device_type_hints.get(sub_name_split),
|
|
231
|
-
)
|
|
232
|
-
if is_signal:
|
|
233
|
-
device = _pvi_mapping[frozenset(pva_entries.keys())](signal_dtype, *pvs)
|
|
234
|
-
else:
|
|
235
|
-
device = getattr(entry.device, sub_name, device_type())
|
|
236
|
-
|
|
237
|
-
sub_entry = _PVIEntry(
|
|
238
|
-
device=device, common_device_type=device_type, sub_entries={}
|
|
239
|
-
)
|
|
240
|
-
|
|
241
|
-
if is_device_vector:
|
|
242
|
-
# If device vector then we store sub_name -> {sub_number -> sub_entry}
|
|
243
|
-
# and aggregate into `DeviceVector` in `_set_device_attributes`
|
|
244
|
-
sub_number_split = 1 if sub_number_split is None else sub_number_split
|
|
245
|
-
if sub_name_split not in entry.sub_entries:
|
|
246
|
-
entry.sub_entries[sub_name_split] = {}
|
|
247
|
-
entry.sub_entries[sub_name_split][sub_number_split] = sub_entry # type: ignore
|
|
248
|
-
else:
|
|
249
|
-
entry.sub_entries[sub_name] = sub_entry
|
|
250
|
-
|
|
251
|
-
if is_pvi_table:
|
|
252
|
-
sub_entry.pvi_pv = pvs[0]
|
|
253
|
-
await _get_pvi_entries(sub_entry)
|
|
254
|
-
|
|
255
|
-
if entry.common_device_type:
|
|
256
|
-
_verify_common_blocks(entry, entry.common_device_type)
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
def _set_device_attributes(entry: _PVIEntry):
|
|
260
|
-
for sub_name, sub_entry in entry.sub_entries.items():
|
|
261
|
-
if isinstance(sub_entry, dict):
|
|
262
|
-
sub_device = DeviceVector() # type: ignore
|
|
263
|
-
for key, device_vector_sub_entry in sub_entry.items():
|
|
264
|
-
sub_device[key] = device_vector_sub_entry.device
|
|
265
|
-
if device_vector_sub_entry.pvi_pv:
|
|
266
|
-
_set_device_attributes(device_vector_sub_entry)
|
|
267
|
-
# Set the device vector entry to have the device vector as a parent
|
|
268
|
-
device_vector_sub_entry.device.parent = sub_device # type: ignore
|
|
269
|
-
else:
|
|
270
|
-
sub_device = sub_entry.device
|
|
271
|
-
assert sub_device, f"Device of {sub_entry} is None"
|
|
272
|
-
if sub_entry.pvi_pv:
|
|
273
|
-
_set_device_attributes(sub_entry)
|
|
274
|
-
|
|
275
|
-
sub_device.parent = entry.device
|
|
276
|
-
setattr(entry.device, sub_name, sub_device)
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
async def fill_pvi_entries(
|
|
280
|
-
device: Device, root_pv: str, timeout=DEFAULT_TIMEOUT, mock=False
|
|
281
|
-
):
|
|
282
|
-
"""
|
|
283
|
-
Fills a ``device`` with signals from a the ``root_pvi:PVI`` table.
|
|
284
|
-
|
|
285
|
-
If the device names match with parent devices of ``device`` then types are used.
|
|
286
|
-
"""
|
|
287
|
-
if mock:
|
|
288
|
-
# set up mock signals for the common annotations
|
|
289
|
-
_mock_common_blocks(device)
|
|
290
|
-
else:
|
|
291
|
-
# check the pvi table for devices and fill the device with them
|
|
292
|
-
root_entry = _PVIEntry(
|
|
293
|
-
pvi_pv=root_pv,
|
|
38
|
+
def create_children_from_annotations(self, device: Device):
|
|
39
|
+
self._filler = DeviceFiller(
|
|
294
40
|
device=device,
|
|
295
|
-
|
|
296
|
-
|
|
41
|
+
signal_backend_factory=PvaSignalBackend,
|
|
42
|
+
device_connector_factory=PviDeviceConnector,
|
|
297
43
|
)
|
|
298
|
-
await _get_pvi_entries(root_entry, timeout=timeout)
|
|
299
|
-
_set_device_attributes(root_entry)
|
|
300
|
-
|
|
301
|
-
# We call set name now the parent field has been set in all of the
|
|
302
|
-
# introspect-initialized devices. This will recursively set the names.
|
|
303
|
-
device.set_name(device.name)
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
def create_children_from_annotations(
|
|
307
|
-
device: Device,
|
|
308
|
-
included_optional_fields: tuple[str, ...] = (),
|
|
309
|
-
device_vectors: dict[str, int] | None = None,
|
|
310
|
-
):
|
|
311
|
-
"""For intializing blocks at __init__ of ``device``."""
|
|
312
|
-
for name, device_type in get_type_hints(type(device)).items():
|
|
313
|
-
if name in ("_name", "parent"):
|
|
314
|
-
continue
|
|
315
|
-
device_type, is_optional = _strip_union(device_type)
|
|
316
|
-
if is_optional and name not in included_optional_fields:
|
|
317
|
-
continue
|
|
318
|
-
is_device_vector, device_type = _strip_device_vector(device_type)
|
|
319
|
-
if (
|
|
320
|
-
(is_device_vector and (not device_vectors or name not in device_vectors))
|
|
321
|
-
or ((origin := get_origin(device_type)) and issubclass(origin, Signal))
|
|
322
|
-
or (isclass(device_type) and issubclass(device_type, Signal))
|
|
323
|
-
):
|
|
324
|
-
continue
|
|
325
44
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
create_children_from_annotations(
|
|
333
|
-
sub_device, device_vectors=device_vectors
|
|
334
|
-
)
|
|
45
|
+
async def connect(
|
|
46
|
+
self, device: Device, mock: bool | Mock, timeout: float, force_reconnect: bool
|
|
47
|
+
) -> None:
|
|
48
|
+
if mock:
|
|
49
|
+
# Make 2 entries for each DeviceVector
|
|
50
|
+
self._filler.make_soft_device_vector_entries(2)
|
|
335
51
|
else:
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
52
|
+
pvi_structure = await pvget_with_timeout(self.pvi_pv, timeout)
|
|
53
|
+
entries: dict[str, dict[str, str]] = pvi_structure["value"].todict()
|
|
54
|
+
# Ensure we have device vectors for everything that should be there
|
|
55
|
+
self._filler.make_device_vectors(list(entries))
|
|
56
|
+
for name, entry in entries.items():
|
|
57
|
+
if set(entry) == {"d"}:
|
|
58
|
+
connector = self._filler.make_child_device(name)
|
|
59
|
+
connector.pvi_pv = entry["d"]
|
|
60
|
+
else:
|
|
61
|
+
signal_type, read_pv, write_pv = _get_signal_details(entry)
|
|
62
|
+
backend = self._filler.make_child_signal(name, signal_type)
|
|
63
|
+
backend.read_pv = read_pv
|
|
64
|
+
backend.write_pv = write_pv
|
|
65
|
+
# Check that all the requested children have been created
|
|
66
|
+
if unfilled := self._filler.unfilled():
|
|
67
|
+
raise RuntimeError(
|
|
68
|
+
f"{device.name}: cannot provision {unfilled} from "
|
|
69
|
+
f"{self.pvi_pv}: {entries}"
|
|
70
|
+
)
|
|
71
|
+
# Set the name of the device to name all children
|
|
72
|
+
device.set_name(device.name)
|
|
73
|
+
return await super().connect(device, mock, timeout, force_reconnect)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from ._common import
|
|
2
|
-
from ._p4p import PvaSignalBackend
|
|
1
|
+
from ._common import get_supported_values
|
|
2
|
+
from ._p4p import PvaSignalBackend, pvget_with_timeout
|
|
3
3
|
from ._signal import (
|
|
4
4
|
epics_signal_r,
|
|
5
5
|
epics_signal_rw,
|
|
@@ -10,9 +10,8 @@ from ._signal import (
|
|
|
10
10
|
|
|
11
11
|
__all__ = [
|
|
12
12
|
"get_supported_values",
|
|
13
|
-
"LimitPair",
|
|
14
|
-
"Limits",
|
|
15
13
|
"PvaSignalBackend",
|
|
14
|
+
"pvget_with_timeout",
|
|
16
15
|
"epics_signal_r",
|
|
17
16
|
"epics_signal_rw",
|
|
18
17
|
"epics_signal_rw_rbv",
|