dls-dodal 1.34.1__py3-none-any.whl → 1.36.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 (81) hide show
  1. {dls_dodal-1.34.1.dist-info → dls_dodal-1.36.0.dist-info}/METADATA +4 -2
  2. dls_dodal-1.36.0.dist-info/RECORD +152 -0
  3. {dls_dodal-1.34.1.dist-info → dls_dodal-1.36.0.dist-info}/WHEEL +1 -1
  4. dodal/_version.py +2 -2
  5. dodal/beamlines/i22.py +24 -11
  6. dodal/beamlines/i24.py +4 -4
  7. dodal/beamlines/p38.py +23 -11
  8. dodal/common/beamlines/beamline_utils.py +1 -2
  9. dodal/common/crystal_metadata.py +61 -0
  10. dodal/common/signal_utils.py +10 -14
  11. dodal/devices/CTAB.py +1 -1
  12. dodal/devices/aperture.py +1 -1
  13. dodal/devices/aperturescatterguard.py +20 -8
  14. dodal/devices/apple2_undulator.py +30 -29
  15. dodal/devices/areadetector/plugins/CAM.py +3 -5
  16. dodal/devices/areadetector/plugins/MJPG.py +1 -1
  17. dodal/devices/attenuator.py +1 -1
  18. dodal/devices/backlight.py +4 -5
  19. dodal/devices/cryostream.py +3 -5
  20. dodal/devices/dcm.py +26 -2
  21. dodal/devices/detector/detector_motion.py +3 -5
  22. dodal/devices/diamond_filter.py +3 -4
  23. dodal/devices/eiger.py +88 -49
  24. dodal/devices/fast_grid_scan.py +1 -1
  25. dodal/devices/fluorescence_detector_motion.py +5 -7
  26. dodal/devices/focusing_mirror.py +12 -11
  27. dodal/devices/hutch_shutter.py +4 -5
  28. dodal/devices/i10/i10_apple2.py +20 -19
  29. dodal/devices/i10/i10_setting_data.py +2 -2
  30. dodal/devices/i22/dcm.py +43 -75
  31. dodal/devices/i22/fswitch.py +5 -5
  32. dodal/devices/i24/aperture.py +3 -5
  33. dodal/devices/i24/beamstop.py +3 -5
  34. dodal/devices/i24/dcm.py +1 -1
  35. dodal/devices/i24/dual_backlight.py +4 -6
  36. dodal/devices/i24/pmac.py +35 -46
  37. dodal/devices/i24/vgonio.py +16 -0
  38. dodal/devices/ipin.py +5 -3
  39. dodal/devices/linkam3.py +7 -7
  40. dodal/devices/oav/oav_detector.py +3 -3
  41. dodal/devices/oav/oav_to_redis_forwarder.py +8 -7
  42. dodal/devices/oav/pin_image_recognition/__init__.py +9 -7
  43. dodal/devices/oav/snapshots/grid_overlay.py +16 -16
  44. dodal/devices/oav/snapshots/snapshot_with_beam_centre.py +5 -5
  45. dodal/devices/oav/snapshots/snapshot_with_grid.py +6 -6
  46. dodal/devices/oav/utils.py +2 -2
  47. dodal/devices/p99/sample_stage.py +3 -5
  48. dodal/devices/pgm.py +5 -6
  49. dodal/devices/qbpm.py +1 -1
  50. dodal/devices/robot.py +3 -3
  51. dodal/devices/smargon.py +1 -1
  52. dodal/devices/synchrotron.py +9 -4
  53. dodal/devices/tetramm.py +7 -7
  54. dodal/devices/thawer.py +13 -7
  55. dodal/devices/undulator.py +5 -5
  56. dodal/devices/util/epics_util.py +1 -1
  57. dodal/devices/watsonmarlow323_pump.py +45 -0
  58. dodal/devices/webcam.py +9 -2
  59. dodal/devices/xbpm_feedback.py +3 -5
  60. dodal/devices/xspress3/xspress3.py +8 -9
  61. dodal/devices/xspress3/xspress3_channel.py +3 -5
  62. dodal/devices/zebra.py +7 -6
  63. dodal/devices/zebra_controlled_shutter.py +5 -6
  64. dodal/devices/zocalo/__init__.py +2 -2
  65. dodal/devices/zocalo/zocalo_constants.py +3 -0
  66. dodal/devices/zocalo/zocalo_interaction.py +2 -1
  67. dodal/devices/zocalo/zocalo_results.py +92 -79
  68. dodal/plan_stubs/__init__.py +0 -0
  69. dodal/{plans/data_session_metadata.py → plan_stubs/data_session.py} +2 -2
  70. dodal/{plans/motor_util_plans.py → plan_stubs/motor_utils.py} +2 -2
  71. dodal/plan_stubs/wrapped.py +150 -0
  72. dodal/plans/__init__.py +4 -0
  73. dodal/plans/scanspec.py +66 -0
  74. dodal/plans/wrapped.py +57 -0
  75. dodal/utils.py +4 -0
  76. dls_dodal-1.34.1.dist-info/RECORD +0 -144
  77. dodal/devices/i24/i24_vgonio.py +0 -17
  78. {dls_dodal-1.34.1.dist-info → dls_dodal-1.36.0.dist-info}/LICENSE +0 -0
  79. {dls_dodal-1.34.1.dist-info → dls_dodal-1.36.0.dist-info}/entry_points.txt +0 -0
  80. {dls_dodal-1.34.1.dist-info → dls_dodal-1.36.0.dist-info}/top_level.txt +0 -0
  81. /dodal/{plans → plan_stubs}/check_topup.py +0 -0
