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.
- ophyd_async/__init__.py +5 -8
- ophyd_async/_docs_parser.py +12 -0
- ophyd_async/_version.py +9 -4
- ophyd_async/core/__init__.py +102 -74
- ophyd_async/core/_derived_signal.py +271 -0
- ophyd_async/core/_derived_signal_backend.py +300 -0
- ophyd_async/core/_detector.py +158 -153
- ophyd_async/core/_device.py +143 -115
- ophyd_async/core/_device_filler.py +82 -9
- ophyd_async/core/_flyer.py +16 -7
- 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 +74 -58
- ophyd_async/core/_settings.py +113 -0
- ophyd_async/core/_signal.py +304 -174
- ophyd_async/core/_signal_backend.py +60 -14
- ophyd_async/core/_soft_signal_backend.py +18 -12
- ophyd_async/core/_status.py +72 -24
- ophyd_async/core/_table.py +54 -17
- ophyd_async/core/_utils.py +101 -52
- ophyd_async/core/_yaml_settings.py +66 -0
- ophyd_async/epics/__init__.py +1 -0
- ophyd_async/epics/adandor/__init__.py +9 -0
- ophyd_async/epics/adandor/_andor.py +45 -0
- ophyd_async/epics/adandor/_andor_controller.py +51 -0
- ophyd_async/epics/adandor/_andor_io.py +34 -0
- ophyd_async/epics/adaravis/__init__.py +8 -1
- ophyd_async/epics/adaravis/_aravis.py +23 -41
- ophyd_async/epics/adaravis/_aravis_controller.py +23 -55
- ophyd_async/epics/adaravis/_aravis_io.py +13 -28
- ophyd_async/epics/adcore/__init__.py +36 -14
- ophyd_async/epics/adcore/_core_detector.py +81 -0
- ophyd_async/epics/adcore/_core_io.py +145 -95
- ophyd_async/epics/adcore/_core_logic.py +179 -88
- ophyd_async/epics/adcore/_core_writer.py +223 -0
- ophyd_async/epics/adcore/_hdf_writer.py +51 -92
- ophyd_async/epics/adcore/_jpeg_writer.py +26 -0
- ophyd_async/epics/adcore/_single_trigger.py +6 -5
- ophyd_async/epics/adcore/_tiff_writer.py +26 -0
- ophyd_async/epics/adcore/_utils.py +3 -2
- ophyd_async/epics/adkinetix/__init__.py +2 -1
- ophyd_async/epics/adkinetix/_kinetix.py +32 -27
- ophyd_async/epics/adkinetix/_kinetix_controller.py +11 -21
- ophyd_async/epics/adkinetix/_kinetix_io.py +12 -13
- ophyd_async/epics/adpilatus/__init__.py +7 -2
- ophyd_async/epics/adpilatus/_pilatus.py +28 -40
- ophyd_async/epics/adpilatus/_pilatus_controller.py +25 -22
- ophyd_async/epics/adpilatus/_pilatus_io.py +11 -9
- ophyd_async/epics/adsimdetector/__init__.py +8 -1
- ophyd_async/epics/adsimdetector/_sim.py +22 -16
- ophyd_async/epics/adsimdetector/_sim_controller.py +9 -43
- ophyd_async/epics/adsimdetector/_sim_io.py +10 -0
- ophyd_async/epics/advimba/__init__.py +10 -1
- ophyd_async/epics/advimba/_vimba.py +26 -25
- ophyd_async/epics/advimba/_vimba_controller.py +12 -24
- ophyd_async/epics/advimba/_vimba_io.py +23 -28
- ophyd_async/epics/core/_aioca.py +66 -30
- ophyd_async/epics/core/_epics_connector.py +4 -0
- ophyd_async/epics/core/_epics_device.py +2 -0
- ophyd_async/epics/core/_p4p.py +50 -18
- ophyd_async/epics/core/_pvi_connector.py +65 -8
- ophyd_async/epics/core/_signal.py +51 -51
- ophyd_async/epics/core/_util.py +5 -5
- ophyd_async/epics/demo/__init__.py +11 -49
- 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/demo/{mover.db → 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 +83 -38
- ophyd_async/epics/signal.py +4 -1
- ophyd_async/epics/testing/__init__.py +14 -14
- ophyd_async/epics/testing/_example_ioc.py +68 -73
- ophyd_async/epics/testing/_utils.py +19 -44
- ophyd_async/epics/testing/test_records.db +16 -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 -8
- ophyd_async/fastcs/panda/_block.py +29 -9
- ophyd_async/fastcs/panda/_control.py +12 -2
- ophyd_async/fastcs/panda/_hdf_panda.py +5 -1
- ophyd_async/fastcs/panda/_table.py +13 -7
- ophyd_async/fastcs/panda/_trigger.py +23 -9
- ophyd_async/fastcs/panda/_writer.py +27 -30
- ophyd_async/plan_stubs/__init__.py +16 -0
- ophyd_async/plan_stubs/_ensure_connected.py +12 -17
- ophyd_async/plan_stubs/_fly.py +3 -5
- ophyd_async/plan_stubs/_nd_attributes.py +9 -5
- ophyd_async/plan_stubs/_panda.py +14 -0
- ophyd_async/plan_stubs/_settings.py +152 -0
- ophyd_async/plan_stubs/_utils.py +3 -0
- ophyd_async/plan_stubs/_wait_for_awaitable.py +13 -0
- ophyd_async/sim/__init__.py +29 -0
- 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 +21 -33
- ophyd_async/tango/core/_tango_readable.py +2 -19
- ophyd_async/tango/core/_tango_transport.py +148 -74
- ophyd_async/tango/core/_utils.py +47 -0
- ophyd_async/tango/demo/_counter.py +2 -0
- ophyd_async/tango/demo/_detector.py +2 -0
- ophyd_async/tango/demo/_mover.py +10 -6
- ophyd_async/tango/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 +48 -7
- ophyd_async/testing/__pytest_assert_rewrite.py +4 -0
- ophyd_async/testing/_assert.py +200 -96
- ophyd_async/testing/_mock_signal_utils.py +59 -73
- ophyd_async/testing/_one_of_everything.py +146 -0
- ophyd_async/testing/_single_derived.py +87 -0
- ophyd_async/testing/_utils.py +3 -0
- {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.10.0a1.dist-info}/METADATA +25 -26
- ophyd_async-0.10.0a1.dist-info/RECORD +149 -0
- {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.10.0a1.dist-info}/WHEEL +1 -1
- ophyd_async/core/_device_save_loader.py +0 -274
- ophyd_async/epics/demo/_mover.py +0 -95
- ophyd_async/epics/demo/_sensor.py +0 -37
- ophyd_async/epics/demo/sensor.db +0 -19
- ophyd_async/fastcs/panda/_utils.py +0 -16
- ophyd_async/sim/demo/__init__.py +0 -19
- ophyd_async/sim/demo/_pattern_detector/__init__.py +0 -13
- ophyd_async/sim/demo/_pattern_detector/_pattern_detector.py +0 -42
- ophyd_async/sim/demo/_pattern_detector/_pattern_detector_controller.py +0 -62
- ophyd_async/sim/demo/_pattern_detector/_pattern_detector_writer.py +0 -41
- ophyd_async/sim/demo/_pattern_detector/_pattern_generator.py +0 -207
- ophyd_async/sim/demo/_sim_motor.py +0 -107
- ophyd_async/sim/testing/__init__.py +0 -0
- ophyd_async-0.9.0a1.dist-info/RECORD +0 -119
- ophyd_async-0.9.0a1.dist-info/entry_points.txt +0 -2
- {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.10.0a1.dist-info/licenses}/LICENSE +0 -0
- {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.10.0a1.dist-info}/top_level.txt +0 -0
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import functools
|
|
3
|
+
import logging
|
|
3
4
|
import time
|
|
4
5
|
from abc import abstractmethod
|
|
5
6
|
from collections.abc import Callable, Coroutine
|
|
6
7
|
from enum import Enum
|
|
7
|
-
from typing import Any, TypeVar, cast
|
|
8
|
+
from typing import Any, ParamSpec, TypeVar, cast
|
|
8
9
|
|
|
9
10
|
import numpy as np
|
|
10
|
-
from bluesky.protocols import
|
|
11
|
+
from bluesky.protocols import Reading
|
|
12
|
+
from event_model import DataKey
|
|
11
13
|
|
|
12
14
|
from ophyd_async.core import (
|
|
13
15
|
AsyncStatus,
|
|
@@ -15,6 +17,7 @@ from ophyd_async.core import (
|
|
|
15
17
|
NotConnected,
|
|
16
18
|
SignalBackend,
|
|
17
19
|
SignalDatatypeT,
|
|
20
|
+
StrictEnum,
|
|
18
21
|
get_dtype,
|
|
19
22
|
get_unique,
|
|
20
23
|
wait_for_connection,
|
|
@@ -37,26 +40,41 @@ from tango.asyncio_executor import (
|
|
|
37
40
|
)
|
|
38
41
|
from tango.utils import is_array, is_binary, is_bool, is_float, is_int, is_str
|
|
39
42
|
|
|
43
|
+
from ._converters import (
|
|
44
|
+
TangoConverter,
|
|
45
|
+
TangoDevStateArrayConverter,
|
|
46
|
+
TangoDevStateConverter,
|
|
47
|
+
TangoEnumArrayConverter,
|
|
48
|
+
TangoEnumConverter,
|
|
49
|
+
)
|
|
50
|
+
from ._utils import DevStateEnum, get_device_trl_and_attr
|
|
51
|
+
|
|
52
|
+
logger = logging.getLogger("ophyd_async")
|
|
53
|
+
|
|
40
54
|
# time constant to wait for timeout
|
|
41
55
|
A_BIT = 1e-5
|
|
42
56
|
|
|
57
|
+
P = ParamSpec("P")
|
|
43
58
|
R = TypeVar("R")
|
|
44
59
|
|
|
45
60
|
|
|
46
61
|
def ensure_proper_executor(
|
|
47
|
-
func: Callable[
|
|
48
|
-
) -> Callable[
|
|
62
|
+
func: Callable[P, Coroutine[Any, Any, R]],
|
|
63
|
+
) -> Callable[P, Coroutine[Any, Any, R]]:
|
|
64
|
+
"""Ensure decorated method has a proper asyncio executor."""
|
|
65
|
+
|
|
49
66
|
@functools.wraps(func)
|
|
50
|
-
async def wrapper(
|
|
67
|
+
async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
51
68
|
current_executor: AsyncioExecutor = get_global_executor() # type: ignore
|
|
52
69
|
if not current_executor.in_executor_context(): # type: ignore
|
|
53
70
|
set_global_executor(AsyncioExecutor())
|
|
54
|
-
return await func(
|
|
71
|
+
return await func(*args, **kwargs)
|
|
55
72
|
|
|
56
|
-
return
|
|
73
|
+
return wrapper
|
|
57
74
|
|
|
58
75
|
|
|
59
76
|
def get_python_type(tango_type: CmdArgType) -> tuple[bool, object, str]:
|
|
77
|
+
"""For converting between recieved tango types and python primatives."""
|
|
60
78
|
array = is_array(tango_type)
|
|
61
79
|
if is_int(tango_type, True):
|
|
62
80
|
return array, int, "integer"
|
|
@@ -83,48 +101,51 @@ class TangoProxy:
|
|
|
83
101
|
support_events: bool = True
|
|
84
102
|
_proxy: DeviceProxy
|
|
85
103
|
_name: str
|
|
104
|
+
_converter: TangoConverter = TangoConverter()
|
|
86
105
|
|
|
87
106
|
def __init__(self, device_proxy: DeviceProxy, name: str):
|
|
88
107
|
self._proxy = device_proxy
|
|
89
108
|
self._name = name
|
|
90
109
|
|
|
91
110
|
async def connect(self) -> None:
|
|
92
|
-
"""
|
|
93
|
-
|
|
111
|
+
"""Perform actions after proxy is connected.
|
|
112
|
+
|
|
113
|
+
e.g. check if signal can be subscribed.
|
|
114
|
+
"""
|
|
94
115
|
|
|
95
116
|
@abstractmethod
|
|
96
117
|
async def get(self) -> object:
|
|
97
|
-
"""Get value from TRL"""
|
|
118
|
+
"""Get value from TRL."""
|
|
98
119
|
|
|
99
120
|
@abstractmethod
|
|
100
121
|
async def get_w_value(self) -> object:
|
|
101
|
-
"""Get last written value from TRL"""
|
|
122
|
+
"""Get last written value from TRL."""
|
|
102
123
|
|
|
103
124
|
@abstractmethod
|
|
104
125
|
async def put(
|
|
105
126
|
self, value: object | None, wait: bool = True, timeout: float | None = None
|
|
106
127
|
) -> AsyncStatus | None:
|
|
107
|
-
"""Put value to TRL"""
|
|
128
|
+
"""Put value to TRL."""
|
|
108
129
|
|
|
109
130
|
@abstractmethod
|
|
110
131
|
async def get_config(self) -> AttributeInfoEx | CommandInfo:
|
|
111
|
-
"""Get TRL config async"""
|
|
132
|
+
"""Get TRL config async."""
|
|
112
133
|
|
|
113
134
|
@abstractmethod
|
|
114
135
|
async def get_reading(self) -> Reading:
|
|
115
|
-
"""Get reading from TRL"""
|
|
136
|
+
"""Get reading from TRL."""
|
|
116
137
|
|
|
117
138
|
@abstractmethod
|
|
118
139
|
def has_subscription(self) -> bool:
|
|
119
|
-
"""
|
|
140
|
+
"""Indicate that this trl already subscribed."""
|
|
120
141
|
|
|
121
142
|
@abstractmethod
|
|
122
143
|
def subscribe_callback(self, callback: Callback | None):
|
|
123
|
-
"""
|
|
144
|
+
"""Subscribe tango CHANGE event to callback."""
|
|
124
145
|
|
|
125
146
|
@abstractmethod
|
|
126
147
|
def unsubscribe_callback(self):
|
|
127
|
-
"""
|
|
148
|
+
"""Delete CHANGE event subscription."""
|
|
128
149
|
|
|
129
150
|
@abstractmethod
|
|
130
151
|
def set_polling(
|
|
@@ -134,10 +155,15 @@ class TangoProxy:
|
|
|
134
155
|
abs_change=None,
|
|
135
156
|
rel_change=None,
|
|
136
157
|
):
|
|
137
|
-
"""Set polling parameters"""
|
|
158
|
+
"""Set polling parameters."""
|
|
159
|
+
|
|
160
|
+
def set_converter(self, converter: "TangoConverter"):
|
|
161
|
+
self._converter = converter
|
|
138
162
|
|
|
139
163
|
|
|
140
164
|
class AttributeProxy(TangoProxy):
|
|
165
|
+
"""Used by the tango transport."""
|
|
166
|
+
|
|
141
167
|
_callback: Callback | None = None
|
|
142
168
|
_eid: int | None = None
|
|
143
169
|
_poll_task: asyncio.Task | None = None
|
|
@@ -163,20 +189,21 @@ class AttributeProxy(TangoProxy):
|
|
|
163
189
|
pass
|
|
164
190
|
|
|
165
191
|
@ensure_proper_executor
|
|
166
|
-
async def get(self) ->
|
|
192
|
+
async def get(self) -> object: # type: ignore
|
|
167
193
|
attr = await self._proxy.read_attribute(self._name)
|
|
168
|
-
return attr.value
|
|
194
|
+
return self._converter.value(attr.value)
|
|
169
195
|
|
|
170
196
|
@ensure_proper_executor
|
|
171
|
-
async def get_w_value(self) -> object:
|
|
197
|
+
async def get_w_value(self) -> object: # type: ignore
|
|
172
198
|
attr = await self._proxy.read_attribute(self._name)
|
|
173
|
-
return attr.w_value
|
|
199
|
+
return self._converter.value(attr.w_value)
|
|
174
200
|
|
|
175
201
|
@ensure_proper_executor
|
|
176
|
-
async def put(
|
|
202
|
+
async def put( # type: ignore
|
|
177
203
|
self, value: object | None, wait: bool = True, timeout: float | None = None
|
|
178
204
|
) -> AsyncStatus | None:
|
|
179
205
|
# TODO: remove the timeout from this as it is handled at the signal level
|
|
206
|
+
value = self._converter.write_value(value)
|
|
180
207
|
if wait:
|
|
181
208
|
try:
|
|
182
209
|
|
|
@@ -210,24 +237,26 @@ class AttributeProxy(TangoProxy):
|
|
|
210
237
|
await asyncio.sleep(A_BIT)
|
|
211
238
|
if to and (time.time() - start_time > to):
|
|
212
239
|
raise TimeoutError(
|
|
213
|
-
f"{self._name} attr put failed:
|
|
240
|
+
f"{self._name} attr put failed: Timeout"
|
|
214
241
|
) from exc
|
|
215
242
|
else:
|
|
216
243
|
raise RuntimeError(
|
|
217
|
-
f"{self._name} device failure:
|
|
244
|
+
f"{self._name} device failure: {exc.args[0].desc}"
|
|
218
245
|
) from exc
|
|
219
246
|
|
|
220
247
|
return AsyncStatus(wait_for_reply(rid, timeout))
|
|
221
248
|
|
|
222
249
|
@ensure_proper_executor
|
|
223
|
-
async def get_config(self) -> AttributeInfoEx:
|
|
250
|
+
async def get_config(self) -> AttributeInfoEx: # type: ignore
|
|
224
251
|
return await self._proxy.get_attribute_config(self._name)
|
|
225
252
|
|
|
226
253
|
@ensure_proper_executor
|
|
227
|
-
async def get_reading(self) -> Reading:
|
|
254
|
+
async def get_reading(self) -> Reading: # type: ignore
|
|
228
255
|
attr = await self._proxy.read_attribute(self._name)
|
|
229
256
|
reading = Reading(
|
|
230
|
-
value=
|
|
257
|
+
value=self._converter.value(attr.value),
|
|
258
|
+
timestamp=attr.time.totime(),
|
|
259
|
+
alarm_severity=attr.quality,
|
|
231
260
|
)
|
|
232
261
|
self._last_reading = reading
|
|
233
262
|
return reading
|
|
@@ -290,7 +319,7 @@ class AttributeProxy(TangoProxy):
|
|
|
290
319
|
def _event_processor(self, event):
|
|
291
320
|
if not event.err:
|
|
292
321
|
reading = Reading(
|
|
293
|
-
value=event.attr_value.value,
|
|
322
|
+
value=self._converter.value(event.attr_value.value),
|
|
294
323
|
timestamp=event.get_date().totime(),
|
|
295
324
|
alarm_severity=event.attr_value.quality,
|
|
296
325
|
)
|
|
@@ -298,10 +327,11 @@ class AttributeProxy(TangoProxy):
|
|
|
298
327
|
self._callback(reading)
|
|
299
328
|
|
|
300
329
|
async def poll(self):
|
|
301
|
-
"""
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
330
|
+
"""Poll the attribute and call the callback if the value has changed.
|
|
331
|
+
|
|
332
|
+
Only callback if value has changed by more than the absolute or relative
|
|
333
|
+
change. This function is used when an attribute that does not support
|
|
334
|
+
events is cached or a callback is passed to it.
|
|
305
335
|
"""
|
|
306
336
|
try:
|
|
307
337
|
last_reading = await self.get_reading()
|
|
@@ -376,9 +406,7 @@ class AttributeProxy(TangoProxy):
|
|
|
376
406
|
abs_change: float | None = None,
|
|
377
407
|
rel_change: float | None = 0.1,
|
|
378
408
|
):
|
|
379
|
-
"""
|
|
380
|
-
Set the polling parameters.
|
|
381
|
-
"""
|
|
409
|
+
"""Set the polling parameters."""
|
|
382
410
|
self._allow_polling = allow_polling
|
|
383
411
|
self._polling_period = polling_period
|
|
384
412
|
self._abs_change = abs_change
|
|
@@ -386,6 +414,8 @@ class AttributeProxy(TangoProxy):
|
|
|
386
414
|
|
|
387
415
|
|
|
388
416
|
class CommandProxy(TangoProxy):
|
|
417
|
+
"""Tango proxy for commands."""
|
|
418
|
+
|
|
389
419
|
_last_reading: Reading = Reading(value=None, timestamp=0, alarm_severity=0)
|
|
390
420
|
|
|
391
421
|
def subscribe_callback(self, callback: Callback | None) -> None:
|
|
@@ -404,9 +434,10 @@ class CommandProxy(TangoProxy):
|
|
|
404
434
|
pass
|
|
405
435
|
|
|
406
436
|
@ensure_proper_executor
|
|
407
|
-
async def put(
|
|
437
|
+
async def put( # type: ignore
|
|
408
438
|
self, value: object | None, wait: bool = True, timeout: float | None = None
|
|
409
439
|
) -> AsyncStatus | None:
|
|
440
|
+
value = self._converter.write_value(value)
|
|
410
441
|
if wait:
|
|
411
442
|
try:
|
|
412
443
|
|
|
@@ -416,13 +447,15 @@ class CommandProxy(TangoProxy):
|
|
|
416
447
|
task = asyncio.create_task(_put())
|
|
417
448
|
val = await asyncio.wait_for(task, timeout)
|
|
418
449
|
self._last_reading = Reading(
|
|
419
|
-
value=
|
|
450
|
+
value=self._converter.value(val),
|
|
451
|
+
timestamp=time.time(),
|
|
452
|
+
alarm_severity=0,
|
|
420
453
|
)
|
|
421
454
|
except asyncio.TimeoutError as te:
|
|
422
455
|
raise TimeoutError(f"{self._name} command failed: Timeout") from te
|
|
423
456
|
except DevFailed as de:
|
|
424
457
|
raise RuntimeError(
|
|
425
|
-
f"{self._name} device
|
|
458
|
+
f"{self._name} device failure: {de.args[0].desc}"
|
|
426
459
|
) from de
|
|
427
460
|
|
|
428
461
|
else:
|
|
@@ -432,7 +465,9 @@ class CommandProxy(TangoProxy):
|
|
|
432
465
|
start_time = time.time()
|
|
433
466
|
while True:
|
|
434
467
|
try:
|
|
435
|
-
reply_value = self.
|
|
468
|
+
reply_value = self._converter.value(
|
|
469
|
+
self._proxy.command_inout_reply(rd)
|
|
470
|
+
)
|
|
436
471
|
self._last_reading = Reading(
|
|
437
472
|
value=reply_value, timestamp=time.time(), alarm_severity=0
|
|
438
473
|
)
|
|
@@ -446,14 +481,13 @@ class CommandProxy(TangoProxy):
|
|
|
446
481
|
) from de_exc
|
|
447
482
|
else:
|
|
448
483
|
raise RuntimeError(
|
|
449
|
-
f"{self._name} device failure:"
|
|
450
|
-
f" {de_exc.args[0].desc}"
|
|
484
|
+
f"{self._name} device failure: {de_exc.args[0].desc}"
|
|
451
485
|
) from de_exc
|
|
452
486
|
|
|
453
487
|
return AsyncStatus(wait_for_reply(rid, timeout))
|
|
454
488
|
|
|
455
489
|
@ensure_proper_executor
|
|
456
|
-
async def get_config(self) -> CommandInfo:
|
|
490
|
+
async def get_config(self) -> CommandInfo: # type: ignore
|
|
457
491
|
return await self._proxy.get_command_config(self._name)
|
|
458
492
|
|
|
459
493
|
async def get_reading(self) -> Reading:
|
|
@@ -475,10 +509,11 @@ class CommandProxy(TangoProxy):
|
|
|
475
509
|
|
|
476
510
|
|
|
477
511
|
def get_dtype_extended(datatype) -> object | None:
|
|
512
|
+
"""For converting tango types to numpy datatype formats."""
|
|
478
513
|
# DevState tango type does not have numpy equivalents
|
|
479
514
|
dtype = get_dtype(datatype)
|
|
480
515
|
if dtype == np.object_:
|
|
481
|
-
if datatype.__args__[1].__args__[0]
|
|
516
|
+
if datatype.__args__[1].__args__[0] in [DevStateEnum, DevState]:
|
|
482
517
|
dtype = CmdArgType.DevState
|
|
483
518
|
return dtype
|
|
484
519
|
|
|
@@ -487,7 +522,8 @@ def get_trl_descriptor(
|
|
|
487
522
|
datatype: type | None,
|
|
488
523
|
tango_resource: str,
|
|
489
524
|
tr_configs: dict[str, AttributeInfoEx | CommandInfo],
|
|
490
|
-
) ->
|
|
525
|
+
) -> DataKey:
|
|
526
|
+
"""Create a descriptor from a tango resource locator."""
|
|
491
527
|
tr_dtype = {}
|
|
492
528
|
for tr_name, config in tr_configs.items():
|
|
493
529
|
if isinstance(config, AttributeInfoEx):
|
|
@@ -545,11 +581,9 @@ def get_trl_descriptor(
|
|
|
545
581
|
raise TypeError(f"{tango_resource} has type [{tr_dtype}] not [{dtype}]")
|
|
546
582
|
|
|
547
583
|
if tr_format == AttrDataFormat.SPECTRUM:
|
|
548
|
-
return
|
|
584
|
+
return DataKey(source=tango_resource, dtype="array", shape=[max_x])
|
|
549
585
|
elif tr_format == AttrDataFormat.IMAGE:
|
|
550
|
-
return
|
|
551
|
-
source=tango_resource, dtype="array", shape=[max_y, max_x]
|
|
552
|
-
)
|
|
586
|
+
return DataKey(source=tango_resource, dtype="array", shape=[max_y, max_x])
|
|
553
587
|
|
|
554
588
|
else:
|
|
555
589
|
if tr_dtype in (Enum, CmdArgType.DevState):
|
|
@@ -569,14 +603,14 @@ def get_trl_descriptor(
|
|
|
569
603
|
# f"{tango_resource} has choices {trl_choices} "
|
|
570
604
|
# f"not {choices}"
|
|
571
605
|
# )
|
|
572
|
-
return
|
|
606
|
+
return DataKey(source=tango_resource, dtype="string", shape=[])
|
|
573
607
|
else:
|
|
574
608
|
if datatype and not issubclass(tr_dtype, datatype):
|
|
575
609
|
raise TypeError(
|
|
576
610
|
f"{tango_resource} has type {tr_dtype.__name__} "
|
|
577
611
|
f"not {datatype.__name__}"
|
|
578
612
|
)
|
|
579
|
-
return
|
|
613
|
+
return DataKey(source=tango_resource, dtype=tr_dtype_desc, shape=[])
|
|
580
614
|
|
|
581
615
|
raise RuntimeError(f"Error getting descriptor for {tango_resource}")
|
|
582
616
|
|
|
@@ -584,9 +618,10 @@ def get_trl_descriptor(
|
|
|
584
618
|
async def get_tango_trl(
|
|
585
619
|
full_trl: str, device_proxy: DeviceProxy | TangoProxy | None, timeout: float
|
|
586
620
|
) -> TangoProxy:
|
|
621
|
+
"""Get the tango resource locator."""
|
|
587
622
|
if isinstance(device_proxy, TangoProxy):
|
|
588
623
|
return device_proxy
|
|
589
|
-
device_trl, trl_name = full_trl
|
|
624
|
+
device_trl, trl_name = get_device_trl_and_attr(full_trl)
|
|
590
625
|
trl_name = trl_name.lower()
|
|
591
626
|
if device_proxy is None:
|
|
592
627
|
device_proxy = await AsyncDeviceProxy(device_trl, timeout=timeout)
|
|
@@ -608,17 +643,51 @@ async def get_tango_trl(
|
|
|
608
643
|
if trl_name in all_cmds:
|
|
609
644
|
return CommandProxy(device_proxy, trl_name)
|
|
610
645
|
|
|
611
|
-
# If version is below tango 9, then pipes are not supported
|
|
612
|
-
if device_proxy.info().server_version >= 9:
|
|
613
|
-
# all pipes can be always accessible with low register
|
|
614
|
-
all_pipes = [pipe_name.lower() for pipe_name in device_proxy.get_pipe_list()]
|
|
615
|
-
if trl_name in all_pipes:
|
|
616
|
-
raise NotImplementedError("Pipes are not supported")
|
|
617
|
-
|
|
618
646
|
raise RuntimeError(f"{trl_name} cannot be found in {device_proxy.name()}")
|
|
619
647
|
|
|
620
648
|
|
|
649
|
+
def make_converter(info: AttributeInfoEx | CommandInfo, datatype) -> TangoConverter:
|
|
650
|
+
if isinstance(info, AttributeInfoEx):
|
|
651
|
+
match info.data_type:
|
|
652
|
+
case CmdArgType.DevEnum:
|
|
653
|
+
if datatype and issubclass(datatype, StrictEnum):
|
|
654
|
+
labels = [e.value for e in datatype]
|
|
655
|
+
else: # get from enum_labels metadata
|
|
656
|
+
labels = list(info.enum_labels)
|
|
657
|
+
if info.data_format == AttrDataFormat.SCALAR:
|
|
658
|
+
return TangoEnumConverter(labels)
|
|
659
|
+
elif info.data_format in [
|
|
660
|
+
AttrDataFormat.SPECTRUM,
|
|
661
|
+
AttrDataFormat.IMAGE,
|
|
662
|
+
]:
|
|
663
|
+
return TangoEnumArrayConverter(labels)
|
|
664
|
+
case CmdArgType.DevState:
|
|
665
|
+
if info.data_format == AttrDataFormat.SCALAR:
|
|
666
|
+
return TangoDevStateConverter()
|
|
667
|
+
elif info.data_format in [
|
|
668
|
+
AttrDataFormat.SPECTRUM,
|
|
669
|
+
AttrDataFormat.IMAGE,
|
|
670
|
+
]:
|
|
671
|
+
return TangoDevStateArrayConverter()
|
|
672
|
+
else: # command info
|
|
673
|
+
match info.in_type:
|
|
674
|
+
case CmdArgType.DevState:
|
|
675
|
+
return TangoDevStateConverter()
|
|
676
|
+
case CmdArgType.DevEnum:
|
|
677
|
+
if datatype and issubclass(datatype, StrictEnum):
|
|
678
|
+
labels = [e.value for e in datatype]
|
|
679
|
+
return TangoEnumConverter(labels)
|
|
680
|
+
else:
|
|
681
|
+
logger.warning(
|
|
682
|
+
"No override enum class provided for Tango enum command"
|
|
683
|
+
)
|
|
684
|
+
# default case return trivial converter
|
|
685
|
+
return TangoConverter()
|
|
686
|
+
|
|
687
|
+
|
|
621
688
|
class TangoSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
689
|
+
"""Tango backend to connect signals over tango."""
|
|
690
|
+
|
|
622
691
|
def __init__(
|
|
623
692
|
self,
|
|
624
693
|
datatype: type[SignalDatatypeT] | None,
|
|
@@ -634,7 +703,7 @@ class TangoSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
|
634
703
|
write_trl: self.device_proxy,
|
|
635
704
|
}
|
|
636
705
|
self.trl_configs: dict[str, AttributeInfoEx] = {}
|
|
637
|
-
self.descriptor:
|
|
706
|
+
self.descriptor: DataKey = {} # type: ignore
|
|
638
707
|
self._polling: tuple[bool, float, float | None, float | None] = (
|
|
639
708
|
False,
|
|
640
709
|
0.1,
|
|
@@ -643,11 +712,12 @@ class TangoSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
|
643
712
|
)
|
|
644
713
|
self.support_events: bool = True
|
|
645
714
|
self.status: AsyncStatus | None = None
|
|
715
|
+
self.converter = TangoConverter() # gets replaced at connect
|
|
646
716
|
super().__init__(datatype)
|
|
647
717
|
|
|
648
718
|
@classmethod
|
|
649
719
|
def datatype_allowed(cls, dtype: Any) -> bool:
|
|
650
|
-
return dtype in (int, float, str, bool, np.ndarray,
|
|
720
|
+
return dtype in (int, float, str, bool, np.ndarray, StrictEnum)
|
|
651
721
|
|
|
652
722
|
def set_trl(self, read_trl: str = "", write_trl: str = ""):
|
|
653
723
|
self.read_trl = read_trl
|
|
@@ -688,6 +758,8 @@ class TangoSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
|
688
758
|
# The same, so only need to connect one
|
|
689
759
|
await self._connect_and_store_config(self.read_trl, timeout)
|
|
690
760
|
self.proxies[self.read_trl].set_polling(*self._polling) # type: ignore
|
|
761
|
+
self.converter = make_converter(self.trl_configs[self.read_trl], self.datatype)
|
|
762
|
+
self.proxies[self.read_trl].set_converter(self.converter) # type: ignore
|
|
691
763
|
self.descriptor = get_trl_descriptor(
|
|
692
764
|
self.datatype, self.read_trl, self.trl_configs
|
|
693
765
|
)
|
|
@@ -699,13 +771,14 @@ class TangoSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
|
699
771
|
put_status = await self.proxies[self.write_trl].put(value, wait, timeout) # type: ignore
|
|
700
772
|
self.status = put_status
|
|
701
773
|
|
|
702
|
-
async def get_datakey(self, source: str) ->
|
|
774
|
+
async def get_datakey(self, source: str) -> DataKey:
|
|
703
775
|
return self.descriptor
|
|
704
776
|
|
|
705
777
|
async def get_reading(self) -> Reading[SignalDatatypeT]:
|
|
706
778
|
if self.proxies[self.read_trl] is None:
|
|
707
779
|
raise NotConnected(f"Not connected to {self.read_trl}")
|
|
708
|
-
|
|
780
|
+
reading = await self.proxies[self.read_trl].get_reading() # type: ignore
|
|
781
|
+
return reading
|
|
709
782
|
|
|
710
783
|
async def get_value(self) -> SignalDatatypeT:
|
|
711
784
|
if self.proxies[self.read_trl] is None:
|
|
@@ -713,7 +786,8 @@ class TangoSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
|
713
786
|
proxy = self.proxies[self.read_trl]
|
|
714
787
|
if proxy is None:
|
|
715
788
|
raise NotConnected(f"Not connected to {self.read_trl}")
|
|
716
|
-
|
|
789
|
+
value = await proxy.get()
|
|
790
|
+
return cast(SignalDatatypeT, value)
|
|
717
791
|
|
|
718
792
|
async def get_setpoint(self) -> SignalDatatypeT:
|
|
719
793
|
if self.proxies[self.write_trl] is None:
|
|
@@ -721,7 +795,8 @@ class TangoSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
|
721
795
|
proxy = self.proxies[self.write_trl]
|
|
722
796
|
if proxy is None:
|
|
723
797
|
raise NotConnected(f"Not connected to {self.write_trl}")
|
|
724
|
-
|
|
798
|
+
w_value = await proxy.get_w_value()
|
|
799
|
+
return cast(SignalDatatypeT, w_value)
|
|
725
800
|
|
|
726
801
|
def set_callback(self, callback: Callback | None) -> None:
|
|
727
802
|
if self.proxies[self.read_trl] is None:
|
|
@@ -733,22 +808,21 @@ class TangoSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
|
733
808
|
" for which polling is disabled."
|
|
734
809
|
)
|
|
735
810
|
|
|
811
|
+
if callback and self.proxies[self.read_trl].has_subscription(): # type: ignore
|
|
812
|
+
msg = "Cannot set a callback when one is already set"
|
|
813
|
+
raise RuntimeError(msg)
|
|
814
|
+
|
|
815
|
+
if self.proxies[self.read_trl].has_subscription(): # type: ignore
|
|
816
|
+
self.proxies[self.read_trl].unsubscribe_callback() # type: ignore
|
|
817
|
+
|
|
736
818
|
if callback:
|
|
737
819
|
try:
|
|
738
|
-
assert not self.proxies[self.read_trl].has_subscription() # type: ignore
|
|
739
820
|
self.proxies[self.read_trl].subscribe_callback(callback) # type: ignore
|
|
740
|
-
except AssertionError as ae:
|
|
741
|
-
raise RuntimeError(
|
|
742
|
-
"Cannot set a callback when one" " is already set"
|
|
743
|
-
) from ae
|
|
744
821
|
except RuntimeError as exc:
|
|
745
822
|
raise RuntimeError(
|
|
746
|
-
f"Cannot set callback
|
|
823
|
+
f"Cannot set callback for {self.read_trl}. {exc}"
|
|
747
824
|
) from exc
|
|
748
825
|
|
|
749
|
-
else:
|
|
750
|
-
self.proxies[self.read_trl].unsubscribe_callback() # type: ignore
|
|
751
|
-
|
|
752
826
|
def set_polling(
|
|
753
827
|
self,
|
|
754
828
|
allow_polling: bool = True,
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from ophyd_async.core import StrictEnum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DevStateEnum(StrictEnum):
|
|
7
|
+
ON = "ON"
|
|
8
|
+
OFF = "OFF"
|
|
9
|
+
CLOSE = "CLOSE"
|
|
10
|
+
OPEN = "OPEN"
|
|
11
|
+
INSERT = "INSERT"
|
|
12
|
+
EXTRACT = "EXTRACT"
|
|
13
|
+
MOVING = "MOVING"
|
|
14
|
+
STANDBY = "STANDBY"
|
|
15
|
+
FAULT = "FAULT"
|
|
16
|
+
INIT = "INIT"
|
|
17
|
+
RUNNING = "RUNNING"
|
|
18
|
+
ALARM = "ALARM"
|
|
19
|
+
DISABLE = "DISABLE"
|
|
20
|
+
UNKNOWN = "UNKNOWN"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_full_attr_trl(device_trl: str, attr_name: str):
|
|
24
|
+
device_parts = device_trl.split("#", 1)
|
|
25
|
+
# my/device/name#dbase=no splits into my/device/name and dbase=no
|
|
26
|
+
full_trl = device_parts[0] + "/" + attr_name
|
|
27
|
+
if len(device_parts) > 1:
|
|
28
|
+
full_trl += "#" + device_parts[1]
|
|
29
|
+
return full_trl
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_device_trl_and_attr(name: str):
|
|
33
|
+
# trl can have form:
|
|
34
|
+
# <protocol://><server:host/>domain/family/member/attr_name<#dbase=no>
|
|
35
|
+
# e.g. tango://127.0.0.1:8888/test/nodb/test#dbase=no
|
|
36
|
+
re_str = (
|
|
37
|
+
r"([\.a-zA-Z0-9_-]*://)?([\.a-zA-Z0-9_-]+:[0-9]+/)?"
|
|
38
|
+
r"([^#/]+/[^#/]+/[^#/]+/)([^#/]+)(#dbase=[a-z]+)?"
|
|
39
|
+
)
|
|
40
|
+
search = re.search(re_str, name)
|
|
41
|
+
if not search:
|
|
42
|
+
raise ValueError(f"Could not parse device and attribute from trl {name}")
|
|
43
|
+
groups = [part if part else "" for part in search.groups()]
|
|
44
|
+
attr = groups.pop(3) # extract attr name from groups
|
|
45
|
+
groups[2] = groups[2].removesuffix("/") # remove trailing slash from device name
|
|
46
|
+
device = "".join(groups)
|
|
47
|
+
return device, attr
|
|
@@ -6,6 +6,8 @@ from ophyd_async.tango.core import TangoPolling, TangoReadable
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class TangoCounter(TangoReadable):
|
|
9
|
+
"""Tango counting device."""
|
|
10
|
+
|
|
9
11
|
# Enter the name and type of the signals you want to use
|
|
10
12
|
# If the server doesn't support events, the TangoPolling annotation gives
|
|
11
13
|
# the parameters for ophyd to poll instead
|
|
@@ -11,6 +11,8 @@ from ._mover import TangoMover
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class TangoDetector(StandardReadable):
|
|
14
|
+
"""For use with tango detector devices."""
|
|
15
|
+
|
|
14
16
|
def __init__(self, mover_trl: str, counter_trls: list[str], name=""):
|
|
15
17
|
# A detector device may be composed of tango sub-devices
|
|
16
18
|
self.mover = TangoMover(mover_trl)
|
ophyd_async/tango/demo/_mover.py
CHANGED
|
@@ -17,17 +17,18 @@ from ophyd_async.core import (
|
|
|
17
17
|
wait_for_value,
|
|
18
18
|
)
|
|
19
19
|
from ophyd_async.core import StandardReadableFormat as Format
|
|
20
|
-
from ophyd_async.tango.core import TangoPolling, TangoReadable
|
|
21
|
-
from tango import DevState
|
|
20
|
+
from ophyd_async.tango.core import DevStateEnum, TangoPolling, TangoReadable
|
|
22
21
|
|
|
23
22
|
|
|
24
23
|
class TangoMover(TangoReadable, Movable, Stoppable):
|
|
24
|
+
"""Tango moving device."""
|
|
25
|
+
|
|
25
26
|
# Enter the name and type of the signals you want to use
|
|
26
27
|
# If the server doesn't support events, the TangoPolling annotation gives
|
|
27
28
|
# the parameters for ophyd to poll instead
|
|
28
29
|
position: A[SignalRW[float], TangoPolling(0.1, 0.1, 0.1)]
|
|
29
30
|
velocity: A[SignalRW[float], TangoPolling(0.1, 0.1, 0.1)]
|
|
30
|
-
state: A[SignalR[
|
|
31
|
+
state: A[SignalR[DevStateEnum], TangoPolling(0.1)]
|
|
31
32
|
# If a tango name clashes with a bluesky verb, add a trailing underscore
|
|
32
33
|
stop_: SignalX
|
|
33
34
|
|
|
@@ -43,8 +44,11 @@ class TangoMover(TangoReadable, Movable, Stoppable):
|
|
|
43
44
|
(old_position, velocity) = await asyncio.gather(
|
|
44
45
|
self.position.get_value(), self.velocity.get_value()
|
|
45
46
|
)
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
# TODO: check whether Tango does work with negative velocity
|
|
48
|
+
if timeout is CALCULATE_TIMEOUT and velocity == 0:
|
|
49
|
+
msg = "Motor has zero velocity"
|
|
50
|
+
raise ValueError(msg)
|
|
51
|
+
else:
|
|
48
52
|
timeout = abs(value - old_position) / velocity + DEFAULT_TIMEOUT
|
|
49
53
|
|
|
50
54
|
if not (isinstance(timeout, float) or timeout is None):
|
|
@@ -53,7 +57,7 @@ class TangoMover(TangoReadable, Movable, Stoppable):
|
|
|
53
57
|
await self.position.set(value, wait=False, timeout=timeout)
|
|
54
58
|
|
|
55
59
|
move_status = AsyncStatus(
|
|
56
|
-
wait_for_value(self.state,
|
|
60
|
+
wait_for_value(self.state, DevStateEnum.ON, timeout=timeout)
|
|
57
61
|
)
|
|
58
62
|
|
|
59
63
|
try:
|
|
@@ -8,6 +8,8 @@ from tango.server import Device, attribute, command
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class DemoMover(Device):
|
|
11
|
+
"""Demo tango moving device."""
|
|
12
|
+
|
|
11
13
|
green_mode = GreenMode.Asyncio
|
|
12
14
|
_position = 0.0
|
|
13
15
|
_setpoint = 0.0
|
|
@@ -65,6 +67,8 @@ class DemoMover(Device):
|
|
|
65
67
|
|
|
66
68
|
|
|
67
69
|
class DemoCounter(Device):
|
|
70
|
+
"""Demo tango counting device."""
|
|
71
|
+
|
|
68
72
|
green_mode = GreenMode.Asyncio
|
|
69
73
|
_counts = 0
|
|
70
74
|
_sample_time = 1.0
|