dls-dodal 1.47.0__py3-none-any.whl → 1.49.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.
Files changed (61) hide show
  1. {dls_dodal-1.47.0.dist-info → dls_dodal-1.49.0.dist-info}/METADATA +3 -2
  2. {dls_dodal-1.47.0.dist-info → dls_dodal-1.49.0.dist-info}/RECORD +59 -49
  3. {dls_dodal-1.47.0.dist-info → dls_dodal-1.49.0.dist-info}/WHEEL +1 -1
  4. dodal/_version.py +2 -2
  5. dodal/beamlines/aithre.py +21 -0
  6. dodal/beamlines/b01_1.py +1 -1
  7. dodal/beamlines/b16.py +65 -0
  8. dodal/beamlines/b18.py +38 -0
  9. dodal/beamlines/i03.py +21 -6
  10. dodal/beamlines/i04.py +17 -10
  11. dodal/beamlines/i10.py +41 -233
  12. dodal/beamlines/i18.py +1 -1
  13. dodal/beamlines/i19_1.py +9 -6
  14. dodal/beamlines/i24.py +5 -5
  15. dodal/beamlines/k11.py +35 -0
  16. dodal/common/beamlines/beamline_parameters.py +2 -28
  17. dodal/common/beamlines/device_helpers.py +1 -0
  18. dodal/devices/aithre_lasershaping/goniometer.py +36 -2
  19. dodal/devices/aithre_lasershaping/laser_robot.py +27 -0
  20. dodal/devices/apple2_undulator.py +257 -136
  21. dodal/devices/b16/__init__.py +0 -0
  22. dodal/devices/b16/detector.py +34 -0
  23. dodal/devices/bimorph_mirror.py +29 -36
  24. dodal/devices/electron_analyser/__init__.py +21 -1
  25. dodal/devices/electron_analyser/abstract/__init__.py +0 -6
  26. dodal/devices/electron_analyser/abstract/base_detector.py +16 -128
  27. dodal/devices/electron_analyser/abstract/base_driver_io.py +122 -8
  28. dodal/devices/electron_analyser/abstract/base_region.py +7 -3
  29. dodal/devices/electron_analyser/detector.py +141 -0
  30. dodal/devices/electron_analyser/enums.py +6 -0
  31. dodal/devices/electron_analyser/specs/__init__.py +3 -2
  32. dodal/devices/electron_analyser/specs/detector.py +6 -22
  33. dodal/devices/electron_analyser/specs/driver_io.py +27 -3
  34. dodal/devices/electron_analyser/specs/enums.py +8 -0
  35. dodal/devices/electron_analyser/specs/region.py +3 -2
  36. dodal/devices/electron_analyser/types.py +30 -4
  37. dodal/devices/electron_analyser/util.py +1 -1
  38. dodal/devices/electron_analyser/vgscienta/__init__.py +3 -2
  39. dodal/devices/electron_analyser/vgscienta/detector.py +9 -23
  40. dodal/devices/electron_analyser/vgscienta/driver_io.py +33 -4
  41. dodal/devices/electron_analyser/vgscienta/enums.py +19 -0
  42. dodal/devices/electron_analyser/vgscienta/region.py +7 -23
  43. dodal/devices/fast_grid_scan.py +1 -1
  44. dodal/devices/i04/murko_results.py +93 -96
  45. dodal/devices/i10/__init__.py +0 -0
  46. dodal/devices/i10/i10_apple2.py +181 -126
  47. dodal/devices/i18/diode.py +37 -4
  48. dodal/devices/i22/nxsas.py +1 -1
  49. dodal/devices/mx_phase1/beamstop.py +23 -6
  50. dodal/devices/oav/oav_detector.py +101 -25
  51. dodal/devices/oav/oav_parameters.py +46 -16
  52. dodal/devices/oav/oav_to_redis_forwarder.py +2 -2
  53. dodal/devices/robot.py +20 -1
  54. dodal/devices/smargon.py +43 -4
  55. dodal/devices/zebra/zebra.py +8 -0
  56. dodal/plans/configure_arm_trigger_and_disarm_detector.py +167 -0
  57. dodal/plan_stubs/electron_analyser/__init__.py +0 -3
  58. dodal/plan_stubs/electron_analyser/configure_driver.py +0 -92
  59. {dls_dodal-1.47.0.dist-info → dls_dodal-1.49.0.dist-info}/entry_points.txt +0 -0
  60. {dls_dodal-1.47.0.dist-info → dls_dodal-1.49.0.dist-info}/licenses/LICENSE +0 -0
  61. {dls_dodal-1.47.0.dist-info → dls_dodal-1.49.0.dist-info}/top_level.txt +0 -0
