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,98 +1,201 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
from dataclasses import dataclass
|
|
2
3
|
|
|
3
4
|
import numpy as np
|
|
4
|
-
from scanspec.core import Path
|
|
5
|
+
from scanspec.core import Path, Slice
|
|
5
6
|
from scanspec.specs import Spec
|
|
6
7
|
|
|
7
8
|
from ophyd_async.core import (
|
|
8
9
|
DEFAULT_TIMEOUT,
|
|
10
|
+
AsyncStatus,
|
|
9
11
|
FlyerController,
|
|
12
|
+
error_if_none,
|
|
13
|
+
observe_value,
|
|
10
14
|
set_and_wait_for_value,
|
|
11
15
|
wait_for_value,
|
|
12
16
|
)
|
|
13
17
|
from ophyd_async.epics.motor import Motor
|
|
14
18
|
from ophyd_async.epics.pmac import PmacIO
|
|
15
19
|
from ophyd_async.epics.pmac._pmac_io import CS_LETTERS
|
|
20
|
+
from ophyd_async.epics.pmac._pmac_trajectory_generation import PVT, Trajectory
|
|
16
21
|
from ophyd_async.epics.pmac._utils import (
|
|
17
22
|
_PmacMotorInfo,
|
|
18
|
-
_Trajectory,
|
|
19
23
|
calculate_ramp_position_and_duration,
|
|
20
24
|
)
|
|
21
25
|
|
|
26
|
+
# PMAC durations are in milliseconds
|
|
27
|
+
# We must convert from scanspec durations (seconds) to milliseconds
|
|
28
|
+
# PMAC motion program multiples durations by 0.001
|
|
29
|
+
# (see https://github.com/DiamondLightSource/pmac/blob/afe81f8bb9179c3a20eff351f30bc6cfd1539ad9/pmacApp/pmc/trajectory_scan_code_ppmac.pmc#L241)
|
|
30
|
+
# Therefore, we must divide scanspec durations by 10e-6
|
|
31
|
+
TICK_S = 0.000001
|
|
32
|
+
SLICE_SIZE = 4000
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class PmacPrepareContext:
|
|
37
|
+
path: Path
|
|
38
|
+
motor_info: _PmacMotorInfo
|
|
39
|
+
ramp_up_time: float
|
|
40
|
+
|
|
22
41
|
|
|
23
42
|
class PmacTrajectoryTriggerLogic(FlyerController):
|
|
24
43
|
def __init__(self, pmac: PmacIO) -> None:
|
|
25
44
|
self.pmac = pmac
|
|
26
|
-
self.
|
|
45
|
+
self._next_pvt: PVT | None
|
|
46
|
+
self._loaded: int = 0
|
|
47
|
+
self._trajectory_status: AsyncStatus | None = None
|
|
48
|
+
self._prepare_context: PmacPrepareContext | None = None
|
|
27
49
|
|
|
28
50
|
async def prepare(self, value: Spec[Motor]):
|
|
29
|
-
|
|
30
|
-
|
|
51
|
+
path = Path(value.calculate())
|
|
52
|
+
slice = path.consume(SLICE_SIZE)
|
|
53
|
+
path_length = len(path)
|
|
54
|
+
motors = slice.axes()
|
|
55
|
+
motor_info = await _PmacMotorInfo.from_motors(self.pmac, motors)
|
|
31
56
|
ramp_up_pos, ramp_up_time = calculate_ramp_position_and_duration(
|
|
32
57
|
slice, motor_info, True
|
|
33
58
|
)
|
|
34
|
-
|
|
35
|
-
|
|
59
|
+
self._prepare_context = PmacPrepareContext(
|
|
60
|
+
path=path, motor_info=motor_info, ramp_up_time=ramp_up_time
|
|
36
61
|
)
|
|
37
|
-
trajectory = _Trajectory.from_slice(slice, ramp_up_time, motor_info)
|
|
38
62
|
await asyncio.gather(
|
|
39
|
-
self._build_trajectory(
|
|
40
|
-
trajectory, motor_info, ramp_down_pos, ramp_down_time
|
|
41
|
-
),
|
|
63
|
+
self._build_trajectory(motor_info, slice, path_length, ramp_up_time),
|
|
42
64
|
self._move_to_start(motor_info, ramp_up_pos),
|
|
43
65
|
)
|
|
44
66
|
|
|
45
67
|
async def kickoff(self):
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
68
|
+
prepare_context = error_if_none(
|
|
69
|
+
self._prepare_context, "Cannot kickoff. Must call prepare first."
|
|
70
|
+
)
|
|
71
|
+
self._prepare_context = None
|
|
72
|
+
self._trajectory_status = self._execute_trajectory(
|
|
73
|
+
prepare_context.path, prepare_context.motor_info
|
|
74
|
+
)
|
|
75
|
+
# Wait for the ramp up to happen
|
|
76
|
+
await wait_for_value(
|
|
77
|
+
self.pmac.trajectory.total_points,
|
|
78
|
+
lambda v: v >= 1,
|
|
79
|
+
prepare_context.ramp_up_time + DEFAULT_TIMEOUT,
|
|
50
80
|
)
|
|
51
81
|
|
|
52
82
|
async def complete(self):
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
await wait_for_value(
|
|
56
|
-
self.pmac.trajectory.execute_profile,
|
|
57
|
-
False,
|
|
58
|
-
timeout=self.scantime + DEFAULT_TIMEOUT,
|
|
83
|
+
trajectory_status = error_if_none(
|
|
84
|
+
self._trajectory_status, "Cannot complete. Must call kickoff first."
|
|
59
85
|
)
|
|
86
|
+
await trajectory_status
|
|
87
|
+
# Reset trajectory status and number of loaded points
|
|
88
|
+
self._trajectory_status = None
|
|
89
|
+
self._loaded = 0
|
|
60
90
|
|
|
61
91
|
async def stop(self):
|
|
62
|
-
await self.pmac.trajectory.abort_profile.
|
|
92
|
+
await self.pmac.trajectory.abort_profile.trigger()
|
|
93
|
+
|
|
94
|
+
@AsyncStatus.wrap
|
|
95
|
+
async def _execute_trajectory(self, path: Path, motor_info: _PmacMotorInfo):
|
|
96
|
+
execute_status = self.pmac.trajectory.execute_profile.set(
|
|
97
|
+
True, wait=True, timeout=None
|
|
98
|
+
)
|
|
99
|
+
# We consume SLICE_SIZE from self.path and parse a trajectory
|
|
100
|
+
# containing at least 2 * SLICE_SIZE, as a gapless trajectory
|
|
101
|
+
# will contain 2 points per slice frame. If gaps are present,
|
|
102
|
+
# additional points are inserted, overfilling the buffer.
|
|
103
|
+
min_buffer_size = SLICE_SIZE * 2
|
|
104
|
+
async for current_point in observe_value(
|
|
105
|
+
self.pmac.trajectory.total_points,
|
|
106
|
+
done_status=execute_status,
|
|
107
|
+
timeout=DEFAULT_TIMEOUT,
|
|
108
|
+
):
|
|
109
|
+
# Ensure we maintain a minimum buffer size, if we have more points to append
|
|
110
|
+
if len(path) != 0 and self._loaded - current_point < min_buffer_size:
|
|
111
|
+
# We have less than SLICE_SIZE * 2 points in the buffer, so refill
|
|
112
|
+
next_slice = path.consume(SLICE_SIZE)
|
|
113
|
+
path_length = len(path)
|
|
114
|
+
await self._append_trajectory(next_slice, path_length, motor_info)
|
|
115
|
+
|
|
116
|
+
async def _append_trajectory(
|
|
117
|
+
self, slice: Slice, path_length: int, motor_info: _PmacMotorInfo
|
|
118
|
+
):
|
|
119
|
+
trajectory = await self._parse_trajectory(slice, path_length, motor_info)
|
|
120
|
+
await self._set_trajectory_arrays(trajectory, motor_info)
|
|
121
|
+
await self.pmac.trajectory.append_profile.trigger()
|
|
63
122
|
|
|
64
123
|
async def _build_trajectory(
|
|
65
124
|
self,
|
|
66
|
-
trajectory: _Trajectory,
|
|
67
125
|
motor_info: _PmacMotorInfo,
|
|
68
|
-
|
|
69
|
-
|
|
126
|
+
slice: Slice,
|
|
127
|
+
path_length: int,
|
|
128
|
+
ramp_up_time: float,
|
|
70
129
|
):
|
|
71
|
-
trajectory =
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
trajectory.positions[motor]
|
|
79
|
-
)
|
|
80
|
-
await self.pmac.trajectory.velocities[number + 1].set(
|
|
81
|
-
trajectory.velocities[motor]
|
|
82
|
-
)
|
|
130
|
+
trajectory = await self._parse_trajectory(
|
|
131
|
+
slice, path_length, motor_info, ramp_up_time
|
|
132
|
+
)
|
|
133
|
+
use_axis = {
|
|
134
|
+
axis + 1: (axis in motor_info.motor_cs_index.values())
|
|
135
|
+
for axis in range(len(CS_LETTERS))
|
|
136
|
+
}
|
|
83
137
|
|
|
84
138
|
coros = [
|
|
85
139
|
self.pmac.trajectory.profile_cs_name.set(motor_info.cs_port),
|
|
86
|
-
self.pmac.trajectory.time_array.set(trajectory.durations),
|
|
87
|
-
self.pmac.trajectory.user_array.set(trajectory.user_programs),
|
|
88
|
-
self.pmac.trajectory.points_to_build.set(len(trajectory.durations)),
|
|
89
140
|
self.pmac.trajectory.calculate_velocities.set(False),
|
|
141
|
+
self._set_trajectory_arrays(trajectory, motor_info),
|
|
90
142
|
] + [
|
|
91
143
|
self.pmac.trajectory.use_axis[number].set(use)
|
|
92
144
|
for number, use in use_axis.items()
|
|
93
145
|
]
|
|
146
|
+
|
|
147
|
+
await asyncio.gather(*coros)
|
|
148
|
+
await self.pmac.trajectory.build_profile.trigger()
|
|
149
|
+
|
|
150
|
+
async def _parse_trajectory(
|
|
151
|
+
self,
|
|
152
|
+
slice: Slice,
|
|
153
|
+
path_length: int,
|
|
154
|
+
motor_info: _PmacMotorInfo,
|
|
155
|
+
ramp_up_time: float | None = None,
|
|
156
|
+
) -> Trajectory:
|
|
157
|
+
trajectory, exit_pvt = Trajectory.from_slice(
|
|
158
|
+
slice,
|
|
159
|
+
motor_info,
|
|
160
|
+
None if ramp_up_time else self._next_pvt,
|
|
161
|
+
ramp_up_time=ramp_up_time,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
if path_length == 0:
|
|
165
|
+
ramp_down_pos, ramp_down_time = calculate_ramp_position_and_duration(
|
|
166
|
+
slice, motor_info, False
|
|
167
|
+
)
|
|
168
|
+
trajectory = trajectory.with_ramp_down(
|
|
169
|
+
exit_pvt, ramp_down_pos, ramp_down_time, 0
|
|
170
|
+
)
|
|
171
|
+
self._next_pvt = exit_pvt
|
|
172
|
+
self._loaded += len(trajectory)
|
|
173
|
+
|
|
174
|
+
return trajectory
|
|
175
|
+
|
|
176
|
+
async def _set_trajectory_arrays(
|
|
177
|
+
self, trajectory: Trajectory, motor_info: _PmacMotorInfo
|
|
178
|
+
):
|
|
179
|
+
coros = []
|
|
180
|
+
for motor, number in motor_info.motor_cs_index.items():
|
|
181
|
+
coros.append(
|
|
182
|
+
self.pmac.trajectory.positions[number + 1].set(
|
|
183
|
+
trajectory.positions[motor]
|
|
184
|
+
)
|
|
185
|
+
)
|
|
186
|
+
coros.append(
|
|
187
|
+
self.pmac.trajectory.velocities[number + 1].set(
|
|
188
|
+
trajectory.velocities[motor]
|
|
189
|
+
)
|
|
190
|
+
)
|
|
191
|
+
coros.extend(
|
|
192
|
+
[
|
|
193
|
+
self.pmac.trajectory.time_array.set(trajectory.durations / TICK_S),
|
|
194
|
+
self.pmac.trajectory.user_array.set(trajectory.user_programs),
|
|
195
|
+
self.pmac.trajectory.points_to_build.set(len(trajectory.durations)),
|
|
196
|
+
]
|
|
197
|
+
)
|
|
94
198
|
await asyncio.gather(*coros)
|
|
95
|
-
await self.pmac.trajectory.build_profile.set(True)
|
|
96
199
|
|
|
97
200
|
async def _move_to_start(
|
|
98
201
|
self, motor_info: _PmacMotorInfo, ramp_up_position: dict[Motor, np.float64]
|