ophyd-async 0.11__py3-none-any.whl → 0.12__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/_flyer.py +4 -4
- ophyd_async/core/_signal.py +15 -2
- ophyd_async/epics/core/_signal.py +8 -3
- ophyd_async/epics/motor.py +2 -2
- ophyd_async/fastcs/panda/_trigger.py +4 -4
- ophyd_async/plan_stubs/__init__.py +0 -8
- ophyd_async/plan_stubs/_fly.py +0 -204
- ophyd_async/testing/__init__.py +2 -0
- ophyd_async/testing/_assert.py +34 -6
- {ophyd_async-0.11.dist-info → ophyd_async-0.12.dist-info}/METADATA +3 -2
- {ophyd_async-0.11.dist-info → ophyd_async-0.12.dist-info}/RECORD +15 -15
- {ophyd_async-0.11.dist-info → ophyd_async-0.12.dist-info}/WHEEL +0 -0
- {ophyd_async-0.11.dist-info → ophyd_async-0.12.dist-info}/licenses/LICENSE +0 -0
- {ophyd_async-0.11.dist-info → ophyd_async-0.12.dist-info}/top_level.txt +0 -0
ophyd_async/_version.py
CHANGED
ophyd_async/core/_flyer.py
CHANGED
|
@@ -17,15 +17,15 @@ class FlyerController(ABC, Generic[T]):
|
|
|
17
17
|
|
|
18
18
|
@abstractmethod
|
|
19
19
|
async def prepare(self, value: T) -> Any:
|
|
20
|
-
"""Move to the start of the
|
|
20
|
+
"""Move to the start of the fly scan."""
|
|
21
21
|
|
|
22
22
|
@abstractmethod
|
|
23
23
|
async def kickoff(self):
|
|
24
|
-
"""Start the
|
|
24
|
+
"""Start the fly scan."""
|
|
25
25
|
|
|
26
26
|
@abstractmethod
|
|
27
27
|
async def complete(self):
|
|
28
|
-
"""Block until the
|
|
28
|
+
"""Block until the fly scan is done."""
|
|
29
29
|
|
|
30
30
|
@abstractmethod
|
|
31
31
|
async def stop(self):
|
|
@@ -101,7 +101,7 @@ class StandardFlyer(
|
|
|
101
101
|
return AsyncStatus(self._prepare(value))
|
|
102
102
|
|
|
103
103
|
async def _prepare(self, value: T) -> None:
|
|
104
|
-
# Move to start and setup the
|
|
104
|
+
# Move to start and setup the fly scan
|
|
105
105
|
await self._trigger_logic.prepare(value)
|
|
106
106
|
|
|
107
107
|
@AsyncStatus.wrap
|
ophyd_async/core/_signal.py
CHANGED
|
@@ -17,6 +17,7 @@ from bluesky.protocols import (
|
|
|
17
17
|
Subscribable,
|
|
18
18
|
)
|
|
19
19
|
from event_model import DataKey
|
|
20
|
+
from stamina import retry_context
|
|
20
21
|
|
|
21
22
|
from ._device import Device, DeviceConnector
|
|
22
23
|
from ._mock_signal_backend import MockSignalBackend
|
|
@@ -89,9 +90,11 @@ class Signal(Device, Generic[SignalDatatypeT]):
|
|
|
89
90
|
backend: SignalBackend[SignalDatatypeT],
|
|
90
91
|
timeout: float | None = DEFAULT_TIMEOUT,
|
|
91
92
|
name: str = "",
|
|
93
|
+
attempts: int = 1,
|
|
92
94
|
) -> None:
|
|
93
95
|
super().__init__(name=name, connector=SignalConnector(backend))
|
|
94
96
|
self._timeout = timeout
|
|
97
|
+
self._attempts = attempts
|
|
95
98
|
|
|
96
99
|
@property
|
|
97
100
|
def source(self) -> str:
|
|
@@ -144,7 +147,8 @@ class _SignalCache(Generic[SignalDatatypeT]):
|
|
|
144
147
|
)
|
|
145
148
|
self._reading = reading
|
|
146
149
|
self._valid.set()
|
|
147
|
-
|
|
150
|
+
items = self._listeners.copy().items()
|
|
151
|
+
for function, want_value in items:
|
|
148
152
|
self._notify(function, want_value)
|
|
149
153
|
|
|
150
154
|
def _notify(
|
|
@@ -287,7 +291,16 @@ class SignalW(Signal[SignalDatatypeT], Movable):
|
|
|
287
291
|
timeout = self._timeout
|
|
288
292
|
source = self._connector.backend.source(self.name, read=False)
|
|
289
293
|
self.log.debug(f"Putting value {value} to backend at source {source}")
|
|
290
|
-
|
|
294
|
+
async for attempt in retry_context(
|
|
295
|
+
on=asyncio.TimeoutError,
|
|
296
|
+
attempts=self._attempts,
|
|
297
|
+
wait_initial=0,
|
|
298
|
+
wait_jitter=0,
|
|
299
|
+
):
|
|
300
|
+
with attempt:
|
|
301
|
+
await _wait_for(
|
|
302
|
+
self._connector.backend.put(value, wait=wait), timeout, source
|
|
303
|
+
)
|
|
291
304
|
self.log.debug(f"Successfully put value {value} to backend at source {source}")
|
|
292
305
|
|
|
293
306
|
|
|
@@ -94,6 +94,7 @@ def epics_signal_rw(
|
|
|
94
94
|
write_pv: str | None = None,
|
|
95
95
|
name: str = "",
|
|
96
96
|
timeout: float = DEFAULT_TIMEOUT,
|
|
97
|
+
attempts: int = 1,
|
|
97
98
|
) -> SignalRW[SignalDatatypeT]:
|
|
98
99
|
"""Create a `SignalRW` backed by 1 or 2 EPICS PVs.
|
|
99
100
|
|
|
@@ -104,7 +105,7 @@ def epics_signal_rw(
|
|
|
104
105
|
:param timeout: A timeout to be used when reading (not connecting) this signal
|
|
105
106
|
"""
|
|
106
107
|
backend = _epics_signal_backend(datatype, read_pv, write_pv or read_pv)
|
|
107
|
-
return SignalRW(backend, name=name, timeout=timeout)
|
|
108
|
+
return SignalRW(backend, name=name, timeout=timeout, attempts=attempts)
|
|
108
109
|
|
|
109
110
|
|
|
110
111
|
def epics_signal_rw_rbv(
|
|
@@ -113,6 +114,7 @@ def epics_signal_rw_rbv(
|
|
|
113
114
|
read_suffix: str = "_RBV",
|
|
114
115
|
name: str = "",
|
|
115
116
|
timeout: float = DEFAULT_TIMEOUT,
|
|
117
|
+
attempts: int = 1,
|
|
116
118
|
) -> SignalRW[SignalDatatypeT]:
|
|
117
119
|
"""Create a `SignalRW` backed by 1 or 2 EPICS PVs, with a suffix on the readback pv.
|
|
118
120
|
|
|
@@ -128,7 +130,9 @@ def epics_signal_rw_rbv(
|
|
|
128
130
|
else:
|
|
129
131
|
read_pv = f"{write_pv}{read_suffix}"
|
|
130
132
|
|
|
131
|
-
return epics_signal_rw(
|
|
133
|
+
return epics_signal_rw(
|
|
134
|
+
datatype, read_pv, write_pv, name, timeout=timeout, attempts=attempts
|
|
135
|
+
)
|
|
132
136
|
|
|
133
137
|
|
|
134
138
|
def epics_signal_r(
|
|
@@ -153,6 +157,7 @@ def epics_signal_w(
|
|
|
153
157
|
write_pv: str,
|
|
154
158
|
name: str = "",
|
|
155
159
|
timeout: float = DEFAULT_TIMEOUT,
|
|
160
|
+
attempts: int = 1,
|
|
156
161
|
) -> SignalW[SignalDatatypeT]:
|
|
157
162
|
"""Create a `SignalW` backed by 1 EPICS PVs.
|
|
158
163
|
|
|
@@ -162,7 +167,7 @@ def epics_signal_w(
|
|
|
162
167
|
:param timeout: A timeout to be used when reading (not connecting) this signal
|
|
163
168
|
"""
|
|
164
169
|
backend = _epics_signal_backend(datatype, write_pv, write_pv)
|
|
165
|
-
return SignalW(backend, name=name, timeout=timeout)
|
|
170
|
+
return SignalW(backend, name=name, timeout=timeout, attempts=attempts)
|
|
166
171
|
|
|
167
172
|
|
|
168
173
|
def epics_signal_x(
|
ophyd_async/epics/motor.py
CHANGED
|
@@ -108,7 +108,7 @@ class Motor(
|
|
|
108
108
|
|
|
109
109
|
@AsyncStatus.wrap
|
|
110
110
|
async def prepare(self, value: FlyMotorInfo):
|
|
111
|
-
"""Move to the beginning of a suitable run-up distance ready for a
|
|
111
|
+
"""Move to the beginning of a suitable run-up distance ready for a fly scan."""
|
|
112
112
|
self._fly_info = value
|
|
113
113
|
|
|
114
114
|
# Velocity, at which motor travels from start_position to end_position, in motor
|
|
@@ -147,7 +147,7 @@ class Motor(
|
|
|
147
147
|
await self.velocity.set(abs(max_speed))
|
|
148
148
|
await self.set(ramp_up_start_pos)
|
|
149
149
|
|
|
150
|
-
# Set velocity we will be using for the
|
|
150
|
+
# Set velocity we will be using for the fly scan
|
|
151
151
|
await self.velocity.set(abs(value.velocity))
|
|
152
152
|
|
|
153
153
|
@AsyncStatus.wrap
|
|
@@ -15,7 +15,7 @@ from ._table import SeqTable
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class SeqTableInfo(ConfinedModel):
|
|
18
|
-
"""Info for the PandA `SeqTable` for
|
|
18
|
+
"""Info for the PandA `SeqTable` for fly scanning."""
|
|
19
19
|
|
|
20
20
|
sequence_table: SeqTable = Field(strict=True)
|
|
21
21
|
repeats: int = Field(ge=0)
|
|
@@ -23,7 +23,7 @@ class SeqTableInfo(ConfinedModel):
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class StaticSeqTableTriggerLogic(FlyerController[SeqTableInfo]):
|
|
26
|
-
"""For controlling the PandA `SeqTable` when
|
|
26
|
+
"""For controlling the PandA `SeqTable` when fly scanning."""
|
|
27
27
|
|
|
28
28
|
def __init__(self, seq: SeqBlock) -> None:
|
|
29
29
|
self.seq = seq
|
|
@@ -52,7 +52,7 @@ class StaticSeqTableTriggerLogic(FlyerController[SeqTableInfo]):
|
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
class PcompInfo(ConfinedModel):
|
|
55
|
-
"""Info for the PandA `PcompBlock` for
|
|
55
|
+
"""Info for the PandA `PcompBlock` for fly scanning."""
|
|
56
56
|
|
|
57
57
|
start_postion: int = Field(description="start position in counts")
|
|
58
58
|
pulse_width: int = Field(description="width of a single pulse in counts", gt=0)
|
|
@@ -76,7 +76,7 @@ class PcompInfo(ConfinedModel):
|
|
|
76
76
|
|
|
77
77
|
|
|
78
78
|
class StaticPcompTriggerLogic(FlyerController[PcompInfo]):
|
|
79
|
-
"""For controlling the PandA `PcompBlock` when
|
|
79
|
+
"""For controlling the PandA `PcompBlock` when fly scanning."""
|
|
80
80
|
|
|
81
81
|
def __init__(self, pcomp: PcompBlock) -> None:
|
|
82
82
|
self.pcomp = pcomp
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
"""Plan stubs for connecting, setting up and flying devices."""
|
|
2
2
|
|
|
3
3
|
from ._ensure_connected import ensure_connected
|
|
4
|
-
from ._fly import (
|
|
5
|
-
fly_and_collect,
|
|
6
|
-
prepare_static_seq_table_flyer_and_detectors_with_same_trigger,
|
|
7
|
-
time_resolved_fly_and_collect_with_static_seq_table,
|
|
8
|
-
)
|
|
9
4
|
from ._nd_attributes import setup_ndattributes, setup_ndstats_sum
|
|
10
5
|
from ._panda import apply_panda_settings
|
|
11
6
|
from ._settings import (
|
|
@@ -17,9 +12,6 @@ from ._settings import (
|
|
|
17
12
|
)
|
|
18
13
|
|
|
19
14
|
__all__ = [
|
|
20
|
-
"fly_and_collect",
|
|
21
|
-
"prepare_static_seq_table_flyer_and_detectors_with_same_trigger",
|
|
22
|
-
"time_resolved_fly_and_collect_with_static_seq_table",
|
|
23
15
|
"ensure_connected",
|
|
24
16
|
"setup_ndattributes",
|
|
25
17
|
"setup_ndstats_sum",
|
ophyd_async/plan_stubs/_fly.py
CHANGED
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
import bluesky.plan_stubs as bps
|
|
2
|
-
from bluesky.utils import short_uid
|
|
3
|
-
|
|
4
|
-
from ophyd_async.core import (
|
|
5
|
-
DetectorTrigger,
|
|
6
|
-
StandardDetector,
|
|
7
|
-
StandardFlyer,
|
|
8
|
-
TriggerInfo,
|
|
9
|
-
in_micros,
|
|
10
|
-
)
|
|
11
|
-
from ophyd_async.fastcs.panda import (
|
|
12
|
-
PandaPcompDirection,
|
|
13
|
-
PcompInfo,
|
|
14
|
-
SeqTable,
|
|
15
|
-
SeqTableInfo,
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def prepare_static_pcomp_flyer_and_detectors(
|
|
20
|
-
flyer: StandardFlyer[PcompInfo],
|
|
21
|
-
detectors: list[StandardDetector],
|
|
22
|
-
pcomp_info: PcompInfo,
|
|
23
|
-
trigger_info: TriggerInfo,
|
|
24
|
-
):
|
|
25
|
-
"""Prepare a hardware triggered flyable and one or more detectors.
|
|
26
|
-
|
|
27
|
-
Prepare a hardware triggered flyable and one or more detectors with the
|
|
28
|
-
same trigger.
|
|
29
|
-
|
|
30
|
-
"""
|
|
31
|
-
for det in detectors:
|
|
32
|
-
yield from bps.prepare(det, trigger_info, wait=False, group="prep")
|
|
33
|
-
yield from bps.prepare(flyer, pcomp_info, wait=False, group="prep")
|
|
34
|
-
yield from bps.wait(group="prep")
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def prepare_static_seq_table_flyer_and_detectors_with_same_trigger(
|
|
38
|
-
flyer: StandardFlyer[SeqTableInfo],
|
|
39
|
-
detectors: list[StandardDetector],
|
|
40
|
-
number_of_frames: int,
|
|
41
|
-
exposure: float,
|
|
42
|
-
shutter_time: float,
|
|
43
|
-
repeats: int = 1,
|
|
44
|
-
period: float = 0.0,
|
|
45
|
-
frame_timeout: float | None = None,
|
|
46
|
-
):
|
|
47
|
-
"""Prepare a hardware triggered flyable and one or more detectors.
|
|
48
|
-
|
|
49
|
-
Prepare a hardware triggered flyable and one or more detectors with the
|
|
50
|
-
same trigger. This method constructs TriggerInfo and a static sequence
|
|
51
|
-
table from required parameters. The table is required to prepare the flyer,
|
|
52
|
-
and the TriggerInfo is required to prepare the detector(s).
|
|
53
|
-
|
|
54
|
-
This prepares all supplied detectors with the same trigger.
|
|
55
|
-
|
|
56
|
-
"""
|
|
57
|
-
if not detectors:
|
|
58
|
-
raise ValueError("No detectors provided. There must be at least one.")
|
|
59
|
-
|
|
60
|
-
deadtime = max(det._controller.get_deadtime(exposure) for det in detectors) # noqa: SLF001
|
|
61
|
-
|
|
62
|
-
trigger_info = TriggerInfo(
|
|
63
|
-
number_of_events=number_of_frames * repeats,
|
|
64
|
-
trigger=DetectorTrigger.CONSTANT_GATE,
|
|
65
|
-
deadtime=deadtime,
|
|
66
|
-
livetime=exposure,
|
|
67
|
-
exposure_timeout=frame_timeout,
|
|
68
|
-
)
|
|
69
|
-
trigger_time = number_of_frames * (exposure + deadtime)
|
|
70
|
-
pre_delay = max(period - 2 * shutter_time - trigger_time, 0)
|
|
71
|
-
|
|
72
|
-
table = (
|
|
73
|
-
# Wait for pre-delay then open shutter
|
|
74
|
-
SeqTable.row(
|
|
75
|
-
time1=in_micros(pre_delay),
|
|
76
|
-
time2=in_micros(shutter_time),
|
|
77
|
-
outa2=True,
|
|
78
|
-
)
|
|
79
|
-
+
|
|
80
|
-
# Keeping shutter open, do N triggers
|
|
81
|
-
SeqTable.row(
|
|
82
|
-
repeats=number_of_frames,
|
|
83
|
-
time1=in_micros(exposure),
|
|
84
|
-
outa1=True,
|
|
85
|
-
outb1=True,
|
|
86
|
-
time2=in_micros(deadtime),
|
|
87
|
-
outa2=True,
|
|
88
|
-
)
|
|
89
|
-
+
|
|
90
|
-
# Add the shutter close
|
|
91
|
-
SeqTable.row(time2=in_micros(shutter_time))
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
table_info = SeqTableInfo(sequence_table=table, repeats=repeats)
|
|
95
|
-
|
|
96
|
-
for det in detectors:
|
|
97
|
-
yield from bps.prepare(det, trigger_info, wait=False, group="prep")
|
|
98
|
-
yield from bps.prepare(flyer, table_info, wait=False, group="prep")
|
|
99
|
-
yield from bps.wait(group="prep")
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def fly_and_collect(
|
|
103
|
-
stream_name: str,
|
|
104
|
-
flyer: StandardFlyer[SeqTableInfo] | StandardFlyer[PcompInfo],
|
|
105
|
-
detectors: list[StandardDetector],
|
|
106
|
-
):
|
|
107
|
-
"""Kickoff, complete and collect with a flyer and multiple detectors.
|
|
108
|
-
|
|
109
|
-
This stub takes a flyer and one or more detectors that have been prepared. It
|
|
110
|
-
declares a stream for the detectors, then kicks off the detectors and the flyer.
|
|
111
|
-
The detectors are collected until the flyer and detectors have completed.
|
|
112
|
-
|
|
113
|
-
"""
|
|
114
|
-
yield from bps.declare_stream(*detectors, name=stream_name, collect=True)
|
|
115
|
-
yield from bps.kickoff(flyer, wait=True)
|
|
116
|
-
for detector in detectors:
|
|
117
|
-
yield from bps.kickoff(detector)
|
|
118
|
-
|
|
119
|
-
# collect_while_completing
|
|
120
|
-
group = short_uid(label="complete")
|
|
121
|
-
|
|
122
|
-
yield from bps.complete(flyer, wait=False, group=group)
|
|
123
|
-
for detector in detectors:
|
|
124
|
-
yield from bps.complete(detector, wait=False, group=group)
|
|
125
|
-
|
|
126
|
-
done = False
|
|
127
|
-
while not done:
|
|
128
|
-
try:
|
|
129
|
-
yield from bps.wait(group=group, timeout=0.5)
|
|
130
|
-
except TimeoutError:
|
|
131
|
-
pass
|
|
132
|
-
else:
|
|
133
|
-
done = True
|
|
134
|
-
yield from bps.collect(
|
|
135
|
-
*detectors,
|
|
136
|
-
return_payload=False,
|
|
137
|
-
name=stream_name,
|
|
138
|
-
)
|
|
139
|
-
yield from bps.wait(group=group)
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
def fly_and_collect_with_static_pcomp(
|
|
143
|
-
stream_name: str,
|
|
144
|
-
flyer: StandardFlyer[PcompInfo],
|
|
145
|
-
detectors: list[StandardDetector],
|
|
146
|
-
number_of_pulses: int,
|
|
147
|
-
pulse_width: int,
|
|
148
|
-
rising_edge_step: int,
|
|
149
|
-
direction: PandaPcompDirection,
|
|
150
|
-
trigger_info: TriggerInfo,
|
|
151
|
-
):
|
|
152
|
-
# Set up scan and prepare trigger
|
|
153
|
-
pcomp_info = PcompInfo(
|
|
154
|
-
start_postion=0,
|
|
155
|
-
pulse_width=pulse_width,
|
|
156
|
-
rising_edge_step=rising_edge_step,
|
|
157
|
-
number_of_pulses=number_of_pulses,
|
|
158
|
-
direction=direction,
|
|
159
|
-
)
|
|
160
|
-
yield from prepare_static_pcomp_flyer_and_detectors(
|
|
161
|
-
flyer, detectors, pcomp_info, trigger_info
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
# Run the fly scan
|
|
165
|
-
yield from fly_and_collect(stream_name, flyer, detectors)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
def time_resolved_fly_and_collect_with_static_seq_table(
|
|
169
|
-
stream_name: str,
|
|
170
|
-
flyer: StandardFlyer[SeqTableInfo],
|
|
171
|
-
detectors: list[StandardDetector],
|
|
172
|
-
number_of_frames: int,
|
|
173
|
-
exposure: float,
|
|
174
|
-
shutter_time: float,
|
|
175
|
-
repeats: int = 1,
|
|
176
|
-
period: float = 0.0,
|
|
177
|
-
frame_timeout: float | None = None,
|
|
178
|
-
):
|
|
179
|
-
"""Run a scan wth a flyer and multiple detectors.
|
|
180
|
-
|
|
181
|
-
The stub demonstrates the standard basic flow for a flyscan:
|
|
182
|
-
|
|
183
|
-
- Prepare the flyer and detectors with a trigger
|
|
184
|
-
- Fly and collect:
|
|
185
|
-
- Declare the stream and kickoff the scan
|
|
186
|
-
- Collect while completing
|
|
187
|
-
|
|
188
|
-
This needs to be used in a plan that instantates detectors and a flyer,
|
|
189
|
-
stages/unstages the devices, and opens and closes the run.
|
|
190
|
-
|
|
191
|
-
"""
|
|
192
|
-
# Set up scan and prepare trigger
|
|
193
|
-
yield from prepare_static_seq_table_flyer_and_detectors_with_same_trigger(
|
|
194
|
-
flyer,
|
|
195
|
-
detectors,
|
|
196
|
-
number_of_frames=number_of_frames,
|
|
197
|
-
exposure=exposure,
|
|
198
|
-
shutter_time=shutter_time,
|
|
199
|
-
repeats=repeats,
|
|
200
|
-
period=period,
|
|
201
|
-
frame_timeout=frame_timeout,
|
|
202
|
-
)
|
|
203
|
-
# Run the fly scan
|
|
204
|
-
yield from fly_and_collect(stream_name, flyer, detectors)
|
ophyd_async/testing/__init__.py
CHANGED
|
@@ -11,6 +11,7 @@ from ._assert import (
|
|
|
11
11
|
assert_emitted,
|
|
12
12
|
assert_reading,
|
|
13
13
|
assert_value,
|
|
14
|
+
partial_reading,
|
|
14
15
|
)
|
|
15
16
|
from ._mock_signal_utils import (
|
|
16
17
|
callback_on_mock_put,
|
|
@@ -47,6 +48,7 @@ __all__ = [
|
|
|
47
48
|
"assert_configuration",
|
|
48
49
|
"assert_describe_signal",
|
|
49
50
|
"assert_emitted",
|
|
51
|
+
"partial_reading",
|
|
50
52
|
# Mocking utilities
|
|
51
53
|
"get_mock",
|
|
52
54
|
"set_mock_value",
|
ophyd_async/testing/_assert.py
CHANGED
|
@@ -21,6 +21,15 @@ from ophyd_async.core import (
|
|
|
21
21
|
from ._utils import T
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
def partial_reading(val: Any) -> dict[str, Any]:
|
|
25
|
+
"""Helper function for building expected reading or configuration dicts.
|
|
26
|
+
|
|
27
|
+
:param val: Value to be wrapped in dict with "value" as the key.
|
|
28
|
+
:return: The dict that has wrapped the val with key "value".
|
|
29
|
+
"""
|
|
30
|
+
return {"value": val}
|
|
31
|
+
|
|
32
|
+
|
|
24
33
|
def approx_value(value: Any):
|
|
25
34
|
"""Allow any value to be compared to another in tests.
|
|
26
35
|
|
|
@@ -45,14 +54,18 @@ async def assert_value(signal: SignalR[SignalDatatypeT], value: Any) -> None:
|
|
|
45
54
|
async def assert_reading(
|
|
46
55
|
readable: AsyncReadable,
|
|
47
56
|
expected_reading: Mapping[str, Mapping[str, Any]],
|
|
57
|
+
full_match: bool = True,
|
|
48
58
|
) -> None:
|
|
49
59
|
"""Assert that a readable Device has the given reading.
|
|
50
60
|
|
|
51
61
|
:param readable: Device with an async ``read()`` method to get the reading from.
|
|
52
62
|
:param expected_reading: The expected reading from the readable.
|
|
63
|
+
:param full_match: if expected_reading keys set is same as actual keys set.
|
|
64
|
+
true: exact match
|
|
65
|
+
false: expected_reading keys is subset of actual reading keys
|
|
53
66
|
"""
|
|
54
67
|
actual_reading = await readable.read()
|
|
55
|
-
_assert_readings_approx_equal(expected_reading, actual_reading)
|
|
68
|
+
_assert_readings_approx_equal(expected_reading, actual_reading, full_match)
|
|
56
69
|
|
|
57
70
|
|
|
58
71
|
def _approx_reading(expected: Mapping[str, Any], actual: Reading) -> Reading:
|
|
@@ -69,16 +82,26 @@ def _approx_reading(expected: Mapping[str, Any], actual: Reading) -> Reading:
|
|
|
69
82
|
|
|
70
83
|
|
|
71
84
|
def _assert_readings_approx_equal(
|
|
72
|
-
expected: Mapping[str, Mapping[str, Any]],
|
|
85
|
+
expected: Mapping[str, Mapping[str, Any]],
|
|
86
|
+
actual: Mapping[str, Reading],
|
|
87
|
+
full_match: bool = True,
|
|
73
88
|
):
|
|
74
|
-
|
|
75
|
-
|
|
89
|
+
# expand the expected keys to include actual if we allow partial matches
|
|
90
|
+
if not full_match:
|
|
91
|
+
expected = dict(actual, **expected)
|
|
92
|
+
# now make them approximate if they are in actual so we get a nicer diff
|
|
93
|
+
approx_expected = {
|
|
94
|
+
k: _approx_reading(v, actual[k]) if k in actual else v
|
|
95
|
+
for k, v in expected.items()
|
|
76
96
|
}
|
|
97
|
+
# now we can compare them
|
|
98
|
+
assert actual == approx_expected
|
|
77
99
|
|
|
78
100
|
|
|
79
101
|
async def assert_configuration(
|
|
80
102
|
configurable: AsyncConfigurable,
|
|
81
|
-
|
|
103
|
+
expected_configuration: dict[str, dict[str, Any]],
|
|
104
|
+
full_match: bool = True,
|
|
82
105
|
) -> None:
|
|
83
106
|
"""Assert that a configurable Device has the given configuration.
|
|
84
107
|
|
|
@@ -86,9 +109,14 @@ async def assert_configuration(
|
|
|
86
109
|
Device with an async ``read_configuration()`` method to get the
|
|
87
110
|
configuration from.
|
|
88
111
|
:param configuration: The expected configuration from the configurable.
|
|
112
|
+
:param full_match: if expected_reading keys set is same as actual keys set.
|
|
113
|
+
true: exact match
|
|
114
|
+
false: expected_reading keys is subset of actual reading keys
|
|
89
115
|
"""
|
|
90
116
|
actual_configuration = await configurable.read_configuration()
|
|
91
|
-
_assert_readings_approx_equal(
|
|
117
|
+
_assert_readings_approx_equal(
|
|
118
|
+
expected_configuration, actual_configuration, full_match
|
|
119
|
+
)
|
|
92
120
|
|
|
93
121
|
|
|
94
122
|
async def assert_describe_signal(signal: SignalR, /, **metadata):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ophyd-async
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12
|
|
4
4
|
Summary: Asynchronous Bluesky hardware abstraction code, compatible with control systems like EPICS and Tango
|
|
5
5
|
Author-email: Tom Cobb <tom.cobb@diamond.ac.uk>
|
|
6
6
|
License: BSD 3-Clause License
|
|
@@ -48,6 +48,7 @@ Requires-Dist: pyyaml
|
|
|
48
48
|
Requires-Dist: colorlog
|
|
49
49
|
Requires-Dist: pydantic>=2.0
|
|
50
50
|
Requires-Dist: pydantic-numpy
|
|
51
|
+
Requires-Dist: stamina>=23.1.0
|
|
51
52
|
Provides-Extra: sim
|
|
52
53
|
Requires-Dist: h5py; extra == "sim"
|
|
53
54
|
Provides-Extra: ca
|
|
@@ -120,7 +121,7 @@ The main differences from ophyd sync are:
|
|
|
120
121
|
- Support for [EPICS][] PVA and [Tango][] as well as the traditional EPICS CA
|
|
121
122
|
- Better library support for splitting the logic from the hardware interface to avoid complex class heirarchies
|
|
122
123
|
|
|
123
|
-
It was written with the aim of implementing
|
|
124
|
+
It was written with the aim of implementing fly scanning in a generic and extensible way with highly customizable devices like PandABox and the Delta Tau PMAC products. Using async code makes it possible to do the "put 3 PVs in parallel, then get from another PV" logic that is common in fly scanning without the performance and complexity overhead of multiple threads.
|
|
124
125
|
|
|
125
126
|
Devices from both ophyd sync and ophyd-async can be used in the same RunEngine and even in the same scan. This allows a per-device migration where devices are reimplemented in ophyd-async one by one.
|
|
126
127
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
ophyd_async/__init__.py,sha256=dcAA3qsj1nNIMe5l-v2tlduZ_ypwBmyuHe45Lsq4k4w,206
|
|
2
2
|
ophyd_async/__main__.py,sha256=n_U4O9bgm97OuboUB_9eK7eFiwy8BZSgXJ0OzbE0DqU,481
|
|
3
3
|
ophyd_async/_docs_parser.py,sha256=gPYrigfSbYCF7QoSf2UvE-cpQu4snSssl7ZWN-kKDzI,352
|
|
4
|
-
ophyd_async/_version.py,sha256=
|
|
4
|
+
ophyd_async/_version.py,sha256=DJRqD0JYfzL1dCvH3-tt5p9TxBGyEU8MKFd2lrz6Yqw,508
|
|
5
5
|
ophyd_async/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
ophyd_async/core/__init__.py,sha256=RtYk6FdJxc7lxoSReRV1D7weTRYFu9ylhNNd3DyN904,4752
|
|
7
7
|
ophyd_async/core/_derived_signal.py,sha256=TuZza_j3J1Bw4QSqBYB9Ta2FyQP5BycO3nSHVtJ890Q,13015
|
|
@@ -9,7 +9,7 @@ ophyd_async/core/_derived_signal_backend.py,sha256=PYyyont_nUR9LBC9eqVwueHCMwLJf
|
|
|
9
9
|
ophyd_async/core/_detector.py,sha256=x1o-eSkvemQ-fTk47440owkTmYhuckA2ILNOCoJlHCY,15201
|
|
10
10
|
ophyd_async/core/_device.py,sha256=lSm8FBul9NTn9VO0rsAlV9pctJyUsMdU2ztEf5CqH5M,14716
|
|
11
11
|
ophyd_async/core/_device_filler.py,sha256=MDz8eQQ-eEAwo-UEMxfqPfpcBuMG01tLCGR6utwVnmE,14825
|
|
12
|
-
ophyd_async/core/_flyer.py,sha256=
|
|
12
|
+
ophyd_async/core/_flyer.py,sha256=8zKyU5aQOr_t59GIUwsYeb8NSabdvBp0swwuRe4v5VQ,3457
|
|
13
13
|
ophyd_async/core/_hdf_dataset.py,sha256=qh4QMJAXuOUQUciLXdMTX9yPnmWp5l-ugGCrJWUXQDQ,2954
|
|
14
14
|
ophyd_async/core/_log.py,sha256=DxKR4Nz3SgTaTzKBZWqt-w48yT8WUAr_3Qr223TEWRw,3587
|
|
15
15
|
ophyd_async/core/_mock_signal_backend.py,sha256=SPdCbVWss6-iL9C3t9u0IvR_Ln9JeDypVd18WlivdjE,3156
|
|
@@ -17,7 +17,7 @@ ophyd_async/core/_protocol.py,sha256=wQ_snxhTprHqEjQb1HgFwBljwolMY6A8C3xgV1PXwdU
|
|
|
17
17
|
ophyd_async/core/_providers.py,sha256=1XuLUw9sT1pKMfH_PsDEpIi1gulla7NfPSp3IR3KfEA,7545
|
|
18
18
|
ophyd_async/core/_readable.py,sha256=iBo1YwA5bsAbzLbznvmSnzKDWUuGkLh850Br3BXsgeU,11707
|
|
19
19
|
ophyd_async/core/_settings.py,sha256=_ZccbXKP7j5rG6-bMKk7aaLr8hChdRDAPY_YSR71XXM,4213
|
|
20
|
-
ophyd_async/core/_signal.py,sha256=
|
|
20
|
+
ophyd_async/core/_signal.py,sha256=wKbDJYpN7zWgRf_jAARFqZwCGXcdzPRDNXwvjGUR6dw,28217
|
|
21
21
|
ophyd_async/core/_signal_backend.py,sha256=PvwTbbSVEGqM-2s5BNRrKGwM_MiYL71qMxYAgyZ7wRM,6930
|
|
22
22
|
ophyd_async/core/_soft_signal_backend.py,sha256=zrE7H2ojHY6oQBucLkFgukszrkdvbIZuavLjEUqc_xM,6227
|
|
23
23
|
ophyd_async/core/_status.py,sha256=h4TtWFM7wFtpxxyAYYSITgcVzArYZdYBHbya6qIX5t0,6553
|
|
@@ -25,7 +25,7 @@ ophyd_async/core/_table.py,sha256=ai-_W-_WMZcy9f69BDYRv9vjVl-AVeOPN_uHYoGCSsc,69
|
|
|
25
25
|
ophyd_async/core/_utils.py,sha256=fePvt3g7eQ6CRQcMVkMeqxcbvYZboZ2sf1fVVZL-26M,12450
|
|
26
26
|
ophyd_async/core/_yaml_settings.py,sha256=Qojhku9l5kPSkTnEylCRWTe0gpw6S_XP5av5dPpqFgQ,2089
|
|
27
27
|
ophyd_async/epics/__init__.py,sha256=ou4yEaH9VZHz70e8oM614-arLMQvUfQyXhRJsnEpWn8,60
|
|
28
|
-
ophyd_async/epics/motor.py,sha256=
|
|
28
|
+
ophyd_async/epics/motor.py,sha256=UFolYxuaePnWNJNOFzgI-He4kBTHhJqaTywtqFFSmsk,8475
|
|
29
29
|
ophyd_async/epics/signal.py,sha256=0A-supp9ajr63O6aD7F9oG0-Q26YmRjk-ZGh57-jo1Y,239
|
|
30
30
|
ophyd_async/epics/adandor/__init__.py,sha256=dlitllrAdhvh16PAcVMUSSEytTDNMu6_HuYk8KD1EoY,343
|
|
31
31
|
ophyd_async/epics/adandor/_andor.py,sha256=SxAIP9OLefUqKcxrxhjNzil5D8-59Ps0vADdR6scO44,1281
|
|
@@ -67,7 +67,7 @@ ophyd_async/epics/core/_epics_connector.py,sha256=S4z_wbj-aogVcjqCyUgjhcq5Y4gDC7
|
|
|
67
67
|
ophyd_async/epics/core/_epics_device.py,sha256=wGdR24I7GSPh3HmM7jsWKZhBZgt4IyLrCn4Ut7Wx_xo,510
|
|
68
68
|
ophyd_async/epics/core/_p4p.py,sha256=uWh3oWPme74G4YfeJ6k8ZlHdKOwcf8Xp1J82b9aa_JI,16407
|
|
69
69
|
ophyd_async/epics/core/_pvi_connector.py,sha256=nAReSiasZA3j_0f8XhuWVO4_ck0MrusnKR9Jg-RT-ok,5584
|
|
70
|
-
ophyd_async/epics/core/_signal.py,sha256=
|
|
70
|
+
ophyd_async/epics/core/_signal.py,sha256=qAEe8mgXRMgBTcxuwN-KDGSRtJTwrhygThTDe5vA3EQ,5916
|
|
71
71
|
ophyd_async/epics/core/_util.py,sha256=G2kYfwsQ5iS3EgGrGuPA8bgC_PEN_XxO28oBurIqJFQ,2522
|
|
72
72
|
ophyd_async/epics/demo/__init__.py,sha256=WR2M3D8dbHcisJW2OIU2ManZu5SWez8ytZEp4jSBfDY,416
|
|
73
73
|
ophyd_async/epics/demo/__main__.py,sha256=o6M0FSWduPHe2lN9yNEdsXb48NckSd54-XJGoLe20Pc,1116
|
|
@@ -100,11 +100,11 @@ ophyd_async/fastcs/panda/_block.py,sha256=SM7NaWCRwLz2Pl4wgjZMrDgx3ZLdGPTw6nU0bA
|
|
|
100
100
|
ophyd_async/fastcs/panda/_control.py,sha256=xtW3dH_MLQoycgP-4vJtYx1M9alHjWo13iu9UFTgwzY,1306
|
|
101
101
|
ophyd_async/fastcs/panda/_hdf_panda.py,sha256=tL_OWHxlMQcMZGq9sxHLSeag6hP9MRIbTPn1W0u0iNI,1237
|
|
102
102
|
ophyd_async/fastcs/panda/_table.py,sha256=maKGoKypEuYqTSVWGgDO6GMEKOtlDm9Dn5YiYdBzu6c,2486
|
|
103
|
-
ophyd_async/fastcs/panda/_trigger.py,sha256=
|
|
103
|
+
ophyd_async/fastcs/panda/_trigger.py,sha256=TLd0ST-ZgsCGpONGUe76qHOaH74TlZNIGNkh-10eRa8,3404
|
|
104
104
|
ophyd_async/fastcs/panda/_writer.py,sha256=9cXDrfzj_e8LyGoG7kvgmftt8D_TS6r0fnlO8uR1fHw,6298
|
|
105
|
-
ophyd_async/plan_stubs/__init__.py,sha256=
|
|
105
|
+
ophyd_async/plan_stubs/__init__.py,sha256=sRe1Jna_6i7aKjE3pPzsP4iNMWeWdtiptLnOq9pov9M,619
|
|
106
106
|
ophyd_async/plan_stubs/_ensure_connected.py,sha256=YR6VRj7koccJ4x35NV-Ugl4ZbxgAoGN9PjVIjhv0gpw,894
|
|
107
|
-
ophyd_async/plan_stubs/_fly.py,sha256=
|
|
107
|
+
ophyd_async/plan_stubs/_fly.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
108
108
|
ophyd_async/plan_stubs/_nd_attributes.py,sha256=kwzyUSeidUH714gaZQtJLxCgDZtmIRyyoKBBRbvqg38,2350
|
|
109
109
|
ophyd_async/plan_stubs/_panda.py,sha256=5_Mf9kGzNjXpf_YscpCUE8tgq284nOHWCG7o_LNFfII,463
|
|
110
110
|
ophyd_async/plan_stubs/_settings.py,sha256=e3dGVSUV-Htay_9fKXyQTAQLdjunetGI3OBYp_oC_FY,5574
|
|
@@ -137,16 +137,16 @@ ophyd_async/tango/demo/_tango/__init__.py,sha256=FfONT7vM49nNo3a1Lv-LcMZO9EHv6bv
|
|
|
137
137
|
ophyd_async/tango/demo/_tango/_servers.py,sha256=putvERDyibibaTbhdWyqZB_axj2fURXqzDsZb9oSW14,2991
|
|
138
138
|
ophyd_async/tango/testing/__init__.py,sha256=SYXPAS00ny3jlUMOJKpaewO4ljPjK1_z1smj7IfsBQg,148
|
|
139
139
|
ophyd_async/tango/testing/_one_of_everything.py,sha256=tsxWgy2f_m9f0FH4XCVO_I8OtEquKbJQZbr4KaXMJL4,6571
|
|
140
|
-
ophyd_async/testing/__init__.py,sha256=
|
|
140
|
+
ophyd_async/testing/__init__.py,sha256=jDBzUAHGDMfkhd-_9u0CJWEq0E0sPrIGGlLmVzEyxY8,1742
|
|
141
141
|
ophyd_async/testing/__pytest_assert_rewrite.py,sha256=_SU2UfChPgEf7CFY7aYH2B7MLp-07_qYnVLyu6QtDL8,129
|
|
142
|
-
ophyd_async/testing/_assert.py,sha256=
|
|
142
|
+
ophyd_async/testing/_assert.py,sha256=zDTJh7QPhaJpzhnuFuejGKTKbKAs7ZNKx3sVsXBMrTU,8758
|
|
143
143
|
ophyd_async/testing/_mock_signal_utils.py,sha256=d-n_923ii59-ae9TbqVuIK9MAJpDmu0k47fzgJLj8t8,5195
|
|
144
144
|
ophyd_async/testing/_one_of_everything.py,sha256=Di0hPoKwrDOSsx50-2UdSHM2EbIKrPG9s0Vp11nE9V8,4773
|
|
145
145
|
ophyd_async/testing/_single_derived.py,sha256=5-HOTzgePcZ354NK_ssVpyIbJoJmKyjVQCxSwQXUC-4,2730
|
|
146
146
|
ophyd_async/testing/_utils.py,sha256=zClRo5ve8RGia7wQnby41W-Zprj-slOA5da1LfYnuhw,45
|
|
147
147
|
ophyd_async/testing/_wait_for_pending.py,sha256=YZAR48n-CW0GsPey3zFRzMJ4byDAr3HvMIoawjmTrHw,732
|
|
148
|
-
ophyd_async-0.
|
|
149
|
-
ophyd_async-0.
|
|
150
|
-
ophyd_async-0.
|
|
151
|
-
ophyd_async-0.
|
|
152
|
-
ophyd_async-0.
|
|
148
|
+
ophyd_async-0.12.dist-info/licenses/LICENSE,sha256=pU5shZcsvWgz701EbT7yjFZ8rMvZcWgRH54CRt8ld_c,1517
|
|
149
|
+
ophyd_async-0.12.dist-info/METADATA,sha256=mpFROvfJrpnj_A-CMYhK4YST2f8ZrkvNkvWg9dVFNVc,7110
|
|
150
|
+
ophyd_async-0.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
151
|
+
ophyd_async-0.12.dist-info/top_level.txt,sha256=-hjorMsv5Rmjo3qrgqhjpal1N6kW5vMxZO3lD4iEaXs,12
|
|
152
|
+
ophyd_async-0.12.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|