dls-dodal 1.29.4__py3-none-any.whl → 1.31.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 (103) hide show
  1. {dls_dodal-1.29.4.dist-info → dls_dodal-1.31.0.dist-info}/METADATA +29 -44
  2. dls_dodal-1.31.0.dist-info/RECORD +134 -0
  3. {dls_dodal-1.29.4.dist-info → dls_dodal-1.31.0.dist-info}/WHEEL +1 -1
  4. dls_dodal-1.31.0.dist-info/entry_points.txt +3 -0
  5. dodal/__init__.py +1 -4
  6. dodal/_version.py +2 -2
  7. dodal/beamline_specific_utils/i03.py +1 -4
  8. dodal/beamlines/__init__.py +7 -1
  9. dodal/beamlines/i03.py +34 -29
  10. dodal/beamlines/i04.py +39 -16
  11. dodal/beamlines/i13_1.py +66 -0
  12. dodal/beamlines/i22.py +22 -22
  13. dodal/beamlines/i24.py +1 -1
  14. dodal/beamlines/p38.py +21 -21
  15. dodal/beamlines/p45.py +18 -16
  16. dodal/beamlines/p99.py +61 -0
  17. dodal/beamlines/training_rig.py +64 -0
  18. dodal/cli.py +6 -3
  19. dodal/common/beamlines/beamline_parameters.py +7 -6
  20. dodal/common/beamlines/beamline_utils.py +15 -14
  21. dodal/common/maths.py +1 -3
  22. dodal/common/types.py +6 -5
  23. dodal/common/udc_directory_provider.py +39 -21
  24. dodal/common/visit.py +60 -62
  25. dodal/devices/CTAB.py +22 -17
  26. dodal/devices/aperture.py +1 -1
  27. dodal/devices/aperturescatterguard.py +139 -209
  28. dodal/devices/areadetector/adaravis.py +8 -6
  29. dodal/devices/areadetector/adsim.py +2 -3
  30. dodal/devices/areadetector/adutils.py +20 -12
  31. dodal/devices/areadetector/plugins/MJPG.py +2 -1
  32. dodal/devices/backlight.py +12 -1
  33. dodal/devices/cryostream.py +19 -7
  34. dodal/devices/dcm.py +1 -1
  35. dodal/devices/detector/__init__.py +13 -2
  36. dodal/devices/detector/det_dim_constants.py +2 -2
  37. dodal/devices/detector/det_dist_to_beam_converter.py +1 -1
  38. dodal/devices/detector/detector.py +33 -32
  39. dodal/devices/detector/detector_motion.py +38 -31
  40. dodal/devices/eiger.py +11 -15
  41. dodal/devices/eiger_odin.py +9 -10
  42. dodal/devices/fast_grid_scan.py +18 -27
  43. dodal/devices/fluorescence_detector_motion.py +13 -4
  44. dodal/devices/focusing_mirror.py +6 -6
  45. dodal/devices/hutch_shutter.py +4 -4
  46. dodal/devices/i22/dcm.py +5 -4
  47. dodal/devices/i22/fswitch.py +10 -6
  48. dodal/devices/i22/nxsas.py +55 -43
  49. dodal/devices/i24/aperture.py +1 -1
  50. dodal/devices/i24/beamstop.py +1 -1
  51. dodal/devices/i24/dcm.py +1 -1
  52. dodal/devices/i24/{I24_detector_motion.py → i24_detector_motion.py} +1 -1
  53. dodal/devices/i24/pmac.py +67 -12
  54. dodal/devices/ipin.py +7 -4
  55. dodal/devices/linkam3.py +12 -6
  56. dodal/devices/logging_ophyd_device.py +1 -1
  57. dodal/devices/motors.py +32 -6
  58. dodal/devices/oav/grid_overlay.py +1 -0
  59. dodal/devices/oav/microns_for_zoom_levels.json +1 -1
  60. dodal/devices/oav/oav_detector.py +2 -1
  61. dodal/devices/oav/oav_parameters.py +18 -10
  62. dodal/devices/oav/oav_to_redis_forwarder.py +129 -0
  63. dodal/devices/oav/pin_image_recognition/__init__.py +6 -6
  64. dodal/devices/oav/pin_image_recognition/utils.py +5 -6
  65. dodal/devices/oav/utils.py +2 -2
  66. dodal/devices/p99/__init__.py +0 -0
  67. dodal/devices/p99/sample_stage.py +43 -0
  68. dodal/devices/robot.py +31 -20
  69. dodal/devices/scatterguard.py +1 -1
  70. dodal/devices/scintillator.py +8 -5
  71. dodal/devices/slits.py +1 -1
  72. dodal/devices/smargon.py +4 -4
  73. dodal/devices/status.py +2 -31
  74. dodal/devices/tetramm.py +23 -19
  75. dodal/devices/thawer.py +5 -3
  76. dodal/devices/training_rig/__init__.py +0 -0
  77. dodal/devices/training_rig/sample_stage.py +10 -0
  78. dodal/devices/turbo_slit.py +1 -1
  79. dodal/devices/undulator.py +1 -1
  80. dodal/devices/undulator_dcm.py +6 -8
  81. dodal/devices/util/adjuster_plans.py +3 -3
  82. dodal/devices/util/epics_util.py +5 -7
  83. dodal/devices/util/lookup_tables.py +2 -3
  84. dodal/devices/util/save_panda.py +87 -0
  85. dodal/devices/util/test_utils.py +17 -0
  86. dodal/devices/webcam.py +3 -3
  87. dodal/devices/xbpm_feedback.py +1 -25
  88. dodal/devices/xspress3/xspress3.py +1 -1
  89. dodal/devices/zebra.py +15 -10
  90. dodal/devices/zebra_controlled_shutter.py +26 -11
  91. dodal/devices/zocalo/zocalo_interaction.py +10 -2
  92. dodal/devices/zocalo/zocalo_results.py +36 -19
  93. dodal/log.py +46 -15
  94. dodal/plans/check_topup.py +65 -10
  95. dodal/plans/data_session_metadata.py +8 -9
  96. dodal/plans/motor_util_plans.py +117 -0
  97. dodal/utils.py +65 -22
  98. dls_dodal-1.29.4.dist-info/RECORD +0 -125
  99. dls_dodal-1.29.4.dist-info/entry_points.txt +0 -2
  100. dodal/devices/beamstop.py +0 -8
  101. dodal/devices/qbpm1.py +0 -8
  102. {dls_dodal-1.29.4.dist-info → dls_dodal-1.31.0.dist-info}/LICENSE +0 -0
  103. {dls_dodal-1.29.4.dist-info → dls_dodal-1.31.0.dist-info}/top_level.txt +0 -0