@@ -1,26 +1,26 @@
1
1
  import abc
2
2
  import asyncio
3
3
  from dataclasses import dataclass
4
- from enum import Enum
5
4
  from typing import Any
6
5
 
7
6
  import numpy as np
8
7
  from bluesky.protocols import Movable
9
8
  from ophyd_async.core import (
10
9
  AsyncStatus,
11
- ConfigSignal,
12
- HintedSignal,
10
+ Reference,
13
11
  StandardReadable,
12
+ StandardReadableFormat,
13
+ StrictEnum,
14
14
  soft_signal_r_and_setter,
15
15
  wait_for_value,
16
16
  )
17
- from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw, epics_signal_w
17
+ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw, epics_signal_w
18
18
  from pydantic import BaseModel, ConfigDict, RootModel
19
19
 
20
20
  from dodal.log import LOGGER
21
21
 
22
22
 
23
- class UndulatorGateStatus(str, Enum):
23
+ class UndulatorGateStatus(StrictEnum):
24
24
  open = "Open"
25
25
  close = "Closed"
26
26
 
@@ -128,12 +128,12 @@ class UndulatorGap(StandardReadable, Movable):
128
128
  )
129
129
  # This is calculated acceleration from speed
130
130
  self.acceleration_time = epics_signal_r(float, prefix + "IDGSETACC")
131
- with self.add_children_as_readables(ConfigSignal):
131
+ with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
132
132
  # Unit
133
133
  self.motor_egu = epics_signal_r(str, prefix + "BLGAPMTR.EGU")
134
134
  # Gap velocity
135
135
  self.velocity = epics_signal_rw(float, prefix + "BLGSETVEL")
136
- with self.add_children_as_readables(HintedSignal):
136
+ with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
137
137
  # Gap readback value
138
138
  self.user_readback = epics_signal_r(float, prefix + "CURRGAPD")
139
139
  super().__init__(name)
@@ -187,10 +187,10 @@ class UndulatorPhaseMotor(StandardReadable):
187
187
  self.user_setpoint_demand_readback = epics_signal_r(float, fullPV + "DMD")
188
188
 
189
189
  fullPV = fullPV + "MTR"
190
- with self.add_children_as_readables(HintedSignal):
190
+ with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
191
191
  self.user_setpoint_readback = epics_signal_r(float, fullPV + ".RBV")
192
192
 
193
- with self.add_children_as_readables(ConfigSignal):
193
+ with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
194
194
  self.motor_egu = epics_signal_r(str, fullPV + ".EGU")
195
195
  self.velocity = epics_signal_rw(float, fullPV + ".VELO")
196
196
 
@@ -388,10 +388,10 @@ class Apple2(StandardReadable, Movable):
388
388
 
389
389
  # Attributes are set after super call so they are not renamed to
390
390
  # <name>-undulator, etc.
391
- with self.add_children_as_readables():
392
- self.gap = id_gap
393
- self.phase = id_phase
394
- with self.add_children_as_readables(HintedSignal):
391
+ self.gap = Reference(id_gap)
392
+ self.phase = Reference(id_phase)
393
+
394
+ with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
395
395
  # Store the polarisation for readback.
