ophyd-async 0.9.0a2__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 (151) 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 +97 -62
  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 +106 -125
  8. ophyd_async/core/_device.py +69 -63
  9. ophyd_async/core/_device_filler.py +65 -1
  10. ophyd_async/core/_flyer.py +14 -5
  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 +44 -35
  17. ophyd_async/core/_settings.py +36 -27
  18. ophyd_async/core/_signal.py +262 -170
  19. ophyd_async/core/_signal_backend.py +56 -13
  20. ophyd_async/core/_soft_signal_backend.py +16 -11
  21. ophyd_async/core/_status.py +72 -24
  22. ophyd_async/core/_table.py +37 -8
  23. ophyd_async/core/_utils.py +96 -49
  24. ophyd_async/core/_yaml_settings.py +2 -0
  25. ophyd_async/epics/__init__.py +1 -0
  26. ophyd_async/epics/adandor/_andor.py +2 -2
  27. ophyd_async/epics/adandor/_andor_controller.py +4 -2
  28. ophyd_async/epics/adandor/_andor_io.py +2 -4
  29. ophyd_async/epics/adaravis/__init__.py +5 -0
  30. ophyd_async/epics/adaravis/_aravis.py +4 -8
  31. ophyd_async/epics/adaravis/_aravis_controller.py +20 -43
  32. ophyd_async/epics/adaravis/_aravis_io.py +13 -28
  33. ophyd_async/epics/adcore/__init__.py +23 -8
  34. ophyd_async/epics/adcore/_core_detector.py +42 -2
  35. ophyd_async/epics/adcore/_core_io.py +124 -99
  36. ophyd_async/epics/adcore/_core_logic.py +106 -27
  37. ophyd_async/epics/adcore/_core_writer.py +12 -8
  38. ophyd_async/epics/adcore/_hdf_writer.py +21 -38
  39. ophyd_async/epics/adcore/_single_trigger.py +2 -2
  40. ophyd_async/epics/adcore/_utils.py +2 -2
  41. ophyd_async/epics/adkinetix/__init__.py +2 -1
  42. ophyd_async/epics/adkinetix/_kinetix.py +3 -3
  43. ophyd_async/epics/adkinetix/_kinetix_controller.py +4 -2
  44. ophyd_async/epics/adkinetix/_kinetix_io.py +12 -13
  45. ophyd_async/epics/adpilatus/__init__.py +5 -0
  46. ophyd_async/epics/adpilatus/_pilatus.py +1 -1
  47. ophyd_async/epics/adpilatus/_pilatus_controller.py +5 -24
  48. ophyd_async/epics/adpilatus/_pilatus_io.py +11 -9
  49. ophyd_async/epics/adsimdetector/__init__.py +8 -1
  50. ophyd_async/epics/adsimdetector/_sim.py +4 -14
  51. ophyd_async/epics/adsimdetector/_sim_controller.py +17 -0
  52. ophyd_async/epics/adsimdetector/_sim_io.py +10 -0
  53. ophyd_async/epics/advimba/__init__.py +10 -1
  54. ophyd_async/epics/advimba/_vimba.py +3 -2
  55. ophyd_async/epics/advimba/_vimba_controller.py +4 -2
  56. ophyd_async/epics/advimba/_vimba_io.py +23 -28
  57. ophyd_async/epics/core/_aioca.py +35 -16
  58. ophyd_async/epics/core/_epics_connector.py +4 -0
  59. ophyd_async/epics/core/_epics_device.py +2 -0
  60. ophyd_async/epics/core/_p4p.py +10 -2
  61. ophyd_async/epics/core/_pvi_connector.py +65 -8
  62. ophyd_async/epics/core/_signal.py +51 -51
  63. ophyd_async/epics/core/_util.py +4 -4
  64. ophyd_async/epics/demo/__init__.py +16 -0
  65. ophyd_async/epics/demo/__main__.py +31 -0
  66. ophyd_async/epics/demo/_ioc.py +32 -0
  67. ophyd_async/epics/demo/_motor.py +82 -0
  68. ophyd_async/epics/demo/_point_detector.py +42 -0
  69. ophyd_async/epics/demo/_point_detector_channel.py +22 -0
  70. ophyd_async/epics/demo/_stage.py +15 -0
  71. ophyd_async/epics/{sim/mover.db → demo/motor.db} +2 -1
  72. ophyd_async/epics/demo/point_detector.db +59 -0
  73. ophyd_async/epics/demo/point_detector_channel.db +21 -0
  74. ophyd_async/epics/eiger/_eiger.py +1 -3
  75. ophyd_async/epics/eiger/_eiger_controller.py +11 -4
  76. ophyd_async/epics/eiger/_eiger_io.py +2 -0
  77. ophyd_async/epics/eiger/_odin_io.py +1 -2
  78. ophyd_async/epics/motor.py +65 -28
  79. ophyd_async/epics/signal.py +4 -1
  80. ophyd_async/epics/testing/_example_ioc.py +21 -9
  81. ophyd_async/epics/testing/_utils.py +3 -0
  82. ophyd_async/epics/testing/test_records.db +8 -0
  83. ophyd_async/epics/testing/test_records_pva.db +17 -16
  84. ophyd_async/fastcs/__init__.py +1 -0
  85. ophyd_async/fastcs/core.py +6 -0
  86. ophyd_async/fastcs/odin/__init__.py +1 -0
  87. ophyd_async/fastcs/panda/__init__.py +8 -6
  88. ophyd_async/fastcs/panda/_block.py +29 -9
  89. ophyd_async/fastcs/panda/_control.py +5 -0
  90. ophyd_async/fastcs/panda/_hdf_panda.py +2 -0
  91. ophyd_async/fastcs/panda/_table.py +9 -6
  92. ophyd_async/fastcs/panda/_trigger.py +23 -9
  93. ophyd_async/fastcs/panda/_writer.py +27 -30
  94. ophyd_async/plan_stubs/__init__.py +2 -0
  95. ophyd_async/plan_stubs/_ensure_connected.py +1 -0
  96. ophyd_async/plan_stubs/_fly.py +2 -4
  97. ophyd_async/plan_stubs/_nd_attributes.py +2 -0
  98. ophyd_async/plan_stubs/_panda.py +1 -0
  99. ophyd_async/plan_stubs/_settings.py +43 -16
  100. ophyd_async/plan_stubs/_utils.py +3 -0
  101. ophyd_async/plan_stubs/_wait_for_awaitable.py +1 -1
  102. ophyd_async/sim/__init__.py +24 -14
  103. ophyd_async/sim/__main__.py +43 -0
  104. ophyd_async/sim/_blob_detector.py +33 -0
  105. ophyd_async/sim/_blob_detector_controller.py +48 -0
  106. ophyd_async/sim/_blob_detector_writer.py +105 -0
  107. ophyd_async/sim/_mirror_horizontal.py +46 -0
  108. ophyd_async/sim/_mirror_vertical.py +74 -0
  109. ophyd_async/sim/_motor.py +233 -0
  110. ophyd_async/sim/_pattern_generator.py +124 -0
  111. ophyd_async/sim/_point_detector.py +86 -0
  112. ophyd_async/sim/_stage.py +19 -0
  113. ophyd_async/tango/__init__.py +1 -0
  114. ophyd_async/tango/core/__init__.py +6 -1
  115. ophyd_async/tango/core/_base_device.py +41 -33
  116. ophyd_async/tango/core/_converters.py +81 -0
  117. ophyd_async/tango/core/_signal.py +18 -32
  118. ophyd_async/tango/core/_tango_readable.py +2 -19
  119. ophyd_async/tango/core/_tango_transport.py +136 -60
  120. ophyd_async/tango/core/_utils.py +47 -0
  121. ophyd_async/tango/{sim → demo}/_counter.py +2 -0
  122. ophyd_async/tango/{sim → demo}/_detector.py +2 -0
  123. ophyd_async/tango/{sim → demo}/_mover.py +5 -4
  124. ophyd_async/tango/{sim → demo}/_tango/_servers.py +4 -0
  125. ophyd_async/tango/testing/__init__.py +6 -0
  126. ophyd_async/tango/testing/_one_of_everything.py +200 -0
  127. ophyd_async/testing/__init__.py +29 -7
  128. ophyd_async/testing/_assert.py +137 -81
  129. ophyd_async/testing/_mock_signal_utils.py +56 -70
  130. ophyd_async/testing/_one_of_everything.py +41 -21
  131. ophyd_async/testing/_single_derived.py +87 -0
  132. ophyd_async/testing/_utils.py +3 -0
  133. {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a1.dist-info}/METADATA +25 -26
  134. ophyd_async-0.10.0a1.dist-info/RECORD +149 -0
  135. {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a1.dist-info}/WHEEL +1 -1
  136. ophyd_async/epics/sim/__init__.py +0 -54
  137. ophyd_async/epics/sim/_ioc.py +0 -29
  138. ophyd_async/epics/sim/_mover.py +0 -101
  139. ophyd_async/epics/sim/_sensor.py +0 -37
  140. ophyd_async/epics/sim/sensor.db +0 -19
  141. ophyd_async/sim/_pattern_detector/__init__.py +0 -13
  142. ophyd_async/sim/_pattern_detector/_pattern_detector.py +0 -42
  143. ophyd_async/sim/_pattern_detector/_pattern_detector_controller.py +0 -69
  144. ophyd_async/sim/_pattern_detector/_pattern_detector_writer.py +0 -41
  145. ophyd_async/sim/_pattern_detector/_pattern_generator.py +0 -214
  146. ophyd_async/sim/_sim_motor.py +0 -107
  147. ophyd_async-0.9.0a2.dist-info/RECORD +0 -129
  148. /ophyd_async/tango/{sim → demo}/__init__.py +0 -0
  149. /ophyd_async/tango/{sim → demo}/_tango/__init__.py +0 -0
  150. {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a1.dist-info/licenses}/LICENSE +0 -0
  151. {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,124 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import time
5
+ from pathlib import Path
6
+
7
+ import h5py
8
+ import numpy as np
9
+
10
+ # raw data path
11
+ DATA_PATH = "/entry/data/data"
12
+
13
+ # pixel sum path
14
+ SUM_PATH = "/entry/sum"
15
+
16
+
17
+ def generate_gaussian_blob(height: int, width: int) -> np.ndarray:
18
+ """Make a Gaussian Blob with float values in range 0..1."""
19
+ x, y = np.meshgrid(np.linspace(-1, 1, width), np.linspace(-1, 1, height))
20
+ d = np.sqrt(x * x + y * y)
21
+ blob = np.exp(-(d**2))
22
+ return blob
23
+
24
+
25
+ def generate_interesting_pattern(
26
+ x: float, y: float, channel: int, offset: float
27
+ ) -> float:
28
+ """Return a float value in range 0..1.
29
+
30
+ Interesting in x and y in range -10..10
31
+ """
32
+ return (np.sin(x) ** channel + np.cos(x * y + offset) + 2) / 4
33
+
34
+
35
+ class PatternFile:
36
+ def __init__(
37
+ self,
38
+ path: Path,
39
+ width: int = 320,
40
+ height: int = 240,
41
+ ):
42
+ self.file = h5py.File(path, "w", libver="latest")
43
+ self.data = self.file.create_dataset(
44
+ name=DATA_PATH,
45
+ shape=(0, height, width),
46
+ dtype=np.uint8,
47
+ maxshape=(None, height, width),
48
+ )
49
+ self.sum = self.file.create_dataset(
50
+ name=SUM_PATH,
51
+ shape=(0,),
52
+ dtype=np.int64,
53
+ maxshape=(None,),
54
+ )
55
+ # Once datasets written, can switch the model to single writer multiple reader
56
+ self.file.swmr_mode = True
57
+ self.blob = generate_gaussian_blob(height, width) * np.iinfo(np.uint8).max
58
+ self.image_counter = 0
59
+ self.e = asyncio.Event()
60
+
61
+ def write_image_to_file(self, intensity: float):
62
+ data = np.floor(self.blob * intensity)
63
+ for dset, value in ((self.data, data), (self.sum, np.sum(data))):
64
+ dset.resize(self.image_counter + 1, axis=0)
65
+ dset[self.image_counter] = value
66
+ dset.flush()
67
+ self.image_counter += 1
68
+ self.e.set()
69
+ self.e.clear()
70
+
71
+ def close(self):
72
+ self.file.close()
73
+
74
+
75
+ class PatternGenerator:
76
+ """Generates pattern images in files."""
77
+
78
+ def __init__(self, sleep=asyncio.sleep):
79
+ self._x = 0.0
80
+ self._y = 0.0
81
+ self._file: PatternFile | None = None
82
+ self.sleep = sleep
83
+
84
+ def set_x(self, x: float):
85
+ self._x = x
86
+
87
+ def set_y(self, y: float):
88
+ self._y = y
89
+
90
+ def generate_point(self, channel: int = 1, high_energy: bool = False) -> float:
91
+ """Make a point between 0 and 1 based on x and y."""
92
+ offset = 100 if high_energy else 10
93
+ return generate_interesting_pattern(self._x, self._y, channel, offset)
94
+
95
+ def open_file(self, path: Path, width: int, height: int):
96
+ self._file = PatternFile(path, width, height)
97
+
98
+ def _get_file(self) -> PatternFile:
99
+ if not self._file:
100
+ raise RuntimeError("open_file not run")
101
+ return self._file
102
+
103
+ async def write_images_to_file(
104
+ self, exposure: float, period: float, number_of_frames: int
105
+ ):
106
+ file = self._get_file()
107
+ start = time.monotonic()
108
+ for i in range(1, number_of_frames + 1):
109
+ deadline = start + i * period
110
+ timeout = deadline - time.monotonic()
111
+ await self.sleep(timeout)
112
+ intensity = self.generate_point() * exposure
113
+ file.write_image_to_file(intensity)
114
+
115
+ async def wait_for_next_index(self, timeout: float):
116
+ await asyncio.wait_for(self._get_file().e.wait(), timeout)
117
+
118
+ def get_last_index(self) -> int:
119
+ return self._get_file().image_counter
120
+
121
+ def close_file(self):
122
+ if self._file:
123
+ self._file.close()
124
+ self._file = None
@@ -0,0 +1,86 @@
1
+ import asyncio
2
+ import time
3
+
4
+ import numpy as np
5
+
6
+ from ophyd_async.core import (
7
+ AsyncStatus,
8
+ DeviceVector,
9
+ SignalR,
10
+ StandardReadable,
11
+ StrictEnum,
12
+ gather_dict,
13
+ soft_signal_r_and_setter,
14
+ soft_signal_rw,
15
+ )
16
+ from ophyd_async.core import StandardReadableFormat as Format
17
+
18
+ from ._pattern_generator import PatternGenerator
19
+
20
+
21
+ class EnergyMode(StrictEnum):
22
+ """Energy mode for `SimPointDetector`."""
23
+
24
+ LOW = "Low Energy"
25
+ """Low energy mode"""
26
+
27
+ HIGH = "High Energy"
28
+ """High energy mode"""
29
+
30
+
31
+ class SimPointDetectorChannel(StandardReadable):
32
+ def __init__(self, value_signal: SignalR[int], name=""):
33
+ with self.add_children_as_readables(Format.HINTED_SIGNAL):
34
+ self.value = value_signal
35
+ with self.add_children_as_readables(Format.CONFIG_SIGNAL):
36
+ self.mode = soft_signal_rw(EnergyMode)
37
+ super().__init__(name)
38
+
39
+
40
+ class SimPointDetector(StandardReadable):
41
+ """Simalutes a point detector with multiple channels."""
42
+
43
+ def __init__(
44
+ self, generator: PatternGenerator, num_channels: int = 3, name: str = ""
45
+ ) -> None:
46
+ self._generator = generator
47
+ self.acquire_time = soft_signal_rw(float, 0.1)
48
+ self.acquiring, self._set_acquiring = soft_signal_r_and_setter(bool)
49
+ self._value_signals = dict(
50
+ soft_signal_r_and_setter(int) for _ in range(num_channels)
51
+ )
52
+ with self.add_children_as_readables():
53
+ self.channel = DeviceVector(
54
+ {
55
+ i + 1: SimPointDetectorChannel(value_signal)
56
+ for i, value_signal in enumerate(self._value_signals)
57
+ }
58
+ )
59
+ super().__init__(name=name)
60
+
61
+ async def _update_values(self, acquire_time: float):
62
+ # Get the modes
63
+ modes = await gather_dict(
64
+ {channel: channel.mode.get_value() for channel in self.channel.values()}
65
+ )
66
+ start = time.monotonic()
67
+ # Make an array of relative update times at 10Hz intervals
68
+ update_times = np.arange(0.1, acquire_time, 0.1)
69
+ # With the end position appended
70
+ update_times = np.concatenate((update_times, [acquire_time]))
71
+ for update_time in update_times:
72
+ # Calculate how long to wait to get there
73
+ relative_time = time.monotonic() - start
74
+ await asyncio.sleep(update_time - relative_time)
75
+ # Update the channel value
76
+ for i, channel in self.channel.items():
77
+ high_energy = modes[channel] == EnergyMode.HIGH
78
+ point = self._generator.generate_point(i, high_energy)
79
+ setter = self._value_signals[channel.value]
80
+ setter(int(point * 10000 * update_time))
81
+
82
+ @AsyncStatus.wrap
83
+ async def trigger(self):
84
+ for setter in self._value_signals.values():
85
+ setter(0)
86
+ await self._update_values(await self.acquire_time.get_value())
@@ -0,0 +1,19 @@
1
+ from ophyd_async.core import StandardReadable
2
+ from ophyd_async.sim._pattern_generator import PatternGenerator
3
+
4
+ from ._motor import SimMotor
5
+
6
+
7
+ class SimStage(StandardReadable):
8
+ """A simulated sample stage with X and Y movables."""
9
+
10
+ def __init__(self, pattern_generator: PatternGenerator, name="") -> None:
11
+ # Define some child Devices
12
+ with self.add_children_as_readables():
13
+ self.x = SimMotor(instant=False)
14
+ self.y = SimMotor(instant=False)
15
+ # Tell the pattern generator about the motor positions
16
+ self.x.user_readback.subscribe_value(pattern_generator.set_x)
17
+ self.y.user_readback.subscribe_value(pattern_generator.set_y)
18
+ # Set name of device and child devices
19
+ super().__init__(name=name)
@@ -0,0 +1 @@
1
+ """Tango support for Signals, and Devices that use them."""
@@ -1,4 +1,4 @@
1
- from ._base_device import TangoDevice, TangoPolling
1
+ from ._base_device import TangoDevice, TangoDeviceConnector, TangoPolling
2
2
  from ._signal import (
3
3
  infer_python_type,
4
4
  infer_signal_type,
@@ -19,10 +19,12 @@ from ._tango_transport import (
19
19
  get_tango_trl,
20
20
  get_trl_descriptor,
21
21
  )
22
+ from ._utils import DevStateEnum, get_device_trl_and_attr, get_full_attr_trl
22
23
 
23
24
  __all__ = [
24
25
  "AttributeProxy",
25
26
  "CommandProxy",
27
+ "DevStateEnum",
26
28
  "ensure_proper_executor",
27
29
  "TangoSignalBackend",
28
30
  "get_python_type",
@@ -39,4 +41,7 @@ __all__ = [
39
41
  "TangoDevice",
40
42
  "TangoReadable",
41
43
  "TangoPolling",
44
+ "TangoDeviceConnector",
45
+ "get_device_trl_and_attr",
46
+ "get_full_attr_trl",
42
47
  ]
@@ -4,27 +4,23 @@ from dataclasses import dataclass
4
4
  from typing import Any, Generic, TypeVar
5
5
 
6
6
  from ophyd_async.core import Device, DeviceConnector, DeviceFiller, LazyMock
7
- from tango import DeviceProxy as DeviceProxy
7
+ from tango import DeviceProxy
8
8
  from tango.asyncio import DeviceProxy as AsyncDeviceProxy
9
9
 
10
10
  from ._signal import TangoSignalBackend, infer_python_type, infer_signal_type
11
+ from ._utils import get_full_attr_trl
11
12
 
12
13
  T = TypeVar("T")
13
14
 
14
15
 
15
16
  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.
17
+ """General class for TangoDevices.
18
+
19
+ Extends Device to provide attributes for Tango devices.
20
+
21
+ :param trl: Tango resource locator, typically of the device server.
22
+ An asynchronous DeviceProxy object will be created using the
23
+ trl and awaited when the device is connected.
28
24
  """
29
25
 
30
26
  trl: str = ""
@@ -32,13 +28,15 @@ class TangoDevice(Device):
32
28
 
33
29
  def __init__(
34
30
  self,
35
- trl: str | None = None,
36
- device_proxy: DeviceProxy | None = None,
31
+ trl: str | None,
37
32
  support_events: bool = False,
38
33
  name: str = "",
34
+ auto_fill_signals: bool = True,
39
35
  ) -> None:
40
36
  connector = TangoDeviceConnector(
41
- trl=trl, device_proxy=device_proxy, support_events=support_events
37
+ trl=trl,
38
+ support_events=support_events,
39
+ auto_fill_signals=auto_fill_signals,
42
40
  )
43
41
  super().__init__(name=name, connector=connector)
44
42
 
@@ -74,12 +72,12 @@ class TangoDeviceConnector(DeviceConnector):
74
72
  def __init__(
75
73
  self,
76
74
  trl: str | None,
77
- device_proxy: DeviceProxy | None,
78
75
  support_events: bool,
76
+ auto_fill_signals: bool = True,
79
77
  ) -> None:
80
78
  self.trl = trl
81
- self.proxy = device_proxy
82
79
  self._support_events = support_events
80
+ self._auto_fill_signals = auto_fill_signals
83
81
 
84
82
  def create_children_from_annotations(self, device: Device):
85
83
  if not hasattr(self, "filler"):
@@ -87,7 +85,7 @@ class TangoDeviceConnector(DeviceConnector):
87
85
  device=device,
88
86
  signal_backend_factory=TangoSignalBackend,
89
87
  device_connector_factory=lambda: TangoDeviceConnector(
90
- None, None, self._support_events
88
+ None, self._support_events
91
89
  ),
92
90
  )
93
91
  list(self.filler.create_devices_from_annotations(filled=False))
@@ -105,28 +103,38 @@ class TangoDeviceConnector(DeviceConnector):
105
103
  return await super().connect_mock(device, mock)
106
104
 
107
105
  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
-
106
+ if not self.trl:
107
+ raise RuntimeError(f"Could not created Device Proxy for TRL {self.trl}")
108
+ self.proxy = await AsyncDeviceProxy(self.trl)
115
109
  children = sorted(
116
110
  set()
117
111
  .union(self.proxy.get_attribute_list())
118
112
  .union(self.proxy.get_command_list())
119
113
  )
114
+
115
+ children = [
116
+ child for child in children if child not in self.filler.ignored_signals
117
+ ]
118
+
119
+ not_filled = {unfilled for unfilled, _ in device.children()}
120
+
121
+ # If auto_fill_signals is True, fill all children inferred from the device
122
+ # else fill only the children that are annotated
120
123
  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)
124
+ if self._auto_fill_signals or name in not_filled:
125
+ # TODO: strip attribute name
126
+ full_trl = get_full_attr_trl(self.trl, name)
127
+ signal_type = await infer_signal_type(full_trl, self.proxy)
128
+ if signal_type:
129
+ backend = self.filler.fill_child_signal(name, signal_type)
130
+ # don't overlaod datatype if provided by annotation
131
+ if backend.datatype is None:
132
+ backend.datatype = await infer_python_type(full_trl, self.proxy)
133
+ backend.set_trl(full_trl)
134
+
128
135
  # Check that all the requested children have been filled
129
136
  self.filler.check_filled(f"{self.trl}: {children}")
137
+
130
138
  # Set the name of the device to name all children
131
139
  device.set_name(device.name)
132
140
  return await super().connect_real(device, timeout, force_reconnect)
@@ -0,0 +1,81 @@
1
+ from typing import Any, Generic
2
+
3
+ import numpy as np
4
+ from numpy.typing import NDArray
5
+
6
+ from ophyd_async.core import (
7
+ SignalDatatypeT,
8
+ )
9
+ from tango import (
10
+ DevState,
11
+ )
12
+
13
+ from ._utils import DevStateEnum
14
+
15
+
16
+ class TangoConverter(Generic[SignalDatatypeT]):
17
+ def write_value(self, value: Any) -> Any:
18
+ return value
19
+
20
+ def value(self, value: Any) -> Any:
21
+ return value
22
+
23
+
24
+ class TangoEnumConverter(TangoConverter):
25
+ def __init__(self, labels: list[str]):
26
+ self._labels = labels
27
+
28
+ def write_value(self, value: str):
29
+ if not isinstance(value, str):
30
+ raise TypeError("TangoEnumConverter expects str value")
31
+ return self._labels.index(value)
32
+
33
+ def value(self, value: int):
34
+ return self._labels[value]
35
+
36
+
37
+ class TangoEnumArrayConverter(TangoConverter):
38
+ def __init__(self, labels: list[str]):
39
+ self._labels = labels
40
+
41
+ def write_value(self, value: NDArray[np.str_]) -> NDArray[np.integer]:
42
+ vfunc = np.vectorize(self._labels.index)
43
+ new_array = vfunc(value)
44
+ return new_array
45
+
46
+ def value(self, value: NDArray[np.integer]) -> NDArray[np.str_]:
47
+ vfunc = np.vectorize(self._labels.__getitem__)
48
+ new_array = vfunc(value)
49
+ return new_array
50
+
51
+
52
+ class TangoDevStateConverter(TangoConverter):
53
+ _labels = [e.value for e in DevStateEnum]
54
+
55
+ def write_value(self, value: str) -> DevState:
56
+ idx = self._labels.index(value)
57
+ return DevState(idx)
58
+
59
+ def value(self, value: DevState) -> str:
60
+ idx = int(value)
61
+ return self._labels[idx]
62
+
63
+
64
+ class TangoDevStateArrayConverter(TangoConverter):
65
+ _labels = [e.value for e in DevStateEnum]
66
+
67
+ def _write_convert(self, value):
68
+ return DevState(self._labels.index(value))
69
+
70
+ def _convert(self, value):
71
+ return self._labels[int(value)]
72
+
73
+ def write_value(self, value: NDArray[np.str_]) -> NDArray[DevState]:
74
+ vfunc = np.vectorize(self._write_convert, otypes=[DevState])
75
+ new_array = vfunc(value)
76
+ return new_array
77
+
78
+ def value(self, value: NDArray[DevState]) -> NDArray[np.str_]:
79
+ vfunc = np.vectorize(self._convert)
80
+ new_array = vfunc(value)
81
+ return new_array
@@ -1,4 +1,4 @@
1
- """Tango Signals over Pytango"""
1
+ """Tango Signals over Pytango."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -22,11 +22,11 @@ from tango import (
22
22
  CmdArgType,
23
23
  DeviceProxy,
24
24
  DevState,
25
- NonSupportedFeature, # type: ignore
26
25
  )
27
26
  from tango.asyncio import DeviceProxy as AsyncDeviceProxy
28
27
 
29
28
  from ._tango_transport import TangoSignalBackend, get_python_type
29
+ from ._utils import get_device_trl_and_attr
30
30
 
31
31
  logger = logging.getLogger("ophyd_async")
32
32
 
@@ -35,20 +35,18 @@ def make_backend(
35
35
  datatype: type[SignalDatatypeT] | None,
36
36
  read_trl: str = "",
37
37
  write_trl: str = "",
38
- device_proxy: DeviceProxy | None = None,
39
38
  ) -> TangoSignalBackend:
40
- return TangoSignalBackend(datatype, read_trl, write_trl, device_proxy)
39
+ return TangoSignalBackend(datatype, read_trl, write_trl)
41
40
 
42
41
 
43
42
  def tango_signal_rw(
44
43
  datatype: type[SignalDatatypeT],
45
44
  read_trl: str,
46
45
  write_trl: str = "",
47
- device_proxy: DeviceProxy | None = None,
48
46
  timeout: float = DEFAULT_TIMEOUT,
49
47
  name: str = "",
50
48
  ) -> SignalRW[SignalDatatypeT]:
51
- """Create a `SignalRW` backed by 1 or 2 Tango Attribute/Command
49
+ """Create a `SignalRW` backed by 1 or 2 Tango Attribute/Command.
52
50
 
53
51
  Parameters
54
52
  ----------
@@ -58,25 +56,23 @@ def tango_signal_rw(
58
56
  The Attribute/Command to read and monitor
59
57
  write_trl:
60
58
  If given, use this Attribute/Command to write to, otherwise use read_trl
61
- device_proxy:
62
- If given, this DeviceProxy will be used
63
59
  timeout:
64
60
  The timeout for the read and write operations
65
61
  name:
66
62
  The name of the Signal
63
+
67
64
  """
68
- backend = make_backend(datatype, read_trl, write_trl or read_trl, device_proxy)
65
+ backend = make_backend(datatype, read_trl, write_trl or read_trl)
69
66
  return SignalRW(backend, timeout=timeout, name=name)
70
67
 
71
68
 
72
69
  def tango_signal_r(
73
70
  datatype: type[SignalDatatypeT],
74
71
  read_trl: str,
75
- device_proxy: DeviceProxy | None = None,
76
72
  timeout: float = DEFAULT_TIMEOUT,
77
73
  name: str = "",
78
74
  ) -> SignalR[SignalDatatypeT]:
79
- """Create a `SignalR` backed by 1 Tango Attribute/Command
75
+ """Create a `SignalR` backed by 1 Tango Attribute/Command.
80
76
 
81
77
  Parameters
82
78
  ----------
@@ -84,25 +80,23 @@ def tango_signal_r(
84
80
  Check that the Attribute/Command is of this type
85
81
  read_trl:
86
82
  The Attribute/Command to read and monitor
87
- device_proxy:
88
- If given, this DeviceProxy will be used
89
83
  timeout:
90
84
  The timeout for the read operation
91
85
  name:
92
86
  The name of the Signal
87
+
93
88
  """
94
- backend = make_backend(datatype, read_trl, read_trl, device_proxy)
89
+ backend = make_backend(datatype, read_trl, read_trl)
95
90
  return SignalR(backend, timeout=timeout, name=name)
96
91
 
97
92
 
98
93
  def tango_signal_w(
99
94
  datatype: type[SignalDatatypeT],
100
95
  write_trl: str,
101
- device_proxy: DeviceProxy | None = None,
102
96
  timeout: float = DEFAULT_TIMEOUT,
103
97
  name: str = "",
104
98
  ) -> SignalW[SignalDatatypeT]:
105
- """Create a `SignalW` backed by 1 Tango Attribute/Command
99
+ """Create a `SignalW` backed by 1 Tango Attribute/Command.
106
100
 
107
101
  Parameters
108
102
  ----------
@@ -110,45 +104,43 @@ def tango_signal_w(
110
104
  Check that the Attribute/Command is of this type
111
105
  write_trl:
112
106
  The Attribute/Command to write to
113
- device_proxy:
114
- If given, this DeviceProxy will be used
115
107
  timeout:
116
108
  The timeout for the write operation
117
109
  name:
118
110
  The name of the Signal
111
+
119
112
  """
120
- backend = make_backend(datatype, write_trl, write_trl, device_proxy)
113
+ backend = make_backend(datatype, write_trl, write_trl)
121
114
  return SignalW(backend, timeout=timeout, name=name)
122
115
 
123
116
 
124
117
  def tango_signal_x(
125
118
  write_trl: str,
126
- device_proxy: DeviceProxy | None = None,
127
119
  timeout: float = DEFAULT_TIMEOUT,
128
120
  name: str = "",
129
121
  ) -> SignalX:
130
- """Create a `SignalX` backed by 1 Tango Attribute/Command
122
+ """Create a `SignalX` backed by 1 Tango Attribute/Command.
131
123
 
132
124
  Parameters
133
125
  ----------
134
126
  write_trl:
135
127
  The Attribute/Command to write its initial value to on execute
136
- device_proxy:
137
- If given, this DeviceProxy will be used
138
128
  timeout:
139
129
  The timeout for the command operation
140
130
  name:
141
131
  The name of the Signal
132
+
142
133
  """
143
- backend = make_backend(None, write_trl, write_trl, device_proxy)
134
+ backend = make_backend(None, write_trl, write_trl)
144
135
  return SignalX(backend, timeout=timeout, name=name)
145
136
 
146
137
 
147
138
  async def infer_python_type(
148
139
  trl: str = "", proxy: DeviceProxy | None = None
149
140
  ) -> object | npt.NDArray | type[DevState] | IntEnum:
141
+ """Infers the python type from the TRL."""
150
142
  # TODO: work out if this is still needed
151
- device_trl, tr_name = trl.rsplit("/", 1)
143
+ device_trl, tr_name = get_device_trl_and_attr(trl)
152
144
  if proxy is None:
153
145
  dev_proxy = await AsyncDeviceProxy(device_trl)
154
146
  else:
@@ -177,18 +169,12 @@ async def infer_python_type(
177
169
  async def infer_signal_type(
178
170
  trl, proxy: DeviceProxy | None = None
179
171
  ) -> type[Signal] | None:
180
- device_trl, tr_name = trl.rsplit("/", 1)
172
+ device_trl, tr_name = get_device_trl_and_attr(trl)
181
173
  if proxy is None:
182
174
  dev_proxy = await AsyncDeviceProxy(device_trl)
183
175
  else:
184
176
  dev_proxy = proxy
185
177
 
186
- try:
187
- if tr_name in dev_proxy.get_pipe_list():
188
- raise NotImplementedError("Pipes are not supported")
189
- except NonSupportedFeature: # type: ignore
190
- pass
191
-
192
178
  if tr_name not in dev_proxy.get_attribute_list():
193
179
  if tr_name not in dev_proxy.get_command_list():
194
180
  raise RuntimeError(f"Cannot find {tr_name} in {device_trl}")
@@ -1,32 +1,15 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from ophyd_async.core import StandardReadable
4
- from tango import DeviceProxy
5
4
 
6
5
  from ._base_device import TangoDevice
7
6
 
8
7
 
9
8
  class TangoReadable(TangoDevice, StandardReadable):
10
- """
11
- General class for readable TangoDevices. Extends StandardReadable to provide
12
- attributes for Tango devices.
13
-
14
- Usage: to proper signals mount should be awaited:
15
- new_device = await TangoDevice(<tango_device>)
16
-
17
- Attributes
18
- ----------
19
- trl : str
20
- Tango resource locator, typically of the device server.
21
- proxy : AsyncDeviceProxy
22
- AsyncDeviceProxy object for the device. This is created when the
23
- device is connected.
24
- """
25
-
26
9
  def __init__(
27
10
  self,
28
11
  trl: str | None = None,
29
- device_proxy: DeviceProxy | None = None,
30
12
  name: str = "",
13
+ auto_fill_signals: bool = True,
31
14
  ) -> None:
32
- TangoDevice.__init__(self, trl, device_proxy=device_proxy, name=name)
15
+ TangoDevice.__init__(self, trl, name=name, auto_fill_signals=auto_fill_signals)