dls-dodal 1.65.0__py3-none-any.whl → 1.66.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.65.0.dist-info → dls_dodal-1.66.0.dist-info}/METADATA +3 -4
- {dls_dodal-1.65.0.dist-info → dls_dodal-1.66.0.dist-info}/RECORD +56 -50
- dodal/_version.py +2 -2
- dodal/beamlines/aithre.py +21 -2
- dodal/beamlines/i03.py +23 -3
- dodal/beamlines/i04.py +18 -3
- dodal/beamlines/i05.py +28 -1
- dodal/beamlines/i06.py +62 -0
- dodal/beamlines/i07.py +20 -0
- dodal/beamlines/i09_1.py +7 -2
- dodal/beamlines/i10_optics.py +18 -8
- dodal/beamlines/i18.py +3 -3
- dodal/beamlines/i22.py +3 -3
- dodal/beamlines/p38.py +3 -3
- dodal/devices/aithre_lasershaping/goniometer.py +26 -9
- dodal/devices/aperturescatterguard.py +3 -2
- dodal/devices/apple2_undulator.py +89 -44
- dodal/devices/areadetector/plugins/mjpg.py +10 -3
- dodal/devices/beamsize/__init__.py +0 -0
- dodal/devices/beamsize/beamsize.py +6 -0
- dodal/devices/detector/det_resolution.py +4 -2
- dodal/devices/fast_grid_scan.py +14 -2
- dodal/devices/i03/beamsize.py +35 -0
- dodal/devices/i03/constants.py +7 -0
- dodal/devices/i03/undulator_dcm.py +2 -2
- dodal/devices/i04/beamsize.py +45 -0
- dodal/devices/i04/murko_results.py +36 -26
- dodal/devices/i04/transfocator.py +23 -29
- dodal/devices/i07/id.py +38 -0
- dodal/devices/i09_1_shared/__init__.py +6 -2
- dodal/devices/i09_1_shared/hard_undulator_functions.py +85 -21
- dodal/devices/i10/i10_apple2.py +22 -316
- dodal/devices/i17/i17_apple2.py +7 -4
- dodal/devices/ipin.py +20 -2
- dodal/devices/motors.py +19 -3
- dodal/devices/mx_phase1/beamstop.py +31 -12
- dodal/devices/oav/oav_calculations.py +9 -4
- dodal/devices/oav/oav_detector.py +65 -7
- dodal/devices/oav/oav_parameters.py +3 -1
- dodal/devices/oav/oav_to_redis_forwarder.py +18 -15
- dodal/devices/oav/pin_image_recognition/__init__.py +5 -1
- dodal/devices/oav/pin_image_recognition/utils.py +23 -1
- dodal/devices/oav/snapshots/snapshot_with_grid.py +8 -2
- dodal/devices/oav/utils.py +16 -6
- dodal/devices/robot.py +17 -7
- dodal/devices/scintillator.py +36 -14
- dodal/devices/smargon.py +2 -3
- dodal/devices/thawer.py +7 -45
- dodal/devices/undulator.py +152 -68
- dodal/devices/util/lookup_tables_apple2.py +390 -0
- dodal/plans/load_panda_yaml.py +9 -0
- dodal/plans/verify_undulator_gap.py +2 -2
- dodal/beamline_specific_utils/i03.py +0 -17
- dodal/testing/__init__.py +0 -3
- dodal/testing/setup.py +0 -67
- {dls_dodal-1.65.0.dist-info → dls_dodal-1.66.0.dist-info}/WHEEL +0 -0
- {dls_dodal-1.65.0.dist-info → dls_dodal-1.66.0.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.65.0.dist-info → dls_dodal-1.66.0.dist-info}/licenses/LICENSE +0 -0
- {dls_dodal-1.65.0.dist-info → dls_dodal-1.66.0.dist-info}/top_level.txt +0 -0
dodal/beamlines/i10_optics.py
CHANGED
|
@@ -6,6 +6,8 @@ note:
|
|
|
6
6
|
idd == id1, idu == id2.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
9
11
|
from daq_config_server.client import ConfigServer
|
|
10
12
|
|
|
11
13
|
from dodal.common.beamlines.beamline_utils import device_factory
|
|
@@ -34,6 +36,12 @@ from dodal.devices.i10.i10_apple2 import (
|
|
|
34
36
|
from dodal.devices.i10.i10_setting_data import I10Grating
|
|
35
37
|
from dodal.devices.pgm import PlaneGratingMonochromator
|
|
36
38
|
from dodal.devices.synchrotron import Synchrotron
|
|
39
|
+
from dodal.devices.util.lookup_tables_apple2 import (
|
|
40
|
+
DEFAULT_GAP_FILE,
|
|
41
|
+
DEFAULT_PHASE_FILE,
|
|
42
|
+
EnergyMotorLookup,
|
|
43
|
+
LookupTableConfig,
|
|
44
|
+
)
|
|
37
45
|
from dodal.log import set_beamline as set_log_beamline
|
|
38
46
|
from dodal.utils import BeamlinePrefix, get_beamline_name
|
|
39
47
|
|
|
@@ -115,12 +123,13 @@ def idd() -> I10Apple2:
|
|
|
115
123
|
@device_factory()
|
|
116
124
|
def idd_controller() -> I10Apple2Controller:
|
|
117
125
|
"""I10 downstream insertion device controller."""
|
|
118
|
-
|
|
119
|
-
apple2=idd(),
|
|
120
|
-
lookuptable_dir=LOOK_UPTABLE_DIR,
|
|
121
|
-
source=("Source", "idd"),
|
|
126
|
+
idd_energy_motor_lut = EnergyMotorLookup(
|
|
122
127
|
config_client=I10_CONF_CLIENT,
|
|
128
|
+
lut_config=LookupTableConfig(source=("Source", "idd")),
|
|
129
|
+
gap_path=Path(LOOK_UPTABLE_DIR, DEFAULT_GAP_FILE),
|
|
130
|
+
phase_path=Path(LOOK_UPTABLE_DIR, DEFAULT_PHASE_FILE),
|
|
123
131
|
)
|
|
132
|
+
return I10Apple2Controller(apple2=idd(), energy_motor_lut=idd_energy_motor_lut)
|
|
124
133
|
|
|
125
134
|
|
|
126
135
|
@device_factory()
|
|
@@ -179,12 +188,13 @@ def idu() -> I10Apple2:
|
|
|
179
188
|
@device_factory()
|
|
180
189
|
def idu_controller() -> I10Apple2Controller:
|
|
181
190
|
"""I10 upstream insertion device controller."""
|
|
182
|
-
|
|
183
|
-
apple2=idu(),
|
|
184
|
-
lookuptable_dir=LOOK_UPTABLE_DIR,
|
|
185
|
-
source=("Source", "idu"),
|
|
191
|
+
idu_energy_motor_lut = EnergyMotorLookup(
|
|
186
192
|
config_client=I10_CONF_CLIENT,
|
|
193
|
+
lut_config=LookupTableConfig(source=("Source", "idu")),
|
|
194
|
+
gap_path=Path(LOOK_UPTABLE_DIR, DEFAULT_GAP_FILE),
|
|
195
|
+
phase_path=Path(LOOK_UPTABLE_DIR, DEFAULT_PHASE_FILE),
|
|
187
196
|
)
|
|
197
|
+
return I10Apple2Controller(apple2=idd(), energy_motor_lut=idu_energy_motor_lut)
|
|
188
198
|
|
|
189
199
|
|
|
190
200
|
@device_factory()
|
dodal/beamlines/i18.py
CHANGED
|
@@ -23,7 +23,7 @@ from dodal.devices.motors import XYStage, XYZThetaStage
|
|
|
23
23
|
from dodal.devices.slits import Slits
|
|
24
24
|
from dodal.devices.synchrotron import Synchrotron
|
|
25
25
|
from dodal.devices.tetramm import TetrammDetector
|
|
26
|
-
from dodal.devices.undulator import
|
|
26
|
+
from dodal.devices.undulator import UndulatorInKeV
|
|
27
27
|
from dodal.log import set_beamline as set_log_beamline
|
|
28
28
|
from dodal.utils import BeamlinePrefix, get_beamline_name
|
|
29
29
|
|
|
@@ -53,8 +53,8 @@ def synchrotron() -> Synchrotron:
|
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
@device_factory()
|
|
56
|
-
def undulator() ->
|
|
57
|
-
return
|
|
56
|
+
def undulator() -> UndulatorInKeV:
|
|
57
|
+
return UndulatorInKeV(f"{PREFIX.insertion_prefix}-MO-SERVC-01:")
|
|
58
58
|
|
|
59
59
|
|
|
60
60
|
# See https://github.com/DiamondLightSource/dodal/issues/1180
|
dodal/beamlines/i22.py
CHANGED
|
@@ -23,7 +23,7 @@ from dodal.devices.motors import XYPitchStage
|
|
|
23
23
|
from dodal.devices.slits import Slits
|
|
24
24
|
from dodal.devices.synchrotron import Synchrotron
|
|
25
25
|
from dodal.devices.tetramm import TetrammDetector
|
|
26
|
-
from dodal.devices.undulator import
|
|
26
|
+
from dodal.devices.undulator import UndulatorInKeV
|
|
27
27
|
from dodal.devices.watsonmarlow323_pump import WatsonMarlow323Pump
|
|
28
28
|
from dodal.log import set_beamline as set_log_beamline
|
|
29
29
|
from dodal.utils import BeamlinePrefix, get_beamline_name
|
|
@@ -160,8 +160,8 @@ def dcm() -> DCM:
|
|
|
160
160
|
|
|
161
161
|
|
|
162
162
|
@device_factory()
|
|
163
|
-
def undulator() ->
|
|
164
|
-
return
|
|
163
|
+
def undulator() -> UndulatorInKeV:
|
|
164
|
+
return UndulatorInKeV(
|
|
165
165
|
prefix=f"{PREFIX.insertion_prefix}-MO-SERVC-01:",
|
|
166
166
|
id_gap_lookup_table_path="/dls_sw/i22/software/daq_configuration/lookup/BeamLine_Undulator_toGap.txt",
|
|
167
167
|
poles=80,
|
dodal/beamlines/p38.py
CHANGED
|
@@ -22,7 +22,7 @@ from dodal.devices.linkam3 import Linkam3
|
|
|
22
22
|
from dodal.devices.pressure_jump_cell import PressureJumpCell
|
|
23
23
|
from dodal.devices.slits import Slits
|
|
24
24
|
from dodal.devices.tetramm import TetrammDetector
|
|
25
|
-
from dodal.devices.undulator import
|
|
25
|
+
from dodal.devices.undulator import UndulatorInKeV
|
|
26
26
|
from dodal.devices.watsonmarlow323_pump import WatsonMarlow323Pump
|
|
27
27
|
from dodal.log import set_beamline as set_log_beamline
|
|
28
28
|
from dodal.utils import BeamlinePrefix, get_beamline_name
|
|
@@ -157,8 +157,8 @@ def dcm() -> DCM:
|
|
|
157
157
|
|
|
158
158
|
|
|
159
159
|
@device_factory(mock=True)
|
|
160
|
-
def undulator() ->
|
|
161
|
-
return
|
|
160
|
+
def undulator() -> UndulatorInKeV:
|
|
161
|
+
return UndulatorInKeV(
|
|
162
162
|
f"{PREFIX.insertion_prefix}-MO-SERVC-01:",
|
|
163
163
|
poles=80,
|
|
164
164
|
length=2.0,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from ophyd_async.epics.motor import Motor
|
|
2
2
|
|
|
3
|
-
from dodal.devices.motors import
|
|
3
|
+
from dodal.devices.motors import XYZOmegaStage, create_axis_perp_to_rotation
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
class Goniometer(
|
|
6
|
+
class Goniometer(XYZOmegaStage):
|
|
7
7
|
"""The Aithre lab goniometer and the XYZ stage it sits on.
|
|
8
8
|
|
|
9
9
|
`x`, `y` and `z` control the axes of the positioner at the base, while `sampy` and
|
|
@@ -15,11 +15,28 @@ class Goniometer(XYZStage):
|
|
|
15
15
|
regardless of the current rotation.
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
-
def __init__(
|
|
19
|
-
self
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
prefix: str,
|
|
21
|
+
name: str = "",
|
|
22
|
+
x_infix: str = "X",
|
|
23
|
+
y_infix: str = "Y",
|
|
24
|
+
z_infix: str = "Z",
|
|
25
|
+
omega_infix: str = "OMEGA",
|
|
26
|
+
sampy_infix: str = "SAMPY",
|
|
27
|
+
sampz_infix: str = "SAMPZ",
|
|
28
|
+
) -> None:
|
|
29
|
+
super().__init__(
|
|
30
|
+
prefix=prefix,
|
|
31
|
+
name=name,
|
|
32
|
+
x_infix=x_infix,
|
|
33
|
+
y_infix=y_infix,
|
|
34
|
+
z_infix=z_infix,
|
|
35
|
+
omega_infix=omega_infix,
|
|
24
36
|
)
|
|
25
|
-
|
|
37
|
+
with self.add_children_as_readables():
|
|
38
|
+
self.sampy = Motor(prefix + sampy_infix)
|
|
39
|
+
self.sampz = Motor(prefix + sampz_infix)
|
|
40
|
+
self.vertical_position = create_axis_perp_to_rotation(
|
|
41
|
+
self.omega, self.sampz, self.sampy
|
|
42
|
+
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
from math import inf
|
|
4
5
|
|
|
5
6
|
from bluesky.protocols import Preparable
|
|
6
7
|
from ophyd_async.core import (
|
|
@@ -112,7 +113,7 @@ def load_positions_from_beamline_parameters(
|
|
|
112
113
|
) -> dict[ApertureValue, AperturePosition]:
|
|
113
114
|
return {
|
|
114
115
|
ApertureValue.OUT_OF_BEAM: AperturePosition.from_gda_params(
|
|
115
|
-
_GDAParamApertureValue.ROBOT_LOAD,
|
|
116
|
+
_GDAParamApertureValue.ROBOT_LOAD, inf, params
|
|
116
117
|
),
|
|
117
118
|
ApertureValue.SMALL: AperturePosition.from_gda_params(
|
|
118
119
|
_GDAParamApertureValue.SMALL, 20, params
|
|
@@ -124,7 +125,7 @@ def load_positions_from_beamline_parameters(
|
|
|
124
125
|
_GDAParamApertureValue.LARGE, 100, params
|
|
125
126
|
),
|
|
126
127
|
ApertureValue.PARKED: AperturePosition.from_gda_params(
|
|
127
|
-
_GDAParamApertureValue.MANUAL_LOAD,
|
|
128
|
+
_GDAParamApertureValue.MANUAL_LOAD, inf, params
|
|
128
129
|
),
|
|
129
130
|
}
|
|
130
131
|
|
|
@@ -23,11 +23,13 @@ from ophyd_async.core import (
|
|
|
23
23
|
from ophyd_async.epics.core import epics_signal_r, epics_signal_rw, epics_signal_w
|
|
24
24
|
from ophyd_async.epics.motor import Motor
|
|
25
25
|
|
|
26
|
+
from dodal.common.enums import EnabledDisabledUpper
|
|
26
27
|
from dodal.log import LOGGER
|
|
27
28
|
|
|
28
29
|
T = TypeVar("T")
|
|
29
30
|
|
|
30
31
|
DEFAULT_MOTOR_MIN_TIMEOUT = 10
|
|
32
|
+
MAXIMUM_MOVE_TIME = 550 # There is no useful movements take longer than this.
|
|
31
33
|
|
|
32
34
|
|
|
33
35
|
class UndulatorGateStatus(StrictEnum):
|
|
@@ -36,16 +38,24 @@ class UndulatorGateStatus(StrictEnum):
|
|
|
36
38
|
|
|
37
39
|
|
|
38
40
|
@dataclass
|
|
39
|
-
class
|
|
41
|
+
class Apple2LockedPhasesVal:
|
|
40
42
|
top_outer: str
|
|
41
|
-
top_inner: str
|
|
42
43
|
btm_inner: str
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class Apple2PhasesVal(Apple2LockedPhasesVal):
|
|
48
|
+
top_inner: str
|
|
43
49
|
btm_outer: str
|
|
44
50
|
|
|
45
51
|
|
|
46
52
|
@dataclass
|
|
47
|
-
class Apple2Val
|
|
53
|
+
class Apple2Val:
|
|
48
54
|
gap: str
|
|
55
|
+
phase: Apple2LockedPhasesVal | Apple2PhasesVal
|
|
56
|
+
|
|
57
|
+
def extract_phase_val(self):
|
|
58
|
+
return self.phase
|
|
49
59
|
|
|
50
60
|
|
|
51
61
|
class Pol(StrictEnum):
|
|
@@ -80,10 +90,7 @@ class SafeUndulatorMover(StandardReadable, Movable[T], Generic[T]):
|
|
|
80
90
|
def __init__(self, set_move: SignalW, prefix: str, name: str = ""):
|
|
81
91
|
# Gate keeper open when move is requested, closed when move is completed
|
|
82
92
|
self.gate = epics_signal_r(UndulatorGateStatus, prefix + "BLGATE")
|
|
83
|
-
|
|
84
|
-
split_pv = prefix.split("-")
|
|
85
|
-
fault_pv = f"{split_pv[0]}-{split_pv[1]}-STAT-{split_pv[3]}ANYFAULT"
|
|
86
|
-
self.fault = epics_signal_r(float, fault_pv)
|
|
93
|
+
self.status = epics_signal_r(EnabledDisabledUpper, prefix + "IDBLENA")
|
|
87
94
|
self.set_move = set_move
|
|
88
95
|
super().__init__(name)
|
|
89
96
|
|
|
@@ -91,14 +98,14 @@ class SafeUndulatorMover(StandardReadable, Movable[T], Generic[T]):
|
|
|
91
98
|
async def set(self, value: T) -> None:
|
|
92
99
|
LOGGER.info(f"Setting {self.name} to {value}")
|
|
93
100
|
await self.raise_if_cannot_move()
|
|
94
|
-
await self.
|
|
101
|
+
await self.set_demand_positions(value)
|
|
95
102
|
timeout = await self.get_timeout()
|
|
96
103
|
LOGGER.info(f"Moving {self.name} to {value} with timeout = {timeout}")
|
|
97
104
|
await self.set_move.set(value=1, timeout=timeout)
|
|
98
105
|
await wait_for_value(self.gate, UndulatorGateStatus.CLOSE, timeout=timeout)
|
|
99
106
|
|
|
100
107
|
@abc.abstractmethod
|
|
101
|
-
async def
|
|
108
|
+
async def set_demand_positions(self, value: T) -> None:
|
|
102
109
|
"""Set the demand positions on the device without actually hitting move."""
|
|
103
110
|
|
|
104
111
|
@abc.abstractmethod
|
|
@@ -106,8 +113,8 @@ class SafeUndulatorMover(StandardReadable, Movable[T], Generic[T]):
|
|
|
106
113
|
"""Get the timeout for the move based on an estimate of how long it will take."""
|
|
107
114
|
|
|
108
115
|
async def raise_if_cannot_move(self) -> None:
|
|
109
|
-
if await self.
|
|
110
|
-
raise RuntimeError(f"{self.name} is
|
|
116
|
+
if await self.status.get_value() is not EnabledDisabledUpper.ENABLED:
|
|
117
|
+
raise RuntimeError(f"{self.name} is DISABLED and cannot move.")
|
|
111
118
|
if await self.gate.get_value() == UndulatorGateStatus.OPEN:
|
|
112
119
|
raise RuntimeError(f"{self.name} is already in motion.")
|
|
113
120
|
|
|
@@ -158,7 +165,7 @@ class UndulatorGap(SafeUndulatorMover[float]):
|
|
|
158
165
|
self.user_readback = epics_signal_r(float, prefix + "CURRGAPD")
|
|
159
166
|
super().__init__(self.set_move, prefix, name)
|
|
160
167
|
|
|
161
|
-
async def
|
|
168
|
+
async def set_demand_positions(self, value: float) -> None:
|
|
162
169
|
await self.user_setpoint.set(str(value))
|
|
163
170
|
|
|
164
171
|
async def get_timeout(self) -> float:
|
|
@@ -206,49 +213,39 @@ class UndulatorPhaseMotor(StandardReadable):
|
|
|
206
213
|
super().__init__(name=name)
|
|
207
214
|
|
|
208
215
|
|
|
209
|
-
|
|
210
|
-
"""
|
|
211
|
-
A collection of 4 phase Motor to make up the full id phase motion. We are using the diamond pv convention.
|
|
212
|
-
e.g. top_outer == Q1
|
|
213
|
-
top_inner == Q2
|
|
214
|
-
btm_inner == q3
|
|
215
|
-
btm_outer == q4
|
|
216
|
+
Apple2PhaseValType = TypeVar("Apple2PhaseValType", bound=Apple2LockedPhasesVal)
|
|
216
217
|
|
|
217
|
-
|
|
218
|
+
|
|
219
|
+
class UndulatorLockedPhaseAxes(SafeUndulatorMover[Apple2PhaseValType]):
|
|
220
|
+
"""Two phase Motor to make up the locked id phase motion."""
|
|
218
221
|
|
|
219
222
|
def __init__(
|
|
220
223
|
self,
|
|
221
224
|
prefix: str,
|
|
222
225
|
top_outer: str,
|
|
223
|
-
top_inner: str,
|
|
224
226
|
btm_inner: str,
|
|
225
|
-
btm_outer: str,
|
|
226
227
|
name: str = "",
|
|
227
228
|
):
|
|
228
229
|
# Gap demand set point and readback
|
|
229
230
|
with self.add_children_as_readables():
|
|
230
231
|
self.top_outer = UndulatorPhaseMotor(prefix=prefix, infix=top_outer)
|
|
231
|
-
self.top_inner = UndulatorPhaseMotor(prefix=prefix, infix=top_inner)
|
|
232
232
|
self.btm_inner = UndulatorPhaseMotor(prefix=prefix, infix=btm_inner)
|
|
233
|
-
self.btm_outer = UndulatorPhaseMotor(prefix=prefix, infix=btm_outer)
|
|
234
233
|
# Nothing move until this is set to 1 and it will return to 0 when done.
|
|
235
234
|
self.set_move = epics_signal_rw(int, f"{prefix}BL{top_outer}" + "MOVE")
|
|
236
|
-
|
|
235
|
+
self.axes = [self.top_outer, self.btm_inner]
|
|
237
236
|
super().__init__(self.set_move, prefix, name)
|
|
238
237
|
|
|
239
|
-
async def
|
|
238
|
+
async def set_demand_positions(self, value: Apple2PhaseValType) -> None:
|
|
240
239
|
await asyncio.gather(
|
|
241
240
|
self.top_outer.user_setpoint.set(value=value.top_outer),
|
|
242
|
-
self.top_inner.user_setpoint.set(value=value.top_inner),
|
|
243
241
|
self.btm_inner.user_setpoint.set(value=value.btm_inner),
|
|
244
|
-
self.btm_outer.user_setpoint.set(value=value.btm_outer),
|
|
245
242
|
)
|
|
246
243
|
|
|
247
244
|
async def get_timeout(self) -> float:
|
|
248
245
|
"""
|
|
249
246
|
Get all four motor speed, current positions and target positions to calculate required timeout.
|
|
250
247
|
"""
|
|
251
|
-
|
|
248
|
+
|
|
252
249
|
timeouts = await asyncio.gather(
|
|
253
250
|
*[
|
|
254
251
|
estimate_motor_timeout(
|
|
@@ -256,7 +253,7 @@ class UndulatorPhaseAxes(SafeUndulatorMover[Apple2PhasesVal]):
|
|
|
256
253
|
axis.user_readback,
|
|
257
254
|
axis.velocity,
|
|
258
255
|
)
|
|
259
|
-
for axis in axes
|
|
256
|
+
for axis in self.axes
|
|
260
257
|
]
|
|
261
258
|
)
|
|
262
259
|
"""A 2.0 multiplier is required to prevent premature motor timeouts in phase
|
|
@@ -266,6 +263,42 @@ class UndulatorPhaseAxes(SafeUndulatorMover[Apple2PhasesVal]):
|
|
|
266
263
|
return np.max(timeouts) * 2.0
|
|
267
264
|
|
|
268
265
|
|
|
266
|
+
class UndulatorPhaseAxes(UndulatorLockedPhaseAxes[Apple2PhasesVal]):
|
|
267
|
+
"""
|
|
268
|
+
A collection of 4 phase Motor to make up the full id phase motion. We are using the diamond pv convention.
|
|
269
|
+
e.g. top_outer == Q1
|
|
270
|
+
top_inner == Q2
|
|
271
|
+
btm_inner == q3
|
|
272
|
+
btm_outer == q4
|
|
273
|
+
|
|
274
|
+
"""
|
|
275
|
+
|
|
276
|
+
def __init__(
|
|
277
|
+
self,
|
|
278
|
+
prefix: str,
|
|
279
|
+
top_outer: str,
|
|
280
|
+
top_inner: str,
|
|
281
|
+
btm_inner: str,
|
|
282
|
+
btm_outer: str,
|
|
283
|
+
name: str = "",
|
|
284
|
+
):
|
|
285
|
+
# Gap demand set point and readback
|
|
286
|
+
with self.add_children_as_readables():
|
|
287
|
+
self.top_inner = UndulatorPhaseMotor(prefix=prefix, infix=top_inner)
|
|
288
|
+
self.btm_outer = UndulatorPhaseMotor(prefix=prefix, infix=btm_outer)
|
|
289
|
+
|
|
290
|
+
super().__init__(prefix, top_outer=top_outer, btm_inner=btm_inner, name=name)
|
|
291
|
+
self.axes.extend([self.top_inner, self.btm_outer])
|
|
292
|
+
|
|
293
|
+
async def set_demand_positions(self, value: Apple2PhasesVal) -> None:
|
|
294
|
+
await asyncio.gather(
|
|
295
|
+
self.top_outer.user_setpoint.set(value=value.top_outer),
|
|
296
|
+
self.top_inner.user_setpoint.set(value=value.top_inner),
|
|
297
|
+
self.btm_inner.user_setpoint.set(value=value.btm_inner),
|
|
298
|
+
self.btm_outer.user_setpoint.set(value=value.btm_outer),
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
|
|
269
302
|
class UndulatorJawPhase(SafeUndulatorMover[float]):
|
|
270
303
|
"""
|
|
271
304
|
A JawPhase movable, this is use for moving the jaw phase which is use to control the
|
|
@@ -287,7 +320,7 @@ class UndulatorJawPhase(SafeUndulatorMover[float]):
|
|
|
287
320
|
|
|
288
321
|
super().__init__(self.set_move, prefix, name)
|
|
289
322
|
|
|
290
|
-
async def
|
|
323
|
+
async def set_demand_positions(self, value: float) -> None:
|
|
291
324
|
await self.jaw_phase.user_setpoint.set(value=str(value))
|
|
292
325
|
|
|
293
326
|
async def get_timeout(self) -> float:
|
|
@@ -301,7 +334,10 @@ class UndulatorJawPhase(SafeUndulatorMover[float]):
|
|
|
301
334
|
)
|
|
302
335
|
|
|
303
336
|
|
|
304
|
-
|
|
337
|
+
PhaseAxesType = TypeVar("PhaseAxesType", bound=UndulatorLockedPhaseAxes)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
class Apple2(StandardReadable, Movable[Apple2Val], Generic[PhaseAxesType]):
|
|
305
341
|
"""
|
|
306
342
|
Device representing the combined motor controls for an Apple2 undulator.
|
|
307
343
|
|
|
@@ -313,7 +349,7 @@ class Apple2(StandardReadable, Movable):
|
|
|
313
349
|
The undulator phase axes device, consisting of four phase motors.
|
|
314
350
|
"""
|
|
315
351
|
|
|
316
|
-
def __init__(self, id_gap: UndulatorGap, id_phase:
|
|
352
|
+
def __init__(self, id_gap: UndulatorGap, id_phase: PhaseAxesType, name=""):
|
|
317
353
|
"""
|
|
318
354
|
Parameters
|
|
319
355
|
----------
|
|
@@ -339,12 +375,12 @@ class Apple2(StandardReadable, Movable):
|
|
|
339
375
|
|
|
340
376
|
# Only need to check gap as the phase motors share both fault and gate with gap.
|
|
341
377
|
await self.gap().raise_if_cannot_move()
|
|
378
|
+
|
|
342
379
|
await asyncio.gather(
|
|
343
|
-
self.phase().
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
self.
|
|
347
|
-
self.gap().user_setpoint.set(value=id_motor_values.gap),
|
|
380
|
+
self.phase().set_demand_positions(
|
|
381
|
+
value=id_motor_values.extract_phase_val()
|
|
382
|
+
),
|
|
383
|
+
self.gap().set_demand_positions(value=float(id_motor_values.gap)),
|
|
348
384
|
)
|
|
349
385
|
timeout = np.max(
|
|
350
386
|
await asyncio.gather(self.gap().get_timeout(), self.phase().get_timeout())
|
|
@@ -367,7 +403,7 @@ class EnergyMotorConvertor(Protocol):
|
|
|
367
403
|
...
|
|
368
404
|
|
|
369
405
|
|
|
370
|
-
Apple2Type = TypeVar("Apple2Type", bound=
|
|
406
|
+
Apple2Type = TypeVar("Apple2Type", bound=Apple2)
|
|
371
407
|
|
|
372
408
|
|
|
373
409
|
class Apple2Controller(abc.ABC, StandardReadable, Generic[Apple2Type]):
|
|
@@ -444,16 +480,25 @@ class Apple2Controller(abc.ABC, StandardReadable, Generic[Apple2Type]):
|
|
|
444
480
|
self.polarisation_setpoint, self._polarisation_setpoint_set = (
|
|
445
481
|
soft_signal_r_and_setter(Pol)
|
|
446
482
|
)
|
|
483
|
+
# check if undulator phase is unlocked.
|
|
484
|
+
if isinstance(self.apple2().phase(), UndulatorPhaseAxes):
|
|
485
|
+
top_inner = self.apple2().phase().top_inner.user_readback
|
|
486
|
+
btm_outer = self.apple2().phase().btm_outer.user_readback
|
|
487
|
+
else:
|
|
488
|
+
# If locked phase axes make the locked phase 0.
|
|
489
|
+
top_inner = btm_outer = soft_signal_rw(float, initial_value=0.0)
|
|
490
|
+
|
|
447
491
|
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
|
|
448
492
|
# Hardware backed read/write for polarisation.
|
|
493
|
+
|
|
449
494
|
self.polarisation = derived_signal_rw(
|
|
450
495
|
raw_to_derived=self._read_pol,
|
|
451
496
|
set_derived=self._set_pol,
|
|
452
497
|
pol=self.polarisation_setpoint,
|
|
453
498
|
top_outer=self.apple2().phase().top_outer.user_readback,
|
|
454
|
-
top_inner=
|
|
499
|
+
top_inner=top_inner,
|
|
455
500
|
btm_inner=self.apple2().phase().btm_inner.user_readback,
|
|
456
|
-
btm_outer=
|
|
501
|
+
btm_outer=btm_outer,
|
|
457
502
|
gap=self.apple2().gap().user_readback,
|
|
458
503
|
)
|
|
459
504
|
super().__init__(name)
|
|
@@ -501,7 +546,7 @@ class Apple2Controller(abc.ABC, StandardReadable, Generic[Apple2Type]):
|
|
|
501
546
|
) -> None:
|
|
502
547
|
# This changes the pol setpoint and then changes polarisation via set energy.
|
|
503
548
|
self._polarisation_setpoint_set(value)
|
|
504
|
-
await self.energy.set(await self.energy.get_value())
|
|
549
|
+
await self.energy.set(await self.energy.get_value(), timeout=MAXIMUM_MOVE_TIME)
|
|
505
550
|
|
|
506
551
|
def _read_pol(
|
|
507
552
|
self,
|
|
@@ -681,7 +726,7 @@ class InsertionDeviceEnergy(InsertionDeviceEnergyBase):
|
|
|
681
726
|
|
|
682
727
|
@AsyncStatus.wrap
|
|
683
728
|
async def set(self, energy: float) -> None:
|
|
684
|
-
await self.energy().set(energy)
|
|
729
|
+
await self.energy().set(energy, timeout=MAXIMUM_MOVE_TIME)
|
|
685
730
|
|
|
686
731
|
|
|
687
732
|
class InsertionDevicePolarisation(StandardReadable, Locatable[Pol]):
|
|
@@ -696,7 +741,7 @@ class InsertionDevicePolarisation(StandardReadable, Locatable[Pol]):
|
|
|
696
741
|
|
|
697
742
|
@AsyncStatus.wrap
|
|
698
743
|
async def set(self, pol: Pol) -> None:
|
|
699
|
-
await self.polarisation().set(pol)
|
|
744
|
+
await self.polarisation().set(pol, timeout=MAXIMUM_MOVE_TIME)
|
|
700
745
|
|
|
701
746
|
async def locate(self) -> Location[Pol]:
|
|
702
747
|
"""Return the current polarisation"""
|
|
@@ -28,11 +28,18 @@ class MJPG(StandardReadable, Triggerable, ABC):
|
|
|
28
28
|
latest image from the stream to the `post_processing` method for child classes to handle.
|
|
29
29
|
"""
|
|
30
30
|
|
|
31
|
-
def __init__(
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
prefix: str,
|
|
34
|
+
name: str = "",
|
|
35
|
+
x_size_pv: str = "ArraySize1_RBV",
|
|
36
|
+
y_size_pv: str = "ArraySize2_RBV",
|
|
37
|
+
) -> None:
|
|
32
38
|
self.url = epics_signal_rw(str, prefix + "JPG_URL_RBV")
|
|
39
|
+
self.video_url = epics_signal_rw(str, prefix + "MJPG_URL_RBV")
|
|
33
40
|
|
|
34
|
-
self.x_size = epics_signal_r(int, prefix +
|
|
35
|
-
self.y_size = epics_signal_r(int, prefix +
|
|
41
|
+
self.x_size = epics_signal_r(int, prefix + x_size_pv)
|
|
42
|
+
self.y_size = epics_signal_r(int, prefix + y_size_pv)
|
|
36
43
|
|
|
37
44
|
with self.add_children_as_readables():
|
|
38
45
|
self.filename = soft_signal_rw(str)
|
|
File without changes
|
|
@@ -10,14 +10,16 @@ def _get_detector_radius_mm(detector_params: DetectorParams):
|
|
|
10
10
|
return 0.5 * _get_detector_max_size_mm(detector_params)
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
def _get_detector_max_size_mm(detector_params):
|
|
13
|
+
def _get_detector_max_size_mm(detector_params: DetectorParams):
|
|
14
14
|
return max(
|
|
15
15
|
detector_params.detector_size_constants.det_dimension.width,
|
|
16
16
|
detector_params.detector_size_constants.det_dimension.height,
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
def _get_beam_xy_accounting_for_roi(
|
|
20
|
+
def _get_beam_xy_accounting_for_roi(
|
|
21
|
+
detector_params: DetectorParams, det_distance_mm: float
|
|
22
|
+
):
|
|
21
23
|
beam_x = detector_params.beam_xy_converter.get_beam_xy_from_det_dist(
|
|
22
24
|
det_distance_mm, Axis.X_AXIS
|
|
23
25
|
)
|
dodal/devices/fast_grid_scan.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from abc import ABC, abstractmethod
|
|
3
|
+
from functools import partial
|
|
4
|
+
from math import isclose
|
|
3
5
|
from typing import Generic, TypeVar
|
|
4
6
|
|
|
5
7
|
import numpy as np
|
|
@@ -307,13 +309,23 @@ class FastGridScanCommon(
|
|
|
307
309
|
set_statuses = []
|
|
308
310
|
|
|
309
311
|
LOGGER.info("Applying gridscan parameters...")
|
|
312
|
+
|
|
310
313
|
# Create arguments for bps.mv
|
|
311
314
|
for key, signal in self._movable_params.items():
|
|
312
315
|
param_value = value.__dict__[key]
|
|
313
|
-
|
|
316
|
+
|
|
317
|
+
matcher = partial(isclose, param_value, abs_tol=0.001)
|
|
318
|
+
|
|
319
|
+
set_statuses.append(
|
|
320
|
+
set_and_wait_for_value(
|
|
321
|
+
signal, # type: ignore
|
|
322
|
+
param_value,
|
|
323
|
+
match_value=matcher,
|
|
324
|
+
)
|
|
325
|
+
)
|
|
314
326
|
|
|
315
327
|
# Counter should always start at 0
|
|
316
|
-
set_statuses.append(
|
|
328
|
+
set_statuses.append(set_and_wait_for_value(self.position_counter, 0))
|
|
317
329
|
|
|
318
330
|
LOGGER.info("Gridscan parameters applied, waiting for sets to complete...")
|
|
319
331
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from ophyd_async.core import Reference, derived_signal_r
|
|
2
|
+
|
|
3
|
+
from dodal.devices.aperturescatterguard import ApertureScatterguard
|
|
4
|
+
from dodal.devices.beamsize.beamsize import BeamsizeBase
|
|
5
|
+
from dodal.devices.i03.constants import BeamsizeConstants
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Beamsize(BeamsizeBase):
|
|
9
|
+
def __init__(self, aperture_scatterguard: ApertureScatterguard, name=""):
|
|
10
|
+
super().__init__(name=name)
|
|
11
|
+
self._aperture_scatterguard_ref = Reference(aperture_scatterguard)
|
|
12
|
+
|
|
13
|
+
with self.add_children_as_readables():
|
|
14
|
+
self.x_um = derived_signal_r(
|
|
15
|
+
self._get_beamsize_x,
|
|
16
|
+
aperture_radius=self._aperture_scatterguard_ref().radius,
|
|
17
|
+
derived_units="µm",
|
|
18
|
+
)
|
|
19
|
+
self.y_um = derived_signal_r(
|
|
20
|
+
self._get_beamsize_y,
|
|
21
|
+
aperture_radius=self._aperture_scatterguard_ref().radius,
|
|
22
|
+
derived_units="µm",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
def _get_beamsize_x(
|
|
26
|
+
self,
|
|
27
|
+
aperture_radius: float,
|
|
28
|
+
) -> float:
|
|
29
|
+
return min(aperture_radius, BeamsizeConstants.BEAM_WIDTH_UM)
|
|
30
|
+
|
|
31
|
+
def _get_beamsize_y(
|
|
32
|
+
self,
|
|
33
|
+
aperture_radius: float,
|
|
34
|
+
) -> float:
|
|
35
|
+
return min(aperture_radius, BeamsizeConstants.BEAM_HEIGHT_UM)
|
|
@@ -5,7 +5,7 @@ from ophyd_async.core import AsyncStatus, Reference, StandardReadable
|
|
|
5
5
|
|
|
6
6
|
from dodal.common.beamlines.beamline_parameters import get_beamline_parameters
|
|
7
7
|
from dodal.devices.i03.dcm import DCM
|
|
8
|
-
from dodal.devices.undulator import
|
|
8
|
+
from dodal.devices.undulator import UndulatorInKeV
|
|
9
9
|
from dodal.log import LOGGER
|
|
10
10
|
|
|
11
11
|
ENERGY_TIMEOUT_S: float = 30.0
|
|
@@ -31,7 +31,7 @@ class UndulatorDCM(StandardReadable, Movable[float]):
|
|
|
31
31
|
|
|
32
32
|
def __init__(
|
|
33
33
|
self,
|
|
34
|
-
undulator:
|
|
34
|
+
undulator: UndulatorInKeV,
|
|
35
35
|
dcm: DCM,
|
|
36
36
|
daq_configuration_path: str,
|
|
37
37
|
name: str = "",
|