396
396
  self.polarisation, self._polarisation_set = soft_signal_r_and_setter(
397
397
  str, initial_value=None
@@ -437,16 +437,16 @@ class Apple2(StandardReadable, Movable):
437
437
  """
438
438
 
439
439
  # Only need to check gap as the phase motors share both fault and gate with gap.
440
- await self.gap.check_id_status()
440
+ await self.gap().check_id_status()
441
441
  await asyncio.gather(
442
- self.phase.top_outer.user_setpoint.set(value=value.top_outer),
443
- self.phase.top_inner.user_setpoint.set(value=value.top_inner),
444
- self.phase.btm_inner.user_setpoint.set(value=value.btm_inner),
445
- self.phase.btm_outer.user_setpoint.set(value=value.btm_outer),
446
- self.gap.user_setpoint.set(value=value.gap),
442
+ self.phase().top_outer.user_setpoint.set(value=value.top_outer),
443
+ self.phase().top_inner.user_setpoint.set(value=value.top_inner),
444
+ self.phase().btm_inner.user_setpoint.set(value=value.btm_inner),
445
+ self.phase().btm_outer.user_setpoint.set(value=value.btm_outer),
446
+ self.gap().user_setpoint.set(value=value.gap),
447
447
  )
448
448
  timeout = np.max(
449
- await asyncio.gather(self.gap.get_timeout(), self.phase.get_timeout())
449
+ await asyncio.gather(self.gap().get_timeout(), self.phase().get_timeout())
450
450
  )
451
451
  LOGGER.info(
452
452
  f"Moving f{self.name} energy and polorisation to {energy}, {self.pol}"
@@ -454,10 +454,12 @@ class Apple2(StandardReadable, Movable):
454
454
  )
455
455
 
456
456
  await asyncio.gather(
457
- self.gap.set_move.set(value=1, timeout=timeout),
458
- self.phase.set_move.set(value=1, timeout=timeout),
457
+ self.gap().set_move.set(value=1, timeout=timeout),
458
+ self.phase().set_move.set(value=1, timeout=timeout),
459
+ )
460
+ await wait_for_value(
461
+ self.gap().gate, UndulatorGateStatus.close, timeout=timeout
459
462
  )
460
- await wait_for_value(self.gap.gate, UndulatorGateStatus.close, timeout=timeout)
461
463
  self._energy_set(energy) # Update energy for after move for readback.
462
464
 
463
465
  def _get_id_gap_phase(self, energy: float) -> tuple[float, float]:
@@ -522,12 +524,11 @@ class Apple2(StandardReadable, Movable):
522
524
  (May be for future one can use the inverse poly to work out the energy and try to match it with the current energy
523
525
  to workout the polarisation but during my test the inverse poly is too unstable for general use.)
524
526
  """
525
- cur_loc = await self.read()
526
- top_outer = cur_loc[self.phase.top_outer.user_setpoint_readback.name]["value"]
527
- top_inner = cur_loc[self.phase.top_inner.user_setpoint_readback.name]["value"]
528
- btm_inner = cur_loc[self.phase.btm_inner.user_setpoint_readback.name]["value"]
529
- btm_outer = cur_loc[self.phase.btm_outer.user_setpoint_readback.name]["value"]
530
- gap = cur_loc[self.gap.user_readback.name]["value"]
527
+ top_outer = await self.phase().top_outer.user_setpoint_readback.get_value()
528
+ top_inner = await self.phase().top_inner.user_setpoint_readback.get_value()
529
+ btm_inner = await self.phase().btm_inner.user_setpoint_readback.get_value()
530
+ btm_outer = await self.phase().btm_outer.user_setpoint_readback.get_value()
531
+ gap = await self.gap().user_readback.get_value()
531
532
  if gap > MAXIMUM_GAP_MOTOR_POSITION:
532
533
  raise RuntimeError(
533
534
  f"{self.name} is not in use, close gap or set polarisation to use this ID"
@@ -1,10 +1,8 @@
1
- from enum import Enum
1
+ from ophyd_async.core import StandardReadable, StrictEnum
2
+ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
2
3
 
3
- from ophyd_async.core import StandardReadable
4
- from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw
5
4
 
6
-
7
- class ColorMode(str, Enum):
5
+ class ColorMode(StrictEnum):
8
6
  """
9
7
  Enum to store the various color modes of the camera. We use RGB1.
10
8
  """
@@ -6,7 +6,7 @@ import aiofiles
6
6
  from aiohttp import ClientSession
7
7
  from bluesky.protocols import Triggerable
8
8
  from ophyd_async.core import AsyncStatus, StandardReadable, soft_signal_rw
9
- from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw
9
+ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
10
10
  from PIL import Image
11
11
 
12
12
  from dodal.log import LOGGER
@@ -9,7 +9,7 @@ from ophyd_async.core import (
9
9
  StandardReadable,
10
10
  wait_for_value,
11
11
  )
12
- from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw, epics_signal_x
12
+ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw, epics_signal_x
13
13
 
14
14
  from dodal.log import LOGGER
15
15
 
@@ -1,17 +1,16 @@
1
1
  from asyncio import sleep
2
- from enum import Enum
3
2
 
4
3
  from bluesky.protocols import Movable
5
- from ophyd_async.core import AsyncStatus, StandardReadable
6
- from ophyd_async.epics.signal import epics_signal_rw
4
+ from ophyd_async.core import AsyncStatus, StandardReadable, StrictEnum
5
+ from ophyd_async.epics.core import epics_signal_rw
7
6
 
8
7
 
9
- class BacklightPower(str, Enum):
8
+ class BacklightPower(StrictEnum):
10
9
  ON = "On"
11
10
  OFF = "Off"
12
11
 
13
12
 
14
- class BacklightPosition(str, Enum):
13
+ class BacklightPosition(StrictEnum):
15
14
  IN = "In"
16
15
  OUT = "Out"
17
16
 
@@ -1,10 +1,8 @@
1
- from enum import Enum
1
+ from ophyd_async.core import StandardReadable, StrictEnum
2
+ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
2
3
 
3
- from ophyd_async.core import StandardReadable
4
- from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw
5
4
 
6
-
7
- class InOut(str, Enum):
5
+ class InOut(StrictEnum):
8
6
  IN = "In"
9
7
  OUT = "Out"
10
8
 
dodal/devices/dcm.py CHANGED
@@ -1,6 +1,14 @@
1
- from ophyd_async.core import StandardReadable
1
+ import numpy as np
2
+ from numpy.typing import NDArray
3
+ from ophyd_async.core import StandardReadable, soft_signal_r_and_setter
4
+ from ophyd_async.epics.core import epics_signal_r
2
5
  from ophyd_async.epics.motor import Motor
3
- from ophyd_async.epics.signal import epics_signal_r
6
+
7
+ from dodal.common.crystal_metadata import (
8
+ CrystalMetadata,
9
+ MaterialsEnum,
10
+ make_crystal_metadata_from_material,
11
+ )
4
12
 
5
13
 
6
14
  class DCM(StandardReadable):
@@ -17,7 +25,11 @@ class DCM(StandardReadable):
17
25
  self,
18
26
  prefix: str,
19
27
  name: str = "",
28
+ crystal_metadata: CrystalMetadata | None = None,
20
29
  ) -> None:
30
+ cm = crystal_metadata or make_crystal_metadata_from_material(
31
+ MaterialsEnum.Si, (1, 1, 1)
32
+ )
21
33
  with self.add_children_as_readables():
22
34
  self.bragg_in_degrees = Motor(prefix + "BRAGG")
23
35
  self.roll_in_mrad = Motor(prefix + "ROLL")
@@ -36,4 +48,16 @@ class DCM(StandardReadable):
36
48
  self.perp_temp = epics_signal_r(float, prefix + "TEMP6")
37
49
  self.perp_sub_assembly_temp = epics_signal_r(float, prefix + "TEMP7")
38
50
 
51
+ self.crystal_metadata_usage, _ = soft_signal_r_and_setter(
52
+ str, initial_value=cm.usage
53
+ )
54
+ self.crystal_metadata_type, _ = soft_signal_r_and_setter(
55
+ str, initial_value=cm.type
56
+ )
57
+ reflection_array = np.array(cm.reflection)
58
+ self.crystal_metadata_reflection, _ = soft_signal_r_and_setter(
59
+ NDArray[np.uint64],
60
+ initial_value=reflection_array,
61
+ )
62
+ self.crystal_metadata_d_spacing = epics_signal_r(float, "DSPACING:RBV")
39
63
  super().__init__(name)
@@ -1,11 +1,9 @@
1
- from enum import Enum
2
-
3
- from ophyd_async.core import Device
1
+ from ophyd_async.core import Device, StrictEnum
2
+ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
4
3
  from ophyd_async.epics.motor import Motor
5
- from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw
6
4
 
7
5
 
8
- class ShutterState(str, Enum):
6
+ class ShutterState(StrictEnum):
9
7
  CLOSED = "Closed"
10
8
  OPEN = "Open"
11
9
 
@@ -1,12 +1,11 @@
1
- from enum import Enum
2
1
  from typing import Generic, TypeVar
3
2
 
4
- from ophyd_async.core import StandardReadable
3
+ from ophyd_async.core import StandardReadable, StrictEnum
4
+ from ophyd_async.epics.core import epics_signal_rw
5
5
  from ophyd_async.epics.motor import Motor
6
- from ophyd_async.epics.signal import epics_signal_rw
7
6
 
8
7
 
9
- class _Filters(str, Enum):
8
+ class _Filters(StrictEnum):
10
9
  pass
11
10
 
12
11
 
dodal/devices/eiger.py CHANGED
@@ -1,4 +1,5 @@
1
1
  # type: ignore # Eiger will soon be ophyd-async https://github.com/DiamondLightSource/dodal/issues/700
2
+ from dataclasses import dataclass
2
3
  from enum import Enum
3
4
 
4
5
  from ophyd import Component, Device, EpicsSignalRO, Signal
@@ -14,6 +15,15 @@ from dodal.log import LOGGER
14
15
  FREE_RUN_MAX_IMAGES = 1000000
15
16
 
16
17
 
18
+ @dataclass
19
+ class EigerTimeouts:
20
+ stale_params_timeout: int = 60
21
+ general_status_timeout: int = 10
22
+ meta_file_ready_timeout: int = 30
23
+ all_frames_timeout: int = 120
24
+ arming_timeout: int = 60
25
+
26
+
17
27
  class InternalEigerTriggerMode(Enum):
18
28
  INTERNAL_SERIES = 0
19
29
  INTERNAL_ENABLE = 1
@@ -21,6 +31,17 @@ class InternalEigerTriggerMode(Enum):
21
31
  EXTERNAL_ENABLE = 3
22
32
 
23
33
 
34
+ AVAILABLE_TIMEOUTS = {
35
+ "i03": EigerTimeouts(
36
+ stale_params_timeout=60,
37
+ general_status_timeout=10,
38
+ meta_file_ready_timeout=30,
39
+ all_frames_timeout=120, # Long timeout for meta file to compensate for filesystem issues
40
+ arming_timeout=60,
41
+ )
42
+ }
43
+
44
+
24
45
  class EigerDetector(Device):
25
46
  class ArmingSignal(Signal):
26
47
  def set(self, value, *, timeout=None, settle_time=None, **kwargs):
@@ -34,13 +55,6 @@ class EigerDetector(Device):
34
55
  stale_params = Component(EpicsSignalRO, "CAM:StaleParameters_RBV")
35
56
  bit_depth = Component(EpicsSignalRO, "CAM:BitDepthImage_RBV")
36
57
 
37
- STALE_PARAMS_TIMEOUT = 60
38
- GENERAL_STATUS_TIMEOUT = 10
39
- # Long timeout for meta file to compensate for filesystem issues
40
- META_FILE_READY_TIMEOUT = 30
41
- ALL_FRAMES_TIMEOUT = 120
42
- ARMING_TIMEOUT = 60
43
-
44
58
  filewriters_finished: StatusBase
45
59
 
46
60
  detector_params: DetectorParams | None = None
@@ -48,13 +62,20 @@ class EigerDetector(Device):
48
62
  arming_status = Status()
49
63
  arming_status.set_finished()
50
64
 
65
+ def __init__(self, beamline: str = "i03", *args, **kwargs):
66
+ super().__init__(*args, **kwargs)
67
+ self.beamline = beamline
68
+ # using i03 timeouts as default
69
+ self.timeouts = AVAILABLE_TIMEOUTS.get(beamline, AVAILABLE_TIMEOUTS["i03"])
70
+
51
71
  @classmethod
52
72
  def with_params(
53
73
  cls,
54
74
  params: DetectorParams,
55
75
  name: str = "EigerDetector",
76
+ beamline: str = "i03",
56
77
  ):
57
- det = cls(name=name)
78
+ det = cls(name=name, beamline=beamline)
58
79
  det.set_detector_parameters(params)
59
80
  return det
60
81
 
@@ -82,7 +103,7 @@ class EigerDetector(Device):
82
103
  def async_stage(self):
83
104
  self.odin.nodes.clear_odin_errors()
84
105
  status_ok, error_message = self.odin.wait_for_odin_initialised(
85
- self.GENERAL_STATUS_TIMEOUT
106
+ self.timeouts.general_status_timeout
86
107
  )
87
108
  if not status_ok:
88
109
  raise Exception(f"Odin not initialised: {error_message}")
@@ -96,14 +117,14 @@ class EigerDetector(Device):
96
117
  def wait_on_arming_if_started(self):
97
118
  if not self.arming_status.done:
98
119
  LOGGER.info("Waiting for arming to finish")
99
- self.arming_status.wait(self.ARMING_TIMEOUT)
120
+ self.arming_status.wait(self.timeouts.arming_timeout)
100
121
 
101
122
  def stage(self):
102
123
  self.wait_on_arming_if_started()
103
124
  if not self.is_armed():
104
125
  LOGGER.info("Eiger not armed, arming")
105
126
 
106
- self.async_stage().wait(timeout=self.ARMING_TIMEOUT)
127
+ self.async_stage().wait(timeout=self.timeouts.arming_timeout)
107
128
 
108
129
  def stop_odin_when_all_frames_collected(self):
109
130
  LOGGER.info("Waiting on all frames")
@@ -111,7 +132,7 @@ class EigerDetector(Device):
111
132
  await_value(
112
133
  self.odin.file_writer.num_captured,
113
134
  self.detector_params.full_number_of_images,
114
- ).wait(self.ALL_FRAMES_TIMEOUT)
135
+ ).wait(self.timeouts.all_frames_timeout)
115
136
  finally:
