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.
Files changed (151) hide show
  1. ophyd_async/__init__.py +5 -8
  2. ophyd_async/_docs_parser.py +12 -0
  3. ophyd_async/_version.py +9 -4
  4. ophyd_async/core/__init__.py +97 -62
  5. ophyd_async/core/_derived_signal.py +271 -0
  6. ophyd_async/core/_derived_signal_backend.py +300 -0
  7. ophyd_async/core/_detector.py +106 -125
  8. ophyd_async/core/_device.py +69 -63
  9. ophyd_async/core/_device_filler.py +65 -1
  10. ophyd_async/core/_flyer.py +14 -5
  11. ophyd_async/core/_hdf_dataset.py +29 -22
  12. ophyd_async/core/_log.py +14 -23
  13. ophyd_async/core/_mock_signal_backend.py +11 -3
  14. ophyd_async/core/_protocol.py +65 -45
  15. ophyd_async/core/_providers.py +28 -9
  16. ophyd_async/core/_readable.py +44 -35
  17. ophyd_async/core/_settings.py +36 -27
  18. ophyd_async/core/_signal.py +262 -170
  19. ophyd_async/core/_signal_backend.py +56 -13
  20. ophyd_async/core/_soft_signal_backend.py +16 -11
  21. ophyd_async/core/_status.py +72 -24
  22. ophyd_async/core/_table.py +41 -11
  23. ophyd_async/core/_utils.py +96 -49
  24. ophyd_async/core/_yaml_settings.py +2 -0
  25. ophyd_async/epics/__init__.py +1 -0
  26. ophyd_async/epics/adandor/_andor.py +2 -2
  27. ophyd_async/epics/adandor/_andor_controller.py +4 -2
  28. ophyd_async/epics/adandor/_andor_io.py +2 -4
  29. ophyd_async/epics/adaravis/__init__.py +5 -0
  30. ophyd_async/epics/adaravis/_aravis.py +4 -8
  31. ophyd_async/epics/adaravis/_aravis_controller.py +20 -43
  32. ophyd_async/epics/adaravis/_aravis_io.py +13 -28
  33. ophyd_async/epics/adcore/__init__.py +23 -8
  34. ophyd_async/epics/adcore/_core_detector.py +42 -2
  35. ophyd_async/epics/adcore/_core_io.py +124 -99
  36. ophyd_async/epics/adcore/_core_logic.py +106 -27
  37. ophyd_async/epics/adcore/_core_writer.py +12 -8
  38. ophyd_async/epics/adcore/_hdf_writer.py +21 -38
  39. ophyd_async/epics/adcore/_single_trigger.py +2 -2
  40. ophyd_async/epics/adcore/_utils.py +2 -2
  41. ophyd_async/epics/adkinetix/__init__.py +2 -1
  42. ophyd_async/epics/adkinetix/_kinetix.py +3 -3
  43. ophyd_async/epics/adkinetix/_kinetix_controller.py +4 -2
  44. ophyd_async/epics/adkinetix/_kinetix_io.py +12 -13
  45. ophyd_async/epics/adpilatus/__init__.py +5 -0
  46. ophyd_async/epics/adpilatus/_pilatus.py +1 -1
  47. ophyd_async/epics/adpilatus/_pilatus_controller.py +5 -24
  48. ophyd_async/epics/adpilatus/_pilatus_io.py +11 -9
  49. ophyd_async/epics/adsimdetector/__init__.py +8 -1
  50. ophyd_async/epics/adsimdetector/_sim.py +4 -14
  51. ophyd_async/epics/adsimdetector/_sim_controller.py +17 -0
  52. ophyd_async/epics/adsimdetector/_sim_io.py +10 -0
  53. ophyd_async/epics/advimba/__init__.py +10 -1
  54. ophyd_async/epics/advimba/_vimba.py +3 -2
  55. ophyd_async/epics/advimba/_vimba_controller.py +4 -2
  56. ophyd_async/epics/advimba/_vimba_io.py +23 -28
  57. ophyd_async/epics/core/_aioca.py +35 -16
  58. ophyd_async/epics/core/_epics_connector.py +4 -0
  59. ophyd_async/epics/core/_epics_device.py +2 -0
  60. ophyd_async/epics/core/_p4p.py +10 -2
  61. ophyd_async/epics/core/_pvi_connector.py +65 -8
  62. ophyd_async/epics/core/_signal.py +51 -51
  63. ophyd_async/epics/core/_util.py +4 -4
  64. ophyd_async/epics/demo/__init__.py +16 -0
  65. ophyd_async/epics/demo/__main__.py +31 -0
  66. ophyd_async/epics/demo/_ioc.py +32 -0
  67. ophyd_async/epics/demo/_motor.py +82 -0
  68. ophyd_async/epics/demo/_point_detector.py +42 -0
  69. ophyd_async/epics/demo/_point_detector_channel.py +22 -0
  70. ophyd_async/epics/demo/_stage.py +15 -0
  71. ophyd_async/epics/{sim/mover.db → demo/motor.db} +2 -1
  72. ophyd_async/epics/demo/point_detector.db +59 -0
  73. ophyd_async/epics/demo/point_detector_channel.db +21 -0
  74. ophyd_async/epics/eiger/_eiger.py +1 -3
  75. ophyd_async/epics/eiger/_eiger_controller.py +11 -4
  76. ophyd_async/epics/eiger/_eiger_io.py +2 -0
  77. ophyd_async/epics/eiger/_odin_io.py +1 -2
  78. ophyd_async/epics/motor.py +65 -28
  79. ophyd_async/epics/signal.py +4 -1
  80. ophyd_async/epics/testing/_example_ioc.py +21 -9
  81. ophyd_async/epics/testing/_utils.py +3 -0
  82. ophyd_async/epics/testing/test_records.db +8 -0
  83. ophyd_async/epics/testing/test_records_pva.db +17 -16
  84. ophyd_async/fastcs/__init__.py +1 -0
  85. ophyd_async/fastcs/core.py +6 -0
  86. ophyd_async/fastcs/odin/__init__.py +1 -0
  87. ophyd_async/fastcs/panda/__init__.py +8 -6
  88. ophyd_async/fastcs/panda/_block.py +29 -9
  89. ophyd_async/fastcs/panda/_control.py +5 -0
  90. ophyd_async/fastcs/panda/_hdf_panda.py +2 -0
  91. ophyd_async/fastcs/panda/_table.py +9 -6
  92. ophyd_async/fastcs/panda/_trigger.py +23 -9
  93. ophyd_async/fastcs/panda/_writer.py +27 -30
  94. ophyd_async/plan_stubs/__init__.py +2 -0
  95. ophyd_async/plan_stubs/_ensure_connected.py +1 -0
  96. ophyd_async/plan_stubs/_fly.py +2 -4
  97. ophyd_async/plan_stubs/_nd_attributes.py +2 -0
  98. ophyd_async/plan_stubs/_panda.py +1 -0
  99. ophyd_async/plan_stubs/_settings.py +43 -16
  100. ophyd_async/plan_stubs/_utils.py +3 -0
  101. ophyd_async/plan_stubs/_wait_for_awaitable.py +1 -1
  102. ophyd_async/sim/__init__.py +24 -14
  103. ophyd_async/sim/__main__.py +43 -0
  104. ophyd_async/sim/_blob_detector.py +33 -0
  105. ophyd_async/sim/_blob_detector_controller.py +48 -0
  106. ophyd_async/sim/_blob_detector_writer.py +105 -0
  107. ophyd_async/sim/_mirror_horizontal.py +46 -0
  108. ophyd_async/sim/_mirror_vertical.py +74 -0
  109. ophyd_async/sim/_motor.py +233 -0
  110. ophyd_async/sim/_pattern_generator.py +124 -0
  111. ophyd_async/sim/_point_detector.py +86 -0
  112. ophyd_async/sim/_stage.py +19 -0
  113. ophyd_async/tango/__init__.py +1 -0
  114. ophyd_async/tango/core/__init__.py +6 -1
  115. ophyd_async/tango/core/_base_device.py +41 -33
  116. ophyd_async/tango/core/_converters.py +81 -0
  117. ophyd_async/tango/core/_signal.py +18 -32
  118. ophyd_async/tango/core/_tango_readable.py +2 -19
  119. ophyd_async/tango/core/_tango_transport.py +136 -60
  120. ophyd_async/tango/core/_utils.py +47 -0
  121. ophyd_async/tango/{sim → demo}/_counter.py +2 -0
  122. ophyd_async/tango/{sim → demo}/_detector.py +2 -0
  123. ophyd_async/tango/{sim → demo}/_mover.py +5 -4
  124. ophyd_async/tango/{sim → demo}/_tango/_servers.py +4 -0
  125. ophyd_async/tango/testing/__init__.py +6 -0
  126. ophyd_async/tango/testing/_one_of_everything.py +200 -0
  127. ophyd_async/testing/__init__.py +29 -7
  128. ophyd_async/testing/_assert.py +145 -83
  129. ophyd_async/testing/_mock_signal_utils.py +56 -70
  130. ophyd_async/testing/_one_of_everything.py +41 -21
  131. ophyd_async/testing/_single_derived.py +89 -0
  132. ophyd_async/testing/_utils.py +3 -0
  133. {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a2.dist-info}/METADATA +25 -26
  134. ophyd_async-0.10.0a2.dist-info/RECORD +149 -0
  135. {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a2.dist-info}/WHEEL +1 -1
  136. ophyd_async/epics/sim/__init__.py +0 -54
  137. ophyd_async/epics/sim/_ioc.py +0 -29
  138. ophyd_async/epics/sim/_mover.py +0 -101
  139. ophyd_async/epics/sim/_sensor.py +0 -37
  140. ophyd_async/epics/sim/sensor.db +0 -19
  141. ophyd_async/sim/_pattern_detector/__init__.py +0 -13
  142. ophyd_async/sim/_pattern_detector/_pattern_detector.py +0 -42
  143. ophyd_async/sim/_pattern_detector/_pattern_detector_controller.py +0 -69
  144. ophyd_async/sim/_pattern_detector/_pattern_detector_writer.py +0 -41
  145. ophyd_async/sim/_pattern_detector/_pattern_generator.py +0 -214
  146. ophyd_async/sim/_sim_motor.py +0 -107
  147. ophyd_async-0.9.0a2.dist-info/RECORD +0 -129
  148. /ophyd_async/tango/{sim → demo}/__init__.py +0 -0
  149. /ophyd_async/tango/{sim → demo}/_tango/__init__.py +0 -0
  150. {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a2.dist-info/licenses}/LICENSE +0 -0
  151. {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 CaptureMode(StrictEnum):
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[CaptureMode]
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 PcompDirection(StrictEnum):
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 BitMux(SubsetEnum):
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[PcompDirection]
52
- enable: SignalRW[BitMux]
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 TimeUnits(StrictEnum):
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[TimeUnits]
72
- enable: SignalRW[BitMux]
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
@@ -15,6 +15,8 @@ MINIMUM_PANDA_IOC = "0.11.4"
15
15
  class HDFPanda(
16
16
  CommonPandaBlocks, StandardDetector[PandaPcapController, PandaHDFWriter]
17
17
  ):
18
+ """PandA with common blocks for standard HDF writing."""
19
+
18
20
  def __init__(
19
21
  self,
20
22
  prefix: str,
@@ -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 validate_max_length(self) -> "SeqTable":
80
- """
81
- Used to check max_length. Unfortunately trying the `max_length` arg in
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 BitMux, PcompBlock, PcompDirection, SeqBlock, TimeUnits
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(TimeUnits.US),
24
- self.seq.enable.set(BitMux.ZERO),
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(BitMux.ONE)
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(BitMux.ZERO)
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: PcompDirection = Field(
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(BitMux.ZERO)
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(BitMux.ONE)
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(BitMux.ZERO)
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
- HDFDataset,
13
- HDFFile,
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 CaptureMode, DataBlock
19
+ from ._block import DataBlock, PandaCaptureMode
21
20
 
22
21
 
23
22
  class PandaHDFWriter(DetectorWriter):
24
- _ctxt: Context | None = None
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[HDFDataset] = []
36
- self._file: HDFFile | None = None
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._file = None
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(CaptureMode.FOREVER),
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="array" if ds.shape != [1] else "number",
83
+ dtype="number",
89
84
  # PandA data should always be written as Float64
90
- dtype_numpy="<f8",
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
- Load data from the datasets PV on the panda, update internal
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
- HDFDataset(
108
- dataset_name, "/" + dataset_name, [1], multiplier=1, chunk_shape=(1024,)
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=DEFAULT_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._file:
152
- self._file = HDFFile(
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._file.stream_resources():
154
+ for doc in self._composer.stream_resources():
158
155
  yield "stream_resource", doc
159
- for doc in self._file.stream_data(indices_written):
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
@@ -1,3 +1,5 @@
1
+ """Plan stubs for connecting, setting up and flying devices."""
2
+
1
3
  from ._ensure_connected import ensure_connected
2
4
  from ._fly import (
3
5
  fly_and_collect,
@@ -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
@@ -9,7 +9,7 @@ from ophyd_async.core import (
9
9
  in_micros,
10
10
  )
11
11
  from ophyd_async.fastcs.panda import (
12
- PcompDirection,
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: PcompDirection,
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 = (
@@ -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
- T,
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(device: Device) -> MsgGenerator[Settings]:
36
- signals = walk_rw_signals(device)
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 with a provider associated
47
- with the given name.
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
- signals = walk_rw_signals(device)
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
- signals = walk_rw_signals(device)
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 to its given value if it is different to the
87
- current value.
116
+ """Set every SignalRW in settings, only if it is different to the current value.
88
117
 
89
- Parameters
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.
@@ -0,0 +1,3 @@
1
+ from typing import TypeVar
2
+
3
+ T = TypeVar("T")
@@ -3,7 +3,7 @@ from collections.abc import Awaitable
3
3
  import bluesky.plan_stubs as bps
4
4
  from bluesky.utils import MsgGenerator, plan
5
5
 
6
- from ophyd_async.core import T
6
+ from ._utils import T
7
7
 
8
8
 
9
9
  @plan
@@ -1,19 +1,29 @@
1
- from ._pattern_detector import (
2
- DATA_PATH,
3
- SUM_PATH,
4
- PatternDetector,
5
- PatternDetectorController,
6
- PatternDetectorWriter,
7
- PatternGenerator,
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 ._sim_motor import SimMotor
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
  ]