dls-dodal 1.66.0__py3-none-any.whl → 1.68.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 (83) hide show
  1. {dls_dodal-1.66.0.dist-info → dls_dodal-1.68.0.dist-info}/METADATA +2 -2
  2. {dls_dodal-1.66.0.dist-info → dls_dodal-1.68.0.dist-info}/RECORD +75 -65
  3. dodal/_version.py +2 -2
  4. dodal/beamlines/b07.py +1 -1
  5. dodal/beamlines/b07_1.py +1 -1
  6. dodal/beamlines/i03.py +92 -208
  7. dodal/beamlines/i04.py +22 -1
  8. dodal/beamlines/i05.py +1 -1
  9. dodal/beamlines/i06.py +1 -1
  10. dodal/beamlines/i09.py +1 -1
  11. dodal/beamlines/i09_1.py +27 -3
  12. dodal/beamlines/i09_2.py +58 -2
  13. dodal/beamlines/i10_optics.py +44 -25
  14. dodal/beamlines/i16.py +23 -0
  15. dodal/beamlines/i17.py +7 -3
  16. dodal/beamlines/i19_1.py +26 -14
  17. dodal/beamlines/i19_2.py +49 -38
  18. dodal/beamlines/i21.py +61 -2
  19. dodal/beamlines/i22.py +16 -1
  20. dodal/beamlines/p60.py +1 -1
  21. dodal/beamlines/training_rig.py +0 -16
  22. dodal/cli.py +26 -12
  23. dodal/common/coordination.py +3 -2
  24. dodal/device_manager.py +604 -0
  25. dodal/devices/cryostream.py +28 -57
  26. dodal/devices/eiger.py +41 -27
  27. dodal/devices/electron_analyser/__init__.py +0 -33
  28. dodal/devices/electron_analyser/base/__init__.py +58 -0
  29. dodal/devices/electron_analyser/base/base_controller.py +73 -0
  30. dodal/devices/electron_analyser/base/base_detector.py +214 -0
  31. dodal/devices/electron_analyser/{abstract → base}/base_driver_io.py +23 -42
  32. dodal/devices/electron_analyser/{abstract → base}/base_region.py +47 -11
  33. dodal/devices/electron_analyser/{util.py → base/base_util.py} +1 -1
  34. dodal/devices/electron_analyser/{energy_sources.py → base/energy_sources.py} +1 -1
  35. dodal/devices/electron_analyser/specs/__init__.py +4 -4
  36. dodal/devices/electron_analyser/specs/specs_detector.py +46 -0
  37. dodal/devices/electron_analyser/specs/{driver_io.py → specs_driver_io.py} +23 -26
  38. dodal/devices/electron_analyser/specs/{region.py → specs_region.py} +4 -3
  39. dodal/devices/electron_analyser/vgscienta/__init__.py +4 -4
  40. dodal/devices/electron_analyser/vgscienta/vgscienta_detector.py +52 -0
  41. dodal/devices/electron_analyser/vgscienta/{driver_io.py → vgscienta_driver_io.py} +25 -31
  42. dodal/devices/electron_analyser/vgscienta/{region.py → vgscienta_region.py} +6 -6
  43. dodal/devices/i04/max_pixel.py +38 -0
  44. dodal/devices/i09_1_shared/__init__.py +8 -1
  45. dodal/devices/i09_1_shared/hard_energy.py +112 -0
  46. dodal/devices/i09_2_shared/__init__.py +0 -0
  47. dodal/devices/i09_2_shared/i09_apple2.py +14 -0
  48. dodal/devices/i10/i10_apple2.py +24 -22
  49. dodal/devices/i17/i17_apple2.py +32 -20
  50. dodal/devices/i19/access_controlled/attenuator_motor_squad.py +61 -0
  51. dodal/devices/i19/access_controlled/blueapi_device.py +9 -1
  52. dodal/devices/i19/access_controlled/shutter.py +2 -4
  53. dodal/devices/i21/__init__.py +3 -1
  54. dodal/devices/insertion_device/__init__.py +58 -0
  55. dodal/devices/{apple2_undulator.py → insertion_device/apple2_undulator.py} +102 -44
  56. dodal/devices/insertion_device/energy_motor_lookup.py +88 -0
  57. dodal/devices/insertion_device/id_enum.py +17 -0
  58. dodal/devices/insertion_device/lookup_table_models.py +317 -0
  59. dodal/devices/motors.py +14 -0
  60. dodal/devices/robot.py +16 -11
  61. dodal/plans/__init__.py +1 -1
  62. dodal/plans/configure_arm_trigger_and_disarm_detector.py +2 -4
  63. dodal/testing/electron_analyser/device_factory.py +4 -4
  64. dodal/testing/fixtures/devices/__init__.py +0 -0
  65. dodal/testing/fixtures/devices/apple2.py +78 -0
  66. dodal/testing/fixtures/run_engine.py +4 -0
  67. dodal/utils.py +6 -3
  68. dodal/devices/electron_analyser/abstract/__init__.py +0 -25
  69. dodal/devices/electron_analyser/abstract/base_detector.py +0 -63
  70. dodal/devices/electron_analyser/abstract/types.py +0 -12
  71. dodal/devices/electron_analyser/detector.py +0 -143
  72. dodal/devices/electron_analyser/specs/detector.py +0 -34
  73. dodal/devices/electron_analyser/types.py +0 -57
  74. dodal/devices/electron_analyser/vgscienta/detector.py +0 -48
  75. dodal/devices/util/lookup_tables_apple2.py +0 -390
  76. {dls_dodal-1.66.0.dist-info → dls_dodal-1.68.0.dist-info}/WHEEL +0 -0
  77. {dls_dodal-1.66.0.dist-info → dls_dodal-1.68.0.dist-info}/entry_points.txt +0 -0
  78. {dls_dodal-1.66.0.dist-info → dls_dodal-1.68.0.dist-info}/licenses/LICENSE +0 -0
  79. {dls_dodal-1.66.0.dist-info → dls_dodal-1.68.0.dist-info}/top_level.txt +0 -0
  80. /dodal/devices/electron_analyser/{enums.py → base/base_enums.py} +0 -0
  81. /dodal/devices/electron_analyser/specs/{enums.py → specs_enums.py} +0 -0
  82. /dodal/devices/electron_analyser/vgscienta/{enums.py → vgscienta_enums.py} +0 -0
  83. /dodal/plans/{scanspec.py → spec_path.py} +0 -0
