dls-dodal 1.53.0__py3-none-any.whl → 1.55.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.53.0.dist-info → dls_dodal-1.55.0.dist-info}/METADATA +4 -6
- {dls_dodal-1.53.0.dist-info → dls_dodal-1.55.0.dist-info}/RECORD +74 -66
- dodal/_version.py +16 -3
- dodal/beamline_specific_utils/i05_shared.py +11 -0
- dodal/beamlines/__init__.py +1 -0
- dodal/beamlines/aithre.py +2 -3
- dodal/beamlines/b01_1.py +2 -2
- dodal/beamlines/b07.py +6 -3
- dodal/beamlines/b07_1.py +17 -6
- dodal/beamlines/b16.py +0 -1
- dodal/beamlines/b21.py +4 -6
- dodal/beamlines/i03.py +10 -31
- dodal/beamlines/i04.py +19 -45
- dodal/beamlines/i05.py +22 -0
- dodal/beamlines/i05_1.py +22 -0
- dodal/beamlines/i09.py +7 -3
- dodal/beamlines/i09_1.py +6 -3
- dodal/beamlines/i13_1.py +0 -1
- dodal/beamlines/i19_1.py +1 -2
- dodal/beamlines/i19_2.py +0 -1
- dodal/beamlines/i19_optics.py +1 -4
- dodal/beamlines/i20_1.py +38 -9
- dodal/beamlines/i23.py +1 -2
- dodal/beamlines/i24.py +0 -12
- dodal/beamlines/p60.py +13 -3
- dodal/common/beamlines/beamline_parameters.py +1 -1
- dodal/common/beamlines/device_helpers.py +0 -33
- dodal/devices/aithre_lasershaping/__init__.py +0 -0
- dodal/devices/aithre_lasershaping/goniometer.py +3 -26
- dodal/devices/aithre_lasershaping/laser_robot.py +2 -2
- dodal/devices/b07/__init__.py +2 -2
- dodal/devices/b07/enums.py +15 -0
- dodal/devices/b07_1/__init__.py +10 -1
- dodal/devices/b07_1/ccmc.py +79 -0
- dodal/devices/b07_1/enums.py +3 -0
- dodal/devices/electron_analyser/abstract/base_driver_io.py +25 -48
- dodal/devices/electron_analyser/abstract/base_region.py +9 -11
- dodal/devices/electron_analyser/abstract/types.py +12 -0
- dodal/devices/electron_analyser/specs/detector.py +9 -9
- dodal/devices/electron_analyser/specs/driver_io.py +54 -21
- dodal/devices/electron_analyser/specs/region.py +13 -8
- dodal/devices/electron_analyser/types.py +15 -6
- dodal/devices/electron_analyser/vgscienta/detector.py +18 -8
- dodal/devices/electron_analyser/vgscienta/driver_io.py +62 -24
- dodal/devices/electron_analyser/vgscienta/region.py +33 -16
- dodal/devices/focusing_mirror.py +1 -1
- dodal/devices/i03/undulator_dcm.py +8 -3
- dodal/devices/i05/__init__.py +3 -0
- dodal/devices/i05/enums.py +8 -0
- dodal/devices/i09/__init__.py +2 -2
- dodal/devices/i09/enums.py +16 -0
- dodal/devices/i09_1/__init__.py +2 -2
- dodal/devices/i09_1/enums.py +13 -0
- dodal/devices/i10/mirrors.py +2 -6
- dodal/devices/i13_1/merlin_controller.py +1 -1
- dodal/devices/i19/beamstop.py +2 -2
- dodal/devices/i24/aperture.py +1 -1
- dodal/devices/motors.py +75 -1
- dodal/devices/oav/oav_to_redis_forwarder.py +1 -1
- dodal/devices/oav/pin_image_recognition/__init__.py +1 -2
- dodal/devices/p60/__init__.py +8 -2
- dodal/devices/p60/enums.py +16 -0
- dodal/devices/robot.py +7 -4
- dodal/devices/tetramm.py +1 -2
- dodal/devices/webcam.py +2 -1
- dodal/devices/zebra/zebra.py +1 -1
- dodal/devices/zebra/zebra_controlled_shutter.py +1 -1
- dodal/devices/zocalo/__init__.py +2 -0
- dodal/devices/zocalo/zocalo_results.py +16 -26
- dodal/utils.py +3 -10
- {dls_dodal-1.53.0.dist-info → dls_dodal-1.55.0.dist-info}/WHEEL +0 -0
- {dls_dodal-1.53.0.dist-info → dls_dodal-1.55.0.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.53.0.dist-info → dls_dodal-1.55.0.dist-info}/licenses/LICENSE +0 -0
- {dls_dodal-1.53.0.dist-info → dls_dodal-1.55.0.dist-info}/top_level.txt +0 -0
dodal/beamlines/i20_1.py
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
|
+
from ophyd_async.epics.motor import Motor
|
|
4
|
+
from ophyd_async.fastcs.panda import HDFPanda
|
|
5
|
+
|
|
3
6
|
from dodal.common.beamlines.beamline_utils import (
|
|
4
7
|
device_factory,
|
|
8
|
+
get_path_provider,
|
|
5
9
|
set_path_provider,
|
|
6
10
|
)
|
|
7
11
|
from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline
|
|
8
12
|
from dodal.common.visit import RemoteDirectoryServiceClient, StaticVisitPathProvider
|
|
9
|
-
from dodal.devices.synchrotron import Synchrotron
|
|
10
13
|
from dodal.devices.turbo_slit import TurboSlit
|
|
11
14
|
from dodal.devices.xspress3.xspress3 import Xspress3
|
|
12
15
|
from dodal.log import set_beamline as set_log_beamline
|
|
@@ -31,9 +34,14 @@ set_path_provider(
|
|
|
31
34
|
)
|
|
32
35
|
)
|
|
33
36
|
|
|
37
|
+
"""
|
|
38
|
+
NOTE: Due to the CA gateway machine being switched off, PVs are not available remotely
|
|
39
|
+
and you need to be on the beamline network to access them.
|
|
40
|
+
The simplest way to do this is to `ssh i20-1-ws001` and run dodal connect i20_1 from there.
|
|
41
|
+
"""
|
|
34
42
|
|
|
35
|
-
|
|
36
|
-
@device_factory(
|
|
43
|
+
|
|
44
|
+
@device_factory()
|
|
37
45
|
def turbo_slit() -> TurboSlit:
|
|
38
46
|
"""
|
|
39
47
|
turboslit for selecting energy from the polychromator
|
|
@@ -42,18 +50,39 @@ def turbo_slit() -> TurboSlit:
|
|
|
42
50
|
return TurboSlit(f"{PREFIX.beamline_prefix}-OP-PCHRO-01:TS:")
|
|
43
51
|
|
|
44
52
|
|
|
53
|
+
@device_factory()
|
|
54
|
+
def turbo_slit_x() -> Motor:
|
|
55
|
+
"""
|
|
56
|
+
turbo slit x motor
|
|
57
|
+
"""
|
|
58
|
+
return Motor(f"{PREFIX.beamline_prefix}-OP-PCHRO-01:TS:XFINE")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@device_factory()
|
|
62
|
+
def panda() -> HDFPanda:
|
|
63
|
+
return HDFPanda(
|
|
64
|
+
f"{PREFIX.beamline_prefix}-EA-PANDA-02:", path_provider=get_path_provider()
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# Use mock device until motors are reconnected on the beamline
|
|
69
|
+
@device_factory(mock=True)
|
|
70
|
+
def alignment_x() -> Motor:
|
|
71
|
+
return Motor(f"{PREFIX.beamline_prefix}-MO-STAGE-01:X")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# Use mock device until motors are reconnected on the beamline
|
|
75
|
+
@device_factory(mock=True)
|
|
76
|
+
def alignment_y() -> Motor:
|
|
77
|
+
return Motor(f"{PREFIX.beamline_prefix}-MO-STAGE-01:Y")
|
|
78
|
+
|
|
79
|
+
|
|
45
80
|
@device_factory(skip=True)
|
|
46
81
|
def xspress3() -> Xspress3:
|
|
47
82
|
"""
|
|
48
83
|
16 channels Xspress3 detector
|
|
49
84
|
"""
|
|
50
|
-
|
|
51
85
|
return Xspress3(
|
|
52
86
|
f"{PREFIX.beamline_prefix}-EA-DET-03:",
|
|
53
87
|
num_channels=16,
|
|
54
88
|
)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
@device_factory()
|
|
58
|
-
def synchrotron() -> Synchrotron:
|
|
59
|
-
return Synchrotron()
|
dodal/beamlines/i23.py
CHANGED
|
@@ -72,7 +72,7 @@ def oav_pin_tip_detection() -> PinTipDetection:
|
|
|
72
72
|
@device_factory()
|
|
73
73
|
def shutter() -> ZebraShutter:
|
|
74
74
|
"""Get the i23 zebra controlled shutter."""
|
|
75
|
-
return ZebraShutter(f"{PREFIX.beamline_prefix}-EA-SHTR-01:"
|
|
75
|
+
return ZebraShutter(f"{PREFIX.beamline_prefix}-EA-SHTR-01:")
|
|
76
76
|
|
|
77
77
|
|
|
78
78
|
@device_factory()
|
|
@@ -85,7 +85,6 @@ def gonio() -> SixAxisGonio:
|
|
|
85
85
|
def zebra() -> Zebra:
|
|
86
86
|
"""Get the i23 zebra"""
|
|
87
87
|
return Zebra(
|
|
88
|
-
name="zebra",
|
|
89
88
|
prefix=f"{PREFIX.beamline_prefix}-EA-ZEBRA-01:ZEBRA:",
|
|
90
89
|
mapping=I23_ZEBRA_MAPPING,
|
|
91
90
|
)
|
dodal/beamlines/i24.py
CHANGED
|
@@ -51,7 +51,6 @@ def attenuator() -> ReadOnlyAttenuator:
|
|
|
51
51
|
existing object."""
|
|
52
52
|
return ReadOnlyAttenuator(
|
|
53
53
|
f"{PREFIX.beamline_prefix}-OP-ATTN-01:",
|
|
54
|
-
"attenuator",
|
|
55
54
|
)
|
|
56
55
|
|
|
57
56
|
|
|
@@ -62,7 +61,6 @@ def aperture() -> Aperture:
|
|
|
62
61
|
"""
|
|
63
62
|
return Aperture(
|
|
64
63
|
f"{PREFIX.beamline_prefix}-AL-APTR-01:",
|
|
65
|
-
"aperture",
|
|
66
64
|
)
|
|
67
65
|
|
|
68
66
|
|
|
@@ -73,7 +71,6 @@ def beamstop() -> Beamstop:
|
|
|
73
71
|
"""
|
|
74
72
|
return Beamstop(
|
|
75
73
|
f"{PREFIX.beamline_prefix}-MO-BS-01:",
|
|
76
|
-
"beamstop",
|
|
77
74
|
)
|
|
78
75
|
|
|
79
76
|
|
|
@@ -84,7 +81,6 @@ def backlight() -> DualBacklight:
|
|
|
84
81
|
"""
|
|
85
82
|
return DualBacklight(
|
|
86
83
|
prefix=PREFIX.beamline_prefix,
|
|
87
|
-
name="backlight",
|
|
88
84
|
)
|
|
89
85
|
|
|
90
86
|
|
|
@@ -94,7 +90,6 @@ def detector_motion() -> YZStage:
|
|
|
94
90
|
If this is called when already instantiated in i24, it will return the existing object.
|
|
95
91
|
"""
|
|
96
92
|
return YZStage(
|
|
97
|
-
name="detector_motion",
|
|
98
93
|
prefix=f"{PREFIX.beamline_prefix}-EA-DET-01:",
|
|
99
94
|
)
|
|
100
95
|
|
|
@@ -105,7 +100,6 @@ def dcm() -> DCM:
|
|
|
105
100
|
If this is called when already instantiated in i24, it will return the existing object.
|
|
106
101
|
"""
|
|
107
102
|
return DCM(
|
|
108
|
-
name="dcm",
|
|
109
103
|
prefix=PREFIX.beamline_prefix,
|
|
110
104
|
)
|
|
111
105
|
|
|
@@ -145,7 +139,6 @@ def pmac() -> PMAC:
|
|
|
145
139
|
# prefix not BL but ME14E
|
|
146
140
|
return PMAC(
|
|
147
141
|
"ME14E-MO-CHIP-01:",
|
|
148
|
-
"pmac",
|
|
149
142
|
)
|
|
150
143
|
|
|
151
144
|
|
|
@@ -153,7 +146,6 @@ def pmac() -> PMAC:
|
|
|
153
146
|
def oav() -> OAVBeamCentreFile:
|
|
154
147
|
return OAVBeamCentreFile(
|
|
155
148
|
prefix=f"{PREFIX.beamline_prefix}-DI-OAV-01:",
|
|
156
|
-
name="oav",
|
|
157
149
|
config=OAVConfigBeamCentre(ZOOM_PARAMS_FILE, DISPLAY_CONFIG),
|
|
158
150
|
)
|
|
159
151
|
|
|
@@ -165,7 +157,6 @@ def vgonio() -> VerticalGoniometer:
|
|
|
165
157
|
"""
|
|
166
158
|
return VerticalGoniometer(
|
|
167
159
|
f"{PREFIX.beamline_prefix}-MO-VGON-01:",
|
|
168
|
-
"vgonio",
|
|
169
160
|
)
|
|
170
161
|
|
|
171
162
|
|
|
@@ -175,7 +166,6 @@ def zebra() -> Zebra:
|
|
|
175
166
|
If this is called when already instantiated in i24, it will return the existing object.
|
|
176
167
|
"""
|
|
177
168
|
return Zebra(
|
|
178
|
-
name="zebra",
|
|
179
169
|
prefix=f"{PREFIX.beamline_prefix}-EA-ZEBRA-01:",
|
|
180
170
|
mapping=I24_ZEBRA_MAPPING,
|
|
181
171
|
)
|
|
@@ -188,7 +178,6 @@ def shutter() -> HutchShutter:
|
|
|
188
178
|
"""
|
|
189
179
|
return HutchShutter(
|
|
190
180
|
f"{PREFIX.beamline_prefix}-PS-SHTR-01:",
|
|
191
|
-
"shutter",
|
|
192
181
|
)
|
|
193
182
|
|
|
194
183
|
|
|
@@ -197,7 +186,6 @@ def focus_mirrors() -> FocusMirrorsMode:
|
|
|
197
186
|
"""Get the i24 focus mirror devise to find the beam size."""
|
|
198
187
|
return FocusMirrorsMode(
|
|
199
188
|
f"{PREFIX.beamline_prefix}-OP-MFM-01:",
|
|
200
|
-
"focus_mirrors",
|
|
201
189
|
)
|
|
202
190
|
|
|
203
191
|
|
dodal/beamlines/p60.py
CHANGED
|
@@ -3,7 +3,13 @@ from dodal.common.beamlines.beamline_utils import (
|
|
|
3
3
|
)
|
|
4
4
|
from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline
|
|
5
5
|
from dodal.devices.electron_analyser.vgscienta import VGScientaAnalyserDriverIO
|
|
6
|
-
from dodal.devices.p60 import
|
|
6
|
+
from dodal.devices.p60 import (
|
|
7
|
+
LabXraySource,
|
|
8
|
+
LabXraySourceReadable,
|
|
9
|
+
LensMode,
|
|
10
|
+
PassEnergy,
|
|
11
|
+
PsuMode,
|
|
12
|
+
)
|
|
7
13
|
from dodal.log import set_beamline as set_log_beamline
|
|
8
14
|
from dodal.utils import BeamlinePrefix, get_beamline_name
|
|
9
15
|
|
|
@@ -23,14 +29,18 @@ def mg_kalpha_source() -> LabXraySourceReadable:
|
|
|
23
29
|
return LabXraySourceReadable(LabXraySource.MG_KALPHA)
|
|
24
30
|
|
|
25
31
|
|
|
32
|
+
# Connect will work again after this work completed
|
|
33
|
+
# https://jira.diamond.ac.uk/browse/P60-13
|
|
26
34
|
@device_factory()
|
|
27
|
-
def analyser_driver() -> VGScientaAnalyserDriverIO[LensMode]:
|
|
35
|
+
def analyser_driver() -> VGScientaAnalyserDriverIO[LensMode, PsuMode, PassEnergy]:
|
|
28
36
|
energy_sources = {
|
|
29
37
|
"source1": al_kalpha_source().energy_ev,
|
|
30
38
|
"source2": mg_kalpha_source().energy_ev,
|
|
31
39
|
}
|
|
32
|
-
return VGScientaAnalyserDriverIO[LensMode](
|
|
40
|
+
return VGScientaAnalyserDriverIO[LensMode, PsuMode, PassEnergy](
|
|
33
41
|
prefix=f"{PREFIX.beamline_prefix}-EA-DET-01:CAM:",
|
|
34
42
|
lens_mode_type=LensMode,
|
|
43
|
+
psu_mode_type=PsuMode,
|
|
44
|
+
pass_energy_type=PassEnergy,
|
|
35
45
|
energy_sources=energy_sources,
|
|
36
46
|
)
|
|
@@ -8,7 +8,7 @@ BEAMLINE_PARAMETER_KEYWORDS = ["FB", "FULL", "deadtime"]
|
|
|
8
8
|
|
|
9
9
|
BEAMLINE_PARAMETER_PATHS = {
|
|
10
10
|
"i03": "/dls_sw/i03/software/daq_configuration/domain/beamlineParameters",
|
|
11
|
-
"i04": "/dls_sw/i04/software/gda_versions/
|
|
11
|
+
"i04": "/dls_sw/i04/software/gda_versions/gda_9_37/workspace_git/gda-mx.git/configurations/i04-config/scripts/beamlineParameters",
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
|
|
@@ -1,37 +1,4 @@
|
|
|
1
|
-
from dodal.common.beamlines.beamline_utils import device_instantiation
|
|
2
|
-
from dodal.devices.slits import Slits
|
|
3
|
-
from dodal.utils import skip_device
|
|
4
|
-
|
|
5
1
|
HDF5_SUFFIX = "HDF5:"
|
|
6
2
|
TIFF_SUFFIX = "TIFF:"
|
|
7
3
|
CAM_SUFFIX = "CAM:"
|
|
8
4
|
DET_SUFFIX = "DET:"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@skip_device()
|
|
12
|
-
def numbered_slits(
|
|
13
|
-
slit_number: int = 1,
|
|
14
|
-
wait_for_connection: bool = True,
|
|
15
|
-
fake_with_ophyd_sim: bool = False,
|
|
16
|
-
) -> Slits:
|
|
17
|
-
"""
|
|
18
|
-
Create a slits object following the {beamline}-AL-SLITS-{slit_number} PV
|
|
19
|
-
convention
|
|
20
|
-
|
|
21
|
-
Args:
|
|
22
|
-
slit_number: The number assigned to the slits in the control system, usually
|
|
23
|
-
its position in the assembly. Defaults to 1.
|
|
24
|
-
wait_for_connection: Require connection on instantiation. Defaults to True.
|
|
25
|
-
fake_with_ophyd_sim: Make a fake device. Defaults to False.
|
|
26
|
-
|
|
27
|
-
Returns:
|
|
28
|
-
Slits: A new slits object
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
return device_instantiation(
|
|
32
|
-
Slits,
|
|
33
|
-
f"slits_{slit_number}",
|
|
34
|
-
f"-AL-SLITS-{slit_number:02}:",
|
|
35
|
-
wait_for_connection,
|
|
36
|
-
fake_with_ophyd_sim,
|
|
37
|
-
)
|
|
File without changes
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import math
|
|
3
|
-
|
|
4
|
-
from ophyd_async.core import derived_signal_rw
|
|
5
1
|
from ophyd_async.epics.motor import Motor
|
|
6
2
|
|
|
7
|
-
from dodal.devices.motors import XYZStage
|
|
3
|
+
from dodal.devices.motors import XYZStage, create_axis_perp_to_rotation
|
|
8
4
|
|
|
9
5
|
|
|
10
6
|
class Goniometer(XYZStage):
|
|
@@ -23,26 +19,7 @@ class Goniometer(XYZStage):
|
|
|
23
19
|
self.sampy = Motor(prefix + "SAMPY")
|
|
24
20
|
self.sampz = Motor(prefix + "SAMPZ")
|
|
25
21
|
self.omega = Motor(prefix + "OMEGA")
|
|
26
|
-
self.vertical_position =
|
|
27
|
-
self.
|
|
28
|
-
self._set,
|
|
29
|
-
sampy=self.sampy,
|
|
30
|
-
sampz=self.sampz,
|
|
31
|
-
omega=self.omega,
|
|
22
|
+
self.vertical_position = create_axis_perp_to_rotation(
|
|
23
|
+
self.omega, self.sampz, self.sampy
|
|
32
24
|
)
|
|
33
25
|
super().__init__(name)
|
|
34
|
-
|
|
35
|
-
def _get(self, sampz: float, sampy: float, omega: float) -> float:
|
|
36
|
-
z_component = sampz * math.cos(math.radians(omega))
|
|
37
|
-
y_component = sampy * math.sin(math.radians(omega))
|
|
38
|
-
return z_component + y_component
|
|
39
|
-
|
|
40
|
-
async def _set(self, value: float) -> None:
|
|
41
|
-
omega = await self.omega.user_readback.get_value()
|
|
42
|
-
z_component = value * math.cos(math.radians(omega))
|
|
43
|
-
y_component = value * math.sin(math.radians(omega))
|
|
44
|
-
await asyncio.gather(
|
|
45
|
-
self.sampy.set(y_component),
|
|
46
|
-
self.sampz.set(z_component),
|
|
47
|
-
self.omega.set(omega),
|
|
48
|
-
)
|
|
@@ -16,7 +16,7 @@ class LidHeatEnable(StrictEnum):
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class LaserRobot(BartRobot):
|
|
19
|
-
def __init__(self,
|
|
19
|
+
def __init__(self, prefix: str, name: str = "") -> None:
|
|
20
20
|
self.dewar_lid_heater = epics_signal_rw(
|
|
21
21
|
LidHeatEnable, prefix + "DW_1_ENABLED", prefix + "DW_1_CTRL"
|
|
22
22
|
)
|
|
@@ -24,4 +24,4 @@ class LaserRobot(BartRobot):
|
|
|
24
24
|
self.set_beamline_safe = epics_signal_rw(
|
|
25
25
|
ForceBit, prefix + "IP_16_FORCE_OPTION"
|
|
26
26
|
)
|
|
27
|
-
super().__init__(
|
|
27
|
+
super().__init__(prefix, name)
|
dodal/devices/b07/__init__.py
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
from dodal.devices.b07.enums import Grating, LensMode
|
|
1
|
+
from dodal.devices.b07.enums import Grating, LensMode, PsuMode
|
|
2
2
|
|
|
3
|
-
__all__ = ["Grating", "LensMode"]
|
|
3
|
+
__all__ = ["Grating", "LensMode", "PsuMode"]
|
dodal/devices/b07/enums.py
CHANGED
|
@@ -21,4 +21,19 @@ class LensMode(SupersetEnum):
|
|
|
21
21
|
MEDIUM_AREA = "MediumArea"
|
|
22
22
|
SMALL_AREA = "SmallArea"
|
|
23
23
|
HIGH_MAGNIFICATION2 = "HighMagnification2"
|
|
24
|
+
# This is connected to the device separately and will only have "Not connected" as
|
|
25
|
+
# option if disconnected. Once it is connected, "Not connected" is replaced with the
|
|
26
|
+
# options above. This is also why this must be a SupersetEnum.
|
|
27
|
+
NOT_CONNECTED = "Not connected"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class PsuMode(SupersetEnum):
|
|
31
|
+
V3500 = "3.5kV"
|
|
32
|
+
V1500 = "1.5kV"
|
|
33
|
+
V400 = "400V"
|
|
34
|
+
V100 = "100V"
|
|
35
|
+
V10 = "10V"
|
|
36
|
+
# This is connected to the device separately and will only have "Not connected" as
|
|
37
|
+
# option if disconnected. Once it is connected, "Not connected" is replaced with the
|
|
38
|
+
# options above. This is also why this must be a SupersetEnum.
|
|
24
39
|
NOT_CONNECTED = "Not connected"
|
dodal/devices/b07_1/__init__.py
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
from dodal.devices.b07_1.ccmc import (
|
|
2
|
+
ChannelCutMonochromator,
|
|
3
|
+
ChannelCutMonochromatorPositions,
|
|
4
|
+
)
|
|
1
5
|
from dodal.devices.b07_1.enums import Grating, LensMode
|
|
2
6
|
|
|
3
|
-
__all__ = [
|
|
7
|
+
__all__ = [
|
|
8
|
+
"Grating",
|
|
9
|
+
"LensMode",
|
|
10
|
+
"ChannelCutMonochromator",
|
|
11
|
+
"ChannelCutMonochromatorPositions",
|
|
12
|
+
]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from bluesky.protocols import Movable
|
|
2
|
+
from ophyd_async.core import (
|
|
3
|
+
AsyncStatus,
|
|
4
|
+
StandardReadable,
|
|
5
|
+
StrictEnum,
|
|
6
|
+
derived_signal_r,
|
|
7
|
+
)
|
|
8
|
+
from ophyd_async.epics.core import epics_signal_rw
|
|
9
|
+
|
|
10
|
+
from dodal.devices.motors import XYZStage
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ChannelCutMonochromatorPositions(StrictEnum):
|
|
14
|
+
OUT = "Out of Beam"
|
|
15
|
+
XTAL_2000 = "Xtal_2000"
|
|
16
|
+
XTAL_2250 = "Xtal_2250"
|
|
17
|
+
XTAL_2500 = "Xtal_2500"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
ccmc_lower_limit = 1500.0
|
|
21
|
+
ccmc_upper_limit = 3000.0
|
|
22
|
+
error_message = "Can not get energy value in eV from ccmc position: "
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ChannelCutMonochromator(
|
|
26
|
+
StandardReadable, Movable[ChannelCutMonochromatorPositions]
|
|
27
|
+
):
|
|
28
|
+
"""
|
|
29
|
+
Device to move the channel cut monochromator (ccmc). CCMC has three
|
|
30
|
+
choices of crystal (Xtal for short). Setting energy is by means of a
|
|
31
|
+
multi-positioner. The positions are named after the nominal energies of the
|
|
32
|
+
crystals. To change energy select one of the crystals from the list.
|
|
33
|
+
This causes the Y motor to move that crystal into the beam and other
|
|
34
|
+
motors have to align the angles correctly.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
prefix: str,
|
|
40
|
+
name: str = "",
|
|
41
|
+
) -> None:
|
|
42
|
+
"""
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
prefix:
|
|
46
|
+
Beamline specific part of the PV
|
|
47
|
+
name:
|
|
48
|
+
Name of the device
|
|
49
|
+
"""
|
|
50
|
+
with self.add_children_as_readables():
|
|
51
|
+
# crystal motors
|
|
52
|
+
self._xyz = XYZStage(prefix)
|
|
53
|
+
# piezo motor in epics
|
|
54
|
+
self._y_rotation = epics_signal_rw(
|
|
55
|
+
float,
|
|
56
|
+
read_pv=prefix + "ROTY:POS:RD",
|
|
57
|
+
write_pv=prefix + "ROTY:MOV:WR",
|
|
58
|
+
)
|
|
59
|
+
# Must be a CHILD as read() must return this signal
|
|
60
|
+
self.crystal = epics_signal_rw(
|
|
61
|
+
ChannelCutMonochromatorPositions, prefix + "CRYSTAL:MP:SELECT"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# energy derived signal as property
|
|
65
|
+
self.energy_in_ev = derived_signal_r(
|
|
66
|
+
self._convert_pos_to_ev, pos_signal=self.crystal
|
|
67
|
+
)
|
|
68
|
+
super().__init__(name=name)
|
|
69
|
+
|
|
70
|
+
def _convert_pos_to_ev(self, pos_signal: ChannelCutMonochromatorPositions) -> float:
|
|
71
|
+
if pos_signal != ChannelCutMonochromatorPositions.OUT:
|
|
72
|
+
energy = float(str(pos_signal.value).split("Xtal_")[1])
|
|
73
|
+
if ccmc_lower_limit < energy < ccmc_upper_limit:
|
|
74
|
+
return energy
|
|
75
|
+
raise ValueError(error_message)
|
|
76
|
+
|
|
77
|
+
@AsyncStatus.wrap
|
|
78
|
+
async def set(self, value: ChannelCutMonochromatorPositions) -> None:
|
|
79
|
+
await self.crystal.set(value, wait=True)
|
dodal/devices/b07_1/enums.py
CHANGED
|
@@ -15,4 +15,7 @@ class LensMode(SupersetEnum):
|
|
|
15
15
|
ANGLE_RESOLVED_MODE_22 = "AngleResolvedMode22"
|
|
16
16
|
ANGLE_RESOLVED_MODE_30 = "AngleResolvedMode30"
|
|
17
17
|
LARGE_AREA = "LargeArea"
|
|
18
|
+
# This is connected to the device separately and will only have "Not connected" as
|
|
19
|
+
# option if disconnected. Once it is connected, "Not connected" is replaced with the
|
|
20
|
+
# options above. This is also why this must be a SupersetEnum.
|
|
18
21
|
NOT_CONNECTED = "Not connected"
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import asyncio
|
|
2
1
|
from abc import ABC, abstractmethod
|
|
3
2
|
from collections.abc import Mapping
|
|
4
3
|
from typing import Generic, TypeVar
|
|
@@ -19,11 +18,15 @@ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
|
|
|
19
18
|
|
|
20
19
|
from dodal.devices.electron_analyser.abstract.base_region import (
|
|
21
20
|
TAbstractBaseRegion,
|
|
21
|
+
)
|
|
22
|
+
from dodal.devices.electron_analyser.abstract.types import (
|
|
22
23
|
TAcquisitionMode,
|
|
23
24
|
TLensMode,
|
|
25
|
+
TPassEnergy,
|
|
26
|
+
TPsuMode,
|
|
24
27
|
)
|
|
25
28
|
from dodal.devices.electron_analyser.enums import EnergyMode
|
|
26
|
-
from dodal.devices.electron_analyser.util import to_binding_energy
|
|
29
|
+
from dodal.devices.electron_analyser.util import to_binding_energy
|
|
27
30
|
|
|
28
31
|
|
|
29
32
|
class AbstractAnalyserDriverIO(
|
|
@@ -31,11 +34,12 @@ class AbstractAnalyserDriverIO(
|
|
|
31
34
|
StandardReadable,
|
|
32
35
|
ADBaseIO,
|
|
33
36
|
Movable[TAbstractBaseRegion],
|
|
34
|
-
Generic[TAbstractBaseRegion, TAcquisitionMode, TLensMode],
|
|
37
|
+
Generic[TAbstractBaseRegion, TAcquisitionMode, TLensMode, TPsuMode, TPassEnergy],
|
|
35
38
|
):
|
|
36
39
|
"""
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
Driver device that defines signals and readables that should be common to all
|
|
41
|
+
electron analysers. Implementations of electron analyser devices should inherit
|
|
42
|
+
from this class and define additional specialised signals and methods.
|
|
39
43
|
"""
|
|
40
44
|
|
|
41
45
|
def __init__(
|
|
@@ -43,6 +47,8 @@ class AbstractAnalyserDriverIO(
|
|
|
43
47
|
prefix: str,
|
|
44
48
|
acquisition_mode_type: type[TAcquisitionMode],
|
|
45
49
|
lens_mode_type: type[TLensMode],
|
|
50
|
+
psu_mode_type: type[TPsuMode],
|
|
51
|
+
pass_energy_type: type[TPassEnergy],
|
|
46
52
|
energy_sources: Mapping[str, SignalR[float]],
|
|
47
53
|
name: str = "",
|
|
48
54
|
) -> None:
|
|
@@ -55,6 +61,10 @@ class AbstractAnalyserDriverIO(
|
|
|
55
61
|
for this device.
|
|
56
62
|
lens_mode_type: Enum that determines the available lens mode for this
|
|
57
63
|
device.
|
|
64
|
+
psu_mode_type: Enum that determines the available psu modes for this device.
|
|
65
|
+
pass_energy_type: Can be enum or float, depends on electron analyser model.
|
|
66
|
+
If enum, it determines the available pass energies for
|
|
67
|
+
this device.
|
|
58
68
|
energy_sources: Map that pairs a source name to an energy value signal
|
|
59
69
|
(in eV).
|
|
60
70
|
name: Name of the device.
|
|
@@ -62,6 +72,8 @@ class AbstractAnalyserDriverIO(
|
|
|
62
72
|
self.energy_sources = energy_sources
|
|
63
73
|
self.acquisition_mode_type = acquisition_mode_type
|
|
64
74
|
self.lens_mode_type = lens_mode_type
|
|
75
|
+
self.psu_mode_type = psu_mode_type
|
|
76
|
+
self.pass_energy_type = pass_energy_type
|
|
65
77
|
|
|
66
78
|
with self.add_children_as_readables():
|
|
67
79
|
self.image = epics_signal_r(Array1D[np.float64], prefix + "IMAGE")
|
|
@@ -78,18 +90,20 @@ class AbstractAnalyserDriverIO(
|
|
|
78
90
|
EnergyMode, initial_value=EnergyMode.KINETIC
|
|
79
91
|
)
|
|
80
92
|
self.low_energy = epics_signal_rw(float, prefix + "LOW_ENERGY")
|
|
93
|
+
self.centre_energy = epics_signal_rw(float, prefix + "CENTRE_ENERGY")
|
|
81
94
|
self.high_energy = epics_signal_rw(float, prefix + "HIGH_ENERGY")
|
|
82
95
|
self.slices = epics_signal_rw(int, prefix + "SLICES")
|
|
83
96
|
self.lens_mode = epics_signal_rw(lens_mode_type, prefix + "LENS_MODE")
|
|
84
|
-
self.pass_energy = epics_signal_rw(
|
|
85
|
-
self.pass_energy_type, prefix + "PASS_ENERGY"
|
|
86
|
-
)
|
|
97
|
+
self.pass_energy = epics_signal_rw(pass_energy_type, prefix + "PASS_ENERGY")
|
|
87
98
|
self.energy_step = epics_signal_rw(float, prefix + "STEP_SIZE")
|
|
88
99
|
self.iterations = epics_signal_rw(int, prefix + "NumExposures")
|
|
89
100
|
self.acquisition_mode = epics_signal_rw(
|
|
90
101
|
acquisition_mode_type, prefix + "ACQ_MODE"
|
|
91
102
|
)
|
|
92
103
|
self.excitation_energy_source = soft_signal_rw(str, initial_value="")
|
|
104
|
+
# This is used by each electron analyser, however it depends on the electron
|
|
105
|
+
# analyser type to know if is moved with region settings.
|
|
106
|
+
self.psu_mode = epics_signal_rw(psu_mode_type, prefix + "PSU_MODE")
|
|
93
107
|
|
|
94
108
|
with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
|
|
95
109
|
# Read once per scan after data acquired
|
|
@@ -114,42 +128,17 @@ class AbstractAnalyserDriverIO(
|
|
|
114
128
|
|
|
115
129
|
super().__init__(prefix=prefix, name=name)
|
|
116
130
|
|
|
131
|
+
@abstractmethod
|
|
117
132
|
@AsyncStatus.wrap
|
|
118
133
|
async def set(self, region: TAbstractBaseRegion):
|
|
119
134
|
"""
|
|
120
|
-
|
|
121
|
-
|
|
135
|
+
Move a group of signals defined in a region. Each implementation of this class
|
|
136
|
+
is responsible for implementing this method correctly.
|
|
122
137
|
|
|
123
138
|
Args:
|
|
124
139
|
region: Contains the parameters to setup the driver for a scan.
|
|
125
140
|
"""
|
|
126
141
|
|
|
127
|
-
source = self._get_energy_source(region.excitation_energy_source)
|
|
128
|
-
excitation_energy = await source.get_value() # eV
|
|
129
|
-
|
|
130
|
-
pass_energy_type = self.pass_energy_type
|
|
131
|
-
pass_energy = pass_energy_type(region.pass_energy)
|
|
132
|
-
|
|
133
|
-
low_energy = to_kinetic_energy(
|
|
134
|
-
region.low_energy, region.energy_mode, excitation_energy
|
|
135
|
-
)
|
|
136
|
-
high_energy = to_kinetic_energy(
|
|
137
|
-
region.high_energy, region.energy_mode, excitation_energy
|
|
138
|
-
)
|
|
139
|
-
await asyncio.gather(
|
|
140
|
-
self.region_name.set(region.name),
|
|
141
|
-
self.energy_mode.set(region.energy_mode),
|
|
142
|
-
self.low_energy.set(low_energy),
|
|
143
|
-
self.high_energy.set(high_energy),
|
|
144
|
-
self.slices.set(region.slices),
|
|
145
|
-
self.lens_mode.set(region.lens_mode),
|
|
146
|
-
self.pass_energy.set(pass_energy),
|
|
147
|
-
self.iterations.set(region.iterations),
|
|
148
|
-
self.acquisition_mode.set(region.acquisition_mode),
|
|
149
|
-
self.excitation_energy.set(excitation_energy),
|
|
150
|
-
self.excitation_energy_source.set(source.name),
|
|
151
|
-
)
|
|
152
|
-
|
|
153
142
|
def _get_energy_source(self, alias_name: str) -> SignalR[float]:
|
|
154
143
|
energy_source = self.energy_sources.get(alias_name)
|
|
155
144
|
if energy_source is None:
|
|
@@ -233,18 +222,6 @@ class AbstractAnalyserDriverIO(
|
|
|
233
222
|
def _calculate_total_intensity(self, spectrum: Array1D[np.float64]) -> float:
|
|
234
223
|
return float(np.sum(spectrum, dtype=np.float64))
|
|
235
224
|
|
|
236
|
-
@property
|
|
237
|
-
@abstractmethod
|
|
238
|
-
def pass_energy_type(self) -> type:
|
|
239
|
-
"""
|
|
240
|
-
Return the type the pass_energy should be. Depends on underlying analyser
|
|
241
|
-
software.
|
|
242
|
-
|
|
243
|
-
Returns:
|
|
244
|
-
Type the pass energy parameter from a region needs to be cast to so it can
|
|
245
|
-
be set correctly on the signal.
|
|
246
|
-
"""
|
|
247
|
-
|
|
248
225
|
|
|
249
226
|
TAbstractAnalyserDriverIO = TypeVar(
|
|
250
227
|
"TAbstractAnalyserDriverIO", bound=AbstractAnalyserDriverIO
|
|
@@ -3,9 +3,13 @@ from abc import ABC
|
|
|
3
3
|
from collections.abc import Callable
|
|
4
4
|
from typing import Generic, TypeVar
|
|
5
5
|
|
|
6
|
-
from ophyd_async.core import StrictEnum, SupersetEnum
|
|
7
6
|
from pydantic import BaseModel, Field, model_validator
|
|
8
7
|
|
|
8
|
+
from dodal.devices.electron_analyser.abstract.types import (
|
|
9
|
+
TAcquisitionMode,
|
|
10
|
+
TLensMode,
|
|
11
|
+
TPassEnergy,
|
|
12
|
+
)
|
|
9
13
|
from dodal.devices.electron_analyser.enums import EnergyMode
|
|
10
14
|
|
|
11
15
|
|
|
@@ -44,17 +48,10 @@ def energy_mode_validation(data: dict) -> dict:
|
|
|
44
48
|
return data
|
|
45
49
|
|
|
46
50
|
|
|
47
|
-
TAcquisitionMode = TypeVar("TAcquisitionMode", bound=StrictEnum)
|
|
48
|
-
# Allow SupersetEnum. Specs analysers can connect to Lens mode separately to the
|
|
49
|
-
# analyser which leaves the enum to either be "Not connected" OR the available enums
|
|
50
|
-
# when connected.
|
|
51
|
-
TLensMode = TypeVar("TLensMode", bound=SupersetEnum | StrictEnum)
|
|
52
|
-
|
|
53
|
-
|
|
54
51
|
class AbstractBaseRegion(
|
|
55
52
|
ABC,
|
|
56
53
|
JavaToPythonModel,
|
|
57
|
-
Generic[TAcquisitionMode, TLensMode],
|
|
54
|
+
Generic[TAcquisitionMode, TLensMode, TPassEnergy],
|
|
58
55
|
):
|
|
59
56
|
"""
|
|
60
57
|
Generic region model that holds the data. Specialised region models should inherit
|
|
@@ -68,9 +65,10 @@ class AbstractBaseRegion(
|
|
|
68
65
|
excitation_energy_source: str = "source1"
|
|
69
66
|
# These ones we need subclasses to provide default values
|
|
70
67
|
lens_mode: TLensMode
|
|
71
|
-
pass_energy:
|
|
68
|
+
pass_energy: TPassEnergy
|
|
72
69
|
acquisition_mode: TAcquisitionMode
|
|
73
70
|
low_energy: float
|
|
71
|
+
centre_energy: float
|
|
74
72
|
high_energy: float
|
|
75
73
|
step_time: float
|
|
76
74
|
energy_step: float # in eV
|
|
@@ -95,7 +93,7 @@ TAbstractBaseRegion = TypeVar("TAbstractBaseRegion", bound=AbstractBaseRegion)
|
|
|
95
93
|
class AbstractBaseSequence(
|
|
96
94
|
ABC,
|
|
97
95
|
JavaToPythonModel,
|
|
98
|
-
Generic[TAbstractBaseRegion
|
|
96
|
+
Generic[TAbstractBaseRegion],
|
|
99
97
|
):
|
|
100
98
|
"""
|
|
101
99
|
Generic sequence model that holds the list of region data. Specialised sequence
|