116
137
  LOGGER.info("Stopping Odin")
117
138
  self.odin.stop().wait(5)
@@ -124,7 +145,9 @@ class EigerDetector(Device):
124
145
  # In free run mode we have to manually stop odin
125
146
  self.stop_odin_when_all_frames_collected()
126
147
 
127
- self.odin.file_writer.start_timeout.set(1).wait(self.GENERAL_STATUS_TIMEOUT)
148
+ self.odin.file_writer.start_timeout.set(1).wait(
149
+ self.timeouts.general_status_timeout
150
+ )
128
151
  LOGGER.info("Waiting on filewriter to finish")
129
152
  self.filewriters_finished.wait(30)
130
153
 
@@ -132,7 +155,7 @@ class EigerDetector(Device):
132
155
  finally:
133
156
  self.disarm_detector()
134
157
  status_ok = self.odin.check_and_wait_for_odin_state(
135
- self.GENERAL_STATUS_TIMEOUT
158
+ self.timeouts.general_status_timeout
136
159
  )
137
160
  self.disable_roi_mode()
138
161
  return status_ok
@@ -142,10 +165,12 @@ class EigerDetector(Device):
142
165
  LOGGER.info("Eiger stop() called - cleaning up...")
143
166
  self.wait_on_arming_if_started()
