dls-dodal 1.39.0__py3-none-any.whl → 1.41.0__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.39.0.dist-info → dls_dodal-1.41.0.dist-info}/METADATA +5 -3
- {dls_dodal-1.39.0.dist-info → dls_dodal-1.41.0.dist-info}/RECORD +61 -52
- {dls_dodal-1.39.0.dist-info → dls_dodal-1.41.0.dist-info}/WHEEL +1 -1
- dodal/_version.py +9 -4
- dodal/beamlines/__init__.py +2 -0
- dodal/beamlines/adsim.py +3 -2
- dodal/beamlines/b01_1.py +3 -3
- dodal/beamlines/i03.py +141 -292
- dodal/beamlines/i04.py +112 -198
- dodal/beamlines/i13_1.py +5 -4
- dodal/beamlines/i18.py +124 -0
- dodal/beamlines/i19_1.py +74 -0
- dodal/beamlines/i19_2.py +61 -0
- dodal/beamlines/i20_1.py +37 -22
- dodal/beamlines/i22.py +7 -7
- dodal/beamlines/i23.py +8 -11
- dodal/beamlines/i24.py +100 -145
- dodal/beamlines/p38.py +84 -220
- dodal/beamlines/p45.py +5 -4
- dodal/beamlines/training_rig.py +4 -4
- dodal/common/beamlines/beamline_utils.py +2 -3
- dodal/common/beamlines/device_helpers.py +3 -1
- dodal/devices/aperturescatterguard.py +150 -64
- dodal/devices/apple2_undulator.py +89 -114
- dodal/devices/attenuator/attenuator.py +1 -1
- dodal/devices/backlight.py +1 -1
- dodal/devices/bimorph_mirror.py +2 -2
- dodal/devices/eiger.py +3 -2
- dodal/devices/fast_grid_scan.py +26 -19
- dodal/devices/hutch_shutter.py +26 -13
- dodal/devices/i10/i10_apple2.py +3 -3
- dodal/devices/i10/rasor/rasor_scaler_cards.py +4 -4
- dodal/devices/i13_1/merlin.py +4 -3
- dodal/devices/i13_1/merlin_controller.py +2 -7
- dodal/devices/i18/KBMirror.py +19 -0
- dodal/devices/i18/diode.py +17 -0
- dodal/devices/i18/table.py +14 -0
- dodal/devices/i18/thor_labs_stage.py +12 -0
- dodal/devices/i19/__init__.py +0 -0
- dodal/devices/i19/shutter.py +57 -0
- dodal/devices/i22/nxsas.py +4 -4
- dodal/devices/i24/pmac.py +2 -2
- dodal/devices/motors.py +2 -2
- dodal/devices/oav/oav_detector.py +10 -19
- dodal/devices/pressure_jump_cell.py +43 -19
- dodal/devices/robot.py +31 -12
- dodal/devices/tetramm.py +8 -3
- dodal/devices/thawer.py +4 -4
- dodal/devices/turbo_slit.py +7 -6
- dodal/devices/undulator.py +1 -1
- dodal/devices/undulator_dcm.py +1 -1
- dodal/devices/util/epics_util.py +1 -1
- dodal/devices/zebra/zebra.py +4 -3
- dodal/devices/zebra/zebra_controlled_shutter.py +1 -1
- dodal/devices/zocalo/zocalo_results.py +21 -4
- dodal/plan_stubs/wrapped.py +10 -12
- dodal/plans/save_panda.py +30 -14
- dodal/utils.py +55 -21
- {dls_dodal-1.39.0.dist-info → dls_dodal-1.41.0.dist-info}/LICENSE +0 -0
- {dls_dodal-1.39.0.dist-info → dls_dodal-1.41.0.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.39.0.dist-info → dls_dodal-1.41.0.dist-info}/top_level.txt +0 -0
dodal/devices/fast_grid_scan.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
from abc import ABC, abstractmethod
|
|
2
3
|
from typing import Generic, TypeVar
|
|
3
4
|
|
|
@@ -9,9 +10,7 @@ from ophyd_async.core import (
|
|
|
9
10
|
AsyncStatus,
|
|
10
11
|
Device,
|
|
11
12
|
Signal,
|
|
12
|
-
SignalR,
|
|
13
13
|
SignalRW,
|
|
14
|
-
SoftSignalBackend,
|
|
15
14
|
StandardReadable,
|
|
16
15
|
wait_for_value,
|
|
17
16
|
)
|
|
@@ -24,6 +23,7 @@ from ophyd_async.epics.core import (
|
|
|
24
23
|
from pydantic import field_validator
|
|
25
24
|
from pydantic.dataclasses import dataclass
|
|
26
25
|
|
|
26
|
+
from dodal.common.signal_utils import create_hardware_backed_soft_signal
|
|
27
27
|
from dodal.log import LOGGER
|
|
28
28
|
from dodal.parameters.experiment_parameter_base import AbstractExperimentWithBeamParams
|
|
29
29
|
|
|
@@ -170,21 +170,6 @@ class MotionProgram(Device):
|
|
|
170
170
|
self.program_number = epics_signal_r(float, prefix + "CS1:PROG_NUM")
|
|
171
171
|
|
|
172
172
|
|
|
173
|
-
class ExpectedImages(SignalR[int]):
|
|
174
|
-
def __init__(self, parent: "FastGridScanCommon") -> None:
|
|
175
|
-
super().__init__(SoftSignalBackend(int))
|
|
176
|
-
self.parent = parent
|
|
177
|
-
|
|
178
|
-
async def get_value(self, cached: bool | None = None):
|
|
179
|
-
assert isinstance(self.parent, FastGridScanCommon)
|
|
180
|
-
x = await self.parent.x_steps.get_value()
|
|
181
|
-
y = await self.parent.y_steps.get_value()
|
|
182
|
-
z = await self.parent.z_steps.get_value()
|
|
183
|
-
first_grid = x * y
|
|
184
|
-
second_grid = x * z
|
|
185
|
-
return first_grid + second_grid
|
|
186
|
-
|
|
187
|
-
|
|
188
173
|
class FastGridScanCommon(StandardReadable, Flyable, ABC, Generic[ParamType]):
|
|
189
174
|
"""Device for a general fast grid scan
|
|
190
175
|
|
|
@@ -215,9 +200,12 @@ class FastGridScanCommon(StandardReadable, Flyable, ABC, Generic[ParamType]):
|
|
|
215
200
|
self.scan_invalid = epics_signal_r(float, f"{prefix}SCAN_INVALID")
|
|
216
201
|
|
|
217
202
|
self.run_cmd = epics_signal_x(f"{prefix}RUN.PROC")
|
|
203
|
+
self.stop_cmd = epics_signal_x(f"{prefix}STOP.PROC")
|
|
218
204
|
self.status = epics_signal_r(int, f"{prefix}SCAN_STATUS")
|
|
219
205
|
|
|
220
|
-
self.expected_images =
|
|
206
|
+
self.expected_images = create_hardware_backed_soft_signal(
|
|
207
|
+
float, self._calculate_expected_images
|
|
208
|
+
)
|
|
221
209
|
|
|
222
210
|
self.motion_program = MotionProgram(smargon_prefix)
|
|
223
211
|
|
|
@@ -243,6 +231,17 @@ class FastGridScanCommon(StandardReadable, Flyable, ABC, Generic[ParamType]):
|
|
|
243
231
|
}
|
|
244
232
|
super().__init__(name)
|
|
245
233
|
|
|
234
|
+
async def _calculate_expected_images(self):
|
|
235
|
+
x, y, z = await asyncio.gather(
|
|
236
|
+
self.x_steps.get_value(),
|
|
237
|
+
self.y_steps.get_value(),
|
|
238
|
+
self.z_steps.get_value(),
|
|
239
|
+
)
|
|
240
|
+
LOGGER.info(f"Reading num of images found {x, y, z} images in each axis")
|
|
241
|
+
first_grid = x * y
|
|
242
|
+
second_grid = x * z
|
|
243
|
+
return first_grid + second_grid
|
|
244
|
+
|
|
246
245
|
@AsyncStatus.wrap
|
|
247
246
|
async def kickoff(self):
|
|
248
247
|
curr_prog = await self.motion_program.program_number.get_value()
|
|
@@ -259,7 +258,15 @@ class FastGridScanCommon(StandardReadable, Flyable, ABC, Generic[ParamType]):
|
|
|
259
258
|
|
|
260
259
|
@AsyncStatus.wrap
|
|
261
260
|
async def complete(self):
|
|
262
|
-
|
|
261
|
+
try:
|
|
262
|
+
await wait_for_value(self.status, 0, self.COMPLETE_STATUS)
|
|
263
|
+
except asyncio.TimeoutError:
|
|
264
|
+
LOGGER.error(
|
|
265
|
+
"Hyperion timed out waiting for FGS motion to complete. This may have been caused by a goniometer stage getting stuck.\n\
|
|
266
|
+
Forcibly stopping the FGS motion program..."
|
|
267
|
+
)
|
|
268
|
+
await self.stop_cmd.trigger()
|
|
269
|
+
raise
|
|
263
270
|
|
|
264
271
|
@abstractmethod
|
|
265
272
|
def _create_position_counter(self, prefix: str) -> SignalRW[int]:
|
dodal/devices/hutch_shutter.py
CHANGED
|
@@ -8,9 +8,16 @@ from ophyd_async.core import (
|
|
|
8
8
|
)
|
|
9
9
|
from ophyd_async.epics.core import epics_signal_r, epics_signal_w
|
|
10
10
|
|
|
11
|
+
from dodal.log import LOGGER
|
|
12
|
+
|
|
11
13
|
HUTCH_SAFE_FOR_OPERATIONS = 0 # Hutch is locked and can't be entered
|
|
12
14
|
|
|
13
15
|
|
|
16
|
+
# Enable to allow testing when the beamline is down, do not change in production!
|
|
17
|
+
TEST_MODE = False
|
|
18
|
+
# will be made more generic in https://github.com/DiamondLightSource/dodal/issues/754
|
|
19
|
+
|
|
20
|
+
|
|
14
21
|
class ShutterNotSafeToOperateError(Exception):
|
|
15
22
|
pass
|
|
16
23
|
|
|
@@ -48,7 +55,7 @@ class HutchInterlock(StandardReadable):
|
|
|
48
55
|
return interlock_state == HUTCH_SAFE_FOR_OPERATIONS
|
|
49
56
|
|
|
50
57
|
|
|
51
|
-
class HutchShutter(StandardReadable, Movable):
|
|
58
|
+
class HutchShutter(StandardReadable, Movable[ShutterDemand]):
|
|
52
59
|
"""Device to operate the hutch shutter.
|
|
53
60
|
|
|
54
61
|
When a demand is sent, the device should first check the hutch status \
|
|
@@ -64,8 +71,8 @@ class HutchShutter(StandardReadable, Movable):
|
|
|
64
71
|
"""
|
|
65
72
|
|
|
66
73
|
def __init__(self, prefix: str, name: str = "") -> None:
|
|
67
|
-
self.control = epics_signal_w(ShutterDemand, prefix
|
|
68
|
-
self.status = epics_signal_r(ShutterState, prefix
|
|
74
|
+
self.control = epics_signal_w(ShutterDemand, f"{prefix}CON")
|
|
75
|
+
self.status = epics_signal_r(ShutterState, f"{prefix}STA")
|
|
69
76
|
|
|
70
77
|
bl_prefix = prefix.split("-")[0]
|
|
71
78
|
self.interlock = HutchInterlock(bl_prefix)
|
|
@@ -75,18 +82,24 @@ class HutchShutter(StandardReadable, Movable):
|
|
|
75
82
|
@AsyncStatus.wrap
|
|
76
83
|
async def set(self, value: ShutterDemand):
|
|
77
84
|
interlock_state = await self.interlock.shutter_safe_to_operate()
|
|
78
|
-
if not interlock_state:
|
|
85
|
+
if not interlock_state and not TEST_MODE:
|
|
86
|
+
# If not in test mode, fail. If in test mode, the optics hutch may be open.
|
|
79
87
|
raise ShutterNotSafeToOperateError(
|
|
80
88
|
"The hutch has not been locked, not operating shutter."
|
|
81
89
|
)
|
|
82
|
-
if
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
if not TEST_MODE:
|
|
91
|
+
if value == ShutterDemand.OPEN:
|
|
92
|
+
await self.control.set(ShutterDemand.RESET, wait=True)
|
|
93
|
+
await self.control.set(value, wait=True)
|
|
94
|
+
return await wait_for_value(
|
|
95
|
+
self.status, match=ShutterState.OPEN, timeout=DEFAULT_TIMEOUT
|
|
96
|
+
)
|
|
97
|
+
else:
|
|
98
|
+
await self.control.set(value, wait=True)
|
|
99
|
+
return await wait_for_value(
|
|
100
|
+
self.status, match=ShutterState.CLOSED, timeout=DEFAULT_TIMEOUT
|
|
101
|
+
)
|
|
88
102
|
else:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
self.status, match=ShutterState.CLOSED, timeout=DEFAULT_TIMEOUT
|
|
103
|
+
LOGGER.warning(
|
|
104
|
+
"Running in test mode, will not operate the experiment shutter."
|
|
92
105
|
)
|
dodal/devices/i10/i10_apple2.py
CHANGED
|
@@ -175,7 +175,7 @@ class I10Apple2(Apple2):
|
|
|
175
175
|
self._available_pol = list(self.lookup_tables["Gap"].keys())
|
|
176
176
|
|
|
177
177
|
|
|
178
|
-
class I10Apple2PGM(StandardReadable, Movable):
|
|
178
|
+
class I10Apple2PGM(StandardReadable, Movable[float]):
|
|
179
179
|
"""
|
|
180
180
|
Compound device to set both ID and PGM energy at the sample time,poly_deg
|
|
181
181
|
|
|
@@ -211,7 +211,7 @@ class I10Apple2PGM(StandardReadable, Movable):
|
|
|
211
211
|
)
|
|
212
212
|
|
|
213
213
|
|
|
214
|
-
class I10Apple2Pol(StandardReadable, Movable):
|
|
214
|
+
class I10Apple2Pol(StandardReadable, Movable[str]):
|
|
215
215
|
"""
|
|
216
216
|
Compound device to set polorisation of ID.
|
|
217
217
|
"""
|
|
@@ -240,7 +240,7 @@ class I10Apple2Pol(StandardReadable, Movable):
|
|
|
240
240
|
) # Move id to new polarisation
|
|
241
241
|
|
|
242
242
|
|
|
243
|
-
class LinearArbitraryAngle(StandardReadable, Movable):
|
|
243
|
+
class LinearArbitraryAngle(StandardReadable, Movable[SupportsFloat]):
|
|
244
244
|
"""
|
|
245
245
|
Device to set polorisation angle of the ID. Linear Arbitrary Angle (laa)
|
|
246
246
|
is the direction of the magnetic field which can be change by varying the jaw_phase
|
|
@@ -5,8 +5,8 @@ from dodal.devices.current_amplifiers import StruckScaler
|
|
|
5
5
|
|
|
6
6
|
class RasorScalerCard1(Device):
|
|
7
7
|
def __init__(self, prefix, name: str = "") -> None:
|
|
8
|
-
self.mon = StruckScaler(prefix=prefix, suffix=".
|
|
9
|
-
self.det = StruckScaler(prefix=prefix, suffix=".
|
|
10
|
-
self.fluo = StruckScaler(prefix=prefix, suffix=".
|
|
11
|
-
self.drain = StruckScaler(prefix=prefix, suffix=".
|
|
8
|
+
self.mon = StruckScaler(prefix=prefix, suffix=".S17")
|
|
9
|
+
self.det = StruckScaler(prefix=prefix, suffix=".S18")
|
|
10
|
+
self.fluo = StruckScaler(prefix=prefix, suffix=".S19")
|
|
11
|
+
self.drain = StruckScaler(prefix=prefix, suffix=".S20")
|
|
12
12
|
super().__init__(name)
|
dodal/devices/i13_1/merlin.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from ophyd_async.core import PathProvider, StandardDetector
|
|
2
2
|
from ophyd_async.epics import adcore
|
|
3
3
|
|
|
4
|
+
from dodal.common.beamlines.device_helpers import CAM_SUFFIX, HDF5_SUFFIX
|
|
4
5
|
from dodal.devices.i13_1.merlin_controller import MerlinController
|
|
5
6
|
from dodal.devices.i13_1.merlin_io import MerlinDriverIO
|
|
6
7
|
|
|
@@ -13,12 +14,12 @@ class Merlin(StandardDetector):
|
|
|
13
14
|
self,
|
|
14
15
|
prefix: str,
|
|
15
16
|
path_provider: PathProvider,
|
|
16
|
-
drv_suffix=
|
|
17
|
-
|
|
17
|
+
drv_suffix=CAM_SUFFIX,
|
|
18
|
+
fileio_suffix=HDF5_SUFFIX,
|
|
18
19
|
name: str = "",
|
|
19
20
|
):
|
|
20
21
|
self.drv = MerlinDriverIO(prefix + drv_suffix)
|
|
21
|
-
self.hdf = adcore.NDFileHDFIO(prefix +
|
|
22
|
+
self.hdf = adcore.NDFileHDFIO(prefix + fileio_suffix)
|
|
22
23
|
|
|
23
24
|
super().__init__(
|
|
24
25
|
MerlinController(self.drv),
|
|
@@ -4,15 +4,15 @@ import logging
|
|
|
4
4
|
from ophyd_async.core import (
|
|
5
5
|
DEFAULT_TIMEOUT,
|
|
6
6
|
AsyncStatus,
|
|
7
|
-
DetectorController,
|
|
8
7
|
TriggerInfo,
|
|
9
8
|
)
|
|
10
9
|
from ophyd_async.epics import adcore
|
|
10
|
+
from ophyd_async.epics.adcore import ADBaseController
|
|
11
11
|
|
|
12
12
|
from dodal.devices.i13_1.merlin_io import MerlinDriverIO, MerlinImageMode
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
class MerlinController(
|
|
15
|
+
class MerlinController(ADBaseController):
|
|
16
16
|
def __init__(
|
|
17
17
|
self,
|
|
18
18
|
driver: MerlinDriverIO,
|
|
@@ -37,11 +37,6 @@ class MerlinController(DetectorController):
|
|
|
37
37
|
self.driver.image_mode.set(MerlinImageMode.MULTIPLE),
|
|
38
38
|
)
|
|
39
39
|
|
|
40
|
-
async def arm(self):
|
|
41
|
-
self._arm_status = await adcore.start_acquiring_driver_and_ensure_status(
|
|
42
|
-
self.driver, good_states=self.good_states, timeout=self.frame_timeout
|
|
43
|
-
)
|
|
44
|
-
|
|
45
40
|
async def wait_for_idle(self):
|
|
46
41
|
if self._arm_status:
|
|
47
42
|
await self._arm_status
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from ophyd_async.core import StandardReadable
|
|
2
|
+
from ophyd_async.epics.core import epics_signal_rw
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class KBMirror(StandardReadable):
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
prefix: str,
|
|
9
|
+
name: str = "",
|
|
10
|
+
):
|
|
11
|
+
self._prefix = prefix
|
|
12
|
+
with self.add_children_as_readables():
|
|
13
|
+
self.x = epics_signal_rw(float, prefix + "X")
|
|
14
|
+
self.y = epics_signal_rw(float, prefix + "Y")
|
|
15
|
+
self.bend1 = epics_signal_rw(float, prefix + "BEND1")
|
|
16
|
+
self.bend2 = epics_signal_rw(float, prefix + "BEND2")
|
|
17
|
+
self.curve = epics_signal_rw(float, prefix + "CURVE")
|
|
18
|
+
self.ellip = epics_signal_rw(float, prefix + "ELLIP")
|
|
19
|
+
super().__init__(name=name)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from ophyd_async.core import (
|
|
2
|
+
StandardReadable,
|
|
3
|
+
)
|
|
4
|
+
from ophyd_async.epics.core import epics_signal_r
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Diode(StandardReadable):
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
prefix: str,
|
|
11
|
+
name: str = "",
|
|
12
|
+
):
|
|
13
|
+
self._prefix = prefix
|
|
14
|
+
with self.add_children_as_readables():
|
|
15
|
+
self.signal = epics_signal_r(float, prefix + "B:DIODE:I")
|
|
16
|
+
|
|
17
|
+
super().__init__(name=name)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from ophyd_async.core import (
|
|
2
|
+
StandardReadable,
|
|
3
|
+
)
|
|
4
|
+
from ophyd_async.epics.motor import Motor
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Table(StandardReadable):
|
|
8
|
+
def __init__(self, prefix: str, name: str = "") -> None:
|
|
9
|
+
with self.add_children_as_readables():
|
|
10
|
+
self.x = Motor(prefix + "X")
|
|
11
|
+
self.y = Motor(prefix + "Y")
|
|
12
|
+
self.z = Motor(prefix + "Z")
|
|
13
|
+
self.theta = Motor(prefix + "THETA")
|
|
14
|
+
super().__init__(name=name)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from ophyd_async.core import (
|
|
2
|
+
StandardReadable,
|
|
3
|
+
)
|
|
4
|
+
from ophyd_async.epics.motor import Motor
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ThorLabsStage(StandardReadable):
|
|
8
|
+
def __init__(self, prefix: str, name: str = "") -> None:
|
|
9
|
+
with self.add_children_as_readables():
|
|
10
|
+
self.x = Motor(prefix + "X")
|
|
11
|
+
self.y = Motor(prefix + "Y")
|
|
12
|
+
super().__init__(name=name)
|
|
File without changes
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
from bluesky.protocols import Movable
|
|
4
|
+
from ophyd_async.core import AsyncStatus, StandardReadable
|
|
5
|
+
from ophyd_async.epics.core import epics_signal_r
|
|
6
|
+
|
|
7
|
+
from dodal.devices.hutch_shutter import HutchShutter, ShutterDemand
|
|
8
|
+
from dodal.log import LOGGER
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class HutchInvalidError(Exception):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class HutchState(str, Enum):
|
|
16
|
+
EH1 = "EH1"
|
|
17
|
+
EH2 = "EH2"
|
|
18
|
+
INVALID = "INVALID"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class HutchConditionalShutter(StandardReadable, Movable[ShutterDemand]):
|
|
22
|
+
""" I19-specific device to operate the hutch shutter.
|
|
23
|
+
|
|
24
|
+
This device evaluates the hutch state value to work out which of the two I19 \
|
|
25
|
+
hutches is in use and then implements the HutchShutter device to operate the \
|
|
26
|
+
experimental shutter.
|
|
27
|
+
As the two hutches are located in series, checking the hutch in use is necessary to \
|
|
28
|
+
avoid accidentally operating the shutter from one hutch while the other has beamtime.
|
|
29
|
+
|
|
30
|
+
The hutch name should be passed to the device upon instantiation. If this does not \
|
|
31
|
+
coincide with the current hutch in use, a warning will be logged and the shutter \
|
|
32
|
+
will not be operated. This is to allow for testing of plans.
|
|
33
|
+
An error will instead be raised if the hutch state reads as "INVALID".
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, prefix: str, hutch: HutchState, name: str = "") -> None:
|
|
37
|
+
self.shutter = HutchShutter(prefix=prefix, name=name)
|
|
38
|
+
bl_prefix = prefix.split("-")[0]
|
|
39
|
+
self.hutch_state = epics_signal_r(str, f"{bl_prefix}-OP-STAT-01:EHStatus.VALA")
|
|
40
|
+
self.hutch_request = hutch
|
|
41
|
+
super().__init__(name)
|
|
42
|
+
|
|
43
|
+
@AsyncStatus.wrap
|
|
44
|
+
async def set(self, value: ShutterDemand):
|
|
45
|
+
hutch_in_use = await self.hutch_state.get_value()
|
|
46
|
+
LOGGER.info(f"Current hutch in use: {hutch_in_use}")
|
|
47
|
+
if hutch_in_use == HutchState.INVALID:
|
|
48
|
+
raise HutchInvalidError(
|
|
49
|
+
"The hutch state is invalid. Contact the beamline staff."
|
|
50
|
+
)
|
|
51
|
+
if hutch_in_use != self.hutch_request:
|
|
52
|
+
# NOTE Warn but don't fail
|
|
53
|
+
LOGGER.warning(
|
|
54
|
+
f"{self.hutch_request} is not the hutch in use. Shutter will not be operated."
|
|
55
|
+
)
|
|
56
|
+
else:
|
|
57
|
+
await self.shutter.set(value)
|
dodal/devices/i22/nxsas.py
CHANGED
|
@@ -103,7 +103,7 @@ class NXSasPilatus(PilatusDetector):
|
|
|
103
103
|
prefix: str,
|
|
104
104
|
path_provider: PathProvider,
|
|
105
105
|
drv_suffix: str,
|
|
106
|
-
|
|
106
|
+
fileio_suffix: str,
|
|
107
107
|
metadata_holder: NXSasMetadataHolder,
|
|
108
108
|
name: str = "",
|
|
109
109
|
):
|
|
@@ -116,7 +116,7 @@ class NXSasPilatus(PilatusDetector):
|
|
|
116
116
|
prefix,
|
|
117
117
|
path_provider,
|
|
118
118
|
drv_suffix=drv_suffix,
|
|
119
|
-
|
|
119
|
+
fileio_suffix=fileio_suffix,
|
|
120
120
|
name=name,
|
|
121
121
|
)
|
|
122
122
|
self._metadata_holder = metadata_holder
|
|
@@ -146,7 +146,7 @@ class NXSasOAV(AravisDetector):
|
|
|
146
146
|
prefix: str,
|
|
147
147
|
path_provider: PathProvider,
|
|
148
148
|
drv_suffix: str,
|
|
149
|
-
|
|
149
|
+
fileio_suffix: str,
|
|
150
150
|
metadata_holder: NXSasMetadataHolder,
|
|
151
151
|
name: str = "",
|
|
152
152
|
gpio_number: AravisController.GPIO_NUMBER = 1,
|
|
@@ -160,7 +160,7 @@ class NXSasOAV(AravisDetector):
|
|
|
160
160
|
prefix,
|
|
161
161
|
path_provider,
|
|
162
162
|
drv_suffix=drv_suffix,
|
|
163
|
-
|
|
163
|
+
fileio_suffix=fileio_suffix,
|
|
164
164
|
name=name,
|
|
165
165
|
gpio_number=gpio_number,
|
|
166
166
|
)
|
dodal/devices/i24/pmac.py
CHANGED
|
@@ -71,7 +71,7 @@ class PMACStringMove(Triggerable):
|
|
|
71
71
|
await self.signal_ref().set(self.cmd_string, wait=True)
|
|
72
72
|
|
|
73
73
|
|
|
74
|
-
class PMACStringLaser(Device, Movable):
|
|
74
|
+
class PMACStringLaser(Device, Movable[LaserSettings]):
|
|
75
75
|
"""Set the pmac_string to control the laser."""
|
|
76
76
|
|
|
77
77
|
def __init__(
|
|
@@ -90,7 +90,7 @@ class PMACStringLaser(Device, Movable):
|
|
|
90
90
|
await self._signal_ref().set(value.value)
|
|
91
91
|
|
|
92
92
|
|
|
93
|
-
class PMACStringEncReset(Device, Movable):
|
|
93
|
+
class PMACStringEncReset(Device, Movable[EncReset]):
|
|
94
94
|
"""Set a pmac_string to control the encoder channels in the controller."""
|
|
95
95
|
|
|
96
96
|
def __init__(
|
dodal/devices/motors.py
CHANGED
|
@@ -19,10 +19,10 @@ class XYZPositioner(StandardReadable):
|
|
|
19
19
|
Notes
|
|
20
20
|
-----
|
|
21
21
|
Example usage::
|
|
22
|
-
async with
|
|
22
|
+
async with init_devices():
|
|
23
23
|
xyz_stage = XYZPositioner("BLXX-MO-STAGE-XX:")
|
|
24
24
|
Or::
|
|
25
|
-
with
|
|
25
|
+
with init_devices():
|
|
26
26
|
xyz_stage = XYZPositioner("BLXX-MO-STAGE-XX:", infix = ("A", "B", "C"))
|
|
27
27
|
|
|
28
28
|
"""
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
from enum import IntEnum
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from bluesky.protocols import Movable
|
|
4
|
+
from ophyd_async.core import (
|
|
5
|
+
DEFAULT_TIMEOUT,
|
|
6
|
+
AsyncStatus,
|
|
7
|
+
LazyMock,
|
|
8
|
+
StandardReadable,
|
|
9
|
+
)
|
|
4
10
|
from ophyd_async.epics.core import epics_signal_rw
|
|
5
11
|
|
|
6
12
|
from dodal.common.signal_utils import create_hardware_backed_soft_signal
|
|
@@ -8,12 +14,6 @@ from dodal.devices.areadetector.plugins.CAM import Cam
|
|
|
8
14
|
from dodal.devices.oav.oav_parameters import DEFAULT_OAV_WINDOW, OAVConfig
|
|
9
15
|
from dodal.devices.oav.snapshots.snapshot_with_beam_centre import SnapshotWithBeamCentre
|
|
10
16
|
from dodal.devices.oav.snapshots.snapshot_with_grid import SnapshotWithGrid
|
|
11
|
-
from dodal.log import LOGGER
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class ZoomLevelNotFoundError(Exception):
|
|
15
|
-
def __init__(self, errmsg):
|
|
16
|
-
LOGGER.error(errmsg)
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class Coords(IntEnum):
|
|
@@ -29,7 +29,7 @@ def _get_correct_zoom_string(zoom: str) -> str:
|
|
|
29
29
|
return zoom
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
class ZoomController(StandardReadable):
|
|
32
|
+
class ZoomController(StandardReadable, Movable[str]):
|
|
33
33
|
"""
|
|
34
34
|
Device to control the zoom level. This should be set like
|
|
35
35
|
o = OAV(name="oav")
|
|
@@ -46,18 +46,9 @@ class ZoomController(StandardReadable):
|
|
|
46
46
|
self.level = epics_signal_rw(str, f"{prefix}MP:SELECT")
|
|
47
47
|
super().__init__(name=name)
|
|
48
48
|
|
|
49
|
-
async def _get_allowed_zoom_levels(self) -> list:
|
|
50
|
-
zoom_levels = await self.level.describe()
|
|
51
|
-
return zoom_levels[self.level.name]["choices"] # type: ignore
|
|
52
|
-
|
|
53
49
|
@AsyncStatus.wrap
|
|
54
|
-
async def set(self,
|
|
55
|
-
|
|
56
|
-
if level_to_set not in allowed_zoom_levels:
|
|
57
|
-
raise ZoomLevelNotFoundError(
|
|
58
|
-
f"{level_to_set} not found, expected one of {allowed_zoom_levels}"
|
|
59
|
-
)
|
|
60
|
-
await self.level.set(level_to_set, wait=True)
|
|
50
|
+
async def set(self, value: str):
|
|
51
|
+
await self.level.set(value, wait=True)
|
|
61
52
|
|
|
62
53
|
|
|
63
54
|
class OAV(StandardReadable):
|
|
@@ -41,8 +41,8 @@ class ValveControlRequest(StrictEnum):
|
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
class ValveOpenSeqRequest(StrictEnum):
|
|
44
|
-
INACTIVE = 0
|
|
45
|
-
OPEN_SEQ = 1
|
|
44
|
+
INACTIVE = "0"
|
|
45
|
+
OPEN_SEQ = "1"
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
class PumpMotorDirectionState(StrictEnum):
|
|
@@ -76,7 +76,7 @@ class AllValvesControlState:
|
|
|
76
76
|
valve_6: FastValveControlRequest | None = None
|
|
77
77
|
|
|
78
78
|
|
|
79
|
-
class AllValvesControl(StandardReadable, Movable):
|
|
79
|
+
class AllValvesControl(StandardReadable, Movable[AllValvesControlState]):
|
|
80
80
|
"""
|
|
81
81
|
valves 2, 4, 7, 8 are not controlled by the IOC,
|
|
82
82
|
as they are under manual control.
|
|
@@ -151,11 +151,13 @@ class AllValvesControl(StandardReadable, Movable):
|
|
|
151
151
|
)
|
|
152
152
|
|
|
153
153
|
|
|
154
|
-
class ValveControl(
|
|
154
|
+
class ValveControl(
|
|
155
|
+
StandardReadable, Movable[ValveControlRequest | ValveOpenSeqRequest]
|
|
156
|
+
):
|
|
155
157
|
def __init__(self, prefix: str, name: str = "") -> None:
|
|
156
158
|
with self.add_children_as_readables():
|
|
157
159
|
self.close = epics_signal_rw(ValveControlRequest, prefix + ":CON")
|
|
158
|
-
self.open = epics_signal_rw(
|
|
160
|
+
self.open = epics_signal_rw(int, prefix + ":OPENSEQ")
|
|
159
161
|
|
|
160
162
|
super().__init__(name)
|
|
161
163
|
|
|
@@ -165,16 +167,18 @@ class ValveControl(StandardReadable):
|
|
|
165
167
|
if isinstance(value, ValveControlRequest):
|
|
166
168
|
set_status = self.close.set(value)
|
|
167
169
|
elif isinstance(value, ValveOpenSeqRequest):
|
|
168
|
-
set_status = self.open.set(value)
|
|
170
|
+
set_status = self.open.set(value.value)
|
|
169
171
|
|
|
170
172
|
return set_status
|
|
171
173
|
|
|
172
174
|
|
|
173
|
-
class FastValveControl(
|
|
175
|
+
class FastValveControl(
|
|
176
|
+
StandardReadable, Movable[FastValveControlRequest | ValveOpenSeqRequest]
|
|
177
|
+
):
|
|
174
178
|
def __init__(self, prefix: str, name: str = "") -> None:
|
|
175
179
|
with self.add_children_as_readables():
|
|
176
180
|
self.close = epics_signal_rw(FastValveControlRequest, prefix + ":CON")
|
|
177
|
-
self.open = epics_signal_rw(
|
|
181
|
+
self.open = epics_signal_rw(int, prefix + ":OPENSEQ")
|
|
178
182
|
|
|
179
183
|
super().__init__(name)
|
|
180
184
|
|
|
@@ -184,7 +188,7 @@ class FastValveControl(StandardReadable):
|
|
|
184
188
|
if isinstance(value, FastValveControlRequest):
|
|
185
189
|
set_status = self.close.set(value)
|
|
186
190
|
elif isinstance(value, ValveOpenSeqRequest):
|
|
187
|
-
set_status = self.open.set(value)
|
|
191
|
+
set_status = self.open.set(value.value)
|
|
188
192
|
|
|
189
193
|
return set_status
|
|
190
194
|
|
|
@@ -219,21 +223,27 @@ class PressureTransducer(StandardReadable):
|
|
|
219
223
|
self,
|
|
220
224
|
prefix: str,
|
|
221
225
|
cell_prefix: str,
|
|
222
|
-
|
|
226
|
+
transducer_number: int,
|
|
227
|
+
ethercat_channel_number: int,
|
|
223
228
|
name: str = "",
|
|
224
229
|
full_different_prefix_adc: str = "",
|
|
225
230
|
) -> None:
|
|
226
231
|
final_prefix = f"{prefix}{cell_prefix}"
|
|
227
232
|
with self.add_children_as_readables():
|
|
228
233
|
self.omron_pressure = epics_signal_r(
|
|
229
|
-
float, f"{final_prefix}PP{
|
|
234
|
+
float, f"{final_prefix}PP{transducer_number}:PRES"
|
|
235
|
+
)
|
|
236
|
+
self.omron_voltage = epics_signal_r(
|
|
237
|
+
float, f"{final_prefix}PP{transducer_number}:RAW"
|
|
230
238
|
)
|
|
231
|
-
self.omron_voltage = epics_signal_r(float, f"{final_prefix}PP{number}:RAW")
|
|
232
239
|
self.beckhoff_pressure = epics_signal_r(
|
|
233
|
-
float, f"{final_prefix}STATP{
|
|
240
|
+
float, f"{final_prefix}STATP{transducer_number}:MeanValue_RBV"
|
|
234
241
|
)
|
|
242
|
+
# P1 beckhoff voltage = BL38P-EA-ADC-02:CH1
|
|
243
|
+
# P2 beckhoff voltage = BL38P-EA-ADC-01:CH2
|
|
244
|
+
# P3 beckhoff voltage = BL38P-EA-ADC-01:CH1
|
|
235
245
|
self.slow_beckhoff_voltage_readout = epics_signal_r(
|
|
236
|
-
float, f"{full_different_prefix_adc}
|
|
246
|
+
float, f"{full_different_prefix_adc}CH{ethercat_channel_number}"
|
|
237
247
|
)
|
|
238
248
|
|
|
239
249
|
super().__init__(name)
|
|
@@ -284,13 +294,27 @@ class PressureJumpCell(StandardReadable):
|
|
|
284
294
|
with self.add_children_as_readables():
|
|
285
295
|
self.pressure_transducers: DeviceVector[PressureTransducer] = DeviceVector(
|
|
286
296
|
{
|
|
287
|
-
|
|
297
|
+
1: PressureTransducer(
|
|
298
|
+
prefix=prefix,
|
|
299
|
+
cell_prefix=cell_prefix,
|
|
300
|
+
transducer_number=1,
|
|
301
|
+
full_different_prefix_adc=f"{prefix}{adc_prefix}-02:",
|
|
302
|
+
ethercat_channel_number=1,
|
|
303
|
+
),
|
|
304
|
+
2: PressureTransducer(
|
|
305
|
+
prefix=prefix,
|
|
306
|
+
cell_prefix=cell_prefix,
|
|
307
|
+
transducer_number=2,
|
|
308
|
+
full_different_prefix_adc=f"{prefix}{adc_prefix}-01:",
|
|
309
|
+
ethercat_channel_number=2,
|
|
310
|
+
),
|
|
311
|
+
3: PressureTransducer(
|
|
288
312
|
prefix=prefix,
|
|
289
|
-
number=i,
|
|
290
313
|
cell_prefix=cell_prefix,
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
314
|
+
transducer_number=3,
|
|
315
|
+
full_different_prefix_adc=f"{prefix}{adc_prefix}-01:",
|
|
316
|
+
ethercat_channel_number=1,
|
|
317
|
+
),
|
|
294
318
|
}
|
|
295
319
|
)
|
|
296
320
|
|