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
|
@@ -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
|
|
|
@@ -220,14 +247,16 @@ class AttributeProxy(TangoProxy):
|
|
|
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,7 +447,9 @@ 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
|
|
@@ -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
|
)
|
|
@@ -452,7 +487,7 @@ class CommandProxy(TangoProxy):
|
|
|
452
487
|
return AsyncStatus(wait_for_reply(rid, timeout))
|
|
453
488
|
|
|
454
489
|
@ensure_proper_executor
|
|
455
|
-
async def get_config(self) -> CommandInfo:
|
|
490
|
+
async def get_config(self) -> CommandInfo: # type: ignore
|
|
456
491
|
return await self._proxy.get_command_config(self._name)
|
|
457
492
|
|
|
458
493
|
async def get_reading(self) -> Reading:
|
|
@@ -474,10 +509,11 @@ class CommandProxy(TangoProxy):
|
|
|
474
509
|
|
|
475
510
|
|
|
476
511
|
def get_dtype_extended(datatype) -> object | None:
|
|
512
|
+
"""For converting tango types to numpy datatype formats."""
|
|
477
513
|
# DevState tango type does not have numpy equivalents
|
|
478
514
|
dtype = get_dtype(datatype)
|
|
479
515
|
if dtype == np.object_:
|
|
480
|
-
if datatype.__args__[1].__args__[0]
|
|
516
|
+
if datatype.__args__[1].__args__[0] in [DevStateEnum, DevState]:
|
|
481
517
|
dtype = CmdArgType.DevState
|
|
482
518
|
return dtype
|
|
483
519
|
|
|
@@ -486,7 +522,8 @@ def get_trl_descriptor(
|
|
|
486
522
|
datatype: type | None,
|
|
487
523
|
tango_resource: str,
|
|
488
524
|
tr_configs: dict[str, AttributeInfoEx | CommandInfo],
|
|
489
|
-
) ->
|
|
525
|
+
) -> DataKey:
|
|
526
|
+
"""Create a descriptor from a tango resource locator."""
|
|
490
527
|
tr_dtype = {}
|
|
491
528
|
for tr_name, config in tr_configs.items():
|
|
492
529
|
if isinstance(config, AttributeInfoEx):
|
|
@@ -544,11 +581,9 @@ def get_trl_descriptor(
|
|
|
544
581
|
raise TypeError(f"{tango_resource} has type [{tr_dtype}] not [{dtype}]")
|
|
545
582
|
|
|
546
583
|
if tr_format == AttrDataFormat.SPECTRUM:
|
|
547
|
-
return
|
|
584
|
+
return DataKey(source=tango_resource, dtype="array", shape=[max_x])
|
|
548
585
|
elif tr_format == AttrDataFormat.IMAGE:
|
|
549
|
-
return
|
|
550
|
-
source=tango_resource, dtype="array", shape=[max_y, max_x]
|
|
551
|
-
)
|
|
586
|
+
return DataKey(source=tango_resource, dtype="array", shape=[max_y, max_x])
|
|
552
587
|
|
|
553
588
|
else:
|
|
554
589
|
if tr_dtype in (Enum, CmdArgType.DevState):
|
|
@@ -568,14 +603,14 @@ def get_trl_descriptor(
|
|
|
568
603
|
# f"{tango_resource} has choices {trl_choices} "
|
|
569
604
|
# f"not {choices}"
|
|
570
605
|
# )
|
|
571
|
-
return
|
|
606
|
+
return DataKey(source=tango_resource, dtype="string", shape=[])
|
|
572
607
|
else:
|
|
573
608
|
if datatype and not issubclass(tr_dtype, datatype):
|
|
574
609
|
raise TypeError(
|
|
575
610
|
f"{tango_resource} has type {tr_dtype.__name__} "
|
|
576
611
|
f"not {datatype.__name__}"
|
|
577
612
|
)
|
|
578
|
-
return
|
|
613
|
+
return DataKey(source=tango_resource, dtype=tr_dtype_desc, shape=[])
|
|
579
614
|
|
|
580
615
|
raise RuntimeError(f"Error getting descriptor for {tango_resource}")
|
|
581
616
|
|
|
@@ -583,9 +618,10 @@ def get_trl_descriptor(
|
|
|
583
618
|
async def get_tango_trl(
|
|
584
619
|
full_trl: str, device_proxy: DeviceProxy | TangoProxy | None, timeout: float
|
|
585
620
|
) -> TangoProxy:
|
|
621
|
+
"""Get the tango resource locator."""
|
|
586
622
|
if isinstance(device_proxy, TangoProxy):
|
|
587
623
|
return device_proxy
|
|
588
|
-
device_trl, trl_name = full_trl
|
|
624
|
+
device_trl, trl_name = get_device_trl_and_attr(full_trl)
|
|
589
625
|
trl_name = trl_name.lower()
|
|
590
626
|
if device_proxy is None:
|
|
591
627
|
device_proxy = await AsyncDeviceProxy(device_trl, timeout=timeout)
|
|
@@ -607,17 +643,51 @@ async def get_tango_trl(
|
|
|
607
643
|
if trl_name in all_cmds:
|
|
608
644
|
return CommandProxy(device_proxy, trl_name)
|
|
609
645
|
|
|
610
|
-
# If version is below tango 9, then pipes are not supported
|
|
611
|
-
if device_proxy.info().server_version >= 9:
|
|
612
|
-
# all pipes can be always accessible with low register
|
|
613
|
-
all_pipes = [pipe_name.lower() for pipe_name in device_proxy.get_pipe_list()]
|
|
614
|
-
if trl_name in all_pipes:
|
|
615
|
-
raise NotImplementedError("Pipes are not supported")
|
|
616
|
-
|
|
617
646
|
raise RuntimeError(f"{trl_name} cannot be found in {device_proxy.name()}")
|
|
618
647
|
|
|
619
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
|
+
|
|
620
688
|
class TangoSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
689
|
+
"""Tango backend to connect signals over tango."""
|
|
690
|
+
|
|
621
691
|
def __init__(
|
|
622
692
|
self,
|
|
623
693
|
datatype: type[SignalDatatypeT] | None,
|
|
@@ -633,7 +703,7 @@ class TangoSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
|
633
703
|
write_trl: self.device_proxy,
|
|
634
704
|
}
|
|
635
705
|
self.trl_configs: dict[str, AttributeInfoEx] = {}
|
|
636
|
-
self.descriptor:
|
|
706
|
+
self.descriptor: DataKey = {} # type: ignore
|
|
637
707
|
self._polling: tuple[bool, float, float | None, float | None] = (
|
|
638
708
|
False,
|
|
639
709
|
0.1,
|
|
@@ -642,11 +712,12 @@ class TangoSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
|
642
712
|
)
|
|
643
713
|
self.support_events: bool = True
|
|
644
714
|
self.status: AsyncStatus | None = None
|
|
715
|
+
self.converter = TangoConverter() # gets replaced at connect
|
|
645
716
|
super().__init__(datatype)
|
|
646
717
|
|
|
647
718
|
@classmethod
|
|
648
719
|
def datatype_allowed(cls, dtype: Any) -> bool:
|
|
649
|
-
return dtype in (int, float, str, bool, np.ndarray,
|
|
720
|
+
return dtype in (int, float, str, bool, np.ndarray, StrictEnum)
|
|
650
721
|
|
|
651
722
|
def set_trl(self, read_trl: str = "", write_trl: str = ""):
|
|
652
723
|
self.read_trl = read_trl
|
|
@@ -687,6 +758,8 @@ class TangoSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
|
687
758
|
# The same, so only need to connect one
|
|
688
759
|
await self._connect_and_store_config(self.read_trl, timeout)
|
|
689
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
|
|
690
763
|
self.descriptor = get_trl_descriptor(
|
|
691
764
|
self.datatype, self.read_trl, self.trl_configs
|
|
692
765
|
)
|
|
@@ -698,13 +771,14 @@ class TangoSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
|
698
771
|
put_status = await self.proxies[self.write_trl].put(value, wait, timeout) # type: ignore
|
|
699
772
|
self.status = put_status
|
|
700
773
|
|
|
701
|
-
async def get_datakey(self, source: str) ->
|
|
774
|
+
async def get_datakey(self, source: str) -> DataKey:
|
|
702
775
|
return self.descriptor
|
|
703
776
|
|
|
704
777
|
async def get_reading(self) -> Reading[SignalDatatypeT]:
|
|
705
778
|
if self.proxies[self.read_trl] is None:
|
|
706
779
|
raise NotConnected(f"Not connected to {self.read_trl}")
|
|
707
|
-
|
|
780
|
+
reading = await self.proxies[self.read_trl].get_reading() # type: ignore
|
|
781
|
+
return reading
|
|
708
782
|
|
|
709
783
|
async def get_value(self) -> SignalDatatypeT:
|
|
710
784
|
if self.proxies[self.read_trl] is None:
|
|
@@ -712,7 +786,8 @@ class TangoSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
|
712
786
|
proxy = self.proxies[self.read_trl]
|
|
713
787
|
if proxy is None:
|
|
714
788
|
raise NotConnected(f"Not connected to {self.read_trl}")
|
|
715
|
-
|
|
789
|
+
value = await proxy.get()
|
|
790
|
+
return cast(SignalDatatypeT, value)
|
|
716
791
|
|
|
717
792
|
async def get_setpoint(self) -> SignalDatatypeT:
|
|
718
793
|
if self.proxies[self.write_trl] is None:
|
|
@@ -720,7 +795,8 @@ class TangoSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
|
720
795
|
proxy = self.proxies[self.write_trl]
|
|
721
796
|
if proxy is None:
|
|
722
797
|
raise NotConnected(f"Not connected to {self.write_trl}")
|
|
723
|
-
|
|
798
|
+
w_value = await proxy.get_w_value()
|
|
799
|
+
return cast(SignalDatatypeT, w_value)
|
|
724
800
|
|
|
725
801
|
def set_callback(self, callback: Callback | None) -> None:
|
|
726
802
|
if self.proxies[self.read_trl] is None:
|
|
@@ -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)
|
|
@@ -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
|
|
|
@@ -56,7 +57,7 @@ class TangoMover(TangoReadable, Movable, Stoppable):
|
|
|
56
57
|
await self.position.set(value, wait=False, timeout=timeout)
|
|
57
58
|
|
|
58
59
|
move_status = AsyncStatus(
|
|
59
|
-
wait_for_value(self.state,
|
|
60
|
+
wait_for_value(self.state, DevStateEnum.ON, timeout=timeout)
|
|
60
61
|
)
|
|
61
62
|
|
|
62
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
|