ophyd-async 0.3.4a1__py3-none-any.whl → 0.5.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 (103) hide show
  1. ophyd_async/_version.py +2 -2
  2. ophyd_async/core/__init__.py +86 -63
  3. ophyd_async/core/{detector.py → _detector.py} +18 -23
  4. ophyd_async/core/{device.py → _device.py} +19 -7
  5. ophyd_async/core/{device_save_loader.py → _device_save_loader.py} +3 -3
  6. ophyd_async/core/{flyer.py → _flyer.py} +6 -8
  7. ophyd_async/core/_hdf_dataset.py +97 -0
  8. ophyd_async/{log.py → core/_log.py} +11 -3
  9. ophyd_async/core/{mock_signal_backend.py → _mock_signal_backend.py} +3 -3
  10. ophyd_async/core/{mock_signal_utils.py → _mock_signal_utils.py} +3 -4
  11. ophyd_async/{protocols.py → core/_protocol.py} +1 -1
  12. ophyd_async/core/_providers.py +186 -24
  13. ophyd_async/core/{standard_readable.py → _readable.py} +6 -16
  14. ophyd_async/core/{signal.py → _signal.py} +39 -16
  15. ophyd_async/core/{signal_backend.py → _signal_backend.py} +4 -13
  16. ophyd_async/core/{soft_signal_backend.py → _soft_signal_backend.py} +24 -18
  17. ophyd_async/core/{async_status.py → _status.py} +3 -11
  18. ophyd_async/epics/adaravis/__init__.py +9 -0
  19. ophyd_async/epics/{areadetector/aravis.py → adaravis/_aravis.py} +12 -14
  20. ophyd_async/epics/{areadetector/controllers/aravis_controller.py → adaravis/_aravis_controller.py} +8 -10
  21. ophyd_async/epics/{areadetector/drivers/aravis_driver.py → adaravis/_aravis_io.py} +6 -3
  22. ophyd_async/epics/adcore/__init__.py +36 -0
  23. ophyd_async/epics/adcore/_core_io.py +114 -0
  24. ophyd_async/epics/{areadetector/drivers/ad_base.py → adcore/_core_logic.py} +17 -52
  25. ophyd_async/epics/{areadetector/writers/hdf_writer.py → adcore/_hdf_writer.py} +36 -18
  26. ophyd_async/epics/{areadetector/single_trigger_det.py → adcore/_single_trigger.py} +5 -6
  27. ophyd_async/epics/{areadetector/utils.py → adcore/_utils.py} +29 -0
  28. ophyd_async/epics/adkinetix/__init__.py +9 -0
  29. ophyd_async/epics/{areadetector/kinetix.py → adkinetix/_kinetix.py} +12 -14
  30. ophyd_async/epics/{areadetector/controllers/kinetix_controller.py → adkinetix/_kinetix_controller.py} +6 -9
  31. ophyd_async/epics/{areadetector/drivers/kinetix_driver.py → adkinetix/_kinetix_io.py} +5 -4
  32. ophyd_async/epics/adpilatus/__init__.py +11 -0
  33. ophyd_async/epics/{areadetector/pilatus.py → adpilatus/_pilatus.py} +12 -16
  34. ophyd_async/epics/{areadetector/controllers/pilatus_controller.py → adpilatus/_pilatus_controller.py} +14 -16
  35. ophyd_async/epics/{areadetector/drivers/pilatus_driver.py → adpilatus/_pilatus_io.py} +5 -3
  36. ophyd_async/epics/adsimdetector/__init__.py +7 -0
  37. ophyd_async/epics/adsimdetector/_sim.py +34 -0
  38. ophyd_async/epics/{areadetector/controllers/ad_sim_controller.py → adsimdetector/_sim_controller.py} +8 -14
  39. ophyd_async/epics/advimba/__init__.py +9 -0
  40. ophyd_async/epics/advimba/_vimba.py +43 -0
  41. ophyd_async/epics/{areadetector/controllers/vimba_controller.py → advimba/_vimba_controller.py} +6 -14
  42. ophyd_async/epics/{areadetector/drivers/vimba_driver.py → advimba/_vimba_io.py} +5 -4
  43. ophyd_async/epics/demo/__init__.py +9 -132
  44. ophyd_async/epics/demo/_mover.py +97 -0
  45. ophyd_async/epics/demo/_sensor.py +36 -0
  46. ophyd_async/epics/motor.py +228 -0
  47. ophyd_async/epics/pvi/__init__.py +2 -2
  48. ophyd_async/epics/pvi/{pvi.py → _pvi.py} +17 -14
  49. ophyd_async/epics/signal/__init__.py +7 -1
  50. ophyd_async/epics/{_backend → signal}/_aioca.py +6 -2
  51. ophyd_async/epics/{_backend/common.py → signal/_common.py} +4 -2
  52. ophyd_async/epics/signal/_epics_transport.py +3 -3
  53. ophyd_async/epics/{_backend → signal}/_p4p.py +53 -4
  54. ophyd_async/epics/signal/{signal.py → _signal.py} +10 -9
  55. ophyd_async/fastcs/odin/__init__.py +0 -0
  56. ophyd_async/{panda → fastcs/panda}/__init__.py +28 -9
  57. ophyd_async/{panda → fastcs/panda}/_common_blocks.py +24 -3
  58. ophyd_async/{panda → fastcs/panda}/_hdf_panda.py +6 -9
  59. ophyd_async/{panda/writers → fastcs/panda}/_hdf_writer.py +24 -14
  60. ophyd_async/{panda → fastcs/panda}/_panda_controller.py +2 -1
  61. ophyd_async/{panda → fastcs/panda}/_table.py +20 -18
  62. ophyd_async/fastcs/panda/_trigger.py +90 -0
  63. ophyd_async/plan_stubs/__init__.py +2 -2
  64. ophyd_async/plan_stubs/_ensure_connected.py +26 -0
  65. ophyd_async/plan_stubs/{fly.py → _fly.py} +67 -12
  66. ophyd_async/sim/__init__.py +0 -11
  67. ophyd_async/sim/demo/__init__.py +18 -2
  68. ophyd_async/sim/demo/_pattern_detector/__init__.py +13 -0
  69. ophyd_async/sim/demo/_pattern_detector/_pattern_detector.py +42 -0
  70. ophyd_async/sim/{sim_pattern_detector_control.py → demo/_pattern_detector/_pattern_detector_controller.py} +6 -7
  71. ophyd_async/sim/{sim_pattern_detector_writer.py → demo/_pattern_detector/_pattern_detector_writer.py} +12 -8
  72. ophyd_async/sim/demo/_pattern_detector/_pattern_generator.py +211 -0
  73. ophyd_async/sim/demo/{sim_motor.py → _sim_motor.py} +7 -5
  74. ophyd_async/sim/testing/__init__.py +0 -0
  75. ophyd_async/tango/__init__.py +0 -0
  76. {ophyd_async-0.3.4a1.dist-info → ophyd_async-0.5.0.dist-info}/METADATA +7 -2
  77. ophyd_async-0.5.0.dist-info/RECORD +89 -0
  78. {ophyd_async-0.3.4a1.dist-info → ophyd_async-0.5.0.dist-info}/WHEEL +1 -1
  79. ophyd_async/epics/areadetector/__init__.py +0 -23
  80. ophyd_async/epics/areadetector/controllers/__init__.py +0 -5
  81. ophyd_async/epics/areadetector/drivers/__init__.py +0 -23
  82. ophyd_async/epics/areadetector/vimba.py +0 -43
  83. ophyd_async/epics/areadetector/writers/__init__.py +0 -5
  84. ophyd_async/epics/areadetector/writers/_hdfdataset.py +0 -10
  85. ophyd_async/epics/areadetector/writers/_hdffile.py +0 -54
  86. ophyd_async/epics/areadetector/writers/nd_file_hdf.py +0 -40
  87. ophyd_async/epics/areadetector/writers/nd_plugin.py +0 -38
  88. ophyd_async/epics/demo/demo_ad_sim_detector.py +0 -35
  89. ophyd_async/epics/motion/__init__.py +0 -3
  90. ophyd_async/epics/motion/motor.py +0 -97
  91. ophyd_async/panda/_trigger.py +0 -39
  92. ophyd_async/panda/writers/__init__.py +0 -3
  93. ophyd_async/panda/writers/_panda_hdf_file.py +0 -54
  94. ophyd_async/plan_stubs/ensure_connected.py +0 -22
  95. ophyd_async/sim/pattern_generator.py +0 -318
  96. ophyd_async/sim/sim_pattern_generator.py +0 -35
  97. ophyd_async-0.3.4a1.dist-info/RECORD +0 -86
  98. /ophyd_async/core/{utils.py → _utils.py} +0 -0
  99. /ophyd_async/{epics/_backend → fastcs}/__init__.py +0 -0
  100. /ophyd_async/{panda → fastcs/panda}/_utils.py +0 -0
  101. {ophyd_async-0.3.4a1.dist-info → ophyd_async-0.5.0.dist-info}/LICENSE +0 -0
  102. {ophyd_async-0.3.4a1.dist-info → ophyd_async-0.5.0.dist-info}/entry_points.txt +0 -0
  103. {ophyd_async-0.3.4a1.dist-info → ophyd_async-0.5.0.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
1
  from enum import Enum
2
2
 
3
- from ...signal import epics_signal_r, epics_signal_rw_rbv
4
- from .ad_base import ADBase
3
+ from ophyd_async.epics import adcore
4
+ from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw_rbv
5
5
 
6
6
 
7
7
  class PilatusTriggerMode(str, Enum):
@@ -12,7 +12,9 @@ class PilatusTriggerMode(str, Enum):
12
12
  alignment = "Alignment"
13
13
 
14
14
 
15
- class PilatusDriver(ADBase):
15
+ class PilatusDriverIO(adcore.ADBaseIO):
16
+ """This mirrors the interface provided by ADPilatus/db/pilatus.template."""
17
+
16
18
  def __init__(self, prefix: str, name: str = "") -> None:
17
19
  self.trigger_mode = epics_signal_rw_rbv(
18
20
  PilatusTriggerMode, prefix + "TriggerMode"
@@ -0,0 +1,7 @@
1
+ from ._sim import SimDetector
2
+ from ._sim_controller import SimController
3
+
4
+ __all__ = [
5
+ "SimDetector",
6
+ "SimController",
7
+ ]
@@ -0,0 +1,34 @@
1
+ from typing import Sequence
2
+
3
+ from ophyd_async.core import PathProvider, SignalR, StandardDetector
4
+ from ophyd_async.epics import adcore
5
+
6
+ from ._sim_controller import SimController
7
+
8
+
9
+ class SimDetector(StandardDetector):
10
+ _controller: SimController
11
+ _writer: adcore.ADHDFWriter
12
+
13
+ def __init__(
14
+ self,
15
+ drv: adcore.ADBaseIO,
16
+ hdf: adcore.NDFileHDFIO,
17
+ path_provider: PathProvider,
18
+ name: str = "",
19
+ config_sigs: Sequence[SignalR] = (),
20
+ ):
21
+ self.drv = drv
22
+ self.hdf = hdf
23
+
24
+ super().__init__(
25
+ SimController(self.drv),
26
+ adcore.ADHDFWriter(
27
+ self.hdf,
28
+ path_provider,
29
+ lambda: self.name,
30
+ adcore.ADBaseShapeProvider(self.drv),
31
+ ),
32
+ config_sigs=config_sigs,
33
+ name=name,
34
+ )
@@ -7,20 +7,14 @@ from ophyd_async.core import (
7
7
  DetectorControl,
8
8
  DetectorTrigger,
9
9
  )
10
-
11
- from ..drivers.ad_base import (
12
- DEFAULT_GOOD_STATES,
13
- ADBase,
14
- DetectorState,
15
- ImageMode,
16
- start_acquiring_driver_and_ensure_status,
17
- )
18
- from ..utils import stop_busy_record
10
+ from ophyd_async.epics import adcore
19
11
 
20
12
 
21
- class ADSimController(DetectorControl):
13
+ class SimController(DetectorControl):
22
14
  def __init__(
23
- self, driver: ADBase, good_states: Set[DetectorState] = set(DEFAULT_GOOD_STATES)
15
+ self,
16
+ driver: adcore.ADBaseIO,
17
+ good_states: Set[adcore.DetectorState] = set(adcore.DEFAULT_GOOD_STATES),
24
18
  ) -> None:
25
19
  self.driver = driver
26
20
  self.good_states = good_states
@@ -40,13 +34,13 @@ class ADSimController(DetectorControl):
40
34
  frame_timeout = DEFAULT_TIMEOUT + await self.driver.acquire_time.get_value()
41
35
  await asyncio.gather(
42
36
  self.driver.num_images.set(num),
43
- self.driver.image_mode.set(ImageMode.multiple),
37
+ self.driver.image_mode.set(adcore.ImageMode.multiple),
44
38
  )
45
- return await start_acquiring_driver_and_ensure_status(
39
+ return await adcore.start_acquiring_driver_and_ensure_status(
46
40
  self.driver, good_states=self.good_states, timeout=frame_timeout
47
41
  )
48
42
 
49
43
  async def disarm(self):
50
44
  # We can't use caput callback as we already used it in arm() and we can't have
51
45
  # 2 or they will deadlock
52
- await stop_busy_record(self.driver.acquire, False, timeout=1)
46
+ await adcore.stop_busy_record(self.driver.acquire, False, timeout=1)
@@ -0,0 +1,9 @@
1
+ from ._vimba import VimbaDetector
2
+ from ._vimba_controller import VimbaController
3
+ from ._vimba_io import VimbaDriverIO
4
+
5
+ __all__ = [
6
+ "VimbaDetector",
7
+ "VimbaController",
8
+ "VimbaDriverIO",
9
+ ]
@@ -0,0 +1,43 @@
1
+ from bluesky.protocols import HasHints, Hints
2
+
3
+ from ophyd_async.core import PathProvider, StandardDetector
4
+ from ophyd_async.epics import adcore
5
+
6
+ from ._vimba_controller import VimbaController
7
+ from ._vimba_io import VimbaDriverIO
8
+
9
+
10
+ class VimbaDetector(StandardDetector, HasHints):
11
+ """
12
+ Ophyd-async implementation of an ADVimba Detector.
13
+ """
14
+
15
+ _controller: VimbaController
16
+ _writer: adcore.ADHDFWriter
17
+
18
+ def __init__(
19
+ self,
20
+ prefix: str,
21
+ path_provider: PathProvider,
22
+ drv_suffix="cam1:",
23
+ hdf_suffix="HDF1:",
24
+ name="",
25
+ ):
26
+ self.drv = VimbaDriverIO(prefix + drv_suffix)
27
+ self.hdf = adcore.NDFileHDFIO(prefix + hdf_suffix)
28
+
29
+ super().__init__(
30
+ VimbaController(self.drv),
31
+ adcore.ADHDFWriter(
32
+ self.hdf,
33
+ path_provider,
34
+ lambda: self.name,
35
+ adcore.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,17 +2,9 @@ import asyncio
2
2
  from typing import Optional
3
3
 
4
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
- )
5
+ from ophyd_async.epics import adcore
8
6
 
9
- from ..drivers.vimba_driver import (
10
- VimbaDriver,
11
- VimbaExposeOutMode,
12
- VimbaOnOff,
13
- VimbaTriggerSource,
14
- )
15
- from ..utils import ImageMode, stop_busy_record
7
+ from ._vimba_io import VimbaDriverIO, VimbaExposeOutMode, VimbaOnOff, VimbaTriggerSource
16
8
 
17
9
  TRIGGER_MODE = {
18
10
  DetectorTrigger.internal: VimbaOnOff.off,
@@ -32,7 +24,7 @@ EXPOSE_OUT_MODE = {
32
24
  class VimbaController(DetectorControl):
33
25
  def __init__(
34
26
  self,
35
- driver: VimbaDriver,
27
+ driver: VimbaDriverIO,
36
28
  ) -> None:
37
29
  self._drv = driver
38
30
 
@@ -49,7 +41,7 @@ class VimbaController(DetectorControl):
49
41
  self._drv.trigger_mode.set(TRIGGER_MODE[trigger]),
50
42
  self._drv.expose_mode.set(EXPOSE_OUT_MODE[trigger]),
51
43
  self._drv.num_images.set(num),
52
- self._drv.image_mode.set(ImageMode.multiple),
44
+ self._drv.image_mode.set(adcore.ImageMode.multiple),
53
45
  )
54
46
  if exposure is not None and trigger not in [
55
47
  DetectorTrigger.variable_gate,
@@ -60,7 +52,7 @@ class VimbaController(DetectorControl):
60
52
  self._drv.trig_source.set(VimbaTriggerSource.line1)
61
53
  else:
62
54
  self._drv.trig_source.set(VimbaTriggerSource.freerun)
63
- return await start_acquiring_driver_and_ensure_status(self._drv)
55
+ return await adcore.start_acquiring_driver_and_ensure_status(self._drv)
64
56
 
65
57
  async def disarm(self):
66
- await stop_busy_record(self._drv.acquire, False, timeout=1)
58
+ await adcore.stop_busy_record(self._drv.acquire, False, timeout=1)
@@ -1,8 +1,7 @@
1
1
  from enum import Enum
2
2
 
3
- from ophyd_async.epics.signal.signal import epics_signal_rw_rbv
4
-
5
- from .ad_base import ADBase
3
+ from ophyd_async.epics import adcore
4
+ from ophyd_async.epics.signal import epics_signal_rw_rbv
6
5
 
7
6
 
8
7
  class VimbaPixelFormat(str, Enum):
@@ -46,7 +45,9 @@ class VimbaExposeOutMode(str, Enum):
46
45
  trigger_width = "TriggerWidth" # Expose for length of high signal
47
46
 
48
47
 
49
- class VimbaDriver(ADBase):
48
+ class VimbaDriverIO(adcore.ADBaseIO):
49
+ """This mirrors the interface provided by ADVimba/db/vimba.template."""
50
+
50
51
  def __init__(self, prefix: str, name: str = "") -> None:
51
52
  # self.pixel_format = epics_signal_rw_rbv(PixelFormat, prefix + "PixelFormat")
52
53
  self.convert_format = epics_signal_rw_rbv(
@@ -1,145 +1,22 @@
1
1
  """Demo EPICS Devices for the tutorial"""
2
2
 
3
- import asyncio
4
3
  import atexit
5
4
  import random
6
5
  import string
7
6
  import subprocess
8
7
  import sys
9
- from enum import Enum
10
8
  from pathlib import Path
11
9
 
12
- import numpy as np
13
- from bluesky.protocols import Movable, Stoppable
10
+ from ._mover import Mover, SampleStage
11
+ from ._sensor import EnergyMode, Sensor, SensorGroup
14
12
 
15
- from ophyd_async.core import (
16
- ConfigSignal,
17
- Device,
18
- DeviceVector,
19
- HintedSignal,
20
- StandardReadable,
21
- WatchableAsyncStatus,
22
- observe_value,
23
- )
24
- from ophyd_async.core.async_status import AsyncStatus
25
- from ophyd_async.core.utils import (
26
- DEFAULT_TIMEOUT,
27
- CalculatableTimeout,
28
- CalculateTimeout,
29
- WatcherUpdate,
30
- )
31
-
32
- from ..signal.signal import epics_signal_r, epics_signal_rw, epics_signal_x
33
-
34
-
35
- class EnergyMode(str, Enum):
36
- """Energy mode for `Sensor`"""
37
-
38
- #: Low energy mode
39
- low = "Low Energy"
40
- #: High energy mode
41
- high = "High Energy"
42
-
43
-
44
- class Sensor(StandardReadable):
45
- """A demo sensor that produces a scalar value based on X and Y Movers"""
46
-
47
- def __init__(self, prefix: str, name="") -> None:
48
- # Define some signals
49
- with self.add_children_as_readables(HintedSignal):
50
- self.value = epics_signal_r(float, prefix + "Value")
51
- with self.add_children_as_readables(ConfigSignal):
52
- self.mode = epics_signal_rw(EnergyMode, prefix + "Mode")
53
-
54
- super().__init__(name=name)
55
-
56
-
57
- class SensorGroup(StandardReadable):
58
- def __init__(self, prefix: str, name: str = "", sensor_count: int = 3) -> None:
59
- with self.add_children_as_readables():
60
- self.sensors = DeviceVector(
61
- {i: Sensor(f"{prefix}{i}:") for i in range(1, sensor_count + 1)}
62
- )
63
-
64
- super().__init__(name)
65
-
66
-
67
- class Mover(StandardReadable, Movable, Stoppable):
68
- """A demo movable that moves based on velocity"""
69
-
70
- def __init__(self, prefix: str, name="") -> None:
71
- # Define some signals
72
- with self.add_children_as_readables(HintedSignal):
73
- self.readback = epics_signal_r(float, prefix + "Readback")
74
- with self.add_children_as_readables(ConfigSignal):
75
- self.velocity = epics_signal_rw(float, prefix + "Velocity")
76
- self.units = epics_signal_r(str, prefix + "Readback.EGU")
77
- self.setpoint = epics_signal_rw(float, prefix + "Setpoint")
78
- self.precision = epics_signal_r(int, prefix + "Readback.PREC")
79
- # Signals that collide with standard methods should have a trailing underscore
80
- self.stop_ = epics_signal_x(prefix + "Stop.PROC")
81
- # Whether set() should complete successfully or not
82
- self._set_success = True
83
-
84
- super().__init__(name=name)
85
-
86
- def set_name(self, name: str):
87
- super().set_name(name)
88
- # Readback should be named the same as its parent in read()
89
- self.readback.set_name(name)
90
-
91
- @WatchableAsyncStatus.wrap
92
- async def set(
93
- self, new_position: float, timeout: CalculatableTimeout = CalculateTimeout
94
- ):
95
- self._set_success = True
96
- old_position, units, precision, velocity = await asyncio.gather(
97
- self.setpoint.get_value(),
98
- self.units.get_value(),
99
- self.precision.get_value(),
100
- self.velocity.get_value(),
101
- )
102
- if timeout is CalculateTimeout:
103
- assert velocity > 0, "Mover has zero velocity"
104
- timeout = abs(new_position - old_position) / velocity + DEFAULT_TIMEOUT
105
- # Make an Event that will be set on completion, and a Status that will
106
- # error if not done in time
107
- done = asyncio.Event()
108
- done_status = AsyncStatus(asyncio.wait_for(done.wait(), timeout))
109
- # Wait for the value to set, but don't wait for put completion callback
110
- await self.setpoint.set(new_position, wait=False)
111
- async for current_position in observe_value(
112
- self.readback, done_status=done_status
113
- ):
114
- yield WatcherUpdate(
115
- current=current_position,
116
- initial=old_position,
117
- target=new_position,
118
- name=self.name,
119
- unit=units,
120
- precision=precision,
121
- )
122
- if np.isclose(current_position, new_position):
123
- done.set()
124
- break
125
- if not self._set_success:
126
- raise RuntimeError("Motor was stopped")
127
-
128
- async def stop(self, success=True):
129
- self._set_success = success
130
- status = self.stop_.trigger()
131
- await status
132
-
133
-
134
- class SampleStage(Device):
135
- """A demo sample stage with X and Y movables"""
136
-
137
- def __init__(self, prefix: str, name="") -> None:
138
- # Define some child Devices
139
- self.x = Mover(prefix + "X:")
140
- self.y = Mover(prefix + "Y:")
141
- # Set name of device and child devices
142
- super().__init__(name=name)
13
+ __all__ = [
14
+ "Mover",
15
+ "SampleStage",
16
+ "EnergyMode",
17
+ "Sensor",
18
+ "SensorGroup",
19
+ ]
143
20
 
144
21
 
145
22
  def start_ioc_subprocess() -> str:
@@ -0,0 +1,97 @@
1
+ import asyncio
2
+
3
+ import numpy as np
4
+ from bluesky.protocols import Movable, Stoppable
5
+
6
+ from ophyd_async.core import (
7
+ DEFAULT_TIMEOUT,
8
+ AsyncStatus,
9
+ CalculatableTimeout,
10
+ CalculateTimeout,
11
+ ConfigSignal,
12
+ Device,
13
+ HintedSignal,
14
+ StandardReadable,
15
+ WatchableAsyncStatus,
16
+ WatcherUpdate,
17
+ observe_value,
18
+ )
19
+ from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw, epics_signal_x
20
+
21
+
22
+ class Mover(StandardReadable, Movable, Stoppable):
23
+ """A demo movable that moves based on velocity"""
24
+
25
+ def __init__(self, prefix: str, name="") -> None:
26
+ # Define some signals
27
+ with self.add_children_as_readables(HintedSignal):
28
+ self.readback = epics_signal_r(float, prefix + "Readback")
29
+ with self.add_children_as_readables(ConfigSignal):
30
+ self.velocity = epics_signal_rw(float, prefix + "Velocity")
31
+ self.units = epics_signal_r(str, prefix + "Readback.EGU")
32
+ self.setpoint = epics_signal_rw(float, prefix + "Setpoint")
33
+ self.precision = epics_signal_r(int, prefix + "Readback.PREC")
34
+ # Signals that collide with standard methods should have a trailing underscore
35
+ self.stop_ = epics_signal_x(prefix + "Stop.PROC")
36
+ # Whether set() should complete successfully or not
37
+ self._set_success = True
38
+
39
+ super().__init__(name=name)
40
+
41
+ def set_name(self, name: str):
42
+ super().set_name(name)
43
+ # Readback should be named the same as its parent in read()
44
+ self.readback.set_name(name)
45
+
46
+ @WatchableAsyncStatus.wrap
47
+ async def set(
48
+ self, new_position: float, timeout: CalculatableTimeout = CalculateTimeout
49
+ ):
50
+ self._set_success = True
51
+ old_position, units, precision, velocity = await asyncio.gather(
52
+ self.setpoint.get_value(),
53
+ self.units.get_value(),
54
+ self.precision.get_value(),
55
+ self.velocity.get_value(),
56
+ )
57
+ if timeout is CalculateTimeout:
58
+ assert velocity > 0, "Mover has zero velocity"
59
+ timeout = abs(new_position - old_position) / velocity + DEFAULT_TIMEOUT
60
+ # Make an Event that will be set on completion, and a Status that will
61
+ # error if not done in time
62
+ done = asyncio.Event()
63
+ done_status = AsyncStatus(asyncio.wait_for(done.wait(), timeout))
64
+ # Wait for the value to set, but don't wait for put completion callback
65
+ await self.setpoint.set(new_position, wait=False)
66
+ async for current_position in observe_value(
67
+ self.readback, done_status=done_status
68
+ ):
69
+ yield WatcherUpdate(
70
+ current=current_position,
71
+ initial=old_position,
72
+ target=new_position,
73
+ name=self.name,
74
+ unit=units,
75
+ precision=precision,
76
+ )
77
+ if np.isclose(current_position, new_position):
78
+ done.set()
79
+ break
80
+ if not self._set_success:
81
+ raise RuntimeError("Motor was stopped")
82
+
83
+ async def stop(self, success=True):
84
+ self._set_success = success
85
+ status = self.stop_.trigger()
86
+ await status
87
+
88
+
89
+ class SampleStage(Device):
90
+ """A demo sample stage with X and Y movables"""
91
+
92
+ def __init__(self, prefix: str, name="") -> None:
93
+ # Define some child Devices
94
+ self.x = Mover(prefix + "X:")
95
+ self.y = Mover(prefix + "Y:")
96
+ # Set name of device and child devices
97
+ super().__init__(name=name)
@@ -0,0 +1,36 @@
1
+ from enum import Enum
2
+
3
+ from ophyd_async.core import ConfigSignal, DeviceVector, HintedSignal, StandardReadable
4
+ from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw
5
+
6
+
7
+ class EnergyMode(str, Enum):
8
+ """Energy mode for `Sensor`"""
9
+
10
+ #: Low energy mode
11
+ low = "Low Energy"
12
+ #: High energy mode
13
+ high = "High Energy"
14
+
15
+
16
+ class Sensor(StandardReadable):
17
+ """A demo sensor that produces a scalar value based on X and Y Movers"""
18
+
19
+ def __init__(self, prefix: str, name="") -> None:
20
+ # Define some signals
21
+ with self.add_children_as_readables(HintedSignal):
22
+ self.value = epics_signal_r(float, prefix + "Value")
23
+ with self.add_children_as_readables(ConfigSignal):
24
+ self.mode = epics_signal_rw(EnergyMode, prefix + "Mode")
25
+
26
+ super().__init__(name=name)
27
+
28
+
29
+ class SensorGroup(StandardReadable):
30
+ def __init__(self, prefix: str, name: str = "", sensor_count: int = 3) -> None:
31
+ with self.add_children_as_readables():
32
+ self.sensors = DeviceVector(
33
+ {i: Sensor(f"{prefix}{i}:") for i in range(1, sensor_count + 1)}
34
+ )
35
+
36
+ super().__init__(name)