ophyd-async 0.1.0__py3-none-any.whl → 0.3a1__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 +47 -12
- ophyd_async/core/_providers.py +66 -0
- ophyd_async/core/async_status.py +7 -5
- ophyd_async/core/detector.py +321 -0
- ophyd_async/core/device.py +184 -0
- ophyd_async/core/device_save_loader.py +286 -0
- ophyd_async/core/flyer.py +94 -0
- ophyd_async/core/{_device/_signal/signal.py → signal.py} +46 -18
- ophyd_async/core/{_device/_backend/signal_backend.py → signal_backend.py} +6 -2
- ophyd_async/core/{_device/_backend/sim_signal_backend.py → sim_signal_backend.py} +6 -2
- ophyd_async/core/{_device/standard_readable.py → standard_readable.py} +3 -3
- ophyd_async/core/utils.py +79 -29
- ophyd_async/epics/_backend/_aioca.py +38 -25
- ophyd_async/epics/_backend/_p4p.py +62 -27
- ophyd_async/epics/_backend/common.py +20 -0
- ophyd_async/epics/areadetector/__init__.py +10 -13
- ophyd_async/epics/areadetector/controllers/__init__.py +4 -0
- ophyd_async/epics/areadetector/controllers/ad_sim_controller.py +52 -0
- ophyd_async/epics/areadetector/controllers/pilatus_controller.py +49 -0
- ophyd_async/epics/areadetector/drivers/__init__.py +15 -0
- ophyd_async/epics/areadetector/drivers/ad_base.py +111 -0
- ophyd_async/epics/areadetector/drivers/pilatus_driver.py +18 -0
- ophyd_async/epics/areadetector/single_trigger_det.py +4 -4
- ophyd_async/epics/areadetector/utils.py +91 -3
- ophyd_async/epics/areadetector/writers/__init__.py +5 -0
- ophyd_async/epics/areadetector/writers/_hdfdataset.py +10 -0
- ophyd_async/epics/areadetector/writers/_hdffile.py +54 -0
- ophyd_async/epics/areadetector/writers/hdf_writer.py +133 -0
- ophyd_async/epics/areadetector/{nd_file_hdf.py → writers/nd_file_hdf.py} +22 -5
- ophyd_async/epics/areadetector/writers/nd_plugin.py +30 -0
- ophyd_async/epics/demo/__init__.py +3 -2
- ophyd_async/epics/demo/demo_ad_sim_detector.py +35 -0
- ophyd_async/epics/motion/motor.py +2 -1
- ophyd_async/epics/pvi.py +70 -0
- ophyd_async/epics/signal/__init__.py +0 -2
- ophyd_async/epics/signal/signal.py +1 -1
- ophyd_async/panda/__init__.py +12 -8
- ophyd_async/panda/panda.py +43 -134
- ophyd_async/panda/panda_controller.py +41 -0
- ophyd_async/panda/table.py +158 -0
- ophyd_async/panda/utils.py +15 -0
- {ophyd_async-0.1.0.dist-info → ophyd_async-0.3a1.dist-info}/METADATA +49 -42
- ophyd_async-0.3a1.dist-info/RECORD +56 -0
- {ophyd_async-0.1.0.dist-info → ophyd_async-0.3a1.dist-info}/WHEEL +1 -1
- ophyd_async/core/_device/__init__.py +0 -0
- ophyd_async/core/_device/_backend/__init__.py +0 -0
- ophyd_async/core/_device/_signal/__init__.py +0 -0
- ophyd_async/core/_device/device.py +0 -60
- ophyd_async/core/_device/device_collector.py +0 -121
- ophyd_async/core/_device/device_vector.py +0 -14
- ophyd_async/epics/areadetector/ad_driver.py +0 -18
- ophyd_async/epics/areadetector/directory_provider.py +0 -18
- ophyd_async/epics/areadetector/hdf_streamer_det.py +0 -167
- ophyd_async/epics/areadetector/nd_plugin.py +0 -13
- ophyd_async/epics/signal/pvi_get.py +0 -22
- ophyd_async-0.1.0.dist-info/RECORD +0 -45
- {ophyd_async-0.1.0.dist-info → ophyd_async-0.3a1.dist-info}/LICENSE +0 -0
- {ophyd_async-0.1.0.dist-info → ophyd_async-0.3a1.dist-info}/entry_points.txt +0 -0
- {ophyd_async-0.1.0.dist-info → ophyd_async-0.3a1.dist-info}/top_level.txt +0 -0
|
@@ -92,7 +92,7 @@ def epics_signal_x(write_pv: str) -> SignalX:
|
|
|
92
92
|
Parameters
|
|
93
93
|
----------
|
|
94
94
|
write_pv:
|
|
95
|
-
The PV to write its initial value to on
|
|
95
|
+
The PV to write its initial value to on trigger
|
|
96
96
|
"""
|
|
97
97
|
backend: SignalBackend = _make_backend(None, write_pv, write_pv)
|
|
98
98
|
return SignalX(backend)
|
ophyd_async/panda/__init__.py
CHANGED
|
@@ -1,21 +1,25 @@
|
|
|
1
|
-
from .panda import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
PulseBlock,
|
|
5
|
-
PVIEntry,
|
|
6
|
-
SeqBlock,
|
|
1
|
+
from .panda import PandA, PcapBlock, PulseBlock, PVIEntry, SeqBlock, SeqTable
|
|
2
|
+
from .panda_controller import PandaPcapController
|
|
3
|
+
from .table import (
|
|
7
4
|
SeqTable,
|
|
5
|
+
SeqTableRow,
|
|
8
6
|
SeqTrigger,
|
|
9
|
-
|
|
7
|
+
seq_table_from_arrays,
|
|
8
|
+
seq_table_from_rows,
|
|
10
9
|
)
|
|
10
|
+
from .utils import phase_sorter
|
|
11
11
|
|
|
12
12
|
__all__ = [
|
|
13
13
|
"PandA",
|
|
14
14
|
"PcapBlock",
|
|
15
15
|
"PulseBlock",
|
|
16
16
|
"PVIEntry",
|
|
17
|
+
"seq_table_from_arrays",
|
|
18
|
+
"seq_table_from_rows",
|
|
17
19
|
"SeqBlock",
|
|
18
20
|
"SeqTable",
|
|
21
|
+
"SeqTableRow",
|
|
19
22
|
"SeqTrigger",
|
|
20
|
-
"
|
|
23
|
+
"phase_sorter",
|
|
24
|
+
"PandaPcapController",
|
|
21
25
|
]
|
ophyd_async/panda/panda.py
CHANGED
|
@@ -1,28 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import atexit
|
|
4
3
|
import re
|
|
5
|
-
from
|
|
6
|
-
from typing import (
|
|
7
|
-
Callable,
|
|
8
|
-
Dict,
|
|
9
|
-
FrozenSet,
|
|
10
|
-
Optional,
|
|
11
|
-
Sequence,
|
|
12
|
-
Tuple,
|
|
13
|
-
Type,
|
|
14
|
-
TypedDict,
|
|
15
|
-
cast,
|
|
16
|
-
get_args,
|
|
17
|
-
get_origin,
|
|
18
|
-
get_type_hints,
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
import numpy as np
|
|
22
|
-
import numpy.typing as npt
|
|
23
|
-
from p4p.client.thread import Context
|
|
4
|
+
from typing import Dict, Optional, Tuple, cast, get_args, get_origin, get_type_hints
|
|
24
5
|
|
|
25
6
|
from ophyd_async.core import (
|
|
7
|
+
DEFAULT_TIMEOUT,
|
|
26
8
|
Device,
|
|
27
9
|
DeviceVector,
|
|
28
10
|
Signal,
|
|
@@ -32,13 +14,8 @@ from ophyd_async.core import (
|
|
|
32
14
|
SignalX,
|
|
33
15
|
SimSignalBackend,
|
|
34
16
|
)
|
|
35
|
-
from ophyd_async.epics.
|
|
36
|
-
|
|
37
|
-
epics_signal_rw,
|
|
38
|
-
epics_signal_w,
|
|
39
|
-
epics_signal_x,
|
|
40
|
-
pvi_get,
|
|
41
|
-
)
|
|
17
|
+
from ophyd_async.epics.pvi import PVIEntry, make_signal, pvi_get
|
|
18
|
+
from ophyd_async.panda.table import SeqTable
|
|
42
19
|
|
|
43
20
|
|
|
44
21
|
class PulseBlock(Device):
|
|
@@ -46,56 +23,14 @@ class PulseBlock(Device):
|
|
|
46
23
|
width: SignalRW[float]
|
|
47
24
|
|
|
48
25
|
|
|
49
|
-
class SeqTrigger(Enum):
|
|
50
|
-
IMMEDIATE = "Immediate"
|
|
51
|
-
BITA_0 = "BITA=0"
|
|
52
|
-
BITA_1 = "BITA=1"
|
|
53
|
-
BITB_0 = "BITB=0"
|
|
54
|
-
BITB_1 = "BITB=1"
|
|
55
|
-
BITC_0 = "BITC=0"
|
|
56
|
-
BITC_1 = "BITC=1"
|
|
57
|
-
POSA_GT = "POSA>=POSITION"
|
|
58
|
-
POSA_LT = "POSA<=POSITION"
|
|
59
|
-
POSB_GT = "POSB>=POSITION"
|
|
60
|
-
POSB_LT = "POSB<=POSITION"
|
|
61
|
-
POSC_GT = "POSC>=POSITION"
|
|
62
|
-
POSC_LT = "POSC<=POSITION"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
class SeqTable(TypedDict):
|
|
66
|
-
repeats: npt.NDArray[np.uint16]
|
|
67
|
-
trigger: Sequence[SeqTrigger]
|
|
68
|
-
position: npt.NDArray[np.int32]
|
|
69
|
-
time1: npt.NDArray[np.uint32]
|
|
70
|
-
outa1: npt.NDArray[np.bool_]
|
|
71
|
-
outb1: npt.NDArray[np.bool_]
|
|
72
|
-
outc1: npt.NDArray[np.bool_]
|
|
73
|
-
outd1: npt.NDArray[np.bool_]
|
|
74
|
-
oute1: npt.NDArray[np.bool_]
|
|
75
|
-
outf1: npt.NDArray[np.bool_]
|
|
76
|
-
time2: npt.NDArray[np.uint32]
|
|
77
|
-
outa2: npt.NDArray[np.bool_]
|
|
78
|
-
outb2: npt.NDArray[np.bool_]
|
|
79
|
-
outc2: npt.NDArray[np.bool_]
|
|
80
|
-
outd2: npt.NDArray[np.bool_]
|
|
81
|
-
oute2: npt.NDArray[np.bool_]
|
|
82
|
-
outf2: npt.NDArray[np.bool_]
|
|
83
|
-
|
|
84
|
-
|
|
85
26
|
class SeqBlock(Device):
|
|
86
27
|
table: SignalRW[SeqTable]
|
|
28
|
+
active: SignalRW[bool]
|
|
87
29
|
|
|
88
30
|
|
|
89
31
|
class PcapBlock(Device):
|
|
90
32
|
active: SignalR[bool]
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
class PVIEntry(TypedDict, total=False):
|
|
94
|
-
d: str
|
|
95
|
-
r: str
|
|
96
|
-
rw: str
|
|
97
|
-
w: str
|
|
98
|
-
x: str
|
|
33
|
+
arm: SignalRW[bool]
|
|
99
34
|
|
|
100
35
|
|
|
101
36
|
def _block_name_number(block_name: str) -> Tuple[str, Optional[int]]:
|
|
@@ -116,7 +51,7 @@ def _block_name_number(block_name: str) -> Tuple[str, Optional[int]]:
|
|
|
116
51
|
return block_name, None
|
|
117
52
|
|
|
118
53
|
|
|
119
|
-
def _remove_inconsistent_blocks(pvi_info: Dict[str, PVIEntry]) -> None:
|
|
54
|
+
def _remove_inconsistent_blocks(pvi_info: Optional[Dict[str, PVIEntry]]) -> None:
|
|
120
55
|
"""Remove blocks from pvi information.
|
|
121
56
|
|
|
122
57
|
This is needed because some pandas have 'pcap' and 'pcap1' blocks, which are
|
|
@@ -124,6 +59,8 @@ def _remove_inconsistent_blocks(pvi_info: Dict[str, PVIEntry]) -> None:
|
|
|
124
59
|
for example.
|
|
125
60
|
|
|
126
61
|
"""
|
|
62
|
+
if pvi_info is None:
|
|
63
|
+
return
|
|
127
64
|
pvi_keys = set(pvi_info.keys())
|
|
128
65
|
for k in pvi_keys:
|
|
129
66
|
kn = re.sub(r"\d*$", "", k)
|
|
@@ -131,44 +68,14 @@ def _remove_inconsistent_blocks(pvi_info: Dict[str, PVIEntry]) -> None:
|
|
|
131
68
|
del pvi_info[k]
|
|
132
69
|
|
|
133
70
|
|
|
134
|
-
async def pvi(pv: str, ctxt: Context, timeout: float = 5.0) -> Dict[str, PVIEntry]:
|
|
135
|
-
result = await pvi_get(pv, ctxt, timeout=timeout)
|
|
136
|
-
_remove_inconsistent_blocks(result)
|
|
137
|
-
return result
|
|
138
|
-
|
|
139
|
-
|
|
140
71
|
class PandA(Device):
|
|
141
|
-
_ctxt: Optional[Context] = None
|
|
142
|
-
|
|
143
72
|
pulse: DeviceVector[PulseBlock]
|
|
144
73
|
seq: DeviceVector[SeqBlock]
|
|
145
74
|
pcap: PcapBlock
|
|
146
75
|
|
|
147
|
-
def __init__(self,
|
|
148
|
-
|
|
149
|
-
self.
|
|
150
|
-
frozenset({"r", "w"}): lambda dtype, rpv, wpv: epics_signal_rw(
|
|
151
|
-
dtype, rpv, wpv
|
|
152
|
-
),
|
|
153
|
-
frozenset({"rw"}): lambda dtype, rpv, wpv: epics_signal_rw(dtype, rpv, wpv),
|
|
154
|
-
frozenset({"r"}): lambda dtype, rpv, wpv: epics_signal_r(dtype, rpv),
|
|
155
|
-
frozenset({"w"}): lambda dtype, rpv, wpv: epics_signal_w(dtype, wpv),
|
|
156
|
-
frozenset({"x"}): lambda dtype, rpv, wpv: epics_signal_x(wpv),
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
@property
|
|
160
|
-
def ctxt(self) -> Context:
|
|
161
|
-
if PandA._ctxt is None:
|
|
162
|
-
PandA._ctxt = Context("pva", nt=False)
|
|
163
|
-
|
|
164
|
-
@atexit.register
|
|
165
|
-
def _del_ctxt():
|
|
166
|
-
# If we don't do this we get messages like this on close:
|
|
167
|
-
# Error in sys.excepthook:
|
|
168
|
-
# Original exception was:
|
|
169
|
-
PandA._ctxt = None
|
|
170
|
-
|
|
171
|
-
return PandA._ctxt
|
|
76
|
+
def __init__(self, prefix: str, name: str = "") -> None:
|
|
77
|
+
super().__init__(name)
|
|
78
|
+
self._prefix = prefix
|
|
172
79
|
|
|
173
80
|
def verify_block(self, name: str, num: Optional[int]):
|
|
174
81
|
"""Given a block name and number, return information about a block."""
|
|
@@ -186,7 +93,12 @@ class PandA(Device):
|
|
|
186
93
|
return block
|
|
187
94
|
|
|
188
95
|
async def _make_block(
|
|
189
|
-
self,
|
|
96
|
+
self,
|
|
97
|
+
name: str,
|
|
98
|
+
num: Optional[int],
|
|
99
|
+
block_pv: str,
|
|
100
|
+
sim: bool = False,
|
|
101
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
190
102
|
):
|
|
191
103
|
"""Makes a block given a block name containing relevant signals.
|
|
192
104
|
|
|
@@ -196,7 +108,7 @@ class PandA(Device):
|
|
|
196
108
|
block = self.verify_block(name, num)
|
|
197
109
|
|
|
198
110
|
field_annos = get_type_hints(block, globalns=globals())
|
|
199
|
-
block_pvi = await
|
|
111
|
+
block_pvi = await pvi_get(block_pv, timeout=timeout) if not sim else None
|
|
200
112
|
|
|
201
113
|
# finds which fields this class actually has, e.g. delay, width...
|
|
202
114
|
for sig_name, sig_type in field_annos.items():
|
|
@@ -205,7 +117,6 @@ class PandA(Device):
|
|
|
205
117
|
|
|
206
118
|
# if not in sim mode,
|
|
207
119
|
if block_pvi:
|
|
208
|
-
block_pvi = cast(Dict[str, PVIEntry], block_pvi)
|
|
209
120
|
# try to get this block in the pvi.
|
|
210
121
|
entry: Optional[PVIEntry] = block_pvi.get(sig_name)
|
|
211
122
|
if entry is None:
|
|
@@ -214,7 +125,7 @@ class PandA(Device):
|
|
|
214
125
|
+ f"an {sig_name} signal which has not been retrieved by PVI."
|
|
215
126
|
)
|
|
216
127
|
|
|
217
|
-
signal =
|
|
128
|
+
signal: Signal = make_signal(entry, args[0] if len(args) > 0 else None)
|
|
218
129
|
|
|
219
130
|
else:
|
|
220
131
|
backend: SignalBackend = SimSignalBackend(
|
|
@@ -229,40 +140,26 @@ class PandA(Device):
|
|
|
229
140
|
for attr, attr_pvi in block_pvi.items():
|
|
230
141
|
if not hasattr(block, attr):
|
|
231
142
|
# makes any extra signals
|
|
232
|
-
|
|
233
|
-
setattr(block, attr, signal)
|
|
143
|
+
setattr(block, attr, make_signal(attr_pvi))
|
|
234
144
|
|
|
235
145
|
return block
|
|
236
146
|
|
|
237
|
-
async def _make_untyped_block(
|
|
147
|
+
async def _make_untyped_block(
|
|
148
|
+
self, block_pv: str, timeout: float = DEFAULT_TIMEOUT
|
|
149
|
+
):
|
|
238
150
|
"""Populates a block using PVI information.
|
|
239
151
|
|
|
240
152
|
This block is not typed as part of the PandA interface but needs to be
|
|
241
153
|
included dynamically anyway.
|
|
242
154
|
"""
|
|
243
155
|
block = Device()
|
|
244
|
-
block_pvi: Dict[str, PVIEntry] = await
|
|
156
|
+
block_pvi: Dict[str, PVIEntry] = await pvi_get(block_pv, timeout=timeout)
|
|
245
157
|
|
|
246
158
|
for signal_name, signal_pvi in block_pvi.items():
|
|
247
|
-
|
|
248
|
-
setattr(block, signal_name, signal)
|
|
159
|
+
setattr(block, signal_name, make_signal(signal_pvi))
|
|
249
160
|
|
|
250
161
|
return block
|
|
251
162
|
|
|
252
|
-
def _make_signal(self, signal_pvi: PVIEntry, dtype: Optional[Type] = None):
|
|
253
|
-
"""Make a signal.
|
|
254
|
-
|
|
255
|
-
This assumes datatype is None so it can be used to create dynamic signals.
|
|
256
|
-
"""
|
|
257
|
-
operations = frozenset(signal_pvi.keys())
|
|
258
|
-
pvs = [signal_pvi[i] for i in operations] # type: ignore
|
|
259
|
-
signal_factory = self.pvi_mapping[operations]
|
|
260
|
-
|
|
261
|
-
write_pv = pvs[0]
|
|
262
|
-
read_pv = write_pv if len(pvs) == 1 else pvs[1]
|
|
263
|
-
|
|
264
|
-
return signal_factory(dtype, "pva://" + read_pv, "pva://" + write_pv)
|
|
265
|
-
|
|
266
163
|
# TODO redo to set_panda_block? confusing name
|
|
267
164
|
def set_attribute(self, name: str, num: Optional[int], block: Device):
|
|
268
165
|
"""Set a block on the panda.
|
|
@@ -279,7 +176,9 @@ class PandA(Device):
|
|
|
279
176
|
else:
|
|
280
177
|
setattr(self, name, block)
|
|
281
178
|
|
|
282
|
-
async def connect(
|
|
179
|
+
async def connect(
|
|
180
|
+
self, sim: bool = False, timeout: float = DEFAULT_TIMEOUT
|
|
181
|
+
) -> None:
|
|
283
182
|
"""Initialises all blocks and connects them.
|
|
284
183
|
|
|
285
184
|
First, checks for pvi information. If it exists, make all blocks from this.
|
|
@@ -288,7 +187,11 @@ class PandA(Device):
|
|
|
288
187
|
If there's no pvi information, that's because we're in sim mode. In that case,
|
|
289
188
|
makes all required blocks.
|
|
290
189
|
"""
|
|
291
|
-
pvi_info =
|
|
190
|
+
pvi_info = (
|
|
191
|
+
await pvi_get(self._prefix + "PVI", timeout=timeout) if not sim else None
|
|
192
|
+
)
|
|
193
|
+
_remove_inconsistent_blocks(pvi_info)
|
|
194
|
+
|
|
292
195
|
hints = {
|
|
293
196
|
attr_name: attr_type
|
|
294
197
|
for attr_name, attr_type in get_type_hints(self, globalns=globals()).items()
|
|
@@ -302,9 +205,13 @@ class PandA(Device):
|
|
|
302
205
|
name, num = _block_name_number(block_name)
|
|
303
206
|
|
|
304
207
|
if name in hints:
|
|
305
|
-
block = await self._make_block(
|
|
208
|
+
block = await self._make_block(
|
|
209
|
+
name, num, block_pvi["d"], timeout=timeout
|
|
210
|
+
)
|
|
306
211
|
else:
|
|
307
|
-
block = await self._make_untyped_block(
|
|
212
|
+
block = await self._make_untyped_block(
|
|
213
|
+
block_pvi["d"], timeout=timeout
|
|
214
|
+
)
|
|
308
215
|
|
|
309
216
|
self.set_attribute(name, num, block)
|
|
310
217
|
|
|
@@ -325,7 +232,9 @@ class PandA(Device):
|
|
|
325
232
|
], f"Expected PandA to only contain blocks, got {entry}"
|
|
326
233
|
else:
|
|
327
234
|
num = 1 if get_origin(hints[block_name]) == DeviceVector else None
|
|
328
|
-
block = await self._make_block(
|
|
235
|
+
block = await self._make_block(
|
|
236
|
+
block_name, num, "sim://", sim=sim, timeout=timeout
|
|
237
|
+
)
|
|
329
238
|
self.set_attribute(block_name, num, block)
|
|
330
239
|
|
|
331
240
|
self.set_name(self.name)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from ophyd_async.core import (
|
|
5
|
+
AsyncStatus,
|
|
6
|
+
DetectorControl,
|
|
7
|
+
DetectorTrigger,
|
|
8
|
+
wait_for_value,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from .panda import PcapBlock
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PandaPcapController(DetectorControl):
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
pcap: PcapBlock,
|
|
18
|
+
) -> None:
|
|
19
|
+
self.pcap = pcap
|
|
20
|
+
|
|
21
|
+
def get_deadtime(self, exposure: float) -> float:
|
|
22
|
+
return 0.000000008
|
|
23
|
+
|
|
24
|
+
async def arm(
|
|
25
|
+
self,
|
|
26
|
+
num: int,
|
|
27
|
+
trigger: DetectorTrigger = DetectorTrigger.constant_gate,
|
|
28
|
+
exposure: Optional[float] = None,
|
|
29
|
+
) -> AsyncStatus:
|
|
30
|
+
assert trigger in (
|
|
31
|
+
DetectorTrigger.constant_gate,
|
|
32
|
+
trigger == DetectorTrigger.variable_gate,
|
|
33
|
+
), "Only constant_gate and variable_gate triggering is supported on the PandA"
|
|
34
|
+
await asyncio.gather(self.pcap.arm.set(True))
|
|
35
|
+
await wait_for_value(self.pcap.active, True, timeout=1)
|
|
36
|
+
return AsyncStatus(wait_for_value(self.pcap.active, False, timeout=None))
|
|
37
|
+
|
|
38
|
+
async def disarm(self):
|
|
39
|
+
await asyncio.gather(self.pcap.arm.set(False))
|
|
40
|
+
await wait_for_value(self.pcap.active, False, timeout=1)
|
|
41
|
+
return AsyncStatus(wait_for_value(self.pcap.active, False, timeout=None))
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from typing import Optional, Sequence, Type, TypedDict, TypeVar
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import numpy.typing as npt
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SeqTrigger(str, Enum):
|
|
10
|
+
IMMEDIATE = "Immediate"
|
|
11
|
+
BITA_0 = "BITA=0"
|
|
12
|
+
BITA_1 = "BITA=1"
|
|
13
|
+
BITB_0 = "BITB=0"
|
|
14
|
+
BITB_1 = "BITB=1"
|
|
15
|
+
BITC_0 = "BITC=0"
|
|
16
|
+
BITC_1 = "BITC=1"
|
|
17
|
+
POSA_GT = "POSA>=POSITION"
|
|
18
|
+
POSA_LT = "POSA<=POSITION"
|
|
19
|
+
POSB_GT = "POSB>=POSITION"
|
|
20
|
+
POSB_LT = "POSB<=POSITION"
|
|
21
|
+
POSC_GT = "POSC>=POSITION"
|
|
22
|
+
POSC_LT = "POSC<=POSITION"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class SeqTableRow:
|
|
27
|
+
repeats: int = 1
|
|
28
|
+
trigger: SeqTrigger = SeqTrigger.IMMEDIATE
|
|
29
|
+
position: int = 0
|
|
30
|
+
time1: int = 0
|
|
31
|
+
outa1: bool = False
|
|
32
|
+
outb1: bool = False
|
|
33
|
+
outc1: bool = False
|
|
34
|
+
outd1: bool = False
|
|
35
|
+
oute1: bool = False
|
|
36
|
+
outf1: bool = False
|
|
37
|
+
time2: int = 0
|
|
38
|
+
outa2: bool = False
|
|
39
|
+
outb2: bool = False
|
|
40
|
+
outc2: bool = False
|
|
41
|
+
outd2: bool = False
|
|
42
|
+
oute2: bool = False
|
|
43
|
+
outf2: bool = False
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class SeqTable(TypedDict):
|
|
47
|
+
repeats: npt.NDArray[np.uint16]
|
|
48
|
+
trigger: Sequence[SeqTrigger]
|
|
49
|
+
position: npt.NDArray[np.int32]
|
|
50
|
+
time1: npt.NDArray[np.uint32]
|
|
51
|
+
outa1: npt.NDArray[np.bool_]
|
|
52
|
+
outb1: npt.NDArray[np.bool_]
|
|
53
|
+
outc1: npt.NDArray[np.bool_]
|
|
54
|
+
outd1: npt.NDArray[np.bool_]
|
|
55
|
+
oute1: npt.NDArray[np.bool_]
|
|
56
|
+
outf1: npt.NDArray[np.bool_]
|
|
57
|
+
time2: npt.NDArray[np.uint32]
|
|
58
|
+
outa2: npt.NDArray[np.bool_]
|
|
59
|
+
outb2: npt.NDArray[np.bool_]
|
|
60
|
+
outc2: npt.NDArray[np.bool_]
|
|
61
|
+
outd2: npt.NDArray[np.bool_]
|
|
62
|
+
oute2: npt.NDArray[np.bool_]
|
|
63
|
+
outf2: npt.NDArray[np.bool_]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def seq_table_from_rows(*rows: SeqTableRow):
|
|
67
|
+
"""
|
|
68
|
+
Constructs a sequence table from a series of rows.
|
|
69
|
+
"""
|
|
70
|
+
return seq_table_from_arrays(
|
|
71
|
+
repeats=np.array([row.repeats for row in rows], dtype=np.uint16),
|
|
72
|
+
trigger=[row.trigger for row in rows],
|
|
73
|
+
position=np.array([row.position for row in rows], dtype=np.int32),
|
|
74
|
+
time1=np.array([row.time1 for row in rows], dtype=np.uint32),
|
|
75
|
+
outa1=np.array([row.outa1 for row in rows], dtype=np.bool_),
|
|
76
|
+
outb1=np.array([row.outb1 for row in rows], dtype=np.bool_),
|
|
77
|
+
outc1=np.array([row.outc1 for row in rows], dtype=np.bool_),
|
|
78
|
+
outd1=np.array([row.outd1 for row in rows], dtype=np.bool_),
|
|
79
|
+
oute1=np.array([row.oute1 for row in rows], dtype=np.bool_),
|
|
80
|
+
outf1=np.array([row.outf1 for row in rows], dtype=np.bool_),
|
|
81
|
+
time2=np.array([row.time2 for row in rows], dtype=np.uint32),
|
|
82
|
+
outa2=np.array([row.outa2 for row in rows], dtype=np.bool_),
|
|
83
|
+
outb2=np.array([row.outb2 for row in rows], dtype=np.bool_),
|
|
84
|
+
outc2=np.array([row.outc2 for row in rows], dtype=np.bool_),
|
|
85
|
+
outd2=np.array([row.outd2 for row in rows], dtype=np.bool_),
|
|
86
|
+
oute2=np.array([row.oute2 for row in rows], dtype=np.bool_),
|
|
87
|
+
outf2=np.array([row.outf2 for row in rows], dtype=np.bool_),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
T = TypeVar("T", bound=np.generic)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def seq_table_from_arrays(
|
|
95
|
+
*,
|
|
96
|
+
repeats: Optional[npt.NDArray[np.uint16]] = None,
|
|
97
|
+
trigger: Optional[Sequence[SeqTrigger]] = None,
|
|
98
|
+
position: Optional[npt.NDArray[np.int32]] = None,
|
|
99
|
+
time1: Optional[npt.NDArray[np.uint32]] = None,
|
|
100
|
+
outa1: Optional[npt.NDArray[np.bool_]] = None,
|
|
101
|
+
outb1: Optional[npt.NDArray[np.bool_]] = None,
|
|
102
|
+
outc1: Optional[npt.NDArray[np.bool_]] = None,
|
|
103
|
+
outd1: Optional[npt.NDArray[np.bool_]] = None,
|
|
104
|
+
oute1: Optional[npt.NDArray[np.bool_]] = None,
|
|
105
|
+
outf1: Optional[npt.NDArray[np.bool_]] = None,
|
|
106
|
+
time2: npt.NDArray[np.uint32],
|
|
107
|
+
outa2: Optional[npt.NDArray[np.bool_]] = None,
|
|
108
|
+
outb2: Optional[npt.NDArray[np.bool_]] = None,
|
|
109
|
+
outc2: Optional[npt.NDArray[np.bool_]] = None,
|
|
110
|
+
outd2: Optional[npt.NDArray[np.bool_]] = None,
|
|
111
|
+
oute2: Optional[npt.NDArray[np.bool_]] = None,
|
|
112
|
+
outf2: Optional[npt.NDArray[np.bool_]] = None,
|
|
113
|
+
) -> SeqTable:
|
|
114
|
+
"""
|
|
115
|
+
Constructs a sequence table from a series of columns as arrays.
|
|
116
|
+
time2 is the only required argument and must not be None.
|
|
117
|
+
All other provided arguments must be of equal length to time2.
|
|
118
|
+
If any other argument is not given, or else given as None or empty,
|
|
119
|
+
an array of length len(time2) filled with the following is defaulted:
|
|
120
|
+
repeats: 1
|
|
121
|
+
trigger: SeqTrigger.IMMEDIATE
|
|
122
|
+
all others: 0/False as appropriate
|
|
123
|
+
"""
|
|
124
|
+
assert time2 is not None, "time2 must be provided"
|
|
125
|
+
length = len(time2)
|
|
126
|
+
assert 0 < length < 4096, f"Length {length} not in range"
|
|
127
|
+
|
|
128
|
+
def or_default(
|
|
129
|
+
value: Optional[npt.NDArray[T]], dtype: Type[T], default_value: int = 0
|
|
130
|
+
) -> npt.NDArray[T]:
|
|
131
|
+
if value is None or len(value) == 0:
|
|
132
|
+
return np.full(length, default_value, dtype=dtype)
|
|
133
|
+
return value
|
|
134
|
+
|
|
135
|
+
table = SeqTable(
|
|
136
|
+
repeats=or_default(repeats, np.uint16, 1),
|
|
137
|
+
trigger=trigger or [SeqTrigger.IMMEDIATE] * length,
|
|
138
|
+
position=or_default(position, np.int32),
|
|
139
|
+
time1=or_default(time1, np.uint32),
|
|
140
|
+
outa1=or_default(outa1, np.bool_),
|
|
141
|
+
outb1=or_default(outb1, np.bool_),
|
|
142
|
+
outc1=or_default(outc1, np.bool_),
|
|
143
|
+
outd1=or_default(outd1, np.bool_),
|
|
144
|
+
oute1=or_default(oute1, np.bool_),
|
|
145
|
+
outf1=or_default(outf1, np.bool_),
|
|
146
|
+
time2=time2,
|
|
147
|
+
outa2=or_default(outa2, np.bool_),
|
|
148
|
+
outb2=or_default(outb2, np.bool_),
|
|
149
|
+
outc2=or_default(outc2, np.bool_),
|
|
150
|
+
outd2=or_default(outd2, np.bool_),
|
|
151
|
+
oute2=or_default(oute2, np.bool_),
|
|
152
|
+
outf2=or_default(outf2, np.bool_),
|
|
153
|
+
)
|
|
154
|
+
for k, v in table.items():
|
|
155
|
+
size = len(v) # type: ignore
|
|
156
|
+
if size != length:
|
|
157
|
+
raise ValueError(f"{k}: has length {size} not {length}")
|
|
158
|
+
return table
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from typing import Any, Dict, Sequence
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def phase_sorter(panda_signal_values: Dict[str, Any]) -> Sequence[Dict[str, Any]]:
|
|
5
|
+
# Panda has two load phases. If the signal name ends in the string "UNITS",
|
|
6
|
+
# it needs to be loaded first so put in first phase
|
|
7
|
+
phase_1, phase_2 = {}, {}
|
|
8
|
+
|
|
9
|
+
for key, value in panda_signal_values.items():
|
|
10
|
+
if key.endswith("units"):
|
|
11
|
+
phase_1[key] = value
|
|
12
|
+
else:
|
|
13
|
+
phase_2[key] = value
|
|
14
|
+
|
|
15
|
+
return [phase_1, phase_2]
|