@@ -1,5 +1,4 @@
1
1
  from ophyd_async.core import (
2
- EnabledDisabled,
3
2
  InOut,
4
3
  StandardReadable,
5
4
  StandardReadableFormat,
@@ -12,20 +11,6 @@ from ophyd_async.epics.core import (
12
11
  )
13
12
 
14
13
 
15
- class CryoStream(StandardReadable):
16
- MAX_TEMP_K = 110
17
- MAX_PRESSURE_BAR = 0.1
18
-
19
- def __init__(self, prefix: str, name: str = ""):
20
- self.course = epics_signal_rw(InOut, f"{prefix}-EA-CJET-01:COARSE:CTRL")
21
- self.fine = epics_signal_rw(InOut, f"{prefix}-EA-CJET-01:FINE:CTRL")
22
- self.temperature_k = epics_signal_r(float, f"{prefix}-EA-CSTRM-01:TEMP")
23
- self.back_pressure_bar = epics_signal_r(
24
- float, f"{prefix}-EA-CSTRM-01:BACKPRESS"
25
- )
26
- super().__init__(name)
27
-
28
-
29
14
  class TurboEnum(StrictEnum):
30
15
  OFF = "Off"
31
16
  ON = "On"
@@ -37,59 +22,33 @@ class CryoStreamSelection(StrictEnum):
37
22
  HC1 = "HC1"
38
23
 
39
24
 
40
- class OxfordCryoStreamController(StandardReadable):
25
+ class OxfordCryoStream(StandardReadable):
41
26
  def __init__(self, prefix: str, name: str = ""):
27
+ with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
28
+ self.temp = epics_signal_r(float, f"{prefix}TEMP")
29
+
42
30
  with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
43
31
  # Any signals that should be read once at the start of the scan
32
+
44
33
  self.turbo = epics_signal_rw(str, f"{prefix}TURBO")
45
34
  self.turbo_mode = epics_signal_rw(TurboEnum, f"{prefix}TURBOMODE")
46
-
47
- self.serial_comms = epics_signal_rw(EnabledDisabled, f"{prefix}DISABLE")
48
35
  self.status = epics_signal_r(str, f"{prefix}STATUS.SEVR")
49
-
50
- with self.add_children_as_readables():
51
- # Any signals that should be read at every point in the scan
52
-
53
- self.purge = epics_signal_x(f"{prefix}PURGE.PROC")
54
- self.hold = epics_signal_x(f"{prefix}HOLD.PROC")
55
- self.start = epics_signal_x(f"{prefix}RESTART.PROC")
56
- self.pause = epics_signal_x(f"{prefix}PAUSE.PROC")
57
- self.resume = epics_signal_x(f"{prefix}RESUME.PROC")
58
- self.end = epics_signal_x(f"{prefix}END.PROC")
59
- self.stop = epics_signal_x(f"{prefix}STOP.PROC")
60
-
61
- self.ramp_rate = epics_signal_rw(float, f"{prefix}RRATE")
62
- self.ramp_temp = epics_signal_rw(float, f"{prefix}RTEMP")
63
- self.ramp = epics_signal_x(f"{prefix}RAMP.PROC")
64
-
65
- self.plat_time = epics_signal_rw(float, f"{prefix}PTIME")
66
- self.plat = epics_signal_x(f"{prefix}PLAT.PROC")
67
-
68
- self.cool_temp = epics_signal_rw(float, f"{prefix}CTEMP")
69
- self.cool = epics_signal_x(f"{prefix}COOL.PROC")
70
-
71
- self.end_rate = epics_signal_rw(float, f"{prefix}ERATE")
72
-
73
- super().__init__(name)
74
-
75
-
76
- class OxfordCryoStreamStatus(StandardReadable):
77
- def __init__(self, prefix: str, name: str = ""):
78
- with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
79
- # Any signals that should be read once at the start of the scan
80
-
81
36
  self.pump_uptime = epics_signal_r(float, f"{prefix}RUNTIME")
82
37
  self.controller_number = epics_signal_r(float, f"{prefix}CTRLNUM")
83
38
  self.software_version = epics_signal_r(float, f"{prefix}VER")
84
39
  self.evap_adjust = epics_signal_r(float, f"{prefix}EVAPADJUST")
85
40
  self.series = epics_signal_r(str, f"{prefix}SERIES")
41
+ self.error = epics_signal_r(float, f"{prefix}ERROR")
42
+ self.mode = epics_signal_r(str, f"{prefix}RUNMODE")
86
43
 
87
44
  with self.add_children_as_readables():
88
45
  # Any signals that should be read at every point in the scan
46
+ self.ramp_rate = epics_signal_rw(float, f"{prefix}RRATE")
47
+ self.ramp_temp = epics_signal_rw(float, f"{prefix}RTEMP")
48
+ self.plat_time = epics_signal_rw(float, f"{prefix}PTIME")
49
+ self.cool_temp = epics_signal_rw(float, f"{prefix}CTEMP")
50
+ self.end_rate = epics_signal_rw(float, f"{prefix}ERATE")
89
51
  self.setpoint = epics_signal_r(float, f"{prefix}SETPOINT")
90
- self.temp = epics_signal_r(float, f"{prefix}TEMP")
91
- self.error = epics_signal_r(float, f"{prefix}ERROR")
92
- self.mode = epics_signal_r(str, f"{prefix}RUNMODE")
93
52
  self.phase = epics_signal_r(str, f"{prefix}PHASE")
94
53
  self.ramp_rate_setpoint = epics_signal_r(float, f"{prefix}RAMPRATE")
95
54
  self.target_temp = epics_signal_r(float, f"{prefix}TARGETTEMP")
@@ -102,14 +61,27 @@ class OxfordCryoStreamStatus(StandardReadable):
102
61
  self.suct_heat = epics_signal_r(float, f"{prefix}SUCTHEAT")
103
62
  self.back_pressure = epics_signal_r(float, f"{prefix}BACKPRESS")
104
63
 
64
+ self.purge = epics_signal_x(f"{prefix}PURGE.PROC")
65
+ self.hold = epics_signal_x(f"{prefix}HOLD.PROC")
66
+ self.start = epics_signal_x(f"{prefix}RESTART.PROC")
67
+ self.pause = epics_signal_x(f"{prefix}PAUSE.PROC")
68
+ self.resume = epics_signal_x(f"{prefix}RESUME.PROC")
69
+ self.end = epics_signal_x(f"{prefix}END.PROC")
70
+ self.stop = epics_signal_x(f"{prefix}STOP.PROC")
71
+ self.plat = epics_signal_x(f"{prefix}PLAT.PROC")
72
+ self.cool = epics_signal_x(f"{prefix}COOL.PROC")
73
+ self.ramp = epics_signal_x(f"{prefix}RAMP.PROC")
74
+
105
75
  super().__init__(name)
106
76
 
107
77
 
108
- class OxfordCryoStream(StandardReadable):
78
+ class OxfordCryoJet(StandardReadable):
79
+ # TODO: https://github.com/DiamondLightSource/dodal/issues/1486
80
+ # This is a placeholder implementation to get it working with I03, the actual cryojet has many more PVs
109
81
  def __init__(self, prefix: str, name=""):
110
82
  with self.add_children_as_readables():
111
- self.controller = OxfordCryoStreamController(prefix=prefix)
112
- self.status = OxfordCryoStreamStatus(prefix=prefix)
83
+ self.coarse = epics_signal_rw(InOut, f"{prefix}COARSE:CTRL")
84
+ self.fine = epics_signal_rw(InOut, f"{prefix}FINE:CTRL")
113
85
 
114
86
  super().__init__(name)
115
87
 
@@ -126,5 +98,4 @@ class CryoStreamGantry(StandardReadable):
126
98
  self.cryostream_selected = epics_signal_r(
127
99
  int, f"{prefix}-MO-STEP-02:GPIO_INP_BITS.B3"
128
100
  )
129
-
130
101
  super().__init__(name)
dodal/devices/eiger.py CHANGED
@@ -1,11 +1,10 @@
1
- # type: ignore # Eiger will soon be ophyd-async https://github.com/DiamondLightSource/dodal/issues/700
2
1
  from dataclasses import dataclass
3
2
  from enum import Enum
4
3
 
5
4
  from bluesky.protocols import Stageable
6
5
  from ophyd import Component, Device, EpicsSignalRO, Signal
7
6
  from ophyd.areadetector.cam import EigerDetectorCam
8
- from ophyd.status import AndStatus, Status, StatusBase
7
+ from ophyd.status import AndStatus, Status, StatusBase, SubscriptionStatus
9
8
 
10
9
  from dodal.devices.detector import DetectorParams, TriggerMode
11
10
  from dodal.devices.eiger_odin import EigerOdin
@@ -64,14 +63,12 @@ class EigerDetector(Device, Stageable):
64
63
  arming_status = Status()
65
64
  arming_status.set_finished()
66
65
 
67
- disarming_status = Status()
68
- disarming_status.set_finished()
69
-
70
66
  def __init__(self, beamline: str = "i03", *args, **kwargs):
71
67
  super().__init__(*args, **kwargs)
72
68
  self.beamline = beamline
73
69
  # using i03 timeouts as default
74
70
  self.timeouts = AVAILABLE_TIMEOUTS.get(beamline, AVAILABLE_TIMEOUTS["i03"])
71
+ self.disarming_status = None
75
72
 
76
73
  @classmethod
77
74
  def with_params(
@@ -106,6 +103,7 @@ class EigerDetector(Device, Stageable):
106
103
  raise Exception("\n".join(errors))
107
104
 
108
105
  def async_stage(self):
106
+ self.disarming_status = None
109
107
  self.odin.nodes.clear_odin_errors()
110
108
  status_ok, error_message = self.odin.wait_for_odin_initialised(
111
109
  self.timeouts.general_status_timeout
@@ -124,7 +122,7 @@ class EigerDetector(Device, Stageable):
124
122
  LOGGER.info("Waiting for arming to finish")
125
123
  self.arming_status.wait(self.timeouts.arming_timeout)
126
124
 
127
- def stage(self):
125
+ def stage(self): # pyright: ignore[reportIncompatibleMethodOverride]
128
126
  self.wait_on_arming_if_started()
129
127
  if not self.is_armed():
130
128
  LOGGER.info("Eiger not armed, arming")
@@ -133,6 +131,7 @@ class EigerDetector(Device, Stageable):
133
131
 
134
132
  def stop_odin_when_all_frames_collected(self):
135
133
  LOGGER.info("Waiting on all frames")
134
+ assert self.detector_params
136
135
  try:
137
136
  await_value(
138
137
  self.odin.file_writer.num_captured,
@@ -142,7 +141,7 @@ class EigerDetector(Device, Stageable):
142
141
  LOGGER.info("Stopping Odin")
143
142
  self.odin.stop().wait(self.timeouts.odin_stop_timeout)
144
143
 
145
- def unstage(self) -> bool:
144
+ def unstage(self) -> bool: # pyright: ignore[reportIncompatibleMethodOverride]
146
145
  assert self.detector_params is not None
147
146
  try:
148
147
  self.disarming_status = Status()
@@ -167,25 +166,34 @@ class EigerDetector(Device, Stageable):
167
166
  self.disarming_status.set_finished()
168
167
  return status_ok
169
168
 
170
- def stop(self, *args):
169
+ def stop(self, *args): # pyright: ignore[reportIncompatibleMethodOverride]
171
170
  """Emergency stop the device, mainly used to clean up after error."""
172
171
  LOGGER.info("Eiger stop() called - cleaning up...")
173
- if not self.disarming_status.done:
172
+ if self.disarming_status and not self.disarming_status.done:
174
173
  LOGGER.info("Eiger still disarming, waiting on disarm")
175
174
  self.disarming_status.wait(self.timeouts.arming_timeout)
175
+ elif not self.disarming_status:
176
+ self.disarming_status = Status()
177
+ try:
178
+ self.wait_on_arming_if_started()
179
+ stop_status = self.odin.stop()
180
+ self.odin.file_writer.start_timeout.set(1).wait(
181
+ self.timeouts.general_status_timeout
182
+ )
183
+ self.disarm_detector()
184
+ stop_status &= self.disable_roi_mode()
185
+ LOGGER.info("Waiting on stop status")
186
+ stop_status.wait(self.timeouts.general_status_timeout)
187
+ # See https://github.com/DiamondLightSource/hyperion/issues/1395
188
+ LOGGER.info("Turning off Eiger dev/shm streaming")
189
+ self.odin.fan.dev_shm_enable.set(0).wait(
190
+ self.timeouts.general_status_timeout
191
+ )
192
+ LOGGER.info("Eiger has successfully been stopped")
193
+ finally:
194
+ self.disarming_status.set_finished()
176
195
  else:
177
- self.wait_on_arming_if_started()
178
- stop_status = self.odin.stop()
179
- self.odin.file_writer.start_timeout.set(1).wait(
180
- self.timeouts.general_status_timeout
181
- )
182
- self.disarm_detector()
183
- stop_status &= self.disable_roi_mode()
184
- stop_status.wait(self.timeouts.general_status_timeout)
185
- # See https://github.com/DiamondLightSource/hyperion/issues/1395
186
- LOGGER.info("Turning off Eiger dev/shm streaming")
187
- self.odin.fan.dev_shm_enable.set(0).wait()
188
- LOGGER.info("Eiger has successfully been stopped")
196
+ LOGGER.info("Already disarmed, doing nothing")
189
197
 
190
198
  def disable_roi_mode(self):
191
199
  return self.change_roi_mode(False)
@@ -194,13 +202,13 @@ class EigerDetector(Device, Stageable):
194
202
  return self.change_roi_mode(True)
195
203
 
196
204
  def change_roi_mode(self, enable: bool) -> StatusBase:
205
+ LOGGER.info(f"Changing ROI mode to {enable}")
197
206
  assert self.detector_params is not None
198
207
  detector_dimensions = (
199
208
  self.detector_params.detector_size_constants.roi_size_pixels
200
209
  if enable
201
210
  else self.detector_params.detector_size_constants.det_size_pixels
202
211
  )
203
-
204
212
  status = self.cam.roi_mode.set(
205
213
  1 if enable else 0, timeout=self.timeouts.general_status_timeout
206
214
  )
@@ -216,7 +224,6 @@ class EigerDetector(Device, Stageable):
216
224
  status &= self.odin.file_writer.num_col_chunks.set(
217
225
  detector_dimensions.width, timeout=self.timeouts.general_status_timeout
218
226
  )
219
-
220
227
  return status
221
228
 
222
229
  def set_cam_pvs(self) -> AndStatus:
@@ -234,7 +241,8 @@ class EigerDetector(Device, Stageable):
234
241
  1, timeout=self.timeouts.general_status_timeout
235
242
  )
236
243
  status &= self.cam.image_mode.set(
237
- self.cam.ImageMode.MULTIPLE, timeout=self.timeouts.general_status_timeout
244
+ self.cam.ImageMode.MULTIPLE, # pyright: ignore[reportAttributeAccessIssue]
245
+ timeout=self.timeouts.general_status_timeout,
238
246
  )
239
247
  status &= self.cam.trigger_mode.set(
240
248
  InternalEigerTriggerMode.EXTERNAL_SERIES.value,
@@ -296,6 +304,7 @@ class EigerDetector(Device, Stageable):
296
304
  self.detector_params.omega_increment,
297
305
  timeout=self.timeouts.general_status_timeout,
298
306
  )
307
+
299
308
  return status
300
309
 
301
310
  def set_detector_threshold(self, energy: float, tolerance: float = 0.1) -> Status:
@@ -307,7 +316,7 @@ class EigerDetector(Device, Stageable):
307
316
  this tolerance it is not set again. Defaults to 0.1eV.
308
317
  """
309
318
 
310
- current_energy = self.cam.photon_energy.get()
319
+ current_energy = float(self.cam.photon_energy.get())
311
320
  if abs(current_energy - energy) > tolerance:
312
321
  LOGGER.info(f"Setting detector threshold to {energy}")
313
322
  return self.cam.photon_energy.set(
@@ -390,7 +399,7 @@ class EigerDetector(Device, Stageable):
390
399
  def disarm_detector(self):
391
400
  self.cam.acquire.set(0).wait(self.timeouts.general_status_timeout)
392
401
 
393
- def wait_for_stale_params(self) -> Status:
402
+ def wait_for_stale_params(self) -> SubscriptionStatus:
394
403
  LOGGER.info("Eiger arming: Waiting for stale params...")
395
404
  return await_value(self.stale_params, 0, 60)
396
405
 
@@ -404,6 +413,11 @@ class EigerDetector(Device, Stageable):
404
413
  detector_params: DetectorParams = self.detector_params
405
414
  if detector_params.use_roi_mode:
406
415
  functions_to_do_arm.append(self.enable_roi_mode)
416
+ threshold_energy = (
417
+ detector_params.expected_energy_ev
418
+ if detector_params.expected_energy_ev
419
+ else float(self.cam.photon_energy.get())
420
+ )
407
421
 
408
422
  arming_sequence_funcs = [
409
423
  # If a beam dump occurs after arming the eiger but prior to eiger staging,
@@ -411,7 +425,7 @@ class EigerDetector(Device, Stageable):
411
425
  # if this previously completed successfully we must reset the odin first
412
426
  self.odin.stop,
413
427
  lambda: self.change_dev_shm(detector_params.enable_dev_shm),
414
- lambda: self.set_detector_threshold(detector_params.expected_energy_ev),
428
+ lambda: self.set_detector_threshold(threshold_energy),
415
429
  self.set_cam_pvs,
416
430
  self.set_odin_number_of_frame_chunks,
417
431
  self.set_odin_pvs,
@@ -1,33 +0,0 @@
1
- from .detector import (
2
- ElectronAnalyserDetector,
3
- ElectronAnalyserRegionDetector,
4
- TElectronAnalyserDetector,
5
- TElectronAnalyserRegionDetector,
6
- )
7
- from .energy_sources import DualEnergySource, EnergySource
8
- from .enums import EnergyMode, SelectedSource
9
- from .types import (
10
- ElectronAnalyserDetectorImpl,
11
- ElectronAnalyserDriverImpl,
12
- GenericElectronAnalyserDetector,
13
- GenericElectronAnalyserRegionDetector,
14
- )
15
- from .util import to_binding_energy, to_kinetic_energy
16
-
17
- __all__ = [
18
- "to_binding_energy",
19
- "to_kinetic_energy",
20
- "DualEnergySource",
21
- "SelectedSource",
22
- "EnergySource",
23
- "EnergyMode",
24
- "SelectedSource",
25
- "ElectronAnalyserDetector",
26
- "ElectronAnalyserDetectorImpl",
27
- "ElectronAnalyserDriverImpl",
28
- "TElectronAnalyserDetector",
29
- "ElectronAnalyserRegionDetector",
30
- "TElectronAnalyserRegionDetector",
31
- "GenericElectronAnalyserDetector",
32
- "GenericElectronAnalyserRegionDetector",
33
- ]
@@ -0,0 +1,58 @@
1
+ from .base_controller import (
2
+ ElectronAnalyserController,
3
+ GenericElectronAnalyserController,
4
+ )
5
+ from .base_detector import (
6
+ BaseElectronAnalyserDetector,
7
+ ElectronAnalyserDetector,
8
+ ElectronAnalyserRegionDetector,
9
+ GenericBaseElectronAnalyserDetector,
10
+ GenericElectronAnalyserDetector,
11
+ GenericElectronAnalyserRegionDetector,
12
+ )
13
+ from .base_driver_io import (
14
+ AbstractAnalyserDriverIO,
15
+ GenericAnalyserDriverIO,
16
+ TAbstractAnalyserDriverIO,
17
+ )
18
+ from .base_enums import EnergyMode, SelectedSource
19
+ from .base_region import (
20
+ AbstractBaseRegion,
21
+ AbstractBaseSequence,
22
+ GenericRegion,
23
+ GenericSequence,
24
+ TAbstractBaseRegion,
25
+ TAbstractBaseSequence,
26
+ TAcquisitionMode,
27
+ TLensMode,
28
+ )
29
+ from .base_util import to_binding_energy, to_kinetic_energy
30
+ from .energy_sources import DualEnergySource, EnergySource
31
+
32
+ __all__ = [
33
+ "ElectronAnalyserController",
34
+ "GenericElectronAnalyserController",
35
+ "BaseElectronAnalyserDetector",
36
+ "ElectronAnalyserDetector",
37
+ "ElectronAnalyserRegionDetector",
38
+ "GenericBaseElectronAnalyserDetector",
39
+ "GenericElectronAnalyserDetector",
40
+ "GenericElectronAnalyserRegionDetector",
41
+ "AbstractAnalyserDriverIO",
42
+ "GenericAnalyserDriverIO",
43
+ "TAbstractAnalyserDriverIO",
44
+ "EnergyMode",
45
+ "SelectedSource",
46
+ "AbstractBaseRegion",
47
+ "AbstractBaseSequence",
48
+ "GenericRegion",
49
+ "GenericSequence",
50
+ "TAbstractBaseRegion",
51
+ "TAbstractBaseSequence",
52
+ "TAcquisitionMode",
53
+ "TLensMode",
54
+ "to_binding_energy",
55
+ "to_kinetic_energy",
56
+ "DualEnergySource",
57
+ "EnergySource",
58
+ ]
@@ -0,0 +1,73 @@
1
+ from typing import Generic, TypeVar
2
+
3
+ from ophyd_async.core import TriggerInfo
4
+ from ophyd_async.epics.adcore import ADImageMode
5
+
6
+ from dodal.devices.controllers import ConstantDeadTimeController
7
+ from dodal.devices.electron_analyser.base.base_driver_io import (
8
+ GenericAnalyserDriverIO,
9
+ TAbstractAnalyserDriverIO,
10
+ )
11
+ from dodal.devices.electron_analyser.base.base_region import (
12
+ GenericRegion,
13
+ TAbstractBaseRegion,
14
+ )
15
+ from dodal.devices.electron_analyser.base.energy_sources import (
16
+ AbstractEnergySource,
17
+ DualEnergySource,
18
+ )
19
+
20
+
21
+ class ElectronAnalyserController(
22
+ ConstantDeadTimeController[TAbstractAnalyserDriverIO],
23
+ Generic[TAbstractAnalyserDriverIO, TAbstractBaseRegion],
24
+ ):
25
+ """
26
+ Specialised controller for the electron analysers to provide additional setup logic
27
+ such as selecting the energy source to use from requested region and giving the
28
+ driver the correct region parameters.
29
+ """
30
+
31
+ def __init__(
32
+ self,
33
+ driver: TAbstractAnalyserDriverIO,
34
+ energy_source: AbstractEnergySource,
35
+ deadtime: float,
36
+ image_mode: ADImageMode = ADImageMode.SINGLE,
37
+ ):
38
+ """
39
+ Parameters:
40
+ driver: The electron analyser driver to wrap around that holds the PV's.
41
+ energy_source: Device that holds the excitation energy and ability to switch
42
+ between sources.
43
+ deadtime: For a given exposure, what is the safest minimum time between
44
+ exposures that can be determined without reading signals.
45
+ image_mode: The image mode to configure the driver with before measuring.
46
+ """
47
+ self.energy_source = energy_source
48
+ super().__init__(driver, deadtime, image_mode)
49
+
50
+ async def setup_with_region(self, region: TAbstractBaseRegion):
51
+ """Logic to set the driver with a region."""
52
+
53
+ if isinstance(self.energy_source, DualEnergySource):
54
+ self.energy_source.selected_source.set(region.excitation_energy_source)
55
+ excitation_energy = await self.energy_source.energy.get_value()
56
+ epics_region = region.prepare_for_epics(excitation_energy)
57
+ await self.driver.set(epics_region)
58
+
59
+ async def prepare(self, trigger_info: TriggerInfo) -> None:
60
+ """Do all necessary steps to prepare the detector for triggers."""
61
+ # Let the driver know the excitation energy before measuring for binding energy
62
+ # axis calculation.
63
+ excitation_energy = await self.energy_source.energy.get_value()
64
+ await self.driver.cached_excitation_energy.set(excitation_energy)
65
+ await super().prepare(trigger_info)
66
+
67
+
68
+ GenericElectronAnalyserController = ElectronAnalyserController[
69
+ GenericAnalyserDriverIO, GenericRegion
70
+ ]
71
+ TElectronAnalyserController = TypeVar(
72
+ "TElectronAnalyserController", bound=ElectronAnalyserController
73
+ )