ophyd-async 0.3a2__py3-none-any.whl → 0.3a3__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 (40) hide show
  1. ophyd_async/_version.py +1 -1
  2. ophyd_async/core/__init__.py +11 -1
  3. ophyd_async/core/detector.py +8 -9
  4. ophyd_async/core/flyer.py +2 -2
  5. ophyd_async/core/signal.py +102 -7
  6. ophyd_async/core/signal_backend.py +2 -2
  7. ophyd_async/core/sim_signal_backend.py +6 -6
  8. ophyd_async/core/standard_readable.py +211 -24
  9. ophyd_async/epics/_backend/_aioca.py +6 -6
  10. ophyd_async/epics/_backend/_p4p.py +17 -11
  11. ophyd_async/epics/areadetector/__init__.py +4 -0
  12. ophyd_async/epics/areadetector/aravis.py +7 -9
  13. ophyd_async/epics/areadetector/controllers/__init__.py +2 -1
  14. ophyd_async/epics/areadetector/controllers/kinetix_controller.py +49 -0
  15. ophyd_async/epics/areadetector/controllers/vimba_controller.py +66 -0
  16. ophyd_async/epics/areadetector/drivers/__init__.py +6 -0
  17. ophyd_async/epics/areadetector/drivers/kinetix_driver.py +24 -0
  18. ophyd_async/epics/areadetector/drivers/vimba_driver.py +58 -0
  19. ophyd_async/epics/areadetector/kinetix.py +46 -0
  20. ophyd_async/epics/areadetector/pilatus.py +7 -12
  21. ophyd_async/epics/areadetector/single_trigger_det.py +14 -6
  22. ophyd_async/epics/areadetector/vimba.py +43 -0
  23. ophyd_async/epics/areadetector/writers/hdf_writer.py +6 -3
  24. ophyd_async/epics/areadetector/writers/nd_file_hdf.py +1 -0
  25. ophyd_async/epics/demo/__init__.py +19 -22
  26. ophyd_async/epics/motion/motor.py +11 -12
  27. ophyd_async/epics/signal/signal.py +1 -1
  28. ophyd_async/log.py +130 -0
  29. ophyd_async/panda/writers/_hdf_writer.py +3 -3
  30. ophyd_async/protocols.py +26 -3
  31. ophyd_async/sim/demo/sim_motor.py +11 -9
  32. ophyd_async/sim/pattern_generator.py +4 -4
  33. ophyd_async/sim/sim_pattern_detector_writer.py +2 -2
  34. ophyd_async/sim/sim_pattern_generator.py +2 -2
  35. {ophyd_async-0.3a2.dist-info → ophyd_async-0.3a3.dist-info}/METADATA +20 -3
  36. {ophyd_async-0.3a2.dist-info → ophyd_async-0.3a3.dist-info}/RECORD +40 -33
  37. {ophyd_async-0.3a2.dist-info → ophyd_async-0.3a3.dist-info}/LICENSE +0 -0
  38. {ophyd_async-0.3a2.dist-info → ophyd_async-0.3a3.dist-info}/WHEEL +0 -0
  39. {ophyd_async-0.3a2.dist-info → ophyd_async-0.3a3.dist-info}/entry_points.txt +0 -0
  40. {ophyd_async-0.3a2.dist-info → ophyd_async-0.3a3.dist-info}/top_level.txt +0 -0
@@ -6,7 +6,7 @@ from dataclasses import dataclass
6
6
  from enum import Enum
7
7
  from typing import Any, Dict, List, Optional, Sequence, Type, Union
8
8
 
9
- from bluesky.protocols import Descriptor, Dtype, Reading
9
+ from bluesky.protocols import DataKey, Dtype, Reading
10
10
  from p4p import Value
11
11
  from p4p.client.asyncio import Context, Subscription
12
12
 
@@ -55,7 +55,7 @@ class PvaConverter:
55
55
  "alarm_severity": -1 if sv > 2 else sv,
56
56
  }
57
57
 
58
- def descriptor(self, source: str, value) -> Descriptor:
58
+ def get_datakey(self, source: str, value) -> DataKey:
59
59
  dtype = specifier_to_dtype[value.type().aspy("value")]