144
167
  stop_status = self.odin.stop()
145
- self.odin.file_writer.start_timeout.set(1).wait(self.GENERAL_STATUS_TIMEOUT)
168
+ self.odin.file_writer.start_timeout.set(1).wait(
169
+ self.timeouts.general_status_timeout
170
+ )
146
171
  self.disarm_detector()
147
172
  stop_status &= self.disable_roi_mode()
148
- stop_status.wait(self.GENERAL_STATUS_TIMEOUT)
173
+ stop_status.wait(self.timeouts.general_status_timeout)
149
174
  # See https://github.com/DiamondLightSource/hyperion/issues/1395
150
175
  LOGGER.info("Turning off Eiger dev/shm streaming")
151
176
  self.odin.fan.dev_shm_enable.set(0).wait()
@@ -166,19 +191,19 @@ class EigerDetector(Device):
166
191
  )
167
192
 
168
193
  status = self.cam.roi_mode.set(
169
- 1 if enable else 0, timeout=self.GENERAL_STATUS_TIMEOUT
194
+ 1 if enable else 0, timeout=self.timeouts.general_status_timeout
170
195
  )
171
196
  status &= self.odin.file_writer.image_height.set(
172
- detector_dimensions.height, timeout=self.GENERAL_STATUS_TIMEOUT
197
+ detector_dimensions.height, timeout=self.timeouts.general_status_timeout
173
198
  )
