dls-dodal 1.36.3__py3-none-any.whl → 1.37.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.36.3.dist-info → dls_dodal-1.37.0.dist-info}/METADATA +3 -3
- {dls_dodal-1.36.3.dist-info → dls_dodal-1.37.0.dist-info}/RECORD +43 -29
- {dls_dodal-1.36.3.dist-info → dls_dodal-1.37.0.dist-info}/WHEEL +1 -1
- dodal/_version.py +2 -2
- dodal/beamlines/i02_1.py +37 -0
- dodal/beamlines/i03.py +20 -3
- dodal/beamlines/i04.py +3 -3
- dodal/beamlines/i10.py +105 -0
- dodal/beamlines/i22.py +15 -0
- dodal/beamlines/i24.py +1 -1
- dodal/beamlines/p99.py +6 -2
- dodal/common/crystal_metadata.py +3 -3
- dodal/common/udc_directory_provider.py +3 -1
- dodal/devices/aperturescatterguard.py +3 -0
- dodal/devices/{attenuator.py → attenuator/attenuator.py} +29 -1
- dodal/devices/attenuator/filter.py +11 -0
- dodal/devices/attenuator/filter_selections.py +72 -0
- dodal/devices/bimorph_mirror.py +151 -0
- dodal/devices/current_amplifiers/__init__.py +34 -0
- dodal/devices/current_amplifiers/current_amplifier.py +103 -0
- dodal/devices/current_amplifiers/current_amplifier_detector.py +109 -0
- dodal/devices/current_amplifiers/femto.py +143 -0
- dodal/devices/current_amplifiers/sr570.py +214 -0
- dodal/devices/current_amplifiers/struck_scaler_counter.py +79 -0
- dodal/devices/detector/det_dim_constants.py +15 -0
- dodal/devices/eiger_odin.py +3 -3
- dodal/devices/fast_grid_scan.py +8 -3
- dodal/devices/i03/beamstop.py +85 -0
- dodal/devices/i04/transfocator.py +67 -53
- dodal/devices/i10/rasor/rasor_current_amp.py +72 -0
- dodal/devices/i10/rasor/rasor_motors.py +62 -0
- dodal/devices/i10/rasor/rasor_scaler_cards.py +12 -0
- dodal/devices/p99/sample_stage.py +2 -28
- dodal/devices/robot.py +2 -2
- dodal/devices/undulator_dcm.py +9 -11
- dodal/devices/zebra.py +6 -1
- dodal/devices/zocalo/zocalo_interaction.py +2 -1
- dodal/devices/zocalo/zocalo_results.py +22 -2
- dodal/log.py +2 -2
- dodal/plans/wrapped.py +3 -3
- {dls_dodal-1.36.3.dist-info → dls_dodal-1.37.0.dist-info}/LICENSE +0 -0
- {dls_dodal-1.36.3.dist-info → dls_dodal-1.37.0.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.36.3.dist-info → dls_dodal-1.37.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
from ophyd_async.core import (
|
|
5
|
+
AsyncStatus,
|
|
6
|
+
StandardReadableFormat,
|
|
7
|
+
StrictEnum,
|
|
8
|
+
)
|
|
9
|
+
from ophyd_async.epics.core import epics_signal_rw
|
|
10
|
+
|
|
11
|
+
from dodal.devices.current_amplifiers.current_amplifier import CurrentAmp
|
|
12
|
+
from dodal.log import LOGGER
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SR570GainTable(StrictEnum):
|
|
16
|
+
"""Coarse/unit sensitivity setting for SR570 current amplifier"""
|
|
17
|
+
|
|
18
|
+
SEN_1 = "mA/V"
|
|
19
|
+
SEN_2 = "uA/V"
|
|
20
|
+
SEN_3 = "nA/V"
|
|
21
|
+
SEN_4 = "pA/V"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SR570FineGainTable(StrictEnum):
|
|
25
|
+
"""Fine sensitivity setting for SR570 current amplifier"""
|
|
26
|
+
|
|
27
|
+
SEN_1 = "1"
|
|
28
|
+
SEN_2 = "2"
|
|
29
|
+
SEN_3 = "5"
|
|
30
|
+
SEN_4 = "10"
|
|
31
|
+
SEN_5 = "20"
|
|
32
|
+
SEN_6 = "50"
|
|
33
|
+
SEN_7 = "100"
|
|
34
|
+
SEN_8 = "200"
|
|
35
|
+
SEN_9 = "500"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class SR570RaiseTimeTable(float, Enum):
|
|
39
|
+
"""These are the gain dependent raise time(s) for SR570 current amplifier"""
|
|
40
|
+
|
|
41
|
+
SEN_1 = 1e-4
|
|
42
|
+
SEN_2 = 1e-2
|
|
43
|
+
SEN_3 = 0.15
|
|
44
|
+
SEN_4 = 0.2
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class SR570FullGainTable(Enum):
|
|
48
|
+
"""Combined gain table, as each gain step is a combination of both coarse gain and
|
|
49
|
+
fine gain setting"""
|
|
50
|
+
|
|
51
|
+
SEN_1 = [SR570GainTable.SEN_1, SR570FineGainTable.SEN_1]
|
|
52
|
+
SEN_2 = [SR570GainTable.SEN_2, SR570FineGainTable.SEN_9]
|
|
53
|
+
SEN_3 = [SR570GainTable.SEN_2, SR570FineGainTable.SEN_8]
|
|
54
|
+
SEN_4 = [SR570GainTable.SEN_2, SR570FineGainTable.SEN_7]
|
|
55
|
+
SEN_5 = [SR570GainTable.SEN_2, SR570FineGainTable.SEN_6]
|
|
56
|
+
SEN_6 = [SR570GainTable.SEN_2, SR570FineGainTable.SEN_5]
|
|
57
|
+
SEN_7 = [SR570GainTable.SEN_2, SR570FineGainTable.SEN_4]
|
|
58
|
+
SEN_8 = [SR570GainTable.SEN_2, SR570FineGainTable.SEN_3]
|
|
59
|
+
SEN_9 = [SR570GainTable.SEN_2, SR570FineGainTable.SEN_2]
|
|
60
|
+
SEN_10 = [SR570GainTable.SEN_2, SR570FineGainTable.SEN_1]
|
|
61
|
+
SEN_11 = [SR570GainTable.SEN_3, SR570FineGainTable.SEN_9]
|
|
62
|
+
SEN_12 = [SR570GainTable.SEN_3, SR570FineGainTable.SEN_8]
|
|
63
|
+
SEN_13 = [SR570GainTable.SEN_3, SR570FineGainTable.SEN_7]
|
|
64
|
+
SEN_14 = [SR570GainTable.SEN_3, SR570FineGainTable.SEN_6]
|
|
65
|
+
SEN_15 = [SR570GainTable.SEN_3, SR570FineGainTable.SEN_5]
|
|
66
|
+
SEN_16 = [SR570GainTable.SEN_3, SR570FineGainTable.SEN_4]
|
|
67
|
+
SEN_17 = [SR570GainTable.SEN_3, SR570FineGainTable.SEN_3]
|
|
68
|
+
SEN_18 = [SR570GainTable.SEN_3, SR570FineGainTable.SEN_2]
|
|
69
|
+
SEN_19 = [SR570GainTable.SEN_3, SR570FineGainTable.SEN_1]
|
|
70
|
+
SEN_20 = [SR570GainTable.SEN_4, SR570FineGainTable.SEN_9]
|
|
71
|
+
SEN_21 = [SR570GainTable.SEN_4, SR570FineGainTable.SEN_8]
|
|
72
|
+
SEN_22 = [SR570GainTable.SEN_4, SR570FineGainTable.SEN_7]
|
|
73
|
+
SEN_23 = [SR570GainTable.SEN_4, SR570FineGainTable.SEN_6]
|
|
74
|
+
SEN_24 = [SR570GainTable.SEN_4, SR570FineGainTable.SEN_5]
|
|
75
|
+
SEN_25 = [SR570GainTable.SEN_4, SR570FineGainTable.SEN_4]
|
|
76
|
+
SEN_26 = [SR570GainTable.SEN_4, SR570FineGainTable.SEN_3]
|
|
77
|
+
SEN_27 = [SR570GainTable.SEN_4, SR570FineGainTable.SEN_2]
|
|
78
|
+
SEN_28 = [SR570GainTable.SEN_4, SR570FineGainTable.SEN_1]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class SR570GainToCurrentTable(float, Enum):
|
|
82
|
+
"""Conversion table for gain(sen) to current"""
|
|
83
|
+
|
|
84
|
+
SEN_1 = 1e3
|
|
85
|
+
SEN_2 = 2e3
|
|
86
|
+
SEN_3 = 5e3
|
|
87
|
+
SEN_4 = 1e4
|
|
88
|
+
SEN_5 = 2e4
|
|
89
|
+
SEN_6 = 5e4
|
|
90
|
+
SEN_7 = 1e5
|
|
91
|
+
SEN_8 = 2e5
|
|
92
|
+
SEN_9 = 5e5
|
|
93
|
+
SEN_10 = 1e6
|
|
94
|
+
SEN_11 = 2e6
|
|
95
|
+
SEN_12 = 5e6
|
|
96
|
+
SEN_13 = 1e7
|
|
97
|
+
SEN_14 = 2e7
|
|
98
|
+
SEN_15 = 5e7
|
|
99
|
+
SEN_16 = 1e8
|
|
100
|
+
SEN_17 = 2e8
|
|
101
|
+
SEN_18 = 5e8
|
|
102
|
+
SEN_19 = 1e9
|
|
103
|
+
SEN_20 = 2e9
|
|
104
|
+
SEN_21 = 5e9
|
|
105
|
+
SEN_22 = 1e10
|
|
106
|
+
SEN_23 = 2e10
|
|
107
|
+
SEN_24 = 5e10
|
|
108
|
+
SEN_25 = 1e11
|
|
109
|
+
SEN_26 = 2e11
|
|
110
|
+
SEN_27 = 5e11
|
|
111
|
+
SEN_28 = 1e12
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class SR570(CurrentAmp):
|
|
115
|
+
"""
|
|
116
|
+
SR570 current amplifier device. This is similar to Femto with the only different
|
|
117
|
+
is SR570 has two gain setting fine and coarse, therefore it requires extra
|
|
118
|
+
gain tables.
|
|
119
|
+
Attributes:
|
|
120
|
+
fine_gain (SignalRW): This is the epic signal that control SR570 fine gain.
|
|
121
|
+
coarse_gain (SignalRW): This is the epic signal that control SR570 coarse gain.
|
|
122
|
+
fine_gain_table (strictEnum): The table that fine_gain use to set gain.
|
|
123
|
+
coarse_gain_table (strictEnum): The table that coarse_gain use to set gain.
|
|
124
|
+
timeout (float): Maximum waiting time in second for setting gain.
|
|
125
|
+
raise_timetable (Enum): Table contain the amount of time to wait after
|
|
126
|
+
setting gain.
|
|
127
|
+
combined_table (Enum): Table that combine fine and coarse table into one.
|
|
128
|
+
gain (SignalRW([str]): Soft signal to store the member name of the current gain
|
|
129
|
+
setting in gain_conversion_table.
|
|
130
|
+
upperlimit (float): upperlimit of the current amplifier
|
|
131
|
+
lowerlimit (float): lowerlimit of the current amplifier
|
|
132
|
+
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
def __init__(
|
|
136
|
+
self,
|
|
137
|
+
prefix: str,
|
|
138
|
+
suffix: str,
|
|
139
|
+
fine_gain_table: type[StrictEnum],
|
|
140
|
+
coarse_gain_table: type[StrictEnum],
|
|
141
|
+
combined_table: type[Enum],
|
|
142
|
+
gain_to_current_table: type[Enum],
|
|
143
|
+
raise_timetable: type[Enum],
|
|
144
|
+
upperlimit: float = 4.8,
|
|
145
|
+
lowerlimit: float = 0.4,
|
|
146
|
+
timeout: float = 1,
|
|
147
|
+
name: str = "",
|
|
148
|
+
) -> None:
|
|
149
|
+
super().__init__(name=name, gain_conversion_table=gain_to_current_table)
|
|
150
|
+
|
|
151
|
+
with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
|
|
152
|
+
self.fine_gain = epics_signal_rw(fine_gain_table, prefix + suffix + "1")
|
|
153
|
+
self.coarse_gain = epics_signal_rw(coarse_gain_table, prefix + suffix + "2")
|
|
154
|
+
|
|
155
|
+
self.fine_gain_table = fine_gain_table
|
|
156
|
+
self.coarse_gain_table = coarse_gain_table
|
|
157
|
+
self.timeout = timeout
|
|
158
|
+
self.raise_timetable = raise_timetable
|
|
159
|
+
self.combined_table = combined_table
|
|
160
|
+
self.upperlimit = upperlimit
|
|
161
|
+
self.lowerlimit = lowerlimit
|
|
162
|
+
|
|
163
|
+
@AsyncStatus.wrap
|
|
164
|
+
async def set(self, value) -> None:
|
|
165
|
+
if value not in [item.value for item in self.gain_conversion_table]:
|
|
166
|
+
raise ValueError(
|
|
167
|
+
f"Gain value {value} is not within {self.name} range."
|
|
168
|
+
+ "\n Available gain:"
|
|
169
|
+
+ f" {[f'{c.value:.0e}' for c in self.gain_conversion_table]}"
|
|
170
|
+
)
|
|
171
|
+
SEN_setting = self.gain_conversion_table(value).name
|
|
172
|
+
LOGGER.info(f"{self.name} gain change to {value}")
|
|
173
|
+
|
|
174
|
+
coarse_gain, fine_gain = self.combined_table[SEN_setting].value
|
|
175
|
+
await asyncio.gather(
|
|
176
|
+
self.fine_gain.set(value=fine_gain, timeout=self.timeout),
|
|
177
|
+
self.coarse_gain.set(value=coarse_gain, timeout=self.timeout),
|
|
178
|
+
)
|
|
179
|
+
await asyncio.sleep(self.raise_timetable[coarse_gain.name].value)
|
|
180
|
+
|
|
181
|
+
@AsyncStatus.wrap
|
|
182
|
+
async def increase_gain(self, value=3) -> None:
|
|
183
|
+
current_gain = int((await self.get_gain()).name.split("_")[-1])
|
|
184
|
+
current_gain += value
|
|
185
|
+
if current_gain > len(self.combined_table):
|
|
186
|
+
await self.set(
|
|
187
|
+
self.gain_conversion_table[f"SEN_{len(self.combined_table)}"]
|
|
188
|
+
)
|
|
189
|
+
raise ValueError("Gain at max value")
|
|
190
|
+
await self.set(self.gain_conversion_table[f"SEN_{current_gain}"])
|
|
191
|
+
|
|
192
|
+
@AsyncStatus.wrap
|
|
193
|
+
async def decrease_gain(self, value=3) -> None:
|
|
194
|
+
current_gain = int((await self.get_gain()).name.split("_")[-1])
|
|
195
|
+
current_gain -= value
|
|
196
|
+
if current_gain < 1:
|
|
197
|
+
await self.set(self.gain_conversion_table["SEN_1"])
|
|
198
|
+
raise ValueError("Gain at min value")
|
|
199
|
+
await self.set(self.gain_conversion_table[f"SEN_{current_gain}"])
|
|
200
|
+
|
|
201
|
+
@AsyncStatus.wrap
|
|
202
|
+
async def get_gain(self) -> Enum:
|
|
203
|
+
result = await asyncio.gather(
|
|
204
|
+
self.coarse_gain.get_value(), self.fine_gain.get_value()
|
|
205
|
+
)
|
|
206
|
+
return self.gain_conversion_table[self.combined_table(result).name]
|
|
207
|
+
|
|
208
|
+
@AsyncStatus.wrap
|
|
209
|
+
async def get_upperlimit(self) -> float:
|
|
210
|
+
return self.upperlimit
|
|
211
|
+
|
|
212
|
+
@AsyncStatus.wrap
|
|
213
|
+
async def get_lowerlimit(self) -> float:
|
|
214
|
+
return self.lowerlimit
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from ophyd_async.core import (
|
|
2
|
+
AsyncStatus,
|
|
3
|
+
StandardReadableFormat,
|
|
4
|
+
StrictEnum,
|
|
5
|
+
set_and_wait_for_other_value,
|
|
6
|
+
)
|
|
7
|
+
from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
|
|
8
|
+
|
|
9
|
+
from dodal.devices.current_amplifiers import (
|
|
10
|
+
CurrentAmpCounter,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CountMode(StrictEnum):
|
|
15
|
+
AUTO = "AutoCount"
|
|
16
|
+
ONE_SHOT = "OneShot"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CountState(StrictEnum):
|
|
20
|
+
DONE = "Done"
|
|
21
|
+
COUNT = "Count" # type: ignore
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
COUNT_PER_VOLTAGE = 100000
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class StruckScaler(CurrentAmpCounter):
|
|
28
|
+
"""
|
|
29
|
+
StruckScaler is a counting card that record the output signal from a wide
|
|
30
|
+
range of detectors. This class contains the basic control to run the struckscaler
|
|
31
|
+
card together with a current amplifier. It has functions that provide conversion
|
|
32
|
+
between count and voltage.
|
|
33
|
+
Attributes:
|
|
34
|
+
readout(SignalR): Scaler card output.
|
|
35
|
+
count_mode (SignalR[CountMode]): Counting card setting.
|
|
36
|
+
count_time (SignalRW([float]): Count time.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self, prefix: str, suffix: str, count_per_volt=COUNT_PER_VOLTAGE, name: str = ""
|
|
41
|
+
):
|
|
42
|
+
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
|
|
43
|
+
self.readout = epics_signal_r(float, prefix + suffix)
|
|
44
|
+
with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
|
|
45
|
+
self.count_mode = epics_signal_rw(CountMode, prefix + ":AutoCount")
|
|
46
|
+
self.count_time = epics_signal_rw(float, prefix + ".TP")
|
|
47
|
+
self.trigger_start = epics_signal_rw(CountState, prefix + ".CNT")
|
|
48
|
+
super().__init__(count_per_volt=count_per_volt, name=name)
|
|
49
|
+
|
|
50
|
+
@AsyncStatus.wrap
|
|
51
|
+
async def stage(self) -> None:
|
|
52
|
+
await self.count_mode.set(CountMode.ONE_SHOT)
|
|
53
|
+
|
|
54
|
+
@AsyncStatus.wrap
|
|
55
|
+
async def unstage(self) -> None:
|
|
56
|
+
await self.count_mode.set(CountMode.AUTO)
|
|
57
|
+
|
|
58
|
+
@AsyncStatus.wrap
|
|
59
|
+
async def trigger(self) -> None:
|
|
60
|
+
await set_and_wait_for_other_value(
|
|
61
|
+
set_signal=self.trigger_start,
|
|
62
|
+
set_value=CountState.COUNT,
|
|
63
|
+
match_signal=self.trigger_start,
|
|
64
|
+
match_value=CountState.COUNT,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
@AsyncStatus.wrap
|
|
68
|
+
async def prepare(self, value) -> None:
|
|
69
|
+
await self.count_time.set(value)
|
|
70
|
+
|
|
71
|
+
async def get_count(self) -> float:
|
|
72
|
+
await self.trigger()
|
|
73
|
+
return await self.readout.get_value()
|
|
74
|
+
|
|
75
|
+
async def get_count_per_sec(self) -> float:
|
|
76
|
+
return await self.get_count() / await self.count_time.get_value()
|
|
77
|
+
|
|
78
|
+
async def get_voltage_per_sec(self) -> float:
|
|
79
|
+
return await self.get_count_per_sec() / self.count_per_volt
|
|
@@ -26,6 +26,21 @@ class DetectorSizeConstants:
|
|
|
26
26
|
ALL_DETECTORS[self.det_type_string] = self
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
PILATUS_TYPE_6M = "PILATUS_6M"
|
|
30
|
+
PILATUS_6M_DIMENSION_X = 423.636
|
|
31
|
+
PILATUS_6M_DIMENSION_Y = 434.644
|
|
32
|
+
PILATUS_6M_DIMENSION = DetectorSize(PILATUS_6M_DIMENSION_X, PILATUS_6M_DIMENSION_Y)
|
|
33
|
+
PIXELS_X_PILATUS_6M = 2463
|
|
34
|
+
PIXELS_Y_PILATUS_6M = 2527
|
|
35
|
+
PIXELS_PILATUS_6M = DetectorSize(PIXELS_X_PILATUS_6M, PIXELS_Y_PILATUS_6M)
|
|
36
|
+
PILATUS_6M_SIZE = DetectorSizeConstants(
|
|
37
|
+
PILATUS_TYPE_6M,
|
|
38
|
+
PILATUS_6M_DIMENSION,
|
|
39
|
+
PIXELS_PILATUS_6M,
|
|
40
|
+
PILATUS_6M_DIMENSION,
|
|
41
|
+
PIXELS_PILATUS_6M,
|
|
42
|
+
)
|
|
43
|
+
|
|
29
44
|
EIGER_TYPE_EIGER2_X_4M = "EIGER2_X_4M"
|
|
30
45
|
EIGER2_X_4M_DIMENSION_X = 155.1
|
|
31
46
|
EIGER2_X_4M_DIMENSION_Y = 162.15
|
dodal/devices/eiger_odin.py
CHANGED
|
@@ -91,10 +91,10 @@ class OdinNodesStatus(Device):
|
|
|
91
91
|
def wait_for_no_errors(self, timeout) -> dict[SubscriptionStatus, str]:
|
|
92
92
|
errors = {}
|
|
93
93
|
for node_number, node_pv in enumerate(self.nodes):
|
|
94
|
-
errors[
|
|
95
|
-
|
|
96
|
-
] = f"Filewriter {node_number} is in an error state with error message\
|
|
94
|
+
errors[await_value(node_pv.error_status, False, timeout)] = (
|
|
95
|
+
f"Filewriter {node_number} is in an error state with error message\
|
|
97
96
|
- {node_pv.error_message.get()}"
|
|
97
|
+
)
|
|
98
98
|
|
|
99
99
|
return errors
|
|
100
100
|
|
dodal/devices/fast_grid_scan.py
CHANGED
|
@@ -97,9 +97,14 @@ class GridScanParamsCommon(AbstractExperimentWithBeamParams):
|
|
|
97
97
|
"""Converts a grid position, given as steps in the x, y, z grid,
|
|
98
98
|
to a real motor position.
|
|
99
99
|
|
|
100
|
-
:
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
Args:
|
|
101
|
+
grid_position: The x, y, z position in grid steps. The origin is at the
|
|
102
|
+
centre of the first grid box
|
|
103
|
+
Returns:
|
|
104
|
+
The motor position this corresponds to.
|
|
105
|
+
Raises:
|
|
106
|
+
IndexError if the desired position is outside the grid.
|
|
107
|
+
"""
|
|
103
108
|
for position, axis in zip(
|
|
104
109
|
grid_position, [self.x_axis, self.y_axis, self.z_axis], strict=False
|
|
105
110
|
):
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from asyncio import gather
|
|
2
|
+
from math import isclose
|
|
3
|
+
|
|
4
|
+
from ophyd_async.core import StandardReadable, StrictEnum
|
|
5
|
+
from ophyd_async.epics.motor import Motor
|
|
6
|
+
|
|
7
|
+
from dodal.common.beamlines.beamline_parameters import GDABeamlineParameters
|
|
8
|
+
from dodal.common.signal_utils import create_hardware_backed_soft_signal
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BeamstopPositions(StrictEnum):
|
|
12
|
+
"""
|
|
13
|
+
Beamstop positions.
|
|
14
|
+
GDA supports Standard/High/Low resolution positions, as well as parked and
|
|
15
|
+
robot load however all 3 resolution positions are the same. We also
|
|
16
|
+
do not use the robot load position in Hyperion.
|
|
17
|
+
|
|
18
|
+
Until we support moving the beamstop it is only necessary to check whether the
|
|
19
|
+
beamstop is in beam or not.
|
|
20
|
+
|
|
21
|
+
See Also:
|
|
22
|
+
https://github.com/DiamondLightSource/mx-bluesky/issues/484
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
DATA_COLLECTION: The beamstop is in beam ready for data collection
|
|
26
|
+
UNKNOWN: The beamstop is in some other position, check the device motor
|
|
27
|
+
positions to determine it.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
DATA_COLLECTION = "Data Collection"
|
|
31
|
+
UNKNOWN = "Unknown"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Beamstop(StandardReadable):
|
|
35
|
+
"""
|
|
36
|
+
Beamstop for I03.
|
|
37
|
+
|
|
38
|
+
Attributes:
|
|
39
|
+
x: beamstop x position in mm
|
|
40
|
+
y: beamstop y position in mm
|
|
41
|
+
z: beamstop z position in mm
|
|
42
|
+
selected_pos: Get the current position of the beamstop as an enum. Currently this
|
|
43
|
+
is read-only.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
prefix: str,
|
|
49
|
+
beamline_parameters: GDABeamlineParameters,
|
|
50
|
+
name: str = "",
|
|
51
|
+
):
|
|
52
|
+
with self.add_children_as_readables():
|
|
53
|
+
self.x_mm = Motor(prefix + "X")
|
|
54
|
+
self.y_mm = Motor(prefix + "Y")
|
|
55
|
+
self.z_mm = Motor(prefix + "Z")
|
|
56
|
+
self.selected_pos = create_hardware_backed_soft_signal(
|
|
57
|
+
BeamstopPositions, self._get_selected_position
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
self._in_beam_xyz_mm = [
|
|
61
|
+
float(beamline_parameters[f"in_beam_{axis}_STANDARD"])
|
|
62
|
+
for axis in ("x", "y", "z")
|
|
63
|
+
]
|
|
64
|
+
self._xyz_tolerance_mm = [
|
|
65
|
+
float(beamline_parameters[f"bs_{axis}_tolerance"])
|
|
66
|
+
for axis in ("x", "y", "z")
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
super().__init__(name)
|
|
70
|
+
|
|
71
|
+
async def _get_selected_position(self) -> BeamstopPositions:
|
|
72
|
+
current_pos = await gather(
|
|
73
|
+
self.x_mm.user_readback.get_value(),
|
|
74
|
+
self.y_mm.user_readback.get_value(),
|
|
75
|
+
self.z_mm.user_readback.get_value(),
|
|
76
|
+
)
|
|
77
|
+
if all(
|
|
78
|
+
isclose(axis_pos, axis_in_beam, abs_tol=axis_tolerance)
|
|
79
|
+
for axis_pos, axis_in_beam, axis_tolerance in zip(
|
|
80
|
+
current_pos, self._in_beam_xyz_mm, self._xyz_tolerance_mm, strict=False
|
|
81
|
+
)
|
|
82
|
+
):
|
|
83
|
+
return BeamstopPositions.DATA_COLLECTION
|
|
84
|
+
else:
|
|
85
|
+
return BeamstopPositions.UNKNOWN
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import math
|
|
2
|
-
from time import sleep, time
|
|
3
3
|
|
|
4
|
-
from
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
from ophyd_async.core import (
|
|
5
|
+
AsyncStatus,
|
|
6
|
+
StandardReadable,
|
|
7
|
+
observe_value,
|
|
8
|
+
wait_for_value,
|
|
9
|
+
)
|
|
10
|
+
from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
|
|
7
11
|
|
|
8
12
|
from dodal.log import LOGGER
|
|
9
13
|
|
|
10
14
|
|
|
11
|
-
class Transfocator(
|
|
15
|
+
class Transfocator(StandardReadable):
|
|
12
16
|
"""The transfocator is a device that puts a number of lenses in the beam to change
|
|
13
17
|
its shape.
|
|
14
18
|
|
|
@@ -18,34 +22,50 @@ class Transfocator(Device):
|
|
|
18
22
|
my_transfocator.set(vert_beamsize_microns)
|
|
19
23
|
"""
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
26
|
+
with self.add_children_as_readables():
|
|
27
|
+
self.beamsize_set_microns = epics_signal_rw(float, prefix + "VERT_REQ")
|
|
28
|
+
self.predicted_vertical_num_lenses = epics_signal_rw(
|
|
29
|
+
float, prefix + "LENS_PRED"
|
|
30
|
+
)
|
|
31
|
+
self.number_filters_sp = epics_signal_rw(int, prefix + "NUM_FILTERS")
|
|
32
|
+
self.start = epics_signal_rw(int, prefix + "START.PROC")
|
|
33
|
+
self.start_rbv = epics_signal_r(int, prefix + "START_RBV")
|
|
34
|
+
self.vertical_lens_rbv = epics_signal_r(float, prefix + "VER")
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
_POLLING_WAIT = 0.01
|
|
36
|
+
self.TIMEOUT = 120
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
# For some reason couldn't get monitors working on START_RBV
|
|
36
|
-
# (See https://github.com/DiamondLightSource/dodal/issues/152)
|
|
37
|
-
start_time = time()
|
|
38
|
-
while time() < start_time + self.TIMEOUT:
|
|
39
|
-
RBV_value = self.start_rbv.get()
|
|
40
|
-
if RBV_value == for_value:
|
|
41
|
-
return
|
|
42
|
-
sleep(self._POLLING_WAIT)
|
|
38
|
+
super().__init__(name=name)
|
|
43
39
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
raise TimeoutError()
|
|
40
|
+
async def _observe_beamsize_microns(self):
|
|
41
|
+
is_set_filters_done = False
|
|
47
42
|
|
|
48
|
-
|
|
43
|
+
async def set_based_on_prediction(value: float):
|
|
44
|
+
if not math.isclose(
|
|
45
|
+
self.latest_pred_vertical_num_lenses, value, abs_tol=1e-8
|
|
46
|
+
):
|
|
47
|
+
# We can only put an integer number of lenses in the beam but the
|
|
48
|
+
# calculation in the IOC returns the theoretical float number of lenses
|
|
49
|
+
nonlocal is_set_filters_done
|
|
50
|
+
value = round(value)
|
|
51
|
+
LOGGER.info(f"Transfocator setting {value} filters")
|
|
52
|
+
await self.number_filters_sp.set(value)
|
|
53
|
+
await self.start.set(1)
|
|
54
|
+
LOGGER.info("Waiting for start_rbv to change to 1")
|
|
55
|
+
await wait_for_value(self.start_rbv, 1, self.TIMEOUT)
|
|
56
|
+
LOGGER.info("Waiting for start_rbv to change to 0")
|
|
57
|
+
await wait_for_value(self.start_rbv, 0, self.TIMEOUT)
|
|
58
|
+
self.latest_pred_vertical_num_lenses = value
|
|
59
|
+
is_set_filters_done = True
|
|
60
|
+
|
|
61
|
+
# The value hasn't changed so assume the device is already set up correctly
|
|
62
|
+
async for value in observe_value(self.predicted_vertical_num_lenses):
|
|
63
|
+
await set_based_on_prediction(value)
|
|
64
|
+
if is_set_filters_done:
|
|
65
|
+
break
|
|
66
|
+
|
|
67
|
+
@AsyncStatus.wrap
|
|
68
|
+
async def set(self, value: float):
|
|
49
69
|
"""To set the beamsize on the transfocator we must:
|
|
50
70
|
1. Set the beamsize in the calculator part of the transfocator
|
|
51
71
|
2. Get the predicted number of lenses needed from this calculator
|
|
@@ -53,30 +73,24 @@ class Transfocator(Device):
|
|
|
53
73
|
4. Start the device moving
|
|
54
74
|
5. Wait for the start_rbv goes high and low again
|
|
55
75
|
"""
|
|
56
|
-
|
|
57
|
-
|
|
76
|
+
self.latest_pred_vertical_num_lenses = (
|
|
77
|
+
await self.predicted_vertical_num_lenses.get_value()
|
|
78
|
+
)
|
|
58
79
|
|
|
59
|
-
|
|
60
|
-
if not math.isclose(old_value, value, abs_tol=1e-8):
|
|
61
|
-
self.predicted_vertical_num_lenses.unsubscribe(subscriber)
|
|
80
|
+
LOGGER.info(f"Transfocator setting {value} beamsize")
|
|
62
81
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
self.
|
|
68
|
-
self.start.set(1).wait()
|
|
69
|
-
self.polling_wait_on_start_rbv(1)
|
|
70
|
-
self.polling_wait_on_start_rbv(0)
|
|
71
|
-
# The value hasn't changed so assume the device is already set up correctly
|
|
72
|
-
status.set_finished()
|
|
73
|
-
|
|
74
|
-
LOGGER.info(f"Transfocator setting {beamsize_microns} beamsize")
|
|
75
|
-
if self.beamsize_set_microns.get() != beamsize_microns:
|
|
76
|
-
subscriber = self.predicted_vertical_num_lenses.subscribe(
|
|
77
|
-
set_based_on_predicition, run=False
|
|
82
|
+
if await self.beamsize_set_microns.get_value() != value:
|
|
83
|
+
# Logic in the IOC calculates predicted_vertical_num_lenses when beam_set_microns changes
|
|
84
|
+
await asyncio.gather(
|
|
85
|
+
self.beamsize_set_microns.set(value),
|
|
86
|
+
self._observe_beamsize_microns(),
|
|
78
87
|
)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
88
|
+
|
|
89
|
+
number_filters_rbv, vertical_lens_size_rbv = await asyncio.gather(
|
|
90
|
+
self.number_filters_sp.get_value(),
|
|
91
|
+
self.vertical_lens_rbv.get_value(),
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
LOGGER.info(
|
|
95
|
+
f"Transfocator set complete. Number of filters is: {number_filters_rbv} and Vertical beam size is: {vertical_lens_size_rbv}"
|
|
96
|
+
)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from ophyd_async.core import Device
|
|
2
|
+
|
|
3
|
+
from dodal.devices.current_amplifiers import (
|
|
4
|
+
SR570,
|
|
5
|
+
Femto3xxGainTable,
|
|
6
|
+
Femto3xxGainToCurrentTable,
|
|
7
|
+
Femto3xxRaiseTime,
|
|
8
|
+
FemtoDDPCA,
|
|
9
|
+
SR570FineGainTable,
|
|
10
|
+
SR570FullGainTable,
|
|
11
|
+
SR570GainTable,
|
|
12
|
+
SR570GainToCurrentTable,
|
|
13
|
+
SR570RaiseTimeTable,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class RasorFemto(Device):
|
|
18
|
+
def __init__(self, prefix: str, suffix: str = "GAIN", name: str = "") -> None:
|
|
19
|
+
self.ca1 = FemtoDDPCA(
|
|
20
|
+
prefix + "-01:",
|
|
21
|
+
suffix=suffix,
|
|
22
|
+
gain_table=Femto3xxGainTable,
|
|
23
|
+
gain_to_current_table=Femto3xxGainToCurrentTable,
|
|
24
|
+
raise_timetable=Femto3xxRaiseTime,
|
|
25
|
+
)
|
|
26
|
+
self.ca2 = FemtoDDPCA(
|
|
27
|
+
prefix + "-02:",
|
|
28
|
+
suffix=suffix,
|
|
29
|
+
gain_table=Femto3xxGainTable,
|
|
30
|
+
gain_to_current_table=Femto3xxGainToCurrentTable,
|
|
31
|
+
raise_timetable=Femto3xxRaiseTime,
|
|
32
|
+
)
|
|
33
|
+
self.ca3 = FemtoDDPCA(
|
|
34
|
+
prefix + "-03:",
|
|
35
|
+
suffix=suffix,
|
|
36
|
+
gain_table=Femto3xxGainTable,
|
|
37
|
+
gain_to_current_table=Femto3xxGainToCurrentTable,
|
|
38
|
+
raise_timetable=Femto3xxRaiseTime,
|
|
39
|
+
)
|
|
40
|
+
super().__init__(name)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class RasorSR570(Device):
|
|
44
|
+
def __init__(self, prefix: str, suffix: str = "SENS:SEL", name: str = "") -> None:
|
|
45
|
+
self.ca1 = SR570(
|
|
46
|
+
prefix + "-04:",
|
|
47
|
+
suffix=suffix,
|
|
48
|
+
fine_gain_table=SR570FineGainTable,
|
|
49
|
+
coarse_gain_table=SR570GainTable,
|
|
50
|
+
combined_table=SR570FullGainTable,
|
|
51
|
+
gain_to_current_table=SR570GainToCurrentTable,
|
|
52
|
+
raise_timetable=SR570RaiseTimeTable,
|
|
53
|
+
)
|
|
54
|
+
self.ca2 = SR570(
|
|
55
|
+
prefix + "-05:",
|
|
56
|
+
suffix=suffix,
|
|
57
|
+
fine_gain_table=SR570FineGainTable,
|
|
58
|
+
coarse_gain_table=SR570GainTable,
|
|
59
|
+
combined_table=SR570FullGainTable,
|
|
60
|
+
gain_to_current_table=SR570GainToCurrentTable,
|
|
61
|
+
raise_timetable=SR570RaiseTimeTable,
|
|
62
|
+
)
|
|
63
|
+
self.ca3 = SR570(
|
|
64
|
+
prefix + "-06:",
|
|
65
|
+
suffix=suffix,
|
|
66
|
+
fine_gain_table=SR570FineGainTable,
|
|
67
|
+
coarse_gain_table=SR570GainTable,
|
|
68
|
+
combined_table=SR570FullGainTable,
|
|
69
|
+
gain_to_current_table=SR570GainToCurrentTable,
|
|
70
|
+
raise_timetable=SR570RaiseTimeTable,
|
|
71
|
+
)
|
|
72
|
+
super().__init__(name)
|