ophyd-async 0.7.0__py3-none-any.whl → 0.8.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 (92) hide show
  1. ophyd_async/_version.py +2 -2
  2. ophyd_async/core/__init__.py +34 -9
  3. ophyd_async/core/_detector.py +5 -10
  4. ophyd_async/core/_device.py +170 -68
  5. ophyd_async/core/_device_filler.py +269 -0
  6. ophyd_async/core/_device_save_loader.py +6 -7
  7. ophyd_async/core/_mock_signal_backend.py +35 -40
  8. ophyd_async/core/_mock_signal_utils.py +25 -16
  9. ophyd_async/core/_protocol.py +28 -8
  10. ophyd_async/core/_readable.py +133 -134
  11. ophyd_async/core/_signal.py +219 -163
  12. ophyd_async/core/_signal_backend.py +131 -64
  13. ophyd_async/core/_soft_signal_backend.py +131 -194
  14. ophyd_async/core/_status.py +22 -6
  15. ophyd_async/core/_table.py +102 -100
  16. ophyd_async/core/_utils.py +143 -32
  17. ophyd_async/epics/adaravis/_aravis_controller.py +2 -2
  18. ophyd_async/epics/adaravis/_aravis_io.py +8 -6
  19. ophyd_async/epics/adcore/_core_io.py +5 -7
  20. ophyd_async/epics/adcore/_core_logic.py +3 -1
  21. ophyd_async/epics/adcore/_hdf_writer.py +2 -2
  22. ophyd_async/epics/adcore/_single_trigger.py +6 -10
  23. ophyd_async/epics/adcore/_utils.py +15 -10
  24. ophyd_async/epics/adkinetix/__init__.py +2 -1
  25. ophyd_async/epics/adkinetix/_kinetix_controller.py +6 -3
  26. ophyd_async/epics/adkinetix/_kinetix_io.py +4 -5
  27. ophyd_async/epics/adpilatus/_pilatus_controller.py +2 -2
  28. ophyd_async/epics/adpilatus/_pilatus_io.py +3 -4
  29. ophyd_async/epics/adsimdetector/_sim_controller.py +2 -2
  30. ophyd_async/epics/advimba/__init__.py +4 -1
  31. ophyd_async/epics/advimba/_vimba_controller.py +6 -3
  32. ophyd_async/epics/advimba/_vimba_io.py +8 -9
  33. ophyd_async/epics/core/__init__.py +26 -0
  34. ophyd_async/epics/core/_aioca.py +323 -0
  35. ophyd_async/epics/core/_epics_connector.py +53 -0
  36. ophyd_async/epics/core/_epics_device.py +13 -0
  37. ophyd_async/epics/core/_p4p.py +383 -0
  38. ophyd_async/epics/core/_pvi_connector.py +91 -0
  39. ophyd_async/epics/core/_signal.py +171 -0
  40. ophyd_async/epics/core/_util.py +61 -0
  41. ophyd_async/epics/demo/_mover.py +4 -5
  42. ophyd_async/epics/demo/_sensor.py +14 -13
  43. ophyd_async/epics/eiger/_eiger.py +1 -2
  44. ophyd_async/epics/eiger/_eiger_controller.py +7 -2
  45. ophyd_async/epics/eiger/_eiger_io.py +3 -5
  46. ophyd_async/epics/eiger/_odin_io.py +5 -5
  47. ophyd_async/epics/motor.py +4 -5
  48. ophyd_async/epics/signal.py +11 -0
  49. ophyd_async/epics/testing/__init__.py +24 -0
  50. ophyd_async/epics/testing/_example_ioc.py +105 -0
  51. ophyd_async/epics/testing/_utils.py +78 -0
  52. ophyd_async/epics/testing/test_records.db +152 -0
  53. ophyd_async/epics/testing/test_records_pva.db +177 -0
  54. ophyd_async/fastcs/core.py +9 -0
  55. ophyd_async/fastcs/panda/__init__.py +4 -4
  56. ophyd_async/fastcs/panda/_block.py +18 -13
  57. ophyd_async/fastcs/panda/_control.py +3 -5
  58. ophyd_async/fastcs/panda/_hdf_panda.py +5 -19
  59. ophyd_async/fastcs/panda/_table.py +30 -52
  60. ophyd_async/fastcs/panda/_trigger.py +8 -8
  61. ophyd_async/fastcs/panda/_writer.py +2 -5
  62. ophyd_async/plan_stubs/_ensure_connected.py +20 -13
  63. ophyd_async/plan_stubs/_fly.py +2 -2
  64. ophyd_async/plan_stubs/_nd_attributes.py +5 -4
  65. ophyd_async/py.typed +0 -0
  66. ophyd_async/sim/demo/_pattern_detector/_pattern_detector_controller.py +1 -2
  67. ophyd_async/sim/demo/_sim_motor.py +3 -4
  68. ophyd_async/tango/__init__.py +0 -45
  69. ophyd_async/tango/{signal → core}/__init__.py +9 -6
  70. ophyd_async/tango/core/_base_device.py +132 -0
  71. ophyd_async/tango/{signal → core}/_signal.py +42 -53
  72. ophyd_async/tango/{base_devices → core}/_tango_readable.py +3 -4
  73. ophyd_async/tango/{signal → core}/_tango_transport.py +38 -40
  74. ophyd_async/tango/demo/_counter.py +12 -23
  75. ophyd_async/tango/demo/_mover.py +13 -13
  76. {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0.dist-info}/METADATA +52 -55
  77. ophyd_async-0.8.0.dist-info/RECORD +116 -0
  78. {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0.dist-info}/WHEEL +1 -1
  79. ophyd_async/epics/pvi/__init__.py +0 -3
  80. ophyd_async/epics/pvi/_pvi.py +0 -338
  81. ophyd_async/epics/signal/__init__.py +0 -21
  82. ophyd_async/epics/signal/_aioca.py +0 -378
  83. ophyd_async/epics/signal/_common.py +0 -57
  84. ophyd_async/epics/signal/_epics_transport.py +0 -34
  85. ophyd_async/epics/signal/_p4p.py +0 -518
  86. ophyd_async/epics/signal/_signal.py +0 -114
  87. ophyd_async/tango/base_devices/__init__.py +0 -4
  88. ophyd_async/tango/base_devices/_base_device.py +0 -225
  89. ophyd_async-0.7.0.dist-info/RECORD +0 -108
  90. {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0.dist-info}/LICENSE +0 -0
  91. {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0.dist-info}/entry_points.txt +0 -0
  92. {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0.dist-info}/top_level.txt +0 -0
