ophyd-async 0.8.0a5__py3-none-any.whl → 0.9.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/_version.py +2 -2
- ophyd_async/core/__init__.py +4 -26
- ophyd_async/core/_detector.py +9 -9
- ophyd_async/core/_device.py +27 -8
- ophyd_async/core/_protocol.py +0 -28
- ophyd_async/core/_signal.py +111 -136
- ophyd_async/core/_table.py +9 -4
- ophyd_async/core/_utils.py +11 -2
- ophyd_async/epics/adaravis/_aravis_controller.py +8 -8
- ophyd_async/epics/adaravis/_aravis_io.py +4 -4
- ophyd_async/epics/adcore/_core_io.py +21 -21
- ophyd_async/epics/adcore/_core_logic.py +6 -3
- ophyd_async/epics/adcore/_hdf_writer.py +6 -3
- ophyd_async/epics/adcore/_single_trigger.py +1 -1
- ophyd_async/epics/adcore/_utils.py +35 -35
- ophyd_async/epics/adkinetix/_kinetix_controller.py +7 -7
- ophyd_async/epics/adkinetix/_kinetix_io.py +7 -7
- ophyd_async/epics/adpilatus/_pilatus.py +3 -3
- ophyd_async/epics/adpilatus/_pilatus_controller.py +4 -4
- ophyd_async/epics/adpilatus/_pilatus_io.py +5 -5
- ophyd_async/epics/adsimdetector/_sim_controller.py +2 -2
- ophyd_async/epics/advimba/_vimba_controller.py +14 -14
- ophyd_async/epics/advimba/_vimba_io.py +23 -23
- ophyd_async/epics/core/_p4p.py +19 -0
- ophyd_async/epics/core/_pvi_connector.py +4 -2
- ophyd_async/epics/core/_signal.py +9 -2
- ophyd_async/epics/core/_util.py +9 -0
- ophyd_async/epics/demo/_mover.py +2 -2
- ophyd_async/epics/demo/_sensor.py +2 -2
- ophyd_async/epics/eiger/_eiger_controller.py +10 -5
- ophyd_async/epics/eiger/_eiger_io.py +3 -3
- ophyd_async/epics/motor.py +8 -5
- ophyd_async/epics/testing/__init__.py +24 -0
- ophyd_async/epics/testing/_example_ioc.py +107 -0
- ophyd_async/epics/testing/_utils.py +78 -0
- ophyd_async/epics/testing/test_records.db +158 -0
- ophyd_async/epics/testing/test_records_pva.db +177 -0
- ophyd_async/fastcs/core.py +2 -2
- ophyd_async/fastcs/panda/_block.py +9 -9
- ophyd_async/fastcs/panda/_control.py +2 -2
- ophyd_async/fastcs/panda/_hdf_panda.py +4 -1
- ophyd_async/fastcs/panda/_trigger.py +7 -7
- ophyd_async/plan_stubs/_fly.py +1 -1
- ophyd_async/sim/demo/_sim_motor.py +34 -32
- ophyd_async/tango/__init__.py +0 -43
- ophyd_async/tango/{signal → core}/__init__.py +7 -2
- ophyd_async/tango/{base_devices → core}/_base_device.py +38 -64
- ophyd_async/tango/{signal → core}/_signal.py +13 -3
- ophyd_async/tango/{base_devices → core}/_tango_readable.py +3 -4
- ophyd_async/tango/{signal → core}/_tango_transport.py +1 -1
- ophyd_async/tango/demo/_counter.py +6 -7
- ophyd_async/tango/demo/_mover.py +8 -7
- ophyd_async/testing/__init__.py +33 -0
- ophyd_async/testing/_assert.py +128 -0
- ophyd_async/{core → testing}/_mock_signal_utils.py +12 -8
- ophyd_async/testing/_wait_for_pending.py +22 -0
- {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0a1.dist-info}/METADATA +49 -47
- ophyd_async-0.9.0a1.dist-info/RECORD +119 -0
- {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0a1.dist-info}/WHEEL +1 -1
- ophyd_async/tango/base_devices/__init__.py +0 -4
- ophyd_async-0.8.0a5.dist-info/RECORD +0 -112
- {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0a1.dist-info}/LICENSE +0 -0
- {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0a1.dist-info}/entry_points.txt +0 -0
- {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0a1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
record(waveform, "$(device)int8a") {
|
|
2
|
+
field(NELM, "3")
|
|
3
|
+
field(FTVL, "CHAR")
|
|
4
|
+
field(INP, {const:[-128, 127]})
|
|
5
|
+
field(PINI, "YES")
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
record(waveform, "$(device)uint16a") {
|
|
9
|
+
field(NELM, "3")
|
|
10
|
+
field(FTVL, "USHORT")
|
|
11
|
+
field(INP, {const:[0, 65535]})
|
|
12
|
+
field(PINI, "YES")
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
record(waveform, "$(device)uint32a") {
|
|
16
|
+
field(NELM, "3")
|
|
17
|
+
field(FTVL, "ULONG")
|
|
18
|
+
field(INP, {const:[0, 4294967295]})
|
|
19
|
+
field(PINI, "YES")
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
record(waveform, "$(device)int64a") {
|
|
23
|
+
field(NELM, "3")
|
|
24
|
+
field(FTVL, "INT64")
|
|
25
|
+
# Can't do 64-bit int with JSON numbers in a const link...
|
|
26
|
+
field(INP, {const:[-2147483649, 2147483648]})
|
|
27
|
+
field(PINI, "YES")
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
record(waveform, "$(device)uint64a") {
|
|
31
|
+
field(NELM, "3")
|
|
32
|
+
field(FTVL, "UINT64")
|
|
33
|
+
field(INP, {const:[0, 4294967297]})
|
|
34
|
+
field(PINI, "YES")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
record(waveform, "$(device)table:labels") {
|
|
38
|
+
field(FTVL, "STRING")
|
|
39
|
+
field(NELM, "5")
|
|
40
|
+
field(INP, {const:["Bool", "Int", "Float", "Str", "Enum"]})
|
|
41
|
+
field(PINI, "YES")
|
|
42
|
+
info(Q:group, {
|
|
43
|
+
"$(device)table": {
|
|
44
|
+
"+id": "epics:nt/NTTable:1.0",
|
|
45
|
+
"labels": {
|
|
46
|
+
"+type": "plain",
|
|
47
|
+
"+channel": "VAL"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
record(waveform, "$(device)table:bool")
|
|
54
|
+
{
|
|
55
|
+
field(FTVL, "UCHAR")
|
|
56
|
+
field(NELM, "4096")
|
|
57
|
+
field(INP, {const:[false, false, true, true]})
|
|
58
|
+
field(PINI, "YES")
|
|
59
|
+
info(Q:group, {
|
|
60
|
+
"$(device)table": {
|
|
61
|
+
"value.bool": {
|
|
62
|
+
"+type": "plain",
|
|
63
|
+
"+channel": "VAL",
|
|
64
|
+
"+putorder": 1
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
record(waveform, "$(device)table:int")
|
|
71
|
+
{
|
|
72
|
+
field(FTVL, "LONG")
|
|
73
|
+
field(NELM, "4096")
|
|
74
|
+
field(INP, {const:[1, 8, -9, 32]})
|
|
75
|
+
field(PINI, "YES")
|
|
76
|
+
info(Q:group, {
|
|
77
|
+
"$(device)table": {
|
|
78
|
+
"value.int": {
|
|
79
|
+
"+type": "plain",
|
|
80
|
+
"+channel": "VAL",
|
|
81
|
+
"+putorder": 2
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
record(waveform, "$(device)table:float")
|
|
88
|
+
{
|
|
89
|
+
field(FTVL, "DOUBLE")
|
|
90
|
+
field(NELM, "4096")
|
|
91
|
+
field(INP, {const:[1.8, 8.2, -6, 32.9887]})
|
|
92
|
+
field(PINI, "YES")
|
|
93
|
+
info(Q:group, {
|
|
94
|
+
"$(device)table": {
|
|
95
|
+
"value.float": {
|
|
96
|
+
"+type": "plain",
|
|
97
|
+
"+channel": "VAL",
|
|
98
|
+
"+putorder": 3
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
record(waveform, "$(device)table:str")
|
|
105
|
+
{
|
|
106
|
+
field(FTVL, "STRING")
|
|
107
|
+
field(NELM, "4096")
|
|
108
|
+
field(INP, {const:["Hello", "World", "Foo", "Bar"]})
|
|
109
|
+
field(PINI, "YES")
|
|
110
|
+
info(Q:group, {
|
|
111
|
+
"$(device)table": {
|
|
112
|
+
"value.str": {
|
|
113
|
+
"+type": "plain",
|
|
114
|
+
"+channel": "VAL",
|
|
115
|
+
"+putorder": 4
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
record(waveform, "$(device)table:enum")
|
|
122
|
+
{
|
|
123
|
+
field(FTVL, "STRING")
|
|
124
|
+
field(NELM, "4096")
|
|
125
|
+
field(INP, {const:["Aaa", "Bbb", "Aaa", "Ccc"]})
|
|
126
|
+
field(PINI, "YES")
|
|
127
|
+
info(Q:group, {
|
|
128
|
+
"$(device)table": {
|
|
129
|
+
"value.enum": {
|
|
130
|
+
"+type": "plain",
|
|
131
|
+
"+channel": "VAL",
|
|
132
|
+
"+putorder": 5,
|
|
133
|
+
"+trigger": "*",
|
|
134
|
+
},
|
|
135
|
+
"": {"+type": "meta", "+channel": "VAL"}
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
record(longout, "$(device)ntndarray:ArraySize0_RBV") {
|
|
141
|
+
field(VAL, "3")
|
|
142
|
+
field(PINI, "YES")
|
|
143
|
+
info(Q:group, {
|
|
144
|
+
"$(device)ntndarray":{
|
|
145
|
+
"dimension[0].size":{+channel:"VAL", +type:"plain", +putorder:0}
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
record(longout, "$(device)ntndarray:ArraySize1_RBV") {
|
|
151
|
+
field(VAL, "2")
|
|
152
|
+
field(PINI, "YES")
|
|
153
|
+
info(Q:group, {
|
|
154
|
+
"$(device)ntndarray":{
|
|
155
|
+
"dimension[1].size":{+channel:"VAL", +type:"plain", +putorder:0}
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
record(waveform, "$(device)ntndarray:data")
|
|
161
|
+
{
|
|
162
|
+
field(FTVL, "INT64")
|
|
163
|
+
field(NELM, "6")
|
|
164
|
+
field(INP, {const:[0, 0, 0, 0, 0, 0]})
|
|
165
|
+
field(PINI, "YES")
|
|
166
|
+
info(Q:group, {
|
|
167
|
+
"$(device)ntndarray":{
|
|
168
|
+
+id:"epics:nt/NTNDArray:1.0",
|
|
169
|
+
"value":{
|
|
170
|
+
+type:"any",
|
|
171
|
+
+channel:"VAL",
|
|
172
|
+
+trigger:"*",
|
|
173
|
+
},
|
|
174
|
+
"": {+type:"meta", +channel:"SEVR"}
|
|
175
|
+
}
|
|
176
|
+
})
|
|
177
|
+
}
|
ophyd_async/fastcs/core.py
CHANGED
|
@@ -2,8 +2,8 @@ from ophyd_async.core import Device, DeviceConnector
|
|
|
2
2
|
from ophyd_async.epics.core import PviDeviceConnector
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
def fastcs_connector(device: Device, uri: str) -> DeviceConnector:
|
|
5
|
+
def fastcs_connector(device: Device, uri: str, error_hint: str = "") -> DeviceConnector:
|
|
6
6
|
# TODO: add Tango support based on uri scheme
|
|
7
|
-
connector = PviDeviceConnector(uri)
|
|
7
|
+
connector = PviDeviceConnector(uri, error_hint)
|
|
8
8
|
connector.create_children_from_annotations(device)
|
|
9
9
|
return connector
|
|
@@ -36,14 +36,14 @@ class PulseBlock(Device):
|
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
class PcompDirection(StrictEnum):
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
POSITIVE = "Positive"
|
|
40
|
+
NEGATIVE = "Negative"
|
|
41
|
+
EITHER = "Either"
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
class BitMux(SubsetEnum):
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
ZERO = "ZERO"
|
|
46
|
+
ONE = "ONE"
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
class PcompBlock(Device):
|
|
@@ -57,10 +57,10 @@ class PcompBlock(Device):
|
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
class TimeUnits(StrictEnum):
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
MIN = "min"
|
|
61
|
+
S = "s"
|
|
62
|
+
MS = "ms"
|
|
63
|
+
US = "us"
|
|
64
64
|
|
|
65
65
|
|
|
66
66
|
class SeqBlock(Device):
|
|
@@ -19,8 +19,8 @@ class PandaPcapController(DetectorController):
|
|
|
19
19
|
|
|
20
20
|
async def prepare(self, trigger_info: TriggerInfo):
|
|
21
21
|
assert trigger_info.trigger in (
|
|
22
|
-
DetectorTrigger.
|
|
23
|
-
DetectorTrigger.
|
|
22
|
+
DetectorTrigger.CONSTANT_GATE,
|
|
23
|
+
DetectorTrigger.VARIABLE_GATE,
|
|
24
24
|
), "Only constant_gate and variable_gate triggering is supported on the PandA"
|
|
25
25
|
|
|
26
26
|
async def arm(self):
|
|
@@ -9,6 +9,8 @@ from ._block import CommonPandaBlocks
|
|
|
9
9
|
from ._control import PandaPcapController
|
|
10
10
|
from ._writer import PandaHDFWriter
|
|
11
11
|
|
|
12
|
+
MINIMUM_PANDA_IOC = "0.11.4"
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
class HDFPanda(CommonPandaBlocks, StandardDetector):
|
|
14
16
|
def __init__(
|
|
@@ -18,8 +20,9 @@ class HDFPanda(CommonPandaBlocks, StandardDetector):
|
|
|
18
20
|
config_sigs: Sequence[SignalR] = (),
|
|
19
21
|
name: str = "",
|
|
20
22
|
):
|
|
23
|
+
error_hint = f"Is PandABlocks-ioc at least version {MINIMUM_PANDA_IOC}?"
|
|
21
24
|
# This has to be first so we make self.pcap
|
|
22
|
-
connector = fastcs_connector(self, prefix)
|
|
25
|
+
connector = fastcs_connector(self, prefix, error_hint)
|
|
23
26
|
controller = PandaPcapController(pcap=self.pcap)
|
|
24
27
|
writer = PandaHDFWriter(
|
|
25
28
|
path_provider=path_provider,
|
|
@@ -20,8 +20,8 @@ class StaticSeqTableTriggerLogic(FlyerController[SeqTableInfo]):
|
|
|
20
20
|
|
|
21
21
|
async def prepare(self, value: SeqTableInfo):
|
|
22
22
|
await asyncio.gather(
|
|
23
|
-
self.seq.prescale_units.set(TimeUnits.
|
|
24
|
-
self.seq.enable.set(BitMux.
|
|
23
|
+
self.seq.prescale_units.set(TimeUnits.US),
|
|
24
|
+
self.seq.enable.set(BitMux.ZERO),
|
|
25
25
|
)
|
|
26
26
|
await asyncio.gather(
|
|
27
27
|
self.seq.prescale.set(value.prescale_as_us),
|
|
@@ -30,14 +30,14 @@ class StaticSeqTableTriggerLogic(FlyerController[SeqTableInfo]):
|
|
|
30
30
|
)
|
|
31
31
|
|
|
32
32
|
async def kickoff(self) -> None:
|
|
33
|
-
await self.seq.enable.set(BitMux.
|
|
33
|
+
await self.seq.enable.set(BitMux.ONE)
|
|
34
34
|
await wait_for_value(self.seq.active, True, timeout=1)
|
|
35
35
|
|
|
36
36
|
async def complete(self) -> None:
|
|
37
37
|
await wait_for_value(self.seq.active, False, timeout=None)
|
|
38
38
|
|
|
39
39
|
async def stop(self):
|
|
40
|
-
await self.seq.enable.set(BitMux.
|
|
40
|
+
await self.seq.enable.set(BitMux.ZERO)
|
|
41
41
|
await wait_for_value(self.seq.active, False, timeout=1)
|
|
42
42
|
|
|
43
43
|
|
|
@@ -68,7 +68,7 @@ class StaticPcompTriggerLogic(FlyerController[PcompInfo]):
|
|
|
68
68
|
self.pcomp = pcomp
|
|
69
69
|
|
|
70
70
|
async def prepare(self, value: PcompInfo):
|
|
71
|
-
await self.pcomp.enable.set(BitMux.
|
|
71
|
+
await self.pcomp.enable.set(BitMux.ZERO)
|
|
72
72
|
await asyncio.gather(
|
|
73
73
|
self.pcomp.start.set(value.start_postion),
|
|
74
74
|
self.pcomp.width.set(value.pulse_width),
|
|
@@ -78,12 +78,12 @@ class StaticPcompTriggerLogic(FlyerController[PcompInfo]):
|
|
|
78
78
|
)
|
|
79
79
|
|
|
80
80
|
async def kickoff(self) -> None:
|
|
81
|
-
await self.pcomp.enable.set(BitMux.
|
|
81
|
+
await self.pcomp.enable.set(BitMux.ONE)
|
|
82
82
|
await wait_for_value(self.pcomp.active, True, timeout=1)
|
|
83
83
|
|
|
84
84
|
async def complete(self, timeout: float | None = None) -> None:
|
|
85
85
|
await wait_for_value(self.pcomp.active, False, timeout=timeout)
|
|
86
86
|
|
|
87
87
|
async def stop(self):
|
|
88
|
-
await self.pcomp.enable.set(BitMux.
|
|
88
|
+
await self.pcomp.enable.set(BitMux.ZERO)
|
|
89
89
|
await wait_for_value(self.pcomp.active, False, timeout=1)
|
ophyd_async/plan_stubs/_fly.py
CHANGED
|
@@ -62,7 +62,7 @@ def prepare_static_seq_table_flyer_and_detectors_with_same_trigger(
|
|
|
62
62
|
|
|
63
63
|
trigger_info = TriggerInfo(
|
|
64
64
|
number_of_triggers=number_of_frames * repeats,
|
|
65
|
-
trigger=DetectorTrigger.
|
|
65
|
+
trigger=DetectorTrigger.CONSTANT_GATE,
|
|
66
66
|
deadtime=deadtime,
|
|
67
67
|
livetime=exposure,
|
|
68
68
|
frame_timeout=frame_timeout,
|
|
@@ -2,6 +2,7 @@ import asyncio
|
|
|
2
2
|
import contextlib
|
|
3
3
|
import time
|
|
4
4
|
|
|
5
|
+
import numpy as np
|
|
5
6
|
from bluesky.protocols import Movable, Stoppable
|
|
6
7
|
|
|
7
8
|
from ophyd_async.core import (
|
|
@@ -44,22 +45,20 @@ class SimMotor(StandardReadable, Movable, Stoppable):
|
|
|
44
45
|
|
|
45
46
|
async def _move(self, old_position: float, new_position: float, move_time: float):
|
|
46
47
|
start = time.monotonic()
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
# 10hz update loop
|
|
62
|
-
await asyncio.sleep(0.1)
|
|
48
|
+
# Make an array of relative update times at 10Hz intervals
|
|
49
|
+
update_times = np.arange(0.1, move_time, 0.1)
|
|
50
|
+
# With the end position appended
|
|
51
|
+
update_times = np.concatenate((update_times, [move_time]))
|
|
52
|
+
# Interpolate the [old, new] position array with those update times
|
|
53
|
+
new_positions = np.interp(
|
|
54
|
+
update_times, [0, move_time], [old_position, new_position]
|
|
55
|
+
)
|
|
56
|
+
for update_time, new_position in zip(update_times, new_positions, strict=True):
|
|
57
|
+
# Calculate how long to wait to get there
|
|
58
|
+
relative_time = time.monotonic() - start
|
|
59
|
+
await asyncio.sleep(update_time - relative_time)
|
|
60
|
+
# Update the readback position
|
|
61
|
+
self._user_readback_set(new_position)
|
|
63
62
|
|
|
64
63
|
@WatchableAsyncStatus.wrap
|
|
65
64
|
async def set(self, value: float):
|
|
@@ -75,22 +74,25 @@ class SimMotor(StandardReadable, Movable, Stoppable):
|
|
|
75
74
|
self.velocity.get_value(),
|
|
76
75
|
)
|
|
77
76
|
# If zero velocity, do instant move
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
):
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
77
|
+
if velocity == 0:
|
|
78
|
+
self._user_readback_set(new_position)
|
|
79
|
+
else:
|
|
80
|
+
move_time = abs(new_position - old_position) / velocity
|
|
81
|
+
self._move_status = AsyncStatus(
|
|
82
|
+
self._move(old_position, new_position, move_time)
|
|
83
|
+
)
|
|
84
|
+
# If stop is called then this will raise a CancelledError, ignore it
|
|
85
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
86
|
+
async for current_position in observe_value(
|
|
87
|
+
self.user_readback, done_status=self._move_status
|
|
88
|
+
):
|
|
89
|
+
yield WatcherUpdate(
|
|
90
|
+
current=current_position,
|
|
91
|
+
initial=old_position,
|
|
92
|
+
target=new_position,
|
|
93
|
+
name=self.name,
|
|
94
|
+
unit=units,
|
|
95
|
+
)
|
|
94
96
|
if not self._set_success:
|
|
95
97
|
raise RuntimeError("Motor was stopped")
|
|
96
98
|
|
ophyd_async/tango/__init__.py
CHANGED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
from .base_devices import (
|
|
2
|
-
TangoDevice,
|
|
3
|
-
TangoReadable,
|
|
4
|
-
tango_polling,
|
|
5
|
-
)
|
|
6
|
-
from .signal import (
|
|
7
|
-
AttributeProxy,
|
|
8
|
-
CommandProxy,
|
|
9
|
-
TangoSignalBackend,
|
|
10
|
-
ensure_proper_executor,
|
|
11
|
-
get_dtype_extended,
|
|
12
|
-
get_python_type,
|
|
13
|
-
get_tango_trl,
|
|
14
|
-
get_trl_descriptor,
|
|
15
|
-
infer_python_type,
|
|
16
|
-
infer_signal_type,
|
|
17
|
-
make_backend,
|
|
18
|
-
tango_signal_r,
|
|
19
|
-
tango_signal_rw,
|
|
20
|
-
tango_signal_w,
|
|
21
|
-
tango_signal_x,
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
__all__ = [
|
|
25
|
-
"TangoDevice",
|
|
26
|
-
"TangoReadable",
|
|
27
|
-
"tango_polling",
|
|
28
|
-
"TangoSignalBackend",
|
|
29
|
-
"get_python_type",
|
|
30
|
-
"get_dtype_extended",
|
|
31
|
-
"get_trl_descriptor",
|
|
32
|
-
"get_tango_trl",
|
|
33
|
-
"infer_python_type",
|
|
34
|
-
"infer_signal_type",
|
|
35
|
-
"make_backend",
|
|
36
|
-
"AttributeProxy",
|
|
37
|
-
"CommandProxy",
|
|
38
|
-
"ensure_proper_executor",
|
|
39
|
-
"tango_signal_r",
|
|
40
|
-
"tango_signal_rw",
|
|
41
|
-
"tango_signal_w",
|
|
42
|
-
"tango_signal_x",
|
|
43
|
-
]
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from ._base_device import TangoDevice, TangoPolling
|
|
1
2
|
from ._signal import (
|
|
2
3
|
infer_python_type,
|
|
3
4
|
infer_signal_type,
|
|
@@ -7,6 +8,7 @@ from ._signal import (
|
|
|
7
8
|
tango_signal_w,
|
|
8
9
|
tango_signal_x,
|
|
9
10
|
)
|
|
11
|
+
from ._tango_readable import TangoReadable
|
|
10
12
|
from ._tango_transport import (
|
|
11
13
|
AttributeProxy,
|
|
12
14
|
CommandProxy,
|
|
@@ -18,7 +20,7 @@ from ._tango_transport import (
|
|
|
18
20
|
get_trl_descriptor,
|
|
19
21
|
)
|
|
20
22
|
|
|
21
|
-
__all__ =
|
|
23
|
+
__all__ = [
|
|
22
24
|
"AttributeProxy",
|
|
23
25
|
"CommandProxy",
|
|
24
26
|
"ensure_proper_executor",
|
|
@@ -34,4 +36,7 @@ __all__ = (
|
|
|
34
36
|
"tango_signal_rw",
|
|
35
37
|
"tango_signal_w",
|
|
36
38
|
"tango_signal_x",
|
|
37
|
-
|
|
39
|
+
"TangoDevice",
|
|
40
|
+
"TangoReadable",
|
|
41
|
+
"TangoPolling",
|
|
42
|
+
]
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
from ophyd_async.core
|
|
7
|
-
from ophyd_async.tango.signal import (
|
|
8
|
-
TangoSignalBackend,
|
|
9
|
-
infer_python_type,
|
|
10
|
-
infer_signal_type,
|
|
11
|
-
)
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Generic, TypeVar
|
|
5
|
+
|
|
6
|
+
from ophyd_async.core import Device, DeviceConnector, DeviceFiller, LazyMock
|
|
12
7
|
from tango import DeviceProxy as DeviceProxy
|
|
13
8
|
from tango.asyncio import DeviceProxy as AsyncDeviceProxy
|
|
14
9
|
|
|
10
|
+
from ._signal import TangoSignalBackend, infer_python_type, infer_signal_type
|
|
11
|
+
|
|
15
12
|
T = TypeVar("T")
|
|
16
13
|
|
|
17
14
|
|
|
@@ -32,63 +29,45 @@ class TangoDevice(Device):
|
|
|
32
29
|
|
|
33
30
|
trl: str = ""
|
|
34
31
|
proxy: DeviceProxy | None = None
|
|
35
|
-
_polling: tuple[bool, float, float | None, float | None] = (False, 0.1, None, 0.1)
|
|
36
|
-
_signal_polling: dict[str, tuple[bool, float, float, float]] = {}
|
|
37
|
-
_poll_only_annotated_signals: bool = True
|
|
38
32
|
|
|
39
33
|
def __init__(
|
|
40
34
|
self,
|
|
41
35
|
trl: str | None = None,
|
|
42
36
|
device_proxy: DeviceProxy | None = None,
|
|
37
|
+
support_events: bool = False,
|
|
43
38
|
name: str = "",
|
|
44
39
|
) -> None:
|
|
45
40
|
connector = TangoDeviceConnector(
|
|
46
|
-
trl=trl,
|
|
47
|
-
device_proxy=device_proxy,
|
|
48
|
-
polling=self._polling,
|
|
49
|
-
signal_polling=self._signal_polling,
|
|
41
|
+
trl=trl, device_proxy=device_proxy, support_events=support_events
|
|
50
42
|
)
|
|
51
43
|
super().__init__(name=name, connector=connector)
|
|
52
44
|
|
|
53
45
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
| None = None
|
|
58
|
-
|
|
59
|
-
):
|
|
60
|
-
"""
|
|
61
|
-
Class decorator to configure polling for Tango devices.
|
|
62
|
-
|
|
63
|
-
This decorator allows for the configuration of both device-level and signal-level
|
|
64
|
-
polling for Tango devices. Polling is useful for device servers that do not support
|
|
65
|
-
event-driven updates.
|
|
66
|
-
|
|
67
|
-
Parameters
|
|
68
|
-
----------
|
|
69
|
-
polling : Optional[Union[Tuple[float, float, float],
|
|
70
|
-
Dict[str, Tuple[float, float, float]]]], optional
|
|
71
|
-
Device-level polling configuration as a tuple of three floats representing the
|
|
72
|
-
polling interval, polling timeout, and polling delay. Alternatively,
|
|
73
|
-
a dictionary can be provided to specify signal-level polling configurations
|
|
74
|
-
directly.
|
|
75
|
-
signal_polling : Optional[Dict[str, Tuple[float, float, float]]], optional
|
|
76
|
-
Signal-level polling configuration as a dictionary where keys are signal names
|
|
77
|
-
and values are tuples of three floats representing the polling interval, polling
|
|
78
|
-
timeout, and polling delay.
|
|
79
|
-
"""
|
|
80
|
-
if isinstance(polling, dict):
|
|
81
|
-
signal_polling = polling
|
|
82
|
-
polling = None
|
|
46
|
+
@dataclass
|
|
47
|
+
class TangoPolling(Generic[T]):
|
|
48
|
+
ophyd_polling_period: float = 0.1
|
|
49
|
+
abs_change: T | None = None
|
|
50
|
+
rel_change: T | None = None
|
|
83
51
|
|
|
84
|
-
def decorator(cls):
|
|
85
|
-
if polling is not None:
|
|
86
|
-
cls._polling = (True, *polling)
|
|
87
|
-
if signal_polling is not None:
|
|
88
|
-
cls._signal_polling = {k: (True, *v) for k, v in signal_polling.items()}
|
|
89
|
-
return cls
|
|
90
52
|
|
|
91
|
-
|
|
53
|
+
def fill_backend_with_polling(
|
|
54
|
+
support_events: bool, backend: TangoSignalBackend, annotations: list[Any]
|
|
55
|
+
):
|
|
56
|
+
unhandled = []
|
|
57
|
+
while annotations:
|
|
58
|
+
annotation = annotations.pop(0)
|
|
59
|
+
backend.allow_events(support_events)
|
|
60
|
+
if isinstance(annotation, TangoPolling):
|
|
61
|
+
backend.set_polling(
|
|
62
|
+
not support_events,
|
|
63
|
+
annotation.ophyd_polling_period,
|
|
64
|
+
annotation.abs_change,
|
|
65
|
+
annotation.rel_change,
|
|
66
|
+
)
|
|
67
|
+
else:
|
|
68
|
+
unhandled.append(annotation)
|
|
69
|
+
annotations.extend(unhandled)
|
|
70
|
+
# These leftover annotations will now be handled by the iterator
|
|
92
71
|
|
|
93
72
|
|
|
94
73
|
class TangoDeviceConnector(DeviceConnector):
|
|
@@ -96,13 +75,11 @@ class TangoDeviceConnector(DeviceConnector):
|
|
|
96
75
|
self,
|
|
97
76
|
trl: str | None,
|
|
98
77
|
device_proxy: DeviceProxy | None,
|
|
99
|
-
|
|
100
|
-
signal_polling: dict[str, tuple[bool, float, float, float]],
|
|
78
|
+
support_events: bool,
|
|
101
79
|
) -> None:
|
|
102
80
|
self.trl = trl
|
|
103
81
|
self.proxy = device_proxy
|
|
104
|
-
self.
|
|
105
|
-
self._signal_polling = signal_polling
|
|
82
|
+
self._support_events = support_events
|
|
106
83
|
|
|
107
84
|
def create_children_from_annotations(self, device: Device):
|
|
108
85
|
if not hasattr(self, "filler"):
|
|
@@ -110,11 +87,14 @@ class TangoDeviceConnector(DeviceConnector):
|
|
|
110
87
|
device=device,
|
|
111
88
|
signal_backend_factory=TangoSignalBackend,
|
|
112
89
|
device_connector_factory=lambda: TangoDeviceConnector(
|
|
113
|
-
None, None,
|
|
90
|
+
None, None, self._support_events
|
|
114
91
|
),
|
|
115
92
|
)
|
|
116
93
|
list(self.filler.create_devices_from_annotations(filled=False))
|
|
117
|
-
|
|
94
|
+
for backend, annotations in self.filler.create_signals_from_annotations(
|
|
95
|
+
filled=False
|
|
96
|
+
):
|
|
97
|
+
fill_backend_with_polling(self._support_events, backend, annotations)
|
|
118
98
|
self.filler.check_created()
|
|
119
99
|
|
|
120
100
|
async def connect_mock(self, device: Device, mock: LazyMock):
|
|
@@ -145,12 +125,6 @@ class TangoDeviceConnector(DeviceConnector):
|
|
|
145
125
|
backend = self.filler.fill_child_signal(name, signal_type)
|
|
146
126
|
backend.datatype = await infer_python_type(full_trl, self.proxy)
|
|
147
127
|
backend.set_trl(full_trl)
|
|
148
|
-
if polling := self._signal_polling.get(name, ()):
|
|
149
|
-
backend.set_polling(*polling)
|
|
150
|
-
backend.allow_events(False)
|
|
151
|
-
elif self._polling[0]:
|
|
152
|
-
backend.set_polling(*self._polling)
|
|
153
|
-
backend.allow_events(False)
|
|
154
128
|
# Check that all the requested children have been filled
|
|
155
129
|
self.filler.check_filled(f"{self.trl}: {children}")
|
|
156
130
|
# Set the name of the device to name all children
|
|
@@ -16,7 +16,14 @@ from ophyd_async.core import (
|
|
|
16
16
|
SignalW,
|
|
17
17
|
SignalX,
|
|
18
18
|
)
|
|
19
|
-
from tango import
|
|
19
|
+
from tango import (
|
|
20
|
+
AttrDataFormat,
|
|
21
|
+
AttrWriteType,
|
|
22
|
+
CmdArgType,
|
|
23
|
+
DeviceProxy,
|
|
24
|
+
DevState,
|
|
25
|
+
NonSupportedFeature, # type: ignore
|
|
26
|
+
)
|
|
20
27
|
from tango.asyncio import DeviceProxy as AsyncDeviceProxy
|
|
21
28
|
|
|
22
29
|
from ._tango_transport import TangoSignalBackend, get_python_type
|
|
@@ -174,8 +181,11 @@ async def infer_signal_type(
|
|
|
174
181
|
else:
|
|
175
182
|
dev_proxy = proxy
|
|
176
183
|
|
|
177
|
-
|
|
178
|
-
|
|
184
|
+
try:
|
|
185
|
+
if tr_name in dev_proxy.get_pipe_list():
|
|
186
|
+
raise NotImplementedError("Pipes are not supported")
|
|
187
|
+
except NonSupportedFeature: # type: ignore
|
|
188
|
+
pass
|
|
179
189
|
|
|
180
190
|
if tr_name not in dev_proxy.get_attribute_list():
|
|
181
191
|
if tr_name not in dev_proxy.get_command_list():
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from ophyd_async.core import
|
|
4
|
-
StandardReadable,
|
|
5
|
-
)
|
|
6
|
-
from ophyd_async.tango.base_devices._base_device import TangoDevice
|
|
3
|
+
from ophyd_async.core import StandardReadable
|
|
7
4
|
from tango import DeviceProxy
|
|
8
5
|
|
|
6
|
+
from ._base_device import TangoDevice
|
|
7
|
+
|
|
9
8
|
|
|
10
9
|
class TangoReadable(TangoDevice, StandardReadable):
|
|
11
10
|
"""
|