dls-dodal 1.55.0__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.0.dist-info → dls_dodal-1.56.0.dist-info}/METADATA +3 -3
- {dls_dodal-1.55.0.dist-info → dls_dodal-1.56.0.dist-info}/RECORD +100 -86
- dodal/_version.py +2 -2
- dodal/beamlines/b01_1.py +6 -13
- 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/attenuator/attenuator.py +3 -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/tetramm.py +13 -0
- dodal/devices/thawer.py +6 -11
- dodal/devices/undulator.py +3 -8
- dodal/devices/util/epics_util.py +1 -1
- dodal/devices/util/test_utils.py +19 -0
- 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.0.dist-info → dls_dodal-1.56.0.dist-info}/WHEEL +0 -0
- {dls_dodal-1.55.0.dist-info → dls_dodal-1.56.0.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.55.0.dist-info → dls_dodal-1.56.0.dist-info}/licenses/LICENSE +0 -0
- {dls_dodal-1.55.0.dist-info → dls_dodal-1.56.0.dist-info}/top_level.txt +0 -0
|
@@ -4,10 +4,30 @@ from dodal.devices.electron_analyser.enums import EnergyMode
|
|
|
4
4
|
def to_kinetic_energy(
|
|
5
5
|
value: float, value_mode: EnergyMode, excitation_energy: float
|
|
6
6
|
) -> float:
|
|
7
|
+
"""
|
|
8
|
+
Convert a value that is binding energy to kinetic energy.
|
|
9
|
+
Parameters:
|
|
10
|
+
value: The value to convert.
|
|
11
|
+
value_mode: Energy mode of the value. If it is already kinetic, return the
|
|
12
|
+
same value. If it is binding, convert to kinetic.
|
|
13
|
+
excitation_energy: Value to calculate the conversion.
|
|
14
|
+
Returns:
|
|
15
|
+
Caluclated kinetic energy value
|
|
16
|
+
"""
|
|
7
17
|
return value if value_mode == EnergyMode.KINETIC else excitation_energy - value
|
|
8
18
|
|
|
9
19
|
|
|
10
20
|
def to_binding_energy(
|
|
11
21
|
value: float, value_mode: EnergyMode, excitation_energy: float
|
|
12
22
|
) -> float:
|
|
23
|
+
"""
|
|
24
|
+
Convert a value that is kinetic energy to binding energy.
|
|
25
|
+
Parameters:
|
|
26
|
+
value: The value to convert.
|
|
27
|
+
value_mode: Energy mode of the value. If it is already binding, return the
|
|
28
|
+
same value. If it is kinetic, convert to binding.
|
|
29
|
+
excitation_energy: Value to calculate the conversion.
|
|
30
|
+
Returns:
|
|
31
|
+
Caluclated binding energy value
|
|
32
|
+
"""
|
|
13
33
|
return value if value_mode == EnergyMode.BINDING else excitation_energy - value
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
from .detector import VGScientaDetector
|
|
2
2
|
from .driver_io import VGScientaAnalyserDriverIO
|
|
3
|
-
from .enums import AcquisitionMode
|
|
4
|
-
from .region import
|
|
3
|
+
from .enums import AcquisitionMode, DetectorMode
|
|
4
|
+
from .region import VGScientaRegion, VGScientaSequence
|
|
5
5
|
|
|
6
6
|
__all__ = [
|
|
7
7
|
"VGScientaDetector",
|
|
8
8
|
"VGScientaAnalyserDriverIO",
|
|
9
9
|
"AcquisitionMode",
|
|
10
|
-
"
|
|
10
|
+
"DetectorMode",
|
|
11
11
|
"VGScientaRegion",
|
|
12
12
|
"VGScientaSequence",
|
|
13
13
|
]
|
|
@@ -11,6 +11,7 @@ from dodal.devices.electron_analyser.abstract.types import (
|
|
|
11
11
|
from dodal.devices.electron_analyser.detector import (
|
|
12
12
|
ElectronAnalyserDetector,
|
|
13
13
|
)
|
|
14
|
+
from dodal.devices.electron_analyser.enums import SelectedSource
|
|
14
15
|
from dodal.devices.electron_analyser.vgscienta.driver_io import (
|
|
15
16
|
VGScientaAnalyserDriverIO,
|
|
16
17
|
)
|
|
@@ -34,7 +35,7 @@ class VGScientaDetector(
|
|
|
34
35
|
lens_mode_type: type[TLensMode],
|
|
35
36
|
psu_mode_type: type[TPsuMode],
|
|
36
37
|
pass_energy_type: type[TPassEnergyEnum],
|
|
37
|
-
energy_sources: Mapping[
|
|
38
|
+
energy_sources: Mapping[SelectedSource, SignalR[float]],
|
|
38
39
|
name: str = "",
|
|
39
40
|
):
|
|
40
41
|
driver = VGScientaAnalyserDriverIO[TLensMode, TPsuMode, TPassEnergyEnum](
|
|
@@ -9,7 +9,6 @@ from ophyd_async.core import (
|
|
|
9
9
|
SignalR,
|
|
10
10
|
StandardReadableFormat,
|
|
11
11
|
)
|
|
12
|
-
from ophyd_async.epics.adcore import ADImageMode
|
|
13
12
|
from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
|
|
14
13
|
|
|
15
14
|
from dodal.devices.electron_analyser.abstract.base_driver_io import (
|
|
@@ -20,7 +19,7 @@ from dodal.devices.electron_analyser.abstract.types import (
|
|
|
20
19
|
TPassEnergyEnum,
|
|
21
20
|
TPsuMode,
|
|
22
21
|
)
|
|
23
|
-
from dodal.devices.electron_analyser.
|
|
22
|
+
from dodal.devices.electron_analyser.enums import EnergyMode, SelectedSource
|
|
24
23
|
from dodal.devices.electron_analyser.vgscienta.enums import (
|
|
25
24
|
AcquisitionMode,
|
|
26
25
|
DetectorMode,
|
|
@@ -46,7 +45,7 @@ class VGScientaAnalyserDriverIO(
|
|
|
46
45
|
lens_mode_type: type[TLensMode],
|
|
47
46
|
psu_mode_type: type[TPsuMode],
|
|
48
47
|
pass_energy_type: type[TPassEnergyEnum],
|
|
49
|
-
energy_sources: Mapping[
|
|
48
|
+
energy_sources: Mapping[SelectedSource, SignalR[float]],
|
|
50
49
|
name: str = "",
|
|
51
50
|
) -> None:
|
|
52
51
|
with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
|
|
@@ -55,11 +54,11 @@ class VGScientaAnalyserDriverIO(
|
|
|
55
54
|
|
|
56
55
|
self.region_min_x = epics_signal_rw(int, prefix + "MinX")
|
|
57
56
|
self.region_size_x = epics_signal_rw(int, prefix + "SizeX")
|
|
58
|
-
self.sensor_max_size_x =
|
|
57
|
+
self.sensor_max_size_x = epics_signal_r(int, prefix + "MaxSizeX_RBV")
|
|
59
58
|
|
|
60
59
|
self.region_min_y = epics_signal_rw(int, prefix + "MinY")
|
|
61
60
|
self.region_size_y = epics_signal_rw(int, prefix + "SizeY")
|
|
62
|
-
self.sensor_max_size_y =
|
|
61
|
+
self.sensor_max_size_y = epics_signal_r(int, prefix + "MaxSizeY_RBV")
|
|
63
62
|
|
|
64
63
|
super().__init__(
|
|
65
64
|
prefix,
|
|
@@ -75,36 +74,29 @@ class VGScientaAnalyserDriverIO(
|
|
|
75
74
|
async def set(self, region: VGScientaRegion[TLensMode, TPassEnergyEnum]):
|
|
76
75
|
source = self._get_energy_source(region.excitation_energy_source)
|
|
77
76
|
excitation_energy = await source.get_value() # eV
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
)
|
|
82
|
-
centre_energy = to_kinetic_energy(
|
|
83
|
-
region.centre_energy, region.energy_mode, excitation_energy
|
|
84
|
-
)
|
|
85
|
-
high_energy = to_kinetic_energy(
|
|
86
|
-
region.high_energy, region.energy_mode, excitation_energy
|
|
87
|
-
)
|
|
77
|
+
# Copy region so doesn't alter the actual region and switch to kinetic energy
|
|
78
|
+
ke_region = region.model_copy()
|
|
79
|
+
ke_region.switch_energy_mode(EnergyMode.KINETIC, excitation_energy)
|
|
88
80
|
await asyncio.gather(
|
|
89
|
-
self.region_name.set(
|
|
90
|
-
self.energy_mode.set(
|
|
91
|
-
self.low_energy.set(low_energy),
|
|
92
|
-
self.centre_energy.set(centre_energy),
|
|
93
|
-
self.high_energy.set(high_energy),
|
|
94
|
-
self.slices.set(
|
|
95
|
-
self.lens_mode.set(
|
|
96
|
-
self.pass_energy.set(
|
|
97
|
-
self.iterations.set(
|
|
98
|
-
self.
|
|
81
|
+
self.region_name.set(ke_region.name),
|
|
82
|
+
self.energy_mode.set(ke_region.energy_mode),
|
|
83
|
+
self.low_energy.set(ke_region.low_energy),
|
|
84
|
+
self.centre_energy.set(ke_region.centre_energy),
|
|
85
|
+
self.high_energy.set(ke_region.high_energy),
|
|
86
|
+
self.slices.set(ke_region.slices),
|
|
87
|
+
self.lens_mode.set(ke_region.lens_mode),
|
|
88
|
+
self.pass_energy.set(ke_region.pass_energy),
|
|
89
|
+
self.iterations.set(ke_region.iterations),
|
|
90
|
+
self.acquire_time.set(ke_region.acquire_time),
|
|
91
|
+
self.acquisition_mode.set(ke_region.acquisition_mode),
|
|
99
92
|
self.excitation_energy.set(excitation_energy),
|
|
100
93
|
self.excitation_energy_source.set(source.name),
|
|
101
|
-
self.energy_step.set(
|
|
102
|
-
self.
|
|
103
|
-
self.
|
|
104
|
-
self.
|
|
105
|
-
self.
|
|
106
|
-
self.
|
|
107
|
-
self.region_size_y.set(region.size_y),
|
|
94
|
+
self.energy_step.set(ke_region.energy_step),
|
|
95
|
+
self.detector_mode.set(ke_region.detector_mode),
|
|
96
|
+
self.region_min_x.set(ke_region.min_x),
|
|
97
|
+
self.region_size_x.set(ke_region.size_x),
|
|
98
|
+
self.region_min_y.set(ke_region.min_y),
|
|
99
|
+
self.region_size_y.set(ke_region.size_y),
|
|
108
100
|
)
|
|
109
101
|
|
|
110
102
|
def _create_energy_axis_signal(self, prefix: str) -> SignalR[Array1D[np.float64]]:
|
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
from ophyd_async.core import StrictEnum
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
class Status(StrictEnum):
|
|
5
|
-
READY = "Ready"
|
|
6
|
-
RUNNING = "Running"
|
|
7
|
-
COMPLETED = "Completed"
|
|
8
|
-
INVALID = "Invalid"
|
|
9
|
-
ABORTED = "Aborted"
|
|
10
|
-
|
|
11
|
-
|
|
12
4
|
class DetectorMode(StrictEnum):
|
|
13
5
|
ADC = "ADC"
|
|
14
6
|
PULSE_COUNTING = "Pulse Counting"
|
|
@@ -6,7 +6,6 @@ from pydantic import Field, field_validator
|
|
|
6
6
|
from dodal.devices.electron_analyser.abstract.base_region import (
|
|
7
7
|
AbstractBaseRegion,
|
|
8
8
|
AbstractBaseSequence,
|
|
9
|
-
JavaToPythonModel,
|
|
10
9
|
)
|
|
11
10
|
from dodal.devices.electron_analyser.abstract.types import (
|
|
12
11
|
TLensMode,
|
|
@@ -16,7 +15,6 @@ from dodal.devices.electron_analyser.abstract.types import (
|
|
|
16
15
|
from dodal.devices.electron_analyser.vgscienta.enums import (
|
|
17
16
|
AcquisitionMode,
|
|
18
17
|
DetectorMode,
|
|
19
|
-
Status,
|
|
20
18
|
)
|
|
21
19
|
|
|
22
20
|
|
|
@@ -29,10 +27,10 @@ class VGScientaRegion(
|
|
|
29
27
|
pass_energy: TPassEnergyEnum
|
|
30
28
|
acquisition_mode: AcquisitionMode = AcquisitionMode.SWEPT
|
|
31
29
|
low_energy: float = 8.0
|
|
30
|
+
centre_energy: float = Field(alias="fix_energy", default=9)
|
|
32
31
|
high_energy: float = 10.0
|
|
33
|
-
|
|
32
|
+
acquire_time: float = Field(default=1.0, alias="step_time")
|
|
34
33
|
energy_step: float = Field(default=200.0)
|
|
35
|
-
centre_energy: float = Field(alias="fix_energy", default=9)
|
|
36
34
|
# Specific to this class
|
|
37
35
|
id: str = Field(default=str(uuid.uuid4()), alias="region_id")
|
|
38
36
|
total_steps: float = 13.0
|
|
@@ -42,7 +40,6 @@ class VGScientaRegion(
|
|
|
42
40
|
min_y: int = Field(alias="first_y_channel", default=101)
|
|
43
41
|
sensor_max_size_y: int = Field(alias="last_y_channel", default=800)
|
|
44
42
|
detector_mode: DetectorMode = DetectorMode.ADC
|
|
45
|
-
status: Status = Status.READY
|
|
46
43
|
|
|
47
44
|
@property
|
|
48
45
|
def size_x(self) -> int:
|
|
@@ -60,37 +57,11 @@ class VGScientaRegion(
|
|
|
60
57
|
return str(val)
|
|
61
58
|
|
|
62
59
|
|
|
63
|
-
class VGScientaExcitationEnergySource(JavaToPythonModel):
|
|
64
|
-
name: str = "source1"
|
|
65
|
-
device_name: str = Field(default="", alias="scannable_name")
|
|
66
|
-
value: float = 0
|
|
67
|
-
|
|
68
|
-
|
|
69
60
|
class VGScientaSequence(
|
|
70
61
|
AbstractBaseSequence[VGScientaRegion[TLensMode, TPassEnergyEnum]],
|
|
71
62
|
Generic[TLensMode, TPsuMode, TPassEnergyEnum],
|
|
72
63
|
):
|
|
73
64
|
psu_mode: TPsuMode = Field(alias="element_set")
|
|
74
|
-
excitation_energy_sources: list[VGScientaExcitationEnergySource] = Field(
|
|
75
|
-
default_factory=lambda: []
|
|
76
|
-
)
|
|
77
65
|
regions: list[VGScientaRegion[TLensMode, TPassEnergyEnum]] = Field(
|
|
78
66
|
default_factory=lambda: []
|
|
79
67
|
)
|
|
80
|
-
|
|
81
|
-
def get_excitation_energy_source_by_region(
|
|
82
|
-
self, region: VGScientaRegion[TLensMode, TPassEnergyEnum]
|
|
83
|
-
) -> VGScientaExcitationEnergySource:
|
|
84
|
-
value = next(
|
|
85
|
-
(
|
|
86
|
-
e
|
|
87
|
-
for e in self.excitation_energy_sources
|
|
88
|
-
if region.excitation_energy_source == e.name
|
|
89
|
-
),
|
|
90
|
-
None,
|
|
91
|
-
)
|
|
92
|
-
if value is None:
|
|
93
|
-
raise ValueError(
|
|
94
|
-
f'Unable to find excitation energy source using region "{region.name}"'
|
|
95
|
-
)
|
|
96
|
-
return value
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
from typing import Generic, TypeVar
|
|
2
|
+
|
|
3
|
+
from bluesky.protocols import Locatable, Location
|
|
4
|
+
from ophyd_async.core import (
|
|
5
|
+
AsyncStatus,
|
|
6
|
+
OnOff,
|
|
7
|
+
StandardReadable,
|
|
8
|
+
StandardReadableFormat,
|
|
9
|
+
StrictEnum,
|
|
10
|
+
)
|
|
11
|
+
from ophyd_async.epics.core import epics_signal_r, epics_signal_rw, epics_signal_rw_rbv
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
Note: See i11 cyberstar blower for implementation of Eurotherm Controller
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AutoManual(StrictEnum):
|
|
19
|
+
AUTO = "Automatic"
|
|
20
|
+
MANUAL = "Manual"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class EurothermUpdate(StrictEnum):
|
|
24
|
+
PASSIVE = "Passive"
|
|
25
|
+
EVENT = "Event"
|
|
26
|
+
IO = "I/O Intr"
|
|
27
|
+
S10 = "10 second"
|
|
28
|
+
S5 = "5 second"
|
|
29
|
+
S2 = "2 second"
|
|
30
|
+
S1 = "1 second"
|
|
31
|
+
S0_5 = ".5 second"
|
|
32
|
+
S0_2 = ".2 second"
|
|
33
|
+
S0_1 = ".1 second"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
_EUROTHERM_RBV: str = ":RBV"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class EurothermPID(StandardReadable):
|
|
40
|
+
"""The class for the Eurotherm PID values"""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
prefix: str,
|
|
45
|
+
name: str = "",
|
|
46
|
+
):
|
|
47
|
+
with self.add_children_as_readables():
|
|
48
|
+
self.P = epics_signal_rw_rbv(float, f"{prefix}P", _EUROTHERM_RBV)
|
|
49
|
+
self.I = epics_signal_rw_rbv(float, f"{prefix}I", _EUROTHERM_RBV)
|
|
50
|
+
self.D = epics_signal_rw_rbv(float, f"{prefix}D", _EUROTHERM_RBV)
|
|
51
|
+
|
|
52
|
+
super().__init__(name)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class UpdatingEurothermPID(EurothermPID):
|
|
56
|
+
"""A Eurotherm PID controller that updates the PID values."""
|
|
57
|
+
|
|
58
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
59
|
+
with self.add_children_as_readables():
|
|
60
|
+
self.update = epics_signal_rw(EurothermUpdate, f"{prefix}UPDATE.SCAN")
|
|
61
|
+
|
|
62
|
+
super().__init__(prefix=prefix, name=name)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
P = TypeVar("P", bound=EurothermPID)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class EurothermGeneral(StandardReadable, Locatable[float], Generic[P]):
|
|
69
|
+
"""A base class for any eurotherm controller."""
|
|
70
|
+
|
|
71
|
+
def __init__(
|
|
72
|
+
self,
|
|
73
|
+
prefix: str,
|
|
74
|
+
name: str = "",
|
|
75
|
+
pid_class: type[P] = EurothermPID,
|
|
76
|
+
temp_suffix: str = "PV:RBV",
|
|
77
|
+
):
|
|
78
|
+
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
|
|
79
|
+
self.temp = epics_signal_r(float, f"{prefix}{temp_suffix}")
|
|
80
|
+
|
|
81
|
+
with self.add_children_as_readables():
|
|
82
|
+
self.setpoint = epics_signal_rw_rbv(float, f"{prefix}SP", _EUROTHERM_RBV)
|
|
83
|
+
self.ramprate = epics_signal_rw_rbv(float, f"{prefix}RR", _EUROTHERM_RBV)
|
|
84
|
+
self.output = epics_signal_rw_rbv(float, f"{prefix}O", _EUROTHERM_RBV)
|
|
85
|
+
self.mode = epics_signal_rw_rbv(AutoManual, f"{prefix}MAN", _EUROTHERM_RBV)
|
|
86
|
+
|
|
87
|
+
self.tune = pid_class(prefix=prefix)
|
|
88
|
+
|
|
89
|
+
super().__init__(name)
|
|
90
|
+
|
|
91
|
+
@AsyncStatus.wrap
|
|
92
|
+
async def set(self, value: float):
|
|
93
|
+
"""Set the blower to a specific temperature."""
|
|
94
|
+
await self.setpoint.set(value, wait=True)
|
|
95
|
+
|
|
96
|
+
async def locate(self) -> Location[float]:
|
|
97
|
+
setpoint = await self.setpoint.get_value()
|
|
98
|
+
current_temp = await self.temp.get_value()
|
|
99
|
+
location = Location(setpoint=setpoint, readback=current_temp)
|
|
100
|
+
return location
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class UpdatingEurothermGeneral(EurothermGeneral):
|
|
104
|
+
"""A Eurotherm controller that updates the setpoint and readback."""
|
|
105
|
+
|
|
106
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
107
|
+
self.update = epics_signal_rw(EurothermUpdate, f"{prefix}UPDATE.SCAN")
|
|
108
|
+
|
|
109
|
+
super().__init__(prefix=prefix, name=name, pid_class=UpdatingEurothermPID)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class EurothermAutotune(StandardReadable):
|
|
113
|
+
"""Newer versions of Eurotherm controllers have the ability to Autotune the
|
|
114
|
+
PID values, and this is the device"""
|
|
115
|
+
|
|
116
|
+
def __init__(
|
|
117
|
+
self,
|
|
118
|
+
prefix: str,
|
|
119
|
+
name: str = "",
|
|
120
|
+
):
|
|
121
|
+
with self.add_children_as_readables():
|
|
122
|
+
self.control = epics_signal_rw(OnOff, f"{prefix}AUTOTUNE")
|
|
123
|
+
self.high_limit = epics_signal_rw(float, f"{prefix}OUTPHI")
|
|
124
|
+
self.low_limit = epics_signal_rw(float, f"{prefix}OUTPLO")
|
|
125
|
+
|
|
126
|
+
super().__init__(name)
|
|
@@ -1,16 +1,9 @@
|
|
|
1
|
-
from ophyd_async.core import
|
|
2
|
-
from ophyd_async.epics.core import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class FluorescenceDetectorControlState(StrictEnum):
|
|
6
|
-
OUT = "Out"
|
|
7
|
-
IN = "In"
|
|
1
|
+
from ophyd_async.core import InOut, StandardReadable
|
|
2
|
+
from ophyd_async.epics.core import epics_signal_rw
|
|
8
3
|
|
|
9
4
|
|
|
10
5
|
class FluorescenceDetector(StandardReadable):
|
|
11
6
|
def __init__(self, prefix: str, name: str = ""):
|
|
12
7
|
with self.add_children_as_readables():
|
|
13
|
-
self.pos =
|
|
14
|
-
FluorescenceDetectorControlState, prefix + "-EA-FLU-01:CTRL"
|
|
15
|
-
)
|
|
8
|
+
self.pos = epics_signal_rw(InOut, f"{prefix}CTRL")
|
|
16
9
|
super().__init__(name)
|
dodal/devices/focusing_mirror.py
CHANGED
|
@@ -56,7 +56,7 @@ class SingleMirrorVoltage(Device):
|
|
|
56
56
|
the demanded voltage setpoint is accepted, without blocking the caller as this process can take significant time.
|
|
57
57
|
"""
|
|
58
58
|
|
|
59
|
-
def __init__(self,
|
|
59
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
60
60
|
self._actual_v = epics_signal_r(int, prefix + "R")
|
|
61
61
|
self._setpoint_v = epics_signal_rw(int, prefix + "D")
|
|
62
62
|
self._demand_accepted = epics_signal_r(MirrorVoltageDemand, prefix + "DSEV")
|
dodal/devices/i09/enums.py
CHANGED
|
@@ -21,11 +21,11 @@ class PsuMode(StrictEnum):
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class PassEnergy(StrictEnum):
|
|
24
|
-
E5 = 5
|
|
25
|
-
E10 = 10
|
|
26
|
-
E20 = 20
|
|
27
|
-
E50 = 50
|
|
28
|
-
E70 = 70
|
|
29
|
-
E100 = 100
|
|
30
|
-
E200 = 200
|
|
31
|
-
E500 = 500
|
|
24
|
+
E5 = "5"
|
|
25
|
+
E10 = "10"
|
|
26
|
+
E20 = "20"
|
|
27
|
+
E50 = "50"
|
|
28
|
+
E70 = "70"
|
|
29
|
+
E100 = "100"
|
|
30
|
+
E200 = "200"
|
|
31
|
+
E500 = "500"
|
dodal/devices/i10/diagnostics.py
CHANGED
|
@@ -54,13 +54,13 @@ class D7Position(StrictEnum):
|
|
|
54
54
|
SHUTTER = "Shutter"
|
|
55
55
|
|
|
56
56
|
|
|
57
|
-
class
|
|
57
|
+
class InStateTable(StrictEnum):
|
|
58
58
|
MOVE_IN = "Move In"
|
|
59
59
|
MOVE_OUT = "Move Out"
|
|
60
60
|
RESET = "Reset"
|
|
61
61
|
|
|
62
62
|
|
|
63
|
-
class
|
|
63
|
+
class InStateReadBackTable(StrictEnum):
|
|
64
64
|
MOVE_IN = "Moving In"
|
|
65
65
|
MOVE_OUT = "Moving Out"
|
|
66
66
|
IN_BEAM = "In Beam"
|
|
@@ -80,11 +80,11 @@ class I10PneumaticStage(StandardReadable):
|
|
|
80
80
|
) -> None:
|
|
81
81
|
with self.add_children_as_readables(Format.HINTED_SIGNAL):
|
|
82
82
|
self.stage_position_set = epics_signal_rw(
|
|
83
|
-
|
|
83
|
+
InStateTable,
|
|
84
84
|
read_pv=prefix + "CON",
|
|
85
85
|
)
|
|
86
86
|
self.stage_position_readback = epics_signal_r(
|
|
87
|
-
|
|
87
|
+
InStateReadBackTable,
|
|
88
88
|
read_pv=prefix + "STA",
|
|
89
89
|
)
|
|
90
90
|
super().__init__(name=name)
|
dodal/devices/i10/i10_apple2.py
CHANGED
|
@@ -69,7 +69,7 @@ class I10Apple2(Apple2):
|
|
|
69
69
|
self,
|
|
70
70
|
look_up_table_dir: str,
|
|
71
71
|
source: tuple[str, str],
|
|
72
|
-
prefix: str
|
|
72
|
+
prefix: str,
|
|
73
73
|
mode: str = "Mode",
|
|
74
74
|
min_energy: str = "MinEnergy",
|
|
75
75
|
max_energy: str = "MaxEnergy",
|
|
@@ -97,11 +97,9 @@ class I10Apple2(Apple2):
|
|
|
97
97
|
Name of the device
|
|
98
98
|
"""
|
|
99
99
|
|
|
100
|
-
energy_gap_table_path = Path(
|
|
101
|
-
look_up_table_dir + "IDEnergy2GapCalibrations.csv",
|
|
102
|
-
)
|
|
100
|
+
energy_gap_table_path = Path(look_up_table_dir, "IDEnergy2GapCalibrations.csv")
|
|
103
101
|
energy_phase_table_path = Path(
|
|
104
|
-
look_up_table_dir
|
|
102
|
+
look_up_table_dir, "IDEnergy2PhaseCalibrations.csv"
|
|
105
103
|
)
|
|
106
104
|
# A dataclass contains the path to the look up table and the expected column names.
|
|
107
105
|
self.lookup_table_config = LookupTableConfig(
|
|
@@ -124,7 +122,6 @@ class I10Apple2(Apple2):
|
|
|
124
122
|
btm_inner="RPQ3",
|
|
125
123
|
btm_outer="RPQ4",
|
|
126
124
|
),
|
|
127
|
-
prefix=prefix,
|
|
128
125
|
name=name,
|
|
129
126
|
)
|
|
130
127
|
self.id_jaw_phase = UndulatorJawPhase(
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from typing import Generic, TypeVar
|
|
2
|
+
|
|
3
|
+
from ophyd_async.core import EnabledDisabled, StandardReadable
|
|
4
|
+
from ophyd_async.epics.core import epics_signal_rw
|
|
5
|
+
|
|
6
|
+
from dodal.devices.eurotherm import EurothermAutotune, EurothermGeneral
|
|
7
|
+
|
|
8
|
+
EU = TypeVar("EU", bound=EurothermGeneral)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CyberstarBlower(StandardReadable, Generic[EU]):
|
|
12
|
+
"""This is a specific device that uses a Eurotherm controller"""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
prefix: str,
|
|
17
|
+
name: str = "",
|
|
18
|
+
controller_type: type[EU] = EurothermGeneral,
|
|
19
|
+
):
|
|
20
|
+
self.enable = epics_signal_rw(EnabledDisabled, f"{prefix}DISABLE")
|
|
21
|
+
self.controller = controller_type(prefix, name)
|
|
22
|
+
|
|
23
|
+
super().__init__(name=name)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AutotunedCyberstarBlower(Generic[EU], CyberstarBlower[EU]):
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
prefix: str,
|
|
30
|
+
name: str = "",
|
|
31
|
+
controller_type: type[EU] = EurothermGeneral,
|
|
32
|
+
):
|
|
33
|
+
self.autotune = EurothermAutotune(prefix)
|
|
34
|
+
super().__init__(prefix, name=name, controller_type=controller_type)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from ophyd_async.epics.motor import Motor
|
|
2
|
+
|
|
3
|
+
from dodal.devices.motors import Stage
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DiffractometerStage(Stage):
|
|
7
|
+
"""
|
|
8
|
+
This is the diffractometer stage which contains both detectors,
|
|
9
|
+
it allows for rotations and also sample position. Contains:
|
|
10
|
+
theta, delta, two_theta, sample_position
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
prefix: str,
|
|
16
|
+
name: str = "",
|
|
17
|
+
theta_suffix: str = "THETA",
|
|
18
|
+
delta_suffix: str = "DELTA",
|
|
19
|
+
two_theta_suffix: str = "2THETA",
|
|
20
|
+
sample_pos_suffix: str = "SPOS",
|
|
21
|
+
):
|
|
22
|
+
with self.add_children_as_readables():
|
|
23
|
+
self.theta = Motor(prefix + theta_suffix)
|
|
24
|
+
self.delta = Motor(prefix + delta_suffix)
|
|
25
|
+
self.two_theta = Motor(prefix + two_theta_suffix)
|
|
26
|
+
self.sample_position = Motor(prefix + sample_pos_suffix)
|
|
27
|
+
|
|
28
|
+
super().__init__(name=name)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class DiffractometerBase(Stage):
|
|
32
|
+
"""
|
|
33
|
+
This is the diffractometer stage which contains both detectors,
|
|
34
|
+
it allows for translation about x and y and also sample position. Contains:
|
|
35
|
+
x1, x2, y1, y2, y3. Used for aligning the detector to the beam/sample
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
prefix: str,
|
|
41
|
+
name: str = "",
|
|
42
|
+
x1_suffix: str = "X1",
|
|
43
|
+
x2_suffix: str = "X2",
|
|
44
|
+
y1_suffix: str = "Y1",
|
|
45
|
+
y2_suffix: str = "Y2",
|
|
46
|
+
y3_suffix: str = "Y3",
|
|
47
|
+
):
|
|
48
|
+
with self.add_children_as_readables():
|
|
49
|
+
self.x1 = Motor(prefix + x1_suffix)
|
|
50
|
+
self.x2 = Motor(prefix + x2_suffix)
|
|
51
|
+
self.y1 = Motor(prefix + y1_suffix)
|
|
52
|
+
self.y2 = Motor(prefix + y2_suffix)
|
|
53
|
+
self.y3 = Motor(prefix + y3_suffix)
|
|
54
|
+
|
|
55
|
+
super().__init__(name=name)
|