dls-dodal 1.55.1__py3-none-any.whl → 1.57.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.55.1.dist-info → dls_dodal-1.57.0.dist-info}/METADATA +3 -3
- {dls_dodal-1.55.1.dist-info → dls_dodal-1.57.0.dist-info}/RECORD +101 -87
- dodal/_version.py +16 -3
- dodal/beamlines/b01_1.py +6 -1
- dodal/beamlines/b07.py +2 -1
- dodal/beamlines/b07_1.py +2 -1
- dodal/beamlines/b21.py +4 -24
- dodal/beamlines/i03.py +53 -53
- dodal/beamlines/i04.py +16 -38
- dodal/beamlines/i09.py +3 -2
- dodal/beamlines/i09_1.py +2 -1
- dodal/beamlines/i11.py +143 -0
- dodal/beamlines/i17.py +37 -0
- dodal/beamlines/i19_1.py +1 -0
- dodal/beamlines/i19_2.py +7 -0
- dodal/beamlines/i22.py +5 -5
- dodal/beamlines/i23.py +3 -3
- dodal/beamlines/i24.py +6 -33
- dodal/beamlines/p38.py +1 -0
- dodal/beamlines/p60.py +3 -2
- dodal/cli.py +11 -1
- dodal/common/__init__.py +4 -0
- dodal/common/beamlines/beamline_parameters.py +1 -1
- dodal/common/beamlines/beamline_utils.py +5 -1
- dodal/common/enums.py +19 -0
- dodal/common/watcher_utils.py +83 -0
- dodal/devices/aithre_lasershaping/laser_robot.py +4 -9
- dodal/devices/aperturescatterguard.py +52 -12
- dodal/devices/apple2_undulator.py +0 -1
- dodal/devices/b16/detector.py +1 -10
- dodal/devices/backlight.py +8 -20
- dodal/devices/bimorph_mirror.py +4 -7
- dodal/devices/collimation_table.py +36 -0
- dodal/devices/controllers.py +21 -0
- dodal/devices/cryostream.py +97 -7
- dodal/devices/current_amplifiers/femto.py +1 -1
- dodal/devices/detector/detector_motion.py +1 -7
- dodal/devices/eiger.py +22 -8
- dodal/devices/eiger_odin.py +2 -0
- dodal/devices/electron_analyser/__init__.py +2 -1
- dodal/devices/electron_analyser/abstract/__init__.py +0 -1
- dodal/devices/electron_analyser/abstract/base_detector.py +3 -25
- dodal/devices/electron_analyser/abstract/base_driver_io.py +18 -9
- dodal/devices/electron_analyser/abstract/base_region.py +34 -3
- dodal/devices/electron_analyser/detector.py +24 -0
- dodal/devices/electron_analyser/enums.py +5 -0
- dodal/devices/electron_analyser/specs/detector.py +2 -1
- dodal/devices/electron_analyser/specs/driver_io.py +21 -26
- dodal/devices/electron_analyser/specs/region.py +1 -1
- dodal/devices/electron_analyser/util.py +20 -0
- dodal/devices/electron_analyser/vgscienta/__init__.py +3 -3
- dodal/devices/electron_analyser/vgscienta/detector.py +2 -1
- dodal/devices/electron_analyser/vgscienta/driver_io.py +24 -32
- dodal/devices/electron_analyser/vgscienta/enums.py +0 -8
- dodal/devices/electron_analyser/vgscienta/region.py +2 -31
- dodal/devices/eurotherm.py +126 -0
- dodal/devices/fluorescence_detector_motion.py +3 -10
- dodal/devices/focusing_mirror.py +1 -1
- dodal/devices/i03/undulator_dcm.py +0 -1
- dodal/devices/i09/enums.py +8 -8
- dodal/devices/i10/diagnostics.py +4 -4
- dodal/devices/i10/i10_apple2.py +3 -6
- dodal/devices/i11/cyberstar_blower.py +34 -0
- dodal/devices/i11/diff_stages.py +55 -0
- dodal/devices/i11/mythen.py +165 -0
- dodal/devices/i11/nx100robot.py +153 -0
- dodal/devices/i11/spinner.py +30 -0
- dodal/devices/i13_1/merlin_controller.py +4 -4
- dodal/devices/i19/diffractometer.py +34 -0
- dodal/devices/i19/shutter.py +11 -1
- dodal/devices/i22/dcm.py +1 -1
- dodal/devices/i22/fswitch.py +3 -12
- dodal/devices/i24/aperture.py +3 -3
- dodal/devices/i24/beam_center.py +1 -2
- dodal/devices/i24/dcm.py +11 -15
- dodal/devices/i24/dual_backlight.py +11 -12
- dodal/devices/i24/pmac.py +8 -7
- dodal/devices/mx_phase1/beamstop.py +10 -11
- dodal/devices/oav/pin_image_recognition/__init__.py +0 -3
- dodal/devices/p60/enums.py +8 -8
- dodal/devices/p60/lab_xray_source.py +3 -2
- dodal/devices/pressure_jump_cell.py +77 -123
- dodal/devices/scintillator.py +76 -4
- dodal/devices/smargon.py +35 -18
- dodal/devices/synchrotron.py +1 -2
- dodal/devices/thawer.py +22 -15
- dodal/devices/undulator.py +3 -8
- dodal/devices/util/epics_util.py +1 -1
- dodal/devices/watsonmarlow323_pump.py +7 -7
- dodal/devices/webcam.py +1 -0
- dodal/devices/xbpm_feedback.py +4 -6
- dodal/devices/xspress3/xspress3.py +0 -5
- dodal/devices/zocalo/zocalo_results.py +1 -3
- dodal/testing/__init__.py +3 -0
- dodal/testing/electron_analyser/__init__.py +6 -0
- dodal/testing/electron_analyser/device_factory.py +59 -0
- dodal/testing/setup.py +67 -0
- dodal/devices/CTAB.py +0 -41
- dodal/devices/i24/pilatus_metadata.py +0 -44
- dodal/devices/util/test_utils.py +0 -37
- {dls_dodal-1.55.1.dist-info → dls_dodal-1.57.0.dist-info}/WHEEL +0 -0
- {dls_dodal-1.55.1.dist-info → dls_dodal-1.57.0.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.55.1.dist-info → dls_dodal-1.57.0.dist-info}/licenses/LICENSE +0 -0
- {dls_dodal-1.55.1.dist-info → dls_dodal-1.57.0.dist-info}/top_level.txt +0 -0
dodal/devices/i24/pmac.py
CHANGED
|
@@ -17,8 +17,9 @@ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
|
|
|
17
17
|
|
|
18
18
|
from dodal.devices.motors import XYZStage
|
|
19
19
|
|
|
20
|
-
HOME_STR = r"\#
|
|
20
|
+
HOME_STR = r"\#5hmz\#6hmz\#7hmz" # Command to home the PMAC motors
|
|
21
21
|
ZERO_STR = "!x0y0z0" # Command to blend any ongoing move into new position
|
|
22
|
+
CS_STR = "&2"
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
class ScanState(IntEnum):
|
|
@@ -196,12 +197,12 @@ class PMAC(XYZStage):
|
|
|
196
197
|
"""Device to control the chip stage on I24."""
|
|
197
198
|
|
|
198
199
|
def __init__(self, prefix: str, name: str = "") -> None:
|
|
199
|
-
self.pmac_string = epics_signal_rw(str, prefix
|
|
200
|
+
self.pmac_string = epics_signal_rw(str, f"{prefix}-MO-IOC-13:PMAC:console")
|
|
200
201
|
self.home = PMACStringMove(
|
|
201
202
|
self.pmac_string,
|
|
202
|
-
HOME_STR,
|
|
203
|
+
f"{CS_STR}{HOME_STR}",
|
|
203
204
|
)
|
|
204
|
-
self.to_xyz_zero = PMACStringMove(self.pmac_string, ZERO_STR)
|
|
205
|
+
self.to_xyz_zero = PMACStringMove(self.pmac_string, f"{CS_STR}{ZERO_STR}")
|
|
205
206
|
|
|
206
207
|
self.laser = PMACStringLaser(self.pmac_string)
|
|
207
208
|
|
|
@@ -211,8 +212,8 @@ class PMAC(XYZStage):
|
|
|
211
212
|
|
|
212
213
|
# These next signals are readback values on PVARS which are set by the motion
|
|
213
214
|
# program.
|
|
214
|
-
self.scanstatus = epics_signal_r(float, "
|
|
215
|
-
self.counter = epics_signal_r(float, "
|
|
215
|
+
self.scanstatus = epics_signal_r(float, f"{prefix}-MO-STEP-13:pmac:read:P2401")
|
|
216
|
+
self.counter = epics_signal_r(float, f"{prefix}-MO-STEP-13:pmac:read:P2402")
|
|
216
217
|
|
|
217
218
|
# A couple of soft signals for running a collection: program number to send to
|
|
218
219
|
# the PMAC_STRING and expected collection time.
|
|
@@ -228,4 +229,4 @@ class PMAC(XYZStage):
|
|
|
228
229
|
)
|
|
229
230
|
self.abort_program = ProgramAbort(self.pmac_string, self.scanstatus)
|
|
230
231
|
|
|
231
|
-
super().__init__(prefix, name)
|
|
232
|
+
super().__init__(f"{prefix}-MO-CHIP-01:", name)
|
|
@@ -18,9 +18,6 @@ class BeamstopPositions(StrictEnum):
|
|
|
18
18
|
robot load however all 3 resolution positions are the same. We also
|
|
19
19
|
do not use the robot load position in Hyperion.
|
|
20
20
|
|
|
21
|
-
Until we support moving the beamstop it is only necessary to check whether the
|
|
22
|
-
beamstop is in beam or not.
|
|
23
|
-
|
|
24
21
|
See Also:
|
|
25
22
|
https://github.com/DiamondLightSource/mx-bluesky/issues/484
|
|
26
23
|
|
|
@@ -86,11 +83,13 @@ class Beamstop(StandardReadable):
|
|
|
86
83
|
return BeamstopPositions.UNKNOWN
|
|
87
84
|
|
|
88
85
|
async def _set_selected_position(self, position: BeamstopPositions) -> None:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
self.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
86
|
+
match position:
|
|
87
|
+
case BeamstopPositions.DATA_COLLECTION:
|
|
88
|
+
# Move z first as it could be under the table
|
|
89
|
+
await self.z_mm.set(self._in_beam_xyz_mm[2])
|
|
90
|
+
await asyncio.gather(
|
|
91
|
+
self.x_mm.set(self._in_beam_xyz_mm[0]),
|
|
92
|
+
self.y_mm.set(self._in_beam_xyz_mm[1]),
|
|
93
|
+
)
|
|
94
|
+
case _:
|
|
95
|
+
raise ValueError(f"Cannot set beamstop to position {position}")
|
|
@@ -48,9 +48,6 @@ class PinTipDetection(StandardReadable):
|
|
|
48
48
|
INVALID_POSITION = np.array([np.iinfo(np.int32).min, np.iinfo(np.int32).min])
|
|
49
49
|
|
|
50
50
|
def __init__(self, prefix: str, name: str = ""):
|
|
51
|
-
self._prefix: str = prefix
|
|
52
|
-
self._name = name
|
|
53
|
-
|
|
54
51
|
self.triggered_tip, self._tip_setter = soft_signal_r_and_setter(
|
|
55
52
|
Tip, name="triggered_tip"
|
|
56
53
|
)
|
dodal/devices/p60/enums.py
CHANGED
|
@@ -16,11 +16,11 @@ class PsuMode(StrictEnum):
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class PassEnergy(StrictEnum):
|
|
19
|
-
E1 = 1
|
|
20
|
-
E2 = 2
|
|
21
|
-
E5 = 5
|
|
22
|
-
E10 = 10
|
|
23
|
-
E20 = 20
|
|
24
|
-
E50 = 50
|
|
25
|
-
E100 = 100
|
|
26
|
-
E200 = 200
|
|
19
|
+
E1 = "1"
|
|
20
|
+
E2 = "2"
|
|
21
|
+
E5 = "5"
|
|
22
|
+
E10 = "10"
|
|
23
|
+
E20 = "20"
|
|
24
|
+
E50 = "50"
|
|
25
|
+
E100 = "100"
|
|
26
|
+
E200 = "200"
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from
|
|
2
|
+
from enum import IntEnum
|
|
3
|
+
from typing import Generic, TypeVar
|
|
3
4
|
|
|
4
|
-
from bluesky.protocols import
|
|
5
|
+
from bluesky.protocols import Movable
|
|
5
6
|
from ophyd_async.core import (
|
|
6
7
|
AsyncStatus,
|
|
7
8
|
DeviceVector,
|
|
@@ -26,23 +27,23 @@ class StopState(StrictEnum):
|
|
|
26
27
|
STOP = "STOP"
|
|
27
28
|
|
|
28
29
|
|
|
29
|
-
class
|
|
30
|
+
class ValveControlRequest(StrictEnum):
|
|
30
31
|
OPEN = "Open"
|
|
31
32
|
CLOSE = "Close"
|
|
32
33
|
RESET = "Reset"
|
|
33
|
-
ARM = "Arm"
|
|
34
|
-
DISARM = "Disarm"
|
|
35
34
|
|
|
36
35
|
|
|
37
|
-
class
|
|
38
|
-
OPEN =
|
|
39
|
-
CLOSE =
|
|
40
|
-
RESET =
|
|
36
|
+
class FastValveControlRequest(StrictEnum):
|
|
37
|
+
OPEN = ValveControlRequest.OPEN.value
|
|
38
|
+
CLOSE = ValveControlRequest.CLOSE.value
|
|
39
|
+
RESET = ValveControlRequest.RESET.value
|
|
40
|
+
ARM = "Arm"
|
|
41
|
+
DISARM = "Disarm"
|
|
41
42
|
|
|
42
43
|
|
|
43
|
-
class ValveOpenSeqRequest(
|
|
44
|
-
INACTIVE =
|
|
45
|
-
OPEN_SEQ =
|
|
44
|
+
class ValveOpenSeqRequest(IntEnum):
|
|
45
|
+
INACTIVE = 0
|
|
46
|
+
OPEN_SEQ = 1
|
|
46
47
|
|
|
47
48
|
|
|
48
49
|
class PumpMotorDirectionState(StrictEnum):
|
|
@@ -60,138 +61,89 @@ class ValveState(StrictEnum):
|
|
|
60
61
|
|
|
61
62
|
|
|
62
63
|
class FastValveState(StrictEnum):
|
|
63
|
-
FAULT =
|
|
64
|
-
OPEN =
|
|
64
|
+
FAULT = ValveState.FAULT.value
|
|
65
|
+
OPEN = ValveState.OPEN.value
|
|
65
66
|
OPEN_ARMED = "Open Armed"
|
|
66
|
-
CLOSED =
|
|
67
|
+
CLOSED = ValveState.CLOSED.value
|
|
67
68
|
CLOSED_ARMED = "Closed Armed"
|
|
68
69
|
NONE = "Unused"
|
|
69
70
|
|
|
70
71
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
TValveControlRequest = TypeVar(
|
|
73
|
+
"TValveControlRequest", bound=ValveControlRequest | FastValveControlRequest
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class ValveControl(
|
|
78
|
+
StandardReadable, Movable[TValveControlRequest], Generic[TValveControlRequest]
|
|
79
|
+
):
|
|
80
|
+
def __init__(
|
|
81
|
+
self,
|
|
82
|
+
prefix: str,
|
|
83
|
+
valve_control_type: type[TValveControlRequest],
|
|
84
|
+
name: str = "",
|
|
85
|
+
):
|
|
86
|
+
with self.add_children_as_readables():
|
|
87
|
+
self.control = epics_signal_rw(valve_control_type, prefix + ":CON")
|
|
88
|
+
self.open = epics_signal_rw(int, prefix + ":OPENSEQ")
|
|
89
|
+
super().__init__(name)
|
|
90
|
+
|
|
91
|
+
@AsyncStatus.wrap
|
|
92
|
+
async def set(self, value: TValveControlRequest):
|
|
93
|
+
if value.value == "Open":
|
|
94
|
+
await self.open.set(ValveOpenSeqRequest.OPEN_SEQ)
|
|
95
|
+
await asyncio.sleep(OPENSEQ_PULSE_LENGTH)
|
|
96
|
+
await self.open.set(ValveOpenSeqRequest.INACTIVE)
|
|
97
|
+
else:
|
|
98
|
+
await self.control.set(value)
|
|
77
99
|
|
|
78
100
|
|
|
79
|
-
class AllValvesControl(StandardReadable
|
|
101
|
+
class AllValvesControl(StandardReadable):
|
|
80
102
|
"""
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
slow_valves: tuple[int, ...] = (1, 3)
|
|
103
|
+
The default IOC for this device only controls
|
|
104
|
+
specific valves. Other valves are under manual
|
|
105
|
+
control.
|
|
85
106
|
"""
|
|
86
107
|
|
|
87
108
|
def __init__(
|
|
88
109
|
self,
|
|
89
110
|
prefix: str,
|
|
90
111
|
name: str = "",
|
|
91
|
-
|
|
92
|
-
|
|
112
|
+
fast_valves_numbers: tuple[int, ...] = (5, 6),
|
|
113
|
+
slow_valves_numbers: tuple[int, ...] = (1, 3),
|
|
93
114
|
) -> None:
|
|
94
|
-
self.fast_valves = fast_valves
|
|
95
|
-
self.slow_valves = slow_valves
|
|
96
115
|
with self.add_children_as_readables():
|
|
97
116
|
self.valve_states: DeviceVector[SignalR[ValveState]] = DeviceVector(
|
|
98
117
|
{
|
|
99
118
|
i: epics_signal_r(ValveState, f"{prefix}V{i}:STA")
|
|
100
|
-
for i in
|
|
119
|
+
for i in slow_valves_numbers
|
|
101
120
|
}
|
|
102
121
|
)
|
|
103
122
|
self.fast_valve_states: DeviceVector[SignalR[FastValveState]] = (
|
|
104
123
|
DeviceVector(
|
|
105
124
|
{
|
|
106
125
|
i: epics_signal_r(FastValveState, f"{prefix}V{i}:STA")
|
|
107
|
-
for i in
|
|
126
|
+
for i in fast_valves_numbers
|
|
108
127
|
}
|
|
109
128
|
)
|
|
110
129
|
)
|
|
111
130
|
|
|
112
|
-
self.fast_valve_control
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
self.valve_control: DeviceVector[ValveControl] = DeviceVector(
|
|
117
|
-
{i: ValveControl(f"{prefix}V{i}") for i in self.slow_valves}
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
super().__init__(name)
|
|
121
|
-
|
|
122
|
-
async def set_valve(
|
|
123
|
-
self,
|
|
124
|
-
valve: int,
|
|
125
|
-
value: ValveControlRequest | FastValveControlRequest,
|
|
126
|
-
):
|
|
127
|
-
if valve in self.slow_valves and (isinstance(value, ValveControlRequest)):
|
|
128
|
-
if value == ValveControlRequest.OPEN:
|
|
129
|
-
await self.valve_control[valve].set(ValveOpenSeqRequest.OPEN_SEQ)
|
|
130
|
-
await asyncio.sleep(OPENSEQ_PULSE_LENGTH)
|
|
131
|
-
await self.valve_control[valve].set(ValveOpenSeqRequest.INACTIVE)
|
|
132
|
-
else:
|
|
133
|
-
await self.valve_control[valve].set(value)
|
|
134
|
-
|
|
135
|
-
elif valve in self.fast_valves and (isinstance(value, FastValveControlRequest)):
|
|
136
|
-
if value == FastValveControlRequest.OPEN:
|
|
137
|
-
await self.fast_valve_control[valve].set(ValveOpenSeqRequest.OPEN_SEQ)
|
|
138
|
-
await asyncio.sleep(OPENSEQ_PULSE_LENGTH)
|
|
139
|
-
await self.fast_valve_control[valve].set(ValveOpenSeqRequest.INACTIVE)
|
|
140
|
-
else:
|
|
141
|
-
await self.fast_valve_control[valve].set(value)
|
|
142
|
-
|
|
143
|
-
@AsyncStatus.wrap
|
|
144
|
-
async def set(self, value: AllValvesControlState):
|
|
145
|
-
await asyncio.gather(
|
|
146
|
-
*(
|
|
147
|
-
self.set_valve(int(i[-1]), value)
|
|
148
|
-
for i, value in value.__dict__.items()
|
|
149
|
-
if value is not None
|
|
150
|
-
)
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
class ValveControl(
|
|
155
|
-
StandardReadable, Movable[ValveControlRequest | ValveOpenSeqRequest]
|
|
156
|
-
):
|
|
157
|
-
def __init__(self, prefix: str, name: str = "") -> None:
|
|
158
|
-
with self.add_children_as_readables():
|
|
159
|
-
self.close = epics_signal_rw(ValveControlRequest, prefix + ":CON")
|
|
160
|
-
self.open = epics_signal_rw(int, prefix + ":OPENSEQ")
|
|
161
|
-
|
|
162
|
-
super().__init__(name)
|
|
163
|
-
|
|
164
|
-
def set(self, value: ValveControlRequest | ValveOpenSeqRequest) -> AsyncStatus:
|
|
165
|
-
set_status = None
|
|
166
|
-
|
|
167
|
-
if isinstance(value, ValveControlRequest):
|
|
168
|
-
set_status = self.close.set(value)
|
|
169
|
-
elif isinstance(value, ValveOpenSeqRequest):
|
|
170
|
-
set_status = self.open.set(value.value)
|
|
131
|
+
self.fast_valve_control = {
|
|
132
|
+
i: ValveControl(f"{prefix}V{i}", FastValveControlRequest)
|
|
133
|
+
for i in fast_valves_numbers
|
|
134
|
+
}
|
|
171
135
|
|
|
172
|
-
|
|
136
|
+
self.slow_valve_control = {
|
|
137
|
+
i: ValveControl(f"{prefix}V{i}", ValveControlRequest)
|
|
138
|
+
for i in slow_valves_numbers
|
|
139
|
+
}
|
|
173
140
|
|
|
141
|
+
all_valves = self.fast_valve_control | self.slow_valve_control
|
|
174
142
|
|
|
175
|
-
|
|
176
|
-
StandardReadable, Movable[FastValveControlRequest | ValveOpenSeqRequest]
|
|
177
|
-
):
|
|
178
|
-
def __init__(self, prefix: str, name: str = "") -> None:
|
|
179
|
-
with self.add_children_as_readables():
|
|
180
|
-
self.close = epics_signal_rw(FastValveControlRequest, prefix + ":CON")
|
|
181
|
-
self.open = epics_signal_rw(int, prefix + ":OPENSEQ")
|
|
143
|
+
self.valve_control: DeviceVector[ValveControl] = DeviceVector(all_valves)
|
|
182
144
|
|
|
183
145
|
super().__init__(name)
|
|
184
146
|
|
|
185
|
-
def set(self, value: FastValveControlRequest | ValveOpenSeqRequest) -> AsyncStatus:
|
|
186
|
-
set_status = None
|
|
187
|
-
|
|
188
|
-
if isinstance(value, FastValveControlRequest):
|
|
189
|
-
set_status = self.close.set(value)
|
|
190
|
-
elif isinstance(value, ValveOpenSeqRequest):
|
|
191
|
-
set_status = self.open.set(value.value)
|
|
192
|
-
|
|
193
|
-
return set_status
|
|
194
|
-
|
|
195
147
|
|
|
196
148
|
class Pump(StandardReadable):
|
|
197
149
|
def __init__(self, prefix: str, name: str = "") -> None:
|
|
@@ -249,25 +201,27 @@ class PressureTransducer(StandardReadable):
|
|
|
249
201
|
super().__init__(name)
|
|
250
202
|
|
|
251
203
|
|
|
252
|
-
class PressureJumpCellController(
|
|
204
|
+
class PressureJumpCellController(StandardReadable):
|
|
205
|
+
"""
|
|
206
|
+
Top-level control for a fixed pressure or pressure jumps.
|
|
207
|
+
"""
|
|
208
|
+
|
|
253
209
|
def __init__(self, prefix: str, name: str = "") -> None:
|
|
254
|
-
self.
|
|
210
|
+
with self.add_children_as_readables():
|
|
211
|
+
self.stop = epics_signal_rw(StopState, f"{prefix}STOP")
|
|
255
212
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
213
|
+
self.target_pressure = epics_signal_rw(float, f"{prefix}TARGET")
|
|
214
|
+
self.timeout = epics_signal_rw(float, f"{prefix}TIMER.HIGH")
|
|
215
|
+
self.go = epics_signal_rw(bool, f"{prefix}GO")
|
|
259
216
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
self.jump_ready = epics_signal_rw(bool, f"{prefix}SETJUMP")
|
|
217
|
+
self.from_pressure = epics_signal_rw(float, f"{prefix}JUMPF")
|
|
218
|
+
self.to_pressure = epics_signal_rw(float, f"{prefix}JUMPT")
|
|
219
|
+
self.jump_ready = epics_signal_rw(bool, f"{prefix}SETJUMP")
|
|
264
220
|
|
|
265
|
-
|
|
266
|
-
super().__init__()
|
|
221
|
+
self.result = epics_signal_r(str, f"{prefix}RESULT")
|
|
267
222
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
return self._name
|
|
223
|
+
self._name = name
|
|
224
|
+
super().__init__(name)
|
|
271
225
|
|
|
272
226
|
|
|
273
227
|
class PressureJumpCell(StandardReadable):
|
dodal/devices/scintillator.py
CHANGED
|
@@ -1,10 +1,82 @@
|
|
|
1
|
-
from
|
|
1
|
+
from math import isclose
|
|
2
|
+
|
|
3
|
+
from ophyd_async.core import Reference, StandardReadable, StrictEnum, derived_signal_rw
|
|
2
4
|
from ophyd_async.epics.motor import Motor
|
|
3
5
|
|
|
6
|
+
from dodal.common.beamlines.beamline_parameters import GDABeamlineParameters
|
|
7
|
+
from dodal.devices.aperturescatterguard import ApertureScatterguard, ApertureValue
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class InOut(StrictEnum):
|
|
11
|
+
"""Currently Hyperion only needs to move the scintillator out for data collection."""
|
|
12
|
+
|
|
13
|
+
OUT = "Out"
|
|
14
|
+
UNKNOWN = "Unknown"
|
|
15
|
+
|
|
4
16
|
|
|
5
17
|
class Scintillator(StandardReadable):
|
|
6
|
-
|
|
18
|
+
"""Moves a scintillator into and out of the beam.
|
|
19
|
+
|
|
20
|
+
The scintillator will light up when hit with xrays, this allows scientists to use it
|
|
21
|
+
in conjunction with the optical OAV camera to commission the beamline.
|
|
22
|
+
|
|
23
|
+
When moved out of the beam it is parked under the table. This parking has a potential
|
|
24
|
+
to collide with the aperture/scatterguard if that is not correctly parked already.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
prefix: str,
|
|
30
|
+
aperture_scatterguard: Reference[ApertureScatterguard],
|
|
31
|
+
beamline_parameters: GDABeamlineParameters,
|
|
32
|
+
name: str = "",
|
|
33
|
+
):
|
|
7
34
|
with self.add_children_as_readables():
|
|
8
|
-
self.
|
|
9
|
-
self.
|
|
35
|
+
self.y_mm = Motor(f"{prefix}Y")
|
|
36
|
+
self.z_mm = Motor(f"{prefix}Z")
|
|
37
|
+
self.selected_pos = derived_signal_rw(
|
|
38
|
+
self._get_selected_position,
|
|
39
|
+
self._set_selected_position,
|
|
40
|
+
y=self.y_mm,
|
|
41
|
+
z=self.z_mm,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
self._aperture_scatterguard = aperture_scatterguard
|
|
45
|
+
self._scintillator_out_yz_mm = [
|
|
46
|
+
float(beamline_parameters[f"scin_{axis}_SCIN_OUT"]) for axis in ("y", "z")
|
|
47
|
+
]
|
|
48
|
+
self._yz_tolerance_mm = [
|
|
49
|
+
float(beamline_parameters[f"scin_{axis}_tolerance"]) for axis in ("y", "z")
|
|
50
|
+
]
|
|
51
|
+
|
|
10
52
|
super().__init__(name)
|
|
53
|
+
|
|
54
|
+
def _get_selected_position(self, y: float, z: float) -> InOut:
|
|
55
|
+
current_pos = [y, z]
|
|
56
|
+
if all(
|
|
57
|
+
isclose(axis_pos, axis_in_beam, abs_tol=axis_tolerance)
|
|
58
|
+
for axis_pos, axis_in_beam, axis_tolerance in zip(
|
|
59
|
+
current_pos,
|
|
60
|
+
self._scintillator_out_yz_mm,
|
|
61
|
+
self._yz_tolerance_mm,
|
|
62
|
+
strict=False,
|
|
63
|
+
)
|
|
64
|
+
):
|
|
65
|
+
return InOut.OUT
|
|
66
|
+
else:
|
|
67
|
+
return InOut.UNKNOWN
|
|
68
|
+
|
|
69
|
+
async def _set_selected_position(self, position: InOut) -> None:
|
|
70
|
+
match position:
|
|
71
|
+
case InOut.OUT:
|
|
72
|
+
if (
|
|
73
|
+
self._aperture_scatterguard().selected_aperture.get_value()
|
|
74
|
+
!= ApertureValue.PARKED
|
|
75
|
+
):
|
|
76
|
+
raise ValueError(
|
|
77
|
+
"Cannot move scintillator out if aperture/scatterguard is not parked"
|
|
78
|
+
)
|
|
79
|
+
await self.y_mm.set(self._scintillator_out_yz_mm[0])
|
|
80
|
+
await self.z_mm.set(self._scintillator_out_yz_mm[1])
|
|
81
|
+
case _:
|
|
82
|
+
raise ValueError(f"Cannot set scintillator to position {position}")
|
dodal/devices/smargon.py
CHANGED
|
@@ -3,7 +3,7 @@ from collections.abc import Collection, Generator
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from enum import Enum
|
|
5
5
|
from math import isclose
|
|
6
|
-
from typing import
|
|
6
|
+
from typing import TypedDict, cast
|
|
7
7
|
|
|
8
8
|
from bluesky import plan_stubs as bps
|
|
9
9
|
from bluesky.protocols import Movable
|
|
@@ -12,6 +12,7 @@ from ophyd_async.core import (
|
|
|
12
12
|
AsyncStatus,
|
|
13
13
|
Device,
|
|
14
14
|
StrictEnum,
|
|
15
|
+
set_and_wait_for_value,
|
|
15
16
|
wait_for_value,
|
|
16
17
|
)
|
|
17
18
|
from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
|
|
@@ -42,7 +43,7 @@ class StubOffsets(Device):
|
|
|
42
43
|
set them so that the current position is zero or to pre-defined positions.
|
|
43
44
|
"""
|
|
44
45
|
|
|
45
|
-
def __init__(self,
|
|
46
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
46
47
|
self.center_at_current_position = SetWhenEnabled(prefix=prefix + "CENTER_CS")
|
|
47
48
|
self.to_robot_load = SetWhenEnabled(prefix=prefix + "SET_STUBS_TO_RL")
|
|
48
49
|
super().__init__(name)
|
|
@@ -104,15 +105,15 @@ class DeferMoves(StrictEnum):
|
|
|
104
105
|
OFF = "Defer Off"
|
|
105
106
|
|
|
106
107
|
|
|
107
|
-
class CombinedMove(TypedDict):
|
|
108
|
+
class CombinedMove(TypedDict, total=False):
|
|
108
109
|
"""A move on multiple axes at once using a deferred move"""
|
|
109
110
|
|
|
110
|
-
x:
|
|
111
|
-
y:
|
|
112
|
-
z:
|
|
113
|
-
omega:
|
|
114
|
-
phi:
|
|
115
|
-
chi:
|
|
111
|
+
x: float | None
|
|
112
|
+
y: float | None
|
|
113
|
+
z: float | None
|
|
114
|
+
omega: float | None
|
|
115
|
+
phi: float | None
|
|
116
|
+
chi: float | None
|
|
116
117
|
|
|
117
118
|
|
|
118
119
|
class Smargon(XYZStage, Movable):
|
|
@@ -123,7 +124,9 @@ class Smargon(XYZStage, Movable):
|
|
|
123
124
|
Robot loading can nudge these and lead to errors.
|
|
124
125
|
"""
|
|
125
126
|
|
|
126
|
-
|
|
127
|
+
DEFERRED_MOVE_SET_TIMEOUT = 5
|
|
128
|
+
|
|
129
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
127
130
|
with self.add_children_as_readables():
|
|
128
131
|
self.chi = Motor(prefix + "CHI")
|
|
129
132
|
self.phi = Motor(prefix + "PHI")
|
|
@@ -161,15 +164,29 @@ class Smargon(XYZStage, Movable):
|
|
|
161
164
|
|
|
162
165
|
@AsyncStatus.wrap
|
|
163
166
|
async def set(self, value: CombinedMove):
|
|
167
|
+
"""This will move all motion together in a deferred move.
|
|
168
|
+
|
|
169
|
+
Once defer_move is on, sets to any axis do not immediately move the axis. Instead
|
|
170
|
+
the setpoint will go to that value. Then, when defer_move is switched off all
|
|
171
|
+
axes will move at the same time. The put callbacks on the axes themselves will
|
|
172
|
+
only come back after the motion on that axis finished.
|
|
173
|
+
"""
|
|
164
174
|
await self.defer_move.set(DeferMoves.ON)
|
|
165
175
|
try:
|
|
166
|
-
|
|
167
|
-
for
|
|
168
|
-
if
|
|
169
|
-
|
|
176
|
+
finished_moving = []
|
|
177
|
+
for motor_name, new_setpoint in value.items():
|
|
178
|
+
if new_setpoint is not None and isinstance(new_setpoint, int | float):
|
|
179
|
+
axis: Motor = getattr(self, motor_name)
|
|
180
|
+
await axis.check_motor_limit(
|
|
181
|
+
await axis.user_setpoint.get_value(), new_setpoint
|
|
182
|
+
)
|
|
183
|
+
put_completion = await set_and_wait_for_value(
|
|
184
|
+
axis.user_setpoint,
|
|
185
|
+
new_setpoint,
|
|
186
|
+
timeout=self.DEFERRED_MOVE_SET_TIMEOUT,
|
|
187
|
+
wait_for_set_completion=False,
|
|
188
|
+
)
|
|
189
|
+
finished_moving.append(put_completion)
|
|
170
190
|
finally:
|
|
171
191
|
await self.defer_move.set(DeferMoves.OFF)
|
|
172
|
-
|
|
173
|
-
# switched back off so we cannot wait for them until this point.
|
|
174
|
-
# see https://github.com/DiamondLightSource/dodal/issues/1315
|
|
175
|
-
await asyncio.gather(*tasks)
|
|
192
|
+
await asyncio.gather(*finished_moving)
|
dodal/devices/synchrotron.py
CHANGED
dodal/devices/thawer.py
CHANGED
|
@@ -1,56 +1,63 @@
|
|
|
1
|
-
from asyncio import Task, create_task, sleep
|
|
1
|
+
from asyncio import CancelledError, Task, create_task, sleep
|
|
2
2
|
|
|
3
3
|
from bluesky.protocols import Movable, Stoppable
|
|
4
4
|
from ophyd_async.core import (
|
|
5
5
|
AsyncStatus,
|
|
6
6
|
Device,
|
|
7
|
+
OnOff,
|
|
7
8
|
Reference,
|
|
8
9
|
SignalRW,
|
|
9
10
|
StandardReadable,
|
|
10
|
-
StrictEnum,
|
|
11
11
|
)
|
|
12
12
|
from ophyd_async.epics.core import epics_signal_rw
|
|
13
13
|
|
|
14
|
+
from dodal.log import LOGGER
|
|
15
|
+
|
|
14
16
|
|
|
15
17
|
class ThawingException(Exception):
|
|
16
18
|
pass
|
|
17
19
|
|
|
18
20
|
|
|
19
|
-
class ThawerStates(StrictEnum):
|
|
20
|
-
OFF = "Off"
|
|
21
|
-
ON = "On"
|
|
22
|
-
|
|
23
|
-
|
|
24
21
|
class ThawingTimer(Device, Stoppable, Movable[float]):
|
|
25
|
-
def __init__(self, control_signal: SignalRW[
|
|
22
|
+
def __init__(self, control_signal: SignalRW[OnOff]) -> None:
|
|
26
23
|
self._control_signal_ref = Reference(control_signal)
|
|
27
24
|
self._thawing_task: Task | None = None
|
|
28
25
|
super().__init__("thaw_for_time_s")
|
|
29
26
|
|
|
30
27
|
@AsyncStatus.wrap
|
|
31
28
|
async def set(self, value: float):
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
if self._thawing_task:
|
|
30
|
+
LOGGER.info("Thawing task already in progress, resetting timer")
|
|
31
|
+
self._thawing_task.cancel()
|
|
32
|
+
else:
|
|
33
|
+
LOGGER.info("Thawing started")
|
|
34
|
+
await self._control_signal_ref().set(OnOff.ON)
|
|
35
35
|
self._thawing_task = create_task(sleep(value))
|
|
36
36
|
try:
|
|
37
37
|
await self._thawing_task
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
except CancelledError:
|
|
39
|
+
LOGGER.info("Timer task cancelled.")
|
|
40
|
+
raise
|
|
41
|
+
else:
|
|
42
|
+
LOGGER.info("Thawing completed")
|
|
43
|
+
await self._control_signal_ref().set(OnOff.OFF)
|
|
40
44
|
|
|
41
45
|
@AsyncStatus.wrap
|
|
42
46
|
async def stop(self, *args, **kwargs):
|
|
43
47
|
if self._thawing_task:
|
|
44
48
|
self._thawing_task.cancel()
|
|
49
|
+
self._thawing_task = None
|
|
50
|
+
LOGGER.info("Thawer stopped.")
|
|
51
|
+
await self._control_signal_ref().set(OnOff.OFF)
|
|
45
52
|
|
|
46
53
|
|
|
47
54
|
class Thawer(StandardReadable, Stoppable):
|
|
48
55
|
def __init__(self, prefix: str, name: str = "") -> None:
|
|
49
|
-
self.control = epics_signal_rw(
|
|
56
|
+
self.control = epics_signal_rw(OnOff, prefix + ":CTRL")
|
|
50
57
|
self.thaw_for_time_s = ThawingTimer(self.control)
|
|
51
58
|
super().__init__(name)
|
|
52
59
|
|
|
53
60
|
@AsyncStatus.wrap
|
|
54
61
|
async def stop(self, *args, **kwargs):
|
|
55
62
|
await self.thaw_for_time_s.stop()
|
|
56
|
-
await self.control.set(
|
|
63
|
+
await self.control.set(OnOff.OFF)
|