dls-dodal 1.36.0__py3-none-any.whl → 1.36.2__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.0.dist-info → dls_dodal-1.36.2.dist-info}/METADATA +33 -33
- {dls_dodal-1.36.0.dist-info → dls_dodal-1.36.2.dist-info}/RECORD +39 -39
- {dls_dodal-1.36.0.dist-info → dls_dodal-1.36.2.dist-info}/WHEEL +1 -1
- dodal/_version.py +2 -2
- dodal/beamlines/__init__.py +1 -0
- dodal/beamlines/adsim.py +75 -0
- dodal/beamlines/b01_1.py +16 -31
- dodal/beamlines/i22.py +124 -265
- dodal/beamlines/i24.py +72 -7
- dodal/beamlines/p38.py +16 -1
- dodal/beamlines/p99.py +22 -53
- dodal/beamlines/training_rig.py +16 -26
- dodal/cli.py +54 -8
- dodal/common/beamlines/beamline_utils.py +32 -2
- dodal/common/beamlines/device_helpers.py +2 -0
- dodal/devices/adsim.py +10 -10
- dodal/devices/attenuator.py +15 -5
- dodal/devices/dcm.py +5 -4
- dodal/devices/fast_grid_scan.py +21 -46
- dodal/devices/focusing_mirror.py +20 -6
- dodal/devices/i24/beam_center.py +12 -0
- dodal/devices/i24/focus_mirrors.py +60 -0
- dodal/devices/i24/pilatus_metadata.py +44 -0
- dodal/devices/linkam3.py +1 -1
- dodal/devices/motors.py +14 -10
- dodal/devices/oav/oav_detector.py +2 -2
- dodal/devices/oav/pin_image_recognition/__init__.py +4 -7
- dodal/devices/oav/utils.py +1 -0
- dodal/devices/p99/sample_stage.py +12 -16
- dodal/devices/pressure_jump_cell.py +299 -0
- dodal/devices/robot.py +1 -1
- dodal/devices/tetramm.py +1 -1
- dodal/devices/undulator.py +4 -1
- dodal/devices/undulator_dcm.py +7 -19
- dodal/devices/zocalo/zocalo_results.py +7 -7
- dodal/utils.py +151 -2
- 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.0.dist-info → dls_dodal-1.36.2.dist-info}/LICENSE +0 -0
- {dls_dodal-1.36.0.dist-info → dls_dodal-1.36.2.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.36.0.dist-info → dls_dodal-1.36.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""A small temporary device to set and read the filename template from the pilatus"""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from ophyd_async.core import StandardReadable
|
|
6
|
+
from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
|
|
7
|
+
|
|
8
|
+
from dodal.common.signal_utils import create_hardware_backed_soft_signal
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PilatusMetadata(StandardReadable):
|
|
12
|
+
def __init__(self, prefix: str, name: str = "") -> None:
|
|
13
|
+
self.filename = epics_signal_rw(str, prefix + "cam1:FileName")
|
|
14
|
+
self.template = epics_signal_r(str, prefix + "cam1:FileTemplate_RBV")
|
|
15
|
+
self.filenumber = epics_signal_r(int, prefix + "cam1:FileNumber_RBV")
|
|
16
|
+
with self.add_children_as_readables():
|
|
17
|
+
self.filename_template = create_hardware_backed_soft_signal(
|
|
18
|
+
str, self._get_full_filename_template
|
|
19
|
+
)
|
|
20
|
+
super().__init__(name)
|
|
21
|
+
|
|
22
|
+
async def _get_full_filename_template(self) -> str:
|
|
23
|
+
"""
|
|
24
|
+
Get the template file path by querying the detector PVs.
|
|
25
|
+
Mirror the construction that the PPU does.
|
|
26
|
+
|
|
27
|
+
Returns: A template string, with the image numbers replaced with '#'
|
|
28
|
+
"""
|
|
29
|
+
filename = await self.filename.get_value()
|
|
30
|
+
filename_template = await self.template.get_value()
|
|
31
|
+
file_number = await self.filenumber.get_value()
|
|
32
|
+
# Exploit fact that passing negative numbers will put the - before the 0's
|
|
33
|
+
expected_filename = str(
|
|
34
|
+
filename_template % (filename, f"{file_number:05d}_", -9)
|
|
35
|
+
)
|
|
36
|
+
# Now, find the -09 part of this
|
|
37
|
+
numberpart = re.search(r"(-0+9)", expected_filename)
|
|
38
|
+
assert numberpart is not None
|
|
39
|
+
template_fill = "#" * len(numberpart.group(0))
|
|
40
|
+
return (
|
|
41
|
+
expected_filename[: numberpart.start()]
|
|
42
|
+
+ template_fill
|
|
43
|
+
+ expected_filename[numberpart.end() :]
|
|
44
|
+
)
|
dodal/devices/linkam3.py
CHANGED
|
@@ -33,7 +33,7 @@ class Linkam3(StandardReadable):
|
|
|
33
33
|
tolerance: float = 0.5
|
|
34
34
|
settle_time: int = 0
|
|
35
35
|
|
|
36
|
-
def __init__(self, prefix: str, name: str):
|
|
36
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
37
37
|
self.temp = epics_signal_r(float, prefix + "TEMP:")
|
|
38
38
|
self.dsc = epics_signal_r(float, prefix + "DSC:")
|
|
39
39
|
self.start_heat = epics_signal_rw(bool, prefix + "STARTHEAT:")
|
dodal/devices/motors.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
from ophyd_async.core import
|
|
1
|
+
from ophyd_async.core import StandardReadable
|
|
2
2
|
from ophyd_async.epics.motor import Motor
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
class XYZPositioner(
|
|
5
|
+
class XYZPositioner(StandardReadable):
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
Standard ophyd_async xyz motor stage, by combining 3 Motors,
|
|
@@ -15,7 +15,7 @@ class XYZPositioner(Device):
|
|
|
15
15
|
name:
|
|
16
16
|
name for the stage.
|
|
17
17
|
infix:
|
|
18
|
-
EPICS PV, default is the
|
|
18
|
+
EPICS PV, default is the ("X", "Y", "Z").
|
|
19
19
|
Notes
|
|
20
20
|
-----
|
|
21
21
|
Example usage::
|
|
@@ -23,14 +23,18 @@ class XYZPositioner(Device):
|
|
|
23
23
|
xyz_stage = XYZPositioner("BLXX-MO-STAGE-XX:")
|
|
24
24
|
Or::
|
|
25
25
|
with DeviceCollector():
|
|
26
|
-
xyz_stage = XYZPositioner("BLXX-MO-STAGE-XX:",
|
|
26
|
+
xyz_stage = XYZPositioner("BLXX-MO-STAGE-XX:", infix = ("A", "B", "C"))
|
|
27
27
|
|
|
28
28
|
"""
|
|
29
29
|
|
|
30
|
-
def __init__(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
prefix: str,
|
|
33
|
+
name: str = "",
|
|
34
|
+
infix: tuple[str, str, str] = ("X", "Y", "Z"),
|
|
35
|
+
):
|
|
36
|
+
with self.add_children_as_readables():
|
|
37
|
+
self.x = Motor(prefix + infix[0])
|
|
38
|
+
self.y = Motor(prefix + infix[1])
|
|
39
|
+
self.z = Motor(prefix + infix[2])
|
|
36
40
|
super().__init__(name=name)
|
|
@@ -40,15 +40,15 @@ class ZoomController(StandardReadable):
|
|
|
40
40
|
"""
|
|
41
41
|
|
|
42
42
|
def __init__(self, prefix: str, name: str = "") -> None:
|
|
43
|
-
super().__init__(name=name)
|
|
44
43
|
self.percentage = epics_signal_rw(float, f"{prefix}ZOOMPOSCMD")
|
|
45
44
|
|
|
46
45
|
# Level is the string description of the zoom level e.g. "1.0x" or "1.0"
|
|
47
46
|
self.level = epics_signal_rw(str, f"{prefix}MP:SELECT")
|
|
47
|
+
super().__init__(name=name)
|
|
48
48
|
|
|
49
49
|
async def _get_allowed_zoom_levels(self) -> list:
|
|
50
50
|
zoom_levels = await self.level.describe()
|
|
51
|
-
return zoom_levels[
|
|
51
|
+
return zoom_levels[self.level.name]["choices"] # type: ignore
|
|
52
52
|
|
|
53
53
|
@AsyncStatus.wrap
|
|
54
54
|
async def set(self, level_to_set: str):
|
|
@@ -2,7 +2,6 @@ import asyncio
|
|
|
2
2
|
import time
|
|
3
3
|
|
|
4
4
|
import numpy as np
|
|
5
|
-
from numpy.typing import NDArray
|
|
6
5
|
from ophyd_async.core import (
|
|
7
6
|
Array1D,
|
|
8
7
|
AsyncStatus,
|
|
@@ -57,12 +56,12 @@ class PinTipDetection(StandardReadable):
|
|
|
57
56
|
Tip, name="triggered_tip"
|
|
58
57
|
)
|
|
59
58
|
self.triggered_top_edge, self._top_edge_setter = soft_signal_r_and_setter(
|
|
60
|
-
|
|
59
|
+
Array1D[np.int32], name="triggered_top_edge"
|
|
61
60
|
)
|
|
62
61
|
self.triggered_bottom_edge, self._bottom_edge_setter = soft_signal_r_and_setter(
|
|
63
|
-
|
|
62
|
+
Array1D[np.int32], name="triggered_bottom_edge"
|
|
64
63
|
)
|
|
65
|
-
self.array_data = epics_signal_r(
|
|
64
|
+
self.array_data = epics_signal_r(np.ndarray, f"pva://{prefix}PVA:ARRAY")
|
|
66
65
|
|
|
67
66
|
# Soft parameters for pin-tip detection.
|
|
68
67
|
self.preprocess_operation = soft_signal_rw(int, 10, name="preprocess")
|
|
@@ -100,9 +99,7 @@ class PinTipDetection(StandardReadable):
|
|
|
100
99
|
self._top_edge_setter(results.edge_top)
|
|
101
100
|
self._bottom_edge_setter(results.edge_bottom)
|
|
102
101
|
|
|
103
|
-
async def _get_tip_and_edge_data(
|
|
104
|
-
self, array_data: NDArray[np.uint8]
|
|
105
|
-
) -> SampleLocation:
|
|
102
|
+
async def _get_tip_and_edge_data(self, array_data: np.ndarray) -> SampleLocation:
|
|
106
103
|
"""
|
|
107
104
|
Gets the location of the pin tip and the top and bottom edges.
|
|
108
105
|
"""
|
dodal/devices/oav/utils.py
CHANGED
|
@@ -87,6 +87,7 @@ def calculate_x_y_z_of_pixel(
|
|
|
87
87
|
beam_centre: tuple[int, int],
|
|
88
88
|
microns_per_pixel: tuple[float, float],
|
|
89
89
|
) -> np.ndarray:
|
|
90
|
+
"""Get the x, y, z position of a pixel in mm"""
|
|
90
91
|
beam_distance_px: Pixel = calculate_beam_distance(beam_centre, *pixel)
|
|
91
92
|
|
|
92
93
|
return current_x_y_z + camera_coordinates_to_xyz(
|
|
@@ -1,18 +1,13 @@
|
|
|
1
|
-
from ophyd_async.core import
|
|
2
|
-
from ophyd_async.epics.core import epics_signal_rw
|
|
1
|
+
from ophyd_async.core import StandardReadable, SubsetEnum
|
|
2
|
+
from ophyd_async.epics.core import epics_signal_rw, epics_signal_rw_rbv
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
class SampleAngleStage(
|
|
6
|
-
def __init__(self, prefix: str, name: str):
|
|
7
|
-
self.
|
|
8
|
-
float, prefix + "WRITETHETA
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
float, prefix + "WRITEROLL:RBV", prefix + "WRITEROLL"
|
|
12
|
-
)
|
|
13
|
-
self.pitch = epics_signal_rw(
|
|
14
|
-
float, prefix + "WRITEPITCH:RBV", prefix + "WRITEPITCH"
|
|
15
|
-
)
|
|
5
|
+
class SampleAngleStage(StandardReadable):
|
|
6
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
7
|
+
with self.add_children_as_readables():
|
|
8
|
+
self.theta = epics_signal_rw_rbv(float, prefix + "WRITETHETA", ":RBV")
|
|
9
|
+
self.roll = epics_signal_rw_rbv(float, prefix + "WRITEROLL", ":RBV")
|
|
10
|
+
self.pitch = epics_signal_rw_rbv(float, prefix + "WRITEPITCH", ":RBV")
|
|
16
11
|
super().__init__(name=name)
|
|
17
12
|
|
|
18
13
|
|
|
@@ -35,7 +30,8 @@ class p99StageSelections(SubsetEnum):
|
|
|
35
30
|
User = "User"
|
|
36
31
|
|
|
37
32
|
|
|
38
|
-
class FilterMotor(
|
|
39
|
-
def __init__(self, prefix: str, name: str):
|
|
40
|
-
self.
|
|
33
|
+
class FilterMotor(StandardReadable):
|
|
34
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
35
|
+
with self.add_children_as_readables():
|
|
36
|
+
self.user_setpoint = epics_signal_rw(p99StageSelections, prefix)
|
|
41
37
|
super().__init__(name=name)
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
from bluesky.protocols import HasName, Movable
|
|
5
|
+
from ophyd_async.core import (
|
|
6
|
+
AsyncStatus,
|
|
7
|
+
DeviceVector,
|
|
8
|
+
SignalR,
|
|
9
|
+
StandardReadable,
|
|
10
|
+
StandardReadableFormat,
|
|
11
|
+
StrictEnum,
|
|
12
|
+
)
|
|
13
|
+
from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
|
|
14
|
+
|
|
15
|
+
OPENSEQ_PULSE_LENGTH = 0.2
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PumpState(StrictEnum):
|
|
19
|
+
MANUAL = "Manual"
|
|
20
|
+
AUTO_PRESSURE = "Auto Pressure"
|
|
21
|
+
AUTO_POSITION = "Auto Position"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class StopState(StrictEnum):
|
|
25
|
+
CONTINUE = "CONTINUE"
|
|
26
|
+
STOP = "STOP"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class FastValveControlRequest(StrictEnum):
|
|
30
|
+
OPEN = "Open"
|
|
31
|
+
CLOSE = "Close"
|
|
32
|
+
RESET = "Reset"
|
|
33
|
+
ARM = "Arm"
|
|
34
|
+
DISARM = "Disarm"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ValveControlRequest(StrictEnum):
|
|
38
|
+
OPEN = "Open"
|
|
39
|
+
CLOSE = "Close"
|
|
40
|
+
RESET = "Reset"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ValveOpenSeqRequest(StrictEnum):
|
|
44
|
+
INACTIVE = 0
|
|
45
|
+
OPEN_SEQ = 1
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class PumpMotorDirectionState(StrictEnum):
|
|
49
|
+
EMPTY = ""
|
|
50
|
+
FORWARD = "Forward"
|
|
51
|
+
REVERSE = "Reverse"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ValveState(StrictEnum):
|
|
55
|
+
FAULT = "Fault"
|
|
56
|
+
OPEN = "Open"
|
|
57
|
+
OPENING = "Opening"
|
|
58
|
+
CLOSED = "Closed"
|
|
59
|
+
CLOSING = "Closing"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class FastValveState(StrictEnum):
|
|
63
|
+
FAULT = "Fault"
|
|
64
|
+
OPEN = "Open"
|
|
65
|
+
OPEN_ARMED = "Open Armed"
|
|
66
|
+
CLOSED = "Closed"
|
|
67
|
+
CLOSED_ARMED = "Closed Armed"
|
|
68
|
+
NONE = "Unused"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class AllValvesControlState:
|
|
73
|
+
valve_1: ValveControlRequest | None = None
|
|
74
|
+
valve_3: ValveControlRequest | None = None
|
|
75
|
+
valve_5: FastValveControlRequest | None = None
|
|
76
|
+
valve_6: FastValveControlRequest | None = None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class AllValvesControl(StandardReadable, Movable):
|
|
80
|
+
"""
|
|
81
|
+
valves 2, 4, 7, 8 are not controlled by the IOC,
|
|
82
|
+
as they are under manual control.
|
|
83
|
+
fast_valves: tuple[int, ...] = (5, 6)
|
|
84
|
+
slow_valves: tuple[int, ...] = (1, 3)
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
def __init__(
|
|
88
|
+
self,
|
|
89
|
+
prefix: str,
|
|
90
|
+
name: str = "",
|
|
91
|
+
fast_valves: tuple[int, ...] = (5, 6),
|
|
92
|
+
slow_valves: tuple[int, ...] = (1, 3),
|
|
93
|
+
) -> None:
|
|
94
|
+
self.fast_valves = fast_valves
|
|
95
|
+
self.slow_valves = slow_valves
|
|
96
|
+
with self.add_children_as_readables():
|
|
97
|
+
self.valve_states: DeviceVector[SignalR[ValveState]] = DeviceVector(
|
|
98
|
+
{
|
|
99
|
+
i: epics_signal_r(ValveState, f"{prefix}V{i}:STA")
|
|
100
|
+
for i in self.slow_valves
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
self.fast_valve_states: DeviceVector[SignalR[FastValveState]] = (
|
|
104
|
+
DeviceVector(
|
|
105
|
+
{
|
|
106
|
+
i: epics_signal_r(FastValveState, f"{prefix}V{i}:STA")
|
|
107
|
+
for i in self.fast_valves
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
self.fast_valve_control: DeviceVector[FastValveControl] = DeviceVector(
|
|
113
|
+
{i: FastValveControl(f"{prefix}V{i}") for i in self.fast_valves}
|
|
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(StandardReadable):
|
|
155
|
+
def __init__(self, prefix: str, name: str = "") -> None:
|
|
156
|
+
with self.add_children_as_readables():
|
|
157
|
+
self.close = epics_signal_rw(ValveControlRequest, prefix + ":CON")
|
|
158
|
+
self.open = epics_signal_rw(ValveOpenSeqRequest, prefix + ":OPENSEQ")
|
|
159
|
+
|
|
160
|
+
super().__init__(name)
|
|
161
|
+
|
|
162
|
+
def set(self, value: ValveControlRequest | ValveOpenSeqRequest) -> AsyncStatus:
|
|
163
|
+
set_status = None
|
|
164
|
+
|
|
165
|
+
if isinstance(value, ValveControlRequest):
|
|
166
|
+
set_status = self.close.set(value)
|
|
167
|
+
elif isinstance(value, ValveOpenSeqRequest):
|
|
168
|
+
set_status = self.open.set(value)
|
|
169
|
+
|
|
170
|
+
return set_status
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class FastValveControl(StandardReadable):
|
|
174
|
+
def __init__(self, prefix: str, name: str = "") -> None:
|
|
175
|
+
with self.add_children_as_readables():
|
|
176
|
+
self.close = epics_signal_rw(FastValveControlRequest, prefix + ":CON")
|
|
177
|
+
self.open = epics_signal_rw(ValveOpenSeqRequest, prefix + ":OPENSEQ")
|
|
178
|
+
|
|
179
|
+
super().__init__(name)
|
|
180
|
+
|
|
181
|
+
def set(self, value: FastValveControlRequest | ValveOpenSeqRequest) -> AsyncStatus:
|
|
182
|
+
set_status = None
|
|
183
|
+
|
|
184
|
+
if isinstance(value, FastValveControlRequest):
|
|
185
|
+
set_status = self.close.set(value)
|
|
186
|
+
elif isinstance(value, ValveOpenSeqRequest):
|
|
187
|
+
set_status = self.open.set(value)
|
|
188
|
+
|
|
189
|
+
return set_status
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class Pump(StandardReadable):
|
|
193
|
+
def __init__(self, prefix: str, name: str = "") -> None:
|
|
194
|
+
with self.add_children_as_readables():
|
|
195
|
+
self.pump_position = epics_signal_r(float, prefix + "POS")
|
|
196
|
+
self.pump_motor_direction = epics_signal_r(
|
|
197
|
+
PumpMotorDirectionState, prefix + "MTRDIR"
|
|
198
|
+
)
|
|
199
|
+
self.pump_speed = epics_signal_rw(
|
|
200
|
+
float, write_pv=prefix + "MSPEED", read_pv=prefix + "MSPEED_RBV"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
|
|
204
|
+
self.pump_mode = epics_signal_rw(PumpState, prefix + "SP:AUTO")
|
|
205
|
+
|
|
206
|
+
super().__init__(name)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class PressureTransducer(StandardReadable):
|
|
210
|
+
"""
|
|
211
|
+
Pressure transducer for a high pressure X-ray cell.
|
|
212
|
+
This is the chamber and there are three of them.
|
|
213
|
+
1 is the start, 3 is where the sample is.
|
|
214
|
+
NOTE: the distinction between the adc prefix and the cell prefix is kept here.
|
|
215
|
+
|
|
216
|
+
"""
|
|
217
|
+
|
|
218
|
+
def __init__(
|
|
219
|
+
self,
|
|
220
|
+
prefix: str,
|
|
221
|
+
cell_prefix: str,
|
|
222
|
+
number: int,
|
|
223
|
+
name: str = "",
|
|
224
|
+
full_different_prefix_adc: str = "",
|
|
225
|
+
) -> None:
|
|
226
|
+
final_prefix = f"{prefix}{cell_prefix}"
|
|
227
|
+
with self.add_children_as_readables():
|
|
228
|
+
self.omron_pressure = epics_signal_r(
|
|
229
|
+
float, f"{final_prefix}PP{number}:PRES"
|
|
230
|
+
)
|
|
231
|
+
self.omron_voltage = epics_signal_r(float, f"{final_prefix}PP{number}:RAW")
|
|
232
|
+
self.beckhoff_pressure = epics_signal_r(
|
|
233
|
+
float, f"{final_prefix}STATP{number}:MeanValue_RBV"
|
|
234
|
+
)
|
|
235
|
+
self.slow_beckhoff_voltage_readout = epics_signal_r(
|
|
236
|
+
float, f"{full_different_prefix_adc}CH1"
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
super().__init__(name)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class PressureJumpCellController(HasName):
|
|
243
|
+
def __init__(self, prefix: str, name: str = "") -> None:
|
|
244
|
+
self.stop = epics_signal_rw(StopState, f"{prefix}STOP")
|
|
245
|
+
|
|
246
|
+
self.target_pressure = epics_signal_rw(float, f"{prefix}TARGET")
|
|
247
|
+
self.timeout = epics_signal_rw(float, f"{prefix}TIMER.HIGH")
|
|
248
|
+
self.go = epics_signal_rw(bool, f"{prefix}GO")
|
|
249
|
+
|
|
250
|
+
## Jump logic ##
|
|
251
|
+
self.start_pressure = epics_signal_rw(float, f"{prefix}JUMPF")
|
|
252
|
+
self.target_pressure = epics_signal_rw(float, f"{prefix}JUMPT")
|
|
253
|
+
self.jump_ready = epics_signal_rw(bool, f"{prefix}SETJUMP")
|
|
254
|
+
|
|
255
|
+
self._name = name
|
|
256
|
+
super().__init__()
|
|
257
|
+
|
|
258
|
+
@property
|
|
259
|
+
def name(self):
|
|
260
|
+
return self._name
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class PressureJumpCell(StandardReadable):
|
|
264
|
+
"""
|
|
265
|
+
High pressure X-ray cell, used to apply pressure or pressure jumps to a sample.
|
|
266
|
+
prefix: str
|
|
267
|
+
The prefix of beamline - SPECIAL - unusual that the cell prefix is computed separately
|
|
268
|
+
"""
|
|
269
|
+
|
|
270
|
+
def __init__(
|
|
271
|
+
self,
|
|
272
|
+
prefix: str,
|
|
273
|
+
cell_prefix: str = "-HPXC-01:",
|
|
274
|
+
adc_prefix: str = "-ADC",
|
|
275
|
+
name: str = "",
|
|
276
|
+
):
|
|
277
|
+
self.all_valves_control = AllValvesControl(f"{prefix}{cell_prefix}", name)
|
|
278
|
+
self.pump = Pump(f"{prefix}{cell_prefix}", name)
|
|
279
|
+
|
|
280
|
+
self.controller = PressureJumpCellController(
|
|
281
|
+
f"{prefix}{cell_prefix}CTRL:", name
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
with self.add_children_as_readables():
|
|
285
|
+
self.pressure_transducers: DeviceVector[PressureTransducer] = DeviceVector(
|
|
286
|
+
{
|
|
287
|
+
i: PressureTransducer(
|
|
288
|
+
prefix=prefix,
|
|
289
|
+
number=i,
|
|
290
|
+
cell_prefix=cell_prefix,
|
|
291
|
+
full_different_prefix_adc=f"{prefix}{adc_prefix}-0{i}:",
|
|
292
|
+
)
|
|
293
|
+
for i in [1, 2, 3]
|
|
294
|
+
}
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
self.cell_temperature = epics_signal_r(float, f"{prefix}{cell_prefix}TEMP")
|
|
298
|
+
|
|
299
|
+
super().__init__(name)
|
dodal/devices/robot.py
CHANGED
|
@@ -42,7 +42,7 @@ class BartRobot(StandardReadable, Movable):
|
|
|
42
42
|
"""The sample changing robot."""
|
|
43
43
|
|
|
44
44
|
# How long to wait for the robot if it is busy soaking/drying
|
|
45
|
-
NOT_BUSY_TIMEOUT = 60
|
|
45
|
+
NOT_BUSY_TIMEOUT = 5 * 60
|
|
46
46
|
# How long to wait for the actual load to happen
|
|
47
47
|
LOAD_TIMEOUT = 60
|
|
48
48
|
NO_PIN_ERROR_CODE = 25
|
dodal/devices/tetramm.py
CHANGED
dodal/devices/undulator.py
CHANGED
|
@@ -108,10 +108,13 @@ class Undulator(StandardReadable, Movable):
|
|
|
108
108
|
"""
|
|
109
109
|
await self._set_undulator_gap(value)
|
|
110
110
|
|
|
111
|
-
async def
|
|
111
|
+
async def raise_if_not_enabled(self):
|
|
112
112
|
access_level = await self.gap_access.get_value()
|
|
113
113
|
if access_level is UndulatorGapAccess.DISABLED and not TEST_MODE:
|
|
114
114
|
raise AccessError("Undulator gap access is disabled. Contact Control Room")
|
|
115
|
+
|
|
116
|
+
async def _set_undulator_gap(self, energy_kev: float) -> None:
|
|
117
|
+
await self.raise_if_not_enabled()
|
|
115
118
|
LOGGER.info(f"Setting undulator gap to {energy_kev:.2f} kev")
|
|
116
119
|
target_gap = await self._get_gap_to_match_energy(energy_kev)
|
|
117
120
|
|
dodal/devices/undulator_dcm.py
CHANGED
|
@@ -5,18 +5,12 @@ from ophyd_async.core import AsyncStatus, StandardReadable
|
|
|
5
5
|
|
|
6
6
|
from dodal.common.beamlines.beamline_parameters import get_beamline_parameters
|
|
7
7
|
|
|
8
|
+
from ..log import LOGGER
|
|
8
9
|
from .dcm import DCM
|
|
9
|
-
from .undulator import Undulator
|
|
10
|
+
from .undulator import Undulator
|
|
10
11
|
|
|
11
12
|
ENERGY_TIMEOUT_S: float = 30.0
|
|
12
13
|
|
|
13
|
-
# Enable to allow testing when the beamline is down, do not change in production!
|
|
14
|
-
TEST_MODE = False
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class AccessError(Exception):
|
|
18
|
-
pass
|
|
19
|
-
|
|
20
14
|
|
|
21
15
|
class UndulatorDCM(StandardReadable, Movable):
|
|
22
16
|
"""
|
|
@@ -61,17 +55,11 @@ class UndulatorDCM(StandardReadable, Movable):
|
|
|
61
55
|
|
|
62
56
|
@AsyncStatus.wrap
|
|
63
57
|
async def set(self, value: float):
|
|
58
|
+
await self.undulator.raise_if_not_enabled()
|
|
64
59
|
await asyncio.gather(
|
|
65
|
-
self.
|
|
60
|
+
self.dcm.energy_in_kev.set(value, timeout=ENERGY_TIMEOUT_S),
|
|
66
61
|
self.undulator.set(value),
|
|
67
62
|
)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if access_level is UndulatorGapAccess.DISABLED and not TEST_MODE:
|
|
72
|
-
raise AccessError("Undulator gap access is disabled. Contact Control Room")
|
|
73
|
-
|
|
74
|
-
await self.dcm.energy_in_kev.set(
|
|
75
|
-
energy_kev,
|
|
76
|
-
timeout=ENERGY_TIMEOUT_S,
|
|
77
|
-
)
|
|
63
|
+
# DCM Perp pitch
|
|
64
|
+
LOGGER.info(f"Adjusting DCM offset to {self.dcm_fixed_offset_mm} mm")
|
|
65
|
+
await self.dcm.offset_in_mm.set(self.dcm_fixed_offset_mm)
|
|
@@ -12,8 +12,8 @@ import workflows.transport
|
|
|
12
12
|
from bluesky.protocols import Triggerable
|
|
13
13
|
from bluesky.utils import Msg
|
|
14
14
|
from deepdiff import DeepDiff
|
|
15
|
-
from numpy.typing import NDArray
|
|
16
15
|
from ophyd_async.core import (
|
|
16
|
+
Array1D,
|
|
17
17
|
AsyncStatus,
|
|
18
18
|
StandardReadable,
|
|
19
19
|
StandardReadableFormat,
|
|
@@ -133,22 +133,22 @@ class ZocaloResults(StandardReadable, Triggerable):
|
|
|
133
133
|
self.use_cpu_and_gpu = use_cpu_and_gpu
|
|
134
134
|
|
|
135
135
|
self.centre_of_mass, self._com_setter = soft_signal_r_and_setter(
|
|
136
|
-
|
|
136
|
+
Array1D[np.uint64], name="centre_of_mass"
|
|
137
137
|
)
|
|
138
138
|
self.bounding_box, self._bounding_box_setter = soft_signal_r_and_setter(
|
|
139
|
-
|
|
139
|
+
Array1D[np.uint64], name="bounding_box"
|
|
140
140
|
)
|
|
141
141
|
self.max_voxel, self._max_voxel_setter = soft_signal_r_and_setter(
|
|
142
|
-
|
|
142
|
+
Array1D[np.uint64], name="max_voxel"
|
|
143
143
|
)
|
|
144
144
|
self.max_count, self._max_count_setter = soft_signal_r_and_setter(
|
|
145
|
-
|
|
145
|
+
Array1D[np.uint64], name="max_count"
|
|
146
146
|
)
|
|
147
147
|
self.n_voxels, self._n_voxels_setter = soft_signal_r_and_setter(
|
|
148
|
-
|
|
148
|
+
Array1D[np.uint64], name="n_voxels"
|
|
149
149
|
)
|
|
150
150
|
self.total_count, self._total_count_setter = soft_signal_r_and_setter(
|
|
151
|
-
|
|
151
|
+
Array1D[np.uint64], name="total_count"
|
|
152
152
|
)
|
|
153
153
|
self.ispyb_dcid, self._ispyb_dcid_setter = soft_signal_r_and_setter(
|
|
154
154
|
int, name="ispyb_dcid"
|