ophyd-async 0.3a3__py3-none-any.whl → 0.3a5__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 +26 -11
  3. ophyd_async/core/async_status.py +96 -33
  4. ophyd_async/core/detector.py +22 -28
  5. ophyd_async/core/device.py +50 -14
  6. ophyd_async/core/mock_signal_backend.py +85 -0
  7. ophyd_async/core/mock_signal_utils.py +149 -0
  8. ophyd_async/core/signal.py +93 -53
  9. ophyd_async/core/{sim_signal_backend.py → soft_signal_backend.py} +23 -33
  10. ophyd_async/core/utils.py +19 -0
  11. ophyd_async/epics/_backend/_aioca.py +11 -7
  12. ophyd_async/epics/_backend/_p4p.py +11 -7
  13. ophyd_async/epics/_backend/common.py +17 -17
  14. ophyd_async/epics/areadetector/__init__.py +0 -4
  15. ophyd_async/epics/areadetector/aravis.py +1 -5
  16. ophyd_async/epics/areadetector/controllers/aravis_controller.py +6 -1
  17. ophyd_async/epics/areadetector/drivers/ad_base.py +12 -10
  18. ophyd_async/epics/areadetector/drivers/aravis_driver.py +6 -122
  19. ophyd_async/epics/areadetector/drivers/kinetix_driver.py +7 -4
  20. ophyd_async/epics/areadetector/drivers/pilatus_driver.py +5 -2
  21. ophyd_async/epics/areadetector/drivers/vimba_driver.py +12 -7
  22. ophyd_async/epics/areadetector/utils.py +2 -12
  23. ophyd_async/epics/areadetector/writers/nd_file_hdf.py +21 -19
  24. ophyd_async/epics/areadetector/writers/nd_plugin.py +6 -7
  25. ophyd_async/epics/demo/__init__.py +27 -32
  26. ophyd_async/epics/motion/motor.py +40 -37
  27. ophyd_async/epics/pvi/pvi.py +13 -13
  28. ophyd_async/epics/signal/__init__.py +8 -1
  29. ophyd_async/panda/_hdf_panda.py +3 -3
  30. ophyd_async/planstubs/__init__.py +5 -1
  31. ophyd_async/planstubs/ensure_connected.py +22 -0
  32. ophyd_async/protocols.py +32 -2
  33. ophyd_async/sim/demo/sim_motor.py +50 -35
  34. ophyd_async/sim/pattern_generator.py +5 -5
  35. {ophyd_async-0.3a3.dist-info → ophyd_async-0.3a5.dist-info}/METADATA +2 -2
  36. {ophyd_async-0.3a3.dist-info → ophyd_async-0.3a5.dist-info}/RECORD +40 -37
  37. {ophyd_async-0.3a3.dist-info → ophyd_async-0.3a5.dist-info}/LICENSE +0 -0
  38. {ophyd_async-0.3a3.dist-info → ophyd_async-0.3a5.dist-info}/WHEEL +0 -0
  39. {ophyd_async-0.3a3.dist-info → ophyd_async-0.3a5.dist-info}/entry_points.txt +0 -0
  40. {ophyd_async-0.3a3.dist-info → ophyd_async-0.3a5.dist-info}/top_level.txt +0 -0
@@ -20,7 +20,7 @@ from ophyd_async.core import (
20
20
  )
21
21
  from ophyd_async.core.utils import DEFAULT_TIMEOUT, NotConnected
22
22
 
23
- from .common import get_supported_enum_class
23
+ from .common import get_supported_values
24
24
 
25
25
  # https://mdavidsaver.github.io/p4p/values.html
