ophyd-async 0.9.0a2__py3-none-any.whl → 0.10.0a2__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.
- ophyd_async/__init__.py +5 -8
- ophyd_async/_docs_parser.py +12 -0
- ophyd_async/_version.py +9 -4
- ophyd_async/core/__init__.py +97 -62
- ophyd_async/core/_derived_signal.py +271 -0
- ophyd_async/core/_derived_signal_backend.py +300 -0
- ophyd_async/core/_detector.py +106 -125
- ophyd_async/core/_device.py +69 -63
- ophyd_async/core/_device_filler.py +65 -1
- ophyd_async/core/_flyer.py +14 -5
- ophyd_async/core/_hdf_dataset.py +29 -22
- ophyd_async/core/_log.py +14 -23
- ophyd_async/core/_mock_signal_backend.py +11 -3
- ophyd_async/core/_protocol.py +65 -45
- ophyd_async/core/_providers.py +28 -9
- ophyd_async/core/_readable.py +44 -35
- ophyd_async/core/_settings.py +36 -27
- ophyd_async/core/_signal.py +262 -170
- ophyd_async/core/_signal_backend.py +56 -13
- ophyd_async/core/_soft_signal_backend.py +16 -11
- ophyd_async/core/_status.py +72 -24
- ophyd_async/core/_table.py +41 -11
- ophyd_async/core/_utils.py +96 -49
- ophyd_async/core/_yaml_settings.py +2 -0
- ophyd_async/epics/__init__.py +1 -0
- ophyd_async/epics/adandor/_andor.py +2 -2
- ophyd_async/epics/adandor/_andor_controller.py +4 -2
- ophyd_async/epics/adandor/_andor_io.py +2 -4
- ophyd_async/epics/adaravis/__init__.py +5 -0
- ophyd_async/epics/adaravis/_aravis.py +4 -8
- ophyd_async/epics/adaravis/_aravis_controller.py +20 -43
- ophyd_async/epics/adaravis/_aravis_io.py +13 -28
- ophyd_async/epics/adcore/__init__.py +23 -8
- ophyd_async/epics/adcore/_core_detector.py +42 -2
- ophyd_async/epics/adcore/_core_io.py +124 -99
- ophyd_async/epics/adcore/_core_logic.py +106 -27
- ophyd_async/epics/adcore/_core_writer.py +12 -8
- ophyd_async/epics/adcore/_hdf_writer.py +21 -38
- ophyd_async/epics/adcore/_single_trigger.py +2 -2
- ophyd_async/epics/adcore/_utils.py +2 -2
- ophyd_async/epics/adkinetix/__init__.py +2 -1
- ophyd_async/epics/adkinetix/_kinetix.py +3 -3
- ophyd_async/epics/adkinetix/_kinetix_controller.py +4 -2
- ophyd_async/epics/adkinetix/_kinetix_io.py +12 -13
- ophyd_async/epics/adpilatus/__init__.py +5 -0
- ophyd_async/epics/adpilatus/_pilatus.py +1 -1
- ophyd_async/epics/adpilatus/_pilatus_controller.py +5 -24
- ophyd_async/epics/adpilatus/_pilatus_io.py +11 -9
- ophyd_async/epics/adsimdetector/__init__.py +8 -1
- ophyd_async/epics/adsimdetector/_sim.py +4 -14
- ophyd_async/epics/adsimdetector/_sim_controller.py +17 -0
- ophyd_async/epics/adsimdetector/_sim_io.py +10 -0
- ophyd_async/epics/advimba/__init__.py +10 -1
- ophyd_async/epics/advimba/_vimba.py +3 -2
- ophyd_async/epics/advimba/_vimba_controller.py +4 -2
- ophyd_async/epics/advimba/_vimba_io.py +23 -28
- ophyd_async/epics/core/_aioca.py +35 -16
- ophyd_async/epics/core/_epics_connector.py +4 -0
- ophyd_async/epics/core/_epics_device.py +2 -0
- ophyd_async/epics/core/_p4p.py +10 -2
- ophyd_async/epics/core/_pvi_connector.py +65 -8
- ophyd_async/epics/core/_signal.py +51 -51
- ophyd_async/epics/core/_util.py +4 -4
- ophyd_async/epics/demo/__init__.py +16 -0
- ophyd_async/epics/demo/__main__.py +31 -0
- ophyd_async/epics/demo/_ioc.py +32 -0
- ophyd_async/epics/demo/_motor.py +82 -0
- ophyd_async/epics/demo/_point_detector.py +42 -0
- ophyd_async/epics/demo/_point_detector_channel.py +22 -0
- ophyd_async/epics/demo/_stage.py +15 -0
- ophyd_async/epics/{sim/mover.db → demo/motor.db} +2 -1
- ophyd_async/epics/demo/point_detector.db +59 -0
- ophyd_async/epics/demo/point_detector_channel.db +21 -0
- ophyd_async/epics/eiger/_eiger.py +1 -3
- ophyd_async/epics/eiger/_eiger_controller.py +11 -4
- ophyd_async/epics/eiger/_eiger_io.py +2 -0
- ophyd_async/epics/eiger/_odin_io.py +1 -2
- ophyd_async/epics/motor.py +65 -28
- ophyd_async/epics/signal.py +4 -1
- ophyd_async/epics/testing/_example_ioc.py +21 -9
- ophyd_async/epics/testing/_utils.py +3 -0
- ophyd_async/epics/testing/test_records.db +8 -0
- ophyd_async/epics/testing/test_records_pva.db +17 -16
- ophyd_async/fastcs/__init__.py +1 -0
- ophyd_async/fastcs/core.py +6 -0
- ophyd_async/fastcs/odin/__init__.py +1 -0
- ophyd_async/fastcs/panda/__init__.py +8 -6
- ophyd_async/fastcs/panda/_block.py +29 -9
- ophyd_async/fastcs/panda/_control.py +5 -0
- ophyd_async/fastcs/panda/_hdf_panda.py +2 -0
- ophyd_async/fastcs/panda/_table.py +9 -6
- ophyd_async/fastcs/panda/_trigger.py +23 -9
- ophyd_async/fastcs/panda/_writer.py +27 -30
- ophyd_async/plan_stubs/__init__.py +2 -0
- ophyd_async/plan_stubs/_ensure_connected.py +1 -0
- ophyd_async/plan_stubs/_fly.py +2 -4
- ophyd_async/plan_stubs/_nd_attributes.py +2 -0
- ophyd_async/plan_stubs/_panda.py +1 -0
- ophyd_async/plan_stubs/_settings.py +43 -16
- ophyd_async/plan_stubs/_utils.py +3 -0
- ophyd_async/plan_stubs/_wait_for_awaitable.py +1 -1
- ophyd_async/sim/__init__.py +24 -14
- ophyd_async/sim/__main__.py +43 -0
- ophyd_async/sim/_blob_detector.py +33 -0
- ophyd_async/sim/_blob_detector_controller.py +48 -0
- ophyd_async/sim/_blob_detector_writer.py +105 -0
- ophyd_async/sim/_mirror_horizontal.py +46 -0
- ophyd_async/sim/_mirror_vertical.py +74 -0
- ophyd_async/sim/_motor.py +233 -0
- ophyd_async/sim/_pattern_generator.py +124 -0
- ophyd_async/sim/_point_detector.py +86 -0
- ophyd_async/sim/_stage.py +19 -0
- ophyd_async/tango/__init__.py +1 -0
- ophyd_async/tango/core/__init__.py +6 -1
- ophyd_async/tango/core/_base_device.py +41 -33
- ophyd_async/tango/core/_converters.py +81 -0
- ophyd_async/tango/core/_signal.py +18 -32
- ophyd_async/tango/core/_tango_readable.py +2 -19
- ophyd_async/tango/core/_tango_transport.py +136 -60
- ophyd_async/tango/core/_utils.py +47 -0
- ophyd_async/tango/{sim → demo}/_counter.py +2 -0
- ophyd_async/tango/{sim → demo}/_detector.py +2 -0
- ophyd_async/tango/{sim → demo}/_mover.py +5 -4
- ophyd_async/tango/{sim → demo}/_tango/_servers.py +4 -0
- ophyd_async/tango/testing/__init__.py +6 -0
- ophyd_async/tango/testing/_one_of_everything.py +200 -0
- ophyd_async/testing/__init__.py +29 -7
- ophyd_async/testing/_assert.py +145 -83
- ophyd_async/testing/_mock_signal_utils.py +56 -70
- ophyd_async/testing/_one_of_everything.py +41 -21
- ophyd_async/testing/_single_derived.py +89 -0
- ophyd_async/testing/_utils.py +3 -0
- {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a2.dist-info}/METADATA +25 -26
- ophyd_async-0.10.0a2.dist-info/RECORD +149 -0
- {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a2.dist-info}/WHEEL +1 -1
- ophyd_async/epics/sim/__init__.py +0 -54
- ophyd_async/epics/sim/_ioc.py +0 -29
- ophyd_async/epics/sim/_mover.py +0 -101
- ophyd_async/epics/sim/_sensor.py +0 -37
- ophyd_async/epics/sim/sensor.db +0 -19
- ophyd_async/sim/_pattern_detector/__init__.py +0 -13
- ophyd_async/sim/_pattern_detector/_pattern_detector.py +0 -42
- ophyd_async/sim/_pattern_detector/_pattern_detector_controller.py +0 -69
- ophyd_async/sim/_pattern_detector/_pattern_detector_writer.py +0 -41
- ophyd_async/sim/_pattern_detector/_pattern_generator.py +0 -214
- ophyd_async/sim/_sim_motor.py +0 -107
- ophyd_async-0.9.0a2.dist-info/RECORD +0 -129
- /ophyd_async/tango/{sim → demo}/__init__.py +0 -0
- /ophyd_async/tango/{sim → demo}/_tango/__init__.py +0 -0
- {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a2.dist-info/licenses}/LICENSE +0 -0
- {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a2.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)
|
ophyd_async/tango/__init__.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
18
|
-
devices.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
15
|
+
TangoDevice.__init__(self, trl, name=name, auto_fill_signals=auto_fill_signals)
|