60
60
  return {"source": source, "dtype": dtype, "shape": []}
61
61
 
@@ -73,7 +73,7 @@ class PvaConverter:
73
73
 
74
74
 
75
75
  class PvaArrayConverter(PvaConverter):
76
- def descriptor(self, source: str, value) -> Descriptor:
76
+ def get_datakey(self, source: str, value) -> DataKey:
77
77
  return {"source": source, "dtype": "array", "shape": [len(value["value"])]}
78
78
 
79
79
 
@@ -96,7 +96,7 @@ class PvaNDArrayConverter(PvaConverter):
96
96
  dims = self._get_dimensions(value)
97
97
  return value["value"].reshape(dims)
98
98
 
99
- def descriptor(self, source: str, value) -> Descriptor:
99
+ def get_datakey(self, source: str, value) -> DataKey:
100
100
  dims = self._get_dimensions(value)
101
101
  return {"source": source, "dtype": "array", "shape": dims}
102
102
 
@@ -120,7 +120,7 @@ class PvaEnumConverter(PvaConverter):
120
120
  def value(self, value):
121
121
  return list(self.enum_class)[value["value"]["index"]]
122
122
 
123
- def descriptor(self, source: str, value) -> Descriptor:
123
+ def get_datakey(self, source: str, value) -> DataKey:
124
124
  choices = [e.value for e in self.enum_class]
125
125
  return {"source": source, "dtype": "string", "shape": [], "choices": choices}
126
126
 
@@ -129,7 +129,7 @@ class PvaEnumBoolConverter(PvaConverter):
129
129
  def value(self, value):
130
130
  return value["value"]["index"]
131
131
 
132
- def descriptor(self, source: str, value) -> Descriptor:
132
+ def get_datakey(self, source: str, value) -> DataKey:
133
133
  return {"source": source, "dtype": "integer", "shape": []}
134
134
 
135
135
 
@@ -137,7 +137,7 @@ class PvaTableConverter(PvaConverter):
137
137
  def value(self, value):
138
138
  return value["value"].todict()
139
139
 
140
- def descriptor(self, source: str, value) -> Descriptor:
140
+ def get_datakey(self, source: str, value) -> DataKey:
141
141
  # This is wrong, but defer until we know how to actually describe a table
142
142
  return {"source": source, "dtype": "object", "shape": []} # type: ignore
143
143
 
@@ -152,7 +152,7 @@ class PvaDictConverter(PvaConverter):
152
152
  def value(self, value: Value):
153
153
  return value.todict()
154
154
 
155
- def descriptor(self, source: str, value) -> Descriptor:
155
+ def get_datakey(self, source: str, value) -> DataKey:
156
156
  raise NotImplementedError("Describing Dict signals not currently supported")
157
157
 
158
158
  def metadata_fields(self) -> List[str]:
@@ -216,7 +216,13 @@ def make_converter(datatype: Optional[Type], values: Dict[str, Any]) -> PvaConve
216
216
  )
217
217
  return PvaEnumConverter(get_supported_enum_class(pv, datatype, pv_choices))
218
218
  elif "NTScalar" in typeid:
219
- if datatype and not issubclass(typ, datatype):
219
+ if (
220
+ datatype
221
+ and not issubclass(typ, datatype)
222
+ and not (
223
+ typ is float and datatype is int
224
+ ) # Allow float -> int since prec can be 0
225
+ ):
220
226
  raise TypeError(f"{pv} has type {typ.__name__} not {datatype.__name__}")
221
227
  return PvaConverter()
222
228
  elif "NTTable" in typeid:
@@ -293,9 +299,9 @@ class PvaSignalBackend(SignalBackend[T]):
293
299
  )
294
300
  raise NotConnected(f"pva://{self.write_pv}") from exc
295
301
 
296
- async def get_descriptor(self, source: str) -> Descriptor:
302
+ async def get_datakey(self, source: str) -> DataKey:
297
303
  value = await self.ctxt.get(self.read_pv)
298
- return self.converter.descriptor(source, value)
304
+ return self.converter.get_datakey(source, value)
299
305
 