26
26
  specifier_to_dtype: Dict[str, Dtype] = {
@@ -109,7 +109,8 @@ class PvaNDArrayConverter(PvaConverter):
109
109
 
110
110
  @dataclass
111
111
  class PvaEnumConverter(PvaConverter):
112
- enum_class: Type[Enum]
112
+ def __init__(self, choices: dict[str, str]):
113
+ self.choices = tuple(choices.values())
113
114
 
114
115
  def write_value(self, value: Union[Enum, str]):
115
116
  if isinstance(value, Enum):
@@ -118,11 +119,15 @@ class PvaEnumConverter(PvaConverter):
118
119
  return value
119
120
 
120
121
  def value(self, value):
121
- return list(self.enum_class)[value["value"]["index"]]
122
+ return self.choices[value["value"]["index"]]
122
123
 
123
124
  def get_datakey(self, source: str, value) -> DataKey:
124
- choices = [e.value for e in self.enum_class]
125
- return {"source": source, "dtype": "string", "shape": [], "choices": choices}
125
+ return {
126
+ "source": source,
127
+ "dtype": "string",
128
+ "shape": [],
129
+ "choices": list(self.choices),
130
+ }
126
131
 
127
132
 
128
133
  class PvaEnumBoolConverter(PvaConverter):
@@ -214,7 +219,7 @@ def make_converter(datatype: Optional[Type], values: Dict[str, Any]) -> PvaConve
214
219
  pv_choices = get_unique(
215
220
  {k: tuple(v["value"]["choices"]) for k, v in values.items()}, "choices"
216
221
  )
217
- return PvaEnumConverter(get_supported_enum_class(pv, datatype, pv_choices))
222
+ return PvaEnumConverter(get_supported_values(pv, datatype, pv_choices))
218
223
  elif "NTScalar" in typeid:
219
224
  if (
220
225
  datatype
@@ -244,7 +249,6 @@ class PvaSignalBackend(SignalBackend[T]):
244
249
  self.converter: PvaConverter = DisconnectedPvaConverter()
245
250
  self.subscription: Optional[Subscription] = None
246
251
 
247
- @property
248
252
  def source(self, name: str):
249
253
  return f"pva://{self.read_pv}"
250
254
 
@@ -1,25 +1,25 @@
1
1
  from enum import Enum
2
- from typing import Any, Optional, Tuple, Type
2
+ from typing import Dict, Optional, Tuple, Type
3
3
 
4
4
 
5
- def get_supported_enum_class(
5
+ def get_supported_values(
6
6
  pv: str,
7
- datatype: Optional[Type[Enum]],
8
- pv_choices: Tuple[Any, ...],
9
- ) -> Type[Enum]:
7
+ datatype: Optional[Type[str]],
8
+ pv_choices: Tuple[str, ...],
9
+ ) -> Dict[str, str]:
10
10
  if not datatype:
11
- return Enum("GeneratedChoices", {x or "_": x for x in pv_choices}, type=str) # type: ignore
11
+ return {x: x or "_" for x in pv_choices}
12
12
 
13
- if not issubclass(datatype, Enum):
14
- raise TypeError(f"{pv} has type Enum not {datatype.__name__}")
15
13
  if not issubclass(datatype, str):
16
- raise TypeError(f"{pv} has type Enum but doesn't inherit from String")
17
- choices = tuple(v.value for v in datatype)
18
- if set(choices) != set(pv_choices):
19
- raise TypeError(
20
- (
21
- f"{pv} has choices {pv_choices}, "
22
- f"which do not match {datatype}, which has {choices}"
14
+ raise TypeError(f"{pv} is type Enum but doesn't inherit from String")
15
+ if issubclass(datatype, Enum):
16
+ choices = tuple(v.value for v in datatype)
17
+ if set(choices) != set(pv_choices):
18
+ raise TypeError(
19
+ (
20
+ f"{pv} has choices {pv_choices}, "
21
+ f"which do not match {datatype}, which has {choices}"
22
+ )
23
23
  )
24
- )
25
- return datatype
24
+ return {x: datatype(x) for x in pv_choices}
25
+ return {x: x for x in pv_choices}
@@ -7,8 +7,6 @@ from .utils import (
7
7
  ImageMode,
8
8
  NDAttributeDataType,
9
9
  NDAttributesXML,
10
- ad_r,
11
- ad_rw,
12
10
  )
13
11
  from .vimba import VimbaDetector
14
12
 
@@ -19,8 +17,6 @@ __all__ = [
19
17
  "SingleTriggerDet",
20
18
  "FileWriteMode",
21
19
  "ImageMode",
22
- "ad_r",
23
- "ad_rw",
24
20
  "NDAttributeDataType",
25
21
  "NDAttributesXML",
26
22
  "PilatusDetector",
@@ -2,7 +2,7 @@ from typing import get_args
2
2
 
3
3
  from bluesky.protocols import HasHints, Hints
4
4
 
5
- from ophyd_async.core import DirectoryProvider, StandardDetector, TriggerInfo
5
+ from ophyd_async.core import DirectoryProvider, StandardDetector
6
6
  from ophyd_async.epics.areadetector.controllers.aravis_controller import (
7
7
  AravisController,
8
8
  )
@@ -45,10 +45,6 @@ class AravisDetector(StandardDetector, HasHints):
45
45
  name=name,
46
46
  )
47
47
 
48
- async def _prepare(self, value: TriggerInfo) -> None:
49
- await self.drv.fetch_deadtime()
50
- await super()._prepare(value)
51
-
52
48
  def get_external_trigger_gpio(self):
53
49
  return self._controller.gpio_number
54
50
 
@@ -14,6 +14,11 @@ from ophyd_async.epics.areadetector.drivers.aravis_driver import (
14
14
  )
15
15
  from ophyd_async.epics.areadetector.utils import ImageMode, stop_busy_record
16
16
 
17
+ # The deadtime of an ADaravis controller varies depending on the exact model of camera.
18
+ # Ideally we would maximize performance by dynamically retrieving the deadtime at
19
+ # runtime. See https://github.com/bluesky/ophyd-async/issues/308
20
+ _HIGHEST_POSSIBLE_DEADTIME = 1961e-6
21
+
17
22
 
18
23
  class AravisController(DetectorControl):
19
24
  GPIO_NUMBER = Literal[1, 2, 3, 4]
@@ -23,7 +28,7 @@ class AravisController(DetectorControl):
23
28
  self.gpio_number = gpio_number
24
29
 
25
30
  def get_deadtime(self, exposure: float) -> float:
26
- return self._drv.dead_time or 0
31
+ return _HIGHEST_POSSIBLE_DEADTIME
27
32
 
28
33
  async def arm(
29
34
  self,
@@ -9,8 +9,8 @@ from ophyd_async.core import (
9
9
  set_and_wait_for_value,
10
10
  )
11
11
 
12
- from ...signal.signal import epics_signal_rw
13
- from ..utils import ImageMode, ad_r, ad_rw
12
+ from ...signal.signal import epics_signal_r, epics_signal_rw, epics_signal_rw_rbv
13
+ from ..utils import ImageMode
14
14
  from ..writers.nd_plugin import NDArrayBase
15
15
 
16
16
 
@@ -43,14 +43,16 @@ DEFAULT_GOOD_STATES: FrozenSet[DetectorState] = frozenset(
43
43
  class ADBase(NDArrayBase):
44
44
  def __init__(self, prefix: str, name: str = "") -> None:
45
45
  # Define some signals
46
- self.acquire = ad_rw(bool, prefix + "Acquire")
47
- self.acquire_time = ad_rw(float, prefix + "AcquireTime")
48
- self.num_images = ad_rw(int, prefix + "NumImages")
49
- self.image_mode = ad_rw(ImageMode, prefix + "ImageMode")
50
- self.array_counter = ad_rw(int, prefix + "ArrayCounter")
51
- self.array_size_x = ad_r(int, prefix + "ArraySizeX")
52
- self.array_size_y = ad_r(int, prefix + "ArraySizeY")
53
- self.detector_state = ad_r(DetectorState, prefix + "DetectorState")
46
+ self.acquire = epics_signal_rw_rbv(bool, prefix + "Acquire")
47
+ self.acquire_time = epics_signal_rw_rbv(float, prefix + "AcquireTime")
48
+ self.num_images = epics_signal_rw_rbv(int, prefix + "NumImages")
49
+ self.image_mode = epics_signal_rw_rbv(ImageMode, prefix + "ImageMode")
50
+ self.array_counter = epics_signal_rw_rbv(int, prefix + "ArrayCounter")
51
+ self.array_size_x = epics_signal_r(int, prefix + "ArraySizeX_RBV")
52
+ self.array_size_y = epics_signal_r(int, prefix + "ArraySizeY_RBV")
53
+ self.detector_state = epics_signal_r(
54
+ DetectorState, prefix + "DetectorState_RBV"
55
+ )
54
56
  # There is no _RBV for this one
55
57
  self.wait_for_plugins = epics_signal_rw(bool, prefix + "WaitForPlugins")
56
58
  super().__init__(prefix, name=name)
@@ -1,8 +1,8 @@
1
1
  from enum import Enum
2
- from typing import Callable, Dict, Literal, Optional, Tuple
2
+ from typing import Literal
3
3
 
4
4
  from ophyd_async.epics.areadetector.drivers import ADBase
5
- from ophyd_async.epics.areadetector.utils import ad_r, ad_rw
5
+ from ophyd_async.epics.signal.signal import epics_signal_rw_rbv
6
6
 
7
7
 
8
8
  class AravisTriggerMode(str, Enum):
@@ -22,113 +22,6 @@ class AravisTriggerMode(str, Enum):
22
22
  AravisTriggerSource = Literal["Freerun", "Line1", "Line2", "Line3", "Line4"]
23
23
 
24
24
 
25
- def _reverse_lookup(
26
- model_deadtimes: Dict[float, Tuple[str, ...]],
27
- ) -> Callable[[str], float]:
28
- def inner(pixel_format: str, model_name: str) -> float:
29
- for deadtime, pixel_formats in model_deadtimes.items():
30
- if pixel_format in pixel_formats:
31
- return deadtime
32
- raise ValueError(
33
- f"Model {model_name} does not have a defined deadtime "
34
- f"for pixel format {pixel_format}"
35
- )
36
-
37
- return inner
38
-
39
-
40
- _deadtimes: Dict[str, Callable[[str, str], float]] = {
41
- # cite: https://cdn.alliedvision.com/fileadmin/content/documents/products/cameras/Manta/techman/Manta_TechMan.pdf retrieved 2024-04-05 # noqa: E501
42
- "Manta G-125": lambda _, __: 63e-6,
43
- "Manta G-145": lambda _, __: 106e-6,
44
- "Manta G-235": _reverse_lookup(
45
- {
46
- 118e-6: (
47
- "Mono8",
48
- "Mono12Packed",
49
- "BayerRG8",
50
- "BayerRG12",
51
- "BayerRG12Packed",
52
- "YUV411Packed",
53
- ),
54
- 256e-6: ("Mono12", "BayerRG12", "YUV422Packed"),
55
- 390e-6: ("RGB8Packed", "BGR8Packed", "YUV444Packed"),
56
- }
57
- ),
58
- "Manta G-895": _reverse_lookup(
59
- {
60
- 404e-6: (
61
- "Mono8",
62
- "Mono12Packed",
63
- "BayerRG8",
64
- "BayerRG12Packed",
65
- "YUV411Packed",
66
- ),
67
- 542e-6: ("Mono12", "BayerRG12", "YUV422Packed"),
68
- 822e-6: ("RGB8Packed", "BGR8Packed", "YUV444Packed"),
69
- }
70
- ),
71
- "Manta G-2460": _reverse_lookup(
72
- {
73
- 979e-6: (
74
- "Mono8",
75
- "Mono12Packed",
76
- "BayerRG8",
77
- "BayerRG12Packed",
78
- "YUV411Packed",
79
- ),
80
- 1304e-6: ("Mono12", "BayerRG12", "YUV422Packed"),
81
- 1961e-6: ("RGB8Packed", "BGR8Packed", "YUV444Packed"),
82
- }
83
- ),
84
- # cite: https://cdn.alliedvision.com/fileadmin/content/documents/products/cameras/various/appnote/GigE/GigE-Cameras_AppNote_PIV-Min-Time-Between-Exposures.pdf retrieved 2024-04-05 # noqa: E501
85
- "Manta G-609": lambda _, __: 47e-6,
86
- # cite: https://cdn.alliedvision.com/fileadmin/content/documents/products/cameras/Mako/techman/Mako_TechMan_en.pdf retrieved 2024-04-05 # noqa: E501
87
- "Mako G-040": _reverse_lookup(
88
- {
89
- 101e-6: (
90
- "Mono8",
91
- "Mono12Packed",
92
- "BayerRG8",
93
- "BayerRG12Packed",
94
- "YUV411Packed",
95
- ),
96
- 140e-6: ("Mono12", "BayerRG12", "YUV422Packed"),
97
- 217e-6: ("RGB8Packed", "BGR8Packed", "YUV444Packed"),
98
- }
99
- ),
100
- "Mako G-125": lambda _, __: 70e-6,
101
- # Assume 12 bits: 10 bits = 275e-6
102
- "Mako G-234": _reverse_lookup(
103
- {
104
- 356e-6: (
105
- "Mono8",
106
- "BayerRG8",
107
- "BayerRG12",
108
- "BayerRG12Packed",
109
- "YUV411Packed",
110
- "YUV422Packed",
111
- ),
112
- # Assume 12 bits: 10 bits = 563e-6
113
- 726e-6: ("RGB8Packed", "BRG8Packed", "YUV444Packed"),
114
- }
115
- ),
116
- "Mako G-507": _reverse_lookup(
117
- {
118
- 270e-6: (
119
- "Mono8",
120
- "Mono12Packed",
121
- "BayerRG8",
122
- "BayerRG12Packed",
123
- "YUV411Packed",
124
- ),
125
- 363e-6: ("Mono12", "BayerRG12", "YUV422Packed"),
126
- 554e-6: ("RGB8Packed", "BGR8Packed", "YUV444Packed"),
127
- }
128
- ),
129
- }
130
-
131
-
132
25
  class AravisDriver(ADBase):
133
26
  # If instantiating a new instance, ensure it is supported in the _deadtimes dict
134
27
  """Generic Driver supporting the Manta and Mako drivers.
@@ -138,17 +31,8 @@ class AravisDriver(ADBase):
138
31
  """
139
32
 
140
33
  def __init__(self, prefix: str, name: str = "") -> None:
141
- self.trigger_mode = ad_rw(AravisTriggerMode, prefix + "TriggerMode")
142
- self.trigger_source = ad_rw(str, prefix + "TriggerSource")
143
- self.model = ad_r(str, prefix + "Model")
144
- self.pixel_format = ad_rw(str, prefix + "PixelFormat")
145
- self.dead_time: Optional[float] = 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")
146
38
  super().__init__(prefix, name=name)
147
-
148
- async def fetch_deadtime(self) -> None:
149
- # All known in-use version B/C have same deadtime as non-B/C
150
- model: str = (await self.model.get_value()).removesuffix("B").removesuffix("C")
151
- if model not in _deadtimes:
152
- raise ValueError(f"Model {model} does not have defined deadtimes")
153
- pixel_format: str = await self.pixel_format.get_value()
154
- self.dead_time = _deadtimes.get(model)(pixel_format, model)
@@ -1,6 +1,7 @@
1
1
  from enum import Enum
2
2
 
3
- from ..utils import ad_rw
3
+ from ophyd_async.epics.signal.signal import epics_signal_rw_rbv
4
+
4
5
  from .ad_base import ADBase
5
6
 
6
7
 
@@ -18,7 +19,9 @@ class KinetixReadoutMode(str, Enum):
18
19
 
19
20
  class KinetixDriver(ADBase):
20
21
  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")
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")
24
27
  super().__init__(prefix, name)
@@ -1,6 +1,7 @@
1
1
  from enum import Enum
2
2
 
3
- from ..utils import ad_rw
3
+ from ophyd_async.epics.signal.signal import epics_signal_rw_rbv
4
+
4
5
  from .ad_base import ADBase
5
6
 
6
7
 
@@ -14,5 +15,7 @@ class PilatusTriggerMode(str, Enum):
14
15
 
15
16
  class PilatusDriver(ADBase):
16
17
  def __init__(self, prefix: str, name: str = "") -> None:
17
- self.trigger_mode = ad_rw(PilatusTriggerMode, prefix + "TriggerMode")
18
+ self.trigger_mode = epics_signal_rw_rbv(
19
+ PilatusTriggerMode, prefix + "TriggerMode"
20
+ )
18
21
  super().__init__(prefix, name)
@@ -1,6 +1,7 @@
1
1
  from enum import Enum
2
2
 
3
- from ..utils import ad_rw
3
+ from ophyd_async.epics.signal.signal import epics_signal_rw_rbv
4
+
4
5
  from .ad_base import ADBase
5
6
 
6
7
 
@@ -47,12 +48,16 @@ class VimbaExposeOutMode(str, Enum):
47
48
 
48
49
  class VimbaDriver(ADBase):
49
50
  def __init__(self, prefix: str, name: str = "") -> None:
50
- # self.pixel_format = ad_rw(PixelFormat, prefix + "PixelFormat")
51
- self.convert_format = ad_rw(
51
+ # self.pixel_format = epics_signal_rw_rbv(PixelFormat, prefix + "PixelFormat")
52
+ self.convert_format = epics_signal_rw_rbv(
52
53
  VimbaConvertFormat, prefix + "ConvertPixelFormat"
53
54
  ) # 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")
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
+ )
58
63
  super().__init__(prefix, name)
@@ -1,18 +1,8 @@
1
1
  from enum import Enum
2
- from typing import Optional, Type
2
+ from typing import Optional
3
3
  from xml.etree import cElementTree as ET
4
4
 
5
- from ophyd_async.core import DEFAULT_TIMEOUT, SignalR, SignalRW, T, wait_for_value
6
-
7
- from ..signal.signal import epics_signal_r, epics_signal_rw
8
-
9
-
10
- def ad_rw(datatype: Type[T], prefix: str) -> SignalRW[T]:
11
- return epics_signal_rw(datatype, prefix + "_RBV", prefix)
12
-
13
-
14
- def ad_r(datatype: Type[T], prefix: str) -> SignalR[T]:
15
- return epics_signal_r(datatype, prefix + "_RBV")
5
+ from ophyd_async.core import DEFAULT_TIMEOUT, SignalRW, T, wait_for_value
16
6
 
17
7
 
18
8
  class FileWriteMode(str, Enum):
@@ -1,7 +1,7 @@
1
1
  from enum import Enum
2
2
 
3
- from ...signal.signal import epics_signal_rw
4
- from ..utils import FileWriteMode, ad_r, ad_rw
3
+ from ...signal.signal import epics_signal_r, epics_signal_rw, epics_signal_rw_rbv
4
+ from ..utils import FileWriteMode
5
5
  from .nd_plugin import NDPluginBase
6
6
 
7
7
 
@@ -19,22 +19,24 @@ class Compression(str, Enum):
19
19
  class NDFileHDF(NDPluginBase):
20
20
  def __init__(self, prefix: str, name="") -> None:
21
21
  # Define some signals
22
- self.position_mode = ad_rw(bool, prefix + "PositionMode")
23
- self.compression = ad_rw(Compression, prefix + "Compression")
24
- self.num_extra_dims = ad_rw(int, prefix + "NumExtraDims")
25
- self.file_path = ad_rw(str, prefix + "FilePath")
26
- self.file_name = ad_rw(str, prefix + "FileName")
27
- self.file_path_exists = ad_r(bool, prefix + "FilePathExists")
28
- self.file_template = ad_rw(str, prefix + "FileTemplate")
29
- self.full_file_name = ad_r(str, prefix + "FullFileName")
30
- self.file_write_mode = ad_rw(FileWriteMode, prefix + "FileWriteMode")
31
- self.num_capture = ad_rw(int, prefix + "NumCapture")
32
- self.num_captured = ad_r(int, prefix + "NumCaptured")
33
- self.swmr_mode = ad_rw(bool, prefix + "SWMRMode")
34
- self.lazy_open = ad_rw(bool, prefix + "LazyOpen")
35
- self.capture = ad_rw(bool, prefix + "Capture")
22
+ self.position_mode = epics_signal_rw_rbv(bool, prefix + "PositionMode")
23
+ self.compression = epics_signal_rw_rbv(Compression, prefix + "Compression")
24
+ self.num_extra_dims = epics_signal_rw_rbv(int, prefix + "NumExtraDims")
25
+ self.file_path = epics_signal_rw_rbv(str, prefix + "FilePath")
26
+ self.file_name = epics_signal_rw_rbv(str, prefix + "FileName")
27
+ self.file_path_exists = epics_signal_r(bool, prefix + "FilePathExists_RBV")
28
+ self.file_template = epics_signal_rw_rbv(str, prefix + "FileTemplate")
29
+ self.full_file_name = epics_signal_r(str, prefix + "FullFileName_RBV")
30
+ self.file_write_mode = epics_signal_rw_rbv(
31
+ FileWriteMode, prefix + "FileWriteMode"
32
+ )
33
+ self.num_capture = epics_signal_rw_rbv(int, prefix + "NumCapture")
34
+ self.num_captured = epics_signal_r(int, prefix + "NumCaptured_RBV")
35
+ self.swmr_mode = epics_signal_rw_rbv(bool, prefix + "SWMRMode")
36
+ self.lazy_open = epics_signal_rw_rbv(bool, prefix + "LazyOpen")
37
+ self.capture = epics_signal_rw_rbv(bool, prefix + "Capture")
36
38
  self.flush_now = epics_signal_rw(bool, prefix + "FlushNow")
37
- self.array_size0 = ad_r(int, prefix + "ArraySize0")
38
- self.array_size1 = ad_r(int, prefix + "ArraySize1")
39
- self.xml_file_name = ad_rw(str, prefix + "XMLFileName")
39
+ self.array_size0 = epics_signal_r(int, prefix + "ArraySize0_RBV")
40
+ self.array_size1 = epics_signal_r(int, prefix + "ArraySize1_RBV")
41
+ self.xml_file_name = epics_signal_rw_rbv(str, prefix + "XMLFileName")
40
42
  super().__init__(prefix, name)
@@ -2,8 +2,7 @@ from enum import Enum
2
2
 
3
3
  from ophyd_async.core import Device
4
4
  from ophyd_async.epics.signal import epics_signal_rw
5
-
6
- from ..utils import ad_r, ad_rw
5
+ from ophyd_async.epics.signal.signal import epics_signal_r, epics_signal_rw_rbv
7
6
 
8
7
 
9
8
  class Callback(str, Enum):
@@ -13,16 +12,16 @@ class Callback(str, Enum):
13
12
 
14
13
  class NDArrayBase(Device):
15
14
  def __init__(self, prefix: str, name: str = "") -> None:
16
- self.unique_id = ad_r(int, prefix + "UniqueId")
15
+ self.unique_id = epics_signal_r(int, prefix + "UniqueId_RBV")
17
16
  self.nd_attributes_file = epics_signal_rw(str, prefix + "NDAttributesFile")
18
- super().__init__(name)
17
+ super().__init__(name=name)
19
18
 
20
19
 
21
20
  class NDPluginBase(NDArrayBase):
22
21
  def __init__(self, prefix: str, name: str = "") -> None:
23
- self.nd_array_port = ad_rw(str, prefix + "NDArrayPort")
24
- self.enable_callback = ad_rw(Callback, prefix + "EnableCallbacks")
25
- self.nd_array_address = ad_rw(int, prefix + "NDArrayAddress")
22
+ self.nd_array_port = epics_signal_rw_rbv(str, prefix + "NDArrayPort")
23
+ self.enable_callback = epics_signal_rw_rbv(Callback, prefix + "EnableCallbacks")
24
+ self.nd_array_address = epics_signal_rw_rbv(int, prefix + "NDArrayAddress")
26
25
  super().__init__(prefix, name)
27
26
 
28
27
 
@@ -6,23 +6,23 @@ import random
6
6
  import string
7
7
  import subprocess
8
8
  import sys
9
- import time
9
+ from dataclasses import replace
10
10
  from enum import Enum
11
11
  from pathlib import Path
12
- from typing import Callable, List, Optional
13
12
 
14
13
  import numpy as np
15
14
  from bluesky.protocols import Movable, Stoppable
16
15
 
17
16
  from ophyd_async.core import (
18
- AsyncStatus,
19
17
  ConfigSignal,
20
18
  Device,
21
19
  DeviceVector,
22
20
  HintedSignal,
23
21
  StandardReadable,
22
+ WatchableAsyncStatus,
24
23
  observe_value,
25
24
  )
25
+ from ophyd_async.core.utils import WatcherUpdate
26
26
 
27
27
  from ..signal.signal import epics_signal_r, epics_signal_rw, epics_signal_x
28
28
 
@@ -85,46 +85,41 @@ class Mover(StandardReadable, Movable, Stoppable):
85
85
  # Readback should be named the same as its parent in read()
86
86
  self.readback.set_name(name)
87
87
 
88
- async def _move(self, new_position: float, watchers: List[Callable] = []):
88
+ async def _move(self, new_position: float):
89
89
  self._set_success = True
90
90
  # time.monotonic won't go backwards in case of NTP corrections
91
- start = time.monotonic()
92
91
  old_position, units, precision = await asyncio.gather(
93
92
  self.setpoint.get_value(),
94
93
  self.units.get_value(),
95
94
  self.precision.get_value(),
96
95
  )
97
96
  # Wait for the value to set, but don't wait for put completion callback
98
- await self.setpoint.set(new_position, wait=False)
99
- async for current_position in observe_value(self.readback):
100
- for watcher in watchers:
101
- watcher(
102
- name=self.name,
103
- current=current_position,
104
- initial=old_position,
105
- target=new_position,
106
- unit=units,
107
- precision=precision,
108
- time_elapsed=time.monotonic() - start,
109
- )
110
- if np.isclose(current_position, new_position):
111
- break
97
+ move_status = self.setpoint.set(new_position, wait=True)
112
98
  if not self._set_success:
113
99
  raise RuntimeError("Motor was stopped")
100
+ # return a template to set() which it can use to yield progress updates
101
+ return (
102
+ WatcherUpdate(
103
+ initial=old_position,
104
+ current=old_position,
105
+ target=new_position,
106
+ unit=units,
107
+ precision=precision,
108
+ ),
109
+ move_status,
110
+ )
114
111
 
115
- def move(self, new_position: float, timeout: Optional[float] = None):
116
- """Commandline only synchronous move of a Motor"""
117
- from bluesky.run_engine import call_in_bluesky_event_loop, in_bluesky_event_loop
118
-
119
- if in_bluesky_event_loop():
120
- raise RuntimeError("Will deadlock run engine if run in a plan")
121
- call_in_bluesky_event_loop(self._move(new_position), timeout) # type: ignore
122
-
123
- # TODO: this fails if we call from the cli, but works if we "ipython await" it
124
- def set(self, new_position: float, timeout: Optional[float] = None) -> AsyncStatus:
125
- watchers: List[Callable] = []
126
- coro = asyncio.wait_for(self._move(new_position, watchers), timeout=timeout)
127
- return AsyncStatus(coro, watchers)
112
+ @WatchableAsyncStatus.wrap # uses the timeout argument from the function it wraps
113
+ async def set(self, new_position: float, timeout: float | None = None):
114
+ update, _ = await self._move(new_position)
115
+ async for current_position in observe_value(self.readback):
116
+ yield replace(
117
+ update,
118
+ name=self.name,
119
+ current=current_position,
120
+ )
121
+ if np.isclose(current_position, new_position):
122
+ break
128
123
 
129
124
  async def stop(self, success=True):
130
125
  self._set_success = success