dodal/beamlines/i10.py CHANGED
@@ -1,19 +1,17 @@
1
- from pathlib import Path
1
+ """
2
+ note:
3
+ I10 has two insertion devices one up(idu) and one down stream(idd).
4
+ It is worth noting that the downstream device is slightly longer,
5
+ so it can reach Mn edge for linear arbitrary.
6
+ idd == id1, idu == id2.
7
+ """
2
8
 
3
- from dodal.common.beamlines.beamline_utils import device_factory, device_instantiation
9
+ from dodal.common.beamlines.beamline_utils import device_factory
4
10
  from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline
5
- from dodal.devices.apple2_undulator import (
6
- UndulatorGap,
7
- UndulatorJawPhase,
8
- UndulatorPhaseAxes,
9
- )
10
11
  from dodal.devices.current_amplifiers import CurrentAmpDet
11
12
  from dodal.devices.i10.diagnostics import I10Diagnostic, I10Diagnostic5ADet
12
13
  from dodal.devices.i10.i10_apple2 import (
13
- I10Apple2,
14
- I10Apple2PGM,
15
- I10Apple2Pol,
16
- LinearArbitraryAngle,
14
+ I10Id,
17
15
  )
18
16
  from dodal.devices.i10.i10_setting_data import I10Grating
19
17
  from dodal.devices.i10.mirrors import PiezoMirror
@@ -37,238 +35,48 @@ set_utils_beamline(BL)
37
35
  PREFIX = BeamlinePrefix(BL)
38
36
 
39
37
 