300
306
  def _pva_request_string(self, fields: List[str]) -> str:
301
307
  """
@@ -1,4 +1,5 @@
1
1
  from .aravis import AravisDetector
2
+ from .kinetix import KinetixDetector
2
3
  from .pilatus import PilatusDetector
3
4
  from .single_trigger_det import SingleTriggerDet
4
5
  from .utils import (
@@ -9,9 +10,12 @@ from .utils import (
9
10
  ad_r,
10
11
  ad_rw,
11
12
  )
13
+ from .vimba import VimbaDetector
12
14
 
13
15
  __all__ = [
14
16
  "AravisDetector",
17
+ "KinetixDetector",
18
+ "VimbaDetector",
15
19
  "SingleTriggerDet",
16
20
  "FileWriteMode",
17
21
  "ImageMode",
@@ -23,16 +23,15 @@ class AravisDetector(StandardDetector, HasHints):
23
23
 
24
24
  def __init__(
25
25
  self,
26
- name: str,
26
+ prefix: str,
27
27
  directory_provider: DirectoryProvider,
28
- driver: AravisDriver,
29
- hdf: NDFileHDF,
28
+ drv_suffix="cam1:",
29
+ hdf_suffix="HDF1:",
30
+ name="",
30
31
  gpio_number: AravisController.GPIO_NUMBER = 1,
31
- **scalar_sigs: str,
32
32
  ):
33
- # Must be child of Detector to pick up connect()
34
- self.drv = driver
35
- self.hdf = hdf
33
+ self.drv = AravisDriver(prefix + drv_suffix)
34
+ self.hdf = NDFileHDF(prefix + hdf_suffix)
36
35
 
37
36
  super().__init__(
38
37
  AravisController(self.drv, gpio_number=gpio_number),
@@ -41,9 +40,8 @@ class AravisDetector(StandardDetector, HasHints):
41
40
  directory_provider,
42
41
  lambda: self.name,
43
42
  ADBaseShapeProvider(self.drv),
44
- **scalar_sigs,
45
43
  ),
46
- config_sigs=(self.drv.acquire_time, self.drv.acquire),
44
+ config_sigs=(self.drv.acquire_time,),
47
45
  name=name,
48
46
  )
49
47
 
@@ -1,4 +1,5 @@
1
1
  from .ad_sim_controller import ADSimController
2
+ from .aravis_controller import AravisController
2
3
  from .pilatus_controller import PilatusController
3
4
 
4
- __all__ = ["PilatusController", "ADSimController"]
5
+ __all__ = ["PilatusController", "ADSimController", "AravisController"]
@@ -0,0 +1,49 @@
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.kinetix_driver import KinetixDriver, KinetixTriggerMode
10
+ from ..utils import ImageMode, stop_busy_record
11
+
12
+ KINETIX_TRIGGER_MODE_MAP = {
13
+ DetectorTrigger.internal: KinetixTriggerMode.internal,
14
+ DetectorTrigger.constant_gate: KinetixTriggerMode.gate,
15
+ DetectorTrigger.variable_gate: KinetixTriggerMode.gate,
16
+ DetectorTrigger.edge_trigger: KinetixTriggerMode.edge,
17
+ }
18
+
19
+
20
+ class KinetixController(DetectorControl):
21
+ def __init__(
22
+ self,
23
+ driver: KinetixDriver,
24
+ ) -> None:
25
+ self._drv = driver
26
+
27
+ def get_deadtime(self, exposure: float) -> float:
28
+ return 0.001
29
+
30
+ async def arm(
31
+ self,
32
+ num: int,
33
+ trigger: DetectorTrigger = DetectorTrigger.internal,
34
+ exposure: Optional[float] = None,
35
+ ) -> AsyncStatus:
36
+ await asyncio.gather(
37
+ self._drv.trigger_mode.set(KINETIX_TRIGGER_MODE_MAP[trigger]),
38
+ self._drv.num_images.set(num),
39
+ self._drv.image_mode.set(ImageMode.multiple),
40
+ )
41
+ if exposure is not None and trigger not in [
42
+ DetectorTrigger.variable_gate,
43
+ DetectorTrigger.constant_gate,
44
+ ]:
45
+ await self._drv.acquire_time.set(exposure)
46
+ return await start_acquiring_driver_and_ensure_status(self._drv)
47
+
48
+ async def disarm(self):
49
+ await stop_busy_record(self._drv.acquire, False, timeout=1)
@@ -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)
@@ -4,12 +4,18 @@ from .ad_base import (
4
4
  DetectorState,
5
5
  start_acquiring_driver_and_ensure_status,
6
6
  )
7
+ from .aravis_driver import AravisDriver
8
+ from .kinetix_driver import KinetixDriver
7
9
  from .pilatus_driver import PilatusDriver
10
+ from .vimba_driver import VimbaDriver
8
11
 
9
12
  __all__ = [
10
13
  "ADBase",
11
14
  "ADBaseShapeProvider",
12
15
  "PilatusDriver",
16
+ "AravisDriver",
17
+ "KinetixDriver",
18
+ "VimbaDriver",
13
19
  "start_acquiring_driver_and_ensure_status",
14
20
  "DetectorState",
15
21
  ]
@@ -0,0 +1,24 @@
1
+ from enum import Enum
2
+
3
+ from ..utils import ad_rw
4
+ from .ad_base import ADBase
5
+
6
+
7
+ class KinetixTriggerMode(str, Enum):
8
+ internal = "Internal"
9
+ edge = "Rising Edge"
10
+ gate = "Exp. Gate"
11
+
12
+
13
+ class KinetixReadoutMode(str, Enum):
14
+ sensitivity = 1
15
+ speed = 2
16
+ dynamic_range = 3
17
+
18
+
19
+ class KinetixDriver(ADBase):
20
+ def __init__(self, prefix: str, name: str = "") -> None:
21
+ # self.pixel_format = ad_rw(PixelFormat, prefix + "PixelFormat")
22
+ self.trigger_mode = ad_rw(KinetixTriggerMode, prefix + "TriggerMode")
23
+ self.mode = ad_rw(KinetixReadoutMode, prefix + "ReadoutPortIdx")
24
+ super().__init__(prefix, name)
@@ -0,0 +1,58 @@
1
+ from enum import Enum
2
+
3
+ from ..utils import ad_rw
4
+ from .ad_base import ADBase
5
+
6
+
7
+ class VimbaPixelFormat(str, Enum):
8
+ internal = "Mono8"
9
+ ext_enable = "Mono12"
10
+ ext_trigger = "Ext. Trigger"
11
+ mult_trigger = "Mult. Trigger"
12
+ alignment = "Alignment"
13
+
14
+
15
+ class VimbaConvertFormat(str, Enum):
16
+ none = "None"
17
+ mono8 = "Mono8"
18
+ mono16 = "Mono16"
19
+ rgb8 = "RGB8"
20
+ rgb16 = "RGB16"
21
+
22
+
23
+ class VimbaTriggerSource(str, Enum):
24
+ freerun = "Freerun"
25
+ line1 = "Line1"
26
+ line2 = "Line2"
27
+ fixed_rate = "FixedRate"
28
+ software = "Software"
29
+ action0 = "Action0"
30
+ action1 = "Action1"
31
+
32
+
33
+ class VimbaOverlap(str, Enum):
34
+ off = "Off"
35
+ prev_frame = "PreviousFrame"
36
+
37
+
38
+ class VimbaOnOff(str, Enum):
39
+ on = "On"
40
+ off = "Off"
41
+
42
+
43
+ class VimbaExposeOutMode(str, Enum):
44
+ timed = "Timed" # Use ExposureTime PV
45
+ trigger_width = "TriggerWidth" # Expose for length of high signal
46
+
47
+
48
+ class VimbaDriver(ADBase):
49
+ def __init__(self, prefix: str, name: str = "") -> None:
50
+ # self.pixel_format = ad_rw(PixelFormat, prefix + "PixelFormat")
51
+ self.convert_format = ad_rw(
52
+ VimbaConvertFormat, prefix + "ConvertPixelFormat"
53
+ ) # Pixel format of data outputted to AD
54
+ self.trig_source = ad_rw(VimbaTriggerSource, prefix + "TriggerSource")
55
+ self.trigger_mode = ad_rw(VimbaOnOff, prefix + "TriggerMode")
56
+ self.overlap = ad_rw(VimbaOverlap, prefix + "TriggerOverlap")
57
+ self.expose_mode = ad_rw(VimbaExposeOutMode, prefix + "ExposureMode")
58
+ 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
@@ -1,10 +1,7 @@
1
- from typing import Optional, Sequence
2
-
3
1
  from bluesky.protocols import Hints
4
2
 
5
3
  from ophyd_async.core import DirectoryProvider
6
4
  from ophyd_async.core.detector import StandardDetector
7
- from ophyd_async.core.signal import SignalR
8
5
  from ophyd_async.epics.areadetector.controllers.pilatus_controller import (
9
6
  PilatusController,
10
7
  )
@@ -22,15 +19,14 @@ class PilatusDetector(StandardDetector):
22
19
 
23
20
  def __init__(
24
21
  self,
25
- name: str,
22
+ prefix: str,
26
23
  directory_provider: DirectoryProvider,
27
- driver: PilatusDriver,
28
- hdf: NDFileHDF,
29
- config_sigs: Optional[Sequence[SignalR]] = None,
30
- **scalar_sigs: str,
24
+ drv_suffix="cam1:",
25
+ hdf_suffix="HDF1:",
26
+ name="",
31
27
  ):
32
- self.drv = driver
33
- self.hdf = hdf
28
+ self.drv = PilatusDriver(prefix + drv_suffix)
29
+ self.hdf = NDFileHDF(prefix + hdf_suffix)
34
30
 
35
31
  super().__init__(
36
32
  PilatusController(self.drv),
@@ -39,9 +35,8 @@ class PilatusDetector(StandardDetector):
39
35
  directory_provider,
40
36
  lambda: self.name,
41
37
  ADBaseShapeProvider(self.drv),
42
- **scalar_sigs,
43
38
  ),
44
- config_sigs=config_sigs or (self.drv.acquire_time,),
39
+ config_sigs=(self.drv.acquire_time,),
45
40
  name=name,
46
41
  )
47
42
 
@@ -3,7 +3,13 @@ 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
14
  from .drivers.ad_base import ADBase
9
15
  from .utils import ImageMode
@@ -20,12 +26,14 @@ class SingleTriggerDet(StandardReadable, Triggerable):
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
@@ -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
@@ -2,7 +2,7 @@ import asyncio
2
2
  from pathlib import Path
3
3
  from typing import AsyncGenerator, AsyncIterator, Dict, List, Optional
4
4
 
5
- from bluesky.protocols import Descriptor, Hints, StreamAsset
5
+ from bluesky.protocols import DataKey, Hints, StreamAsset
6
6
 
7
7
  from ophyd_async.core import (
8
8
  DEFAULT_TIMEOUT,
@@ -40,7 +40,7 @@ class HDFWriter(DetectorWriter):
40
40
  self._file: Optional[_HDFFile] = None
41
41
  self._multiplier = 1
42
42
 
43
- async def open(self, multiplier: int = 1) -> Dict[str, Descriptor]:
43
+ async def open(self, multiplier: int = 1) -> Dict[str, DataKey]:
44
44
  self._file = None
45
45
  info = self._directory_provider()
46
46
  await asyncio.gather(
@@ -52,6 +52,9 @@ class HDFWriter(DetectorWriter):
52
52
  self.hdf.file_name.set(f"{info.prefix}{self.hdf.name}{info.suffix}"),
53
53
  self.hdf.file_template.set("%s/%s.h5"),
54
54
  self.hdf.file_write_mode.set(FileWriteMode.stream),
55
+ # Never use custom xml layout file but use the one defined
56
+ # in the source code file NDFileHDF5LayoutXML.cpp
57
+ self.hdf.xml_file_name.set(""),
55
58
  )
56
59
 
57
60
  assert (
@@ -81,7 +84,7 @@ class HDFWriter(DetectorWriter):
81
84
  )
82
85
  )
83
86
  describe = {
84
- ds.name: Descriptor(
87
+ ds.name: DataKey(
85
88
  source=self.hdf.full_file_name.source,
86
89
  shape=outer_shape + tuple(ds.shape),
87
90
  dtype="array" if ds.shape else "number",
@@ -36,4 +36,5 @@ class NDFileHDF(NDPluginBase):
36
36
  self.flush_now = epics_signal_rw(bool, prefix + "FlushNow")
37
37
  self.array_size0 = ad_r(int, prefix + "ArraySize0")
38
38
  self.array_size1 = ad_r(int, prefix + "ArraySize1")
39
+ self.xml_file_name = ad_rw(str, prefix + "XMLFileName")
39
40
  super().__init__(prefix, name)
@@ -16,8 +16,10 @@ from bluesky.protocols import Movable, Stoppable
16
16
 
17
17
  from ophyd_async.core import (
18
18
  AsyncStatus,
19
+ ConfigSignal,
19
20
  Device,
20
21
  DeviceVector,
22
+ HintedSignal,
21
23
  StandardReadable,
22
24
  observe_value,
23
25
  )
@@ -39,26 +41,21 @@ class Sensor(StandardReadable):
39
41
 
40
42
  def __init__(self, prefix: str, name="") -> None:
41
43
  # Define some signals
42
- self.value = epics_signal_r(float, prefix + "Value")
43
- self.mode = epics_signal_rw(EnergyMode, prefix + "Mode")
44
- # Set name and signals for read() and read_configuration()
45
- self.set_readable_signals(
46
- read=[self.value],
47
- config=[self.mode],
48
- )
44
+ with self.add_children_as_readables(HintedSignal):
45
+ self.value = epics_signal_r(float, prefix + "Value")
46
+ with self.add_children_as_readables(ConfigSignal):
47
+ self.mode = epics_signal_rw(EnergyMode, prefix + "Mode")
48
+
49
49
  super().__init__(name=name)
50
50
 
51
51
 
52
52
  class SensorGroup(StandardReadable):
53
53
  def __init__(self, prefix: str, name: str = "", sensor_count: int = 3) -> None:
54
- self.sensors = DeviceVector(
55
- {i: Sensor(f"{prefix}{i}:") for i in range(1, sensor_count + 1)}
56
- )
54
+ with self.add_children_as_readables():
55
+ self.sensors = DeviceVector(
56
+ {i: Sensor(f"{prefix}{i}:") for i in range(1, sensor_count + 1)}
57
+ )
57
58
 
58
- # Makes read() produce the values of all sensors
59
- self.set_readable_signals(
60
- read=[sensor.value for sensor in self.sensors.values()],
61
- )
62
59
  super().__init__(name)
63
60
 
64
61
 
@@ -67,20 +64,20 @@ class Mover(StandardReadable, Movable, Stoppable):
67
64
 
68
65
  def __init__(self, prefix: str, name="") -> None:
69
66
  # Define some signals
67
+ with self.add_children_as_readables(HintedSignal):
68
+ self.readback = epics_signal_r(float, prefix + "Readback")
69
+
70
+ with self.add_children_as_readables(ConfigSignal):
71
+ self.velocity = epics_signal_rw(float, prefix + "Velocity")
72
+ self.units = epics_signal_r(str, prefix + "Readback.EGU")
73
+
70
74
  self.setpoint = epics_signal_rw(float, prefix + "Setpoint")
71
- self.readback = epics_signal_r(float, prefix + "Readback")
72
- self.velocity = epics_signal_rw(float, prefix + "Velocity")
73
- self.units = epics_signal_r(str, prefix + "Readback.EGU")
74
75
  self.precision = epics_signal_r(int, prefix + "Readback.PREC")
75
76
  # Signals that collide with standard methods should have a trailing underscore
76
77
  self.stop_ = epics_signal_x(prefix + "Stop.PROC")
77
78
  # Whether set() should complete successfully or not
78
79
  self._set_success = True
79
- # Set name and signals for read() and read_configuration()
80
- self.set_readable_signals(
81
- read=[self.readback],
82
- config=[self.velocity, self.units],
83
- )
80
+
84
81
  super().__init__(name=name)
85
82
 
86
83
  def set_name(self, name: str):