@@ -5,6 +5,7 @@ import numpy as np
5
5
  from numpy.typing import NDArray
6
6
  from ophyd_async.core import (
7
7
  AsyncStatus,
8
+ HintedSignal,
8
9
  StandardReadable,
9
10
  observe_value,
10
11
  soft_signal_r_and_setter,
@@ -77,12 +78,13 @@ class PinTipDetection(StandardReadable):
77
78
  self.min_tip_height = soft_signal_rw(int, 5, name="min_tip_height")
78
79
  self.validity_timeout = soft_signal_rw(float, 5.0, name="validity_timeout")
79
80
 
80
- self.set_readable_signals(
81
- read=[
81
+ self.add_readables(
82
+ [
82
83
  self.triggered_tip,
83
84
  self.triggered_top_edge,
84
85
  self.triggered_bottom_edge,
85
86
  ],
87
+ wrapper=HintedSignal,
86
88
  )
87
89
 
88
90
  super().__init__(name=name)
@@ -134,9 +136,7 @@ class PinTipDetection(StandardReadable):
134
136
  location = sample_detection.processArray(array_data)
135
137
  end_time = time.time()
136
138
  LOGGER.debug(
137
- "Sample location detection took {}ms".format(
138
- (end_time - start_time) * 1000.0
139
- )
139
+ f"Sample location detection took {(end_time - start_time) * 1000.0}ms"
140
140
  )
141
141
  return location
142
142
 
@@ -154,7 +154,7 @@ class PinTipDetection(StandardReadable):
154
154
  location = await self._get_tip_and_edge_data(value)
155
155
  self._set_triggered_values(location)
156
156
  except Exception as e:
157
- LOGGER.warn(
157
+ LOGGER.warning(
158
158
  f"Failed to detect pin-tip location, will retry with next image: {e}"
159
159
  )
160
160
  else:
@@ -1,6 +1,7 @@
1
+ from collections.abc import Callable
1
2
  from dataclasses import dataclass
2
3
  from enum import Enum
3
- from typing import Callable, Final, Tuple
4
+ from typing import Final
4
5
 
5
6
  import cv2
6
7
  import numpy as np
@@ -103,7 +104,7 @@ class SampleLocation:
103
104
  edge_bottom: np.ndarray
104
105
 
105
106
 
106
- class MxSampleDetect(object):
107
+ class MxSampleDetect:
107
108
  def __init__(
108
109
  self,
109
110
  *,
@@ -161,7 +162,7 @@ class MxSampleDetect(object):
161
162
  @staticmethod
162
163
  def _first_and_last_nonzero_by_columns(
163
164
  arr: np.ndarray,
164
- ) -> Tuple[np.ndarray, np.ndarray]:
165
+ ) -> tuple[np.ndarray, np.ndarray]:
165
166
  """
166
167
  Finds the indexes of the first & last non-zero values by column in a 2d array.
167
168
 
@@ -243,9 +244,7 @@ class MxSampleDetect(object):
243
244
  bottom[x + 1 :] = NONE_VALUE
244
245
 
245
246
  LOGGER.info(
246
- "pin-tip detection: Successfully located pin tip at (x={}, y={})".format(
247
- tip_x, tip_y
248
- )
247
+ f"pin-tip detection: Successfully located pin tip at (x={tip_x}, y={tip_y})"
249
248
  )
250
249
  return SampleLocation(
251
250
  tip_x=tip_x, tip_y=tip_y, edge_bottom=bottom, edge_top=top
@@ -1,5 +1,5 @@
1
+ from collections.abc import Generator
1
2
  from enum import IntEnum
2
- from typing import Generator, Tuple
3
3
 
4
4
  import bluesky.plan_stubs as bps
5
5
  import numpy as np
@@ -10,7 +10,7 @@ from dodal.devices.oav.oav_detector import OAVConfigParams
10
10
  from dodal.devices.oav.pin_image_recognition import PinTipDetection
11
11
  from dodal.devices.smargon import Smargon
12
12
 
13
- Pixel = Tuple[int, int]
13
+ Pixel = tuple[int, int]
14
14
 
15
15
 
16
16
  class PinNotFoundException(Exception):
File without changes
@@ -0,0 +1,43 @@
1
+ from enum import Enum
2
+
3
+ from ophyd_async.core import Device
4
+ from ophyd_async.epics.signal import epics_signal_rw
5
+
6
+
7
+ class SampleAngleStage(Device):
8
+ def __init__(self, prefix: str, name: str):
9
+ self.theta = epics_signal_rw(
10
+ float, prefix + "WRITETHETA:RBV", prefix + "WRITETHETA"
11
+ )
12
+ self.roll = epics_signal_rw(
13
+ float, prefix + "WRITEROLL:RBV", prefix + "WRITEROLL"
14
+ )
15
+ self.pitch = epics_signal_rw(
16
+ float, prefix + "WRITEPITCH:RBV", prefix + "WRITEPITCH"
17
+ )
18
+ super().__init__(name=name)
19
+
20
+
21
+ class p99StageSelections(str, Enum):
22
+ Empty = "Empty"
23
+ Mn5um = "Mn 5um"
24
+ Fe = "Fe (empty)"
25
+ Co5um = "Co 5um"
26
+ Ni5um = "Ni 5um"
27
+ Cu5um = "Cu 5um"
28
+ Zn5um = "Zn 5um"
29
+ Zr = "Zr (empty)"
30
+ Mo = "Mo (empty)"
31
+ Rh = "Rh (empty)"
32
+ Pd = "Pd (empty)"
33
+ Ag = "Ag (empty)"
34
+ Cd25um = "Cd 25um"
35
+ W = "W (empty)"
36
+ Pt = "Pt (empty)"
37
+ User = "User"
38
+
39
+
40
+ class FilterMotor(Device):
41
+ def __init__(self, prefix: str, name: str):
42
+ self.user_setpoint = epics_signal_rw(p99StageSelections, prefix)
43
+ super().__init__(name=name)
dodal/devices/robot.py CHANGED
@@ -1,5 +1,5 @@
1
1
  import asyncio
2
- from asyncio import FIRST_COMPLETED, Task
2
+ from asyncio import FIRST_COMPLETED, CancelledError, Task
3
3
  from dataclasses import dataclass
4
4
  from enum import Enum
5
5
 
@@ -10,8 +10,7 @@ from ophyd_async.core import (
10
10
  set_and_wait_for_value,
11
11
  wait_for_value,
12
12
  )
13
- from ophyd_async.epics.signal import epics_signal_r, epics_signal_x
14
- from ophyd_async.epics.signal.signal import epics_signal_rw_rbv
13
+ from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw_rbv, epics_signal_x
15
14
 
16
15
  from dodal.log import LOGGER
17
16
 
@@ -45,6 +44,9 @@ class BartRobot(StandardReadable, Movable):
45
44
  LOAD_TIMEOUT = 60
46
45
  NO_PIN_ERROR_CODE = 25
47
46
 
47
+ # How far the gonio position can be out before loading will fail
48
+ LOAD_TOLERANCE_MM = 0.02
49
+
48
50
  def __init__(
49
51
  self,
50
52
  name: str,
@@ -74,19 +76,28 @@ class BartRobot(StandardReadable, Movable):
74
76
  await wait_for_value(self.error_code, self.NO_PIN_ERROR_CODE, None)
75
77
  raise RobotLoadFailed(self.NO_PIN_ERROR_CODE, "Pin was not detected")
76
78
 
77
- finished, unfinished = await asyncio.wait(
78
- [
79
- Task(raise_if_no_pin()),
80
- Task(
81
- wait_for_value(self.gonio_pin_sensor, PinMounted.PIN_MOUNTED, None)
82
- ),
83
- ],
84
- return_when=FIRST_COMPLETED,
85
- )
86
- for task in unfinished:
87
- task.cancel()
88
- for task in finished:
89
- await task
79
+ async def wfv():
80
+ await wait_for_value(self.gonio_pin_sensor, PinMounted.PIN_MOUNTED, None)
81
+
82
+ tasks = [
83
+ (Task(raise_if_no_pin())),
84
+ (Task(wfv())),
85
+ ]
86
+ try:
87
+ finished, unfinished = await asyncio.wait(
88
+ tasks,
89
+ return_when=FIRST_COMPLETED,
90
+ )
91
+ for task in unfinished:
92
+ task.cancel()
93
+ for task in finished:
94
+ await task
95
+ except CancelledError:
96
+ # If the outer enclosing task cancels after LOAD_TIMEOUT, this causes CancelledError to be raised
97
+ # in the current task, when it propagates to here we should cancel all pending tasks before bubbling up
98
+ for task in tasks:
99
+ task.cancel()
100
+ raise
90
101
 
91
102
  async def _load_pin_and_puck(self, sample_location: SampleLocation):
92
103
  LOGGER.info(f"Loading pin {sample_location}")
@@ -108,12 +119,12 @@ class BartRobot(StandardReadable, Movable):
108
119
  await self.pin_mounted_or_no_pin_found()
109
120
 
110
121
  @AsyncStatus.wrap
111
- async def set(self, sample_location: SampleLocation):
122
+ async def set(self, value: SampleLocation):
112
123
  try:
113
124
  await asyncio.wait_for(
114
- self._load_pin_and_puck(sample_location), timeout=self.LOAD_TIMEOUT
125
+ self._load_pin_and_puck(value), timeout=self.LOAD_TIMEOUT
115
126
  )
116
- except asyncio.TimeoutError:
127
+ except asyncio.TimeoutError as e:
117
128
  error_code = await self.error_code.get_value()
118
129
  error_string = await self.error_str.get_value()
119
- raise RobotLoadFailed(error_code, error_string)
130
+ raise RobotLoadFailed(int(error_code), error_string) from e
@@ -1,5 +1,5 @@
1
1
  from ophyd_async.core import StandardReadable
2
- from ophyd_async.epics.motion import Motor
2
+ from ophyd_async.epics.motor import Motor
3
3
 
4
4
 
5
5
  class Scatterguard(StandardReadable):
@@ -1,7 +1,10 @@
1
- from ophyd import Component as Cpt
2
- from ophyd import Device, EpicsMotor
1
+ from ophyd_async.core import StandardReadable
2
+ from ophyd_async.epics.motor import Motor
3
3
 
4
4
 
5
- class Scintillator(Device):
6
- y = Cpt(EpicsMotor, "-MO-SCIN-01:Y")
7
- z = Cpt(EpicsMotor, "-MO-SCIN-01:Z")
5
+ class Scintillator(StandardReadable):
6
+ def __init__(self, prefix: str, name: str = ""):
7
+ with self.add_children_as_readables():
8
+ self.y = Motor(prefix + "-MO-SCIN-01:Y")
9
+ self.z = Motor(prefix + "-MO-SCIN-01:Z")
10
+ super().__init__(name)
dodal/devices/slits.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from ophyd_async.core import StandardReadable
2
- from ophyd_async.epics.motion import Motor
2
+ from ophyd_async.epics.motor import Motor
3
3
 
4
4
 
5
5
  class Slits(StandardReadable):
dodal/devices/smargon.py CHANGED
@@ -1,13 +1,13 @@
1
- from collections.abc import Generator
1
+ from collections.abc import Collection, Generator
2
2
  from dataclasses import dataclass
3
3
  from enum import Enum
4
4
  from math import isclose
5
- from typing import Collection, cast
5
+ from typing import cast
6
6
 
7
7
  from bluesky import plan_stubs as bps
8
8
  from bluesky.utils import Msg
9
9
  from ophyd_async.core import AsyncStatus, Device, StandardReadable, wait_for_value
10
- from ophyd_async.epics.motion import Motor
10
+ from ophyd_async.epics.motor import Motor
11
11
  from ophyd_async.epics.signal import epics_signal_r
12
12
 
13
13
  from dodal.devices.util.epics_util import SetWhenEnabled
@@ -87,7 +87,7 @@ class XYZLimits:
87
87
  def position_valid(self, pos: Collection[float]) -> bool:
88
88
  return all(
89
89
  axis_limits.contains(value)
90
- for axis_limits, value in zip([self.x, self.y, self.z], pos)
90
+ for axis_limits, value in zip([self.x, self.y, self.z], pos, strict=False)
91
91
  )
92
92
 
93
93
 
dodal/devices/status.py CHANGED
@@ -1,41 +1,12 @@
1
- from typing import Any, TypeVar
1
+ from typing import Any
2
2
 
3
3
  from ophyd.status import SubscriptionStatus
4
4
 
5
- T = TypeVar("T")
6
-
7
5
 
8
6
  def await_value(
9
- subscribable: Any, expected_value: T, timeout: None | int = None
7
+ subscribable: Any, expected_value: object, timeout: None | int = None
10
8
  ) -> SubscriptionStatus:
11
9
  def value_is(value, **_):
12
10
  return value == expected_value
13
11
 
14
12
  return SubscriptionStatus(subscribable, value_is, timeout=timeout)
15
-
16
-
17
- def await_value_in_list(
18
- subscribable: Any, expected_value: list, timeout: None | int = None
19
- ) -> SubscriptionStatus:
20
- """Returns a status which is completed when the subscriptable contains a value
21
- within the expected_value list"""
22
-
23
- def value_is(value, **_):
24
- return value in expected_value
25
-
26
- if not isinstance(expected_value, list):
27
- raise TypeError(f"expected value {expected_value} is not a list")
28
- else:
29
- return SubscriptionStatus(subscribable, value_is, timeout=timeout)
30
-
31
-
32
- def await_approx_value(
33
- subscribable: Any,
34
- expected_value: T,
35
- deadband: float = 1e-09,
36
- timeout: None | int = None,
37
- ) -> SubscriptionStatus:
38
- def value_is_approx(value, **_):
39
- return abs(value - expected_value) <= deadband
40
-
41
- return SubscriptionStatus(subscribable, value_is_approx, timeout=timeout)
dodal/devices/tetramm.py CHANGED
@@ -1,23 +1,24 @@
1
1
  import asyncio
2
2
  from enum import Enum
3
- from typing import Sequence
4
3
 
5
4
  from bluesky.protocols import Hints
6
5
  from ophyd_async.core import (
7
6
  AsyncStatus,
7
+ DatasetDescriber,
8
8
  DetectorControl,
9
9
  DetectorTrigger,
10
10
  Device,
11
- DirectoryProvider,
12
- ShapeProvider,
11
+ PathProvider,
13
12
  StandardDetector,
14
13
  set_and_wait_for_value,
15
14
  soft_signal_r_and_setter,
16
15
  )
17
- from ophyd_async.epics.areadetector.utils import stop_busy_record
18
- from ophyd_async.epics.areadetector.writers import HDFWriter, NDFileHDF
19
- from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw
20
- from ophyd_async.epics.signal.signal import epics_signal_rw_rbv
16
+ from ophyd_async.epics.adcore import ADHDFWriter, NDFileHDFIO, stop_busy_record
17
+ from ophyd_async.epics.signal import (
18
+ epics_signal_r,
19
+ epics_signal_rw,
20
+ epics_signal_rw_rbv,
21
+ )
21
22
 
22
23
 
23
24
  class TetrammRange(str, Enum):
@@ -108,14 +109,14 @@ class TetrammController(DetectorControl):
108
109
  self.minimum_values_per_reading = minimum_values_per_reading
109
110
  self.readings_per_frame = readings_per_frame
110
111
 
111
- def get_deadtime(self, exposure: float) -> float:
112
+ def get_deadtime(self, exposure: float | None) -> float:
112
113
  # 2 internal clock cycles. Best effort approximation
113
114
  return 2 / self.base_sample_rate
114
115
 
115
116
  async def arm(
116
117
  self,
117
118
  num: int,
118
- trigger: DetectorTrigger,
119
+ trigger: DetectorTrigger = DetectorTrigger.edge_trigger,
119
120
  exposure: float | None = None,
120
121
  ) -> AsyncStatus:
121
122
  if exposure is None:
@@ -132,7 +133,7 @@ class TetrammController(DetectorControl):
132
133
  self._drv.averaging_time.set(exposure), self.set_exposure(exposure)
133
134
  )
134
135
 
135
- status = await set_and_wait_for_value(self._drv.acquire, 1)
136
+ status = await set_and_wait_for_value(self._drv.acquire, True)
136
137
 
137
138
  return status
138
139
 
@@ -150,7 +151,7 @@ class TetrammController(DetectorControl):
150
151
  )
151
152
 
152
153
  async def disarm(self):
153
- await stop_busy_record(self._drv.acquire, 0, timeout=1)
154
+ await stop_busy_record(self._drv.acquire, False, timeout=1)
154
155
 
155
156
  async def set_exposure(self, exposure: float):
156
157
  """Tries to set the exposure time of a single frame.
@@ -204,14 +205,17 @@ class TetrammController(DetectorControl):
204
205
  )
205
206
 
206
207
 
207
- class TetrammShapeProvider(ShapeProvider):
208
+ class TetrammDatasetDescriber(DatasetDescriber):
208
209
  max_channels = 11
209
210
 
210
211
  def __init__(self, controller: TetrammController) -> None:
211
212
  self.controller = controller
212
213
 
213
- async def __call__(self) -> Sequence[int]:
214
- return [self.max_channels, self.controller.readings_per_frame]
214
+ async def np_datatype(self) -> str:
215
+ return "<f8" # IEEE 754 double precision floating point
216
+
217
+ async def shape(self) -> tuple[int, int]:
218
+ return (self.max_channels, self.controller.readings_per_frame)
215
219
 
216
220
 
217
221
  # TODO: Support MeanValue signals https://github.com/DiamondLightSource/dodal/issues/337
@@ -219,13 +223,13 @@ class TetrammDetector(StandardDetector):
219
223
  def __init__(
220
224
  self,
221
225
  prefix: str,
222
- directory_provider: DirectoryProvider,
226
+ path_provider: PathProvider,
223
227
  name: str,
224
228
  type: str | None = None,
225
229
  **scalar_sigs: str,
226
230
  ) -> None:
227
231
  self.drv = TetrammDriver(prefix + "DRV:")
228
- self.hdf = NDFileHDF(prefix + "HDF5:")
232
+ self.hdf = NDFileHDFIO(prefix + "HDF5:")
229
233
  controller = TetrammController(self.drv)
230
234
  config_signals = [
231
235
  self.drv.values_per_reading,
@@ -239,11 +243,11 @@ class TetrammDetector(StandardDetector):
239
243
  self.type = None
240
244
  super().__init__(
241
245
  controller,
242
- HDFWriter(
246
+ ADHDFWriter(
243
247
  self.hdf,
244
- directory_provider,
248
+ path_provider,
245
249
  lambda: self.name,
246
- TetrammShapeProvider(controller),
250
+ TetrammDatasetDescriber(controller),
247
251
  **scalar_sigs,
248
252
  ),
249
253
  config_signals,
dodal/devices/thawer.py CHANGED
@@ -15,7 +15,7 @@ class ThawerStates(str, Enum):
15
15
  ON = "On"
16
16
 
17
17
 
18
- class ThawingTimer(Device):
18
+ class ThawingTimer(Device, Stoppable):
19
19
  def __init__(self, control_signal: SignalRW[ThawerStates]) -> None:
20
20
  self._control_signal = control_signal
21
21
  self._thawing_task: Task | None = None
@@ -32,7 +32,8 @@ class ThawingTimer(Device):
32
32
  finally:
33
33
  await self._control_signal.set(ThawerStates.OFF)
34
34
 
35
- async def stop(self):
35
+ @AsyncStatus.wrap
36
+ async def stop(self, *args, **kwargs):
36
37
  if self._thawing_task:
37
38
  self._thawing_task.cancel()
38
39
 
@@ -43,6 +44,7 @@ class Thawer(StandardReadable, Stoppable):
43
44
  self.thaw_for_time_s = ThawingTimer(self.control)
44
45
  super().__init__(name)
45
46
 
46
- async def stop(self):
47
+ @AsyncStatus.wrap
48
+ async def stop(self, *args, **kwargs):
47
49
  await self.thaw_for_time_s.stop()
48
50
  await self.control.set(ThawerStates.OFF)
File without changes
@@ -0,0 +1,10 @@
1
+ from ophyd_async.core import StandardReadable
2
+ from ophyd_async.epics.motor import Motor
3
+
4
+
5
+ class TrainingRigSampleStage(StandardReadable):
6
+ def __init__(self, prefix: str, name: str = ""):
7
+ with self.add_children_as_readables():
8
+ self.x = Motor(prefix + "X")
9
+ self.theta = Motor(prefix + "A")
10
+ super().__init__(name)
@@ -1,5 +1,5 @@
1
1
  from ophyd_async.core import Device
2
- from ophyd_async.epics.motion.motor import Motor
2
+ from ophyd_async.epics.motor import Motor
3
3
 
4
4
 
5
5
  class TurboSlit(Device):
@@ -1,7 +1,7 @@
1
1
  from enum import Enum
2
2
 
3
3
  from ophyd_async.core import ConfigSignal, StandardReadable, soft_signal_r_and_setter
4
- from ophyd_async.epics.motion import Motor
4
+ from ophyd_async.epics.motor import Motor
5
5
  from ophyd_async.epics.signal import epics_signal_r
6
6
 
7
7
  # The acceptable difference, in mm, between the undulator gap and the DCM
@@ -74,14 +74,12 @@ class UndulatorDCM(StandardReadable, Movable):
74
74
  daq_configuration_path + "/domain/beamlineParameters"
75
75
  )["DCM_Perp_Offset_FIXED"]
76
76
 
77
- def set(self, value: float) -> AsyncStatus:
78
- async def _set():
79
- await asyncio.gather(
80
- self._set_dcm_energy(value),
81
- self._set_undulator_gap_if_required(value),
82
- )
83
-
84
- return AsyncStatus(_set())
77
+ @AsyncStatus.wrap
78
+ async def set(self, value: float):
79
+ await asyncio.gather(
80
+ self._set_dcm_energy(value),
81
+ self._set_undulator_gap_if_required(value),
82
+ )
85
83
 
86
84
  async def _set_dcm_energy(self, energy_kev: float) -> None:
87
85
  access_level = await self.undulator.gap_access.get_value()
@@ -3,12 +3,12 @@ All the methods in this module return a bluesky plan generator that adjusts a va
3
3
  according to some criteria either via feedback, preset positions, lookup tables etc.
4
4
  """
5
5
 
6
- from typing import Callable, Generator
6
+ from collections.abc import Callable, Generator
7
7
 
8
8
  from bluesky import plan_stubs as bps
9
- from bluesky.run_engine import Msg
9
+ from bluesky.utils import Msg
10
10
  from ophyd.epics_motor import EpicsMotor
11
- from ophyd_async.epics.motion import Motor
11
+ from ophyd_async.epics.motor import Motor
12
12
 
13
13
  from dodal.log import LOGGER
14
14
 
@@ -1,5 +1,5 @@
1
+ from collections.abc import Callable, Sequence
1
2
  from functools import partial
2
- from typing import Callable, Sequence
3
3
 
4
4
  from bluesky.protocols import Movable
5
5
  from ophyd import Component, EpicsSignal
@@ -61,9 +61,9 @@ def run_functions_without_blocking(
61
61
  # Wrap each function by first checking the previous status and attaching a callback
62
62
  # to the next function in the chain
63
63
  def wrap_func(
64
- old_status: Status, current_func: Callable[[], StatusBase], next_func
64
+ old_status: Status | None, current_func: Callable[[], StatusBase], next_func
65
65
  ):
66
- if old_status.exception() is not None:
66
+ if old_status is not None and old_status.exception() is not None:
67
67
  set_global_exception_and_log(old_status)
68
68
  return
69
69
 
@@ -96,7 +96,7 @@ def run_functions_without_blocking(
96
96
  )
97
97
 
98
98
  # Wrap each function in reverse
99
- for num, func in enumerate(list(reversed(functions_to_chain))[1:-1]):
99
+ for func in list(reversed(functions_to_chain))[1:-1]:
100
100
  wrapped_funcs.append(
101
101
  partial(
102
102
  wrap_func,
@@ -105,10 +105,8 @@ def run_functions_without_blocking(
105
105
  )
106
106
  )
107
107
 
108
- starting_status = Status(done=True, success=True)
109
-
110
108
  # Initiate the chain of functions
111
- wrap_func(starting_status, functions_to_chain[0], wrapped_funcs[-1])
109
+ wrap_func(None, functions_to_chain[0], wrapped_funcs[-1])
112
110
  return full_status
113
111
 
114
112
 
@@ -3,9 +3,8 @@ All the public methods in this module return a lookup table of some kind that
3
3
  converts the source value s to a target value t for different values of s.
4
4
  """
5
5
 
6
- from collections.abc import Sequence
6
+ from collections.abc import Callable, Sequence
7
7
  from io import StringIO
8
- from typing import Callable
9
8
 
10
9
  import aiofiles
11
10
  import numpy as np
@@ -36,7 +35,7 @@ async def energy_distance_table(lookup_table_path: str) -> np.ndarray:
36
35
  def linear_interpolation_lut(filename: str) -> Callable[[float], float]:
37
36
  """Returns a callable that converts values by linear interpolation of lookup table values"""
38
37
  LOGGER.info(f"Using lookup table {filename}")
39
- s_and_t_vals = zip(*loadtxt(filename, comments=["#", "Units"]))
38
+ s_and_t_vals = zip(*loadtxt(filename, comments=["#", "Units"]), strict=False)
40
39
 
41
40
  s_values: Sequence
42
41
  t_values: Sequence