dls-dodal 1.63.0__py3-none-any.whl → 1.65.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.63.0.dist-info → dls_dodal-1.65.0.dist-info}/METADATA +3 -3
- {dls_dodal-1.63.0.dist-info → dls_dodal-1.65.0.dist-info}/RECORD +47 -39
- dodal/_version.py +2 -2
- dodal/beamline_specific_utils/i05_shared.py +6 -3
- dodal/beamlines/b01_1.py +1 -1
- dodal/beamlines/b07.py +6 -3
- dodal/beamlines/b07_1.py +6 -3
- dodal/beamlines/i03.py +9 -1
- dodal/beamlines/i05.py +2 -2
- dodal/beamlines/i05_1.py +2 -2
- dodal/beamlines/i07.py +21 -0
- dodal/beamlines/i09.py +4 -4
- dodal/beamlines/i09_1.py +7 -1
- dodal/beamlines/i09_2.py +36 -3
- dodal/beamlines/i10_optics.py +53 -27
- dodal/beamlines/i17.py +21 -11
- dodal/beamlines/i19_2.py +22 -0
- dodal/beamlines/i21.py +34 -4
- dodal/beamlines/i22.py +0 -17
- dodal/beamlines/k07.py +6 -3
- dodal/cli.py +3 -3
- dodal/devices/apple2_undulator.py +19 -17
- dodal/devices/b07_1/ccmc.py +1 -1
- dodal/devices/common_dcm.py +3 -3
- dodal/devices/cryostream.py +21 -0
- dodal/devices/i03/undulator_dcm.py +1 -1
- dodal/devices/i07/__init__.py +0 -0
- dodal/devices/i07/dcm.py +33 -0
- dodal/devices/i09_1_shared/__init__.py +3 -0
- dodal/devices/i09_1_shared/hard_undulator_functions.py +111 -0
- dodal/devices/i10/i10_apple2.py +4 -4
- dodal/devices/i15/dcm.py +1 -1
- dodal/devices/i22/dcm.py +1 -1
- dodal/devices/i22/nxsas.py +5 -24
- dodal/devices/pgm.py +1 -1
- dodal/devices/scintillator.py +4 -0
- dodal/devices/undulator.py +29 -1
- dodal/devices/util/lookup_tables.py +8 -2
- dodal/plan_stubs/__init__.py +3 -0
- dodal/plans/verify_undulator_gap.py +2 -2
- dodal/testing/fixtures/__init__.py +0 -0
- dodal/testing/fixtures/run_engine.py +118 -0
- dodal/testing/fixtures/utils.py +57 -0
- {dls_dodal-1.63.0.dist-info → dls_dodal-1.65.0.dist-info}/WHEEL +0 -0
- {dls_dodal-1.63.0.dist-info → dls_dodal-1.65.0.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.63.0.dist-info → dls_dodal-1.65.0.dist-info}/licenses/LICENSE +0 -0
- {dls_dodal-1.63.0.dist-info → dls_dodal-1.65.0.dist-info}/top_level.txt +0 -0
dodal/beamlines/i17.py
CHANGED
|
@@ -17,7 +17,7 @@ from dodal.devices.apple2_undulator import (
|
|
|
17
17
|
UndulatorPhaseAxes,
|
|
18
18
|
)
|
|
19
19
|
from dodal.devices.i17.i17_apple2 import I17Apple2Controller
|
|
20
|
-
from dodal.devices.pgm import
|
|
20
|
+
from dodal.devices.pgm import PlaneGratingMonochromator
|
|
21
21
|
from dodal.devices.synchrotron import Synchrotron
|
|
22
22
|
from dodal.log import set_beamline as set_log_beamline
|
|
23
23
|
from dodal.utils import BeamlinePrefix, get_beamline_name
|
|
@@ -39,26 +39,36 @@ def synchrotron() -> Synchrotron:
|
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
@device_factory(skip=True)
|
|
42
|
-
def pgm() ->
|
|
43
|
-
return
|
|
42
|
+
def pgm() -> PlaneGratingMonochromator:
|
|
43
|
+
return PlaneGratingMonochromator(
|
|
44
44
|
prefix=f"{PREFIX.beamline_prefix}-OP-PGM-01:",
|
|
45
45
|
grating=I17Grating,
|
|
46
46
|
grating_pv="NLINES2",
|
|
47
47
|
)
|
|
48
48
|
|
|
49
49
|
|
|
50
|
+
@device_factory()
|
|
51
|
+
def id_gap() -> UndulatorGap:
|
|
52
|
+
return UndulatorGap(prefix=f"{PREFIX.insertion_prefix}-MO-SERVC-01:")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@device_factory()
|
|
56
|
+
def id_phase() -> UndulatorPhaseAxes:
|
|
57
|
+
return UndulatorPhaseAxes(
|
|
58
|
+
prefix=f"{PREFIX.insertion_prefix}-MO-SERVC-01:",
|
|
59
|
+
top_outer="RPQ1",
|
|
60
|
+
top_inner="RPQ2",
|
|
61
|
+
btm_inner="RPQ3",
|
|
62
|
+
btm_outer="RPQ4",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
50
66
|
@device_factory(skip=True)
|
|
51
67
|
def id() -> Apple2:
|
|
52
68
|
"""I17 insertion device:"""
|
|
53
69
|
return Apple2(
|
|
54
|
-
id_gap=
|
|
55
|
-
id_phase=
|
|
56
|
-
prefix=f"{PREFIX.insertion_prefix}-MO-SERVC-01:",
|
|
57
|
-
top_outer="RPQ1",
|
|
58
|
-
top_inner="RPQ2",
|
|
59
|
-
btm_inner="RPQ3",
|
|
60
|
-
btm_outer="RPQ4",
|
|
61
|
-
),
|
|
70
|
+
id_gap=id_gap(),
|
|
71
|
+
id_phase=id_phase(),
|
|
62
72
|
)
|
|
63
73
|
|
|
64
74
|
|
dodal/beamlines/i19_2.py
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from ophyd_async.fastcs.eiger import EigerDetector
|
|
1
4
|
from ophyd_async.fastcs.panda import HDFPanda
|
|
2
5
|
|
|
3
6
|
from dodal.common.beamlines.beamline_utils import (
|
|
4
7
|
device_factory,
|
|
5
8
|
get_path_provider,
|
|
9
|
+
set_path_provider,
|
|
6
10
|
)
|
|
7
11
|
from dodal.common.beamlines.beamline_utils import (
|
|
8
12
|
set_beamline as set_utils_beamline,
|
|
9
13
|
)
|
|
14
|
+
from dodal.common.visit import StaticVisitPathProvider
|
|
10
15
|
from dodal.devices.i19.access_controlled.blueapi_device import HutchState
|
|
11
16
|
from dodal.devices.i19.access_controlled.shutter import AccessControlledShutter
|
|
12
17
|
from dodal.devices.i19.backlight import BacklightPosition
|
|
@@ -31,6 +36,13 @@ PREFIX = BeamlinePrefix("i19", "I")
|
|
|
31
36
|
set_log_beamline(BL)
|
|
32
37
|
set_utils_beamline(BL)
|
|
33
38
|
|
|
39
|
+
set_path_provider(
|
|
40
|
+
StaticVisitPathProvider(
|
|
41
|
+
BL,
|
|
42
|
+
Path("/dls/i19-2/data/2025/cm40639-4/"),
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
|
|
34
46
|
|
|
35
47
|
I19_2_ZEBRA_MAPPING = ZebraMapping(
|
|
36
48
|
outputs=ZebraTTLOutputs(),
|
|
@@ -105,3 +117,13 @@ def panda() -> HDFPanda:
|
|
|
105
117
|
prefix=f"{PREFIX.beamline_prefix}-EA-PANDA-01:",
|
|
106
118
|
path_provider=get_path_provider(),
|
|
107
119
|
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@device_factory()
|
|
123
|
+
def eiger() -> EigerDetector:
|
|
124
|
+
return EigerDetector(
|
|
125
|
+
prefix=PREFIX.beamline_prefix,
|
|
126
|
+
path_provider=get_path_provider(),
|
|
127
|
+
drv_suffix="-EA-EIGER-01:",
|
|
128
|
+
hdf_suffix="-EA-EIGER-01:OD:",
|
|
129
|
+
)
|
dodal/beamlines/i21.py
CHANGED
|
@@ -2,14 +2,19 @@ from dodal.common.beamlines.beamline_utils import (
|
|
|
2
2
|
device_factory,
|
|
3
3
|
)
|
|
4
4
|
from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline
|
|
5
|
+
from dodal.devices.apple2_undulator import (
|
|
6
|
+
Apple2,
|
|
7
|
+
UndulatorGap,
|
|
8
|
+
UndulatorPhaseAxes,
|
|
9
|
+
)
|
|
5
10
|
from dodal.devices.i21 import Grating
|
|
6
|
-
from dodal.devices.pgm import
|
|
11
|
+
from dodal.devices.pgm import PlaneGratingMonochromator
|
|
7
12
|
from dodal.devices.synchrotron import Synchrotron
|
|
8
13
|
from dodal.log import set_beamline as set_log_beamline
|
|
9
14
|
from dodal.utils import BeamlinePrefix, get_beamline_name
|
|
10
15
|
|
|
11
16
|
BL = get_beamline_name("i21")
|
|
12
|
-
PREFIX = BeamlinePrefix(BL
|
|
17
|
+
PREFIX = BeamlinePrefix(BL)
|
|
13
18
|
set_log_beamline(BL)
|
|
14
19
|
set_utils_beamline(BL)
|
|
15
20
|
|
|
@@ -20,8 +25,33 @@ def synchrotron() -> Synchrotron:
|
|
|
20
25
|
|
|
21
26
|
|
|
22
27
|
@device_factory()
|
|
23
|
-
def pgm() ->
|
|
24
|
-
return
|
|
28
|
+
def pgm() -> PlaneGratingMonochromator:
|
|
29
|
+
return PlaneGratingMonochromator(
|
|
25
30
|
prefix=f"{PREFIX.beamline_prefix}-OP-PGM-01:",
|
|
26
31
|
grating=Grating,
|
|
27
32
|
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@device_factory()
|
|
36
|
+
def id_gap() -> UndulatorGap:
|
|
37
|
+
return UndulatorGap(prefix=f"{PREFIX.insertion_prefix}-MO-SERVC-01:")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@device_factory()
|
|
41
|
+
def id_phase() -> UndulatorPhaseAxes:
|
|
42
|
+
return UndulatorPhaseAxes(
|
|
43
|
+
prefix=f"{PREFIX.insertion_prefix}-MO-SERVC-01:",
|
|
44
|
+
top_outer="PUO",
|
|
45
|
+
top_inner="PUI",
|
|
46
|
+
btm_inner="PLI",
|
|
47
|
+
btm_outer="PLO",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@device_factory()
|
|
52
|
+
def id() -> Apple2:
|
|
53
|
+
"""I21 insertion device."""
|
|
54
|
+
return Apple2(
|
|
55
|
+
id_gap=id_gap(),
|
|
56
|
+
id_phase=id_phase(),
|
|
57
|
+
)
|
dodal/beamlines/i22.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
|
|
3
1
|
from ophyd_async.epics.adaravis import AravisDetector
|
|
4
2
|
from ophyd_async.epics.adcore import NDPluginBaseIO, NDPluginStatsIO
|
|
5
3
|
from ophyd_async.epics.adpilatus import PilatusDetector
|
|
@@ -8,7 +6,6 @@ from ophyd_async.fastcs.panda import HDFPanda
|
|
|
8
6
|
from dodal.common.beamlines.beamline_utils import (
|
|
9
7
|
device_factory,
|
|
10
8
|
get_path_provider,
|
|
11
|
-
set_path_provider,
|
|
12
9
|
)
|
|
13
10
|
from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline
|
|
14
11
|
from dodal.common.beamlines.device_helpers import CAM_SUFFIX, DET_SUFFIX, HDF5_SUFFIX
|
|
@@ -16,7 +13,6 @@ from dodal.common.crystal_metadata import (
|
|
|
16
13
|
MaterialsEnum,
|
|
17
14
|
make_crystal_metadata_from_material,
|
|
18
15
|
)
|
|
19
|
-
from dodal.common.visit import RemoteDirectoryServiceClient, StaticVisitPathProvider
|
|
20
16
|
from dodal.devices.bimorph_mirror import BimorphMirror
|
|
21
17
|
from dodal.devices.focusing_mirror import FocusingMirror
|
|
22
18
|
from dodal.devices.i22.dcm import DCM
|
|
@@ -37,19 +33,6 @@ PREFIX = BeamlinePrefix(BL)
|
|
|
37
33
|
set_log_beamline(BL)
|
|
38
34
|
set_utils_beamline(BL)
|
|
39
35
|
|
|
40
|
-
# Currently we must hard-code the visit, determining the visit at runtime requires
|
|
41
|
-
# infrastructure that is still WIP.
|
|
42
|
-
# Communication with GDA is also WIP so for now we determine an arbitrary scan number
|
|
43
|
-
# locally and write the commissioning directory. The scan number is not guaranteed to
|
|
44
|
-
# be unique and the data is at risk - this configuration is for testing only.
|
|
45
|
-
set_path_provider(
|
|
46
|
-
StaticVisitPathProvider(
|
|
47
|
-
BL,
|
|
48
|
-
Path("/dls/i22/data/2025/cm40643-4/"),
|
|
49
|
-
client=RemoteDirectoryServiceClient("http://i22-control:8088/api"),
|
|
50
|
-
)
|
|
51
|
-
)
|
|
52
|
-
|
|
53
36
|
|
|
54
37
|
@device_factory()
|
|
55
38
|
def saxs() -> PilatusDetector:
|
dodal/beamlines/k07.py
CHANGED
|
@@ -4,7 +4,7 @@ from dodal.common.beamlines.beamline_utils import (
|
|
|
4
4
|
device_factory,
|
|
5
5
|
)
|
|
6
6
|
from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline
|
|
7
|
-
from dodal.devices.pgm import
|
|
7
|
+
from dodal.devices.pgm import PlaneGratingMonochromator
|
|
8
8
|
from dodal.devices.synchrotron import Synchrotron
|
|
9
9
|
from dodal.log import set_beamline as set_log_beamline
|
|
10
10
|
from dodal.utils import BeamlinePrefix, get_beamline_name
|
|
@@ -27,5 +27,8 @@ class Grating(StrictEnum):
|
|
|
27
27
|
|
|
28
28
|
# Grating does not exist yet - this class is a placeholder for when it does
|
|
29
29
|
@device_factory(skip=True)
|
|
30
|
-
def pgm() ->
|
|
31
|
-
return
|
|
30
|
+
def pgm() -> PlaneGratingMonochromator:
|
|
31
|
+
return PlaneGratingMonochromator(
|
|
32
|
+
prefix=f"{PREFIX.beamline_prefix}-OP-PGM-01:",
|
|
33
|
+
grating=Grating,
|
|
34
|
+
)
|
dodal/cli.py
CHANGED
|
@@ -4,7 +4,7 @@ from pathlib import Path
|
|
|
4
4
|
|
|
5
5
|
import click
|
|
6
6
|
from bluesky.run_engine import RunEngine
|
|
7
|
-
from ophyd_async.core import
|
|
7
|
+
from ophyd_async.core import NotConnectedError, StaticPathProvider, UUIDFilenameProvider
|
|
8
8
|
from ophyd_async.plan_stubs import ensure_connected
|
|
9
9
|
|
|
10
10
|
from dodal.beamlines import all_beamline_names, module_name_for_beamline
|
|
@@ -79,7 +79,7 @@ def connect(beamline: str, all: bool, sim_backend: bool) -> None:
|
|
|
79
79
|
# If exceptions have occurred, this will print details of the relevant PVs
|
|
80
80
|
exceptions = {**instance_exceptions, **connect_exceptions}
|
|
81
81
|
if len(exceptions) > 0:
|
|
82
|
-
raise
|
|
82
|
+
raise NotConnectedError(exceptions)
|
|
83
83
|
|
|
84
84
|
|
|
85
85
|
def _report_successful_devices(
|
|
@@ -113,7 +113,7 @@ def _connect_devices(
|
|
|
113
113
|
# Connect ophyd-async devices
|
|
114
114
|
try:
|
|
115
115
|
run_engine(ensure_connected(*ophyd_async_devices.values(), mock=sim_backend))
|
|
116
|
-
except
|
|
116
|
+
except NotConnectedError as ex:
|
|
117
117
|
exceptions = {**exceptions, **ex.sub_errors}
|
|
118
118
|
|
|
119
119
|
# Only return the subset of devices that haven't raised an exception
|
|
@@ -326,8 +326,8 @@ class Apple2(StandardReadable, Movable):
|
|
|
326
326
|
Name of the device.
|
|
327
327
|
"""
|
|
328
328
|
with self.add_children_as_readables():
|
|
329
|
-
self.gap = id_gap
|
|
330
|
-
self.phase = id_phase
|
|
329
|
+
self.gap = Reference(id_gap)
|
|
330
|
+
self.phase = Reference(id_phase)
|
|
331
331
|
super().__init__(name=name)
|
|
332
332
|
|
|
333
333
|
@AsyncStatus.wrap
|
|
@@ -338,25 +338,27 @@ class Apple2(StandardReadable, Movable):
|
|
|
338
338
|
"""
|
|
339
339
|
|
|
340
340
|
# Only need to check gap as the phase motors share both fault and gate with gap.
|
|
341
|
-
await self.gap.raise_if_cannot_move()
|
|
341
|
+
await self.gap().raise_if_cannot_move()
|
|
342
342
|
await asyncio.gather(
|
|
343
|
-
self.phase.top_outer.user_setpoint.set(value=id_motor_values.top_outer),
|
|
344
|
-
self.phase.top_inner.user_setpoint.set(value=id_motor_values.top_inner),
|
|
345
|
-
self.phase.btm_inner.user_setpoint.set(value=id_motor_values.btm_inner),
|
|
346
|
-
self.phase.btm_outer.user_setpoint.set(value=id_motor_values.btm_outer),
|
|
347
|
-
self.gap.user_setpoint.set(value=id_motor_values.gap),
|
|
343
|
+
self.phase().top_outer.user_setpoint.set(value=id_motor_values.top_outer),
|
|
344
|
+
self.phase().top_inner.user_setpoint.set(value=id_motor_values.top_inner),
|
|
345
|
+
self.phase().btm_inner.user_setpoint.set(value=id_motor_values.btm_inner),
|
|
346
|
+
self.phase().btm_outer.user_setpoint.set(value=id_motor_values.btm_outer),
|
|
347
|
+
self.gap().user_setpoint.set(value=id_motor_values.gap),
|
|
348
348
|
)
|
|
349
349
|
timeout = np.max(
|
|
350
|
-
await asyncio.gather(self.gap.get_timeout(), self.phase.get_timeout())
|
|
350
|
+
await asyncio.gather(self.gap().get_timeout(), self.phase().get_timeout())
|
|
351
351
|
)
|
|
352
352
|
LOGGER.info(
|
|
353
353
|
f"Moving f{self.name} apple2 motors to {id_motor_values}, timeout = {timeout}"
|
|
354
354
|
)
|
|
355
355
|
await asyncio.gather(
|
|
356
|
-
self.gap.set_move.set(value=1, wait=False, timeout=timeout),
|
|
357
|
-
self.phase.set_move.set(value=1, wait=False, timeout=timeout),
|
|
356
|
+
self.gap().set_move.set(value=1, wait=False, timeout=timeout),
|
|
357
|
+
self.phase().set_move.set(value=1, wait=False, timeout=timeout),
|
|
358
|
+
)
|
|
359
|
+
await wait_for_value(
|
|
360
|
+
self.gap().gate, UndulatorGateStatus.CLOSE, timeout=timeout
|
|
358
361
|
)
|
|
359
|
-
await wait_for_value(self.gap.gate, UndulatorGateStatus.CLOSE, timeout=timeout)
|
|
360
362
|
|
|
361
363
|
|
|
362
364
|
class EnergyMotorConvertor(Protocol):
|
|
@@ -448,11 +450,11 @@ class Apple2Controller(abc.ABC, StandardReadable, Generic[Apple2Type]):
|
|
|
448
450
|
raw_to_derived=self._read_pol,
|
|
449
451
|
set_derived=self._set_pol,
|
|
450
452
|
pol=self.polarisation_setpoint,
|
|
451
|
-
top_outer=self.apple2().phase.top_outer.user_readback,
|
|
452
|
-
top_inner=self.apple2().phase.top_inner.user_readback,
|
|
453
|
-
btm_inner=self.apple2().phase.btm_inner.user_readback,
|
|
454
|
-
btm_outer=self.apple2().phase.btm_outer.user_readback,
|
|
455
|
-
gap=self.apple2().gap.user_readback,
|
|
453
|
+
top_outer=self.apple2().phase().top_outer.user_readback,
|
|
454
|
+
top_inner=self.apple2().phase().top_inner.user_readback,
|
|
455
|
+
btm_inner=self.apple2().phase().btm_inner.user_readback,
|
|
456
|
+
btm_outer=self.apple2().phase().btm_outer.user_readback,
|
|
457
|
+
gap=self.apple2().gap().user_readback,
|
|
456
458
|
)
|
|
457
459
|
super().__init__(name)
|
|
458
460
|
|
dodal/devices/b07_1/ccmc.py
CHANGED
dodal/devices/common_dcm.py
CHANGED
|
@@ -53,9 +53,9 @@ class DoubleCrystalMonochromatorBase(StandardReadable, Generic[Xtal_1, Xtal_2]):
|
|
|
53
53
|
) -> None:
|
|
54
54
|
with self.add_children_as_readables():
|
|
55
55
|
# Virtual motor PV's which set the physical motors so that the DCM produces requested energy
|
|
56
|
-
self.
|
|
57
|
-
self.
|
|
58
|
-
self._convert_keV_to_eV, energy_signal=self.
|
|
56
|
+
self.energy_in_keV = Motor(prefix + "ENERGY")
|
|
57
|
+
self.energy_in_eV = derived_signal_r(
|
|
58
|
+
self._convert_keV_to_eV, energy_signal=self.energy_in_keV.user_readback
|
|
59
59
|
)
|
|
60
60
|
|
|
61
61
|
self._make_crystals(prefix, xtal_1, xtal_2)
|
dodal/devices/cryostream.py
CHANGED
|
@@ -32,6 +32,11 @@ class TurboEnum(StrictEnum):
|
|
|
32
32
|
AUTO = "Auto"
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
class CryoStreamSelection(StrictEnum):
|
|
36
|
+
CRYOJET = "CryoJet"
|
|
37
|
+
HC1 = "HC1"
|
|
38
|
+
|
|
39
|
+
|
|
35
40
|
class OxfordCryoStreamController(StandardReadable):
|
|
36
41
|
def __init__(self, prefix: str, name: str = ""):
|
|
37
42
|
with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
|
|
@@ -107,3 +112,19 @@ class OxfordCryoStream(StandardReadable):
|
|
|
107
112
|
self.status = OxfordCryoStreamStatus(prefix=prefix)
|
|
108
113
|
|
|
109
114
|
super().__init__(name)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class CryoStreamGantry(StandardReadable):
|
|
118
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
119
|
+
with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
|
|
120
|
+
self.cryostream_selector = epics_signal_r(
|
|
121
|
+
CryoStreamSelection, f"{prefix}-EA-GANT-01:CTRL"
|
|
122
|
+
)
|
|
123
|
+
self.hc1_selected = epics_signal_r(
|
|
124
|
+
int, f"{prefix}-MO-STEP-02:GPIO_INP_BITS.B2"
|
|
125
|
+
)
|
|
126
|
+
self.cryostream_selected = epics_signal_r(
|
|
127
|
+
int, f"{prefix}-MO-STEP-02:GPIO_INP_BITS.B3"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
super().__init__(name)
|
|
@@ -58,7 +58,7 @@ class UndulatorDCM(StandardReadable, Movable[float]):
|
|
|
58
58
|
async def set(self, value: float):
|
|
59
59
|
await self.undulator_ref().raise_if_not_enabled()
|
|
60
60
|
await asyncio.gather(
|
|
61
|
-
self.dcm_ref().
|
|
61
|
+
self.dcm_ref().energy_in_keV.set(value, timeout=ENERGY_TIMEOUT_S),
|
|
62
62
|
self.undulator_ref().set(value),
|
|
63
63
|
)
|
|
64
64
|
|
|
File without changes
|
dodal/devices/i07/dcm.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from ophyd_async.epics.core import epics_signal_r
|
|
2
|
+
from ophyd_async.epics.motor import Motor
|
|
3
|
+
|
|
4
|
+
from dodal.devices.common_dcm import (
|
|
5
|
+
DoubleCrystalMonochromator,
|
|
6
|
+
PitchAndRollCrystal,
|
|
7
|
+
StationaryCrystal,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DCM(DoubleCrystalMonochromator[PitchAndRollCrystal, StationaryCrystal]):
|
|
12
|
+
"""
|
|
13
|
+
Device for i07's DCM, including temperature monitors and vertical motor which were
|
|
14
|
+
included in GDA.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
motor_prefix: str,
|
|
20
|
+
xtal_prefix: str,
|
|
21
|
+
name: str = "",
|
|
22
|
+
) -> None:
|
|
23
|
+
super().__init__(motor_prefix, PitchAndRollCrystal, StationaryCrystal, name)
|
|
24
|
+
with self.add_children_as_readables():
|
|
25
|
+
self.vertical_in_mm = Motor(motor_prefix + "PERP")
|
|
26
|
+
|
|
27
|
+
# temperatures
|
|
28
|
+
self.xtal1_temp = epics_signal_r(float, xtal_prefix + "PT100-2")
|
|
29
|
+
self.xtal2_temp = epics_signal_r(float, xtal_prefix + "PT100-3")
|
|
30
|
+
self.xtal1_holder_temp = epics_signal_r(float, xtal_prefix + "PT100-1")
|
|
31
|
+
self.xtal2_holder_temp = epics_signal_r(float, xtal_prefix + "PT100-4")
|
|
32
|
+
self.gap_motor = epics_signal_r(float, xtal_prefix + "TC-1")
|
|
33
|
+
self.white_beam_stop_temp = epics_signal_r(float, xtal_prefix + "WBS:TEMP")
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from dodal.devices.util.lookup_tables import energy_distance_table
|
|
4
|
+
from dodal.log import LOGGER
|
|
5
|
+
|
|
6
|
+
LUT_COMMENTS = ["#"]
|
|
7
|
+
HU_SKIP_ROWS = 3
|
|
8
|
+
|
|
9
|
+
# Physics constants
|
|
10
|
+
ELECTRON_REST_ENERGY_MEV = 0.510999
|
|
11
|
+
|
|
12
|
+
# Columns in the lookup table
|
|
13
|
+
RING_ENERGY_COLUMN = 1
|
|
14
|
+
MAGNET_FIELD_COLUMN = 2
|
|
15
|
+
MIN_ENERGY_COLUMN = 3
|
|
16
|
+
MAX_ENERGY_COLUMN = 4
|
|
17
|
+
GAP_OFFSET_COLUMN = 7
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def get_hu_lut_as_dict(lut_path: str) -> dict:
|
|
21
|
+
lut_dict: dict = {}
|
|
22
|
+
_lookup_table: np.ndarray = await energy_distance_table(
|
|
23
|
+
lut_path,
|
|
24
|
+
comments=LUT_COMMENTS,
|
|
25
|
+
skiprows=HU_SKIP_ROWS,
|
|
26
|
+
)
|
|
27
|
+
for i in range(_lookup_table.shape[0]):
|
|
28
|
+
lut_dict[_lookup_table[i][0]] = _lookup_table[i]
|
|
29
|
+
LOGGER.debug(f"Loaded lookup table:\n {lut_dict}")
|
|
30
|
+
return lut_dict
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def calculate_gap_i09_hu(
|
|
34
|
+
photon_energy_kev: float,
|
|
35
|
+
look_up_table: dict[int, "np.ndarray"],
|
|
36
|
+
order: int = 1,
|
|
37
|
+
gap_offset: float = 0.0,
|
|
38
|
+
undulator_period_mm: int = 27,
|
|
39
|
+
) -> float:
|
|
40
|
+
"""
|
|
41
|
+
Calculate the undulator gap required to produce a given energy at a given harmonic order.
|
|
42
|
+
This algorithm was provided by the I09 beamline scientists, and is based on the physics of undulator radiation.
|
|
43
|
+
https://cxro.lbl.gov//PDF/X-Ray-Data-Booklet.pdf
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
photon_energy_kev (float): Requested photon energy in keV.
|
|
47
|
+
look_up_table (dict[int, np.ndarray]): Lookup table containing undulator and beamline parameters for each harmonic order.
|
|
48
|
+
order (int, optional): Harmonic order for which to calculate the gap. Defaults to 1.
|
|
49
|
+
gap_offset (float, optional): Additional gap offset to apply (in mm). Defaults to 0.0.
|
|
50
|
+
undulator_period_mm (int, optional): Undulator period in mm. Defaults to 27.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
float: Calculated undulator gap in millimeters.
|
|
54
|
+
"""
|
|
55
|
+
magnet_blocks_per_period = 4
|
|
56
|
+
magnet_block_height_mm = 16
|
|
57
|
+
|
|
58
|
+
if order not in look_up_table.keys():
|
|
59
|
+
raise ValueError(f"Order parameter {order} not found in lookup table")
|
|
60
|
+
|
|
61
|
+
gamma = 1000 * look_up_table[order][RING_ENERGY_COLUMN] / ELECTRON_REST_ENERGY_MEV
|
|
62
|
+
|
|
63
|
+
# Constructive interference of radiation emitted at different poles
|
|
64
|
+
# lamda = (lambda_u/2*gamma^2)*(1+K^2/2 + gamma^2*theta^2)/n for n=1,2,3...
|
|
65
|
+
# theta is the observation angle, assumed to be 0 here.
|
|
66
|
+
# Rearranging for K (the undulator parameter, related to magnetic field and gap)
|
|
67
|
+
# gives K^2 = 2*((2*n*gamma^2*lamda/lambda_u)-1)
|
|
68
|
+
|
|
69
|
+
undulator_parameter_sqr = (
|
|
70
|
+
4.959368e-6
|
|
71
|
+
* (order * gamma * gamma / (undulator_period_mm * photon_energy_kev))
|
|
72
|
+
- 2
|
|
73
|
+
)
|
|
74
|
+
if undulator_parameter_sqr < 0:
|
|
75
|
+
raise ValueError(
|
|
76
|
+
f"Diffraction parameter squared must be positive! Calculated value {undulator_parameter_sqr}."
|
|
77
|
+
)
|
|
78
|
+
undulator_parameter = np.sqrt(undulator_parameter_sqr)
|
|
79
|
+
|
|
80
|
+
# Undulator_parameter K is also defined as K = 0.934*B0[T]*lambda_u[cm],
|
|
81
|
+
# where B0[T] is a peak magnetic field that must depend on gap,
|
|
82
|
+
# but in our LUT it is does not depend on gap, so it's a factor,
|
|
83
|
+
# leading to K = 0.934*B0[T]*lambda_u[cm]*exp(-pi*gap/lambda_u) or
|
|
84
|
+
# K = undulator_parameter_max*exp(-pi*gap/lambda_u)
|
|
85
|
+
# Calculating undulator_parameter_max gives:
|
|
86
|
+
undulator_parameter_max = (
|
|
87
|
+
(
|
|
88
|
+
2
|
|
89
|
+
* 0.0934
|
|
90
|
+
* undulator_period_mm
|
|
91
|
+
* look_up_table[order][MAGNET_FIELD_COLUMN]
|
|
92
|
+
* magnet_blocks_per_period
|
|
93
|
+
/ np.pi
|
|
94
|
+
)
|
|
95
|
+
* np.sin(np.pi / magnet_blocks_per_period)
|
|
96
|
+
* (1 - np.exp(-2 * np.pi * magnet_block_height_mm / undulator_period_mm))
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Finnaly, rearranging the equation:
|
|
100
|
+
# undulator_parameter = undulator_parameter_max*exp(-pi*gap/lambda_u) for gap gives
|
|
101
|
+
gap = (
|
|
102
|
+
(undulator_period_mm / np.pi)
|
|
103
|
+
* np.log(undulator_parameter_max / undulator_parameter)
|
|
104
|
+
+ look_up_table[order][GAP_OFFSET_COLUMN]
|
|
105
|
+
+ gap_offset
|
|
106
|
+
)
|
|
107
|
+
LOGGER.debug(
|
|
108
|
+
f"Calculated gap is {gap}mm for energy {photon_energy_kev}keV at order {order}"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
return gap
|
dodal/devices/i10/i10_apple2.py
CHANGED
|
@@ -340,7 +340,7 @@ class I10Apple2(Apple2):
|
|
|
340
340
|
The name of the device, by default "".
|
|
341
341
|
"""
|
|
342
342
|
with self.add_children_as_readables():
|
|
343
|
-
self.jaw_phase = id_jaw_phase
|
|
343
|
+
self.jaw_phase = Reference(id_jaw_phase)
|
|
344
344
|
super().__init__(id_gap=id_gap, id_phase=id_phase, name=name)
|
|
345
345
|
|
|
346
346
|
|
|
@@ -425,7 +425,7 @@ class I10Apple2Controller(Apple2Controller[I10Apple2]):
|
|
|
425
425
|
f"jaw_phase position for angle ({pol_angle}) is outside permitted range"
|
|
426
426
|
f" [-{self.jaw_phase_limit}, {self.jaw_phase_limit}]"
|
|
427
427
|
)
|
|
428
|
-
await self.apple2().jaw_phase.set(jaw_phase)
|
|
428
|
+
await self.apple2().jaw_phase().set(jaw_phase)
|
|
429
429
|
await self._linear_arbitrary_angle.set(pol_angle)
|
|
430
430
|
|
|
431
431
|
async def _set_motors_from_energy(self, value: float) -> None:
|
|
@@ -447,8 +447,8 @@ class I10Apple2Controller(Apple2Controller[I10Apple2]):
|
|
|
447
447
|
LOGGER.info(f"Setting polarisation to {pol}, with values: {id_set_val}")
|
|
448
448
|
await self.apple2().set(id_motor_values=id_set_val)
|
|
449
449
|
if pol != Pol.LA:
|
|
450
|
-
await self.apple2().jaw_phase.set(0)
|
|
451
|
-
await self.apple2().jaw_phase.set_move.set(1)
|
|
450
|
+
await self.apple2().jaw_phase().set(0)
|
|
451
|
+
await self.apple2().jaw_phase().set_move.set(1)
|
|
452
452
|
|
|
453
453
|
def _raise_if_not_la(self, pol: Pol) -> None:
|
|
454
454
|
if pol != Pol.LA:
|
dodal/devices/i15/dcm.py
CHANGED
|
@@ -33,7 +33,7 @@ class DCM(DoubleCrystalMonochromatorBase[ThetaRollYZCrystal, ThetaYCrystal]):
|
|
|
33
33
|
|
|
34
34
|
def __init__(self, prefix: str, name: str = "") -> None:
|
|
35
35
|
with self.add_children_as_readables():
|
|
36
|
-
self.
|
|
36
|
+
self.calibrated_energy_in_keV = Motor(prefix + "CAL")
|
|
37
37
|
self.x1 = Motor(prefix + "X1")
|
|
38
38
|
|
|
39
39
|
super().__init__(prefix, ThetaRollYZCrystal, ThetaYCrystal, name)
|
dodal/devices/i22/dcm.py
CHANGED
|
@@ -107,7 +107,7 @@ class DCM(DoubleCrystalMonochromatorWithDSpacing[RollCrystal, PitchAndRollCrysta
|
|
|
107
107
|
|
|
108
108
|
async def read(self) -> dict[str, Reading]:
|
|
109
109
|
default_reading = await super().read()
|
|
110
|
-
energy: float = default_reading[f"{self.name}-
|
|
110
|
+
energy: float = default_reading[f"{self.name}-energy_in_keV"]["value"]
|
|
111
111
|
if energy > 0.0:
|
|
112
112
|
wavelength = _CONVERSION_CONSTANT / energy
|
|
113
113
|
else:
|
dodal/devices/i22/nxsas.py
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
from collections.abc import Awaitable, Iterable
|
|
3
1
|
from dataclasses import dataclass, fields
|
|
4
2
|
from typing import TypeVar
|
|
5
3
|
|
|
6
4
|
from bluesky.protocols import Reading
|
|
7
5
|
from event_model.documents.event_descriptor import DataKey
|
|
8
|
-
from ophyd_async.core import PathProvider
|
|
6
|
+
from ophyd_async.core import PathProvider, merge_gathered_dicts
|
|
9
7
|
from ophyd_async.epics.adaravis import AravisDetector
|
|
10
8
|
from ophyd_async.epics.adcore import NDPluginBaseIO
|
|
11
9
|
from ophyd_async.epics.adpilatus import PilatusDetector
|
|
@@ -14,23 +12,6 @@ ValueAndUnits = tuple[float, str]
|
|
|
14
12
|
T = TypeVar("T")
|
|
15
13
|
|
|
16
14
|
|
|
17
|
-
# TODO: Remove this file as part of github.com/DiamondLightSource/dodal/issues/595
|
|
18
|
-
# Until which, temporarily duplicated non-public method from ophyd_async
|
|
19
|
-
async def _merge_gathered_dicts(
|
|
20
|
-
coros: Iterable[Awaitable[dict[str, T]]],
|
|
21
|
-
) -> dict[str, T]:
|
|
22
|
-
"""Merge dictionaries produced by a sequence of coroutines.
|
|
23
|
-
|
|
24
|
-
Can be used for merging ``read()`` or ``describe``. For instance::
|
|
25
|
-
|
|
26
|
-
combined_read = await merge_gathered_dicts(s.read() for s in signals)
|
|
27
|
-
"""
|
|
28
|
-
ret: dict[str, T] = {}
|
|
29
|
-
for result in await asyncio.gather(*coros):
|
|
30
|
-
ret.update(result)
|
|
31
|
-
return ret
|
|
32
|
-
|
|
33
|
-
|
|
34
15
|
@dataclass
|
|
35
16
|
class MetadataHolder:
|
|
36
17
|
# TODO: just in case this is useful more widely...
|
|
@@ -124,7 +105,7 @@ class NXSasPilatus(PilatusDetector):
|
|
|
124
105
|
self._metadata_holder = metadata_holder
|
|
125
106
|
|
|
126
107
|
async def read_configuration(self) -> dict[str, Reading]:
|
|
127
|
-
return await
|
|
108
|
+
return await merge_gathered_dicts(
|
|
128
109
|
r
|
|
129
110
|
for r in (
|
|
130
111
|
super().read_configuration(),
|
|
@@ -133,7 +114,7 @@ class NXSasPilatus(PilatusDetector):
|
|
|
133
114
|
)
|
|
134
115
|
|
|
135
116
|
async def describe_configuration(self) -> dict[str, DataKey]:
|
|
136
|
-
return await
|
|
117
|
+
return await merge_gathered_dicts(
|
|
137
118
|
r
|
|
138
119
|
for r in (
|
|
139
120
|
super().describe_configuration(),
|
|
@@ -167,7 +148,7 @@ class NXSasOAV(AravisDetector):
|
|
|
167
148
|
self._metadata_holder = metadata_holder
|
|
168
149
|
|
|
169
150
|
async def read_configuration(self) -> dict[str, Reading]:
|
|
170
|
-
return await
|
|
151
|
+
return await merge_gathered_dicts(
|
|
171
152
|
r
|
|
172
153
|
for r in (
|
|
173
154
|
super().read_configuration(),
|
|
@@ -176,7 +157,7 @@ class NXSasOAV(AravisDetector):
|
|
|
176
157
|
)
|
|
177
158
|
|
|
178
159
|
async def describe_configuration(self) -> dict[str, DataKey]:
|
|
179
|
-
return await
|
|
160
|
+
return await merge_gathered_dicts(
|
|
180
161
|
r
|
|
181
162
|
for r in (
|
|
182
163
|
super().describe_configuration(),
|
dodal/devices/pgm.py
CHANGED
|
@@ -7,7 +7,7 @@ from ophyd_async.epics.core import epics_signal_rw
|
|
|
7
7
|
from ophyd_async.epics.motor import Motor
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
class
|
|
10
|
+
class PlaneGratingMonochromator(StandardReadable):
|
|
11
11
|
"""
|
|
12
12
|
Plane grating monochromator, it is use in soft x-ray beamline to generate monochromic beam.
|
|
13
13
|
"""
|