ophyd-async 0.9.0a1__py3-none-any.whl → 0.10.0a1__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 (157) hide show
  1. ophyd_async/__init__.py +5 -8
  2. ophyd_async/_docs_parser.py +12 -0
  3. ophyd_async/_version.py +9 -4
  4. ophyd_async/core/__init__.py +102 -74
  5. ophyd_async/core/_derived_signal.py +271 -0
  6. ophyd_async/core/_derived_signal_backend.py +300 -0
  7. ophyd_async/core/_detector.py +158 -153
  8. ophyd_async/core/_device.py +143 -115
  9. ophyd_async/core/_device_filler.py +82 -9
  10. ophyd_async/core/_flyer.py +16 -7
  11. ophyd_async/core/_hdf_dataset.py +29 -22
  12. ophyd_async/core/_log.py +14 -23
  13. ophyd_async/core/_mock_signal_backend.py +11 -3
  14. ophyd_async/core/_protocol.py +65 -45
  15. ophyd_async/core/_providers.py +28 -9
  16. ophyd_async/core/_readable.py +74 -58
  17. ophyd_async/core/_settings.py +113 -0
  18. ophyd_async/core/_signal.py +304 -174
  19. ophyd_async/core/_signal_backend.py +60 -14
  20. ophyd_async/core/_soft_signal_backend.py +18 -12
  21. ophyd_async/core/_status.py +72 -24
  22. ophyd_async/core/_table.py +54 -17
  23. ophyd_async/core/_utils.py +101 -52
  24. ophyd_async/core/_yaml_settings.py +66 -0
  25. ophyd_async/epics/__init__.py +1 -0
  26. ophyd_async/epics/adandor/__init__.py +9 -0
  27. ophyd_async/epics/adandor/_andor.py +45 -0
  28. ophyd_async/epics/adandor/_andor_controller.py +51 -0
  29. ophyd_async/epics/adandor/_andor_io.py +34 -0
  30. ophyd_async/epics/adaravis/__init__.py +8 -1
  31. ophyd_async/epics/adaravis/_aravis.py +23 -41
  32. ophyd_async/epics/adaravis/_aravis_controller.py +23 -55
  33. ophyd_async/epics/adaravis/_aravis_io.py +13 -28
  34. ophyd_async/epics/adcore/__init__.py +36 -14
  35. ophyd_async/epics/adcore/_core_detector.py +81 -0
  36. ophyd_async/epics/adcore/_core_io.py +145 -95
  37. ophyd_async/epics/adcore/_core_logic.py +179 -88
  38. ophyd_async/epics/adcore/_core_writer.py +223 -0
  39. ophyd_async/epics/adcore/_hdf_writer.py +51 -92
  40. ophyd_async/epics/adcore/_jpeg_writer.py +26 -0
  41. ophyd_async/epics/adcore/_single_trigger.py +6 -5
  42. ophyd_async/epics/adcore/_tiff_writer.py +26 -0
  43. ophyd_async/epics/adcore/_utils.py +3 -2
  44. ophyd_async/epics/adkinetix/__init__.py +2 -1
  45. ophyd_async/epics/adkinetix/_kinetix.py +32 -27
  46. ophyd_async/epics/adkinetix/_kinetix_controller.py +11 -21
  47. ophyd_async/epics/adkinetix/_kinetix_io.py +12 -13
  48. ophyd_async/epics/adpilatus/__init__.py +7 -2
  49. ophyd_async/epics/adpilatus/_pilatus.py +28 -40
  50. ophyd_async/epics/adpilatus/_pilatus_controller.py +25 -22
  51. ophyd_async/epics/adpilatus/_pilatus_io.py +11 -9
  52. ophyd_async/epics/adsimdetector/__init__.py +8 -1
  53. ophyd_async/epics/adsimdetector/_sim.py +22 -16
  54. ophyd_async/epics/adsimdetector/_sim_controller.py +9 -43
  55. ophyd_async/epics/adsimdetector/_sim_io.py +10 -0
  56. ophyd_async/epics/advimba/__init__.py +10 -1
  57. ophyd_async/epics/advimba/_vimba.py +26 -25
  58. ophyd_async/epics/advimba/_vimba_controller.py +12 -24
  59. ophyd_async/epics/advimba/_vimba_io.py +23 -28
  60. ophyd_async/epics/core/_aioca.py +66 -30
  61. ophyd_async/epics/core/_epics_connector.py +4 -0
  62. ophyd_async/epics/core/_epics_device.py +2 -0
  63. ophyd_async/epics/core/_p4p.py +50 -18
  64. ophyd_async/epics/core/_pvi_connector.py +65 -8
  65. ophyd_async/epics/core/_signal.py +51 -51
  66. ophyd_async/epics/core/_util.py +5 -5
  67. ophyd_async/epics/demo/__init__.py +11 -49
  68. ophyd_async/epics/demo/__main__.py +31 -0
  69. ophyd_async/epics/demo/_ioc.py +32 -0
  70. ophyd_async/epics/demo/_motor.py +82 -0
  71. ophyd_async/epics/demo/_point_detector.py +42 -0
  72. ophyd_async/epics/demo/_point_detector_channel.py +22 -0
  73. ophyd_async/epics/demo/_stage.py +15 -0
  74. ophyd_async/epics/demo/{mover.db → motor.db} +2 -1
  75. ophyd_async/epics/demo/point_detector.db +59 -0
  76. ophyd_async/epics/demo/point_detector_channel.db +21 -0
  77. ophyd_async/epics/eiger/_eiger.py +1 -3
  78. ophyd_async/epics/eiger/_eiger_controller.py +11 -4
  79. ophyd_async/epics/eiger/_eiger_io.py +2 -0
  80. ophyd_async/epics/eiger/_odin_io.py +1 -2
  81. ophyd_async/epics/motor.py +83 -38
  82. ophyd_async/epics/signal.py +4 -1
  83. ophyd_async/epics/testing/__init__.py +14 -14
  84. ophyd_async/epics/testing/_example_ioc.py +68 -73
  85. ophyd_async/epics/testing/_utils.py +19 -44
  86. ophyd_async/epics/testing/test_records.db +16 -0
  87. ophyd_async/epics/testing/test_records_pva.db +17 -16
  88. ophyd_async/fastcs/__init__.py +1 -0
  89. ophyd_async/fastcs/core.py +6 -0
  90. ophyd_async/fastcs/odin/__init__.py +1 -0
  91. ophyd_async/fastcs/panda/__init__.py +8 -8
  92. ophyd_async/fastcs/panda/_block.py +29 -9
  93. ophyd_async/fastcs/panda/_control.py +12 -2
  94. ophyd_async/fastcs/panda/_hdf_panda.py +5 -1
  95. ophyd_async/fastcs/panda/_table.py +13 -7
  96. ophyd_async/fastcs/panda/_trigger.py +23 -9
  97. ophyd_async/fastcs/panda/_writer.py +27 -30
  98. ophyd_async/plan_stubs/__init__.py +16 -0
  99. ophyd_async/plan_stubs/_ensure_connected.py +12 -17
  100. ophyd_async/plan_stubs/_fly.py +3 -5
  101. ophyd_async/plan_stubs/_nd_attributes.py +9 -5
  102. ophyd_async/plan_stubs/_panda.py +14 -0
  103. ophyd_async/plan_stubs/_settings.py +152 -0
  104. ophyd_async/plan_stubs/_utils.py +3 -0
  105. ophyd_async/plan_stubs/_wait_for_awaitable.py +13 -0
  106. ophyd_async/sim/__init__.py +29 -0
  107. ophyd_async/sim/__main__.py +43 -0
  108. ophyd_async/sim/_blob_detector.py +33 -0
  109. ophyd_async/sim/_blob_detector_controller.py +48 -0
  110. ophyd_async/sim/_blob_detector_writer.py +105 -0
  111. ophyd_async/sim/_mirror_horizontal.py +46 -0
  112. ophyd_async/sim/_mirror_vertical.py +74 -0
  113. ophyd_async/sim/_motor.py +233 -0
  114. ophyd_async/sim/_pattern_generator.py +124 -0
  115. ophyd_async/sim/_point_detector.py +86 -0
  116. ophyd_async/sim/_stage.py +19 -0
  117. ophyd_async/tango/__init__.py +1 -0
  118. ophyd_async/tango/core/__init__.py +6 -1
  119. ophyd_async/tango/core/_base_device.py +41 -33
  120. ophyd_async/tango/core/_converters.py +81 -0
  121. ophyd_async/tango/core/_signal.py +21 -33
  122. ophyd_async/tango/core/_tango_readable.py +2 -19
  123. ophyd_async/tango/core/_tango_transport.py +148 -74
  124. ophyd_async/tango/core/_utils.py +47 -0
  125. ophyd_async/tango/demo/_counter.py +2 -0
  126. ophyd_async/tango/demo/_detector.py +2 -0
  127. ophyd_async/tango/demo/_mover.py +10 -6
  128. ophyd_async/tango/demo/_tango/_servers.py +4 -0
  129. ophyd_async/tango/testing/__init__.py +6 -0
  130. ophyd_async/tango/testing/_one_of_everything.py +200 -0
  131. ophyd_async/testing/__init__.py +48 -7
  132. ophyd_async/testing/__pytest_assert_rewrite.py +4 -0
  133. ophyd_async/testing/_assert.py +200 -96
  134. ophyd_async/testing/_mock_signal_utils.py +59 -73
  135. ophyd_async/testing/_one_of_everything.py +146 -0
  136. ophyd_async/testing/_single_derived.py +87 -0
  137. ophyd_async/testing/_utils.py +3 -0
  138. {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.10.0a1.dist-info}/METADATA +25 -26
  139. ophyd_async-0.10.0a1.dist-info/RECORD +149 -0
  140. {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.10.0a1.dist-info}/WHEEL +1 -1
  141. ophyd_async/core/_device_save_loader.py +0 -274
  142. ophyd_async/epics/demo/_mover.py +0 -95
  143. ophyd_async/epics/demo/_sensor.py +0 -37
  144. ophyd_async/epics/demo/sensor.db +0 -19
  145. ophyd_async/fastcs/panda/_utils.py +0 -16
  146. ophyd_async/sim/demo/__init__.py +0 -19
  147. ophyd_async/sim/demo/_pattern_detector/__init__.py +0 -13
  148. ophyd_async/sim/demo/_pattern_detector/_pattern_detector.py +0 -42
  149. ophyd_async/sim/demo/_pattern_detector/_pattern_detector_controller.py +0 -62
  150. ophyd_async/sim/demo/_pattern_detector/_pattern_detector_writer.py +0 -41
  151. ophyd_async/sim/demo/_pattern_detector/_pattern_generator.py +0 -207
  152. ophyd_async/sim/demo/_sim_motor.py +0 -107
  153. ophyd_async/sim/testing/__init__.py +0 -0
  154. ophyd_async-0.9.0a1.dist-info/RECORD +0 -119
  155. ophyd_async-0.9.0a1.dist-info/entry_points.txt +0 -2
  156. {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.10.0a1.dist-info/licenses}/LICENSE +0 -0
  157. {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.10.0a1.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
1
- from collections.abc import Awaitable, Callable, Iterable
1
+ from collections.abc import Awaitable, Callable, Iterable, Iterator
2
2
  from contextlib import contextmanager
3
3
  from unittest.mock import AsyncMock, Mock
4
4
 
@@ -14,6 +14,10 @@ from ophyd_async.core import (
14
14
 
15
15
 
16
16
  def get_mock(device: Device | Signal) -> Mock:
17
+ """Return the mock (which may have child mocks attached) for a Device.
18
+
19
+ The device must have been connected in mock mode.
20
+ """
17
21
  mock = device._mock # noqa: SLF001
18
22
  assert isinstance(mock, LazyMock), f"Device {device} not connected in mock mode"
19
23
  return mock()
@@ -22,9 +26,9 @@ def get_mock(device: Device | Signal) -> Mock:
22
26
  def _get_mock_signal_backend(signal: Signal) -> MockSignalBackend:
23
27
  connector = signal._connector # noqa: SLF001
24
28
  assert isinstance(connector, SignalConnector), f"Expected Signal, got {signal}"
25
- assert isinstance(
26
- connector.backend, MockSignalBackend
27
- ), f"Signal {signal} not connected in mock mode"
29
+ assert isinstance(connector.backend, MockSignalBackend), (
30
+ f"Signal {signal} not connected in mock mode"
31
+ )
28
32
  return connector.backend
29
33
 
30
34
 
@@ -34,36 +38,7 @@ def set_mock_value(signal: Signal[SignalDatatypeT], value: SignalDatatypeT):
34
38
  backend.set_value(value)
35
39
 
36
40
 
37
- def set_mock_put_proceeds(signal: Signal, proceeds: bool):
38
- """Allow or block a put with wait=True from proceeding"""
39
- backend = _get_mock_signal_backend(signal)
40
-
41
- if proceeds:
42
- backend.put_proceeds.set()
43
- else:
44
- backend.put_proceeds.clear()
45
-
46
-
47
- @contextmanager
48
- def mock_puts_blocked(*signals: Signal):
49
- for signal in signals:
50
- set_mock_put_proceeds(signal, False)
51
- yield
52
- for signal in signals:
53
- set_mock_put_proceeds(signal, True)
54
-
55
-
56
- def get_mock_put(signal: Signal) -> AsyncMock:
57
- """Get the mock associated with the put call on the signal."""
58
- return _get_mock_signal_backend(signal).put_mock
59
-
60
-
61
- def reset_mock_put_calls(signal: Signal):
62
- backend = _get_mock_signal_backend(signal)
63
- backend.put_mock.reset_mock()
64
-
65
-
66
- class _SetValuesIterator:
41
+ class _SetValuesIterator(Iterator[SignalDatatypeT]):
67
42
  # Garbage collected by the time __del__ is called unless we put it as a
68
43
  # global attrbute here.
69
44
  require_all_consumed: bool = False
@@ -78,13 +53,9 @@ class _SetValuesIterator:
78
53
  self.values = values
79
54
  self.require_all_consumed = require_all_consumed
80
55
  self.index = 0
81
-
82
56
  self.iterator = enumerate(values, start=1)
83
57
 
84
- def __iter__(self):
85
- return self
86
-
87
- def __next__(self):
58
+ def __next__(self) -> SignalDatatypeT:
88
59
  # Will propogate StopIteration
89
60
  self.index, next_value = next(self.iterator)
90
61
  set_mock_value(self.signal, next_value)
@@ -113,33 +84,26 @@ def set_mock_values(
113
84
  signal: SignalR[SignalDatatypeT],
114
85
  values: Iterable[SignalDatatypeT],
115
86
  require_all_consumed: bool = False,
116
- ) -> _SetValuesIterator:
117
- """Iterator to set a signal to a sequence of values, optionally repeating the
118
- sequence.
119
-
120
- Parameters
121
- ----------
122
- signal:
123
- A signal with a `MockSignalBackend` backend.
124
- values:
125
- An iterable of the values to set the signal to, on each iteration
126
- the value will be set.
127
- require_all_consumed:
128
- If True, an AssertionError will be raised if the iterator is deleted before
129
- all values have been consumed.
130
-
131
- Notes
132
- -----
133
- Example usage::
134
-
135
- for value_set in set_mock_values(signal, [1, 2, 3]):
136
- # do something
137
-
138
- cm = set_mock_values(signal, 1, 2, 3, require_all_consumed=True):
139
- next(cm)
140
- # do something
87
+ ) -> Iterator[SignalDatatypeT]:
88
+ """Set a signal to a sequence of values, optionally repeating.
89
+
90
+ :param signal: A signal connected in mock mode.
91
+ :param values:
92
+ An iterable of the values to set the signal to, on each iteration the
93
+ next value will be set.
94
+ :param require_all_consumed:
95
+ If True, an AssertionError will be raised if the iterator is deleted
96
+ before all values have been consumed.
97
+
98
+ :example:
99
+ ```python
100
+ for value_set in set_mock_values(signal, range(3)):
101
+ # do something
102
+
103
+ cm = set_mock_values(signal, [1, 3, 8], require_all_consumed=True):
104
+ next(cm) # do something
105
+ ```
141
106
  """
142
-
143
107
  return _SetValuesIterator(
144
108
  signal,
145
109
  values,
@@ -160,16 +124,38 @@ def callback_on_mock_put(
160
124
  ):
161
125
  """For setting a callback when a backend is put to.
162
126
 
163
- Can either be used in a context, with the callback being
164
- unset on exit, or as an ordinary function.
127
+ Can either be used in a context, with the callback being unset on exit, or
128
+ as an ordinary function.
165
129
 
166
- Parameters
167
- ----------
168
- signal:
169
- A signal with a `MockSignalBackend` backend.
170
- callback:
171
- The callback to call when the backend is put to during the context.
130
+ :param signal: A signal with a `MockSignalBackend` backend.
131
+ :param callback: The callback to call when the backend is put to during the
132
+ context.
172
133
  """
173
134
  backend = _get_mock_signal_backend(signal)
174
135
  backend.put_mock.side_effect = callback
175
136
  return _unset_side_effect_cm(backend.put_mock)
137
+
138
+
139
+ def set_mock_put_proceeds(signal: Signal, proceeds: bool):
140
+ """Allow or block a put with wait=True from proceeding."""
141
+ backend = _get_mock_signal_backend(signal)
142
+
143
+ if proceeds:
144
+ backend.put_proceeds.set()
145
+ else:
146
+ backend.put_proceeds.clear()
147
+
148
+
149
+ @contextmanager
150
+ def mock_puts_blocked(*signals: Signal):
151
+ """Context manager to block puts at the start and unblock at the end."""
152
+ for signal in signals:
153
+ set_mock_put_proceeds(signal, False)
154
+ yield
155
+ for signal in signals:
156
+ set_mock_put_proceeds(signal, True)
157
+
158
+
159
+ def get_mock_put(signal: Signal) -> AsyncMock:
160
+ """Get the mock associated with the put call on the signal."""
161
+ return _get_mock_signal_backend(signal).put_mock
@@ -0,0 +1,146 @@
1
+ from collections.abc import Sequence
2
+ from typing import Any
3
+
4
+ import numpy as np
5
+
6
+ from ophyd_async.core import (
7
+ Array1D,
8
+ Device,
9
+ DTypeScalar_co,
10
+ SignalRW,
11
+ StandardReadable,
12
+ StrictEnum,
13
+ Table,
14
+ soft_signal_r_and_setter,
15
+ soft_signal_rw,
16
+ )
17
+ from ophyd_async.core import StandardReadableFormat as Format
18
+ from ophyd_async.core._device import DeviceVector
19
+
20
+
21
+ class ExampleEnum(StrictEnum):
22
+ """Example of a strict Enum datatype."""
23
+
24
+ A = "Aaa"
25
+ B = "Bbb"
26
+ C = "Ccc"
27
+
28
+
29
+ class ExampleTable(Table):
30
+ a_bool: Array1D[np.bool_]
31
+ a_int: Array1D[np.int32]
32
+ a_float: Array1D[np.float64]
33
+ a_str: Sequence[str]
34
+ a_enum: Sequence[ExampleEnum]
35
+
36
+
37
+ def int_array_value(dtype: type[DTypeScalar_co]):
38
+ iinfo = np.iinfo(dtype) # type: ignore
39
+ return np.array([iinfo.min, iinfo.max, 0, 1, 2, 3, 4], dtype=dtype)
40
+
41
+
42
+ def int_array_signal(
43
+ dtype: type[DTypeScalar_co], name: str = ""
44
+ ) -> SignalRW[Array1D[DTypeScalar_co]]:
45
+ value = int_array_value(dtype)
46
+ return soft_signal_rw(Array1D[dtype], value, name)
47
+
48
+
49
+ def float_array_value(dtype: type[DTypeScalar_co]):
50
+ finfo = np.finfo(dtype) # type: ignore
51
+ return np.array(
52
+ [
53
+ finfo.min,
54
+ finfo.max,
55
+ finfo.smallest_normal,
56
+ finfo.smallest_subnormal,
57
+ 0,
58
+ 1.234,
59
+ 2.34e5,
60
+ 3.45e-6,
61
+ ],
62
+ dtype=dtype,
63
+ )
64
+
65
+
66
+ def float_array_signal(
67
+ dtype: type[DTypeScalar_co], name: str = ""
68
+ ) -> SignalRW[Array1D[DTypeScalar_co]]:
69
+ value = float_array_value(dtype)
70
+ return soft_signal_rw(Array1D[dtype], value, name)
71
+
72
+
73
+ class OneOfEverythingDevice(StandardReadable):
74
+ """A device with one of every datatype allowed on signals."""
75
+
76
+ # make a detector to test assert_configuration
77
+ def __init__(self, name=""):
78
+ # add all signals to configuration
79
+ with self.add_children_as_readables(Format.CONFIG_SIGNAL):
80
+ self.a_int = soft_signal_rw(int, 1)
81
+ self.a_float = soft_signal_rw(float, 1.234)
82
+ self.a_str = soft_signal_rw(str, "test_string")
83
+ self.a_bool = soft_signal_rw(bool, True)
84
+ self.a_enum = soft_signal_rw(ExampleEnum, ExampleEnum.B)
85
+ self.boola = soft_signal_rw(
86
+ Array1D[np.bool_], np.array([False, False, True])
87
+ )
88
+ self.int8a = int_array_signal(np.int8)
89
+ self.uint8a = int_array_signal(np.uint8)
90
+ self.int16a = int_array_signal(np.int16)
91
+ self.uint16a = int_array_signal(np.uint16)
92
+ self.int32a = int_array_signal(np.int32)
93
+ self.uint32a = int_array_signal(np.uint32)
94
+ self.int64a = int_array_signal(np.int64)
95
+ self.uint64a = int_array_signal(np.uint64)
96
+ self.float32a = float_array_signal(np.float32)
97
+ self.float64a = float_array_signal(np.float64)
98
+ self.stra = soft_signal_rw(
99
+ Sequence[str],
100
+ ["one", "two", "three"],
101
+ )
102
+ self.enuma = soft_signal_rw(
103
+ Sequence[ExampleEnum],
104
+ [ExampleEnum.A, ExampleEnum.C],
105
+ )
106
+ self.table = soft_signal_rw(
107
+ ExampleTable,
108
+ ExampleTable(
109
+ a_bool=np.array([False, False, True, True], np.bool_),
110
+ a_int=np.array([1, 8, -9, 32], np.int32),
111
+ a_float=np.array([1.8, 8.2, -6, 32.9887], np.float64),
112
+ a_str=["Hello", "World", "Foo", "Bar"],
113
+ a_enum=[ExampleEnum.A, ExampleEnum.B, ExampleEnum.A, ExampleEnum.C],
114
+ ),
115
+ )
116
+ self.ndarray = soft_signal_rw(np.ndarray, np.array(([1, 2, 3], [4, 5, 6])))
117
+ super().__init__(name)
118
+
119
+ async def get_signal_values(self):
120
+ return await _get_signal_values(self)
121
+
122
+
123
+ async def _get_signal_values(child: Device) -> dict[SignalRW, Any]:
124
+ if isinstance(child, SignalRW):
125
+ return {child: await child.get_value()}
126
+ ret = {}
127
+ for _, c in child.children():
128
+ ret.update(await _get_signal_values(c))
129
+ return ret
130
+
131
+
132
+ class ParentOfEverythingDevice(Device):
133
+ """Device containing subdevices with one of every datatype allowed on signals."""
134
+
135
+ def __init__(self, name=""):
136
+ self.child = OneOfEverythingDevice()
137
+ self.vector = DeviceVector(
138
+ {1: OneOfEverythingDevice(), 3: OneOfEverythingDevice()}
139
+ )
140
+ self.sig_rw = soft_signal_rw(str, "Top level SignalRW")
141
+ self.sig_r, _ = soft_signal_r_and_setter(str, "Top level SignalR")
142
+ self._sig_rw = soft_signal_rw(str, "Top level private SignalRW")
143
+ super().__init__(name=name)
144
+
145
+ async def get_signal_values(self):
146
+ return await _get_signal_values(self)
@@ -0,0 +1,87 @@
1
+ import asyncio
2
+
3
+ from ophyd_async.core import (
4
+ Device,
5
+ DeviceVector,
6
+ StandardReadable,
7
+ StrictEnum,
8
+ derived_signal_r,
9
+ derived_signal_rw,
10
+ derived_signal_w,
11
+ soft_signal_rw,
12
+ )
13
+
14
+
15
+ class BeamstopPosition(StrictEnum):
16
+ IN_POSITION = "In position"
17
+ OUT_OF_POSITION = "Out of position"
18
+
19
+
20
+ class ReadOnlyBeamstop(Device):
21
+ """Reads from 2 motors to work out if the beamstop is in position.
22
+
23
+ E.g. bps.rd(beamstop.position)
24
+ """
25
+
26
+ def __init__(self, name=""):
27
+ # Raw signals
28
+ self.x = soft_signal_rw(float)
29
+ self.y = soft_signal_rw(float)
30
+ # Derived signals
31
+ self.position = derived_signal_r(self._get_position, x=self.x, y=self.y)
32
+ super().__init__(name=name)
33
+
34
+ def _get_position(self, x: float, y: float) -> BeamstopPosition:
35
+ if abs(x) < 1 and abs(y) < 2:
36
+ return BeamstopPosition.IN_POSITION
37
+ else:
38
+ return BeamstopPosition.OUT_OF_POSITION
39
+
40
+
41
+ class MovableBeamstop(Device):
42
+ """As well as reads, this one allows you to move it.
43
+
44
+ E.g. bps.mv(beamstop.position, BeamstopPosition.IN_POSITION)
45
+ """
46
+
47
+ def __init__(self, name=""):
48
+ # Raw signals
49
+ self.x = soft_signal_rw(float)
50
+ self.y = soft_signal_rw(float)
51
+ # Derived signals
52
+ self.position = derived_signal_rw(
53
+ self._get_position, self._set_from_position, x=self.x, y=self.y
54
+ )
55
+ super().__init__(name=name)
56
+
57
+ def _get_position(self, x: float, y: float) -> BeamstopPosition:
58
+ if abs(x) < 1 and abs(y) < 2:
59
+ return BeamstopPosition.IN_POSITION
60
+ else:
61
+ return BeamstopPosition.OUT_OF_POSITION
62
+
63
+ async def _set_from_position(self, position: BeamstopPosition) -> None:
64
+ if position == BeamstopPosition.IN_POSITION:
65
+ await asyncio.gather(self.x.set(0), self.y.set(0))
66
+ else:
67
+ await asyncio.gather(self.x.set(3), self.y.set(5))
68
+
69
+
70
+ class Exploder(StandardReadable):
71
+ """This one takes a value and sets all its signal to that value.
72
+
73
+ This allows convenience "set all" functions, while the individual
74
+ signals are still free to be set to different values.
75
+ """
76
+
77
+ def __init__(self, num_signals: int, name=""):
78
+ with self.add_children_as_readables():
79
+ self.signals = DeviceVector(
80
+ {i: soft_signal_rw(int, units="cts") for i in range(1, num_signals + 1)}
81
+ )
82
+ self.set_all = derived_signal_w(self._set_all, derived_units="cts")
83
+ super().__init__(name=name)
84
+
85
+ async def _set_all(self, value: int) -> None:
86
+ coros = [sig.set(value) for sig in self.signals.values()]
87
+ await asyncio.gather(*coros)
@@ -0,0 +1,3 @@
1
+ from typing import TypeVar
2
+
3
+ T = TypeVar("T")
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: ophyd-async
3
- Version: 0.9.0a1
3
+ Version: 0.10.0a1
4
4
  Summary: Asynchronous Bluesky hardware abstraction code, compatible with control systems like EPICS and Tango
5
5
  Author-email: Tom Cobb <tom.cobb@diamond.ac.uk>
6
6
  License: BSD 3-Clause License
@@ -41,35 +41,33 @@ Classifier: Programming Language :: Python :: 3.12
41
41
  Requires-Python: >=3.10
42
42
  Description-Content-Type: text/markdown
43
43
  License-File: LICENSE
44
- Requires-Dist: networkx>=2.0
45
44
  Requires-Dist: numpy
46
- Requires-Dist: packaging
47
- Requires-Dist: pint
48
- Requires-Dist: bluesky>=1.13
45
+ Requires-Dist: bluesky>=1.13.1rc2
49
46
  Requires-Dist: event-model>=1.22.1
50
- Requires-Dist: p4p>=4.2.0a3
51
47
  Requires-Dist: pyyaml
52
48
  Requires-Dist: colorlog
53
49
  Requires-Dist: pydantic>=2.0
54
50
  Requires-Dist: pydantic-numpy
51
+ Provides-Extra: sim
52
+ Requires-Dist: h5py; extra == "sim"
55
53
  Provides-Extra: ca
56
54
  Requires-Dist: aioca>=1.6; extra == "ca"
57
55
  Provides-Extra: pva
58
- Requires-Dist: p4p; extra == "pva"
59
- Provides-Extra: sim
60
- Requires-Dist: h5py; extra == "sim"
56
+ Requires-Dist: p4p>=4.2.0; extra == "pva"
61
57
  Provides-Extra: tango
62
- Requires-Dist: pytango>=10.0.0; extra == "tango"
58
+ Requires-Dist: pytango==10.0.0; extra == "tango"
59
+ Provides-Extra: demo
60
+ Requires-Dist: ipython; extra == "demo"
61
+ Requires-Dist: matplotlib; extra == "demo"
62
+ Requires-Dist: pyqt6; extra == "demo"
63
63
  Provides-Extra: dev
64
- Requires-Dist: ophyd_async[pva]; extra == "dev"
65
64
  Requires-Dist: ophyd_async[sim]; extra == "dev"
66
65
  Requires-Dist: ophyd_async[ca]; extra == "dev"
66
+ Requires-Dist: ophyd_async[pva]; extra == "dev"
67
67
  Requires-Dist: ophyd_async[tango]; extra == "dev"
68
+ Requires-Dist: ophyd_async[demo]; extra == "dev"
68
69
  Requires-Dist: inflection; extra == "dev"
69
- Requires-Dist: ipython; extra == "dev"
70
- Requires-Dist: ipywidgets; extra == "dev"
71
70
  Requires-Dist: import-linter; extra == "dev"
72
- Requires-Dist: matplotlib; extra == "dev"
73
71
  Requires-Dist: myst-parser; extra == "dev"
74
72
  Requires-Dist: numpydoc; extra == "dev"
75
73
  Requires-Dist: ophyd; extra == "dev"
@@ -79,7 +77,6 @@ Requires-Dist: pre-commit; extra == "dev"
79
77
  Requires-Dist: pydata-sphinx-theme>=0.12; extra == "dev"
80
78
  Requires-Dist: pyepics>=3.4.2; extra == "dev"
81
79
  Requires-Dist: pyright; extra == "dev"
82
- Requires-Dist: pyside6==6.7.0; extra == "dev"
83
80
  Requires-Dist: pytest; extra == "dev"
84
81
  Requires-Dist: pytest-asyncio; extra == "dev"
85
82
  Requires-Dist: pytest-cov; extra == "dev"
@@ -88,16 +85,15 @@ Requires-Dist: pytest-forked; extra == "dev"
88
85
  Requires-Dist: pytest-rerunfailures; extra == "dev"
89
86
  Requires-Dist: pytest-timeout; extra == "dev"
90
87
  Requires-Dist: ruff; extra == "dev"
91
- Requires-Dist: sphinx<7.4.0; extra == "dev"
92
88
  Requires-Dist: sphinx-autobuild; extra == "dev"
93
- Requires-Dist: autodoc-pydantic; extra == "dev"
89
+ Requires-Dist: sphinx-autodoc2; extra == "dev"
94
90
  Requires-Dist: sphinxcontrib-mermaid; extra == "dev"
95
91
  Requires-Dist: sphinx-copybutton; extra == "dev"
96
92
  Requires-Dist: sphinx-design; extra == "dev"
97
- Requires-Dist: super_state_machine; extra == "dev"
98
93
  Requires-Dist: tox-direct; extra == "dev"
99
94
  Requires-Dist: types-mock; extra == "dev"
100
95
  Requires-Dist: types-pyyaml; extra == "dev"
96
+ Dynamic: license-file
101
97
 
102
98
  [![CI](https://github.com/bluesky/ophyd-async/actions/workflows/ci.yml/badge.svg)](https://github.com/bluesky/ophyd-async/actions/workflows/ci.yml)
103
99
  [![Coverage](https://codecov.io/gh/bluesky/ophyd-async/branch/main/graph/badge.svg)](https://codecov.io/gh/bluesky/ophyd-async)
@@ -114,16 +110,19 @@ Asynchronous Bluesky hardware abstraction code, compatible with control systems
114
110
  | Documentation | <https://bluesky.github.io/ophyd-async> |
115
111
  | Releases | <https://github.com/bluesky/ophyd-async/releases> |
116
112
 
117
- Ophyd-async is a Python library for asynchronously interfacing with hardware, intended to
118
- be used as an abstraction layer that enables experiment orchestration and data acquisition code to operate above the specifics of particular devices and control
119
- systems.
113
+ Ophyd-async is a Python library for asynchronously interfacing with hardware, intended to be used as an abstraction layer that enables experiment orchestration and data acquisition code to operate above the specifics of particular devices and control systems.
114
+
115
+ Both ophyd sync and ophyd-async are typically used with the [Bluesky Run Engine][] for experiment orchestration and data acquisition.
116
+
117
+ The main differences from ophyd sync are:
120
118
 
121
- Both ophyd and ophyd-async are typically used with the [Bluesky Run Engine][] for experiment orchestration and data acquisition.
119
+ - Asynchronous Signal access, simplifying the parallel control of multiple Signals
120
+ - Support for [EPICS][] PVA and [Tango][] as well as the traditional EPICS CA
121
+ - Better library support for splitting the logic from the hardware interface to avoid complex class heirarchies
122
122
 
123
- While [EPICS][] is the most common control system layer that ophyd-async can interface with, support for other control systems like [Tango][] will be supported in the future. The focus of ophyd-async is:
123
+ It was written with the aim of implementing flyscanning in a generic and extensible way with highly customizable devices like PandABox and the Delta Tau PMAC products. Using async code makes it possible to do the "put 3 PVs in parallel, then get from another PV" logic that is common in flyscanning without the performance and complexity overhead of multiple threads.
124
124
 
125
- * Asynchronous signal access, opening the possibility for hardware-triggered scanning (also known as fly-scanning)
126
- * Simpler instantiation of devices (groupings of signals) with less reliance upon complex class hierarchies
125
+ Devices from both ophyd sync and ophyd-async can be used in the same RunEngine and even in the same scan. This allows a per-device migration where devices are reimplemented in ophyd-async one by one.
127
126
 
128
127
  [Bluesky Run Engine]: http://blueskyproject.io/bluesky
129
128
  [EPICS]: http://www.aps.anl.gov/epics/