ophyd-async 0.1.0__py3-none-any.whl → 0.3.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.
Files changed (94) hide show
  1. ophyd_async/__init__.py +1 -4
  2. ophyd_async/_version.py +2 -2
  3. ophyd_async/core/__init__.py +91 -19
  4. ophyd_async/core/_providers.py +68 -0
  5. ophyd_async/core/async_status.py +90 -42
  6. ophyd_async/core/detector.py +341 -0
  7. ophyd_async/core/device.py +226 -0
  8. ophyd_async/core/device_save_loader.py +286 -0
  9. ophyd_async/core/flyer.py +85 -0
  10. ophyd_async/core/mock_signal_backend.py +82 -0
  11. ophyd_async/core/mock_signal_utils.py +145 -0
  12. ophyd_async/core/{_device/_signal/signal.py → signal.py} +249 -61
  13. ophyd_async/core/{_device/_backend/signal_backend.py → signal_backend.py} +12 -5
  14. ophyd_async/core/{_device/_backend/sim_signal_backend.py → soft_signal_backend.py} +54 -48
  15. ophyd_async/core/standard_readable.py +261 -0
  16. ophyd_async/core/utils.py +127 -30
  17. ophyd_async/epics/_backend/_aioca.py +62 -43
  18. ophyd_async/epics/_backend/_p4p.py +100 -52
  19. ophyd_async/epics/_backend/common.py +25 -0
  20. ophyd_async/epics/areadetector/__init__.py +16 -15
  21. ophyd_async/epics/areadetector/aravis.py +63 -0
  22. ophyd_async/epics/areadetector/controllers/__init__.py +5 -0
  23. ophyd_async/epics/areadetector/controllers/ad_sim_controller.py +52 -0
  24. ophyd_async/epics/areadetector/controllers/aravis_controller.py +78 -0
  25. ophyd_async/epics/areadetector/controllers/kinetix_controller.py +49 -0
  26. ophyd_async/epics/areadetector/controllers/pilatus_controller.py +61 -0
  27. ophyd_async/epics/areadetector/controllers/vimba_controller.py +66 -0
  28. ophyd_async/epics/areadetector/drivers/__init__.py +21 -0
  29. ophyd_async/epics/areadetector/drivers/ad_base.py +107 -0
  30. ophyd_async/epics/areadetector/drivers/aravis_driver.py +38 -0
  31. ophyd_async/epics/areadetector/drivers/kinetix_driver.py +27 -0
  32. ophyd_async/epics/areadetector/drivers/pilatus_driver.py +21 -0
  33. ophyd_async/epics/areadetector/drivers/vimba_driver.py +63 -0
  34. ophyd_async/epics/areadetector/kinetix.py +46 -0
  35. ophyd_async/epics/areadetector/pilatus.py +45 -0
  36. ophyd_async/epics/areadetector/single_trigger_det.py +18 -10
  37. ophyd_async/epics/areadetector/utils.py +91 -13
  38. ophyd_async/epics/areadetector/vimba.py +43 -0
  39. ophyd_async/epics/areadetector/writers/__init__.py +5 -0
  40. ophyd_async/epics/areadetector/writers/_hdfdataset.py +10 -0
  41. ophyd_async/epics/areadetector/writers/_hdffile.py +54 -0
  42. ophyd_async/epics/areadetector/writers/hdf_writer.py +142 -0
  43. ophyd_async/epics/areadetector/writers/nd_file_hdf.py +40 -0
  44. ophyd_async/epics/areadetector/writers/nd_plugin.py +38 -0
  45. ophyd_async/epics/demo/__init__.py +78 -51
  46. ophyd_async/epics/demo/demo_ad_sim_detector.py +35 -0
  47. ophyd_async/epics/motion/motor.py +67 -52
  48. ophyd_async/epics/pvi/__init__.py +3 -0
  49. ophyd_async/epics/pvi/pvi.py +318 -0
  50. ophyd_async/epics/signal/__init__.py +8 -3
  51. ophyd_async/epics/signal/signal.py +27 -10
  52. ophyd_async/log.py +130 -0
  53. ophyd_async/panda/__init__.py +24 -7
  54. ophyd_async/panda/_common_blocks.py +49 -0
  55. ophyd_async/panda/_hdf_panda.py +48 -0
  56. ophyd_async/panda/_panda_controller.py +37 -0
  57. ophyd_async/panda/_table.py +158 -0
  58. ophyd_async/panda/_trigger.py +39 -0
  59. ophyd_async/panda/_utils.py +15 -0
  60. ophyd_async/panda/writers/__init__.py +3 -0
  61. ophyd_async/panda/writers/_hdf_writer.py +220 -0
  62. ophyd_async/panda/writers/_panda_hdf_file.py +58 -0
  63. ophyd_async/plan_stubs/__init__.py +13 -0
  64. ophyd_async/plan_stubs/ensure_connected.py +22 -0
  65. ophyd_async/plan_stubs/fly.py +149 -0
  66. ophyd_async/protocols.py +126 -0
  67. ophyd_async/sim/__init__.py +11 -0
  68. ophyd_async/sim/demo/__init__.py +3 -0
  69. ophyd_async/sim/demo/sim_motor.py +103 -0
  70. ophyd_async/sim/pattern_generator.py +318 -0
  71. ophyd_async/sim/sim_pattern_detector_control.py +55 -0
  72. ophyd_async/sim/sim_pattern_detector_writer.py +34 -0
  73. ophyd_async/sim/sim_pattern_generator.py +37 -0
  74. {ophyd_async-0.1.0.dist-info → ophyd_async-0.3.0.dist-info}/METADATA +35 -67
  75. ophyd_async-0.3.0.dist-info/RECORD +86 -0
  76. {ophyd_async-0.1.0.dist-info → ophyd_async-0.3.0.dist-info}/WHEEL +1 -1
  77. ophyd_async/core/_device/__init__.py +0 -0
  78. ophyd_async/core/_device/_backend/__init__.py +0 -0
  79. ophyd_async/core/_device/_signal/__init__.py +0 -0
  80. ophyd_async/core/_device/device.py +0 -60
  81. ophyd_async/core/_device/device_collector.py +0 -121
  82. ophyd_async/core/_device/device_vector.py +0 -14
  83. ophyd_async/core/_device/standard_readable.py +0 -72
  84. ophyd_async/epics/areadetector/ad_driver.py +0 -18
  85. ophyd_async/epics/areadetector/directory_provider.py +0 -18
  86. ophyd_async/epics/areadetector/hdf_streamer_det.py +0 -167
  87. ophyd_async/epics/areadetector/nd_file_hdf.py +0 -22
  88. ophyd_async/epics/areadetector/nd_plugin.py +0 -13
  89. ophyd_async/epics/signal/pvi_get.py +0 -22
  90. ophyd_async/panda/panda.py +0 -332
  91. ophyd_async-0.1.0.dist-info/RECORD +0 -45
  92. {ophyd_async-0.1.0.dist-info → ophyd_async-0.3.0.dist-info}/LICENSE +0 -0
  93. {ophyd_async-0.1.0.dist-info → ophyd_async-0.3.0.dist-info}/entry_points.txt +0 -0
  94. {ophyd_async-0.1.0.dist-info → ophyd_async-0.3.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,66 @@
1
+ import asyncio
2
+ from typing import Optional
3
+
4
+ from ophyd_async.core import AsyncStatus, DetectorControl, DetectorTrigger
5
+ from ophyd_async.epics.areadetector.drivers.ad_base import (
6
+ start_acquiring_driver_and_ensure_status,
7
+ )
8
+
9
+ from ..drivers.vimba_driver import (
10
+ VimbaDriver,
11
+ VimbaExposeOutMode,
12
+ VimbaOnOff,
13
+ VimbaTriggerSource,
14
+ )
15
+ from ..utils import ImageMode, stop_busy_record
16
+
17
+ TRIGGER_MODE = {
18
+ DetectorTrigger.internal: VimbaOnOff.off,
19
+ DetectorTrigger.constant_gate: VimbaOnOff.on,
20
+ DetectorTrigger.variable_gate: VimbaOnOff.on,
21
+ DetectorTrigger.edge_trigger: VimbaOnOff.on,
22
+ }
23
+
24
+ EXPOSE_OUT_MODE = {
25
+ DetectorTrigger.internal: VimbaExposeOutMode.timed,
26
+ DetectorTrigger.constant_gate: VimbaExposeOutMode.trigger_width,
27
+ DetectorTrigger.variable_gate: VimbaExposeOutMode.trigger_width,
28
+ DetectorTrigger.edge_trigger: VimbaExposeOutMode.timed,
29
+ }
30
+
31
+
32
+ class VimbaController(DetectorControl):
33
+ def __init__(
34
+ self,
35
+ driver: VimbaDriver,
36
+ ) -> None:
37
+ self._drv = driver
38
+
39
+ def get_deadtime(self, exposure: float) -> float:
40
+ return 0.001
41
+
42
+ async def arm(
43
+ self,
44
+ num: int,
45
+ trigger: DetectorTrigger = DetectorTrigger.internal,
46
+ exposure: Optional[float] = None,
47
+ ) -> AsyncStatus:
48
+ await asyncio.gather(
49
+ self._drv.trigger_mode.set(TRIGGER_MODE[trigger]),
50
+ self._drv.expose_mode.set(EXPOSE_OUT_MODE[trigger]),
51
+ self._drv.num_images.set(num),
52
+ self._drv.image_mode.set(ImageMode.multiple),
53
+ )
54
+ if exposure is not None and trigger not in [
55
+ DetectorTrigger.variable_gate,
56
+ DetectorTrigger.constant_gate,
57
+ ]:
58
+ await self._drv.acquire_time.set(exposure)
59
+ if trigger != DetectorTrigger.internal:
60
+ self._drv.trig_source.set(VimbaTriggerSource.line1)
61
+ else:
62
+ self._drv.trig_source.set(VimbaTriggerSource.freerun)
63
+ return await start_acquiring_driver_and_ensure_status(self._drv)
64
+
65
+ async def disarm(self):
66
+ await stop_busy_record(self._drv.acquire, False, timeout=1)
@@ -0,0 +1,21 @@
1
+ from .ad_base import (
2
+ ADBase,
3
+ ADBaseShapeProvider,
4
+ DetectorState,
5
+ start_acquiring_driver_and_ensure_status,
6
+ )
7
+ from .aravis_driver import AravisDriver
8
+ from .kinetix_driver import KinetixDriver
9
+ from .pilatus_driver import PilatusDriver
10
+ from .vimba_driver import VimbaDriver
11
+
12
+ __all__ = [
13
+ "ADBase",
14
+ "ADBaseShapeProvider",
15
+ "PilatusDriver",
16
+ "AravisDriver",
17
+ "KinetixDriver",
18
+ "VimbaDriver",
19
+ "start_acquiring_driver_and_ensure_status",
20
+ "DetectorState",
21
+ ]
@@ -0,0 +1,107 @@
1
+ import asyncio
2
+ from enum import Enum
3
+ from typing import FrozenSet, Sequence, Set
4
+
5
+ from ophyd_async.core import (
6
+ DEFAULT_TIMEOUT,
7
+ AsyncStatus,
8
+ ShapeProvider,
9
+ set_and_wait_for_value,
10
+ )
11
+
12
+ from ...signal.signal import epics_signal_r, epics_signal_rw_rbv
13
+ from ..utils import ImageMode
14
+ from ..writers.nd_plugin import NDArrayBase
15
+
16
+
17
+ class DetectorState(str, Enum):
18
+ """
19
+ Default set of states of an AreaDetector driver.
20
+ See definition in ADApp/ADSrc/ADDriver.h in https://github.com/areaDetector/ADCore
21
+ """
22
+
23
+ Idle = "Idle"
24
+ Acquire = "Acquire"
25
+ Readout = "Readout"
26
+ Correct = "Correct"
27
+ Saving = "Saving"
28
+ Aborting = "Aborting"
29
+ Error = "Error"
30
+ Waiting = "Waiting"
31
+ Initializing = "Initializing"
32
+ Disconnected = "Disconnected"
33
+ Aborted = "Aborted"
34
+
35
+
36
+ #: Default set of states that we should consider "good" i.e. the acquisition
37
+ # is complete and went well
38
+ DEFAULT_GOOD_STATES: FrozenSet[DetectorState] = frozenset(
39
+ [DetectorState.Idle, DetectorState.Aborted]
40
+ )
41
+
42
+
43
+ class ADBase(NDArrayBase):
44
+ def __init__(self, prefix: str, name: str = "") -> None:
45
+ # Define some signals
46
+ self.acquire_time = epics_signal_rw_rbv(float, prefix + "AcquireTime")
47
+ self.num_images = epics_signal_rw_rbv(int, prefix + "NumImages")
48
+ self.image_mode = epics_signal_rw_rbv(ImageMode, prefix + "ImageMode")
49
+ self.detector_state = epics_signal_r(
50
+ DetectorState, prefix + "DetectorState_RBV"
51
+ )
52
+ super().__init__(prefix, name=name)
53
+
54
+
55
+ async def start_acquiring_driver_and_ensure_status(
56
+ driver: ADBase,
57
+ good_states: Set[DetectorState] = set(DEFAULT_GOOD_STATES),
58
+ timeout: float = DEFAULT_TIMEOUT,
59
+ ) -> AsyncStatus:
60
+ """
61
+ Start acquiring driver, raising ValueError if the detector is in a bad state.
62
+
63
+ This sets driver.acquire to True, and waits for it to be True up to a timeout.
64
+ Then, it checks that the DetectorState PV is in DEFAULT_GOOD_STATES, and otherwise
65
+ raises a ValueError.
66
+
67
+ Parameters
68
+ ----------
69
+ driver:
70
+ The driver to start acquiring. Must subclass ADBase.
71
+ good_states:
72
+ set of states defined in DetectorState enum which are considered good states.
73
+ timeout:
74
+ How long to wait for driver.acquire to readback True (i.e. acquiring).
75
+
76
+ Returns
77
+ -------
78
+ AsyncStatus:
79
+ An AsyncStatus that can be awaited to set driver.acquire to True and perform
80
+ subsequent raising (if applicable) due to detector state.
81
+ """
82
+
83
+ status = await set_and_wait_for_value(driver.acquire, True, timeout=timeout)
84
+
85
+ async def complete_acquisition() -> None:
86
+ """NOTE: possible race condition here between the callback from
87
+ set_and_wait_for_value and the detector state updating."""
88
+ await status
89
+ state = await driver.detector_state.get_value()
90
+ if state not in good_states:
91
+ raise ValueError(
92
+ f"Final detector state {state} not in valid end states: {good_states}"
93
+ )
94
+
95
+ return AsyncStatus(complete_acquisition())
96
+
97
+
98
+ class ADBaseShapeProvider(ShapeProvider):
99
+ def __init__(self, driver: ADBase) -> None:
100
+ self._driver = driver
101
+
102
+ async def __call__(self) -> Sequence[int]:
103
+ shape = await asyncio.gather(
104
+ self._driver.array_size_y.get_value(),
105
+ self._driver.array_size_x.get_value(),
106
+ )
107
+ return shape
@@ -0,0 +1,38 @@
1
+ from enum import Enum
2
+ from typing import Literal
3
+
4
+ from ophyd_async.epics.areadetector.drivers import ADBase
5
+ from ophyd_async.epics.signal.signal import epics_signal_rw_rbv
6
+
7
+
8
+ class AravisTriggerMode(str, Enum):
9
+ """GigEVision GenICAM standard: on=externally triggered"""
10
+
11
+ on = "On"
12
+ off = "Off"
13
+
14
+
15
+ """A minimal set of TriggerSources that must be supported by the underlying record.
16
+ To enable hardware triggered scanning, line_N must support each N in GPIO_NUMBER.
17
+ To enable software triggered scanning, freerun must be supported.
18
+ Other enumerated values may or may not be preset.
19
+ To prevent requiring one Enum class per possible configuration, we set as this Enum
20
+ but read from the underlying signal as a str.
21
+ """
22
+ AravisTriggerSource = Literal["Freerun", "Line1", "Line2", "Line3", "Line4"]
23
+
24
+
25
+ class AravisDriver(ADBase):
26
+ # If instantiating a new instance, ensure it is supported in the _deadtimes dict
27
+ """Generic Driver supporting the Manta and Mako drivers.
28
+ Fetches deadtime prior to use in a Streaming scan.
29
+ Requires driver firmware up to date:
30
+ - Model_RBV must be of the form "^(Mako|Manta) (model)$"
31
+ """
32
+
33
+ def __init__(self, prefix: str, name: str = "") -> None:
34
+ self.trigger_mode = epics_signal_rw_rbv(
35
+ AravisTriggerMode, prefix + "TriggerMode"
36
+ )
37
+ self.trigger_source = epics_signal_rw_rbv(str, prefix + "TriggerSource")
38
+ super().__init__(prefix, name=name)
@@ -0,0 +1,27 @@
1
+ from enum import Enum
2
+
3
+ from ophyd_async.epics.signal.signal import epics_signal_rw_rbv
4
+
5
+ from .ad_base import ADBase
6
+
7
+
8
+ class KinetixTriggerMode(str, Enum):
9
+ internal = "Internal"
10
+ edge = "Rising Edge"
11
+ gate = "Exp. Gate"
12
+
13
+
14
+ class KinetixReadoutMode(str, Enum):
15
+ sensitivity = 1
16
+ speed = 2
17
+ dynamic_range = 3
18
+
19
+
20
+ class KinetixDriver(ADBase):
21
+ def __init__(self, prefix: str, name: str = "") -> None:
22
+ # self.pixel_format = epics_signal_rw_rbv(PixelFormat, prefix + "PixelFormat")
23
+ self.trigger_mode = epics_signal_rw_rbv(
24
+ KinetixTriggerMode, prefix + "TriggerMode"
25
+ )
26
+ self.mode = epics_signal_rw_rbv(KinetixReadoutMode, prefix + "ReadoutPortIdx")
27
+ super().__init__(prefix, name)
@@ -0,0 +1,21 @@
1
+ from enum import Enum
2
+
3
+ from ophyd_async.epics.signal.signal import epics_signal_rw_rbv
4
+
5
+ from .ad_base import ADBase
6
+
7
+
8
+ class PilatusTriggerMode(str, Enum):
9
+ internal = "Internal"
10
+ ext_enable = "Ext. Enable"
11
+ ext_trigger = "Ext. Trigger"
12
+ mult_trigger = "Mult. Trigger"
13
+ alignment = "Alignment"
14
+
15
+
16
+ class PilatusDriver(ADBase):
17
+ def __init__(self, prefix: str, name: str = "") -> None:
18
+ self.trigger_mode = epics_signal_rw_rbv(
19
+ PilatusTriggerMode, prefix + "TriggerMode"
20
+ )
21
+ super().__init__(prefix, name)
@@ -0,0 +1,63 @@
1
+ from enum import Enum
2
+
3
+ from ophyd_async.epics.signal.signal import epics_signal_rw_rbv
4
+
5
+ from .ad_base import ADBase
6
+
7
+
8
+ class VimbaPixelFormat(str, Enum):
9
+ internal = "Mono8"
10
+ ext_enable = "Mono12"
11
+ ext_trigger = "Ext. Trigger"
12
+ mult_trigger = "Mult. Trigger"
13
+ alignment = "Alignment"
14
+
15
+
16
+ class VimbaConvertFormat(str, Enum):
17
+ none = "None"
18
+ mono8 = "Mono8"
19
+ mono16 = "Mono16"
20
+ rgb8 = "RGB8"
21
+ rgb16 = "RGB16"
22
+
23
+
24
+ class VimbaTriggerSource(str, Enum):
25
+ freerun = "Freerun"
26
+ line1 = "Line1"
27
+ line2 = "Line2"
28
+ fixed_rate = "FixedRate"
29
+ software = "Software"
30
+ action0 = "Action0"
31
+ action1 = "Action1"
32
+
33
+
34
+ class VimbaOverlap(str, Enum):
35
+ off = "Off"
36
+ prev_frame = "PreviousFrame"
37
+
38
+
39
+ class VimbaOnOff(str, Enum):
40
+ on = "On"
41
+ off = "Off"
42
+
43
+
44
+ class VimbaExposeOutMode(str, Enum):
45
+ timed = "Timed" # Use ExposureTime PV
46
+ trigger_width = "TriggerWidth" # Expose for length of high signal
47
+
48
+
49
+ class VimbaDriver(ADBase):
50
+ def __init__(self, prefix: str, name: str = "") -> None:
51
+ # self.pixel_format = epics_signal_rw_rbv(PixelFormat, prefix + "PixelFormat")
52
+ self.convert_format = epics_signal_rw_rbv(
53
+ VimbaConvertFormat, prefix + "ConvertPixelFormat"
54
+ ) # Pixel format of data outputted to AD
55
+ self.trig_source = epics_signal_rw_rbv(
56
+ VimbaTriggerSource, prefix + "TriggerSource"
57
+ )
58
+ self.trigger_mode = epics_signal_rw_rbv(VimbaOnOff, prefix + "TriggerMode")
59
+ self.overlap = epics_signal_rw_rbv(VimbaOverlap, prefix + "TriggerOverlap")
60
+ self.expose_mode = epics_signal_rw_rbv(
61
+ VimbaExposeOutMode, prefix + "ExposureMode"
62
+ )
63
+ super().__init__(prefix, name)
@@ -0,0 +1,46 @@
1
+ from bluesky.protocols import HasHints, Hints
2
+
3
+ from ophyd_async.core import DirectoryProvider, StandardDetector
4
+ from ophyd_async.epics.areadetector.controllers.kinetix_controller import (
5
+ KinetixController,
6
+ )
7
+ from ophyd_async.epics.areadetector.drivers import ADBaseShapeProvider
8
+ from ophyd_async.epics.areadetector.drivers.kinetix_driver import KinetixDriver
9
+ from ophyd_async.epics.areadetector.writers import HDFWriter, NDFileHDF
10
+
11
+
12
+ class KinetixDetector(StandardDetector, HasHints):
13
+ """
14
+ Ophyd-async implementation of an ADKinetix Detector.
15
+ https://github.com/NSLS-II/ADKinetix
16
+ """
17
+
18
+ _controller: KinetixController
19
+ _writer: HDFWriter
20
+
21
+ def __init__(
22
+ self,
23
+ prefix: str,
24
+ directory_provider: DirectoryProvider,
25
+ drv_suffix="cam1:",
26
+ hdf_suffix="HDF1:",
27
+ name="",
28
+ ):
29
+ self.drv = KinetixDriver(prefix + drv_suffix)
30
+ self.hdf = NDFileHDF(prefix + hdf_suffix)
31
+
32
+ super().__init__(
33
+ KinetixController(self.drv),
34
+ HDFWriter(
35
+ self.hdf,
36
+ directory_provider,
37
+ lambda: self.name,
38
+ ADBaseShapeProvider(self.drv),
39
+ ),
40
+ config_sigs=(self.drv.acquire_time,),
41
+ name=name,
42
+ )
43
+
44
+ @property
45
+ def hints(self) -> Hints:
46
+ return self._writer.hints
@@ -0,0 +1,45 @@
1
+ from bluesky.protocols import Hints
2
+
3
+ from ophyd_async.core import DirectoryProvider
4
+ from ophyd_async.core.detector import StandardDetector
5
+ from ophyd_async.epics.areadetector.controllers.pilatus_controller import (
6
+ PilatusController,
7
+ )
8
+ from ophyd_async.epics.areadetector.drivers.ad_base import ADBaseShapeProvider
9
+ from ophyd_async.epics.areadetector.drivers.pilatus_driver import PilatusDriver
10
+ from ophyd_async.epics.areadetector.writers.hdf_writer import HDFWriter
11
+ from ophyd_async.epics.areadetector.writers.nd_file_hdf import NDFileHDF
12
+
13
+
14
+ class PilatusDetector(StandardDetector):
15
+ """A Pilatus StandardDetector writing HDF files"""
16
+
17
+ _controller: PilatusController
18
+ _writer: HDFWriter
19
+
20
+ def __init__(
21
+ self,
22
+ prefix: str,
23
+ directory_provider: DirectoryProvider,
24
+ drv_suffix="cam1:",
25
+ hdf_suffix="HDF1:",
26
+ name="",
27
+ ):
28
+ self.drv = PilatusDriver(prefix + drv_suffix)
29
+ self.hdf = NDFileHDF(prefix + hdf_suffix)
30
+
31
+ super().__init__(
32
+ PilatusController(self.drv),
33
+ HDFWriter(
34
+ self.hdf,
35
+ directory_provider,
36
+ lambda: self.name,
37
+ ADBaseShapeProvider(self.drv),
38
+ ),
39
+ config_sigs=(self.drv.acquire_time,),
40
+ name=name,
41
+ )
42
+
43
+ @property
44
+ def hints(self) -> Hints:
45
+ return self._writer.hints
@@ -3,29 +3,37 @@ from typing import Sequence
3
3
 
4
4
  from bluesky.protocols import Triggerable
5
5
 
6
- from ophyd_async.core import AsyncStatus, SignalR, StandardReadable
6
+ from ophyd_async.core import (
7
+ AsyncStatus,
8
+ ConfigSignal,
9
+ HintedSignal,
10
+ SignalR,
11
+ StandardReadable,
12
+ )
7
13
 
8
- from .ad_driver import ADDriver
9
- from .nd_plugin import NDPlugin
14
+ from .drivers.ad_base import ADBase
10
15
  from .utils import ImageMode
16
+ from .writers.nd_plugin import NDPluginBase
11
17
 
12
18
 
13
19
  class SingleTriggerDet(StandardReadable, Triggerable):
14
20
  def __init__(
15
21
  self,
16
- drv: ADDriver,
22
+ drv: ADBase,
17
23
  read_uncached: Sequence[SignalR] = (),
18
24
  name="",
19
- **plugins: NDPlugin,
25
+ **plugins: NDPluginBase,
20
26
  ) -> None:
21
27
  self.drv = drv
22
28
  self.__dict__.update(plugins)
23
- self.set_readable_signals(
24
- # Can't subscribe to read signals as race between monitor coming back and
25
- # caput callback on acquire
26
- read_uncached=[self.drv.array_counter] + list(read_uncached),
27
- config=[self.drv.acquire_time],
29
+
30
+ self.add_readables(
31
+ [self.drv.array_counter, *read_uncached],
32
+ wrapper=HintedSignal.uncached,
28
33
  )
34
+
35
+ self.add_readables([self.drv.acquire_time], wrapper=ConfigSignal)
36
+
29
37
  super().__init__(name=name)
30
38
 
31
39
  @AsyncStatus.wrap
@@ -1,17 +1,8 @@
1
1
  from enum import Enum
2
- from typing import Type
2
+ from typing import Optional
3
+ from xml.etree import cElementTree as ET
3
4
 
4
- from ophyd_async.core import SignalR, SignalRW, T
5
-
6
- from ..signal.signal import epics_signal_r, epics_signal_rw
7
-
8
-
9
- def ad_rw(datatype: Type[T], prefix: str) -> SignalRW[T]:
10
- return epics_signal_rw(datatype, prefix + "_RBV", prefix)
11
-
12
-
13
- def ad_r(datatype: Type[T], prefix: str) -> SignalR[T]:
14
- return epics_signal_r(datatype, prefix + "_RBV")
5
+ from ophyd_async.core import DEFAULT_TIMEOUT, SignalRW, T, wait_for_value
15
6
 
16
7
 
17
8
  class FileWriteMode(str, Enum):
@@ -20,7 +11,94 @@ class FileWriteMode(str, Enum):
20
11
  stream = "Stream"
21
12
 
22
13
 
23
- class ImageMode(Enum):
14
+ class ImageMode(str, Enum):
24
15
  single = "Single"
25
16
  multiple = "Multiple"
26
17
  continuous = "Continuous"
18
+
19
+
20
+ class NDAttributeDataType(str, Enum):
21
+ INT = "INT"
22
+ DOUBLE = "DOUBLE"
23
+ STRING = "STRING"
24
+
25
+
26
+ class NDAttributesXML:
27
+ """Helper to make NDAttributesFile XML for areaDetector"""
28
+
29
+ _dbr_types = {
30
+ None: "DBR_NATIVE",
31
+ NDAttributeDataType.INT: "DBR_LONG",
32
+ NDAttributeDataType.DOUBLE: "DBR_DOUBLE",
33
+ NDAttributeDataType.STRING: "DBR_STRING",
34
+ }
35
+
36
+ def __init__(self):
37
+ self._root = ET.Element("Attributes")
38
+
39
+ def add_epics_pv(
40
+ self,
41
+ name: str,
42
+ pv: str,
43
+ datatype: Optional[NDAttributeDataType] = None,
44
+ description: str = "",
45
+ ):
46
+ """Add a PV to the attribute list
47
+
48
+ Args:
49
+ name: The attribute name
50
+ pv: The pv to get from
51
+ datatype: An override datatype, otherwise will use native EPICS type
52
+ description: A description that appears in the HDF file as an attribute
53
+ """
54
+ ET.SubElement(
55
+ self._root,
56
+ "Attribute",
57
+ name=name,
58
+ type="EPICS_PV",
59
+ source=pv,
60
+ datatype=self._dbr_types[datatype],
61
+ description=description,
62
+ )
63
+
64
+ def add_param(
65
+ self,
66
+ name: str,
67
+ param: str,
68
+ datatype: NDAttributeDataType,
69
+ addr: int = 0,
70
+ description: str = "",
71
+ ):
72
+ """Add a driver or plugin parameter to the attribute list
73
+
74
+ Args:
75
+ name: The attribute name
76
+ param: The parameter string as seen in the INP link of the record
77
+ datatype: The datatype of the parameter
78
+ description: A description that appears in the HDF file as an attribute
79
+ """
80
+ ET.SubElement(
81
+ self._root,
82
+ "Attribute",
83
+ name=name,
84
+ type="PARAM",
85
+ source=param,
86
+ addr=str(addr),
87
+ datatype=datatype.value,
88
+ description=description,
89
+ )
90
+
91
+ def __str__(self) -> str:
92
+ """Output the XML pretty printed"""
93
+ ET.indent(self._root, space=" ", level=0)
94
+ return ET.tostring(self._root, xml_declaration=True, encoding="utf-8").decode()
95
+
96
+
97
+ async def stop_busy_record(
98
+ signal: SignalRW[T],
99
+ value: T,
100
+ timeout: float = DEFAULT_TIMEOUT,
101
+ status_timeout: Optional[float] = None,
102
+ ) -> None:
103
+ await signal.set(value, wait=False, timeout=status_timeout)
104
+ await wait_for_value(signal, value, timeout=timeout)
@@ -0,0 +1,43 @@
1
+ from bluesky.protocols import HasHints, Hints
2
+
3
+ from ophyd_async.core import DirectoryProvider, StandardDetector
4
+ from ophyd_async.epics.areadetector.controllers.vimba_controller import VimbaController
5
+ from ophyd_async.epics.areadetector.drivers import ADBaseShapeProvider
6
+ from ophyd_async.epics.areadetector.drivers.vimba_driver import VimbaDriver
7
+ from ophyd_async.epics.areadetector.writers import HDFWriter, NDFileHDF
8
+
9
+
10
+ class VimbaDetector(StandardDetector, HasHints):
11
+ """
12
+ Ophyd-async implementation of an ADVimba Detector.
13
+ """
14
+
15
+ _controller: VimbaController
16
+ _writer: HDFWriter
17
+
18
+ def __init__(
19
+ self,
20
+ prefix: str,
21
+ directory_provider: DirectoryProvider,
22
+ drv_suffix="cam1:",
23
+ hdf_suffix="HDF1:",
24
+ name="",
25
+ ):
26
+ self.drv = VimbaDriver(prefix + drv_suffix)
27
+ self.hdf = NDFileHDF(prefix + hdf_suffix)
28
+
29
+ super().__init__(
30
+ VimbaController(self.drv),
31
+ HDFWriter(
32
+ self.hdf,
33
+ directory_provider,
34
+ lambda: self.name,
35
+ ADBaseShapeProvider(self.drv),
36
+ ),
37
+ config_sigs=(self.drv.acquire_time,),
38
+ name=name,
39
+ )
40
+
41
+ @property
42
+ def hints(self) -> Hints:
43
+ return self._writer.hints
@@ -0,0 +1,5 @@
1
+ from .hdf_writer import HDFWriter
2
+ from .nd_file_hdf import NDFileHDF
3
+ from .nd_plugin import NDPluginBase, NDPluginStats
4
+
5
+ __all__ = ["HDFWriter", "NDFileHDF", "NDPluginBase", "NDPluginStats"]
@@ -0,0 +1,10 @@
1
+ from dataclasses import dataclass
2
+ from typing import Sequence
3
+
4
+
5
+ @dataclass
6
+ class _HDFDataset:
7
+ name: str
8
+ path: str
9
+ shape: Sequence[int]
10
+ multiplier: int