174
199
  status &= self.odin.file_writer.image_width.set(
175
- detector_dimensions.width, timeout=self.GENERAL_STATUS_TIMEOUT
200
+ detector_dimensions.width, timeout=self.timeouts.general_status_timeout
176
201
  )
177
202
  status &= self.odin.file_writer.num_row_chunks.set(
178
- detector_dimensions.height, timeout=self.GENERAL_STATUS_TIMEOUT
203
+ detector_dimensions.height, timeout=self.timeouts.general_status_timeout
179
204
  )
180
205
  status &= self.odin.file_writer.num_col_chunks.set(
181
- detector_dimensions.width, timeout=self.GENERAL_STATUS_TIMEOUT
206
+ detector_dimensions.width, timeout=self.timeouts.general_status_timeout
182
207
  )
183
208
 
184
209
  return status
@@ -186,25 +211,29 @@ class EigerDetector(Device):
186
211
  def set_cam_pvs(self) -> AndStatus:
187
212
  assert self.detector_params is not None
188
213
  status = self.cam.acquire_time.set(
189
- self.detector_params.exposure_time, timeout=self.GENERAL_STATUS_TIMEOUT
214
+ self.detector_params.exposure_time,
215
+ timeout=self.timeouts.general_status_timeout,
190
216
  )
191
217
  status &= self.cam.acquire_period.set(
192
- self.detector_params.exposure_time, timeout=self.GENERAL_STATUS_TIMEOUT
218
+ self.detector_params.exposure_time,
219
+ timeout=self.timeouts.general_status_timeout,
220
+ )
221
+ status &= self.cam.num_exposures.set(
222
+ 1, timeout=self.timeouts.general_status_timeout
193
223
  )
194
- status &= self.cam.num_exposures.set(1, timeout=self.GENERAL_STATUS_TIMEOUT)
195
224
  status &= self.cam.image_mode.set(
196
- self.cam.ImageMode.MULTIPLE, timeout=self.GENERAL_STATUS_TIMEOUT
225
+ self.cam.ImageMode.MULTIPLE, timeout=self.timeouts.general_status_timeout
197
226
  )
198
227
  status &= self.cam.trigger_mode.set(
199
228
  InternalEigerTriggerMode.EXTERNAL_SERIES.value,
200
- timeout=self.GENERAL_STATUS_TIMEOUT,
229
+ timeout=self.timeouts.general_status_timeout,
201
230
  )
202
231
  return status
203
232
 