40
- LOOK_UPTABLE_DIR = "/dls_sw/i10/software/gda/workspace_git/gda-diamond.git/configurations/i10-shared/lookupTables/"
41
- """
42
- I10 has two insertion devices one up(idu) and one down stream(idd).
43
- It is worth noting that the down stream device is slightly longer,
44
- so it can reach Mn edge for linear arbitrary.
45
- idd == id1
46
- and
47
- idu == id2.
48
- """
49
-
50
-
51
- def idd_gap(
52
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
53
- ) -> UndulatorGap:
54
- return device_instantiation(
55
- device_factory=UndulatorGap,
56
- name="idd_gap",
57
- prefix=f"{PREFIX.insertion_prefix}-MO-SERVC-01:",
58
- wait=wait_for_connection,
59
- fake=fake_with_ophyd_sim,
60
- bl_prefix=False,
61
- )
62
-
63
-
64
- def idd_phase_axes(
65
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
66
- ) -> UndulatorPhaseAxes:
67
- return device_instantiation(
68
- device_factory=UndulatorPhaseAxes,
69
- name="idd_phase_axes",
70
- prefix=f"{PREFIX.insertion_prefix}-MO-SERVC-01:",
71
- top_outer="RPQ1",
72
- top_inner="RPQ2",
73
- btm_inner="RPQ3",
74
- btm_outer="RPQ4",
75
- wait=wait_for_connection,
76
- fake=fake_with_ophyd_sim,
77
- bl_prefix=False,
78
- )
79
-
80
-
81
- def idd_jaw(
82
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
83
- ) -> UndulatorJawPhase:
84
- return device_instantiation(
85
- device_factory=UndulatorJawPhase,
86
- name="idd_jaw",
87
- prefix=f"{PREFIX.insertion_prefix}-MO-SERVC-01:",
88
- move_pv="RPQ1",
89
- wait=wait_for_connection,
90
- fake=fake_with_ophyd_sim,
91
- bl_prefix=False,
92
- )
93
-
94
-
95
- def idu_gap(
96
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
97
- ) -> UndulatorGap:
98
- return device_instantiation(
99
- device_factory=UndulatorGap,
100
- name="idu_gap",
101
- prefix=f"{PREFIX.insertion_prefix}-MO-SERVC-21:",
102
- wait=wait_for_connection,
103
- fake=fake_with_ophyd_sim,
104
- bl_prefix=False,
105
- )
106
-
107
-
108
- def idu_phase_axes(
109
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
110
- ) -> UndulatorPhaseAxes:
111
- return device_instantiation(
112
- device_factory=UndulatorPhaseAxes,
113
- name="idu_phase_axes",
114
- prefix=f"{PREFIX.insertion_prefix}-MO-SERVC-21:",
115
- top_outer="RPQ1",
116
- top_inner="RPQ2",
117
- btm_inner="RPQ3",
118
- btm_outer="RPQ4",
119
- wait=wait_for_connection,
120
- fake=fake_with_ophyd_sim,
121
- bl_prefix=False,
122
- )
123
-
124
-
125
- def idu_jaw(
126
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
127
- ) -> UndulatorJawPhase:
128
- return device_instantiation(
129
- device_factory=UndulatorJawPhase,
130
- name="idu_jaw",
131
- prefix=f"{PREFIX.insertion_prefix}-MO-SERVC-21:",
132
- move_pv="RPQ1",
133
- wait=wait_for_connection,
134
- fake=fake_with_ophyd_sim,
135
- bl_prefix=False,
136
- )
38
+ LOOK_UPTABLE_DIR = "/dls_sw/i10/software/blueapi/scratch/i10-config/lookupTables/"
137
39
 
138
40
 
