dls-dodal 1.55.1__py3-none-any.whl → 1.56.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dls_dodal-1.55.1.dist-info → dls_dodal-1.56.0.dist-info}/METADATA +2 -2
- {dls_dodal-1.55.1.dist-info → dls_dodal-1.56.0.dist-info}/RECORD +97 -83
- dodal/_version.py +16 -3
- dodal/beamlines/b01_1.py +6 -1
- dodal/beamlines/b07.py +2 -1
- dodal/beamlines/b07_1.py +2 -1
- dodal/beamlines/b21.py +4 -24
- dodal/beamlines/i03.py +53 -53
- dodal/beamlines/i04.py +16 -38
- dodal/beamlines/i09.py +3 -2
- dodal/beamlines/i09_1.py +2 -1
- dodal/beamlines/i11.py +143 -0
- dodal/beamlines/i19_1.py +1 -0
- dodal/beamlines/i19_2.py +7 -0
- dodal/beamlines/i22.py +2 -2
- dodal/beamlines/i23.py +3 -3
- dodal/beamlines/i24.py +6 -14
- dodal/beamlines/p38.py +1 -0
- dodal/beamlines/p60.py +3 -2
- dodal/cli.py +11 -1
- dodal/common/__init__.py +4 -0
- dodal/common/beamlines/beamline_parameters.py +1 -1
- dodal/common/beamlines/beamline_utils.py +5 -1
- dodal/common/enums.py +19 -0
- dodal/common/watcher_utils.py +83 -0
- dodal/devices/aithre_lasershaping/laser_robot.py +4 -9
- dodal/devices/aperturescatterguard.py +52 -12
- dodal/devices/apple2_undulator.py +0 -1
- dodal/devices/b16/detector.py +1 -10
- dodal/devices/backlight.py +8 -20
- dodal/devices/bimorph_mirror.py +4 -7
- dodal/devices/collimation_table.py +36 -0
- dodal/devices/controllers.py +21 -0
- dodal/devices/cryostream.py +97 -7
- dodal/devices/current_amplifiers/femto.py +1 -1
- dodal/devices/detector/detector_motion.py +1 -7
- dodal/devices/eiger.py +22 -8
- dodal/devices/eiger_odin.py +2 -0
- dodal/devices/electron_analyser/__init__.py +2 -1
- dodal/devices/electron_analyser/abstract/__init__.py +0 -1
- dodal/devices/electron_analyser/abstract/base_detector.py +3 -25
- dodal/devices/electron_analyser/abstract/base_driver_io.py +18 -9
- dodal/devices/electron_analyser/abstract/base_region.py +34 -3
- dodal/devices/electron_analyser/detector.py +24 -0
- dodal/devices/electron_analyser/enums.py +5 -0
- dodal/devices/electron_analyser/specs/detector.py +2 -1
- dodal/devices/electron_analyser/specs/driver_io.py +21 -26
- dodal/devices/electron_analyser/specs/region.py +1 -1
- dodal/devices/electron_analyser/util.py +20 -0
- dodal/devices/electron_analyser/vgscienta/__init__.py +3 -3
- dodal/devices/electron_analyser/vgscienta/detector.py +2 -1
- dodal/devices/electron_analyser/vgscienta/driver_io.py +24 -32
- dodal/devices/electron_analyser/vgscienta/enums.py +0 -8
- dodal/devices/electron_analyser/vgscienta/region.py +2 -31
- dodal/devices/eurotherm.py +126 -0
- dodal/devices/fluorescence_detector_motion.py +3 -10
- dodal/devices/focusing_mirror.py +1 -1
- dodal/devices/i03/undulator_dcm.py +0 -1
- dodal/devices/i09/enums.py +8 -8
- dodal/devices/i10/diagnostics.py +4 -4
- dodal/devices/i10/i10_apple2.py +3 -6
- dodal/devices/i11/cyberstar_blower.py +34 -0
- dodal/devices/i11/diff_stages.py +55 -0
- dodal/devices/i11/mythen.py +165 -0
- dodal/devices/i11/nx100robot.py +153 -0
- dodal/devices/i11/spinner.py +30 -0
- dodal/devices/i13_1/merlin_controller.py +4 -4
- dodal/devices/i19/diffractometer.py +34 -0
- dodal/devices/i19/shutter.py +11 -1
- dodal/devices/i22/dcm.py +1 -1
- dodal/devices/i22/fswitch.py +3 -12
- dodal/devices/i24/aperture.py +3 -3
- dodal/devices/i24/dcm.py +11 -15
- dodal/devices/i24/dual_backlight.py +11 -12
- dodal/devices/i24/pmac.py +8 -7
- dodal/devices/mx_phase1/beamstop.py +10 -11
- dodal/devices/oav/pin_image_recognition/__init__.py +0 -3
- dodal/devices/p60/enums.py +8 -8
- dodal/devices/p60/lab_xray_source.py +3 -2
- dodal/devices/pressure_jump_cell.py +77 -123
- dodal/devices/scintillator.py +76 -4
- dodal/devices/smargon.py +2 -2
- dodal/devices/synchrotron.py +1 -2
- dodal/devices/thawer.py +6 -11
- dodal/devices/undulator.py +3 -8
- dodal/devices/util/epics_util.py +1 -1
- dodal/devices/watsonmarlow323_pump.py +7 -7
- dodal/devices/xbpm_feedback.py +4 -6
- dodal/devices/xspress3/xspress3.py +0 -5
- dodal/devices/zocalo/zocalo_results.py +1 -3
- dodal/testing/__init__.py +0 -0
- dodal/testing/electron_analyser/__init__.py +6 -0
- dodal/testing/electron_analyser/device_factory.py +59 -0
- dodal/devices/CTAB.py +0 -41
- {dls_dodal-1.55.1.dist-info → dls_dodal-1.56.0.dist-info}/WHEEL +0 -0
- {dls_dodal-1.55.1.dist-info → dls_dodal-1.56.0.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.55.1.dist-info → dls_dodal-1.56.0.dist-info}/licenses/LICENSE +0 -0
- {dls_dodal-1.55.1.dist-info → dls_dodal-1.56.0.dist-info}/top_level.txt +0 -0
dodal/beamlines/i24.py
CHANGED
|
@@ -100,7 +100,8 @@ def dcm() -> DCM:
|
|
|
100
100
|
If this is called when already instantiated in i24, it will return the existing object.
|
|
101
101
|
"""
|
|
102
102
|
return DCM(
|
|
103
|
-
prefix=PREFIX.beamline_prefix,
|
|
103
|
+
prefix=f"{PREFIX.beamline_prefix}-DI-DCM-01",
|
|
104
|
+
motion_prefix=f"{PREFIX.beamline_prefix}-MO-DCM-01",
|
|
104
105
|
)
|
|
105
106
|
|
|
106
107
|
|
|
@@ -136,10 +137,7 @@ def pmac() -> PMAC:
|
|
|
136
137
|
"""Get the i24 PMAC device, instantiate it if it hasn't already been.
|
|
137
138
|
If this is called when already instantiated in i24, it will return the existing object.
|
|
138
139
|
"""
|
|
139
|
-
|
|
140
|
-
return PMAC(
|
|
141
|
-
"ME14E-MO-CHIP-01:",
|
|
142
|
-
)
|
|
140
|
+
return PMAC(PREFIX.beamline_prefix)
|
|
143
141
|
|
|
144
142
|
|
|
145
143
|
@device_factory()
|
|
@@ -155,9 +153,7 @@ def vgonio() -> VerticalGoniometer:
|
|
|
155
153
|
"""Get the i24 vertical goniometer device, instantiate it if it hasn't already been.
|
|
156
154
|
If this is called when already instantiated, it will return the existing object.
|
|
157
155
|
"""
|
|
158
|
-
return VerticalGoniometer(
|
|
159
|
-
f"{PREFIX.beamline_prefix}-MO-VGON-01:",
|
|
160
|
-
)
|
|
156
|
+
return VerticalGoniometer(f"{PREFIX.beamline_prefix}-MO-VGON-01:")
|
|
161
157
|
|
|
162
158
|
|
|
163
159
|
@device_factory()
|
|
@@ -176,17 +172,13 @@ def shutter() -> HutchShutter:
|
|
|
176
172
|
"""Get the i24 hutch shutter device, instantiate it if it hasn't already been.
|
|
177
173
|
If this is called when already instantiated, it will return the existing object.
|
|
178
174
|
"""
|
|
179
|
-
return HutchShutter(
|
|
180
|
-
f"{PREFIX.beamline_prefix}-PS-SHTR-01:",
|
|
181
|
-
)
|
|
175
|
+
return HutchShutter(f"{PREFIX.beamline_prefix}-PS-SHTR-01:")
|
|
182
176
|
|
|
183
177
|
|
|
184
178
|
@device_factory()
|
|
185
179
|
def focus_mirrors() -> FocusMirrorsMode:
|
|
186
180
|
"""Get the i24 focus mirror devise to find the beam size."""
|
|
187
|
-
return FocusMirrorsMode(
|
|
188
|
-
f"{PREFIX.beamline_prefix}-OP-MFM-01:",
|
|
189
|
-
)
|
|
181
|
+
return FocusMirrorsMode(f"{PREFIX.beamline_prefix}-OP-MFM-01:")
|
|
190
182
|
|
|
191
183
|
|
|
192
184
|
@device_factory()
|
dodal/beamlines/p38.py
CHANGED
|
@@ -145,6 +145,7 @@ def hfm() -> FocusingMirror:
|
|
|
145
145
|
@device_factory(mock=True)
|
|
146
146
|
def dcm() -> DCM:
|
|
147
147
|
return DCM(
|
|
148
|
+
prefix=f"{PREFIX.beamline_prefix}-MO-DCM-01:",
|
|
148
149
|
temperature_prefix=f"{PREFIX.beamline_prefix}-DI-DCM-01:",
|
|
149
150
|
crystal_1_metadata=make_crystal_metadata_from_material(
|
|
150
151
|
MaterialsEnum.Si, (1, 1, 1)
|
dodal/beamlines/p60.py
CHANGED
|
@@ -2,6 +2,7 @@ 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.electron_analyser import SelectedSource
|
|
5
6
|
from dodal.devices.electron_analyser.vgscienta import VGScientaAnalyserDriverIO
|
|
6
7
|
from dodal.devices.p60 import (
|
|
7
8
|
LabXraySource,
|
|
@@ -34,8 +35,8 @@ def mg_kalpha_source() -> LabXraySourceReadable:
|
|
|
34
35
|
@device_factory()
|
|
35
36
|
def analyser_driver() -> VGScientaAnalyserDriverIO[LensMode, PsuMode, PassEnergy]:
|
|
36
37
|
energy_sources = {
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
SelectedSource.SOURCE1: al_kalpha_source().energy_ev,
|
|
39
|
+
SelectedSource.SOURCE2: mg_kalpha_source().energy_ev,
|
|
39
40
|
}
|
|
40
41
|
return VGScientaAnalyserDriverIO[LensMode, PsuMode, PassEnergy](
|
|
41
42
|
prefix=f"{PREFIX.beamline_prefix}-EA-DET-01:CAM:",
|
dodal/cli.py
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from collections.abc import Mapping
|
|
3
|
+
from pathlib import Path
|
|
3
4
|
|
|
4
5
|
import click
|
|
5
6
|
from bluesky.run_engine import RunEngine
|
|
6
|
-
from ophyd_async.core import NotConnected
|
|
7
|
+
from ophyd_async.core import NotConnected, StaticPathProvider, UUIDFilenameProvider
|
|
7
8
|
from ophyd_async.plan_stubs import ensure_connected
|
|
8
9
|
|
|
9
10
|
from dodal.beamlines import all_beamline_names, module_name_for_beamline
|
|
11
|
+
from dodal.common.beamlines.beamline_utils import set_path_provider
|
|
10
12
|
from dodal.utils import AnyDevice, filter_ophyd_devices, make_all_devices
|
|
11
13
|
|
|
12
14
|
from . import __version__
|
|
@@ -47,6 +49,10 @@ def connect(beamline: str, all: bool, sim_backend: bool) -> None:
|
|
|
47
49
|
|
|
48
50
|
os.environ["BEAMLINE"] = beamline
|
|
49
51
|
|
|
52
|
+
# We need to make a fake path provider for any detectors that need one,
|
|
53
|
+
# it is not used in dodal connect
|
|
54
|
+
_spoof_path_provider()
|
|
55
|
+
|
|
50
56
|
module_name = module_name_for_beamline(beamline)
|
|
51
57
|
full_module_path = f"dodal.beamlines.{module_name}"
|
|
52
58
|
|
|
@@ -115,3 +121,7 @@ def _connect_devices(
|
|
|
115
121
|
name: device for name, device in devices.items() if name not in exceptions
|
|
116
122
|
}
|
|
117
123
|
return successful_devices, exceptions
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _spoof_path_provider() -> None:
|
|
127
|
+
set_path_provider(StaticPathProvider(UUIDFilenameProvider(), Path("/tmp")))
|
dodal/common/__init__.py
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
from .coordination import group_uuid, inject
|
|
2
|
+
from .enums import EnabledDisabledUpper, InOutUpper, OnOffUpper
|
|
2
3
|
from .maths import in_micros, step_to_num
|
|
3
4
|
from .types import MsgGenerator, PlanGenerator
|
|
4
5
|
|
|
5
6
|
__all__ = [
|
|
6
7
|
"group_uuid",
|
|
7
8
|
"inject",
|
|
9
|
+
"EnabledDisabledUpper",
|
|
10
|
+
"InOutUpper",
|
|
11
|
+
"OnOffUpper",
|
|
8
12
|
"in_micros",
|
|
9
13
|
"MsgGenerator",
|
|
10
14
|
"PlanGenerator",
|
|
@@ -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/
|
|
11
|
+
"i04": "/dls_sw/i04/software/daq_configuration/domain/beamlineParameters",
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
|
|
@@ -169,4 +169,8 @@ def get_path_provider() -> PathProvider:
|
|
|
169
169
|
|
|
170
170
|
def clear_path_provider() -> None:
|
|
171
171
|
global PATH_PROVIDER
|
|
172
|
-
|
|
172
|
+
try:
|
|
173
|
+
del PATH_PROVIDER
|
|
174
|
+
except NameError:
|
|
175
|
+
# In this case the path provider was never set so we can do nothing
|
|
176
|
+
pass
|
dodal/common/enums.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from ophyd_async.core import StrictEnum
|
|
2
|
+
|
|
3
|
+
# Any capitalised enums needs to be removed and replaced with ones from ophyd-async.core
|
|
4
|
+
# https://github.com/DiamondLightSource/dodal/issues/1416
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class OnOffUpper(StrictEnum):
|
|
8
|
+
ON = "ON"
|
|
9
|
+
OFF = "OFF"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class EnabledDisabledUpper(StrictEnum):
|
|
13
|
+
ENABLED = "ENABLED"
|
|
14
|
+
DISABLED = "DISABLED"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class InOutUpper(StrictEnum):
|
|
18
|
+
IN = "IN"
|
|
19
|
+
OUT = "OUT"
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from ophyd_async.core import WatchableAsyncStatus, Watcher
|
|
2
|
+
|
|
3
|
+
from dodal.log import LOGGER
|
|
4
|
+
|
|
5
|
+
Number = int | float
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class _LogOnPercentageProgressWatcher(Watcher[Number]):
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
status: WatchableAsyncStatus[Number],
|
|
12
|
+
message_prefix: str,
|
|
13
|
+
percent_interval: Number = 25,
|
|
14
|
+
):
|
|
15
|
+
status.watch(self)
|
|
16
|
+
self.percent_interval = percent_interval
|
|
17
|
+
self._current_percent_interval = 0
|
|
18
|
+
self.message_prefix = message_prefix
|
|
19
|
+
if self.percent_interval <= 0:
|
|
20
|
+
raise ValueError(
|
|
21
|
+
f"Percent interval on class _LogOnPercentageProgressWatcher must be a positive number, but received {self.percent_interval}"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
def __call__(
|
|
25
|
+
self,
|
|
26
|
+
current: Number | None = None,
|
|
27
|
+
initial: Number | None = None,
|
|
28
|
+
target: Number | None = None,
|
|
29
|
+
name: str | None = None,
|
|
30
|
+
unit: str | None = None,
|
|
31
|
+
precision: int | None = None,
|
|
32
|
+
fraction: float | None = None,
|
|
33
|
+
time_elapsed: float | None = None,
|
|
34
|
+
time_remaining: float | None = None,
|
|
35
|
+
):
|
|
36
|
+
if (
|
|
37
|
+
isinstance(current, Number)
|
|
38
|
+
and isinstance(target, Number)
|
|
39
|
+
and isinstance(initial, Number)
|
|
40
|
+
and target != initial
|
|
41
|
+
):
|
|
42
|
+
current_percent = int(((current - initial) / (target - initial)) * 100)
|
|
43
|
+
if (
|
|
44
|
+
current_percent
|
|
45
|
+
>= (self._current_percent_interval + 1) * self.percent_interval
|
|
46
|
+
):
|
|
47
|
+
LOGGER.info(f"{self.message_prefix}: {current_percent}%")
|
|
48
|
+
self._current_percent_interval = (
|
|
49
|
+
current_percent // self.percent_interval
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def log_on_percentage_complete(
|
|
54
|
+
status: WatchableAsyncStatus[int | float],
|
|
55
|
+
message_prefix: str,
|
|
56
|
+
percent_interval: int = 25,
|
|
57
|
+
):
|
|
58
|
+
"""
|
|
59
|
+
Add watcher to a WatchableAsyncStatus status which will periodically log a message based on percentage completion
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
status: A WatchableAsyncStatus. For example, Ophyd-async produces this status from a Motor.set method
|
|
63
|
+
|
|
64
|
+
message_prefix: The string at the start of each of the produced logging messages
|
|
65
|
+
|
|
66
|
+
percent_interval: How often to produce logging message, in terms of percentage completion
|
|
67
|
+
of the status.
|
|
68
|
+
|
|
69
|
+
Note that when using with Bluesky plan stubs you will need to cast the status (as of
|
|
70
|
+
Bluesky v1.14.2), since a Bluesky status doesn't use generics - see https://github.com/bluesky/bluesky/issues/1948.
|
|
71
|
+
|
|
72
|
+
When running Bluesky plans using an interactive terminal, it is better to use the standard bluesky progress
|
|
73
|
+
bar instead of this function. See https://blueskyproject.io/bluesky/main/progress-bar.html#progress-bar
|
|
74
|
+
|
|
75
|
+
Example usage within a Bluesky plan:
|
|
76
|
+
yield from bps.kickoff(my_detector)
|
|
77
|
+
status = yield from bps.complete(my_detector, group="collection complete")
|
|
78
|
+
status = cast(WatchableAsyncStatus, status)
|
|
79
|
+
log_on_percentage_complete(status, "Data collection triggers received", 10)
|
|
80
|
+
yield from bps.wait("collection complete")
|
|
81
|
+
|
|
82
|
+
"""
|
|
83
|
+
_LogOnPercentageProgressWatcher(status, message_prefix, percent_interval)
|
|
@@ -1,24 +1,19 @@
|
|
|
1
|
-
from ophyd_async.core import StrictEnum
|
|
1
|
+
from ophyd_async.core import EnabledDisabled, OnOff, StrictEnum
|
|
2
2
|
from ophyd_async.epics.core import epics_signal_rw
|
|
3
3
|
|
|
4
4
|
from dodal.devices.robot import BartRobot
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class ForceBit(StrictEnum):
|
|
8
|
-
ON =
|
|
8
|
+
ON = OnOff.ON.value
|
|
9
9
|
NO = "No"
|
|
10
|
-
OFF =
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class LidHeatEnable(StrictEnum):
|
|
14
|
-
ENABLED = "Enabled"
|
|
15
|
-
DISABLED = "Disabled"
|
|
10
|
+
OFF = OnOff.OFF.value
|
|
16
11
|
|
|
17
12
|
|
|
18
13
|
class LaserRobot(BartRobot):
|
|
19
14
|
def __init__(self, prefix: str, name: str = "") -> None:
|
|
20
15
|
self.dewar_lid_heater = epics_signal_rw(
|
|
21
|
-
|
|
16
|
+
EnabledDisabled, prefix + "DW_1_ENABLED", prefix + "DW_1_CTRL"
|
|
22
17
|
)
|
|
23
18
|
self.cryojet_retract = epics_signal_rw(ForceBit, prefix + "OP_24_FORCE_OPTION")
|
|
24
19
|
self.set_beamline_safe = epics_signal_rw(
|
|
@@ -29,6 +29,7 @@ class _GDAParamApertureValue(StrictEnum):
|
|
|
29
29
|
SMALL = "SMALL_APERTURE"
|
|
30
30
|
MEDIUM = "MEDIUM_APERTURE"
|
|
31
31
|
LARGE = "LARGE_APERTURE"
|
|
32
|
+
MANUAL_LOAD = "MANUAL_LOAD"
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
class AperturePosition(BaseModel):
|
|
@@ -92,7 +93,7 @@ class AperturePosition(BaseModel):
|
|
|
92
93
|
class ApertureValue(StrictEnum):
|
|
93
94
|
"""The possible apertures that can be selected.
|
|
94
95
|
|
|
95
|
-
Changing these means changing the external
|
|
96
|
+
Changing these means changing the external parameter model of Hyperion.
|
|
96
97
|
See https://github.com/DiamondLightSource/mx-bluesky/issues/760
|
|
97
98
|
"""
|
|
98
99
|
|
|
@@ -100,6 +101,7 @@ class ApertureValue(StrictEnum):
|
|
|
100
101
|
MEDIUM = "MEDIUM_APERTURE"
|
|
101
102
|
LARGE = "LARGE_APERTURE"
|
|
102
103
|
OUT_OF_BEAM = "Out of beam"
|
|
104
|
+
PARKED = "Parked" # Parked under the collimation table for manual load
|
|
103
105
|
|
|
104
106
|
def __str__(self):
|
|
105
107
|
return self.name.capitalize()
|
|
@@ -121,6 +123,9 @@ def load_positions_from_beamline_parameters(
|
|
|
121
123
|
ApertureValue.LARGE: AperturePosition.from_gda_params(
|
|
122
124
|
_GDAParamApertureValue.LARGE, 100, params
|
|
123
125
|
),
|
|
126
|
+
ApertureValue.PARKED: AperturePosition.from_gda_params(
|
|
127
|
+
_GDAParamApertureValue.MANUAL_LOAD, 0, params
|
|
128
|
+
),
|
|
124
129
|
}
|
|
125
130
|
|
|
126
131
|
|
|
@@ -158,13 +163,14 @@ class ApertureScatterguard(StandardReadable, Preparable):
|
|
|
158
163
|
|
|
159
164
|
def __init__(
|
|
160
165
|
self,
|
|
166
|
+
aperture_prefix: str,
|
|
167
|
+
scatterguard_prefix: str,
|
|
161
168
|
loaded_positions: dict[ApertureValue, AperturePosition],
|
|
162
169
|
tolerances: AperturePosition,
|
|
163
|
-
prefix: str = "",
|
|
164
170
|
name: str = "",
|
|
165
171
|
) -> None:
|
|
166
|
-
self.aperture = Aperture(
|
|
167
|
-
self.scatterguard = XYStage(
|
|
172
|
+
self.aperture = Aperture(aperture_prefix)
|
|
173
|
+
self.scatterguard = XYStage(scatterguard_prefix)
|
|
168
174
|
self._loaded_positions = loaded_positions
|
|
169
175
|
self._tolerances = tolerances
|
|
170
176
|
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
|
|
@@ -175,6 +181,7 @@ class ApertureScatterguard(StandardReadable, Preparable):
|
|
|
175
181
|
medium=self.aperture.medium,
|
|
176
182
|
small=self.aperture.small,
|
|
177
183
|
current_ap_y=self.aperture.y.user_readback,
|
|
184
|
+
current_ap_z=self.aperture.z.user_readback,
|
|
178
185
|
)
|
|
179
186
|
|
|
180
187
|
self.radius = derived_signal_r(
|
|
@@ -196,13 +203,30 @@ class ApertureScatterguard(StandardReadable, Preparable):
|
|
|
196
203
|
|
|
197
204
|
super().__init__(name)
|
|
198
205
|
|
|
206
|
+
async def _unpark(self, position_to_move_to: ApertureValue):
|
|
207
|
+
"""When the aperture is parked it is under the collimation table. It needs to be
|
|
208
|
+
moved out from under the table before it is moved up to beam height.
|
|
209
|
+
"""
|
|
210
|
+
position = self._loaded_positions[position_to_move_to]
|
|
211
|
+
await self.aperture.z.set(position.aperture_z)
|
|
212
|
+
|
|
199
213
|
async def _set_current_aperture_position(self, value: ApertureValue) -> None:
|
|
214
|
+
if value == ApertureValue.PARKED:
|
|
215
|
+
raise NotImplementedError(
|
|
216
|
+
"Currently not able to park aperture/scatterguard, see https://github.com/DiamondLightSource/mx-bluesky/issues/1197"
|
|
217
|
+
)
|
|
218
|
+
|
|
200
219
|
position = self._loaded_positions[value]
|
|
220
|
+
|
|
221
|
+
current_ap_y = await self.aperture.y.user_readback.get_value()
|
|
222
|
+
current_ap_z = await self.aperture.z.user_readback.get_value()
|
|
223
|
+
if self._is_in_position(ApertureValue.PARKED, current_ap_y, current_ap_z):
|
|
224
|
+
await self._unpark(value)
|
|
225
|
+
|
|
201
226
|
await self._check_safe_to_move(position.aperture_z)
|
|
202
227
|
|
|
203
228
|
if value == ApertureValue.OUT_OF_BEAM:
|
|
204
|
-
|
|
205
|
-
await self.aperture.y.set(out_y)
|
|
229
|
+
await self.aperture.y.set(position.aperture_y)
|
|
206
230
|
else:
|
|
207
231
|
await self._safe_move_whilst_in_beam(position)
|
|
208
232
|
|
|
@@ -240,12 +264,22 @@ class ApertureScatterguard(StandardReadable, Preparable):
|
|
|
240
264
|
def _get_current_radius(self, current_aperture: ApertureValue) -> float:
|
|
241
265
|
return self._loaded_positions[current_aperture].radius
|
|
242
266
|
|
|
243
|
-
def
|
|
244
|
-
|
|
245
|
-
|
|
267
|
+
def _is_in_position(
|
|
268
|
+
self, position: ApertureValue, current_ap_y: float, current_ap_z: float
|
|
269
|
+
) -> bool:
|
|
270
|
+
position_y = self._loaded_positions[position].aperture_y
|
|
271
|
+
position_z = self._loaded_positions[position].aperture_z
|
|
272
|
+
y_matches = abs(current_ap_y - position_y) <= self._tolerances.aperture_y
|
|
273
|
+
z_matches = abs(current_ap_z - position_z) <= self._tolerances.aperture_z
|
|
274
|
+
return y_matches and z_matches
|
|
246
275
|
|
|
247
276
|
def _get_current_aperture_position(
|
|
248
|
-
self,
|
|
277
|
+
self,
|
|
278
|
+
large: float,
|
|
279
|
+
medium: float,
|
|
280
|
+
small: float,
|
|
281
|
+
current_ap_y: float,
|
|
282
|
+
current_ap_z: float,
|
|
249
283
|
) -> ApertureValue:
|
|
250
284
|
if large == 1:
|
|
251
285
|
return ApertureValue.LARGE
|
|
@@ -253,7 +287,11 @@ class ApertureScatterguard(StandardReadable, Preparable):
|
|
|
253
287
|
return ApertureValue.MEDIUM
|
|
254
288
|
elif small == 1:
|
|
255
289
|
return ApertureValue.SMALL
|
|
256
|
-
elif self.
|
|
290
|
+
elif self._is_in_position(ApertureValue.PARKED, current_ap_y, current_ap_z):
|
|
291
|
+
return ApertureValue.PARKED
|
|
292
|
+
elif self._is_in_position(
|
|
293
|
+
ApertureValue.OUT_OF_BEAM, current_ap_y, current_ap_z
|
|
294
|
+
):
|
|
257
295
|
return ApertureValue.OUT_OF_BEAM
|
|
258
296
|
|
|
259
297
|
raise InvalidApertureMove("Current aperture/scatterguard state unrecognised")
|
|
@@ -317,7 +355,9 @@ class ApertureScatterguard(StandardReadable, Preparable):
|
|
|
317
355
|
Moving the assembly whilst out of the beam has no collision risk so we can just
|
|
318
356
|
move all the motors together.
|
|
319
357
|
"""
|
|
320
|
-
|
|
358
|
+
current_y = await self.aperture.y.user_readback.get_value()
|
|
359
|
+
current_z = await self.aperture.z.user_readback.get_value()
|
|
360
|
+
if self._is_in_position(ApertureValue.OUT_OF_BEAM, current_y, current_z):
|
|
321
361
|
aperture_x, _, aperture_z, scatterguard_x, scatterguard_y = (
|
|
322
362
|
self._loaded_positions[value].values
|
|
323
363
|
)
|
dodal/devices/b16/detector.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from ophyd_async.epics.adcore import (
|
|
2
|
-
ADBaseController,
|
|
3
2
|
ADBaseIO,
|
|
4
3
|
ADTIFFWriter,
|
|
5
4
|
AreaDetector,
|
|
@@ -7,15 +6,7 @@ from ophyd_async.epics.adcore import (
|
|
|
7
6
|
|
|
8
7
|
from dodal.common.beamlines.beamline_utils import get_path_provider
|
|
9
8
|
from dodal.common.beamlines.device_helpers import CAM_SUFFIX, TIFF_SUFFIX
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class ConstantDeadTimeController(ADBaseController):
|
|
13
|
-
def __init__(self, driver: ADBaseIO, deadtime: float):
|
|
14
|
-
super().__init__(driver)
|
|
15
|
-
self.deadtime = deadtime
|
|
16
|
-
|
|
17
|
-
def get_deadtime(self, exposure: float | None) -> float:
|
|
18
|
-
return self.deadtime
|
|
9
|
+
from dodal.devices.controllers import ConstantDeadTimeController
|
|
19
10
|
|
|
20
11
|
|
|
21
12
|
def software_triggered_tiff_area_detector(prefix: str, deadtime: float = 0.0):
|
dodal/devices/backlight.py
CHANGED
|
@@ -1,35 +1,23 @@
|
|
|
1
1
|
from asyncio import sleep
|
|
2
2
|
|
|
3
3
|
from bluesky.protocols import Movable
|
|
4
|
-
from ophyd_async.core import AsyncStatus,
|
|
4
|
+
from ophyd_async.core import AsyncStatus, InOut, OnOff, StandardReadable
|
|
5
5
|
from ophyd_async.epics.core import epics_signal_rw
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
class
|
|
9
|
-
ON = "On"
|
|
10
|
-
OFF = "Off"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class BacklightPosition(StrictEnum):
|
|
14
|
-
IN = "In"
|
|
15
|
-
OUT = "Out"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class Backlight(StandardReadable, Movable[BacklightPosition]):
|
|
8
|
+
class Backlight(StandardReadable, Movable[InOut]):
|
|
19
9
|
"""Simple device to trigger the pneumatic in/out."""
|
|
20
10
|
|
|
21
11
|
TIME_TO_MOVE_S = 1.0 # Tested using a stopwatch on the beamline 09/2024
|
|
22
12
|
|
|
23
13
|
def __init__(self, prefix: str, name: str = "") -> None:
|
|
24
14
|
with self.add_children_as_readables():
|
|
25
|
-
self.power = epics_signal_rw(
|
|
26
|
-
self.position = epics_signal_rw(
|
|
27
|
-
BacklightPosition, prefix + "-EA-BL-01:CTRL"
|
|
28
|
-
)
|
|
15
|
+
self.power = epics_signal_rw(OnOff, prefix + "-EA-BLIT-01:TOGGLE")
|
|
16
|
+
self.position = epics_signal_rw(InOut, prefix + "-EA-BL-01:CTRL")
|
|
29
17
|
super().__init__(name)
|
|
30
18
|
|
|
31
19
|
@AsyncStatus.wrap
|
|
32
|
-
async def set(self, value:
|
|
20
|
+
async def set(self, value: InOut):
|
|
33
21
|
"""This setter will turn the backlight on when we move it in to the beam and off
|
|
34
22
|
when we move it out.
|
|
35
23
|
|
|
@@ -39,9 +27,9 @@ class Backlight(StandardReadable, Movable[BacklightPosition]):
|
|
|
39
27
|
"""
|
|
40
28
|
old_position = await self.position.get_value()
|
|
41
29
|
await self.position.set(value)
|
|
42
|
-
if value ==
|
|
43
|
-
await self.power.set(
|
|
30
|
+
if value == InOut.OUT:
|
|
31
|
+
await self.power.set(OnOff.OFF)
|
|
44
32
|
else:
|
|
45
|
-
await self.power.set(
|
|
33
|
+
await self.power.set(OnOff.ON)
|
|
46
34
|
if old_position != value:
|
|
47
35
|
await sleep(self.TIME_TO_MOVE_S)
|
dodal/devices/bimorph_mirror.py
CHANGED
|
@@ -22,12 +22,9 @@ from ophyd_async.epics.core import (
|
|
|
22
22
|
epics_signal_x,
|
|
23
23
|
)
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
from dodal.common.enums import OnOffUpper
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
ON = "ON"
|
|
30
|
-
OFF = "OFF"
|
|
27
|
+
DEFAULT_TIMEOUT = 60
|
|
31
28
|
|
|
32
29
|
|
|
33
30
|
class BimorphMirrorMode(StrictEnum):
|
|
@@ -54,7 +51,7 @@ class BimorphMirrorChannel(StandardReadable, EpicsDevice):
|
|
|
54
51
|
|
|
55
52
|
target_voltage: A[SignalRW[float], PvSuffix.rbv("VTRGT"), Format.CONFIG_SIGNAL]
|
|
56
53
|
output_voltage: A[SignalRW[float], PvSuffix.rbv("VOUT"), Format.HINTED_SIGNAL]
|
|
57
|
-
status: A[SignalR[
|
|
54
|
+
status: A[SignalR[OnOffUpper], PvSuffix("STATUS"), Format.CONFIG_SIGNAL]
|
|
58
55
|
shift: A[SignalW[float], PvSuffix("SHIFT")]
|
|
59
56
|
|
|
60
57
|
|
|
@@ -87,7 +84,7 @@ class BimorphMirror(StandardReadable, Movable[list[float]]):
|
|
|
87
84
|
for i in range(1, number_of_channels + 1)
|
|
88
85
|
}
|
|
89
86
|
)
|
|
90
|
-
self.enabled = epics_signal_w(
|
|
87
|
+
self.enabled = epics_signal_w(OnOffUpper, f"{prefix}ONOFF")
|
|
91
88
|
self.commit_target_voltages = epics_signal_x(f"{prefix}ALLTRGT.PROC")
|
|
92
89
|
self.status = epics_signal_r(BimorphMirrorStatus, f"{prefix}STATUS")
|
|
93
90
|
self.err = epics_signal_r(str, f"{prefix}ERR")
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from ophyd_async.core import StandardReadable
|
|
2
|
+
from ophyd_async.epics.motor import Motor
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class CollimationTable(StandardReadable):
|
|
6
|
+
"""Basic collimation table device for motion plus the motion disable signal
|
|
7
|
+
when laser curtain triggered and hutch not locked.
|
|
8
|
+
|
|
9
|
+
The table has 3 physical vertical motors, the jacks. 1 upstream and 2 downstream.
|
|
10
|
+
The two downstream jacks are labelled as outboard (away from the ring) and
|
|
11
|
+
inboard (towards the ring).
|
|
12
|
+
Together these 3 jacks provide compound motion for vertical motion and pitch/roll.
|
|
13
|
+
There are 2 physical horizontal motors 1 upstream, 1 downstream. These provide yaw.
|
|
14
|
+
|
|
15
|
+
Table motion is disabled by an object being within the laser curtain area and can be
|
|
16
|
+
overridden by use of the dead man's handle device or locking the hutch. The effect of
|
|
17
|
+
these disabling systems is to cut power to the motors - signal for this is crate_power
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
21
|
+
with self.add_children_as_readables():
|
|
22
|
+
self.inboard_y = Motor(f"{prefix}:INBOARDY")
|
|
23
|
+
self.outboard_y = Motor(f"{prefix}:OUTBOARDY")
|
|
24
|
+
self.upstream_y = Motor(f"{prefix}:UPSTREAMY")
|
|
25
|
+
self.combined_downstream_y = Motor(f"{prefix}:DOWNSTREAMY")
|
|
26
|
+
self.combined_all_y = Motor(f"{prefix}:Y")
|
|
27
|
+
|
|
28
|
+
self.downstream_x = Motor(f"{prefix}:DOWNSTREAMX")
|
|
29
|
+
self.upstream_x = Motor(f"{prefix}:UPSTREAMX")
|
|
30
|
+
self.combined_all_x = Motor(f"{prefix}:X")
|
|
31
|
+
|
|
32
|
+
self.pitch = Motor(f"{prefix}:PITCH")
|
|
33
|
+
self.roll = Motor(f"{prefix}:ROLL")
|
|
34
|
+
self.yaw = Motor(f"{prefix}:YAW")
|
|
35
|
+
|
|
36
|
+
super().__init__(name)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from typing import TypeVar
|
|
2
|
+
|
|
3
|
+
from ophyd_async.epics.adcore import (
|
|
4
|
+
ADBaseController,
|
|
5
|
+
ADBaseIO,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
ADBaseIOT = TypeVar("ADBaseIOT", bound=ADBaseIO)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ConstantDeadTimeController(ADBaseController[ADBaseIOT]):
|
|
12
|
+
"""
|
|
13
|
+
ADBaseController with a configured constant deadtime for a driver of type ADBaseIO.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, driver: ADBaseIOT, deadtime: float):
|
|
17
|
+
super().__init__(driver)
|
|
18
|
+
self.deadtime = deadtime
|
|
19
|
+
|
|
20
|
+
def get_deadtime(self, exposure: float | None) -> float:
|
|
21
|
+
return self.deadtime
|