ophyd-async 0.13.3__py3-none-any.whl → 0.13.5__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 +26 -3
- ophyd_async/core/_derived_signal_backend.py +2 -1
- ophyd_async/core/_detector.py +2 -2
- ophyd_async/core/_device.py +9 -9
- ophyd_async/core/_enums.py +5 -0
- ophyd_async/core/_signal.py +34 -38
- ophyd_async/core/_signal_backend.py +3 -1
- ophyd_async/core/_status.py +2 -2
- ophyd_async/core/_table.py +8 -0
- ophyd_async/core/_utils.py +11 -11
- ophyd_async/epics/adcore/_core_logic.py +3 -1
- ophyd_async/epics/adcore/_utils.py +4 -4
- ophyd_async/epics/core/_aioca.py +2 -2
- ophyd_async/epics/core/_p4p.py +2 -2
- ophyd_async/epics/motor.py +28 -7
- ophyd_async/epics/pmac/_pmac_io.py +8 -4
- ophyd_async/epics/pmac/_pmac_trajectory.py +144 -41
- ophyd_async/epics/pmac/_pmac_trajectory_generation.py +692 -0
- ophyd_async/epics/pmac/_utils.py +1 -681
- ophyd_async/fastcs/jungfrau/__init__.py +2 -1
- ophyd_async/fastcs/jungfrau/_controller.py +29 -11
- ophyd_async/fastcs/jungfrau/_utils.py +10 -2
- ophyd_async/fastcs/panda/__init__.py +10 -0
- ophyd_async/fastcs/panda/_block.py +14 -0
- ophyd_async/fastcs/panda/_trigger.py +123 -3
- ophyd_async/sim/_motor.py +4 -2
- ophyd_async/sim/_stage.py +14 -4
- ophyd_async/tango/core/__init__.py +17 -3
- ophyd_async/tango/core/_signal.py +18 -22
- ophyd_async/tango/core/_tango_transport.py +407 -239
- ophyd_async/tango/core/_utils.py +9 -0
- ophyd_async/tango/demo/_mover.py +1 -2
- ophyd_async/tango/testing/__init__.py +2 -1
- ophyd_async/tango/testing/_one_of_everything.py +13 -5
- ophyd_async/tango/testing/_test_config.py +11 -0
- ophyd_async/testing/_assert.py +2 -2
- {ophyd_async-0.13.3.dist-info → ophyd_async-0.13.5.dist-info}/METADATA +2 -36
- {ophyd_async-0.13.3.dist-info → ophyd_async-0.13.5.dist-info}/RECORD +42 -40
- {ophyd_async-0.13.3.dist-info → ophyd_async-0.13.5.dist-info}/WHEEL +0 -0
- {ophyd_async-0.13.3.dist-info → ophyd_async-0.13.5.dist-info}/licenses/LICENSE +0 -0
- {ophyd_async-0.13.3.dist-info → ophyd_async-0.13.5.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from ._controller import JungfrauController
|
|
1
|
+
from ._controller import JUNGFRAU_DEADTIME_S, JungfrauController
|
|
2
2
|
from ._jungfrau import Jungfrau
|
|
3
3
|
from ._signals import (
|
|
4
4
|
AcquisitionType,
|
|
@@ -26,4 +26,5 @@ __all__ = [
|
|
|
26
26
|
"AcquisitionType",
|
|
27
27
|
"GainMode",
|
|
28
28
|
"PedestalMode",
|
|
29
|
+
"JUNGFRAU_DEADTIME_S",
|
|
29
30
|
]
|
|
@@ -113,20 +113,34 @@ class JungfrauController(DetectorController):
|
|
|
113
113
|
]
|
|
114
114
|
)
|
|
115
115
|
case AcquisitionType.PEDESTAL:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
116
|
+
if trigger_info.number_of_events % 2 == 0:
|
|
117
|
+
coros.extend(
|
|
118
|
+
[
|
|
119
|
+
self._driver.pedestal_mode_frames.set(
|
|
120
|
+
trigger_info.exposures_per_event
|
|
121
|
+
),
|
|
122
|
+
# No. events is double the pedestal loops,
|
|
123
|
+
# since pedestal scan does the entire loop
|
|
124
|
+
# twice.
|
|
125
|
+
self._driver.pedestal_mode_loops.set(
|
|
126
|
+
int(trigger_info.number_of_events / 2)
|
|
127
|
+
),
|
|
128
|
+
]
|
|
129
|
+
)
|
|
130
|
+
else:
|
|
131
|
+
raise ValueError(
|
|
132
|
+
f"Invalid trigger info for pedestal mode. "
|
|
133
|
+
f"{trigger_info.number_of_events=} must be divisible by two. "
|
|
134
|
+
f"Was create_jungfrau_pedestal_triggering_info used?"
|
|
135
|
+
)
|
|
127
136
|
|
|
128
137
|
await asyncio.gather(*coros)
|
|
129
138
|
|
|
139
|
+
# Setting signals once the detector is in pedestal mode can cause errors,
|
|
140
|
+
# so do this last
|
|
141
|
+
if acquisition_type == AcquisitionType.PEDESTAL:
|
|
142
|
+
await self._driver.pedestal_mode_state.set(PedestalMode.ON)
|
|
143
|
+
|
|
130
144
|
async def arm(self):
|
|
131
145
|
await self._driver.acquisition_start.trigger()
|
|
132
146
|
|
|
@@ -137,3 +151,7 @@ class JungfrauController(DetectorController):
|
|
|
137
151
|
|
|
138
152
|
async def disarm(self):
|
|
139
153
|
await self._driver.acquisition_stop.trigger()
|
|
154
|
+
await asyncio.gather(
|
|
155
|
+
self._driver.pedestal_mode_state.set(PedestalMode.OFF),
|
|
156
|
+
self._driver.acquisition_type.set(AcquisitionType.STANDARD),
|
|
157
|
+
)
|
|
@@ -2,6 +2,8 @@ from pydantic import PositiveInt
|
|
|
2
2
|
|
|
3
3
|
from ophyd_async.core import DetectorTrigger, TriggerInfo
|
|
4
4
|
|
|
5
|
+
from ._controller import JUNGFRAU_DEADTIME_S
|
|
6
|
+
|
|
5
7
|
|
|
6
8
|
def create_jungfrau_external_triggering_info(
|
|
7
9
|
total_triggers: PositiveInt,
|
|
@@ -11,7 +13,7 @@ def create_jungfrau_external_triggering_info(
|
|
|
11
13
|
|
|
12
14
|
Uses parameters which more closely-align with Jungfrau terminology
|
|
13
15
|
to create TriggerInfo. This device currently only supports one frame per trigger
|
|
14
|
-
when being externally triggered
|
|
16
|
+
when being externally triggered.
|
|
15
17
|
|
|
16
18
|
Args:
|
|
17
19
|
total_triggers: Total external triggers expected before ending acquisition.
|
|
@@ -24,6 +26,7 @@ def create_jungfrau_external_triggering_info(
|
|
|
24
26
|
number_of_events=total_triggers,
|
|
25
27
|
trigger=DetectorTrigger.EDGE_TRIGGER,
|
|
26
28
|
livetime=exposure_time_s,
|
|
29
|
+
deadtime=JUNGFRAU_DEADTIME_S,
|
|
27
30
|
)
|
|
28
31
|
|
|
29
32
|
|
|
@@ -60,6 +63,11 @@ def create_jungfrau_pedestal_triggering_info(
|
|
|
60
63
|
Uses parameters which more closely-align with Jungfrau terminology
|
|
61
64
|
to create TriggerInfo.
|
|
62
65
|
|
|
66
|
+
When the Jungfrau is triggered in pedestal mode, it will run pedestal_frames-1
|
|
67
|
+
frames in dynamic gain mode, then one frame in gain mode 1, then repeat this for
|
|
68
|
+
pedelestal_loops number of times. This entire pattern is then repeated,
|
|
69
|
+
but gain mode 2 is used instead of gain mode 1 for the "one frame" part.
|
|
70
|
+
|
|
63
71
|
NOTE: To trigger the jungfrau in pedestal mode, you must first set the
|
|
64
72
|
jungfrau acquisition_type signal to AcquisitionType.PEDESTAL!
|
|
65
73
|
|
|
@@ -72,7 +80,7 @@ def create_jungfrau_pedestal_triggering_info(
|
|
|
72
80
|
`TriggerInfo`
|
|
73
81
|
"""
|
|
74
82
|
return TriggerInfo(
|
|
75
|
-
number_of_events=pedestal_loops,
|
|
83
|
+
number_of_events=pedestal_loops * 2,
|
|
76
84
|
exposures_per_event=pedestal_frames,
|
|
77
85
|
trigger=DetectorTrigger.INTERNAL,
|
|
78
86
|
livetime=exposure_time_s,
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
from ._block import (
|
|
2
2
|
CommonPandaBlocks,
|
|
3
3
|
DataBlock,
|
|
4
|
+
InencBlock,
|
|
4
5
|
PandaBitMux,
|
|
5
6
|
PandaCaptureMode,
|
|
6
7
|
PandaPcompDirection,
|
|
8
|
+
PandaPosMux,
|
|
7
9
|
PandaTimeUnits,
|
|
8
10
|
PcapBlock,
|
|
9
11
|
PcompBlock,
|
|
@@ -20,6 +22,9 @@ from ._table import (
|
|
|
20
22
|
)
|
|
21
23
|
from ._trigger import (
|
|
22
24
|
PcompInfo,
|
|
25
|
+
PosOutScaleOffset,
|
|
26
|
+
ScanSpecInfo,
|
|
27
|
+
ScanSpecSeqTableTriggerLogic,
|
|
23
28
|
SeqTableInfo,
|
|
24
29
|
StaticPcompTriggerLogic,
|
|
25
30
|
StaticSeqTableTriggerLogic,
|
|
@@ -29,11 +34,13 @@ from ._writer import PandaHDFWriter
|
|
|
29
34
|
__all__ = [
|
|
30
35
|
"CommonPandaBlocks",
|
|
31
36
|
"DataBlock",
|
|
37
|
+
"InencBlock",
|
|
32
38
|
"PandaBitMux",
|
|
33
39
|
"PandaCaptureMode",
|
|
34
40
|
"PcapBlock",
|
|
35
41
|
"PcompBlock",
|
|
36
42
|
"PandaPcompDirection",
|
|
43
|
+
"PandaPosMux",
|
|
37
44
|
"PulseBlock",
|
|
38
45
|
"SeqBlock",
|
|
39
46
|
"PandaTimeUnits",
|
|
@@ -48,4 +55,7 @@ __all__ = [
|
|
|
48
55
|
"SeqTableInfo",
|
|
49
56
|
"StaticPcompTriggerLogic",
|
|
50
57
|
"StaticSeqTableTriggerLogic",
|
|
58
|
+
"ScanSpecInfo",
|
|
59
|
+
"ScanSpecSeqTableTriggerLogic",
|
|
60
|
+
"PosOutScaleOffset",
|
|
51
61
|
]
|
|
@@ -58,6 +58,12 @@ class PandaBitMux(SubsetEnum):
|
|
|
58
58
|
ONE = "ONE"
|
|
59
59
|
|
|
60
60
|
|
|
61
|
+
class PandaPosMux(SubsetEnum):
|
|
62
|
+
"""Pos input in the PandA."""
|
|
63
|
+
|
|
64
|
+
ZERO = "ZERO"
|
|
65
|
+
|
|
66
|
+
|
|
61
67
|
class PcompBlock(Device):
|
|
62
68
|
"""Position compare block in the PandA."""
|
|
63
69
|
|
|
@@ -88,6 +94,7 @@ class SeqBlock(Device):
|
|
|
88
94
|
prescale: SignalRW[float]
|
|
89
95
|
prescale_units: SignalRW[PandaTimeUnits]
|
|
90
96
|
enable: SignalRW[PandaBitMux]
|
|
97
|
+
posa: SignalRW[PandaPosMux]
|
|
91
98
|
|
|
92
99
|
|
|
93
100
|
class PcapBlock(Device):
|
|
@@ -97,6 +104,13 @@ class PcapBlock(Device):
|
|
|
97
104
|
arm: SignalRW[bool]
|
|
98
105
|
|
|
99
106
|
|
|
107
|
+
class InencBlock(Device):
|
|
108
|
+
"""In encoder block in the PandA."""
|
|
109
|
+
|
|
110
|
+
val_scale: SignalRW[float]
|
|
111
|
+
val_offset: SignalRW[float]
|
|
112
|
+
|
|
113
|
+
|
|
100
114
|
class CommonPandaBlocks(Device):
|
|
101
115
|
"""Pandablocks device with blocks which are common and required on introspection."""
|
|
102
116
|
|
|
@@ -1,17 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import asyncio
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import cast
|
|
2
6
|
|
|
7
|
+
import numpy as np
|
|
3
8
|
from pydantic import Field
|
|
4
|
-
|
|
5
|
-
from
|
|
9
|
+
from scanspec.core import Path
|
|
10
|
+
from scanspec.specs import Spec
|
|
11
|
+
|
|
12
|
+
from ophyd_async.core import (
|
|
13
|
+
ConfinedModel,
|
|
14
|
+
FlyerController,
|
|
15
|
+
SignalRW,
|
|
16
|
+
error_if_none,
|
|
17
|
+
wait_for_value,
|
|
18
|
+
)
|
|
19
|
+
from ophyd_async.epics.motor import Motor
|
|
6
20
|
|
|
7
21
|
from ._block import (
|
|
22
|
+
CommonPandaBlocks,
|
|
8
23
|
PandaBitMux,
|
|
9
24
|
PandaPcompDirection,
|
|
25
|
+
PandaPosMux,
|
|
10
26
|
PandaTimeUnits,
|
|
11
27
|
PcompBlock,
|
|
12
28
|
SeqBlock,
|
|
13
29
|
)
|
|
14
|
-
from ._table import SeqTable
|
|
30
|
+
from ._table import SeqTable, SeqTrigger
|
|
15
31
|
|
|
16
32
|
|
|
17
33
|
class SeqTableInfo(ConfinedModel):
|
|
@@ -22,6 +38,11 @@ class SeqTableInfo(ConfinedModel):
|
|
|
22
38
|
prescale_as_us: float = Field(default=1, ge=0) # microseconds
|
|
23
39
|
|
|
24
40
|
|
|
41
|
+
class ScanSpecInfo(ConfinedModel):
|
|
42
|
+
spec: Spec[Motor]
|
|
43
|
+
deadtime: float
|
|
44
|
+
|
|
45
|
+
|
|
25
46
|
class StaticSeqTableTriggerLogic(FlyerController[SeqTableInfo]):
|
|
26
47
|
"""For controlling the PandA `SeqTable` when fly scanning."""
|
|
27
48
|
|
|
@@ -51,6 +72,105 @@ class StaticSeqTableTriggerLogic(FlyerController[SeqTableInfo]):
|
|
|
51
72
|
await wait_for_value(self.seq.active, False, timeout=1)
|
|
52
73
|
|
|
53
74
|
|
|
75
|
+
@dataclass
|
|
76
|
+
class PosOutScaleOffset:
|
|
77
|
+
name: str
|
|
78
|
+
scale: SignalRW[float]
|
|
79
|
+
offset: SignalRW[float]
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def from_inenc(cls, panda: CommonPandaBlocks, number: int) -> PosOutScaleOffset:
|
|
83
|
+
inenc = panda.inenc[number] # type: ignore
|
|
84
|
+
return cls(
|
|
85
|
+
name=f"INENC{number}.VAL",
|
|
86
|
+
scale=inenc.val_scale, # type: ignore
|
|
87
|
+
offset=inenc.val_offset, # type: ignore
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class ScanSpecSeqTableTriggerLogic(FlyerController[ScanSpecInfo]):
|
|
92
|
+
def __init__(
|
|
93
|
+
self,
|
|
94
|
+
seq: SeqBlock,
|
|
95
|
+
motor_pos_outs: dict[Motor, PosOutScaleOffset] | None = None,
|
|
96
|
+
) -> None:
|
|
97
|
+
self.seq = seq
|
|
98
|
+
self.motor_pos_outs = motor_pos_outs or {}
|
|
99
|
+
|
|
100
|
+
async def prepare(self, value: ScanSpecInfo):
|
|
101
|
+
await self.seq.enable.set(PandaBitMux.ZERO)
|
|
102
|
+
slice = Path(value.spec.calculate()).consume()
|
|
103
|
+
slice_duration = error_if_none(slice.duration, "Slice must have duration")
|
|
104
|
+
|
|
105
|
+
# Start of window is where the is a gap to the previous point
|
|
106
|
+
window_start = np.nonzero(slice.gap)[0]
|
|
107
|
+
# End of window is either the next gap, or the end of the scan
|
|
108
|
+
window_end = np.append(window_start[1:], len(slice))
|
|
109
|
+
fast_axis = slice.axes()[-1]
|
|
110
|
+
pos_out = self.motor_pos_outs.get(fast_axis)
|
|
111
|
+
# If we have a motor to compare against, get its scale and offset
|
|
112
|
+
# otherwise don't connect POSA to anything
|
|
113
|
+
if pos_out is not None:
|
|
114
|
+
scale, offset = await asyncio.gather(
|
|
115
|
+
pos_out.scale.get_value(),
|
|
116
|
+
pos_out.offset.get_value(),
|
|
117
|
+
)
|
|
118
|
+
compare_pos_name = cast(PandaPosMux, pos_out.name)
|
|
119
|
+
else:
|
|
120
|
+
scale, offset = 1, 0
|
|
121
|
+
compare_pos_name = PandaPosMux.ZERO
|
|
122
|
+
|
|
123
|
+
rows = SeqTable.empty()
|
|
124
|
+
for start, end in zip(window_start, window_end, strict=True):
|
|
125
|
+
# GPIO goes low then high
|
|
126
|
+
rows += SeqTable.row(trigger=SeqTrigger.BITA_0)
|
|
127
|
+
rows += SeqTable.row(trigger=SeqTrigger.BITA_1)
|
|
128
|
+
# Wait for position if we are comparing against a motor
|
|
129
|
+
if pos_out is not None:
|
|
130
|
+
lower = (slice.lower[fast_axis][start] - offset) / scale
|
|
131
|
+
midpoint = (slice.midpoints[fast_axis][start] - offset) / scale
|
|
132
|
+
if midpoint > lower:
|
|
133
|
+
trigger = SeqTrigger.POSA_GT
|
|
134
|
+
elif midpoint < lower:
|
|
135
|
+
trigger = SeqTrigger.POSA_LT
|
|
136
|
+
else:
|
|
137
|
+
trigger = None
|
|
138
|
+
if trigger is not None:
|
|
139
|
+
rows += SeqTable.row(
|
|
140
|
+
trigger=trigger,
|
|
141
|
+
position=int(lower),
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Time based Triggers
|
|
145
|
+
rows += SeqTable.row(
|
|
146
|
+
repeats=end - start,
|
|
147
|
+
trigger=SeqTrigger.IMMEDIATE,
|
|
148
|
+
time1=int((slice_duration[0] - value.deadtime) * 10**6),
|
|
149
|
+
time2=int(value.deadtime * 10**6),
|
|
150
|
+
outa1=True,
|
|
151
|
+
outa2=False,
|
|
152
|
+
)
|
|
153
|
+
# Need to do units before value for PandA, otherwise it scales the current value
|
|
154
|
+
await self.seq.prescale_units.set(PandaTimeUnits.US)
|
|
155
|
+
await asyncio.gather(
|
|
156
|
+
self.seq.posa.set(compare_pos_name),
|
|
157
|
+
self.seq.prescale.set(1.0),
|
|
158
|
+
self.seq.repeats.set(1),
|
|
159
|
+
self.seq.table.set(rows),
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
async def kickoff(self) -> None:
|
|
163
|
+
await self.seq.enable.set(PandaBitMux.ONE)
|
|
164
|
+
await wait_for_value(self.seq.active, True, timeout=1)
|
|
165
|
+
|
|
166
|
+
async def complete(self) -> None:
|
|
167
|
+
await wait_for_value(self.seq.active, False, timeout=None)
|
|
168
|
+
|
|
169
|
+
async def stop(self):
|
|
170
|
+
await self.seq.enable.set(PandaBitMux.ZERO)
|
|
171
|
+
await wait_for_value(self.seq.active, False, timeout=1)
|
|
172
|
+
|
|
173
|
+
|
|
54
174
|
class PcompInfo(ConfinedModel):
|
|
55
175
|
"""Info for the PandA `PcompBlock` for fly scanning."""
|
|
56
176
|
|
ophyd_async/sim/_motor.py
CHANGED
|
@@ -82,8 +82,10 @@ class SimMotor(StandardReadable, Stoppable, Subscribable[float], Locatable[float
|
|
|
82
82
|
)
|
|
83
83
|
return Location(setpoint=setpoint, readback=readback)
|
|
84
84
|
|
|
85
|
-
def
|
|
86
|
-
self.user_readback.
|
|
85
|
+
def subscribe_reading(self, function: Callback[dict[str, Reading[float]]]) -> None:
|
|
86
|
+
self.user_readback.subscribe_reading(function)
|
|
87
|
+
|
|
88
|
+
subscribe = subscribe_reading
|
|
87
89
|
|
|
88
90
|
def clear_sub(self, function: Callback[dict[str, Reading[float]]]) -> None:
|
|
89
91
|
self.user_readback.clear_sub(function)
|
ophyd_async/sim/_stage.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from bluesky.protocols import Reading
|
|
2
|
+
|
|
1
3
|
from ophyd_async.core import StandardReadable
|
|
2
4
|
from ophyd_async.sim._pattern_generator import PatternGenerator
|
|
3
5
|
|
|
@@ -16,15 +18,23 @@ class SimStage(StandardReadable):
|
|
|
16
18
|
# Set name of device and child devices
|
|
17
19
|
super().__init__(name=name)
|
|
18
20
|
|
|
21
|
+
def _set_x_from_reading(self, readings: dict[str, Reading[float]]):
|
|
22
|
+
(x_reading,) = readings.values()
|
|
23
|
+
self.pattern_generator.set_x(x_reading["value"])
|
|
24
|
+
|
|
25
|
+
def _set_y_from_reading(self, readings: dict[str, Reading[float]]):
|
|
26
|
+
(y_reading,) = readings.values()
|
|
27
|
+
self.pattern_generator.set_y(y_reading["value"])
|
|
28
|
+
|
|
19
29
|
def stage(self):
|
|
20
30
|
"""Stage the motors and report the position to the pattern generator."""
|
|
21
31
|
# Tell the pattern generator about the motor positions
|
|
22
|
-
self.x.user_readback.
|
|
23
|
-
self.y.user_readback.
|
|
32
|
+
self.x.user_readback.subscribe_reading(self._set_x_from_reading)
|
|
33
|
+
self.y.user_readback.subscribe_reading(self._set_y_from_reading)
|
|
24
34
|
return super().stage()
|
|
25
35
|
|
|
26
36
|
def unstage(self):
|
|
27
37
|
"""Unstage the motors and remove the position subscription."""
|
|
28
|
-
self.x.user_readback.clear_sub(self.
|
|
29
|
-
self.y.user_readback.clear_sub(self.
|
|
38
|
+
self.x.user_readback.clear_sub(self._set_x_from_reading)
|
|
39
|
+
self.y.user_readback.clear_sub(self._set_y_from_reading)
|
|
30
40
|
return super().unstage()
|
|
@@ -12,24 +12,35 @@ from ._tango_readable import TangoReadable
|
|
|
12
12
|
from ._tango_transport import (
|
|
13
13
|
AttributeProxy,
|
|
14
14
|
CommandProxy,
|
|
15
|
+
CommandProxyReadCharacter,
|
|
16
|
+
TangoDoubleStringTable,
|
|
17
|
+
TangoLongStringTable,
|
|
15
18
|
TangoSignalBackend,
|
|
16
19
|
ensure_proper_executor,
|
|
20
|
+
get_command_character,
|
|
17
21
|
get_dtype_extended,
|
|
18
22
|
get_python_type,
|
|
23
|
+
get_source_metadata,
|
|
19
24
|
get_tango_trl,
|
|
20
|
-
get_trl_descriptor,
|
|
21
25
|
)
|
|
22
|
-
from ._utils import
|
|
26
|
+
from ._utils import (
|
|
27
|
+
DevStateEnum,
|
|
28
|
+
get_device_trl_and_attr,
|
|
29
|
+
get_full_attr_trl,
|
|
30
|
+
try_to_cast_as_float,
|
|
31
|
+
)
|
|
23
32
|
|
|
24
33
|
__all__ = [
|
|
25
34
|
"AttributeProxy",
|
|
26
35
|
"CommandProxy",
|
|
36
|
+
"CommandProxyReadCharacter",
|
|
27
37
|
"DevStateEnum",
|
|
28
38
|
"ensure_proper_executor",
|
|
29
39
|
"TangoSignalBackend",
|
|
40
|
+
"get_command_character",
|
|
30
41
|
"get_python_type",
|
|
31
42
|
"get_dtype_extended",
|
|
32
|
-
"
|
|
43
|
+
"get_source_metadata",
|
|
33
44
|
"get_tango_trl",
|
|
34
45
|
"infer_python_type",
|
|
35
46
|
"infer_signal_type",
|
|
@@ -42,6 +53,9 @@ __all__ = [
|
|
|
42
53
|
"TangoReadable",
|
|
43
54
|
"TangoPolling",
|
|
44
55
|
"TangoDeviceConnector",
|
|
56
|
+
"TangoLongStringTable",
|
|
57
|
+
"TangoDoubleStringTable",
|
|
58
|
+
"try_to_cast_as_float",
|
|
45
59
|
"get_device_trl_and_attr",
|
|
46
60
|
"get_full_attr_trl",
|
|
47
61
|
]
|
|
@@ -3,13 +3,11 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
|
-
from enum import
|
|
6
|
+
from enum import IntEnum
|
|
7
7
|
|
|
8
8
|
import numpy.typing as npt
|
|
9
9
|
from tango import (
|
|
10
|
-
AttrDataFormat,
|
|
11
10
|
AttrWriteType,
|
|
12
|
-
CmdArgType,
|
|
13
11
|
DeviceProxy,
|
|
14
12
|
DevState,
|
|
15
13
|
)
|
|
@@ -25,7 +23,12 @@ from ophyd_async.core import (
|
|
|
25
23
|
SignalX,
|
|
26
24
|
)
|
|
27
25
|
|
|
28
|
-
from ._tango_transport import
|
|
26
|
+
from ._tango_transport import (
|
|
27
|
+
CommandProxyReadCharacter,
|
|
28
|
+
TangoSignalBackend,
|
|
29
|
+
get_command_character,
|
|
30
|
+
get_python_type,
|
|
31
|
+
)
|
|
29
32
|
from ._utils import get_device_trl_and_attr
|
|
30
33
|
|
|
31
34
|
logger = logging.getLogger("ophyd_async")
|
|
@@ -148,22 +151,13 @@ async def infer_python_type(
|
|
|
148
151
|
|
|
149
152
|
if tr_name in dev_proxy.get_command_list():
|
|
150
153
|
config = await dev_proxy.get_command_config(tr_name)
|
|
151
|
-
|
|
154
|
+
py_type = get_python_type(config)
|
|
152
155
|
elif tr_name in dev_proxy.get_attribute_list():
|
|
153
156
|
config = await dev_proxy.get_attribute_config(tr_name)
|
|
154
|
-
|
|
155
|
-
if py_type is Enum:
|
|
156
|
-
enum_dict = {label: i for i, label in enumerate(config.enum_labels)}
|
|
157
|
-
py_type = IntEnum("TangoEnum", enum_dict)
|
|
158
|
-
if config.data_format in [AttrDataFormat.SPECTRUM, AttrDataFormat.IMAGE]:
|
|
159
|
-
isarray = True
|
|
157
|
+
py_type = get_python_type(config)
|
|
160
158
|
else:
|
|
161
159
|
raise RuntimeError(f"Cannot find {tr_name} in {device_trl}")
|
|
162
|
-
|
|
163
|
-
if py_type is CmdArgType.DevState:
|
|
164
|
-
py_type = DevState
|
|
165
|
-
|
|
166
|
-
return npt.NDArray[py_type] if isarray else py_type
|
|
160
|
+
return py_type
|
|
167
161
|
|
|
168
162
|
|
|
169
163
|
async def infer_signal_type(
|
|
@@ -190,11 +184,13 @@ async def infer_signal_type(
|
|
|
190
184
|
|
|
191
185
|
if tr_name in dev_proxy.get_command_list():
|
|
192
186
|
config = await dev_proxy.get_command_config(tr_name)
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
return
|
|
198
|
-
|
|
187
|
+
command_character = get_command_character(config)
|
|
188
|
+
if command_character == CommandProxyReadCharacter.READ:
|
|
189
|
+
return SignalR
|
|
190
|
+
elif command_character == CommandProxyReadCharacter.WRITE:
|
|
191
|
+
return SignalW
|
|
192
|
+
elif command_character == CommandProxyReadCharacter.READ_WRITE:
|
|
199
193
|
return SignalRW
|
|
194
|
+
elif command_character == CommandProxyReadCharacter.EXECUTE:
|
|
195
|
+
return SignalX
|
|
200
196
|
raise RuntimeError(f"Unable to infer signal character for {trl}")
|