139
- def pgm(wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False) -> PGM:
140
- return device_instantiation(
141
- device_factory=PGM,
142
- name="pgm",
143
- prefix="-OP-PGM-01:",
41
+ @device_factory()
42
+ def pgm() -> PGM:
43
+ "I10 Plane Grating Monochromator, it can change energy via pgm.energy.set(<energy>)"
44
+ return PGM(
45
+ prefix=f"{PREFIX.beamline_prefix}-OP-PGM-01:",
144
46
  grating=I10Grating,
145
47
  gratingPv="NLINES2",
146
- wait=wait_for_connection,
147
- fake=fake_with_ophyd_sim,
148
- )
149
-
150
-
151
- def idu_gap_phase(
152
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
153
- ) -> I10Apple2:
154
- return device_instantiation(
155
- device_factory=I10Apple2,
156
- id_gap=idu_gap(wait_for_connection, fake_with_ophyd_sim),
157
- id_phase=idu_phase_axes(wait_for_connection, fake_with_ophyd_sim),
158
- id_jaw_phase=idu_jaw(wait_for_connection, fake_with_ophyd_sim),
159
- energy_gap_table_path=Path(
160
- LOOK_UPTABLE_DIR + "IDEnergy2GapCalibrations.csv",
161
- ),
162
- energy_phase_table_path=Path(
163
- LOOK_UPTABLE_DIR + "IDEnergy2PhaseCalibrations.csv",
164
- ),
165
- source=("Source", "idu"),
166
- name="idu_gap_phase",
167
- prefix="",
168
- wait=wait_for_connection,
169
- fake=fake_with_ophyd_sim,
170
48
  )
171
49
 
172
50
 
173
- def idd_gap_phase(
174
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
175
- ) -> I10Apple2:
176
- return device_instantiation(
177
- device_factory=I10Apple2,
178
- id_gap=idd_gap(wait_for_connection, fake_with_ophyd_sim),
179
- id_phase=idd_phase_axes(wait_for_connection, fake_with_ophyd_sim),
180
- id_jaw_phase=idd_jaw(wait_for_connection, fake_with_ophyd_sim),
181
- energy_gap_table_path=Path(
182
- LOOK_UPTABLE_DIR + "IDEnergy2GapCalibrations.csv",
183
- ),
184
- energy_phase_table_path=Path(
185
- LOOK_UPTABLE_DIR + "IDEnergy2PhaseCalibrations.csv",
186
- ),
51
+ @device_factory()
52
+ def idd() -> I10Id:
53
+ """i10 downstream insertion device:
54
+ id.energy.set(<energy>) to change beamline energy.
55
+ id.energy.energy_offset.set(<off_set>) to change id energy offset relative to pgm.
56
+ id.pol.set(<polarisation>) to change polarisation.
57
+ id.laa.set(<linear polarisation angle>) to change polarisation angle, must be in LA mode.
58
+ """
59
+ return I10Id(
60
+ prefix=f"{PREFIX.insertion_prefix}-MO-SERVC-01:",
61
+ pgm=pgm(),
62
+ look_up_table_dir=LOOK_UPTABLE_DIR,
187
63
  source=("Source", "idd"),
188
- name="idd_gap_phase",
189
- prefix="",
190
- wait=wait_for_connection,
191
- fake=fake_with_ophyd_sim,
192
64
  )
193
65
 
194
66
 
195
- def idu_pol(
196
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
197
- ) -> I10Apple2Pol:
198
- return device_instantiation(
199
- device_factory=I10Apple2Pol,
200
- prefix="",
201
- id=idu_gap_phase(wait_for_connection, fake_with_ophyd_sim),
202
- name="idu_pol",
203
- wait=wait_for_connection,
204
- fake=fake_with_ophyd_sim,
205
- )
206
-
207
-
208
- def idd_pol(
209
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
210
- ) -> I10Apple2Pol:
211
- return device_instantiation(
212
- device_factory=I10Apple2Pol,
213
- prefix="",
214
- id=idd_gap_phase(wait_for_connection, fake_with_ophyd_sim),
215
- name="idd_pol",
216
- wait=wait_for_connection,
217
- fake=fake_with_ophyd_sim,
218
- )
219
-
220
-
221
- def idu(
222
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
223
- ) -> I10Apple2PGM:
224
- return device_instantiation(
225
- device_factory=I10Apple2PGM,
226
- prefix="",
227
- id=idu_gap_phase(wait_for_connection, fake_with_ophyd_sim),
228
- pgm=pgm(wait_for_connection, fake_with_ophyd_sim),
229
- name="idu",
230
- wait=wait_for_connection,
231
- fake=fake_with_ophyd_sim,
232
- )
233
-
234
-
235
- def idd(
236
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
237
- ) -> I10Apple2PGM:
238
- return device_instantiation(
239
- device_factory=I10Apple2PGM,
240
- prefix="",
241
- id=idd_gap_phase(wait_for_connection, fake_with_ophyd_sim),
242
- pgm=pgm(wait_for_connection, fake_with_ophyd_sim),
243
- name="idd",
244
- wait=wait_for_connection,
245
- fake=fake_with_ophyd_sim,
246
- )
247
-
248
-
249
- def idu_la_angle(
250
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
251
- ) -> LinearArbitraryAngle:
252
- return device_instantiation(
253
- device_factory=LinearArbitraryAngle,
254
- prefix="",
255
- id=idu(wait_for_connection, fake_with_ophyd_sim),
256
- name="idu_la_angle",
257
- wait=wait_for_connection,
258
- fake=fake_with_ophyd_sim,
259
- )
260
-
261
-
262
- def idd_la_angle(
263
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
264
- ) -> LinearArbitraryAngle:
265
- return device_instantiation(
266
- device_factory=LinearArbitraryAngle,
267
- prefix="",
268
- id=idu(wait_for_connection, fake_with_ophyd_sim),
269
- name="idd_la_angle",
270
- wait=wait_for_connection,
271
- fake=fake_with_ophyd_sim,
67
+ @device_factory()
68
+ def idu() -> I10Id:
69
+ """i10 upstream insertion device:
70
+ id.energy.set(<energy>) to change beamline energy.
71
+ id.energy.energy_offset.set(<off_set>) to change id energy offset relative to pgm.
72
+ id.pol.set(<polarisation>) to change polarisation.
73
+ id.laa.set(<linear polarisation angle>) to change polarisation angle, must be in LA mode.
74
+ """
75
+ return I10Id(
76
+ prefix=f"{PREFIX.insertion_prefix}-MO-SERVC-21:",
77
+ pgm=pgm(),
78
+ look_up_table_dir=LOOK_UPTABLE_DIR,
79
+ source=("Source", "idu"),
272
80
  )
273
81
 
274
82
 
@@ -342,7 +150,7 @@ def pa_stage() -> PaStage:
342
150
 
343
151
 
344
152
  @device_factory()
345
- def simple_stage() -> XYZPositioner:
153
+ def sample_stage() -> XYZPositioner:
346
154
  return XYZPositioner(prefix="ME01D-MO-CRYO-01:")
347
155
 
348
156
 
dodal/beamlines/i18.py CHANGED
@@ -113,7 +113,7 @@ def hfm() -> KBMirror:
113
113
 
114
114
 
115
115
  @device_factory()
116
- def d7diode() -> Diode:
116
+ def d7_diode() -> Diode:
117
117
  return Diode(f"{PREFIX.beamline_prefix}-DI-PHDGN-07:")
118
118
 
119
119
 
dodal/beamlines/i19_1.py CHANGED
@@ -6,9 +6,12 @@ from dodal.common.beamlines.beamline_utils import (
6
6
  )
7
7
  from dodal.devices.i19.beamstop import BeamStop
8
8
  from dodal.devices.i19.blueapi_device import HutchState
9
- from dodal.devices.i19.shutter import AccessControlledShutter
10
- from dodal.devices.oav.oav_detector import OAV
11
- from dodal.devices.oav.oav_parameters import OAVConfig
9
+ from dodal.devices.i19.shutter import (
10
+ AccessControlledShutter,
11
+ HutchState,
12
+ )
13
+ from dodal.devices.oav.oav_detector import OAVBeamCentreFile
14
+ from dodal.devices.oav.oav_parameters import OAVConfigBeamCentre
12
15
  from dodal.devices.synchrotron import Synchrotron
13
16
  from dodal.devices.zebra.zebra import Zebra
14
17
  from dodal.devices.zebra.zebra_constants_mapping import (
@@ -48,10 +51,10 @@ def beamstop() -> BeamStop:
48
51
 
49
52
 
50
53
  @device_factory()
51
- def oav() -> OAV:
52
- return OAV(
54
+ def oav() -> OAVBeamCentreFile:
55
+ return OAVBeamCentreFile(
53
56
  prefix=f"{PREFIX.beamline_prefix}-EA-OAV-01:",
54
- config=OAVConfig(ZOOM_PARAMS_FILE, DISPLAY_CONFIG),
57
+ config=OAVConfigBeamCentre(ZOOM_PARAMS_FILE, DISPLAY_CONFIG),
55
58
  )
56
59
 
57
60
 
dodal/beamlines/i24.py CHANGED
@@ -15,8 +15,8 @@ from dodal.devices.i24.i24_detector_motion import DetectorMotion
15
15
  from dodal.devices.i24.pilatus_metadata import PilatusMetadata
16
16
  from dodal.devices.i24.pmac import PMAC
17
17
  from dodal.devices.i24.vgonio import VerticalGoniometer
18
- from dodal.devices.oav.oav_detector import OAV
19
- from dodal.devices.oav.oav_parameters import OAVConfig
18
+ from dodal.devices.oav.oav_detector import OAVBeamCentreFile
19
+ from dodal.devices.oav.oav_parameters import OAVConfigBeamCentre
20
20
  from dodal.devices.zebra.zebra import Zebra
21
21
  from dodal.devices.zebra.zebra_constants_mapping import (
22
22
  ZebraMapping,
@@ -150,11 +150,11 @@ def pmac() -> PMAC:
150
150
 
151
151
 
152
152
  @device_factory(skip=BL == "s24")
153
- def oav() -> OAV:
154
- return OAV(
153
+ def oav() -> OAVBeamCentreFile:
154
+ return OAVBeamCentreFile(
155
155
  prefix=f"{PREFIX.beamline_prefix}-DI-OAV-01:",
156
156
  name="oav",
157
- config=OAVConfig(ZOOM_PARAMS_FILE, DISPLAY_CONFIG),
157
+ config=OAVConfigBeamCentre(ZOOM_PARAMS_FILE, DISPLAY_CONFIG),
158
158
  )
159
159
 
160
160
 
dodal/beamlines/k11.py ADDED
@@ -0,0 +1,35 @@
1
+ from pathlib import Path
2
+
3
+ from ophyd_async.epics.motor import Motor
4
+
5
+ from dodal.common.beamlines.beamline_utils import (
6
+ device_factory,
7
+ set_path_provider,
8
+ )
9
+ from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline
10
+ from dodal.common.visit import RemoteDirectoryServiceClient, StaticVisitPathProvider
11
+ from dodal.log import set_beamline as set_log_beamline
12
+ from dodal.utils import BeamlinePrefix, get_beamline_name
13
+
14
+ beamline = get_beamline_name("k11")
15
+ PREFIX = BeamlinePrefix(beamline)
16
+ set_log_beamline(beamline)
17
+ set_utils_beamline(beamline)
18
+
19
+ set_path_provider(
20
+ StaticVisitPathProvider(
21
+ beamline,
22
+ Path("/dls/k11/data/2025/cm40627-3"),
23
+ client=RemoteDirectoryServiceClient("https://k11-control:8088/api"),
24
+ )
25
+ )
26
+
27
+
28
+ @device_factory()
29
+ def kb_x() -> Motor:
30
+ return Motor(f"{PREFIX.beamline_prefix}-OP-KBM-01:CS:X")
31
+
32
+
33
+ @device_factory()
34
+ def kb_y() -> Motor:
35
+ return Motor(f"{PREFIX.beamline_prefix}-OP-KBM-01:CS:Y")
@@ -1,3 +1,4 @@
1
+ import ast
1
2
  from typing import Any, cast
2
3
 
3
4
  from dodal.log import LOGGER
@@ -57,34 +58,7 @@ class GDABeamlineParameters:
57
58
 
58
59
  @classmethod
59
60
  def parse_value(cls, value: str):
60
- if value[0] == "[":
61
- return cls.parse_list(value[1:].strip())
62
- else:
63
- return cls.parse_list_element(value)
64
-
65
- @classmethod
66
- def parse_list_element(cls, value: str):
67
- if value == "Yes":
68
- return True
69
- elif value == "No":
70
- return False
71
- else:
72
- return float(value)
73
-
74
- @classmethod
75
- def parse_list(cls, value: str):
76
- list_output = []
77
- remaining = value.strip()
78
- i = 0
79
- while (i := remaining.find(",")) != -1:
80
- list_output.append(cls.parse_list_element(remaining[:i]))
81
- remaining = remaining[i + 1 :].lstrip()
82
- if (i := remaining.find("]")) != -1:
83
- list_output.append(cls.parse_list_element(remaining[:i]))
84
- remaining = remaining[i + 1 :].lstrip()
85
- else:
86
- raise ValueError("Missing closing ']' in list expression")
87
- return list_output
61
+ return ast.literal_eval(value.replace("Yes", "True").replace("No", "False"))
88
62
 
89
63
 
90
64
  def get_beamline_parameters(beamline_param_path: str | None = None):
@@ -3,6 +3,7 @@ from dodal.devices.slits import Slits
3
3
  from dodal.utils import skip_device
4
4
 
5
5
  HDF5_SUFFIX = "HDF5:"
6
+ TIFF_SUFFIX = "TIFF:"
6
7
  CAM_SUFFIX = "CAM:"
7
8
  DET_SUFFIX = "DET:"
8
9
 
@@ -1,9 +1,21 @@
1
- from ophyd_async.core import StandardReadable
1
+ import asyncio
2
+ import math
3
+
4
+ from ophyd_async.core import StandardReadable, derived_signal_rw
2
5
  from ophyd_async.epics.motor import Motor
3
6
 
4
7
 
5
8
  class Goniometer(StandardReadable):
6
- """Goniometer and the stages it sits on"""
9
+ """The Aithre lab goniometer and the XYZ stage it sits on.
10
+
11
+ `x`, `y` and `z` control the axes of the positioner at the base, while `sampy` and
12
+ `sampz` control the positioner of the sample. `omega` is the rotation about the
13
+ x-axis (along the length of the sample holder).
14
+
15
+ The `vertical_position` signal refers to the height of the sample from the point of
16
+ view of the OAV and setting this value moves the sample vertically in the OAV plane
17
+ regardless of the current rotation.
18
+ """
7
19
 
8
20
  def __init__(self, prefix: str, name: str = "") -> None:
9
21
  self.x = Motor(prefix + "X")
@@ -12,4 +24,26 @@ class Goniometer(StandardReadable):
12
24
  self.sampy = Motor(prefix + "SAMPY")
13
25
  self.sampz = Motor(prefix + "SAMPZ")
14
26
  self.omega = Motor(prefix + "OMEGA")
27
+ self.vertical_position = derived_signal_rw(
28
+ self._get,
29
+ self._set,
30
+ sampy=self.sampy,
31
+ sampz=self.sampz,
32
+ omega=self.omega,
33
+ )
15
34
  super().__init__(name)
35
+
36
+ def _get(self, sampz: float, sampy: float, omega: float) -> float:
37
+ z_component = sampz * math.cos(math.radians(omega))
38
+ y_component = sampy * math.sin(math.radians(omega))
39
+ return z_component + y_component
40
+
41
+ async def _set(self, value: float) -> None:
42
+ omega = await self.omega.user_readback.get_value()
43
+ z_component = value * math.cos(math.radians(omega))
44
+ y_component = value * math.sin(math.radians(omega))
45
+ await asyncio.gather(
46
+ self.sampy.set(y_component),
47
+ self.sampz.set(z_component),
48
+ self.omega.set(omega),
49
+ )
@@ -0,0 +1,27 @@
1
+ from ophyd_async.core import StrictEnum
2
+ from ophyd_async.epics.core import epics_signal_rw
3
+
4
+ from dodal.devices.robot import BartRobot
5
+
6
+
7
+ class ForceBit(StrictEnum):
8
+ ON = "On"
9
+ NO = "No"
10
+ OFF = "Off"
11
+
12
+
13
+ class LidHeatEnable(StrictEnum):
14
+ ENABLED = "Enabled"
15
+ DISABLED = "Disabled"
16
+
17
+
18
+ class LaserRobot(BartRobot):
19
+ def __init__(self, name: str, prefix: str) -> None:
20
+ self.dewar_lid_heater = epics_signal_rw(
21
+ LidHeatEnable, prefix + "DW_1_ENABLED", prefix + "DW_1_CTRL"
22
+ )
23
+ self.cryojet_retract = epics_signal_rw(ForceBit, prefix + "OP_24_FORCE_OPTION")
24
+ self.set_beamline_safe = epics_signal_rw(
25
+ ForceBit, prefix + "IP_16_FORCE_OPTION"
26
+ )
27
+ super().__init__(name=name, prefix=prefix)