204
233
  def set_odin_number_of_frame_chunks(self) -> Status:
205
234
  assert self.detector_params is not None
206
235
  status = self.odin.file_writer.num_frames_chunks.set(
207
- 1, timeout=self.GENERAL_STATUS_TIMEOUT
236
+ 1, timeout=self.timeouts.general_status_timeout
208
237
  )
209
238
  return status
210
239
 
@@ -212,16 +241,20 @@ class EigerDetector(Device):
212
241
  assert self.detector_params is not None
213
242
  file_prefix = self.detector_params.full_filename
214
243
  status = self.odin.file_writer.file_path.set(
215
- self.detector_params.directory, timeout=self.GENERAL_STATUS_TIMEOUT
244
+ self.detector_params.directory, timeout=self.timeouts.general_status_timeout
216
245
  )
217
246
  status &= self.odin.file_writer.file_name.set(
218
- file_prefix, timeout=self.GENERAL_STATUS_TIMEOUT
247
+ file_prefix, timeout=self.timeouts.general_status_timeout
219
248
  )
220
249
  status &= await_value(
221
- self.odin.meta.file_name, file_prefix, timeout=self.GENERAL_STATUS_TIMEOUT
250
+ self.odin.meta.file_name,
251
+ file_prefix,
252
+ timeout=self.timeouts.general_status_timeout,
222
253
  )
223
254
  status &= await_value(
224
- self.odin.file_writer.id, file_prefix, timeout=self.GENERAL_STATUS_TIMEOUT
255
+ self.odin.file_writer.id,
256
+ file_prefix,
257
+ timeout=self.timeouts.general_status_timeout,
225
258
  )
226
259
  return status
227
260
 
@@ -231,19 +264,22 @@ class EigerDetector(Device):
231
264
  self.detector_params.detector_distance
232
265
  )
233
266
  status = self.cam.beam_center_x.set(
234
- beam_x_pixels, timeout=self.GENERAL_STATUS_TIMEOUT
267
+ beam_x_pixels, timeout=self.timeouts.general_status_timeout
235
268
  )
236
269
  status &= self.cam.beam_center_y.set(
237
- beam_y_pixels, timeout=self.GENERAL_STATUS_TIMEOUT
270
+ beam_y_pixels, timeout=self.timeouts.general_status_timeout
238
271
  )
239
272
  status &= self.cam.det_distance.set(
240
- self.detector_params.detector_distance, timeout=self.GENERAL_STATUS_TIMEOUT
273
+ self.detector_params.detector_distance,
274
+ timeout=self.timeouts.general_status_timeout,
241
275
  )
242
276
  status &= self.cam.omega_start.set(
243
- self.detector_params.omega_start, timeout=self.GENERAL_STATUS_TIMEOUT
277
+ self.detector_params.omega_start,
278
+ timeout=self.timeouts.general_status_timeout,
244
279
  )
245
280
  status &= self.cam.omega_incr.set(
246
- self.detector_params.omega_increment, timeout=self.GENERAL_STATUS_TIMEOUT
281
+ self.detector_params.omega_increment,
282
+ timeout=self.timeouts.general_status_timeout,
247
283
  )
248
284
  return status
249
285
 
@@ -259,7 +295,7 @@ class EigerDetector(Device):
259
295
  current_energy = self.cam.photon_energy.get()
260
296
  if abs(current_energy - energy) > tolerance:
261
297
  return self.cam.photon_energy.set(
262
- energy, timeout=self.GENERAL_STATUS_TIMEOUT
298
+ energy, timeout=self.timeouts.general_status_timeout
263
299
  )
264
300
  else:
265
301
  status = Status()
@@ -275,45 +311,46 @@ class EigerDetector(Device):
275
311
  assert self.detector_params is not None
276
312
  status = self.cam.num_images.set(
277
313
  self.detector_params.num_images_per_trigger,
278
- timeout=self.GENERAL_STATUS_TIMEOUT,
314
+ timeout=self.timeouts.general_status_timeout,
279
315
  )
280
316
  if self.detector_params.trigger_mode == TriggerMode.FREE_RUN:
281
317
  # The Eiger can't actually free run so we set a very large number of frames
282
318
  status &= self.cam.num_triggers.set(
283
- FREE_RUN_MAX_IMAGES, timeout=self.GENERAL_STATUS_TIMEOUT
319
+ FREE_RUN_MAX_IMAGES, timeout=self.timeouts.general_status_timeout
284
320
  )
285
321
  # Setting Odin to write 0 frames tells it to write until externally stopped
286
322
  status &= self.odin.file_writer.num_capture.set(
287
- 0, timeout=self.GENERAL_STATUS_TIMEOUT
323
+ 0, timeout=self.timeouts.general_status_timeout
288
324
  )
289
325
  elif self.detector_params.trigger_mode == TriggerMode.SET_FRAMES:
290
326
  status &= self.cam.num_triggers.set(
291
- self.detector_params.num_triggers, timeout=self.GENERAL_STATUS_TIMEOUT
327
+ self.detector_params.num_triggers,
328
+ timeout=self.timeouts.general_status_timeout,
292
329
  )
293
330
  status &= self.odin.file_writer.num_capture.set(
294
331
  self.detector_params.full_number_of_images,
295
- timeout=self.GENERAL_STATUS_TIMEOUT,
332
+ timeout=self.timeouts.general_status_timeout,
296
333
  )
297
334
 
298
335
  return status
299
336
 
300
337
  def _wait_for_odin_status(self) -> StatusBase:
301
338
  self.forward_bit_depth_to_filewriter()
302
- await_value(self.odin.meta.active, 1).wait(self.GENERAL_STATUS_TIMEOUT)
339
+ await_value(self.odin.meta.active, 1).wait(self.timeouts.general_status_timeout)
303
340
 
304
341
  status = self.odin.file_writer.capture.set(
305
- 1, timeout=self.GENERAL_STATUS_TIMEOUT
342
+ 1, timeout=self.timeouts.general_status_timeout
306
343
  )
307
344
  LOGGER.info("Eiger staging: awaiting odin metadata")
308
345
  status &= await_value(
309
- self.odin.meta.ready, 1, timeout=self.META_FILE_READY_TIMEOUT
346
+ self.odin.meta.ready, 1, timeout=self.timeouts.meta_file_ready_timeout
310
347
  )
311
348
  return status
312
349
 
313
350
  def _wait_fan_ready(self) -> StatusBase:
314
351
  self.filewriters_finished = self.odin.create_finished_status()
315
352
  LOGGER.info("Eiger staging: awaiting odin fan ready")
316
- return await_value(self.odin.fan.ready, 1, self.GENERAL_STATUS_TIMEOUT)
353
+ return await_value(self.odin.fan.ready, 1, self.timeouts.general_status_timeout)
317
354
 
318
355
  def _finish_arm(self) -> Status:
319
356
  LOGGER.info("Eiger staging: Finishing arming")
@@ -324,7 +361,7 @@ class EigerDetector(Device):
324
361
  def forward_bit_depth_to_filewriter(self):
325
362
  bit_depth = self.bit_depth.get()
326
363
  self.odin.file_writer.data_type.set(f"UInt{bit_depth}").wait(
327
- self.GENERAL_STATUS_TIMEOUT
364
+ self.timeouts.general_status_timeout
328
365
  )
329
366
 
330
367
  def change_dev_shm(self, enable_dev_shm: bool):
@@ -332,7 +369,7 @@ class EigerDetector(Device):
332
369
  return self.odin.fan.dev_shm_enable.set(1 if enable_dev_shm else 0)
333
370
 
334
371
  def disarm_detector(self):
335
- self.cam.acquire.set(0).wait(self.GENERAL_STATUS_TIMEOUT)
372
+ self.cam.acquire.set(0).wait(self.timeouts.general_status_timeout)
336
373
 
337
374
  def do_arming_chain(self) -> Status:
338
375
  functions_to_do_arm = []
@@ -355,7 +392,9 @@ class EigerDetector(Device):
355
392
  self.set_num_triggers_and_captures,
356
393
  lambda: await_value(self.stale_params, 0, 60),
357
394
  self._wait_for_odin_status,
358
- lambda: self.cam.acquire.set(1, timeout=self.GENERAL_STATUS_TIMEOUT),
395
+ lambda: self.cam.acquire.set(
396
+ 1, timeout=self.timeouts.general_status_timeout
397
+ ),
359
398
  self._wait_fan_ready,
360
399
  self._finish_arm,
361
400
  ]
@@ -15,7 +15,7 @@ from ophyd_async.core import (
15
15
  StandardReadable,
16
16
  wait_for_value,
17
17
  )
18
- from ophyd_async.epics.signal import (
18
+ from ophyd_async.epics.core import (
19
19
  epics_signal_r,
20
20
  epics_signal_rw,
21
21
  epics_signal_rw_rbv,
@@ -1,12 +1,10 @@
1
- from enum import Enum
1
+ from ophyd_async.core import StandardReadable, StrictEnum
2
+ from ophyd_async.epics.core import epics_signal_r
2
3
 
3
- from ophyd_async.core import StandardReadable
4
- from ophyd_async.epics.signal import epics_signal_r
5
4
 
6
-
7
- class FluorescenceDetectorControlState(Enum):
8
- OUT = 0
9
- IN = 1
5
+ class FluorescenceDetectorControlState(StrictEnum):
6
+ OUT = "Out"
7
+ IN = "In"
10
8
 
11
9
 
12
10
  class FluorescenceDetector(StandardReadable):