@@ -6,8 +6,6 @@ from bluesky.protocols import Movable, Stoppable
6
6
 
7
7
  from ophyd_async.core import (
8
8
  AsyncStatus,
9
- ConfigSignal,
10
- HintedSignal,
11
9
  StandardReadable,
12
10
  WatchableAsyncStatus,
13
11
  WatcherUpdate,
@@ -15,6 +13,7 @@ from ophyd_async.core import (
15
13
  soft_signal_r_and_setter,
16
14
  soft_signal_rw,
17
15
  )
16
+ from ophyd_async.core import StandardReadableFormat as Format
18
17
 
19
18
 
20
19
  class SimMotor(StandardReadable, Movable, Stoppable):
@@ -28,11 +27,11 @@ class SimMotor(StandardReadable, Movable, Stoppable):
28
27
  - instant: bool: whether to move instantly, or with a delay
29
28
  """
30
29
  # Define some signals
31
- with self.add_children_as_readables(HintedSignal):
30
+ with self.add_children_as_readables(Format.HINTED_SIGNAL):
32
31
  self.user_readback, self._user_readback_set = soft_signal_r_and_setter(
33
32
  float, 0
34
33
  )
35
- with self.add_children_as_readables(ConfigSignal):
34
+ with self.add_children_as_readables(Format.CONFIG_SIGNAL):
36
35
  self.velocity = soft_signal_rw(float, 0 if instant else 1.0)
37
36
  self.units = soft_signal_rw(str, "mm")
38
37
  self.user_setpoint = soft_signal_rw(float, 0)
@@ -1,45 +0,0 @@
1
- from .base_devices import (
2
- TangoDevice,
3
- TangoReadable,
4
- tango_polling,
5
- )
6
- from .signal import (
7
- AttributeProxy,
8
- CommandProxy,
9
- TangoSignalBackend,
10
- __tango_signal_auto,
11
- ensure_proper_executor,
12
- get_dtype_extended,
13
- get_python_type,
14
- get_tango_trl,
15
- get_trl_descriptor,
16
- infer_python_type,
17
- infer_signal_character,
18
- make_backend,
19
- tango_signal_r,
20
- tango_signal_rw,
21
- tango_signal_w,
22
- tango_signal_x,
23
- )
24
-
25
- __all__ = [
26
- "TangoDevice",
27
- "TangoReadable",
28
- "tango_polling",
29
- "TangoSignalBackend",
30
- "get_python_type",
31
- "get_dtype_extended",
32
- "get_trl_descriptor",
33
- "get_tango_trl",
34
- "infer_python_type",
35
- "infer_signal_character",
36
- "make_backend",
37
- "AttributeProxy",
38
- "CommandProxy",
39
- "ensure_proper_executor",
40
- "__tango_signal_auto",
41
- "tango_signal_r",
42
- "tango_signal_rw",
43
- "tango_signal_w",
44
- "tango_signal_x",
45
- ]
@@ -1,13 +1,14 @@
1
+ from ._base_device import TangoDevice, TangoPolling
1
2
  from ._signal import (
2
- __tango_signal_auto,
3
3
  infer_python_type,
4
- infer_signal_character,
4
+ infer_signal_type,
5
5
  make_backend,
6
6
  tango_signal_r,
7
7
  tango_signal_rw,
8
8
  tango_signal_w,
9
9
  tango_signal_x,
10
10
  )
11
+ from ._tango_readable import TangoReadable
11
12
  from ._tango_transport import (
12
13
  AttributeProxy,
13
14
  CommandProxy,
@@ -19,7 +20,7 @@ from ._tango_transport import (
19
20
  get_trl_descriptor,
20
21
  )
21
22
 
22
- __all__ = (
23
+ __all__ = [
23
24
  "AttributeProxy",
24
25
  "CommandProxy",
25
26
  "ensure_proper_executor",
@@ -29,11 +30,13 @@ __all__ = (
29
30
  "get_trl_descriptor",
30
31
  "get_tango_trl",
31
32
  "infer_python_type",
32
- "infer_signal_character",
33
+ "infer_signal_type",
33
34
  "make_backend",
34
35
  "tango_signal_r",
35
36
  "tango_signal_rw",
36
37
  "tango_signal_w",
37
38
  "tango_signal_x",
38
- "__tango_signal_auto",
39
- )
39
+ "TangoDevice",
40
+ "TangoReadable",
41
+ "TangoPolling",
42
+ ]
@@ -0,0 +1,132 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any, Generic, TypeVar
5
+
6
+ from ophyd_async.core import Device, DeviceConnector, DeviceFiller, LazyMock
7
+ from tango import DeviceProxy as DeviceProxy
8
+ from tango.asyncio import DeviceProxy as AsyncDeviceProxy
9
+
10
+ from ._signal import TangoSignalBackend, infer_python_type, infer_signal_type
11
+
12
+ T = TypeVar("T")
13
+
14
+
15
+ class TangoDevice(Device):
16
+ """
17
+ General class for TangoDevices. Extends Device to provide attributes for Tango
18
+ devices.
19
+
20
+ Parameters
21
+ ----------
22
+ trl: str
23
+ Tango resource locator, typically of the device server.
24
+ device_proxy: Optional[Union[AsyncDeviceProxy, SyncDeviceProxy]]
25
+ Asynchronous or synchronous DeviceProxy object for the device. If not provided,
26
+ an asynchronous DeviceProxy object will be created using the trl and awaited
27
+ when the device is connected.
28
+ """
29
+
30
+ trl: str = ""
31
+ proxy: DeviceProxy | None = None
32
+
33
+ def __init__(
34
+ self,
35
+ trl: str | None = None,
36
+ device_proxy: DeviceProxy | None = None,
37
+ support_events: bool = False,
38
+ name: str = "",
39
+ ) -> None:
40
+ connector = TangoDeviceConnector(
41
+ trl=trl, device_proxy=device_proxy, support_events=support_events
42
+ )
43
+ super().__init__(name=name, connector=connector)
44
+
45
+
46
+ @dataclass
47
+ class TangoPolling(Generic[T]):
48
+ ophyd_polling_period: float = 0.1
49
+ abs_change: T | None = None
50
+ rel_change: T | None = None
51
+
52
+
53
+ def fill_backend_with_polling(
54
+ support_events: bool, backend: TangoSignalBackend, annotations: list[Any]
55
+ ):
56
+ unhandled = []
57
+ while annotations:
58
+ annotation = annotations.pop(0)
59
+ backend.allow_events(support_events)
60
+ if isinstance(annotation, TangoPolling):
61
+ backend.set_polling(
62
+ not support_events,
63
+ annotation.ophyd_polling_period,
64
+ annotation.abs_change,
65
+ annotation.rel_change,
66
+ )
67
+ else:
68
+ unhandled.append(annotation)
69
+ annotations.extend(unhandled)
70
+ # These leftover annotations will now be handled by the iterator
71
+
72
+
73
+ class TangoDeviceConnector(DeviceConnector):
74
+ def __init__(
75
+ self,
76
+ trl: str | None,
77
+ device_proxy: DeviceProxy | None,
78
+ support_events: bool,
79
+ ) -> None:
80
+ self.trl = trl
81
+ self.proxy = device_proxy
82
+ self._support_events = support_events
83
+
84
+ def create_children_from_annotations(self, device: Device):
85
+ if not hasattr(self, "filler"):
86
+ self.filler = DeviceFiller(
87
+ device=device,
88
+ signal_backend_factory=TangoSignalBackend,
89
+ device_connector_factory=lambda: TangoDeviceConnector(
90
+ None, None, self._support_events
91
+ ),
92
+ )
93
+ list(self.filler.create_devices_from_annotations(filled=False))
94
+ for backend, annotations in self.filler.create_signals_from_annotations(
95
+ filled=False
96
+ ):
97
+ fill_backend_with_polling(self._support_events, backend, annotations)
98
+ self.filler.check_created()
99
+
100
+ async def connect_mock(self, device: Device, mock: LazyMock):
101
+ # Make 2 entries for each DeviceVector
102
+ self.filler.create_device_vector_entries_to_mock(2)
103
+ # Set the name of the device to name all children
104
+ device.set_name(device.name)
105
+ return await super().connect_mock(device, mock)
106
+
107
+ async def connect_real(self, device: Device, timeout: float, force_reconnect: bool):
108
+ if self.trl and self.proxy is None:
109
+ self.proxy = await AsyncDeviceProxy(self.trl)
110
+ elif self.proxy and not self.trl:
111
+ self.trl = self.proxy.name()
112
+ else:
113
+ raise TypeError("Neither proxy nor trl supplied")
114
+
115
+ children = sorted(
116
+ set()
117
+ .union(self.proxy.get_attribute_list())
118
+ .union(self.proxy.get_command_list())
119
+ )
120
+ for name in children:
121
+ # TODO: strip attribute name
122
+ full_trl = f"{self.trl}/{name}"
123
+ signal_type = await infer_signal_type(full_trl, self.proxy)
124
+ if signal_type:
125
+ backend = self.filler.fill_child_signal(name, signal_type)
126
+ backend.datatype = await infer_python_type(full_trl, self.proxy)
127
+ backend.set_trl(full_trl)
128
+ # Check that all the requested children have been filled
129
+ self.filler.check_filled(f"{self.trl}: {children}")
130
+ # Set the name of the device to name all children
131
+ device.set_name(device.name)
132
+ return await super().connect_real(device, timeout, force_reconnect)
@@ -2,21 +2,35 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import logging
5
6
  from enum import Enum, IntEnum
6
7
 
7
8
  import numpy.typing as npt
8
9
 
9
- from ophyd_async.core import DEFAULT_TIMEOUT, SignalR, SignalRW, SignalW, SignalX, T
10
- from ophyd_async.tango.signal._tango_transport import (
11
- TangoSignalBackend,
12
- get_python_type,
10
+ from ophyd_async.core import (
11
+ DEFAULT_TIMEOUT,
12
+ Signal,
13
+ SignalDatatypeT,
14
+ SignalR,
15
+ SignalRW,
16
+ SignalW,
17
+ SignalX,
18
+ )
19
+ from tango import (
20
+ AttrDataFormat,
21
+ AttrWriteType,
22
+ CmdArgType,
23
+ DeviceProxy,
24
+ DevState,
25
+ NonSupportedFeature, # type: ignore
13
26
  )
14
- from tango import AttrDataFormat, AttrWriteType, CmdArgType, DeviceProxy, DevState
15
27
  from tango.asyncio import DeviceProxy as AsyncDeviceProxy
16
28
 
29
+ from ._tango_transport import TangoSignalBackend, get_python_type
30
+
17
31
 
18
32
  def make_backend(
19
- datatype: type[T] | None,
33
+ datatype: type[SignalDatatypeT] | None,
20
34
  read_trl: str = "",
21
35
  write_trl: str = "",
22
36
  device_proxy: DeviceProxy | None = None,
@@ -25,13 +39,13 @@ def make_backend(
25
39
 
26
40
 
27
41
  def tango_signal_rw(
28
- datatype: type[T],
42
+ datatype: type[SignalDatatypeT],
29
43
  read_trl: str,
30
44
  write_trl: str = "",
31
45
  device_proxy: DeviceProxy | None = None,
32
46
  timeout: float = DEFAULT_TIMEOUT,
33
47
  name: str = "",
34
- ) -> SignalRW[T]:
48
+ ) -> SignalRW[SignalDatatypeT]:
35
49
  """Create a `SignalRW` backed by 1 or 2 Tango Attribute/Command
36
50
 
37
51
  Parameters
@@ -54,12 +68,12 @@ def tango_signal_rw(
54
68
 
55
69
 
56
70
  def tango_signal_r(
57
- datatype: type[T],
71
+ datatype: type[SignalDatatypeT],
58
72
  read_trl: str,
59
73
  device_proxy: DeviceProxy | None = None,
60
74
  timeout: float = DEFAULT_TIMEOUT,
61
75
  name: str = "",
62
- ) -> SignalR[T]:
76
+ ) -> SignalR[SignalDatatypeT]:
63
77
  """Create a `SignalR` backed by 1 Tango Attribute/Command
64
78
 
65
79
  Parameters
@@ -80,12 +94,12 @@ def tango_signal_r(
80
94
 
81
95
 
82
96
  def tango_signal_w(
83
- datatype: type[T],
97
+ datatype: type[SignalDatatypeT],
84
98
  write_trl: str,
85
99
  device_proxy: DeviceProxy | None = None,
86
100
  timeout: float = DEFAULT_TIMEOUT,
87
101
  name: str = "",
88
- ) -> SignalW[T]:
102
+ ) -> SignalW[SignalDatatypeT]:
89
103
  """Create a `SignalW` backed by 1 Tango Attribute/Command
90
104
 
91
105
  Parameters
@@ -128,39 +142,10 @@ def tango_signal_x(
128
142
  return SignalX(backend, timeout=timeout, name=name)
129
143
 
130
144
 
131
- async def __tango_signal_auto(
132
- datatype: type[T] | None = None,
133
- *,
134
- trl: str,
135
- device_proxy: DeviceProxy | None,
136
- timeout: float = DEFAULT_TIMEOUT,
137
- name: str = "",
138
- ) -> SignalW | SignalX | SignalR | SignalRW | None:
139
- try:
140
- signal_character = await infer_signal_character(trl, device_proxy)
141
- except RuntimeError as e:
142
- if "Commands with different in and out dtypes" in str(e):
143
- return None
144
- else:
145
- raise e
146
-
147
- if datatype is None:
148
- datatype = await infer_python_type(trl, device_proxy)
149
-
150
- backend = make_backend(datatype, trl, trl, device_proxy)
151
- if signal_character == "RW":
152
- return SignalRW(backend=backend, timeout=timeout, name=name)
153
- if signal_character == "R":
154
- return SignalR(backend=backend, timeout=timeout, name=name)
155
- if signal_character == "W":
156
- return SignalW(backend=backend, timeout=timeout, name=name)
157
- if signal_character == "X":
158
- return SignalX(backend=backend, timeout=timeout, name=name)
159
-
160
-
161
145
  async def infer_python_type(
162
146
  trl: str = "", proxy: DeviceProxy | None = None
163
147
  ) -> object | npt.NDArray | type[DevState] | IntEnum:
148
+ # TODO: work out if this is still needed
164
149
  device_trl, tr_name = trl.rsplit("/", 1)
165
150
  if proxy is None:
166
151
  dev_proxy = await AsyncDeviceProxy(device_trl)
@@ -187,15 +172,20 @@ async def infer_python_type(
187
172
  return npt.NDArray[py_type] if isarray else py_type
188
173
 
189
174
 
190
- async def infer_signal_character(trl, proxy: DeviceProxy | None = None) -> str:
175
+ async def infer_signal_type(
176
+ trl, proxy: DeviceProxy | None = None
177
+ ) -> type[Signal] | None:
191
178
  device_trl, tr_name = trl.rsplit("/", 1)
192
179
  if proxy is None:
193
180
  dev_proxy = await AsyncDeviceProxy(device_trl)
194
181
  else:
195
182
  dev_proxy = proxy
196
183
 
197
- if tr_name in dev_proxy.get_pipe_list():
198
- raise NotImplementedError("Pipes are not supported")
184
+ try:
185
+ if tr_name in dev_proxy.get_pipe_list():
186
+ raise NotImplementedError("Pipes are not supported")
187
+ except NonSupportedFeature: # type: ignore
188
+ pass
199
189
 
200
190
  if tr_name not in dev_proxy.get_attribute_list():
201
191
  if tr_name not in dev_proxy.get_command_list():
@@ -204,20 +194,19 @@ async def infer_signal_character(trl, proxy: DeviceProxy | None = None) -> str:
204
194
  if tr_name in dev_proxy.get_attribute_list():
205
195
  config = await dev_proxy.get_attribute_config(tr_name)
206
196
  if config.writable in [AttrWriteType.READ_WRITE, AttrWriteType.READ_WITH_WRITE]:
207
- return "RW"
197
+ return SignalRW
208
198
  elif config.writable == AttrWriteType.READ:
209
- return "R"
199
+ return SignalR
210
200
  else:
211
- return "W"
201
+ return SignalW
212
202
 
213
203
  if tr_name in dev_proxy.get_command_list():
214
204
  config = await dev_proxy.get_command_config(tr_name)
215
205
  if config.in_type == CmdArgType.DevVoid:
216
- return "X"
206
+ return SignalX
217
207
  elif config.in_type != config.out_type:
218
- raise RuntimeError(
219
- "Commands with different in and out dtypes are not" " supported"
220
- )
208
+ logging.debug("Commands with different in and out dtypes are not supported")
209
+ return None
221
210
  else:
222
- return "RW"
211
+ return SignalRW
223
212
  raise RuntimeError(f"Unable to infer signal character for {trl}")
@@ -1,11 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
- from ophyd_async.core import (
4
- StandardReadable,
5
- )
6
- from ophyd_async.tango.base_devices._base_device import TangoDevice
3
+ from ophyd_async.core import StandardReadable
7
4
  from tango import DeviceProxy
8
5
 
6
+ from ._base_device import TangoDevice
7
+
9
8
 
10
9
  class TangoReadable(TangoDevice, StandardReadable):
11
10
  """