dls-dodal 1.36.1a0__py3-none-any.whl → 1.36.3__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.
- {dls_dodal-1.36.1a0.dist-info → dls_dodal-1.36.3.dist-info}/METADATA +2 -2
- {dls_dodal-1.36.1a0.dist-info → dls_dodal-1.36.3.dist-info}/RECORD +31 -33
- dodal/_version.py +2 -2
- dodal/beamlines/__init__.py +1 -0
- dodal/beamlines/adsim.py +75 -0
- dodal/beamlines/i10.py +74 -7
- dodal/beamlines/i24.py +17 -1
- dodal/devices/adsim.py +10 -10
- dodal/devices/aperture.py +0 -7
- dodal/devices/aperturescatterguard.py +79 -195
- dodal/devices/apple2_undulator.py +9 -9
- dodal/devices/attenuator.py +15 -5
- dodal/devices/focusing_mirror.py +12 -3
- dodal/devices/i10/i10_setting_data.py +3 -3
- dodal/devices/i10/mirrors.py +24 -0
- dodal/devices/i10/slits.py +37 -0
- dodal/devices/i24/dual_backlight.py +1 -0
- dodal/devices/i24/focus_mirrors.py +12 -12
- dodal/devices/linkam3.py +2 -2
- dodal/devices/oav/pin_image_recognition/__init__.py +2 -4
- dodal/devices/p99/sample_stage.py +15 -15
- dodal/devices/slits.py +29 -7
- dodal/devices/tetramm.py +16 -16
- dodal/devices/undulator_dcm.py +4 -0
- dodal/devices/util/test_utils.py +2 -2
- dodal/devices/xspress3/xspress3.py +3 -3
- dodal/devices/zebra.py +13 -13
- dodal/adsim.py +0 -17
- dodal/devices/areadetector/__init__.py +0 -10
- dodal/devices/areadetector/adaravis.py +0 -101
- dodal/devices/areadetector/adsim.py +0 -47
- dodal/devices/areadetector/adutils.py +0 -81
- {dls_dodal-1.36.1a0.dist-info → dls_dodal-1.36.3.dist-info}/LICENSE +0 -0
- {dls_dodal-1.36.1a0.dist-info → dls_dodal-1.36.3.dist-info}/WHEEL +0 -0
- {dls_dodal-1.36.1a0.dist-info → dls_dodal-1.36.3.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.36.1a0.dist-info → dls_dodal-1.36.3.dist-info}/top_level.txt +0 -0
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
-
from collections.abc import Callable, Coroutine
|
|
5
|
-
from typing import Any
|
|
6
4
|
|
|
7
|
-
from bluesky.protocols import Movable
|
|
5
|
+
from bluesky.protocols import Movable
|
|
8
6
|
from ophyd_async.core import (
|
|
9
7
|
AsyncStatus,
|
|
10
|
-
Reference,
|
|
11
8
|
StandardReadable,
|
|
12
9
|
StandardReadableFormat,
|
|
13
10
|
StrictEnum,
|
|
14
11
|
)
|
|
15
|
-
from ophyd_async.epics.motor import Motor
|
|
16
12
|
from pydantic import BaseModel, Field
|
|
17
13
|
|
|
18
14
|
from dodal.common.beamlines.beamline_parameters import GDABeamlineParameters
|
|
@@ -111,157 +107,7 @@ def load_positions_from_beamline_parameters(
|
|
|
111
107
|
}
|
|
112
108
|
|
|
113
109
|
|
|
114
|
-
async def _safe_move_whilst_in_beam(
|
|
115
|
-
aperture: Aperture,
|
|
116
|
-
scatterguard: Scatterguard,
|
|
117
|
-
position: AperturePosition,
|
|
118
|
-
aperture_z_tolerance: float,
|
|
119
|
-
):
|
|
120
|
-
"""
|
|
121
|
-
Move the aperture and scatterguard combo safely to a new position.
|
|
122
|
-
See https://github.com/DiamondLightSource/hyperion/wiki/Aperture-Scatterguard-Collisions
|
|
123
|
-
for why this is required. TLDR is that we have a collision at the top of y so we need
|
|
124
|
-
to make sure we move the assembly down before we move the scatterguard up.
|
|
125
|
-
|
|
126
|
-
We also check that the assembly has been moved into the correct z position
|
|
127
|
-
previously. If we try and move whilst in the incorrect Z position we will collide
|
|
128
|
-
with the table.
|
|
129
|
-
"""
|
|
130
|
-
ap_z_in_position = await aperture.z.motor_done_move.get_value()
|
|
131
|
-
if not ap_z_in_position:
|
|
132
|
-
raise InvalidApertureMove(
|
|
133
|
-
"ApertureScatterguard z is still moving. Wait for it to finish "
|
|
134
|
-
"before triggering another move."
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
current_ap_z = await aperture.z.user_readback.get_value()
|
|
138
|
-
diff_on_z = abs(current_ap_z - position.aperture_z)
|
|
139
|
-
if diff_on_z > aperture_z_tolerance:
|
|
140
|
-
raise InvalidApertureMove(
|
|
141
|
-
f"Current aperture z ({current_ap_z}), outside of tolerance ({aperture_z_tolerance}) from target ({position.aperture_z})."
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
current_ap_y = await aperture.y.user_readback.get_value()
|
|
145
|
-
|
|
146
|
-
aperture_x, aperture_y, aperture_z, scatterguard_x, scatterguard_y = position.values
|
|
147
|
-
|
|
148
|
-
if aperture_y > current_ap_y:
|
|
149
|
-
# Assembly needs to move up so move the scatterguard down first
|
|
150
|
-
await asyncio.gather(
|
|
151
|
-
scatterguard.x.set(scatterguard_x),
|
|
152
|
-
scatterguard.y.set(scatterguard_y),
|
|
153
|
-
)
|
|
154
|
-
await asyncio.gather(
|
|
155
|
-
aperture.x.set(aperture_x),
|
|
156
|
-
aperture.y.set(aperture_y),
|
|
157
|
-
aperture.z.set(aperture_z),
|
|
158
|
-
)
|
|
159
|
-
else:
|
|
160
|
-
await asyncio.gather(
|
|
161
|
-
aperture.x.set(aperture_x),
|
|
162
|
-
aperture.y.set(aperture_y),
|
|
163
|
-
aperture.z.set(aperture_z),
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
await asyncio.gather(
|
|
167
|
-
scatterguard.x.set(scatterguard_x),
|
|
168
|
-
scatterguard.y.set(scatterguard_y),
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
class ApertureSelector(StandardReadable, Movable):
|
|
173
|
-
"""Allows for moving all axes other than Y into the correct position, this means
|
|
174
|
-
that we can set up the aperture while it is out of the beam then move it in later."""
|
|
175
|
-
|
|
176
|
-
def __init__(
|
|
177
|
-
self,
|
|
178
|
-
aperture: Aperture,
|
|
179
|
-
scatterguard: Scatterguard,
|
|
180
|
-
out_of_beam: Callable[[], Coroutine[Any, Any, bool]],
|
|
181
|
-
loaded_positions: dict[ApertureValue, AperturePosition],
|
|
182
|
-
aperture_z_tolerance: float,
|
|
183
|
-
):
|
|
184
|
-
self.aperture = Reference(aperture)
|
|
185
|
-
self.scatterguard = Reference(scatterguard)
|
|
186
|
-
self.loaded_positions = loaded_positions
|
|
187
|
-
self.get_is_out_of_beam = out_of_beam
|
|
188
|
-
self.aperture_z_tolerance = aperture_z_tolerance
|
|
189
|
-
super().__init__()
|
|
190
|
-
|
|
191
|
-
@AsyncStatus.wrap
|
|
192
|
-
async def set(self, value: ApertureValue):
|
|
193
|
-
"""Moves the assembly to the position for the specified aperture, whilst keeping
|
|
194
|
-
it out of the beam if it already is so.
|
|
195
|
-
|
|
196
|
-
Moving the assembly whilst out of the beam has no collision risk so we can just
|
|
197
|
-
move all the motors together.
|
|
198
|
-
"""
|
|
199
|
-
if await self.get_is_out_of_beam():
|
|
200
|
-
aperture_x, _, aperture_z, scatterguard_x, scatterguard_y = (
|
|
201
|
-
self.loaded_positions[value].values
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
await asyncio.gather(
|
|
205
|
-
self.aperture().x.set(aperture_x),
|
|
206
|
-
self.aperture().z.set(aperture_z),
|
|
207
|
-
self.scatterguard().x.set(scatterguard_x),
|
|
208
|
-
self.scatterguard().y.set(scatterguard_y),
|
|
209
|
-
)
|
|
210
|
-
else:
|
|
211
|
-
await _safe_move_whilst_in_beam(
|
|
212
|
-
self.aperture(),
|
|
213
|
-
self.scatterguard(),
|
|
214
|
-
self.loaded_positions[value],
|
|
215
|
-
self.aperture_z_tolerance,
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
class OutTrigger(StandardReadable, Triggerable):
|
|
220
|
-
"""Allows for moving just the Y stage of the assembly out of the beam."""
|
|
221
|
-
|
|
222
|
-
def __init__(
|
|
223
|
-
self,
|
|
224
|
-
aperture_y: Motor,
|
|
225
|
-
out_y: float,
|
|
226
|
-
):
|
|
227
|
-
self.aperture_y = Reference(aperture_y)
|
|
228
|
-
self.out_y = out_y
|
|
229
|
-
super().__init__()
|
|
230
|
-
|
|
231
|
-
@AsyncStatus.wrap
|
|
232
|
-
async def trigger(self):
|
|
233
|
-
"""Moves the assembly out of the beam."""
|
|
234
|
-
await self.aperture_y().set(self.out_y)
|
|
235
|
-
|
|
236
|
-
|
|
237
110
|
class ApertureScatterguard(StandardReadable, Movable):
|
|
238
|
-
"""Move the aperture and scatterguard assembly in a safe way. There are two ways to
|
|
239
|
-
interact with the device depending on if you want simplicity or move flexibility.
|
|
240
|
-
|
|
241
|
-
The simple interface is using:
|
|
242
|
-
|
|
243
|
-
await aperture_scatterguard.set(ApertureValue.LARGE)
|
|
244
|
-
|
|
245
|
-
This will move the assembly so that the large aperture is in the beam, regardless
|
|
246
|
-
of where the assembly currently is.
|
|
247
|
-
|
|
248
|
-
However, the aperture Y axis is faster than the others. In some cases we may want to
|
|
249
|
-
move the assembly out of the beam with this axis without moving others:
|
|
250
|
-
|
|
251
|
-
await aperture_scatterguard.move_out.trigger()
|
|
252
|
-
|
|
253
|
-
We may then want to keep the assembly out of the beam whilst asynchronously preparing
|
|
254
|
-
the other axes for the aperture that's to follow:
|
|
255
|
-
|
|
256
|
-
await aperture_scatterguard.aperture_outside_beam.set(ApertureValue.LARGE)
|
|
257
|
-
|
|
258
|
-
Then, at a later time, move back into the beam:
|
|
259
|
-
|
|
260
|
-
await aperture_scatterguard.set(ApertureValue.LARGE)
|
|
261
|
-
|
|
262
|
-
This move will now be faster as only the y is left to move.
|
|
263
|
-
"""
|
|
264
|
-
|
|
265
111
|
def __init__(
|
|
266
112
|
self,
|
|
267
113
|
loaded_positions: dict[ApertureValue, AperturePosition],
|
|
@@ -269,8 +115,8 @@ class ApertureScatterguard(StandardReadable, Movable):
|
|
|
269
115
|
prefix: str = "",
|
|
270
116
|
name: str = "",
|
|
271
117
|
) -> None:
|
|
272
|
-
self.
|
|
273
|
-
self.
|
|
118
|
+
self.aperture = Aperture(prefix + "-MO-MAPT-01:")
|
|
119
|
+
self.scatterguard = Scatterguard(prefix + "-MO-SCAT-01:")
|
|
274
120
|
self.radius = create_hardware_backed_soft_signal(
|
|
275
121
|
float, self._get_current_radius, units="µm"
|
|
276
122
|
)
|
|
@@ -278,43 +124,30 @@ class ApertureScatterguard(StandardReadable, Movable):
|
|
|
278
124
|
self._tolerances = tolerances
|
|
279
125
|
self.add_readables(
|
|
280
126
|
[
|
|
281
|
-
self.
|
|
282
|
-
self.
|
|
283
|
-
self.
|
|
284
|
-
self.
|
|
285
|
-
self.
|
|
127
|
+
self.aperture.x.user_readback,
|
|
128
|
+
self.aperture.y.user_readback,
|
|
129
|
+
self.aperture.z.user_readback,
|
|
130
|
+
self.scatterguard.x.user_readback,
|
|
131
|
+
self.scatterguard.y.user_readback,
|
|
286
132
|
self.radius,
|
|
287
133
|
],
|
|
288
134
|
)
|
|
289
|
-
|
|
290
135
|
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
|
|
291
136
|
self.selected_aperture = create_hardware_backed_soft_signal(
|
|
292
137
|
ApertureValue, self._get_current_aperture_position
|
|
293
138
|
)
|
|
294
139
|
|
|
295
|
-
# Setting this will select the aperture but not move it into beam
|
|
296
|
-
self.aperture_outside_beam = ApertureSelector(
|
|
297
|
-
self._aperture,
|
|
298
|
-
self._scatterguard,
|
|
299
|
-
self._is_out_of_beam,
|
|
300
|
-
self._loaded_positions,
|
|
301
|
-
self._tolerances.aperture_z,
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
# Setting this will just move the assembly out of the beam
|
|
305
|
-
self.move_out = OutTrigger(
|
|
306
|
-
self._aperture.y, loaded_positions[ApertureValue.ROBOT_LOAD].aperture_y
|
|
307
|
-
)
|
|
308
|
-
|
|
309
140
|
super().__init__(name)
|
|
310
141
|
|
|
142
|
+
def get_position_from_gda_aperture_name(
|
|
143
|
+
self, gda_aperture_name: str
|
|
144
|
+
) -> ApertureValue:
|
|
145
|
+
return ApertureValue(gda_aperture_name)
|
|
146
|
+
|
|
311
147
|
@AsyncStatus.wrap
|
|
312
148
|
async def set(self, value: ApertureValue):
|
|
313
|
-
"""This set will move the aperture into the beam or move to robot load"""
|
|
314
149
|
position = self._loaded_positions[value]
|
|
315
|
-
await
|
|
316
|
-
self._aperture, self._scatterguard, position, self._tolerances.aperture_z
|
|
317
|
-
)
|
|
150
|
+
await self._safe_move_within_datacollection_range(position, value)
|
|
318
151
|
|
|
319
152
|
@AsyncStatus.wrap
|
|
320
153
|
async def _set_raw_unsafe(self, position: AperturePosition):
|
|
@@ -324,18 +157,13 @@ class ApertureScatterguard(StandardReadable, Movable):
|
|
|
324
157
|
)
|
|
325
158
|
|
|
326
159
|
await asyncio.gather(
|
|
327
|
-
self.
|
|
328
|
-
self.
|
|
329
|
-
self.
|
|
330
|
-
self.
|
|
331
|
-
self.
|
|
160
|
+
self.aperture.x.set(aperture_x),
|
|
161
|
+
self.aperture.y.set(aperture_y),
|
|
162
|
+
self.aperture.z.set(aperture_z),
|
|
163
|
+
self.scatterguard.x.set(scatterguard_x),
|
|
164
|
+
self.scatterguard.y.set(scatterguard_y),
|
|
332
165
|
)
|
|
333
166
|
|
|
334
|
-
async def _is_out_of_beam(self) -> bool:
|
|
335
|
-
current_ap_y = await self._aperture.y.user_readback.get_value()
|
|
336
|
-
robot_load_ap_y = self._loaded_positions[ApertureValue.ROBOT_LOAD].aperture_y
|
|
337
|
-
return current_ap_y <= robot_load_ap_y + self._tolerances.aperture_y
|
|
338
|
-
|
|
339
167
|
async def _get_current_aperture_position(self) -> ApertureValue:
|
|
340
168
|
"""
|
|
341
169
|
Returns the current aperture position using readback values
|
|
@@ -343,13 +171,15 @@ class ApertureScatterguard(StandardReadable, Movable):
|
|
|
343
171
|
mini aperture y <= ROBOT_LOAD.location.aperture_y + tolerance.
|
|
344
172
|
If no position is found then raises InvalidApertureMove.
|
|
345
173
|
"""
|
|
346
|
-
|
|
174
|
+
current_ap_y = await self.aperture.y.user_readback.get_value(cached=False)
|
|
175
|
+
robot_load_ap_y = self._loaded_positions[ApertureValue.ROBOT_LOAD].aperture_y
|
|
176
|
+
if await self.aperture.large.get_value(cached=False) == 1:
|
|
347
177
|
return ApertureValue.LARGE
|
|
348
|
-
elif await self.
|
|
178
|
+
elif await self.aperture.medium.get_value(cached=False) == 1:
|
|
349
179
|
return ApertureValue.MEDIUM
|
|
350
|
-
elif await self.
|
|
180
|
+
elif await self.aperture.small.get_value(cached=False) == 1:
|
|
351
181
|
return ApertureValue.SMALL
|
|
352
|
-
elif
|
|
182
|
+
elif current_ap_y <= robot_load_ap_y + self._tolerances.aperture_y:
|
|
353
183
|
return ApertureValue.ROBOT_LOAD
|
|
354
184
|
|
|
355
185
|
raise InvalidApertureMove("Current aperture/scatterguard state unrecognised")
|
|
@@ -357,3 +187,57 @@ class ApertureScatterguard(StandardReadable, Movable):
|
|
|
357
187
|
async def _get_current_radius(self) -> float:
|
|
358
188
|
current_value = await self._get_current_aperture_position()
|
|
359
189
|
return self._loaded_positions[current_value].radius
|
|
190
|
+
|
|
191
|
+
async def _safe_move_within_datacollection_range(
|
|
192
|
+
self, position: AperturePosition, value: ApertureValue
|
|
193
|
+
):
|
|
194
|
+
"""
|
|
195
|
+
Move the aperture and scatterguard combo safely to a new position.
|
|
196
|
+
See https://github.com/DiamondLightSource/hyperion/wiki/Aperture-Scatterguard-Collisions
|
|
197
|
+
for why this is required.
|
|
198
|
+
"""
|
|
199
|
+
assert self._loaded_positions is not None
|
|
200
|
+
|
|
201
|
+
ap_z_in_position = await self.aperture.z.motor_done_move.get_value()
|
|
202
|
+
if not ap_z_in_position:
|
|
203
|
+
raise InvalidApertureMove(
|
|
204
|
+
"ApertureScatterguard z is still moving. Wait for it to finish "
|
|
205
|
+
"before triggering another move."
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
current_ap_z = await self.aperture.z.user_readback.get_value()
|
|
209
|
+
diff_on_z = abs(current_ap_z - position.aperture_z)
|
|
210
|
+
if diff_on_z > self._tolerances.aperture_z:
|
|
211
|
+
raise InvalidApertureMove(
|
|
212
|
+
"ApertureScatterguard safe move is not yet defined for positions "
|
|
213
|
+
"outside of LARGE, MEDIUM, SMALL, ROBOT_LOAD. "
|
|
214
|
+
f"Current aperture z ({current_ap_z}), outside of tolerance ({self._tolerances.aperture_z}) from target ({position.aperture_z})."
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
current_ap_y = await self.aperture.y.user_readback.get_value()
|
|
218
|
+
|
|
219
|
+
aperture_x, aperture_y, aperture_z, scatterguard_x, scatterguard_y = (
|
|
220
|
+
position.values
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
if position.aperture_y > current_ap_y:
|
|
224
|
+
await asyncio.gather(
|
|
225
|
+
self.scatterguard.x.set(scatterguard_x),
|
|
226
|
+
self.scatterguard.y.set(scatterguard_y),
|
|
227
|
+
)
|
|
228
|
+
await asyncio.gather(
|
|
229
|
+
self.aperture.x.set(aperture_x),
|
|
230
|
+
self.aperture.y.set(aperture_y),
|
|
231
|
+
self.aperture.z.set(aperture_z),
|
|
232
|
+
)
|
|
233
|
+
else:
|
|
234
|
+
await asyncio.gather(
|
|
235
|
+
self.aperture.x.set(aperture_x),
|
|
236
|
+
self.aperture.y.set(aperture_y),
|
|
237
|
+
self.aperture.z.set(aperture_z),
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
await asyncio.gather(
|
|
241
|
+
self.scatterguard.x.set(scatterguard_x),
|
|
242
|
+
self.scatterguard.y.set(scatterguard_y),
|
|
243
|
+
)
|
|
@@ -21,8 +21,8 @@ from dodal.log import LOGGER
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class UndulatorGateStatus(StrictEnum):
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
OPEN = "Open"
|
|
25
|
+
CLOSE = "Closed"
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
@dataclass
|
|
@@ -146,7 +146,7 @@ class UndulatorGap(StandardReadable, Movable):
|
|
|
146
146
|
timeout = await self._cal_timeout()
|
|
147
147
|
LOGGER.info(f"Moving {self.name} to {value} with timeout = {timeout}")
|
|
148
148
|
await self.set_move.set(value=1, timeout=timeout)
|
|
149
|
-
await wait_for_value(self.gate, UndulatorGateStatus.
|
|
149
|
+
await wait_for_value(self.gate, UndulatorGateStatus.CLOSE, timeout=timeout)
|
|
150
150
|
|
|
151
151
|
async def _cal_timeout(self) -> float:
|
|
152
152
|
vel = await self.velocity.get_value()
|
|
@@ -157,7 +157,7 @@ class UndulatorGap(StandardReadable, Movable):
|
|
|
157
157
|
async def check_id_status(self) -> None:
|
|
158
158
|
if await self.fault.get_value() != 0:
|
|
159
159
|
raise RuntimeError(f"{self.name} is in fault state")
|
|
160
|
-
if await self.gate.get_value() == UndulatorGateStatus.
|
|
160
|
+
if await self.gate.get_value() == UndulatorGateStatus.OPEN:
|
|
161
161
|
raise RuntimeError(f"{self.name} is already in motion.")
|
|
162
162
|
|
|
163
163
|
async def get_timeout(self) -> float:
|
|
@@ -251,7 +251,7 @@ class UndulatorPhaseAxes(StandardReadable, Movable):
|
|
|
251
251
|
)
|
|
252
252
|
timeout = await self._cal_timeout()
|
|
253
253
|
await self.set_move.set(value=1, timeout=timeout)
|
|
254
|
-
await wait_for_value(self.gate, UndulatorGateStatus.
|
|
254
|
+
await wait_for_value(self.gate, UndulatorGateStatus.CLOSE, timeout=timeout)
|
|
255
255
|
|
|
256
256
|
async def _cal_timeout(self) -> float:
|
|
257
257
|
"""
|
|
@@ -283,7 +283,7 @@ class UndulatorPhaseAxes(StandardReadable, Movable):
|
|
|
283
283
|
async def check_id_status(self) -> None:
|
|
284
284
|
if await self.fault.get_value() != 0:
|
|
285
285
|
raise RuntimeError(f"{self.name} is in fault state")
|
|
286
|
-
if await self.gate.get_value() == UndulatorGateStatus.
|
|
286
|
+
if await self.gate.get_value() == UndulatorGateStatus.OPEN:
|
|
287
287
|
raise RuntimeError(f"{self.name} is already in motion.")
|
|
288
288
|
|
|
289
289
|
async def get_timeout(self) -> float:
|
|
@@ -325,7 +325,7 @@ class UndulatorJawPhase(StandardReadable, Movable):
|
|
|
325
325
|
)
|
|
326
326
|
timeout = await self._cal_timeout()
|
|
327
327
|
await self.set_move.set(value=1, timeout=timeout)
|
|
328
|
-
await wait_for_value(self.gate, UndulatorGateStatus.
|
|
328
|
+
await wait_for_value(self.gate, UndulatorGateStatus.CLOSE, timeout=timeout)
|
|
329
329
|
|
|
330
330
|
async def _cal_timeout(self) -> float:
|
|
331
331
|
"""
|
|
@@ -345,7 +345,7 @@ class UndulatorJawPhase(StandardReadable, Movable):
|
|
|
345
345
|
async def check_id_status(self) -> None:
|
|
346
346
|
if await self.fault.get_value() != 0:
|
|
347
347
|
raise RuntimeError(f"{self.name} is in fault state")
|
|
348
|
-
if await self.gate.get_value() == UndulatorGateStatus.
|
|
348
|
+
if await self.gate.get_value() == UndulatorGateStatus.OPEN:
|
|
349
349
|
raise RuntimeError(f"{self.name} is already in motion.")
|
|
350
350
|
|
|
351
351
|
async def get_timeout(self) -> float:
|
|
@@ -458,7 +458,7 @@ class Apple2(StandardReadable, Movable):
|
|
|
458
458
|
self.phase().set_move.set(value=1, timeout=timeout),
|
|
459
459
|
)
|
|
460
460
|
await wait_for_value(
|
|
461
|
-
self.gap().gate, UndulatorGateStatus.
|
|
461
|
+
self.gap().gate, UndulatorGateStatus.CLOSE, timeout=timeout
|
|
462
462
|
)
|
|
463
463
|
self._energy_set(energy) # Update energy for after move for readback.
|
|
464
464
|
|
dodal/devices/attenuator.py
CHANGED
|
@@ -14,7 +14,20 @@ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw, epics_signal
|
|
|
14
14
|
from dodal.log import LOGGER
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
class
|
|
17
|
+
class ReadOnlyAttenuator(StandardReadable):
|
|
18
|
+
"""A read-only attenuator class with a minimum set of PVs for reading.
|
|
19
|
+
|
|
20
|
+
The actual_transmission will return a fractional transmission between 0-1.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, prefix: str, name: str = "") -> None:
|
|
24
|
+
with self.add_children_as_readables():
|
|
25
|
+
self.actual_transmission = epics_signal_r(float, prefix + "MATCH")
|
|
26
|
+
|
|
27
|
+
super().__init__(name)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Attenuator(ReadOnlyAttenuator, Movable):
|
|
18
31
|
"""The attenuator will insert filters into the beam to reduce its transmission.
|
|
19
32
|
|
|
20
33
|
This device should be set with:
|
|
@@ -42,10 +55,7 @@ class Attenuator(StandardReadable, Movable):
|
|
|
42
55
|
self._use_current_energy = epics_signal_x(prefix + "E2WL:USECURRENTENERGY.PROC")
|
|
43
56
|
self._change = epics_signal_x(prefix + "FANOUT")
|
|
44
57
|
|
|
45
|
-
|
|
46
|
-
self.actual_transmission = epics_signal_r(float, prefix + "MATCH")
|
|
47
|
-
|
|
48
|
-
super().__init__(name)
|
|
58
|
+
super().__init__(prefix, name)
|
|
49
59
|
|
|
50
60
|
@AsyncStatus.wrap
|
|
51
61
|
async def set(self, value: float):
|
dodal/devices/focusing_mirror.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import TypedDict
|
|
2
|
+
|
|
1
3
|
from ophyd_async.core import (
|
|
2
4
|
AsyncStatus,
|
|
3
5
|
Device,
|
|
@@ -36,6 +38,12 @@ class MirrorStripe(StrictEnum):
|
|
|
36
38
|
PLATINUM = "Platinum"
|
|
37
39
|
|
|
38
40
|
|
|
41
|
+
class MirrorStripeConfiguration(TypedDict):
|
|
42
|
+
stripe: MirrorStripe
|
|
43
|
+
yaw_mrad: float
|
|
44
|
+
lat_mm: float
|
|
45
|
+
|
|
46
|
+
|
|
39
47
|
class MirrorVoltageDemand(StrictEnum):
|
|
40
48
|
N_A = "N/A"
|
|
41
49
|
OK = "OK"
|
|
@@ -173,9 +181,10 @@ class FocusingMirrorWithStripes(FocusingMirror):
|
|
|
173
181
|
|
|
174
182
|
super().__init__(prefix, name, *args, **kwargs)
|
|
175
183
|
|
|
176
|
-
def energy_to_stripe(self, energy_kev) ->
|
|
184
|
+
def energy_to_stripe(self, energy_kev) -> MirrorStripeConfiguration:
|
|
185
|
+
"""Return the stripe, yaw angle and lateral position for the specified energy"""
|
|
177
186
|
# In future, this should be configurable per-mirror
|
|
178
187
|
if energy_kev < 7:
|
|
179
|
-
return MirrorStripe.BARE
|
|
188
|
+
return {"stripe": MirrorStripe.BARE, "yaw_mrad": 6.2, "lat_mm": 0.0}
|
|
180
189
|
else:
|
|
181
|
-
return MirrorStripe.RHODIUM
|
|
190
|
+
return {"stripe": MirrorStripe.RHODIUM, "yaw_mrad": 0.0, "lat_mm": 10.0}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from ophyd_async.core import StandardReadable
|
|
2
|
+
from ophyd_async.epics.core import epics_signal_rw
|
|
3
|
+
from ophyd_async.epics.motor import Motor
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PiezoMirror(StandardReadable):
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
prefix: str,
|
|
10
|
+
name: str = "",
|
|
11
|
+
):
|
|
12
|
+
with self.add_children_as_readables():
|
|
13
|
+
self.x = Motor(prefix + "X")
|
|
14
|
+
self.y = Motor(prefix + "Y")
|
|
15
|
+
self.z = Motor(prefix + "Z")
|
|
16
|
+
self.yaw = Motor(prefix + "YAW")
|
|
17
|
+
self.pitch = Motor(prefix + "PITCH")
|
|
18
|
+
self.roll = Motor(prefix + "ROLL")
|
|
19
|
+
self.fine_pitch = epics_signal_rw(
|
|
20
|
+
float,
|
|
21
|
+
read_pv=prefix + "FPITCH:RBV:AI",
|
|
22
|
+
write_pv=prefix + "FPITCH:DMD:AO",
|
|
23
|
+
)
|
|
24
|
+
super().__init__(name=name)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from ophyd_async.epics.motor import Motor
|
|
2
|
+
|
|
3
|
+
from dodal.devices.slits import Slits
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class I10Slits(Slits):
|
|
7
|
+
def __init__(self, prefix: str, name: str = "") -> None:
|
|
8
|
+
with self.add_children_as_readables():
|
|
9
|
+
self.x_ring_blade = Motor(prefix + "XRING")
|
|
10
|
+
self.x_hall_blade = Motor(prefix + "XHALL")
|
|
11
|
+
self.y_top_blade = Motor(prefix + "YPLUS")
|
|
12
|
+
self.y_bot_blade = Motor(prefix + "YMINUS")
|
|
13
|
+
super().__init__(
|
|
14
|
+
prefix=prefix,
|
|
15
|
+
x_gap="XSIZE",
|
|
16
|
+
x_centre="XCENTRE",
|
|
17
|
+
y_gap="YSIZE",
|
|
18
|
+
y_centre="YCENTRE",
|
|
19
|
+
name=name,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class I10PrimarySlits(Slits):
|
|
24
|
+
def __init__(self, prefix: str, name: str = "") -> None:
|
|
25
|
+
with self.add_children_as_readables():
|
|
26
|
+
self.x_aptr_1 = Motor(prefix + "APTR1:X")
|
|
27
|
+
self.x_aptr_2 = Motor(prefix + "APTR2:X")
|
|
28
|
+
self.y_aptr_1 = Motor(prefix + "APTR1:Y")
|
|
29
|
+
self.y_aptr_1 = Motor(prefix + "APTR2:Y")
|
|
30
|
+
super().__init__(
|
|
31
|
+
prefix=prefix,
|
|
32
|
+
x_gap="XSIZE",
|
|
33
|
+
x_centre="XCENTRE",
|
|
34
|
+
y_gap="YSIZE",
|
|
35
|
+
y_centre="YCENTRE",
|
|
36
|
+
name=name,
|
|
37
|
+
)
|
|
@@ -5,21 +5,21 @@ from dodal.common.signal_utils import create_hardware_backed_soft_signal
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class HFocusMode(StrictEnum):
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
FOCUS_10 = "HMFMfocus10"
|
|
9
|
+
FOCUS_20D = "HMFMfocus20d"
|
|
10
|
+
FOCUS_30D = "HMFMfocus30d"
|
|
11
|
+
FOCUS_50D = "HMFMfocus50d"
|
|
12
|
+
FOCUS_1050D = "HMFMfocus1030d"
|
|
13
|
+
FOCUS_3010D = "HMFMfocus3010d"
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class VFocusMode(StrictEnum):
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
FOCUS_10 = "VMFMfocus10"
|
|
18
|
+
FOCUS_20D = "VMFMfocus20d"
|
|
19
|
+
FOCUS_30D = "VMFMfocus30d"
|
|
20
|
+
FOCUS_50D = "VMFMfocus50d"
|
|
21
|
+
FOCUS_1030D = "VMFMfocus1030d"
|
|
22
|
+
FOCUS_3010D = "VMFMfocus3010d"
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
BEAM_SIZES = {
|
dodal/devices/linkam3.py
CHANGED
|
@@ -61,7 +61,7 @@ class PinTipDetection(StandardReadable):
|
|
|
61
61
|
self.triggered_bottom_edge, self._bottom_edge_setter = soft_signal_r_and_setter(
|
|
62
62
|
Array1D[np.int32], name="triggered_bottom_edge"
|
|
63
63
|
)
|
|
64
|
-
self.array_data = epics_signal_r(
|
|
64
|
+
self.array_data = epics_signal_r(np.ndarray, f"pva://{prefix}PVA:ARRAY")
|
|
65
65
|
|
|
66
66
|
# Soft parameters for pin-tip detection.
|
|
67
67
|
self.preprocess_operation = soft_signal_rw(int, 10, name="preprocess")
|
|
@@ -99,9 +99,7 @@ class PinTipDetection(StandardReadable):
|
|
|
99
99
|
self._top_edge_setter(results.edge_top)
|
|
100
100
|
self._bottom_edge_setter(results.edge_bottom)
|
|
101
101
|
|
|
102
|
-
async def _get_tip_and_edge_data(
|
|
103
|
-
self, array_data: Array1D[np.uint8]
|
|
104
|
-
) -> SampleLocation:
|
|
102
|
+
async def _get_tip_and_edge_data(self, array_data: np.ndarray) -> SampleLocation:
|
|
105
103
|
"""
|
|
106
104
|
Gets the location of the pin tip and the top and bottom edges.
|
|
107
105
|
"""
|
|
@@ -12,22 +12,22 @@ class SampleAngleStage(StandardReadable):
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class p99StageSelections(SubsetEnum):
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
15
|
+
EMPTY = "Empty"
|
|
16
|
+
MN5UM = "Mn 5um"
|
|
17
|
+
FE = "Fe (empty)"
|
|
18
|
+
CO5UM = "Co 5um"
|
|
19
|
+
NI5UM = "Ni 5um"
|
|
20
|
+
CU5UM = "Cu 5um"
|
|
21
|
+
ZN5UM = "Zn 5um"
|
|
22
|
+
ZR = "Zr (empty)"
|
|
23
|
+
MO = "Mo (empty)"
|
|
24
|
+
RH = "Rh (empty)"
|
|
25
|
+
PD = "Pd (empty)"
|
|
26
|
+
AG = "Ag (empty)"
|
|
27
|
+
CD25UM = "Cd 25um"
|
|
28
28
|
W = "W (empty)"
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
PT = "Pt (empty)"
|
|
30
|
+
USER = "User"
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
class FilterMotor(StandardReadable):
|