ophyd-async 0.5.2__py3-none-any.whl → 0.6.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/__init__.py +10 -1
- ophyd_async/__main__.py +12 -4
- ophyd_async/_version.py +2 -2
- ophyd_async/core/__init__.py +11 -3
- ophyd_async/core/_detector.py +72 -63
- ophyd_async/core/_device.py +13 -15
- ophyd_async/core/_device_save_loader.py +30 -19
- ophyd_async/core/_flyer.py +6 -4
- ophyd_async/core/_hdf_dataset.py +8 -9
- ophyd_async/core/_log.py +3 -1
- ophyd_async/core/_mock_signal_backend.py +11 -9
- ophyd_async/core/_mock_signal_utils.py +8 -5
- ophyd_async/core/_protocol.py +7 -7
- ophyd_async/core/_providers.py +11 -11
- ophyd_async/core/_readable.py +30 -22
- ophyd_async/core/_signal.py +52 -51
- ophyd_async/core/_signal_backend.py +20 -7
- ophyd_async/core/_soft_signal_backend.py +62 -32
- ophyd_async/core/_status.py +7 -9
- ophyd_async/core/_table.py +63 -0
- ophyd_async/core/_utils.py +24 -28
- ophyd_async/epics/adaravis/_aravis_controller.py +17 -16
- ophyd_async/epics/adaravis/_aravis_io.py +2 -1
- ophyd_async/epics/adcore/_core_io.py +2 -0
- ophyd_async/epics/adcore/_core_logic.py +2 -3
- ophyd_async/epics/adcore/_hdf_writer.py +19 -8
- ophyd_async/epics/adcore/_single_trigger.py +1 -1
- ophyd_async/epics/adcore/_utils.py +5 -6
- ophyd_async/epics/adkinetix/_kinetix_controller.py +19 -14
- ophyd_async/epics/adpilatus/_pilatus_controller.py +18 -16
- ophyd_async/epics/adsimdetector/_sim.py +6 -5
- ophyd_async/epics/adsimdetector/_sim_controller.py +20 -15
- ophyd_async/epics/advimba/_vimba_controller.py +21 -16
- ophyd_async/epics/demo/_mover.py +4 -5
- ophyd_async/epics/demo/sensor.db +0 -1
- ophyd_async/epics/eiger/_eiger.py +1 -1
- ophyd_async/epics/eiger/_eiger_controller.py +16 -16
- ophyd_async/epics/eiger/_odin_io.py +6 -5
- ophyd_async/epics/motor.py +8 -10
- ophyd_async/epics/pvi/_pvi.py +30 -33
- ophyd_async/epics/signal/_aioca.py +55 -25
- ophyd_async/epics/signal/_common.py +3 -10
- ophyd_async/epics/signal/_epics_transport.py +11 -8
- ophyd_async/epics/signal/_p4p.py +79 -30
- ophyd_async/epics/signal/_signal.py +6 -8
- ophyd_async/fastcs/panda/__init__.py +0 -6
- ophyd_async/fastcs/panda/_control.py +14 -15
- ophyd_async/fastcs/panda/_hdf_panda.py +11 -4
- ophyd_async/fastcs/panda/_table.py +111 -138
- ophyd_async/fastcs/panda/_trigger.py +1 -2
- ophyd_async/fastcs/panda/_utils.py +3 -2
- ophyd_async/fastcs/panda/_writer.py +28 -13
- ophyd_async/plan_stubs/_fly.py +16 -16
- ophyd_async/plan_stubs/_nd_attributes.py +12 -6
- ophyd_async/sim/demo/_pattern_detector/_pattern_detector.py +3 -3
- ophyd_async/sim/demo/_pattern_detector/_pattern_detector_controller.py +24 -20
- ophyd_async/sim/demo/_pattern_detector/_pattern_detector_writer.py +9 -6
- ophyd_async/sim/demo/_pattern_detector/_pattern_generator.py +21 -23
- ophyd_async/sim/demo/_sim_motor.py +2 -1
- {ophyd_async-0.5.2.dist-info → ophyd_async-0.6.0.dist-info}/METADATA +46 -45
- ophyd_async-0.6.0.dist-info/RECORD +96 -0
- {ophyd_async-0.5.2.dist-info → ophyd_async-0.6.0.dist-info}/WHEEL +1 -1
- ophyd_async-0.5.2.dist-info/RECORD +0 -95
- {ophyd_async-0.5.2.dist-info → ophyd_async-0.6.0.dist-info}/LICENSE +0 -0
- {ophyd_async-0.5.2.dist-info → ophyd_async-0.6.0.dist-info}/entry_points.txt +0 -0
- {ophyd_async-0.5.2.dist-info → ophyd_async-0.6.0.dist-info}/top_level.txt +0 -0
ophyd_async/epics/signal/_p4p.py
CHANGED
|
@@ -3,14 +3,19 @@ import atexit
|
|
|
3
3
|
import inspect
|
|
4
4
|
import logging
|
|
5
5
|
import time
|
|
6
|
+
from collections.abc import Sequence
|
|
6
7
|
from dataclasses import dataclass
|
|
7
8
|
from enum import Enum
|
|
8
9
|
from math import isnan, nan
|
|
9
|
-
from typing import Any,
|
|
10
|
+
from typing import Any, get_origin
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
import numpy as np
|
|
13
|
+
from bluesky.protocols import Reading
|
|
14
|
+
from event_model import DataKey
|
|
15
|
+
from event_model.documents.event_descriptor import Dtype
|
|
12
16
|
from p4p import Value
|
|
13
17
|
from p4p.client.asyncio import Context, Subscription
|
|
18
|
+
from pydantic import BaseModel
|
|
14
19
|
|
|
15
20
|
from ophyd_async.core import (
|
|
16
21
|
DEFAULT_TIMEOUT,
|
|
@@ -21,13 +26,14 @@ from ophyd_async.core import (
|
|
|
21
26
|
T,
|
|
22
27
|
get_dtype,
|
|
23
28
|
get_unique,
|
|
29
|
+
is_pydantic_model,
|
|
24
30
|
wait_for_connection,
|
|
25
31
|
)
|
|
26
32
|
|
|
27
33
|
from ._common import LimitPair, Limits, common_meta, get_supported_values
|
|
28
34
|
|
|
29
35
|
# https://mdavidsaver.github.io/p4p/values.html
|
|
30
|
-
specifier_to_dtype:
|
|
36
|
+
specifier_to_dtype: dict[str, Dtype] = {
|
|
31
37
|
"?": "integer", # bool
|
|
32
38
|
"b": "integer", # int8
|
|
33
39
|
"B": "integer", # uint8
|
|
@@ -42,7 +48,7 @@ specifier_to_dtype: Dict[str, Dtype] = {
|
|
|
42
48
|
"s": "string",
|
|
43
49
|
}
|
|
44
50
|
|
|
45
|
-
specifier_to_np_dtype:
|
|
51
|
+
specifier_to_np_dtype: dict[str, str] = {
|
|
46
52
|
"?": "<i2", # bool
|
|
47
53
|
"b": "|i1", # int8
|
|
48
54
|
"B": "|u1", # uint8
|
|
@@ -62,9 +68,9 @@ def _data_key_from_value(
|
|
|
62
68
|
source: str,
|
|
63
69
|
value: Value,
|
|
64
70
|
*,
|
|
65
|
-
shape:
|
|
66
|
-
choices:
|
|
67
|
-
dtype:
|
|
71
|
+
shape: list[int] | None = None,
|
|
72
|
+
choices: list[str] | None = None,
|
|
73
|
+
dtype: Dtype | None = None,
|
|
68
74
|
) -> DataKey:
|
|
69
75
|
"""
|
|
70
76
|
Args:
|
|
@@ -105,7 +111,8 @@ def _data_key_from_value(
|
|
|
105
111
|
d = DataKey(
|
|
106
112
|
source=source,
|
|
107
113
|
dtype=dtype,
|
|
108
|
-
|
|
114
|
+
# type ignore until https://github.com/bluesky/event-model/issues/308
|
|
115
|
+
dtype_numpy=dtype_numpy, # type: ignore
|
|
109
116
|
shape=shape,
|
|
110
117
|
)
|
|
111
118
|
if display_data is not None:
|
|
@@ -115,10 +122,12 @@ def _data_key_from_value(
|
|
|
115
122
|
d[key] = attr
|
|
116
123
|
|
|
117
124
|
if choices is not None:
|
|
118
|
-
|
|
125
|
+
# type ignore until https://github.com/bluesky/event-model/issues/309
|
|
126
|
+
d["choices"] = choices # type: ignore
|
|
119
127
|
|
|
120
128
|
if limits := _limits_from_value(value):
|
|
121
|
-
|
|
129
|
+
# type ignore until https://github.com/bluesky/event-model/issues/309
|
|
130
|
+
d["limits"] = limits # type: ignore
|
|
122
131
|
|
|
123
132
|
return d
|
|
124
133
|
|
|
@@ -149,7 +158,7 @@ class PvaConverter:
|
|
|
149
158
|
def value(self, value):
|
|
150
159
|
return value["value"]
|
|
151
160
|
|
|
152
|
-
def reading(self, value):
|
|
161
|
+
def reading(self, value) -> Reading:
|
|
153
162
|
ts = value["timeStamp"]
|
|
154
163
|
sv = value["alarm"]["severity"]
|
|
155
164
|
return {
|
|
@@ -161,13 +170,13 @@ class PvaConverter:
|
|
|
161
170
|
def get_datakey(self, source: str, value) -> DataKey:
|
|
162
171
|
return _data_key_from_value(source, value)
|
|
163
172
|
|
|
164
|
-
def metadata_fields(self) ->
|
|
173
|
+
def metadata_fields(self) -> list[str]:
|
|
165
174
|
"""
|
|
166
175
|
Fields to request from PVA for metadata.
|
|
167
176
|
"""
|
|
168
177
|
return ["alarm", "timeStamp"]
|
|
169
178
|
|
|
170
|
-
def value_fields(self) ->
|
|
179
|
+
def value_fields(self) -> list[str]:
|
|
171
180
|
"""
|
|
172
181
|
Fields to request from PVA for the value.
|
|
173
182
|
"""
|
|
@@ -182,11 +191,11 @@ class PvaArrayConverter(PvaConverter):
|
|
|
182
191
|
|
|
183
192
|
|
|
184
193
|
class PvaNDArrayConverter(PvaConverter):
|
|
185
|
-
def metadata_fields(self) ->
|
|
194
|
+
def metadata_fields(self) -> list[str]:
|
|
186
195
|
return super().metadata_fields() + ["dimension"]
|
|
187
196
|
|
|
188
|
-
def _get_dimensions(self, value) ->
|
|
189
|
-
dimensions:
|
|
197
|
+
def _get_dimensions(self, value) -> list[int]:
|
|
198
|
+
dimensions: list[Value] = value["dimension"]
|
|
190
199
|
dims = [dim.size for dim in dimensions]
|
|
191
200
|
# Note: dimensions in NTNDArray are in fortran-like order
|
|
192
201
|
# with first index changing fastest.
|
|
@@ -221,7 +230,7 @@ class PvaEnumConverter(PvaConverter):
|
|
|
221
230
|
def __init__(self, choices: dict[str, str]):
|
|
222
231
|
self.choices = tuple(choices.values())
|
|
223
232
|
|
|
224
|
-
def write_value(self, value:
|
|
233
|
+
def write_value(self, value: Enum | str):
|
|
225
234
|
if isinstance(value, Enum):
|
|
226
235
|
return value.value
|
|
227
236
|
else:
|
|
@@ -250,11 +259,24 @@ class PvaTableConverter(PvaConverter):
|
|
|
250
259
|
|
|
251
260
|
def get_datakey(self, source: str, value) -> DataKey:
|
|
252
261
|
# This is wrong, but defer until we know how to actually describe a table
|
|
253
|
-
return _data_key_from_value(source, value, dtype="object")
|
|
262
|
+
return _data_key_from_value(source, value, dtype="object") # type: ignore
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class PvaPydanticModelConverter(PvaConverter):
|
|
266
|
+
def __init__(self, datatype: BaseModel):
|
|
267
|
+
self.datatype = datatype
|
|
268
|
+
|
|
269
|
+
def value(self, value: Value):
|
|
270
|
+
return self.datatype(**value.todict()) # type: ignore
|
|
271
|
+
|
|
272
|
+
def write_value(self, value: BaseModel | dict[str, Any]):
|
|
273
|
+
if isinstance(value, self.datatype): # type: ignore
|
|
274
|
+
return value.model_dump(mode="python") # type: ignore
|
|
275
|
+
return value
|
|
254
276
|
|
|
255
277
|
|
|
256
278
|
class PvaDictConverter(PvaConverter):
|
|
257
|
-
def reading(self, value):
|
|
279
|
+
def reading(self, value) -> Reading:
|
|
258
280
|
ts = time.time()
|
|
259
281
|
value = value.todict()
|
|
260
282
|
# Alarm severity is vacuously 0 for a table
|
|
@@ -266,13 +288,13 @@ class PvaDictConverter(PvaConverter):
|
|
|
266
288
|
def get_datakey(self, source: str, value) -> DataKey:
|
|
267
289
|
raise NotImplementedError("Describing Dict signals not currently supported")
|
|
268
290
|
|
|
269
|
-
def metadata_fields(self) ->
|
|
291
|
+
def metadata_fields(self) -> list[str]:
|
|
270
292
|
"""
|
|
271
293
|
Fields to request from PVA for metadata.
|
|
272
294
|
"""
|
|
273
295
|
return []
|
|
274
296
|
|
|
275
|
-
def value_fields(self) ->
|
|
297
|
+
def value_fields(self) -> list[str]:
|
|
276
298
|
"""
|
|
277
299
|
Fields to request from PVA for the value.
|
|
278
300
|
"""
|
|
@@ -284,7 +306,7 @@ class DisconnectedPvaConverter(PvaConverter):
|
|
|
284
306
|
raise NotImplementedError("No PV has been set as connect() has not been called")
|
|
285
307
|
|
|
286
308
|
|
|
287
|
-
def make_converter(datatype:
|
|
309
|
+
def make_converter(datatype: type | None, values: dict[str, Any]) -> PvaConverter:
|
|
288
310
|
pv = list(values)[0]
|
|
289
311
|
typeid = get_unique({k: v.getID() for k, v in values.items()}, "typeids")
|
|
290
312
|
typ = get_unique(
|
|
@@ -333,7 +355,7 @@ def make_converter(datatype: Optional[Type], values: Dict[str, Any]) -> PvaConve
|
|
|
333
355
|
and issubclass(datatype, RuntimeSubsetEnum)
|
|
334
356
|
):
|
|
335
357
|
return PvaEnumConverter(
|
|
336
|
-
get_supported_values(pv, datatype, datatype.choices)
|
|
358
|
+
get_supported_values(pv, datatype, datatype.choices) # type: ignore
|
|
337
359
|
)
|
|
338
360
|
elif datatype and not issubclass(typ, datatype):
|
|
339
361
|
# Allow int signals to represent float records when prec is 0
|
|
@@ -348,6 +370,8 @@ def make_converter(datatype: Optional[Type], values: Dict[str, Any]) -> PvaConve
|
|
|
348
370
|
raise TypeError(f"{pv} has type {typ.__name__} not {datatype.__name__}")
|
|
349
371
|
return PvaConverter()
|
|
350
372
|
elif "NTTable" in typeid:
|
|
373
|
+
if is_pydantic_model(datatype):
|
|
374
|
+
return PvaPydanticModelConverter(datatype) # type: ignore
|
|
351
375
|
return PvaTableConverter()
|
|
352
376
|
elif "structure" in typeid:
|
|
353
377
|
return PvaDictConverter()
|
|
@@ -356,15 +380,40 @@ def make_converter(datatype: Optional[Type], values: Dict[str, Any]) -> PvaConve
|
|
|
356
380
|
|
|
357
381
|
|
|
358
382
|
class PvaSignalBackend(SignalBackend[T]):
|
|
359
|
-
_ctxt:
|
|
383
|
+
_ctxt: Context | None = None
|
|
384
|
+
|
|
385
|
+
_ALLOWED_DATATYPES = (
|
|
386
|
+
bool,
|
|
387
|
+
int,
|
|
388
|
+
float,
|
|
389
|
+
str,
|
|
390
|
+
Sequence,
|
|
391
|
+
np.ndarray,
|
|
392
|
+
Enum,
|
|
393
|
+
RuntimeSubsetEnum,
|
|
394
|
+
BaseModel,
|
|
395
|
+
dict,
|
|
396
|
+
)
|
|
360
397
|
|
|
361
|
-
|
|
398
|
+
@classmethod
|
|
399
|
+
def datatype_allowed(cls, dtype: Any) -> bool:
|
|
400
|
+
stripped_origin = get_origin(dtype) or dtype
|
|
401
|
+
if dtype is None:
|
|
402
|
+
return True
|
|
403
|
+
return inspect.isclass(stripped_origin) and issubclass(
|
|
404
|
+
stripped_origin, cls._ALLOWED_DATATYPES
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
def __init__(self, datatype: type[T] | None, read_pv: str, write_pv: str):
|
|
362
408
|
self.datatype = datatype
|
|
409
|
+
if not PvaSignalBackend.datatype_allowed(self.datatype):
|
|
410
|
+
raise TypeError(f"Given datatype {self.datatype} unsupported in PVA.")
|
|
411
|
+
|
|
363
412
|
self.read_pv = read_pv
|
|
364
413
|
self.write_pv = write_pv
|
|
365
|
-
self.initial_values:
|
|
414
|
+
self.initial_values: dict[str, Any] = {}
|
|
366
415
|
self.converter: PvaConverter = DisconnectedPvaConverter()
|
|
367
|
-
self.subscription:
|
|
416
|
+
self.subscription: Subscription | None = None
|
|
368
417
|
|
|
369
418
|
def source(self, name: str):
|
|
370
419
|
return f"pva://{self.read_pv}"
|
|
@@ -404,7 +453,7 @@ class PvaSignalBackend(SignalBackend[T]):
|
|
|
404
453
|
await self._store_initial_value(self.read_pv, timeout=timeout)
|
|
405
454
|
self.converter = make_converter(self.datatype, self.initial_values)
|
|
406
455
|
|
|
407
|
-
async def put(self, value:
|
|
456
|
+
async def put(self, value: T | None, wait=True, timeout=None):
|
|
408
457
|
if value is None:
|
|
409
458
|
write_value = self.initial_values[self.write_pv]
|
|
410
459
|
else:
|
|
@@ -424,7 +473,7 @@ class PvaSignalBackend(SignalBackend[T]):
|
|
|
424
473
|
value = await self.ctxt.get(self.read_pv)
|
|
425
474
|
return self.converter.get_datakey(source, value)
|
|
426
475
|
|
|
427
|
-
def _pva_request_string(self, fields:
|
|
476
|
+
def _pva_request_string(self, fields: list[str]) -> str:
|
|
428
477
|
"""
|
|
429
478
|
Converts a list of requested fields into a PVA request string which can be
|
|
430
479
|
passed to p4p.
|
|
@@ -447,7 +496,7 @@ class PvaSignalBackend(SignalBackend[T]):
|
|
|
447
496
|
value = await self.ctxt.get(self.write_pv, "field(value)")
|
|
448
497
|
return self.converter.value(value)
|
|
449
498
|
|
|
450
|
-
def set_callback(self, callback:
|
|
499
|
+
def set_callback(self, callback: ReadingValueCallback[T] | None) -> None:
|
|
451
500
|
if callback:
|
|
452
501
|
assert (
|
|
453
502
|
not self.subscription
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from typing import Optional, Tuple, Type
|
|
6
|
-
|
|
7
5
|
from ophyd_async.core import (
|
|
8
6
|
SignalBackend,
|
|
9
7
|
SignalR,
|
|
@@ -19,7 +17,7 @@ from ._epics_transport import _EpicsTransport
|
|
|
19
17
|
_default_epics_transport = _EpicsTransport.ca
|
|
20
18
|
|
|
21
19
|
|
|
22
|
-
def _transport_pv(pv: str) ->
|
|
20
|
+
def _transport_pv(pv: str) -> tuple[_EpicsTransport, str]:
|
|
23
21
|
split = pv.split("://", 1)
|
|
24
22
|
if len(split) > 1:
|
|
25
23
|
# We got something like pva://mydevice, so use specified comms mode
|
|
@@ -32,7 +30,7 @@ def _transport_pv(pv: str) -> Tuple[_EpicsTransport, str]:
|
|
|
32
30
|
|
|
33
31
|
|
|
34
32
|
def _epics_signal_backend(
|
|
35
|
-
datatype:
|
|
33
|
+
datatype: type[T] | None, read_pv: str, write_pv: str
|
|
36
34
|
) -> SignalBackend[T]:
|
|
37
35
|
"""Create an epics signal backend."""
|
|
38
36
|
r_transport, r_pv = _transport_pv(read_pv)
|
|
@@ -42,7 +40,7 @@ def _epics_signal_backend(
|
|
|
42
40
|
|
|
43
41
|
|
|
44
42
|
def epics_signal_rw(
|
|
45
|
-
datatype:
|
|
43
|
+
datatype: type[T], read_pv: str, write_pv: str | None = None, name: str = ""
|
|
46
44
|
) -> SignalRW[T]:
|
|
47
45
|
"""Create a `SignalRW` backed by 1 or 2 EPICS PVs
|
|
48
46
|
|
|
@@ -60,7 +58,7 @@ def epics_signal_rw(
|
|
|
60
58
|
|
|
61
59
|
|
|
62
60
|
def epics_signal_rw_rbv(
|
|
63
|
-
datatype:
|
|
61
|
+
datatype: type[T], write_pv: str, read_suffix: str = "_RBV", name: str = ""
|
|
64
62
|
) -> SignalRW[T]:
|
|
65
63
|
"""Create a `SignalRW` backed by 1 or 2 EPICS PVs, with a suffix on the readback pv
|
|
66
64
|
|
|
@@ -76,7 +74,7 @@ def epics_signal_rw_rbv(
|
|
|
76
74
|
return epics_signal_rw(datatype, f"{write_pv}{read_suffix}", write_pv, name)
|
|
77
75
|
|
|
78
76
|
|
|
79
|
-
def epics_signal_r(datatype:
|
|
77
|
+
def epics_signal_r(datatype: type[T], read_pv: str, name: str = "") -> SignalR[T]:
|
|
80
78
|
"""Create a `SignalR` backed by 1 EPICS PV
|
|
81
79
|
|
|
82
80
|
Parameters
|
|
@@ -90,7 +88,7 @@ def epics_signal_r(datatype: Type[T], read_pv: str, name: str = "") -> SignalR[T
|
|
|
90
88
|
return SignalR(backend, name=name)
|
|
91
89
|
|
|
92
90
|
|
|
93
|
-
def epics_signal_w(datatype:
|
|
91
|
+
def epics_signal_w(datatype: type[T], write_pv: str, name: str = "") -> SignalW[T]:
|
|
94
92
|
"""Create a `SignalW` backed by 1 EPICS PVs
|
|
95
93
|
|
|
96
94
|
Parameters
|
|
@@ -15,10 +15,7 @@ from ._table import (
|
|
|
15
15
|
DatasetTable,
|
|
16
16
|
PandaHdf5DatasetType,
|
|
17
17
|
SeqTable,
|
|
18
|
-
SeqTableRow,
|
|
19
18
|
SeqTrigger,
|
|
20
|
-
seq_table_from_arrays,
|
|
21
|
-
seq_table_from_rows,
|
|
22
19
|
)
|
|
23
20
|
from ._trigger import (
|
|
24
21
|
PcompInfo,
|
|
@@ -45,10 +42,7 @@ __all__ = [
|
|
|
45
42
|
"DatasetTable",
|
|
46
43
|
"PandaHdf5DatasetType",
|
|
47
44
|
"SeqTable",
|
|
48
|
-
"SeqTableRow",
|
|
49
45
|
"SeqTrigger",
|
|
50
|
-
"seq_table_from_arrays",
|
|
51
|
-
"seq_table_from_rows",
|
|
52
46
|
"PcompInfo",
|
|
53
47
|
"SeqTableInfo",
|
|
54
48
|
"StaticPcompTriggerLogic",
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from typing import Optional
|
|
3
2
|
|
|
4
3
|
from ophyd_async.core import (
|
|
5
|
-
AsyncStatus,
|
|
6
4
|
DetectorControl,
|
|
7
5
|
DetectorTrigger,
|
|
8
6
|
wait_for_value,
|
|
9
7
|
)
|
|
8
|
+
from ophyd_async.core._detector import TriggerInfo
|
|
9
|
+
from ophyd_async.core._status import AsyncStatus
|
|
10
10
|
|
|
11
11
|
from ._block import PcapBlock
|
|
12
12
|
|
|
@@ -14,25 +14,24 @@ from ._block import PcapBlock
|
|
|
14
14
|
class PandaPcapController(DetectorControl):
|
|
15
15
|
def __init__(self, pcap: PcapBlock) -> None:
|
|
16
16
|
self.pcap = pcap
|
|
17
|
+
self._arm_status: AsyncStatus | None = None
|
|
17
18
|
|
|
18
|
-
def get_deadtime(self, exposure: float) -> float:
|
|
19
|
+
def get_deadtime(self, exposure: float | None) -> float:
|
|
19
20
|
return 0.000000008
|
|
20
21
|
|
|
21
|
-
async def
|
|
22
|
-
|
|
23
|
-
num: int,
|
|
24
|
-
trigger: DetectorTrigger = DetectorTrigger.constant_gate,
|
|
25
|
-
exposure: Optional[float] = None,
|
|
26
|
-
) -> AsyncStatus:
|
|
27
|
-
assert trigger in (
|
|
22
|
+
async def prepare(self, trigger_info: TriggerInfo):
|
|
23
|
+
assert trigger_info.trigger in (
|
|
28
24
|
DetectorTrigger.constant_gate,
|
|
29
|
-
|
|
25
|
+
DetectorTrigger.variable_gate,
|
|
30
26
|
), "Only constant_gate and variable_gate triggering is supported on the PandA"
|
|
31
|
-
|
|
27
|
+
|
|
28
|
+
async def arm(self):
|
|
29
|
+
self._arm_status = self.pcap.arm.set(True)
|
|
32
30
|
await wait_for_value(self.pcap.active, True, timeout=1)
|
|
33
|
-
return AsyncStatus(wait_for_value(self.pcap.active, False, timeout=None))
|
|
34
31
|
|
|
35
|
-
async def
|
|
32
|
+
async def wait_for_idle(self):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
async def disarm(self):
|
|
36
36
|
await asyncio.gather(self.pcap.arm.set(False))
|
|
37
37
|
await wait_for_value(self.pcap.active, False, timeout=1)
|
|
38
|
-
return AsyncStatus(wait_for_value(self.pcap.active, False, timeout=None))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from collections.abc import Sequence
|
|
4
4
|
|
|
5
5
|
from ophyd_async.core import DEFAULT_TIMEOUT, PathProvider, SignalR, StandardDetector
|
|
6
6
|
from ophyd_async.epics.pvi import create_children_from_annotations, fill_pvi_entries
|
|
@@ -36,7 +36,14 @@ class HDFPanda(CommonPandaBlocks, StandardDetector):
|
|
|
36
36
|
)
|
|
37
37
|
|
|
38
38
|
async def connect(
|
|
39
|
-
self,
|
|
40
|
-
|
|
39
|
+
self,
|
|
40
|
+
mock: bool = False,
|
|
41
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
42
|
+
force_reconnect: bool = False,
|
|
43
|
+
):
|
|
44
|
+
# TODO: this doesn't support caching
|
|
45
|
+
# https://github.com/bluesky/ophyd-async/issues/472
|
|
41
46
|
await fill_pvi_entries(self, self._prefix + "PVI", timeout=timeout, mock=mock)
|
|
42
|
-
await super().connect(
|
|
47
|
+
await super().connect(
|
|
48
|
+
mock=mock, timeout=timeout, force_reconnect=force_reconnect
|
|
49
|
+
)
|