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
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
from typing import Any, get_args, get_origin
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from ophyd_async.core import (
|
|
7
|
+
SignalBackend,
|
|
8
|
+
SignalDatatypeT,
|
|
9
|
+
SubsetEnum,
|
|
10
|
+
get_dtype,
|
|
11
|
+
get_enum_cls,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_supported_values(
|
|
16
|
+
pv: str,
|
|
17
|
+
datatype: type,
|
|
18
|
+
pv_choices: Sequence[str],
|
|
19
|
+
) -> dict[str, str]:
|
|
20
|
+
enum_cls = get_enum_cls(datatype)
|
|
21
|
+
if not enum_cls:
|
|
22
|
+
raise TypeError(f"{datatype} is not an Enum")
|
|
23
|
+
choices = [v.value for v in enum_cls]
|
|
24
|
+
error_msg = f"{pv} has choices {pv_choices}, but {datatype} requested {choices} "
|
|
25
|
+
if issubclass(enum_cls, SubsetEnum):
|
|
26
|
+
if not set(choices).issubset(pv_choices):
|
|
27
|
+
raise TypeError(error_msg + "to be a subset of them.")
|
|
28
|
+
else:
|
|
29
|
+
if set(choices) != set(pv_choices):
|
|
30
|
+
raise TypeError(error_msg + "to be strictly equal to them.")
|
|
31
|
+
|
|
32
|
+
# Take order from the pv choices
|
|
33
|
+
supported_values = {x: x for x in pv_choices}
|
|
34
|
+
# But override those that we specify via the datatype
|
|
35
|
+
for v in enum_cls:
|
|
36
|
+
supported_values[v.value] = v
|
|
37
|
+
return supported_values
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def format_datatype(datatype: Any) -> str:
|
|
41
|
+
if get_origin(datatype) is np.ndarray and get_args(datatype)[0] == tuple[int]:
|
|
42
|
+
dtype = get_dtype(datatype)
|
|
43
|
+
return f"Array1D[np.{dtype.name}]"
|
|
44
|
+
elif get_origin(datatype) is Sequence:
|
|
45
|
+
return f"Sequence[{get_args(datatype)[0].__name__}]"
|
|
46
|
+
elif isinstance(datatype, type):
|
|
47
|
+
return datatype.__name__
|
|
48
|
+
else:
|
|
49
|
+
return str(datatype)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class EpicsSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
datatype: type[SignalDatatypeT] | None,
|
|
56
|
+
read_pv: str = "",
|
|
57
|
+
write_pv: str = "",
|
|
58
|
+
):
|
|
59
|
+
self.read_pv = read_pv
|
|
60
|
+
self.write_pv = write_pv
|
|
61
|
+
super().__init__(datatype)
|
ophyd_async/epics/demo/_mover.py
CHANGED
|
@@ -8,15 +8,14 @@ from ophyd_async.core import (
|
|
|
8
8
|
DEFAULT_TIMEOUT,
|
|
9
9
|
AsyncStatus,
|
|
10
10
|
CalculatableTimeout,
|
|
11
|
-
ConfigSignal,
|
|
12
11
|
Device,
|
|
13
|
-
HintedSignal,
|
|
14
12
|
StandardReadable,
|
|
15
13
|
WatchableAsyncStatus,
|
|
16
14
|
WatcherUpdate,
|
|
17
15
|
observe_value,
|
|
18
16
|
)
|
|
19
|
-
from ophyd_async.
|
|
17
|
+
from ophyd_async.core import StandardReadableFormat as Format
|
|
18
|
+
from ophyd_async.epics.core import epics_signal_r, epics_signal_rw, epics_signal_x
|
|
20
19
|
|
|
21
20
|
|
|
22
21
|
class Mover(StandardReadable, Movable, Stoppable):
|
|
@@ -24,9 +23,9 @@ class Mover(StandardReadable, Movable, Stoppable):
|
|
|
24
23
|
|
|
25
24
|
def __init__(self, prefix: str, name="") -> None:
|
|
26
25
|
# Define some signals
|
|
27
|
-
with self.add_children_as_readables(
|
|
26
|
+
with self.add_children_as_readables(Format.HINTED_SIGNAL):
|
|
28
27
|
self.readback = epics_signal_r(float, prefix + "Readback")
|
|
29
|
-
with self.add_children_as_readables(
|
|
28
|
+
with self.add_children_as_readables(Format.CONFIG_SIGNAL):
|
|
30
29
|
self.velocity = epics_signal_rw(float, prefix + "Velocity")
|
|
31
30
|
self.units = epics_signal_r(str, prefix + "Readback.EGU")
|
|
32
31
|
self.setpoint = epics_signal_rw(float, prefix + "Setpoint")
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
from
|
|
1
|
+
from typing import Annotated as A
|
|
2
2
|
|
|
3
|
-
from ophyd_async.core import
|
|
4
|
-
|
|
3
|
+
from ophyd_async.core import (
|
|
4
|
+
DeviceVector,
|
|
5
|
+
SignalR,
|
|
6
|
+
SignalRW,
|
|
7
|
+
StandardReadable,
|
|
8
|
+
StrictEnum,
|
|
9
|
+
)
|
|
10
|
+
from ophyd_async.core import StandardReadableFormat as Format
|
|
11
|
+
from ophyd_async.epics.core import EpicsDevice, PvSuffix
|
|
5
12
|
|
|
6
13
|
|
|
7
|
-
class EnergyMode(
|
|
14
|
+
class EnergyMode(StrictEnum):
|
|
8
15
|
"""Energy mode for `Sensor`"""
|
|
9
16
|
|
|
10
17
|
#: Low energy mode
|
|
@@ -13,17 +20,11 @@ class EnergyMode(str, Enum):
|
|
|
13
20
|
high = "High Energy"
|
|
14
21
|
|
|
15
22
|
|
|
16
|
-
class Sensor(StandardReadable):
|
|
23
|
+
class Sensor(StandardReadable, EpicsDevice):
|
|
17
24
|
"""A demo sensor that produces a scalar value based on X and Y Movers"""
|
|
18
25
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
with self.add_children_as_readables(HintedSignal):
|
|
22
|
-
self.value = epics_signal_r(float, prefix + "Value")
|
|
23
|
-
with self.add_children_as_readables(ConfigSignal):
|
|
24
|
-
self.mode = epics_signal_rw(EnergyMode, prefix + "Mode")
|
|
25
|
-
|
|
26
|
-
super().__init__(name=name)
|
|
26
|
+
value: A[SignalR[float], PvSuffix("Value"), Format.HINTED_SIGNAL]
|
|
27
|
+
mode: A[SignalRW[EnergyMode], PvSuffix("Mode"), Format.CONFIG_SIGNAL]
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
class SensorGroup(StandardReadable):
|
|
@@ -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
|
|
1
|
+
from ophyd_async.core import Device, StrictEnum
|
|
2
|
+
from ophyd_async.epics.core import epics_signal_r, epics_signal_rw_rbv, epics_signal_w
|
|
2
3
|
|
|
3
|
-
from ophyd_async.core import Device
|
|
4
|
-
from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw_rbv, epics_signal_w
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
class EigerTriggerMode(str, Enum):
|
|
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,17 +11,18 @@ 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
|
)
|
|
18
|
-
from ophyd_async.epics.
|
|
18
|
+
from ophyd_async.epics.core import (
|
|
19
19
|
epics_signal_r,
|
|
20
20
|
epics_signal_rw,
|
|
21
21
|
epics_signal_rw_rbv,
|
|
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
|
}
|
ophyd_async/epics/motor.py
CHANGED
|
@@ -14,14 +14,13 @@ from ophyd_async.core import (
|
|
|
14
14
|
DEFAULT_TIMEOUT,
|
|
15
15
|
AsyncStatus,
|
|
16
16
|
CalculatableTimeout,
|
|
17
|
-
ConfigSignal,
|
|
18
|
-
HintedSignal,
|
|
19
17
|
StandardReadable,
|
|
20
18
|
WatchableAsyncStatus,
|
|
21
19
|
WatcherUpdate,
|
|
22
20
|
observe_value,
|
|
23
21
|
)
|
|
24
|
-
from ophyd_async.
|
|
22
|
+
from ophyd_async.core import StandardReadableFormat as Format
|
|
23
|
+
from ophyd_async.epics.core import epics_signal_r, epics_signal_rw, epics_signal_x
|
|
25
24
|
|
|
26
25
|
|
|
27
26
|
class MotorLimitsException(Exception):
|
|
@@ -61,11 +60,11 @@ class Motor(StandardReadable, Locatable, Stoppable, Flyable, Preparable):
|
|
|
61
60
|
|
|
62
61
|
def __init__(self, prefix: str, name="") -> None:
|
|
63
62
|
# Define some signals
|
|
64
|
-
with self.add_children_as_readables(
|
|
63
|
+
with self.add_children_as_readables(Format.CONFIG_SIGNAL):
|
|
65
64
|
self.motor_egu = epics_signal_r(str, prefix + ".EGU")
|
|
66
65
|
self.velocity = epics_signal_rw(float, prefix + ".VELO")
|
|
67
66
|
|
|
68
|
-
with self.add_children_as_readables(
|
|
67
|
+
with self.add_children_as_readables(Format.HINTED_SIGNAL):
|
|
69
68
|
self.user_readback = epics_signal_r(float, prefix + ".RBV")
|
|
70
69
|
|
|
71
70
|
self.user_setpoint = epics_signal_rw(float, prefix + ".VAL")
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from ophyd_async.core import Device, DeviceConnector
|
|
2
|
+
from ophyd_async.epics.core import PviDeviceConnector
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def fastcs_connector(device: Device, uri: str) -> DeviceConnector:
|
|
6
|
+
# TODO: add Tango support based on uri scheme
|
|
7
|
+
connector = PviDeviceConnector(uri)
|
|
8
|
+
connector.create_children_from_annotations(device)
|
|
9
|
+
return connector
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
from ._block import (
|
|
2
|
+
BitMux,
|
|
2
3
|
CommonPandaBlocks,
|
|
3
4
|
DataBlock,
|
|
4
|
-
EnableDisableOptions,
|
|
5
5
|
PcapBlock,
|
|
6
6
|
PcompBlock,
|
|
7
|
-
|
|
7
|
+
PcompDirection,
|
|
8
8
|
PulseBlock,
|
|
9
9
|
SeqBlock,
|
|
10
10
|
TimeUnits,
|
|
@@ -29,10 +29,10 @@ from ._writer import PandaHDFWriter
|
|
|
29
29
|
__all__ = [
|
|
30
30
|
"CommonPandaBlocks",
|
|
31
31
|
"DataBlock",
|
|
32
|
-
"
|
|
32
|
+
"BitMux",
|
|
33
33
|
"PcapBlock",
|
|
34
34
|
"PcompBlock",
|
|
35
|
-
"
|
|
35
|
+
"PcompDirection",
|
|
36
36
|
"PulseBlock",
|
|
37
37
|
"SeqBlock",
|
|
38
38
|
"TimeUnits",
|
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
from
|
|
1
|
+
from ophyd_async.core import (
|
|
2
|
+
Device,
|
|
3
|
+
DeviceVector,
|
|
4
|
+
SignalR,
|
|
5
|
+
SignalRW,
|
|
6
|
+
StrictEnum,
|
|
7
|
+
SubsetEnum,
|
|
8
|
+
)
|
|
2
9
|
|
|
3
|
-
from
|
|
10
|
+
from ._table import DatasetTable, SeqTable
|
|
4
11
|
|
|
5
|
-
from ophyd_async.core import Device, DeviceVector, SignalR, SignalRW, SubsetEnum
|
|
6
12
|
|
|
7
|
-
|
|
13
|
+
class CaptureMode(StrictEnum):
|
|
14
|
+
FIRST_N = "FIRST_N"
|
|
15
|
+
LAST_N = "LAST_N"
|
|
16
|
+
FOREVER = "FOREVER"
|
|
8
17
|
|
|
9
18
|
|
|
10
19
|
class DataBlock(Device):
|
|
@@ -15,6 +24,7 @@ class DataBlock(Device):
|
|
|
15
24
|
num_captured: SignalR[int]
|
|
16
25
|
create_directory: SignalRW[int]
|
|
17
26
|
directory_exists: SignalR[bool]
|
|
27
|
+
capture_mode: SignalRW[CaptureMode]
|
|
18
28
|
capture: SignalRW[bool]
|
|
19
29
|
flush_period: SignalRW[float]
|
|
20
30
|
datasets: SignalR[DatasetTable]
|
|
@@ -25,26 +35,28 @@ class PulseBlock(Device):
|
|
|
25
35
|
width: SignalRW[float]
|
|
26
36
|
|
|
27
37
|
|
|
28
|
-
class
|
|
38
|
+
class PcompDirection(StrictEnum):
|
|
29
39
|
positive = "Positive"
|
|
30
40
|
negative = "Negative"
|
|
31
41
|
either = "Either"
|
|
32
42
|
|
|
33
43
|
|
|
34
|
-
|
|
44
|
+
class BitMux(SubsetEnum):
|
|
45
|
+
zero = "ZERO"
|
|
46
|
+
one = "ONE"
|
|
35
47
|
|
|
36
48
|
|
|
37
49
|
class PcompBlock(Device):
|
|
38
50
|
active: SignalR[bool]
|
|
39
|
-
dir: SignalRW[
|
|
40
|
-
enable: SignalRW[
|
|
51
|
+
dir: SignalRW[PcompDirection]
|
|
52
|
+
enable: SignalRW[BitMux]
|
|
41
53
|
pulses: SignalRW[int]
|
|
42
54
|
start: SignalRW[int]
|
|
43
55
|
step: SignalRW[int]
|
|
44
56
|
width: SignalRW[int]
|
|
45
57
|
|
|
46
58
|
|
|
47
|
-
class TimeUnits(
|
|
59
|
+
class TimeUnits(StrictEnum):
|
|
48
60
|
min = "min"
|
|
49
61
|
s = "s"
|
|
50
62
|
ms = "ms"
|
|
@@ -53,11 +65,11 @@ class TimeUnits(str, Enum):
|
|
|
53
65
|
|
|
54
66
|
class SeqBlock(Device):
|
|
55
67
|
table: SignalRW[SeqTable]
|
|
56
|
-
active:
|
|
68
|
+
active: SignalR[bool]
|
|
57
69
|
repeats: SignalRW[int]
|
|
58
70
|
prescale: SignalRW[float]
|
|
59
71
|
prescale_units: SignalRW[TimeUnits]
|
|
60
|
-
enable: SignalRW[
|
|
72
|
+
enable: SignalRW[BitMux]
|
|
61
73
|
|
|
62
74
|
|
|
63
75
|
class PcapBlock(Device):
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
|
|
3
1
|
from ophyd_async.core import (
|
|
2
|
+
AsyncStatus,
|
|
4
3
|
DetectorController,
|
|
5
4
|
DetectorTrigger,
|
|
5
|
+
TriggerInfo,
|
|
6
6
|
wait_for_value,
|
|
7
7
|
)
|
|
8
|
-
from ophyd_async.core._detector import TriggerInfo
|
|
9
|
-
from ophyd_async.core._status import AsyncStatus
|
|
10
8
|
|
|
11
9
|
from ._block import PcapBlock
|
|
12
10
|
|
|
@@ -33,5 +31,5 @@ class PandaPcapController(DetectorController):
|
|
|
33
31
|
pass
|
|
34
32
|
|
|
35
33
|
async def disarm(self):
|
|
36
|
-
await
|
|
34
|
+
await self.pcap.arm.set(False)
|
|
37
35
|
await wait_for_value(self.pcap.active, False, timeout=1)
|
|
@@ -2,8 +2,8 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from collections.abc import Sequence
|
|
4
4
|
|
|
5
|
-
from ophyd_async.core import
|
|
6
|
-
from ophyd_async.
|
|
5
|
+
from ophyd_async.core import PathProvider, SignalR, StandardDetector
|
|
6
|
+
from ophyd_async.fastcs.core import fastcs_connector
|
|
7
7
|
|
|
8
8
|
from ._block import CommonPandaBlocks
|
|
9
9
|
from ._control import PandaPcapController
|
|
@@ -18,12 +18,10 @@ class HDFPanda(CommonPandaBlocks, StandardDetector):
|
|
|
18
18
|
config_sigs: Sequence[SignalR] = (),
|
|
19
19
|
name: str = "",
|
|
20
20
|
):
|
|
21
|
-
self.
|
|
22
|
-
|
|
23
|
-
create_children_from_annotations(self)
|
|
21
|
+
# This has to be first so we make self.pcap
|
|
22
|
+
connector = fastcs_connector(self, prefix)
|
|
24
23
|
controller = PandaPcapController(pcap=self.pcap)
|
|
25
24
|
writer = PandaHDFWriter(
|
|
26
|
-
prefix=prefix,
|
|
27
25
|
path_provider=path_provider,
|
|
28
26
|
name_provider=lambda: name,
|
|
29
27
|
panda_data_block=self.data,
|
|
@@ -33,17 +31,5 @@ class HDFPanda(CommonPandaBlocks, StandardDetector):
|
|
|
33
31
|
writer=writer,
|
|
34
32
|
config_sigs=config_sigs,
|
|
35
33
|
name=name,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
async def connect(
|
|
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
|
|
46
|
-
await fill_pvi_entries(self, self._prefix + "PVI", timeout=timeout, mock=mock)
|
|
47
|
-
await super().connect(
|
|
48
|
-
mock=mock, timeout=timeout, force_reconnect=force_reconnect
|
|
34
|
+
connector=connector,
|
|
49
35
|
)
|
|
@@ -1,27 +1,22 @@
|
|
|
1
1
|
from collections.abc import Sequence
|
|
2
|
-
from enum import Enum
|
|
3
|
-
from typing import Annotated
|
|
4
2
|
|
|
5
3
|
import numpy as np
|
|
6
|
-
|
|
7
|
-
from pydantic import Field, model_validator
|
|
8
|
-
from pydantic_numpy.helper.annotation import NpArrayPydanticAnnotation
|
|
9
|
-
from typing_extensions import TypedDict
|
|
4
|
+
from pydantic import model_validator
|
|
10
5
|
|
|
11
|
-
from ophyd_async.core import Table
|
|
6
|
+
from ophyd_async.core import Array1D, StrictEnum, Table
|
|
12
7
|
|
|
13
8
|
|
|
14
|
-
class PandaHdf5DatasetType(
|
|
9
|
+
class PandaHdf5DatasetType(StrictEnum):
|
|
15
10
|
FLOAT_64 = "float64"
|
|
16
11
|
UINT_32 = "uint32"
|
|
17
12
|
|
|
18
13
|
|
|
19
|
-
class DatasetTable(
|
|
20
|
-
name:
|
|
14
|
+
class DatasetTable(Table):
|
|
15
|
+
name: Sequence[str]
|
|
21
16
|
hdf5_type: Sequence[PandaHdf5DatasetType]
|
|
22
17
|
|
|
23
18
|
|
|
24
|
-
class SeqTrigger(
|
|
19
|
+
class SeqTrigger(StrictEnum):
|
|
25
20
|
IMMEDIATE = "Immediate"
|
|
26
21
|
BITA_0 = "BITA=0"
|
|
27
22
|
BITA_1 = "BITA=1"
|
|
@@ -37,45 +32,27 @@ class SeqTrigger(str, Enum):
|
|
|
37
32
|
POSC_LT = "POSC<=POSITION"
|
|
38
33
|
|
|
39
34
|
|
|
40
|
-
PydanticNp1DArrayInt32 = Annotated[
|
|
41
|
-
np.ndarray[tuple[int], np.dtype[np.int32]],
|
|
42
|
-
NpArrayPydanticAnnotation.factory(
|
|
43
|
-
data_type=np.int32, dimensions=1, strict_data_typing=False
|
|
44
|
-
),
|
|
45
|
-
Field(default_factory=lambda: np.array([], np.int32)),
|
|
46
|
-
]
|
|
47
|
-
PydanticNp1DArrayBool = Annotated[
|
|
48
|
-
np.ndarray[tuple[int], np.dtype[np.bool_]],
|
|
49
|
-
NpArrayPydanticAnnotation.factory(
|
|
50
|
-
data_type=np.bool_, dimensions=1, strict_data_typing=False
|
|
51
|
-
),
|
|
52
|
-
Field(default_factory=lambda: np.array([], dtype=np.bool_)),
|
|
53
|
-
]
|
|
54
|
-
TriggerStr = Annotated[Sequence[SeqTrigger], Field(default_factory=list)]
|
|
55
|
-
|
|
56
|
-
|
|
57
35
|
class SeqTable(Table):
|
|
58
|
-
repeats:
|
|
59
|
-
trigger:
|
|
60
|
-
position:
|
|
61
|
-
time1:
|
|
62
|
-
outa1:
|
|
63
|
-
outb1:
|
|
64
|
-
outc1:
|
|
65
|
-
outd1:
|
|
66
|
-
oute1:
|
|
67
|
-
outf1:
|
|
68
|
-
time2:
|
|
69
|
-
outa2:
|
|
70
|
-
outb2:
|
|
71
|
-
outc2:
|
|
72
|
-
outd2:
|
|
73
|
-
oute2:
|
|
74
|
-
outf2:
|
|
36
|
+
repeats: Array1D[np.uint16]
|
|
37
|
+
trigger: Sequence[SeqTrigger]
|
|
38
|
+
position: Array1D[np.int32]
|
|
39
|
+
time1: Array1D[np.uint32]
|
|
40
|
+
outa1: Array1D[np.bool_]
|
|
41
|
+
outb1: Array1D[np.bool_]
|
|
42
|
+
outc1: Array1D[np.bool_]
|
|
43
|
+
outd1: Array1D[np.bool_]
|
|
44
|
+
oute1: Array1D[np.bool_]
|
|
45
|
+
outf1: Array1D[np.bool_]
|
|
46
|
+
time2: Array1D[np.uint32]
|
|
47
|
+
outa2: Array1D[np.bool_]
|
|
48
|
+
outb2: Array1D[np.bool_]
|
|
49
|
+
outc2: Array1D[np.bool_]
|
|
50
|
+
outd2: Array1D[np.bool_]
|
|
51
|
+
oute2: Array1D[np.bool_]
|
|
52
|
+
outf2: Array1D[np.bool_]
|
|
75
53
|
|
|
76
|
-
@
|
|
77
|
-
def row(
|
|
78
|
-
cls,
|
|
54
|
+
@staticmethod
|
|
55
|
+
def row(
|
|
79
56
|
*,
|
|
80
57
|
repeats: int = 1,
|
|
81
58
|
trigger: str = SeqTrigger.IMMEDIATE,
|
|
@@ -95,7 +72,8 @@ class SeqTable(Table):
|
|
|
95
72
|
oute2: bool = False,
|
|
96
73
|
outf2: bool = False,
|
|
97
74
|
) -> "SeqTable":
|
|
98
|
-
|
|
75
|
+
# Let pydantic do the conversions for us
|
|
76
|
+
return SeqTable(**{k: [v] for k, v in locals().items()}) # type: ignore
|
|
99
77
|
|
|
100
78
|
@model_validator(mode="after")
|
|
101
79
|
def validate_max_length(self) -> "SeqTable":
|
|
@@ -104,6 +82,6 @@ class SeqTable(Table):
|
|
|
104
82
|
the pydantic field doesn't work
|
|
105
83
|
"""
|
|
106
84
|
|
|
107
|
-
first_length = len(
|
|
108
|
-
assert
|
|
85
|
+
first_length = len(self)
|
|
86
|
+
assert first_length <= 4096, f"Length {first_length} is too long"
|
|
109
87
|
return self
|
|
@@ -4,7 +4,7 @@ from pydantic import BaseModel, Field
|
|
|
4
4
|
|
|
5
5
|
from ophyd_async.core import FlyerController, wait_for_value
|
|
6
6
|
|
|
7
|
-
from ._block import PcompBlock,
|
|
7
|
+
from ._block import BitMux, PcompBlock, PcompDirection, SeqBlock, TimeUnits
|
|
8
8
|
from ._table import SeqTable
|
|
9
9
|
|
|
10
10
|
|
|
@@ -21,7 +21,7 @@ class StaticSeqTableTriggerLogic(FlyerController[SeqTableInfo]):
|
|
|
21
21
|
async def prepare(self, value: SeqTableInfo):
|
|
22
22
|
await asyncio.gather(
|
|
23
23
|
self.seq.prescale_units.set(TimeUnits.us),
|
|
24
|
-
self.seq.enable.set(
|
|
24
|
+
self.seq.enable.set(BitMux.zero),
|
|
25
25
|
)
|
|
26
26
|
await asyncio.gather(
|
|
27
27
|
self.seq.prescale.set(value.prescale_as_us),
|
|
@@ -30,14 +30,14 @@ class StaticSeqTableTriggerLogic(FlyerController[SeqTableInfo]):
|
|
|
30
30
|
)
|
|
31
31
|
|
|
32
32
|
async def kickoff(self) -> None:
|
|
33
|
-
await self.seq.enable.set(
|
|
33
|
+
await self.seq.enable.set(BitMux.one)
|
|
34
34
|
await wait_for_value(self.seq.active, True, timeout=1)
|
|
35
35
|
|
|
36
36
|
async def complete(self) -> None:
|
|
37
37
|
await wait_for_value(self.seq.active, False, timeout=None)
|
|
38
38
|
|
|
39
39
|
async def stop(self):
|
|
40
|
-
await self.seq.enable.set(
|
|
40
|
+
await self.seq.enable.set(BitMux.zero)
|
|
41
41
|
await wait_for_value(self.seq.active, False, timeout=1)
|
|
42
42
|
|
|
43
43
|
|
|
@@ -54,7 +54,7 @@ class PcompInfo(BaseModel):
|
|
|
54
54
|
),
|
|
55
55
|
ge=0,
|
|
56
56
|
)
|
|
57
|
-
direction:
|
|
57
|
+
direction: PcompDirection = Field(
|
|
58
58
|
description=(
|
|
59
59
|
"Specifies which direction the motor counts should be "
|
|
60
60
|
"moving. Pulses won't be sent unless the values are moving in "
|
|
@@ -68,7 +68,7 @@ class StaticPcompTriggerLogic(FlyerController[PcompInfo]):
|
|
|
68
68
|
self.pcomp = pcomp
|
|
69
69
|
|
|
70
70
|
async def prepare(self, value: PcompInfo):
|
|
71
|
-
await self.pcomp.enable.set(
|
|
71
|
+
await self.pcomp.enable.set(BitMux.zero)
|
|
72
72
|
await asyncio.gather(
|
|
73
73
|
self.pcomp.start.set(value.start_postion),
|
|
74
74
|
self.pcomp.width.set(value.pulse_width),
|
|
@@ -78,12 +78,12 @@ class StaticPcompTriggerLogic(FlyerController[PcompInfo]):
|
|
|
78
78
|
)
|
|
79
79
|
|
|
80
80
|
async def kickoff(self) -> None:
|
|
81
|
-
await self.pcomp.enable.set(
|
|
81
|
+
await self.pcomp.enable.set(BitMux.one)
|
|
82
82
|
await wait_for_value(self.pcomp.active, True, timeout=1)
|
|
83
83
|
|
|
84
84
|
async def complete(self, timeout: float | None = None) -> None:
|
|
85
85
|
await wait_for_value(self.pcomp.active, False, timeout=timeout)
|
|
86
86
|
|
|
87
87
|
async def stop(self):
|
|
88
|
-
await self.pcomp.enable.set(
|
|
88
|
+
await self.pcomp.enable.set(BitMux.zero)
|
|
89
89
|
await wait_for_value(self.pcomp.active, False, timeout=1)
|
|
@@ -17,7 +17,7 @@ from ophyd_async.core import (
|
|
|
17
17
|
wait_for_value,
|
|
18
18
|
)
|
|
19
19
|
|
|
20
|
-
from ._block import DataBlock
|
|
20
|
+
from ._block import CaptureMode, DataBlock
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class PandaHDFWriter(DetectorWriter):
|
|
@@ -25,13 +25,11 @@ class PandaHDFWriter(DetectorWriter):
|
|
|
25
25
|
|
|
26
26
|
def __init__(
|
|
27
27
|
self,
|
|
28
|
-
prefix: str,
|
|
29
28
|
path_provider: PathProvider,
|
|
30
29
|
name_provider: NameProvider,
|
|
31
30
|
panda_data_block: DataBlock,
|
|
32
31
|
) -> None:
|
|
33
32
|
self.panda_data_block = panda_data_block
|
|
34
|
-
self._prefix = prefix
|
|
35
33
|
self._path_provider = path_provider
|
|
36
34
|
self._name_provider = name_provider
|
|
37
35
|
self._datasets: list[HDFDataset] = []
|
|
@@ -58,7 +56,7 @@ class PandaHDFWriter(DetectorWriter):
|
|
|
58
56
|
self.panda_data_block.hdf_file_name.set(
|
|
59
57
|
f"{info.filename}.h5",
|
|
60
58
|
),
|
|
61
|
-
self.panda_data_block.
|
|
59
|
+
self.panda_data_block.capture_mode.set(CaptureMode.FOREVER),
|
|
62
60
|
)
|
|
63
61
|
|
|
64
62
|
# Make sure that directory exists or has been created.
|
|
@@ -89,8 +87,7 @@ class PandaHDFWriter(DetectorWriter):
|
|
|
89
87
|
shape=list(ds.shape),
|
|
90
88
|
dtype="array" if ds.shape != [1] else "number",
|
|
91
89
|
# PandA data should always be written as Float64
|
|
92
|
-
|
|
93
|
-
dtype_numpy="<f8", # type: ignore
|
|
90
|
+
dtype_numpy="<f8",
|
|
94
91
|
external="STREAM:",
|
|
95
92
|
)
|
|
96
93
|
for ds in self._datasets
|
|
@@ -110,7 +107,7 @@ class PandaHDFWriter(DetectorWriter):
|
|
|
110
107
|
HDFDataset(
|
|
111
108
|
dataset_name, "/" + dataset_name, [1], multiplier=1, chunk_shape=(1024,)
|
|
112
109
|
)
|
|
113
|
-
for dataset_name in capture_table
|
|
110
|
+
for dataset_name in capture_table.name
|
|
114
111
|
]
|
|
115
112
|
|
|
116
113
|
# Warn user if dataset table is empty in PandA
|