ophyd-async 0.9.0a2__py3-none-any.whl → 0.10.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/__init__.py +5 -8
- ophyd_async/_docs_parser.py +12 -0
- ophyd_async/_version.py +9 -4
- ophyd_async/core/__init__.py +97 -62
- ophyd_async/core/_derived_signal.py +271 -0
- ophyd_async/core/_derived_signal_backend.py +300 -0
- ophyd_async/core/_detector.py +106 -125
- ophyd_async/core/_device.py +69 -63
- ophyd_async/core/_device_filler.py +65 -1
- ophyd_async/core/_flyer.py +14 -5
- ophyd_async/core/_hdf_dataset.py +29 -22
- ophyd_async/core/_log.py +14 -23
- ophyd_async/core/_mock_signal_backend.py +11 -3
- ophyd_async/core/_protocol.py +65 -45
- ophyd_async/core/_providers.py +28 -9
- ophyd_async/core/_readable.py +44 -35
- ophyd_async/core/_settings.py +36 -27
- ophyd_async/core/_signal.py +262 -170
- ophyd_async/core/_signal_backend.py +56 -13
- ophyd_async/core/_soft_signal_backend.py +16 -11
- ophyd_async/core/_status.py +72 -24
- ophyd_async/core/_table.py +41 -11
- ophyd_async/core/_utils.py +96 -49
- ophyd_async/core/_yaml_settings.py +2 -0
- ophyd_async/epics/__init__.py +1 -0
- ophyd_async/epics/adandor/_andor.py +2 -2
- ophyd_async/epics/adandor/_andor_controller.py +4 -2
- ophyd_async/epics/adandor/_andor_io.py +2 -4
- ophyd_async/epics/adaravis/__init__.py +5 -0
- ophyd_async/epics/adaravis/_aravis.py +4 -8
- ophyd_async/epics/adaravis/_aravis_controller.py +20 -43
- ophyd_async/epics/adaravis/_aravis_io.py +13 -28
- ophyd_async/epics/adcore/__init__.py +23 -8
- ophyd_async/epics/adcore/_core_detector.py +42 -2
- ophyd_async/epics/adcore/_core_io.py +124 -99
- ophyd_async/epics/adcore/_core_logic.py +106 -27
- ophyd_async/epics/adcore/_core_writer.py +12 -8
- ophyd_async/epics/adcore/_hdf_writer.py +21 -38
- ophyd_async/epics/adcore/_single_trigger.py +2 -2
- ophyd_async/epics/adcore/_utils.py +2 -2
- ophyd_async/epics/adkinetix/__init__.py +2 -1
- ophyd_async/epics/adkinetix/_kinetix.py +3 -3
- ophyd_async/epics/adkinetix/_kinetix_controller.py +4 -2
- ophyd_async/epics/adkinetix/_kinetix_io.py +12 -13
- ophyd_async/epics/adpilatus/__init__.py +5 -0
- ophyd_async/epics/adpilatus/_pilatus.py +1 -1
- ophyd_async/epics/adpilatus/_pilatus_controller.py +5 -24
- ophyd_async/epics/adpilatus/_pilatus_io.py +11 -9
- ophyd_async/epics/adsimdetector/__init__.py +8 -1
- ophyd_async/epics/adsimdetector/_sim.py +4 -14
- ophyd_async/epics/adsimdetector/_sim_controller.py +17 -0
- ophyd_async/epics/adsimdetector/_sim_io.py +10 -0
- ophyd_async/epics/advimba/__init__.py +10 -1
- ophyd_async/epics/advimba/_vimba.py +3 -2
- ophyd_async/epics/advimba/_vimba_controller.py +4 -2
- ophyd_async/epics/advimba/_vimba_io.py +23 -28
- ophyd_async/epics/core/_aioca.py +35 -16
- ophyd_async/epics/core/_epics_connector.py +4 -0
- ophyd_async/epics/core/_epics_device.py +2 -0
- ophyd_async/epics/core/_p4p.py +10 -2
- ophyd_async/epics/core/_pvi_connector.py +65 -8
- ophyd_async/epics/core/_signal.py +51 -51
- ophyd_async/epics/core/_util.py +4 -4
- ophyd_async/epics/demo/__init__.py +16 -0
- ophyd_async/epics/demo/__main__.py +31 -0
- ophyd_async/epics/demo/_ioc.py +32 -0
- ophyd_async/epics/demo/_motor.py +82 -0
- ophyd_async/epics/demo/_point_detector.py +42 -0
- ophyd_async/epics/demo/_point_detector_channel.py +22 -0
- ophyd_async/epics/demo/_stage.py +15 -0
- ophyd_async/epics/{sim/mover.db → demo/motor.db} +2 -1
- ophyd_async/epics/demo/point_detector.db +59 -0
- ophyd_async/epics/demo/point_detector_channel.db +21 -0
- ophyd_async/epics/eiger/_eiger.py +1 -3
- ophyd_async/epics/eiger/_eiger_controller.py +11 -4
- ophyd_async/epics/eiger/_eiger_io.py +2 -0
- ophyd_async/epics/eiger/_odin_io.py +1 -2
- ophyd_async/epics/motor.py +65 -28
- ophyd_async/epics/signal.py +4 -1
- ophyd_async/epics/testing/_example_ioc.py +21 -9
- ophyd_async/epics/testing/_utils.py +3 -0
- ophyd_async/epics/testing/test_records.db +8 -0
- ophyd_async/epics/testing/test_records_pva.db +17 -16
- ophyd_async/fastcs/__init__.py +1 -0
- ophyd_async/fastcs/core.py +6 -0
- ophyd_async/fastcs/odin/__init__.py +1 -0
- ophyd_async/fastcs/panda/__init__.py +8 -6
- ophyd_async/fastcs/panda/_block.py +29 -9
- ophyd_async/fastcs/panda/_control.py +5 -0
- ophyd_async/fastcs/panda/_hdf_panda.py +2 -0
- ophyd_async/fastcs/panda/_table.py +9 -6
- ophyd_async/fastcs/panda/_trigger.py +23 -9
- ophyd_async/fastcs/panda/_writer.py +27 -30
- ophyd_async/plan_stubs/__init__.py +2 -0
- ophyd_async/plan_stubs/_ensure_connected.py +1 -0
- ophyd_async/plan_stubs/_fly.py +2 -4
- ophyd_async/plan_stubs/_nd_attributes.py +2 -0
- ophyd_async/plan_stubs/_panda.py +1 -0
- ophyd_async/plan_stubs/_settings.py +43 -16
- ophyd_async/plan_stubs/_utils.py +3 -0
- ophyd_async/plan_stubs/_wait_for_awaitable.py +1 -1
- ophyd_async/sim/__init__.py +24 -14
- ophyd_async/sim/__main__.py +43 -0
- ophyd_async/sim/_blob_detector.py +33 -0
- ophyd_async/sim/_blob_detector_controller.py +48 -0
- ophyd_async/sim/_blob_detector_writer.py +105 -0
- ophyd_async/sim/_mirror_horizontal.py +46 -0
- ophyd_async/sim/_mirror_vertical.py +74 -0
- ophyd_async/sim/_motor.py +233 -0
- ophyd_async/sim/_pattern_generator.py +124 -0
- ophyd_async/sim/_point_detector.py +86 -0
- ophyd_async/sim/_stage.py +19 -0
- ophyd_async/tango/__init__.py +1 -0
- ophyd_async/tango/core/__init__.py +6 -1
- ophyd_async/tango/core/_base_device.py +41 -33
- ophyd_async/tango/core/_converters.py +81 -0
- ophyd_async/tango/core/_signal.py +18 -32
- ophyd_async/tango/core/_tango_readable.py +2 -19
- ophyd_async/tango/core/_tango_transport.py +136 -60
- ophyd_async/tango/core/_utils.py +47 -0
- ophyd_async/tango/{sim → demo}/_counter.py +2 -0
- ophyd_async/tango/{sim → demo}/_detector.py +2 -0
- ophyd_async/tango/{sim → demo}/_mover.py +5 -4
- ophyd_async/tango/{sim → demo}/_tango/_servers.py +4 -0
- ophyd_async/tango/testing/__init__.py +6 -0
- ophyd_async/tango/testing/_one_of_everything.py +200 -0
- ophyd_async/testing/__init__.py +29 -7
- ophyd_async/testing/_assert.py +145 -83
- ophyd_async/testing/_mock_signal_utils.py +56 -70
- ophyd_async/testing/_one_of_everything.py +41 -21
- ophyd_async/testing/_single_derived.py +89 -0
- ophyd_async/testing/_utils.py +3 -0
- {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a2.dist-info}/METADATA +25 -26
- ophyd_async-0.10.0a2.dist-info/RECORD +149 -0
- {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a2.dist-info}/WHEEL +1 -1
- ophyd_async/epics/sim/__init__.py +0 -54
- ophyd_async/epics/sim/_ioc.py +0 -29
- ophyd_async/epics/sim/_mover.py +0 -101
- ophyd_async/epics/sim/_sensor.py +0 -37
- ophyd_async/epics/sim/sensor.db +0 -19
- ophyd_async/sim/_pattern_detector/__init__.py +0 -13
- ophyd_async/sim/_pattern_detector/_pattern_detector.py +0 -42
- ophyd_async/sim/_pattern_detector/_pattern_detector_controller.py +0 -69
- ophyd_async/sim/_pattern_detector/_pattern_detector_writer.py +0 -41
- ophyd_async/sim/_pattern_detector/_pattern_generator.py +0 -214
- ophyd_async/sim/_sim_motor.py +0 -107
- ophyd_async-0.9.0a2.dist-info/RECORD +0 -129
- /ophyd_async/tango/{sim → demo}/__init__.py +0 -0
- /ophyd_async/tango/{sim → demo}/_tango/__init__.py +0 -0
- {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a2.dist-info/licenses}/LICENSE +0 -0
- {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a2.dist-info}/top_level.txt +0 -0
|
@@ -10,13 +10,17 @@ from ophyd_async.core import (
|
|
|
10
10
|
from ._table import DatasetTable, SeqTable
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
class
|
|
13
|
+
class PandaCaptureMode(StrictEnum):
|
|
14
|
+
"""Capture mode for the `DataBlock` on the PandA."""
|
|
15
|
+
|
|
14
16
|
FIRST_N = "FIRST_N"
|
|
15
17
|
LAST_N = "LAST_N"
|
|
16
18
|
FOREVER = "FOREVER"
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
class DataBlock(Device):
|
|
22
|
+
"""Data block for the PandA. Used for writing data through the IOC."""
|
|
23
|
+
|
|
20
24
|
# In future we may decide to make hdf_* optional
|
|
21
25
|
hdf_directory: SignalRW[str]
|
|
22
26
|
hdf_file_name: SignalRW[str]
|
|
@@ -24,39 +28,49 @@ class DataBlock(Device):
|
|
|
24
28
|
num_captured: SignalR[int]
|
|
25
29
|
create_directory: SignalRW[int]
|
|
26
30
|
directory_exists: SignalR[bool]
|
|
27
|
-
capture_mode: SignalRW[
|
|
31
|
+
capture_mode: SignalRW[PandaCaptureMode]
|
|
28
32
|
capture: SignalRW[bool]
|
|
29
33
|
flush_period: SignalRW[float]
|
|
30
34
|
datasets: SignalR[DatasetTable]
|
|
31
35
|
|
|
32
36
|
|
|
33
37
|
class PulseBlock(Device):
|
|
38
|
+
"""Used for configuring pulses in the PandA."""
|
|
39
|
+
|
|
34
40
|
delay: SignalRW[float]
|
|
35
41
|
width: SignalRW[float]
|
|
36
42
|
|
|
37
43
|
|
|
38
|
-
class
|
|
44
|
+
class PandaPcompDirection(StrictEnum):
|
|
45
|
+
"""Direction options for position compare in the PandA."""
|
|
46
|
+
|
|
39
47
|
POSITIVE = "Positive"
|
|
40
48
|
NEGATIVE = "Negative"
|
|
41
49
|
EITHER = "Either"
|
|
42
50
|
|
|
43
51
|
|
|
44
|
-
class
|
|
52
|
+
class PandaBitMux(SubsetEnum):
|
|
53
|
+
"""Bit input with configurable delay in the PandA."""
|
|
54
|
+
|
|
45
55
|
ZERO = "ZERO"
|
|
46
56
|
ONE = "ONE"
|
|
47
57
|
|
|
48
58
|
|
|
49
59
|
class PcompBlock(Device):
|
|
60
|
+
"""Position compare block in the PandA."""
|
|
61
|
+
|
|
50
62
|
active: SignalR[bool]
|
|
51
|
-
dir: SignalRW[
|
|
52
|
-
enable: SignalRW[
|
|
63
|
+
dir: SignalRW[PandaPcompDirection]
|
|
64
|
+
enable: SignalRW[PandaBitMux]
|
|
53
65
|
pulses: SignalRW[int]
|
|
54
66
|
start: SignalRW[int]
|
|
55
67
|
step: SignalRW[int]
|
|
56
68
|
width: SignalRW[int]
|
|
57
69
|
|
|
58
70
|
|
|
59
|
-
class
|
|
71
|
+
class PandaTimeUnits(StrictEnum):
|
|
72
|
+
"""Options for units of time in the PandA."""
|
|
73
|
+
|
|
60
74
|
MIN = "min"
|
|
61
75
|
S = "s"
|
|
62
76
|
MS = "ms"
|
|
@@ -64,20 +78,26 @@ class TimeUnits(StrictEnum):
|
|
|
64
78
|
|
|
65
79
|
|
|
66
80
|
class SeqBlock(Device):
|
|
81
|
+
"""Sequencer block in the PandA."""
|
|
82
|
+
|
|
67
83
|
table: SignalRW[SeqTable]
|
|
68
84
|
active: SignalR[bool]
|
|
69
85
|
repeats: SignalRW[int]
|
|
70
86
|
prescale: SignalRW[float]
|
|
71
|
-
prescale_units: SignalRW[
|
|
72
|
-
enable: SignalRW[
|
|
87
|
+
prescale_units: SignalRW[PandaTimeUnits]
|
|
88
|
+
enable: SignalRW[PandaBitMux]
|
|
73
89
|
|
|
74
90
|
|
|
75
91
|
class PcapBlock(Device):
|
|
92
|
+
"""Position capture block in the PandA."""
|
|
93
|
+
|
|
76
94
|
active: SignalR[bool]
|
|
77
95
|
arm: SignalRW[bool]
|
|
78
96
|
|
|
79
97
|
|
|
80
98
|
class CommonPandaBlocks(Device):
|
|
99
|
+
"""Pandablocks device with blocks which are common and required on introspection."""
|
|
100
|
+
|
|
81
101
|
pulse: DeviceVector[PulseBlock]
|
|
82
102
|
seq: DeviceVector[SeqBlock]
|
|
83
103
|
pcomp: DeviceVector[PcompBlock]
|
|
@@ -10,6 +10,8 @@ from ._block import PcapBlock
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class PandaPcapController(DetectorController):
|
|
13
|
+
"""For controlling a PCAP capture on the PandA."""
|
|
14
|
+
|
|
13
15
|
def __init__(self, pcap: PcapBlock) -> None:
|
|
14
16
|
self.pcap = pcap
|
|
15
17
|
self._arm_status: AsyncStatus | None = None
|
|
@@ -38,3 +40,6 @@ class PandaPcapController(DetectorController):
|
|
|
38
40
|
async def disarm(self):
|
|
39
41
|
await self.pcap.arm.set(False)
|
|
40
42
|
await wait_for_value(self.pcap.active, False, timeout=1)
|
|
43
|
+
if self._arm_status and not self._arm_status.done:
|
|
44
|
+
await self._arm_status
|
|
45
|
+
self._arm_status = None
|
|
@@ -7,6 +7,8 @@ from ophyd_async.core import Array1D, StrictEnum, Table
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class PandaHdf5DatasetType(StrictEnum):
|
|
10
|
+
"""Dataset options for HDF capture."""
|
|
11
|
+
|
|
10
12
|
FLOAT_64 = "float64"
|
|
11
13
|
UINT_32 = "uint32"
|
|
12
14
|
|
|
@@ -17,6 +19,8 @@ class DatasetTable(Table):
|
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
class SeqTrigger(StrictEnum):
|
|
22
|
+
"""Trigger options for the SeqTable."""
|
|
23
|
+
|
|
20
24
|
IMMEDIATE = "Immediate"
|
|
21
25
|
BITA_0 = "BITA=0"
|
|
22
26
|
BITA_1 = "BITA=1"
|
|
@@ -33,6 +37,8 @@ class SeqTrigger(StrictEnum):
|
|
|
33
37
|
|
|
34
38
|
|
|
35
39
|
class SeqTable(Table):
|
|
40
|
+
"""Data type for the panda seq table."""
|
|
41
|
+
|
|
36
42
|
repeats: Array1D[np.uint16]
|
|
37
43
|
trigger: Sequence[SeqTrigger]
|
|
38
44
|
position: Array1D[np.int32]
|
|
@@ -76,12 +82,9 @@ class SeqTable(Table):
|
|
|
76
82
|
return SeqTable(**{k: [v] for k, v in locals().items()}) # type: ignore
|
|
77
83
|
|
|
78
84
|
@model_validator(mode="after")
|
|
79
|
-
def
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
the pydantic field doesn't work
|
|
83
|
-
"""
|
|
84
|
-
|
|
85
|
+
def _validate_max_length(self) -> "SeqTable":
|
|
86
|
+
# Used to check max_length. Unfortunately trying the ``max_length`` arg in
|
|
87
|
+
# the pydantic field doesn't work.
|
|
85
88
|
first_length = len(self)
|
|
86
89
|
max_length = 4096
|
|
87
90
|
if first_length > max_length:
|
|
@@ -4,24 +4,34 @@ from pydantic import BaseModel, Field
|
|
|
4
4
|
|
|
5
5
|
from ophyd_async.core import FlyerController, wait_for_value
|
|
6
6
|
|
|
7
|
-
from ._block import
|
|
7
|
+
from ._block import (
|
|
8
|
+
PandaBitMux,
|
|
9
|
+
PandaPcompDirection,
|
|
10
|
+
PandaTimeUnits,
|
|
11
|
+
PcompBlock,
|
|
12
|
+
SeqBlock,
|
|
13
|
+
)
|
|
8
14
|
from ._table import SeqTable
|
|
9
15
|
|
|
10
16
|
|
|
11
17
|
class SeqTableInfo(BaseModel):
|
|
18
|
+
"""Info for the PandA `SeqTable` for flyscanning."""
|
|
19
|
+
|
|
12
20
|
sequence_table: SeqTable = Field(strict=True)
|
|
13
21
|
repeats: int = Field(ge=0)
|
|
14
22
|
prescale_as_us: float = Field(default=1, ge=0) # microseconds
|
|
15
23
|
|
|
16
24
|
|
|
17
25
|
class StaticSeqTableTriggerLogic(FlyerController[SeqTableInfo]):
|
|
26
|
+
"""For controlling the PandA `SeqTable` when flyscanning."""
|
|
27
|
+
|
|
18
28
|
def __init__(self, seq: SeqBlock) -> None:
|
|
19
29
|
self.seq = seq
|
|
20
30
|
|
|
21
31
|
async def prepare(self, value: SeqTableInfo):
|
|
22
32
|
await asyncio.gather(
|
|
23
|
-
self.seq.prescale_units.set(
|
|
24
|
-
self.seq.enable.set(
|
|
33
|
+
self.seq.prescale_units.set(PandaTimeUnits.US),
|
|
34
|
+
self.seq.enable.set(PandaBitMux.ZERO),
|
|
25
35
|
)
|
|
26
36
|
await asyncio.gather(
|
|
27
37
|
self.seq.prescale.set(value.prescale_as_us),
|
|
@@ -30,18 +40,20 @@ class StaticSeqTableTriggerLogic(FlyerController[SeqTableInfo]):
|
|
|
30
40
|
)
|
|
31
41
|
|
|
32
42
|
async def kickoff(self) -> None:
|
|
33
|
-
await self.seq.enable.set(
|
|
43
|
+
await self.seq.enable.set(PandaBitMux.ONE)
|
|
34
44
|
await wait_for_value(self.seq.active, True, timeout=1)
|
|
35
45
|
|
|
36
46
|
async def complete(self) -> None:
|
|
37
47
|
await wait_for_value(self.seq.active, False, timeout=None)
|
|
38
48
|
|
|
39
49
|
async def stop(self):
|
|
40
|
-
await self.seq.enable.set(
|
|
50
|
+
await self.seq.enable.set(PandaBitMux.ZERO)
|
|
41
51
|
await wait_for_value(self.seq.active, False, timeout=1)
|
|
42
52
|
|
|
43
53
|
|
|
44
54
|
class PcompInfo(BaseModel):
|
|
55
|
+
"""Info for the PandA `PcompBlock` for flyscanning."""
|
|
56
|
+
|
|
45
57
|
start_postion: int = Field(description="start position in counts")
|
|
46
58
|
pulse_width: int = Field(description="width of a single pulse in counts", gt=0)
|
|
47
59
|
rising_edge_step: int = Field(
|
|
@@ -54,7 +66,7 @@ class PcompInfo(BaseModel):
|
|
|
54
66
|
),
|
|
55
67
|
ge=0,
|
|
56
68
|
)
|
|
57
|
-
direction:
|
|
69
|
+
direction: PandaPcompDirection = Field(
|
|
58
70
|
description=(
|
|
59
71
|
"Specifies which direction the motor counts should be "
|
|
60
72
|
"moving. Pulses won't be sent unless the values are moving in "
|
|
@@ -64,11 +76,13 @@ class PcompInfo(BaseModel):
|
|
|
64
76
|
|
|
65
77
|
|
|
66
78
|
class StaticPcompTriggerLogic(FlyerController[PcompInfo]):
|
|
79
|
+
"""For controlling the PandA `PcompBlock` when flyscanning."""
|
|
80
|
+
|
|
67
81
|
def __init__(self, pcomp: PcompBlock) -> None:
|
|
68
82
|
self.pcomp = pcomp
|
|
69
83
|
|
|
70
84
|
async def prepare(self, value: PcompInfo):
|
|
71
|
-
await self.pcomp.enable.set(
|
|
85
|
+
await self.pcomp.enable.set(PandaBitMux.ZERO)
|
|
72
86
|
await asyncio.gather(
|
|
73
87
|
self.pcomp.start.set(value.start_postion),
|
|
74
88
|
self.pcomp.width.set(value.pulse_width),
|
|
@@ -78,12 +92,12 @@ class StaticPcompTriggerLogic(FlyerController[PcompInfo]):
|
|
|
78
92
|
)
|
|
79
93
|
|
|
80
94
|
async def kickoff(self) -> None:
|
|
81
|
-
await self.pcomp.enable.set(
|
|
95
|
+
await self.pcomp.enable.set(PandaBitMux.ONE)
|
|
82
96
|
await wait_for_value(self.pcomp.active, True, timeout=1)
|
|
83
97
|
|
|
84
98
|
async def complete(self, timeout: float | None = None) -> None:
|
|
85
99
|
await wait_for_value(self.pcomp.active, False, timeout=timeout)
|
|
86
100
|
|
|
87
101
|
async def stop(self):
|
|
88
|
-
await self.pcomp.enable.set(
|
|
102
|
+
await self.pcomp.enable.set(PandaBitMux.ZERO)
|
|
89
103
|
await wait_for_value(self.pcomp.active, False, timeout=1)
|
|
@@ -4,24 +4,23 @@ from pathlib import Path
|
|
|
4
4
|
|
|
5
5
|
from bluesky.protocols import StreamAsset
|
|
6
6
|
from event_model import DataKey
|
|
7
|
-
from p4p.client.thread import Context
|
|
8
7
|
|
|
9
8
|
from ophyd_async.core import (
|
|
10
9
|
DEFAULT_TIMEOUT,
|
|
11
10
|
DetectorWriter,
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
HDFDatasetDescription,
|
|
12
|
+
HDFDocumentComposer,
|
|
14
13
|
NameProvider,
|
|
15
14
|
PathProvider,
|
|
16
15
|
observe_value,
|
|
17
16
|
wait_for_value,
|
|
18
17
|
)
|
|
19
18
|
|
|
20
|
-
from ._block import
|
|
19
|
+
from ._block import DataBlock, PandaCaptureMode
|
|
21
20
|
|
|
22
21
|
|
|
23
22
|
class PandaHDFWriter(DetectorWriter):
|
|
24
|
-
|
|
23
|
+
"""For writing for PandA data from the `DataBlock`."""
|
|
25
24
|
|
|
26
25
|
def __init__(
|
|
27
26
|
self,
|
|
@@ -32,18 +31,17 @@ class PandaHDFWriter(DetectorWriter):
|
|
|
32
31
|
self.panda_data_block = panda_data_block
|
|
33
32
|
self._path_provider = path_provider
|
|
34
33
|
self._name_provider = name_provider
|
|
35
|
-
self._datasets: list[
|
|
36
|
-
self.
|
|
34
|
+
self._datasets: list[HDFDatasetDescription] = []
|
|
35
|
+
self._composer: HDFDocumentComposer | None = None
|
|
37
36
|
self._multiplier = 1
|
|
38
37
|
|
|
39
38
|
# Triggered on PCAP arm
|
|
40
39
|
async def open(self, multiplier: int = 1) -> dict[str, DataKey]:
|
|
41
|
-
"""Retrieve and get descriptor of all PandA signals marked for capture"""
|
|
42
|
-
|
|
40
|
+
"""Retrieve and get descriptor of all PandA signals marked for capture."""
|
|
43
41
|
# Ensure flushes are immediate
|
|
44
42
|
await self.panda_data_block.flush_period.set(0)
|
|
45
43
|
|
|
46
|
-
self.
|
|
44
|
+
self._composer = None
|
|
47
45
|
info = self._path_provider(device_name=self._name_provider())
|
|
48
46
|
|
|
49
47
|
# Set create dir depth first to guarantee that callback when setting
|
|
@@ -56,7 +54,7 @@ class PandaHDFWriter(DetectorWriter):
|
|
|
56
54
|
self.panda_data_block.hdf_file_name.set(
|
|
57
55
|
f"{info.filename}.h5",
|
|
58
56
|
),
|
|
59
|
-
self.panda_data_block.capture_mode.set(
|
|
57
|
+
self.panda_data_block.capture_mode.set(PandaCaptureMode.FOREVER),
|
|
60
58
|
)
|
|
61
59
|
|
|
62
60
|
# Make sure that directory exists or has been created.
|
|
@@ -76,18 +74,15 @@ class PandaHDFWriter(DetectorWriter):
|
|
|
76
74
|
return await self._describe()
|
|
77
75
|
|
|
78
76
|
async def _describe(self) -> dict[str, DataKey]:
|
|
79
|
-
"""
|
|
80
|
-
Return a describe based on the datasets PV
|
|
81
|
-
"""
|
|
82
|
-
|
|
77
|
+
"""Return a describe based on the datasets PV."""
|
|
83
78
|
await self._update_datasets()
|
|
84
79
|
describe = {
|
|
85
80
|
ds.data_key: DataKey(
|
|
86
81
|
source=self.panda_data_block.hdf_directory.source,
|
|
87
82
|
shape=list(ds.shape),
|
|
88
|
-
dtype="
|
|
83
|
+
dtype="number",
|
|
89
84
|
# PandA data should always be written as Float64
|
|
90
|
-
dtype_numpy=
|
|
85
|
+
dtype_numpy=ds.dtype_numpy,
|
|
91
86
|
external="STREAM:",
|
|
92
87
|
)
|
|
93
88
|
for ds in self._datasets
|
|
@@ -95,17 +90,19 @@ class PandaHDFWriter(DetectorWriter):
|
|
|
95
90
|
return describe
|
|
96
91
|
|
|
97
92
|
async def _update_datasets(self) -> None:
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
representation of datasets that the panda will write.
|
|
101
|
-
"""
|
|
102
|
-
|
|
93
|
+
# Load data from the datasets PV on the panda, update internal
|
|
94
|
+
# representation of datasets that the panda will write.
|
|
103
95
|
capture_table = await self.panda_data_block.datasets.get_value()
|
|
104
96
|
self._datasets = [
|
|
105
97
|
# TODO: Update chunk size to read signal once available in IOC
|
|
106
98
|
# Currently PandA IOC sets chunk size to 1024 points per chunk
|
|
107
|
-
|
|
108
|
-
dataset_name,
|
|
99
|
+
HDFDatasetDescription(
|
|
100
|
+
data_key=dataset_name,
|
|
101
|
+
dataset="/" + dataset_name,
|
|
102
|
+
shape=(),
|
|
103
|
+
dtype_numpy="<f8",
|
|
104
|
+
multiplier=1,
|
|
105
|
+
chunk_shape=(1024,),
|
|
109
106
|
)
|
|
110
107
|
for dataset_name in capture_table.name
|
|
111
108
|
]
|
|
@@ -135,9 +132,9 @@ class PandaHDFWriter(DetectorWriter):
|
|
|
135
132
|
return await self.panda_data_block.num_captured.get_value()
|
|
136
133
|
|
|
137
134
|
async def observe_indices_written(
|
|
138
|
-
self, timeout
|
|
135
|
+
self, timeout: float
|
|
139
136
|
) -> AsyncGenerator[int, None]:
|
|
140
|
-
"""Wait until a specific index is ready to be collected"""
|
|
137
|
+
"""Wait until a specific index is ready to be collected."""
|
|
141
138
|
async for num_captured in observe_value(
|
|
142
139
|
self.panda_data_block.num_captured, timeout
|
|
143
140
|
):
|
|
@@ -148,15 +145,15 @@ class PandaHDFWriter(DetectorWriter):
|
|
|
148
145
|
) -> AsyncIterator[StreamAsset]:
|
|
149
146
|
# TODO: fail if we get dropped frames
|
|
150
147
|
if indices_written:
|
|
151
|
-
if not self.
|
|
152
|
-
self.
|
|
148
|
+
if not self._composer:
|
|
149
|
+
self._composer = HDFDocumentComposer(
|
|
153
150
|
Path(await self.panda_data_block.hdf_directory.get_value())
|
|
154
151
|
/ Path(await self.panda_data_block.hdf_file_name.get_value()),
|
|
155
152
|
self._datasets,
|
|
156
153
|
)
|
|
157
|
-
for doc in self.
|
|
154
|
+
for doc in self._composer.stream_resources():
|
|
158
155
|
yield "stream_resource", doc
|
|
159
|
-
for doc in self.
|
|
156
|
+
for doc in self._composer.stream_data(indices_written):
|
|
160
157
|
yield "stream_datum", doc
|
|
161
158
|
|
|
162
159
|
# Could put this function as default for StandardDetector
|
|
@@ -12,6 +12,7 @@ def ensure_connected(
|
|
|
12
12
|
timeout: float = DEFAULT_TIMEOUT,
|
|
13
13
|
force_reconnect=False,
|
|
14
14
|
):
|
|
15
|
+
"""Plan stub to ensure devices are connected with a given timeout."""
|
|
15
16
|
device_names = [device.name for device in devices]
|
|
16
17
|
non_unique = {
|
|
17
18
|
device: device.name for device in devices if device_names.count(device.name) > 1
|
ophyd_async/plan_stubs/_fly.py
CHANGED
|
@@ -9,7 +9,7 @@ from ophyd_async.core import (
|
|
|
9
9
|
in_micros,
|
|
10
10
|
)
|
|
11
11
|
from ophyd_async.fastcs.panda import (
|
|
12
|
-
|
|
12
|
+
PandaPcompDirection,
|
|
13
13
|
PcompInfo,
|
|
14
14
|
SeqTable,
|
|
15
15
|
SeqTableInfo,
|
|
@@ -28,7 +28,6 @@ def prepare_static_pcomp_flyer_and_detectors(
|
|
|
28
28
|
same trigger.
|
|
29
29
|
|
|
30
30
|
"""
|
|
31
|
-
|
|
32
31
|
for det in detectors:
|
|
33
32
|
yield from bps.prepare(det, trigger_info, wait=False, group="prep")
|
|
34
33
|
yield from bps.prepare(flyer, pcomp_info, wait=False, group="prep")
|
|
@@ -147,7 +146,7 @@ def fly_and_collect_with_static_pcomp(
|
|
|
147
146
|
number_of_pulses: int,
|
|
148
147
|
pulse_width: int,
|
|
149
148
|
rising_edge_step: int,
|
|
150
|
-
direction:
|
|
149
|
+
direction: PandaPcompDirection,
|
|
151
150
|
trigger_info: TriggerInfo,
|
|
152
151
|
):
|
|
153
152
|
# Set up scan and prepare trigger
|
|
@@ -190,7 +189,6 @@ def time_resolved_fly_and_collect_with_static_seq_table(
|
|
|
190
189
|
stages/unstages the devices, and opens and closes the run.
|
|
191
190
|
|
|
192
191
|
"""
|
|
193
|
-
|
|
194
192
|
# Set up scan and prepare trigger
|
|
195
193
|
yield from prepare_static_seq_table_flyer_and_detectors_with_same_trigger(
|
|
196
194
|
flyer,
|
|
@@ -16,6 +16,7 @@ from ophyd_async.epics.adcore import (
|
|
|
16
16
|
def setup_ndattributes(
|
|
17
17
|
device: NDArrayBaseIO, ndattributes: Sequence[NDAttributePv | NDAttributeParam]
|
|
18
18
|
):
|
|
19
|
+
"""Set up attributes on NdArray devices."""
|
|
19
20
|
root = ET.Element("Attributes")
|
|
20
21
|
|
|
21
22
|
for ndattribute in ndattributes:
|
|
@@ -50,6 +51,7 @@ def setup_ndattributes(
|
|
|
50
51
|
|
|
51
52
|
|
|
52
53
|
def setup_ndstats_sum(detector: Device):
|
|
54
|
+
"""Set up nd stats for a detector."""
|
|
53
55
|
hdf = getattr(detector, "fileio", None)
|
|
54
56
|
if not isinstance(hdf, NDFileHDFIO):
|
|
55
57
|
msg = (
|
ophyd_async/plan_stubs/_panda.py
CHANGED
|
@@ -8,6 +8,7 @@ from ._settings import apply_settings
|
|
|
8
8
|
|
|
9
9
|
@plan
|
|
10
10
|
def apply_panda_settings(settings: Settings[panda.HDFPanda]) -> MsgGenerator[None]:
|
|
11
|
+
"""Apply given settings to a panda device."""
|
|
11
12
|
units, others = settings.partition(lambda signal: signal.name.endswith("_units"))
|
|
12
13
|
yield from apply_settings(units)
|
|
13
14
|
yield from apply_settings(others)
|
|
@@ -13,11 +13,12 @@ from ophyd_async.core import (
|
|
|
13
13
|
Settings,
|
|
14
14
|
SettingsProvider,
|
|
15
15
|
SignalRW,
|
|
16
|
-
|
|
16
|
+
walk_config_signals,
|
|
17
17
|
walk_rw_signals,
|
|
18
18
|
)
|
|
19
19
|
from ophyd_async.core._table import Table
|
|
20
20
|
|
|
21
|
+
from ._utils import T
|
|
21
22
|
from ._wait_for_awaitable import wait_for_awaitable
|
|
22
23
|
|
|
23
24
|
|
|
@@ -32,8 +33,17 @@ def _get_values_of_signals(
|
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
@plan
|
|
35
|
-
def get_current_settings(
|
|
36
|
-
|
|
36
|
+
def get_current_settings(
|
|
37
|
+
device: Device, only_config: bool = False
|
|
38
|
+
) -> MsgGenerator[Settings]:
|
|
39
|
+
"""Get current settings on `Device`.
|
|
40
|
+
|
|
41
|
+
If `only_config` is True, get current configuration settings on `Configurable`.
|
|
42
|
+
"""
|
|
43
|
+
if only_config:
|
|
44
|
+
signals = yield from wait_for_awaitable(walk_config_signals(device))
|
|
45
|
+
else:
|
|
46
|
+
signals = walk_rw_signals(device)
|
|
37
47
|
named_values = yield from _get_values_of_signals(signals)
|
|
38
48
|
signal_values = {signals[name]: value for name, value in named_values.items()}
|
|
39
49
|
return Settings(device, signal_values)
|
|
@@ -41,23 +51,43 @@ def get_current_settings(device: Device) -> MsgGenerator[Settings]:
|
|
|
41
51
|
|
|
42
52
|
@plan
|
|
43
53
|
def store_settings(
|
|
44
|
-
provider: SettingsProvider, name: str, device: Device
|
|
54
|
+
provider: SettingsProvider, name: str, device: Device, only_config: bool = False
|
|
45
55
|
) -> MsgGenerator[None]:
|
|
46
|
-
"""Walk a Device for SignalRWs and store their values
|
|
47
|
-
|
|
56
|
+
"""Walk a Device for SignalRWs and store their values.
|
|
57
|
+
|
|
58
|
+
If `only_config` is True, store only configuration settings on `Configurable`.
|
|
59
|
+
|
|
60
|
+
:param provider: The provider to store the settings with.
|
|
61
|
+
:param name: The name to store the settings under.
|
|
62
|
+
:param device: The Device to walk for SignalRWs.
|
|
63
|
+
:param only_config: If True, store only configuration settings.
|
|
48
64
|
"""
|
|
49
|
-
|
|
65
|
+
if only_config:
|
|
66
|
+
signals = yield from wait_for_awaitable(walk_config_signals(device))
|
|
67
|
+
else:
|
|
68
|
+
signals = walk_rw_signals(device)
|
|
50
69
|
named_values = yield from _get_values_of_signals(signals)
|
|
51
70
|
yield from wait_for_awaitable(provider.store(name, named_values))
|
|
52
71
|
|
|
53
72
|
|
|
54
73
|
@plan
|
|
55
74
|
def retrieve_settings(
|
|
56
|
-
provider: SettingsProvider, name: str, device: Device
|
|
75
|
+
provider: SettingsProvider, name: str, device: Device, only_config: bool = False
|
|
57
76
|
) -> MsgGenerator[Settings]:
|
|
58
|
-
"""Retrieve named Settings for a Device from a provider.
|
|
77
|
+
"""Retrieve named Settings for a Device from a provider.
|
|
78
|
+
|
|
79
|
+
If `only_config` is True, retrieve only configuration settings on `Configurable`.
|
|
80
|
+
|
|
81
|
+
:param provider: The provider to retrieve the settings from.
|
|
82
|
+
:param name: The name of the settings to retrieve.
|
|
83
|
+
:param device: The Device to retrieve the settings for.
|
|
84
|
+
:param only_config: If True, retrieve only configuration settings.
|
|
85
|
+
"""
|
|
59
86
|
named_values = yield from wait_for_awaitable(provider.retrieve(name))
|
|
60
|
-
|
|
87
|
+
if only_config:
|
|
88
|
+
signals = yield from wait_for_awaitable(walk_config_signals(device))
|
|
89
|
+
else:
|
|
90
|
+
signals = walk_rw_signals(device)
|
|
61
91
|
unknown_names = set(named_values) - set(signals)
|
|
62
92
|
if unknown_names:
|
|
63
93
|
raise NameError(f"Unknown signal names {sorted(unknown_names)}")
|
|
@@ -83,15 +113,12 @@ def apply_settings_if_different(
|
|
|
83
113
|
apply_plan: Callable[[Settings], MsgGenerator[None]],
|
|
84
114
|
current_settings: Settings | None = None,
|
|
85
115
|
) -> MsgGenerator[None]:
|
|
86
|
-
"""Set every SignalRW in settings
|
|
87
|
-
current value.
|
|
116
|
+
"""Set every SignalRW in settings, only if it is different to the current value.
|
|
88
117
|
|
|
89
|
-
|
|
90
|
-
----------
|
|
91
|
-
apply_plan:
|
|
118
|
+
:param apply_plan:
|
|
92
119
|
A device specific plan which takes the Settings to apply and applies them to
|
|
93
120
|
the Device. Used to add device specific ordering to setting the signals.
|
|
94
|
-
current_settings:
|
|
121
|
+
:param current_settings:
|
|
95
122
|
If given, should be a superset of settings containing the current value of
|
|
96
123
|
the Settings in the Device. If not given it will be created by reading just
|
|
97
124
|
the signals given in settings.
|
ophyd_async/sim/__init__.py
CHANGED
|
@@ -1,19 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
"""Some simulated devices to be used in tutorials and testing."""
|
|
2
|
+
|
|
3
|
+
from ._blob_detector import SimBlobDetector
|
|
4
|
+
from ._mirror_horizontal import HorizontalMirror, HorizontalMirrorDerived
|
|
5
|
+
from ._mirror_vertical import (
|
|
6
|
+
TwoJackDerived,
|
|
7
|
+
TwoJackRaw,
|
|
8
|
+
TwoJackTransform,
|
|
9
|
+
VerticalMirror,
|
|
8
10
|
)
|
|
9
|
-
from .
|
|
11
|
+
from ._motor import FlySimMotorInfo, SimMotor
|
|
12
|
+
from ._pattern_generator import PatternGenerator
|
|
13
|
+
from ._point_detector import SimPointDetector
|
|
14
|
+
from ._stage import SimStage
|
|
10
15
|
|
|
11
16
|
__all__ = [
|
|
12
|
-
"DATA_PATH",
|
|
13
|
-
"SUM_PATH",
|
|
14
|
-
"PatternGenerator",
|
|
15
|
-
"PatternDetector",
|
|
16
|
-
"PatternDetectorController",
|
|
17
|
-
"PatternDetectorWriter",
|
|
18
17
|
"SimMotor",
|
|
18
|
+
"FlySimMotorInfo",
|
|
19
|
+
"SimStage",
|
|
20
|
+
"PatternGenerator",
|
|
21
|
+
"SimPointDetector",
|
|
22
|
+
"SimBlobDetector",
|
|
23
|
+
"VerticalMirror",
|
|
24
|
+
"HorizontalMirror",
|
|
25
|
+
"HorizontalMirrorDerived",
|
|
26
|
+
"TwoJackTransform",
|
|
27
|
+
"TwoJackDerived",
|
|
28
|
+
"TwoJackRaw",
|
|
19
29
|
]
|