dls-dodal 1.36.0__py3-none-any.whl → 1.36.1a0__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 (37) hide show
  1. {dls_dodal-1.36.0.dist-info → dls_dodal-1.36.1a0.dist-info}/METADATA +33 -33
  2. {dls_dodal-1.36.0.dist-info → dls_dodal-1.36.1a0.dist-info}/RECORD +37 -33
  3. {dls_dodal-1.36.0.dist-info → dls_dodal-1.36.1a0.dist-info}/WHEEL +1 -1
  4. dodal/_version.py +2 -2
  5. dodal/beamlines/b01_1.py +16 -31
  6. dodal/beamlines/i22.py +124 -265
  7. dodal/beamlines/i24.py +56 -7
  8. dodal/beamlines/p38.py +16 -1
  9. dodal/beamlines/p99.py +22 -53
  10. dodal/beamlines/training_rig.py +16 -26
  11. dodal/cli.py +54 -8
  12. dodal/common/beamlines/beamline_utils.py +32 -2
  13. dodal/common/beamlines/device_helpers.py +2 -0
  14. dodal/devices/aperture.py +7 -0
  15. dodal/devices/aperturescatterguard.py +195 -79
  16. dodal/devices/dcm.py +5 -4
  17. dodal/devices/fast_grid_scan.py +21 -46
  18. dodal/devices/focusing_mirror.py +8 -3
  19. dodal/devices/i24/beam_center.py +12 -0
  20. dodal/devices/i24/focus_mirrors.py +60 -0
  21. dodal/devices/i24/pilatus_metadata.py +44 -0
  22. dodal/devices/linkam3.py +1 -1
  23. dodal/devices/motors.py +14 -10
  24. dodal/devices/oav/oav_detector.py +2 -2
  25. dodal/devices/oav/pin_image_recognition/__init__.py +4 -5
  26. dodal/devices/oav/utils.py +1 -0
  27. dodal/devices/p99/sample_stage.py +12 -16
  28. dodal/devices/pressure_jump_cell.py +299 -0
  29. dodal/devices/robot.py +1 -1
  30. dodal/devices/tetramm.py +1 -1
  31. dodal/devices/undulator.py +4 -1
  32. dodal/devices/undulator_dcm.py +3 -19
  33. dodal/devices/zocalo/zocalo_results.py +7 -7
  34. dodal/utils.py +151 -2
  35. {dls_dodal-1.36.0.dist-info → dls_dodal-1.36.1a0.dist-info}/LICENSE +0 -0
  36. {dls_dodal-1.36.0.dist-info → dls_dodal-1.36.1a0.dist-info}/entry_points.txt +0 -0
  37. {dls_dodal-1.36.0.dist-info → dls_dodal-1.36.1a0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,299 @@
1
+ import asyncio
2
+ from dataclasses import dataclass
3
+
4
+ from bluesky.protocols import HasName, Movable
5
+ from ophyd_async.core import (
6
+ AsyncStatus,
7
+ DeviceVector,
8
+ SignalR,
9
+ StandardReadable,
10
+ StandardReadableFormat,
11
+ StrictEnum,
12
+ )
13
+ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
14
+
15
+ OPENSEQ_PULSE_LENGTH = 0.2
16
+
17
+
18
+ class PumpState(StrictEnum):
19
+ MANUAL = "Manual"
20
+ AUTO_PRESSURE = "Auto Pressure"
21
+ AUTO_POSITION = "Auto Position"
22
+
23
+
24
+ class StopState(StrictEnum):
25
+ CONTINUE = "CONTINUE"
26
+ STOP = "STOP"
27
+
28
+
29
+ class FastValveControlRequest(StrictEnum):
30
+ OPEN = "Open"
31
+ CLOSE = "Close"
32
+ RESET = "Reset"
33
+ ARM = "Arm"
34
+ DISARM = "Disarm"
35
+
36
+
37
+ class ValveControlRequest(StrictEnum):
38
+ OPEN = "Open"
39
+ CLOSE = "Close"
40
+ RESET = "Reset"
41
+
42
+
43
+ class ValveOpenSeqRequest(StrictEnum):
44
+ INACTIVE = 0
45
+ OPEN_SEQ = 1
46
+
47
+
48
+ class PumpMotorDirectionState(StrictEnum):
49
+ EMPTY = ""
50
+ FORWARD = "Forward"
51
+ REVERSE = "Reverse"
52
+
53
+
54
+ class ValveState(StrictEnum):
55
+ FAULT = "Fault"
56
+ OPEN = "Open"
57
+ OPENING = "Opening"
58
+ CLOSED = "Closed"
59
+ CLOSING = "Closing"
60
+
61
+
62
+ class FastValveState(StrictEnum):
63
+ FAULT = "Fault"
64
+ OPEN = "Open"
65
+ OPEN_ARMED = "Open Armed"
66
+ CLOSED = "Closed"
67
+ CLOSED_ARMED = "Closed Armed"
68
+ NONE = "Unused"
69
+
70
+
71
+ @dataclass
72
+ class AllValvesControlState:
73
+ valve_1: ValveControlRequest | None = None
74
+ valve_3: ValveControlRequest | None = None
75
+ valve_5: FastValveControlRequest | None = None
76
+ valve_6: FastValveControlRequest | None = None
77
+
78
+
79
+ class AllValvesControl(StandardReadable, Movable):
80
+ """
81
+ valves 2, 4, 7, 8 are not controlled by the IOC,
82
+ as they are under manual control.
83
+ fast_valves: tuple[int, ...] = (5, 6)
84
+ slow_valves: tuple[int, ...] = (1, 3)
85
+ """
86
+
87
+ def __init__(
88
+ self,
89
+ prefix: str,
90
+ name: str = "",
91
+ fast_valves: tuple[int, ...] = (5, 6),
92
+ slow_valves: tuple[int, ...] = (1, 3),
93
+ ) -> None:
94
+ self.fast_valves = fast_valves
95
+ self.slow_valves = slow_valves
96
+ with self.add_children_as_readables():
97
+ self.valve_states: DeviceVector[SignalR[ValveState]] = DeviceVector(
98
+ {
99
+ i: epics_signal_r(ValveState, f"{prefix}V{i}:STA")
100
+ for i in self.slow_valves
101
+ }
102
+ )
103
+ self.fast_valve_states: DeviceVector[SignalR[FastValveState]] = (
104
+ DeviceVector(
105
+ {
106
+ i: epics_signal_r(FastValveState, f"{prefix}V{i}:STA")
107
+ for i in self.fast_valves
108
+ }
109
+ )
110
+ )
111
+
112
+ self.fast_valve_control: DeviceVector[FastValveControl] = DeviceVector(
113
+ {i: FastValveControl(f"{prefix}V{i}") for i in self.fast_valves}
114
+ )
115
+
116
+ self.valve_control: DeviceVector[ValveControl] = DeviceVector(
117
+ {i: ValveControl(f"{prefix}V{i}") for i in self.slow_valves}
118
+ )
119
+
120
+ super().__init__(name)
121
+
122
+ async def set_valve(
123
+ self,
124
+ valve: int,
125
+ value: ValveControlRequest | FastValveControlRequest,
126
+ ):
127
+ if valve in self.slow_valves and (isinstance(value, ValveControlRequest)):
128
+ if value == ValveControlRequest.OPEN:
129
+ await self.valve_control[valve].set(ValveOpenSeqRequest.OPEN_SEQ)
130
+ await asyncio.sleep(OPENSEQ_PULSE_LENGTH)
131
+ await self.valve_control[valve].set(ValveOpenSeqRequest.INACTIVE)
132
+ else:
133
+ await self.valve_control[valve].set(value)
134
+
135
+ elif valve in self.fast_valves and (isinstance(value, FastValveControlRequest)):
136
+ if value == FastValveControlRequest.OPEN:
137
+ await self.fast_valve_control[valve].set(ValveOpenSeqRequest.OPEN_SEQ)
138
+ await asyncio.sleep(OPENSEQ_PULSE_LENGTH)
139
+ await self.fast_valve_control[valve].set(ValveOpenSeqRequest.INACTIVE)
140
+ else:
141
+ await self.fast_valve_control[valve].set(value)
142
+
143
+ @AsyncStatus.wrap
144
+ async def set(self, value: AllValvesControlState):
145
+ await asyncio.gather(
146
+ *(
147
+ self.set_valve(int(i[-1]), value)
148
+ for i, value in value.__dict__.items()
149
+ if value is not None
150
+ )
151
+ )
152
+
153
+
154
+ class ValveControl(StandardReadable):
155
+ def __init__(self, prefix: str, name: str = "") -> None:
156
+ with self.add_children_as_readables():
157
+ self.close = epics_signal_rw(ValveControlRequest, prefix + ":CON")
158
+ self.open = epics_signal_rw(ValveOpenSeqRequest, prefix + ":OPENSEQ")
159
+
160
+ super().__init__(name)
161
+
162
+ def set(self, value: ValveControlRequest | ValveOpenSeqRequest) -> AsyncStatus:
163
+ set_status = None
164
+
165
+ if isinstance(value, ValveControlRequest):
166
+ set_status = self.close.set(value)
167
+ elif isinstance(value, ValveOpenSeqRequest):
168
+ set_status = self.open.set(value)
169
+
170
+ return set_status
171
+
172
+
173
+ class FastValveControl(StandardReadable):
174
+ def __init__(self, prefix: str, name: str = "") -> None:
175
+ with self.add_children_as_readables():
176
+ self.close = epics_signal_rw(FastValveControlRequest, prefix + ":CON")
177
+ self.open = epics_signal_rw(ValveOpenSeqRequest, prefix + ":OPENSEQ")
178
+
179
+ super().__init__(name)
180
+
181
+ def set(self, value: FastValveControlRequest | ValveOpenSeqRequest) -> AsyncStatus:
182
+ set_status = None
183
+
184
+ if isinstance(value, FastValveControlRequest):
185
+ set_status = self.close.set(value)
186
+ elif isinstance(value, ValveOpenSeqRequest):
187
+ set_status = self.open.set(value)
188
+
189
+ return set_status
190
+
191
+
192
+ class Pump(StandardReadable):
193
+ def __init__(self, prefix: str, name: str = "") -> None:
194
+ with self.add_children_as_readables():
195
+ self.pump_position = epics_signal_r(float, prefix + "POS")
196
+ self.pump_motor_direction = epics_signal_r(
197
+ PumpMotorDirectionState, prefix + "MTRDIR"
198
+ )
199
+ self.pump_speed = epics_signal_rw(
200
+ float, write_pv=prefix + "MSPEED", read_pv=prefix + "MSPEED_RBV"
201
+ )
202
+
203
+ with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
204
+ self.pump_mode = epics_signal_rw(PumpState, prefix + "SP:AUTO")
205
+
206
+ super().__init__(name)
207
+
208
+
209
+ class PressureTransducer(StandardReadable):
210
+ """
211
+ Pressure transducer for a high pressure X-ray cell.
212
+ This is the chamber and there are three of them.
213
+ 1 is the start, 3 is where the sample is.
214
+ NOTE: the distinction between the adc prefix and the cell prefix is kept here.
215
+
216
+ """
217
+
218
+ def __init__(
219
+ self,
220
+ prefix: str,
221
+ cell_prefix: str,
222
+ number: int,
223
+ name: str = "",
224
+ full_different_prefix_adc: str = "",
225
+ ) -> None:
226
+ final_prefix = f"{prefix}{cell_prefix}"
227
+ with self.add_children_as_readables():
228
+ self.omron_pressure = epics_signal_r(
229
+ float, f"{final_prefix}PP{number}:PRES"
230
+ )
231
+ self.omron_voltage = epics_signal_r(float, f"{final_prefix}PP{number}:RAW")
232
+ self.beckhoff_pressure = epics_signal_r(
233
+ float, f"{final_prefix}STATP{number}:MeanValue_RBV"
234
+ )
235
+ self.slow_beckhoff_voltage_readout = epics_signal_r(
236
+ float, f"{full_different_prefix_adc}CH1"
237
+ )
238
+
239
+ super().__init__(name)
240
+
241
+
242
+ class PressureJumpCellController(HasName):
243
+ def __init__(self, prefix: str, name: str = "") -> None:
244
+ self.stop = epics_signal_rw(StopState, f"{prefix}STOP")
245
+
246
+ self.target_pressure = epics_signal_rw(float, f"{prefix}TARGET")
247
+ self.timeout = epics_signal_rw(float, f"{prefix}TIMER.HIGH")
248
+ self.go = epics_signal_rw(bool, f"{prefix}GO")
249
+
250
+ ## Jump logic ##
251
+ self.start_pressure = epics_signal_rw(float, f"{prefix}JUMPF")
252
+ self.target_pressure = epics_signal_rw(float, f"{prefix}JUMPT")
253
+ self.jump_ready = epics_signal_rw(bool, f"{prefix}SETJUMP")
254
+
255
+ self._name = name
256
+ super().__init__()
257
+
258
+ @property
259
+ def name(self):
260
+ return self._name
261
+
262
+
263
+ class PressureJumpCell(StandardReadable):
264
+ """
265
+ High pressure X-ray cell, used to apply pressure or pressure jumps to a sample.
266
+ prefix: str
267
+ The prefix of beamline - SPECIAL - unusual that the cell prefix is computed separately
268
+ """
269
+
270
+ def __init__(
271
+ self,
272
+ prefix: str,
273
+ cell_prefix: str = "-HPXC-01:",
274
+ adc_prefix: str = "-ADC",
275
+ name: str = "",
276
+ ):
277
+ self.all_valves_control = AllValvesControl(f"{prefix}{cell_prefix}", name)
278
+ self.pump = Pump(f"{prefix}{cell_prefix}", name)
279
+
280
+ self.controller = PressureJumpCellController(
281
+ f"{prefix}{cell_prefix}CTRL:", name
282
+ )
283
+
284
+ with self.add_children_as_readables():
285
+ self.pressure_transducers: DeviceVector[PressureTransducer] = DeviceVector(
286
+ {
287
+ i: PressureTransducer(
288
+ prefix=prefix,
289
+ number=i,
290
+ cell_prefix=cell_prefix,
291
+ full_different_prefix_adc=f"{prefix}{adc_prefix}-0{i}:",
292
+ )
293
+ for i in [1, 2, 3]
294
+ }
295
+ )
296
+
297
+ self.cell_temperature = epics_signal_r(float, f"{prefix}{cell_prefix}TEMP")
298
+
299
+ super().__init__(name)
dodal/devices/robot.py CHANGED
@@ -42,7 +42,7 @@ class BartRobot(StandardReadable, Movable):
42
42
  """The sample changing robot."""
43
43
 
44
44
  # How long to wait for the robot if it is busy soaking/drying
45
- NOT_BUSY_TIMEOUT = 60
45
+ NOT_BUSY_TIMEOUT = 5 * 60
46
46
  # How long to wait for the actual load to happen
47
47
  LOAD_TIMEOUT = 60
48
48
  NO_PIN_ERROR_CODE = 25
dodal/devices/tetramm.py CHANGED
@@ -219,7 +219,7 @@ class TetrammDetector(StandardDetector):
219
219
  self,
220
220
  prefix: str,
221
221
  path_provider: PathProvider,
222
- name: str,
222
+ name: str = "",
223
223
  type: str | None = None,
224
224
  **scalar_sigs: str,
225
225
  ) -> None:
@@ -108,10 +108,13 @@ class Undulator(StandardReadable, Movable):
108
108
  """
109
109
  await self._set_undulator_gap(value)
110
110
 
111
- async def _set_undulator_gap(self, energy_kev: float) -> None:
111
+ async def raise_if_not_enabled(self):
112
112
  access_level = await self.gap_access.get_value()
113
113
  if access_level is UndulatorGapAccess.DISABLED and not TEST_MODE:
114
114
  raise AccessError("Undulator gap access is disabled. Contact Control Room")
115
+
116
+ async def _set_undulator_gap(self, energy_kev: float) -> None:
117
+ await self.raise_if_not_enabled()
115
118
  LOGGER.info(f"Setting undulator gap to {energy_kev:.2f} kev")
116
119
  target_gap = await self._get_gap_to_match_energy(energy_kev)
117
120
 
@@ -6,17 +6,10 @@ from ophyd_async.core import AsyncStatus, StandardReadable
6
6
  from dodal.common.beamlines.beamline_parameters import get_beamline_parameters
7
7
 
8
8
  from .dcm import DCM
9
- from .undulator import Undulator, UndulatorGapAccess
9
+ from .undulator import Undulator
10
10
 
11
11
  ENERGY_TIMEOUT_S: float = 30.0
12
12
 
13
- # Enable to allow testing when the beamline is down, do not change in production!
14
- TEST_MODE = False
15
-
16
-
17
- class AccessError(Exception):
18
- pass
19
-
20
13
 
21
14
  class UndulatorDCM(StandardReadable, Movable):
22
15
  """
@@ -61,17 +54,8 @@ class UndulatorDCM(StandardReadable, Movable):
61
54
 
62
55
  @AsyncStatus.wrap
63
56
  async def set(self, value: float):
57
+ await self.undulator.raise_if_not_enabled()
64
58
  await asyncio.gather(
65
- self._set_dcm_energy(value),
59
+ self.dcm.energy_in_kev.set(value, timeout=ENERGY_TIMEOUT_S),
66
60
  self.undulator.set(value),
67
61
  )
68
-
69
- async def _set_dcm_energy(self, energy_kev: float) -> None:
70
- access_level = await self.undulator.gap_access.get_value()
71
- if access_level is UndulatorGapAccess.DISABLED and not TEST_MODE:
72
- raise AccessError("Undulator gap access is disabled. Contact Control Room")
73
-
74
- await self.dcm.energy_in_kev.set(
75
- energy_kev,
76
- timeout=ENERGY_TIMEOUT_S,
77
- )
@@ -12,8 +12,8 @@ import workflows.transport
12
12
  from bluesky.protocols import Triggerable
13
13
  from bluesky.utils import Msg
14
14
  from deepdiff import DeepDiff
15
- from numpy.typing import NDArray
16
15
  from ophyd_async.core import (
16
+ Array1D,
17
17
  AsyncStatus,
18
18
  StandardReadable,
19
19
  StandardReadableFormat,
@@ -133,22 +133,22 @@ class ZocaloResults(StandardReadable, Triggerable):
133
133
  self.use_cpu_and_gpu = use_cpu_and_gpu
134
134
 
135
135
  self.centre_of_mass, self._com_setter = soft_signal_r_and_setter(
136
- NDArray[np.uint64], name="centre_of_mass"
136
+ Array1D[np.uint64], name="centre_of_mass"
137
137
  )
138
138
  self.bounding_box, self._bounding_box_setter = soft_signal_r_and_setter(
139
- NDArray[np.uint64], name="bounding_box"
139
+ Array1D[np.uint64], name="bounding_box"
140
140
  )
141
141
  self.max_voxel, self._max_voxel_setter = soft_signal_r_and_setter(
142
- NDArray[np.uint64], name="max_voxel"
142
+ Array1D[np.uint64], name="max_voxel"
143
143
  )
144
144
  self.max_count, self._max_count_setter = soft_signal_r_and_setter(
145
- NDArray[np.uint64], name="max_count"
145
+ Array1D[np.uint64], name="max_count"
146
146
  )
147
147
  self.n_voxels, self._n_voxels_setter = soft_signal_r_and_setter(
148
- NDArray[np.uint64], name="n_voxels"
148
+ Array1D[np.uint64], name="n_voxels"
149
149
  )
150
150
  self.total_count, self._total_count_setter = soft_signal_r_and_setter(
151
- NDArray[np.uint64], name="total_count"
151
+ Array1D[np.uint64], name="total_count"
152
152
  )
153
153
  self.ispyb_dcid, self._ispyb_dcid_setter = soft_signal_r_and_setter(
154
154
  int, name="ispyb_dcid"
dodal/utils.py CHANGED
@@ -1,3 +1,4 @@
1
+ import functools
1
2
  import importlib
2
3
  import inspect
3
4
  import os
@@ -6,13 +7,14 @@ import socket
6
7
  import string
7
8
  from collections.abc import Callable, Iterable, Mapping
8
9
  from dataclasses import dataclass
9
- from functools import wraps
10
+ from functools import update_wrapper, wraps
10
11
  from importlib import import_module
11
12
  from inspect import signature
12
13
  from os import environ
13
14
  from types import ModuleType
14
15
  from typing import (
15
16
  Any,
17
+ Generic,
16
18
  Protocol,
17
19
  TypeGuard,
18
20
  TypeVar,
@@ -35,6 +37,7 @@ from bluesky.protocols import (
35
37
  Triggerable,
36
38
  WritesExternalAssets,
37
39
  )
40
+ from bluesky.run_engine import call_in_bluesky_event_loop
38
41
  from ophyd.device import Device as OphydV1Device
39
42
  from ophyd_async.core import Device as OphydV2Device
40
43
 
@@ -99,6 +102,8 @@ class BeamlinePrefix:
99
102
 
100
103
 
101
104
  T = TypeVar("T", bound=AnyDevice)
105
+ D = TypeVar("D", bound=OphydV2Device)
106
+ SkipType = bool | Callable[[], bool]
102
107
 
103
108
 
104
109
  def skip_device(precondition=lambda: True):
@@ -114,6 +119,91 @@ def skip_device(precondition=lambda: True):
114
119
  return decorator
115
120
 
116
121
 
122
+ class DeviceInitializationController(Generic[D]):
123
+ def __init__(
124
+ self,
125
+ factory: Callable[[], D],
126
+ use_factory_name: bool,
127
+ timeout: float,
128
+ mock: bool,
129
+ skip: SkipType,
130
+ ):
131
+ self._factory: Callable[[], D] = functools.cache(factory)
132
+ self._use_factory_name = use_factory_name
133
+ self._timeout = timeout
134
+ self._mock = mock
135
+ self._skip = skip
136
+ update_wrapper(self, factory)
137
+
138
+ @property
139
+ def skip(self) -> bool:
140
+ return self._skip() if callable(self._skip) else self._skip
141
+
142
+ def cache_clear(self) -> None:
143
+ """Clears the controller's internal cached instance of the device, if present.
144
+ Noop if not."""
145
+
146
+ # Functools adds the cache_clear function via setattr so the type checker
147
+ # does not pick it up.
148
+ self._factory.cache_clear() # type: ignore
149
+
150
+ def __call__(
151
+ self,
152
+ connect_immediately: bool = False,
153
+ name: str | None = None,
154
+ connection_timeout: float | None = None,
155
+ mock: bool | None = None,
156
+ ) -> D:
157
+ """Returns an instance of the Device the wrapped factory produces: the same
158
+ instance will be returned if this method is called multiple times, and arguments
159
+ may be passed to override this Controller's configuration.
160
+ Once the device is connected, the value of mock must be consistent, or connect
161
+ must be False.
162
+
163
+
164
+ Args:
165
+ connect_immediately (bool, default False): whether to call connect on the
166
+ device before returning it- connect is idempotent for ophyd-async devices.
167
+ Not connecting to the device allows for the instance to be created prior
168
+ to the RunEngine event loop being configured or for connect to be called
169
+ lazily e.g. by the `ensure_connected` stub.
170
+ name (str | None, optional): an override name to give the device, which is
171
+ also used to name its children. Defaults to None, which does not name the
172
+ device unless the device has no name and this Controller is configured to
173
+ use_factory_name, which propagates the name of the wrapped factory
174
+ function to the device instance.
175
+ connection_timeout (float | None, optional): an override timeout length in
176
+ seconds for the connect method, if it is called. Defaults to None, which
177
+ defers to the timeout configured for this Controller: the default uses
178
+ ophyd_async's DEFAULT_TIMEOUT.
179
+ mock (bool | None, optional): overrides whether to connect to Mock signal
180
+ backends, if connect is called. Defaults to None, which uses the mock
181
+ parameter of this Controller. This value must be used consistently when
182
+ connect is called on the Device.
183
+
184
+ Returns:
185
+ D: a singleton instance of the Device class returned by the wrapped factory.
186
+ """
187
+ device = self._factory()
188
+
189
+ if connect_immediately:
190
+ call_in_bluesky_event_loop(
191
+ device.connect(
192
+ timeout=connection_timeout
193
+ if connection_timeout is not None
194
+ else self._timeout,
195
+ mock=mock if mock is not None else self._mock,
196
+ )
197
+ )
198
+
199
+ if name:
200
+ device.set_name(name)
201
+ elif not device.name and self._use_factory_name:
202
+ device.set_name(self._factory.__name__)
203
+
204
+ return device
205
+
206
+
117
207
  def make_device(
118
208
  module: str | ModuleType,
119
209
  device_name: str,
@@ -206,7 +296,33 @@ def invoke_factories(
206
296
  dependent_name = leaves.pop()
207
297
  params = {name: devices[name] for name in dependencies[dependent_name]}
208
298
  try:
209
- devices[dependent_name] = factories[dependent_name](**params, **kwargs)
299
+ factory = factories[dependent_name]
300
+ if isinstance(factory, DeviceInitializationController):
301
+ # For now we translate the old-style parameters that
302
+ # device_instantiation expects. Once device_instantiation is gone and
303
+ # replaced with DeviceInitializationController we can formalise the
304
+ # API of make_all_devices and make these parameters explicit.
305
+ # https://github.com/DiamondLightSource/dodal/issues/844
306
+ mock = kwargs.get(
307
+ "mock",
308
+ kwargs.get(
309
+ "fake_with_ophyd_sim",
310
+ False,
311
+ ),
312
+ )
313
+ connect_immediately = kwargs.get(
314
+ "connect_immediately",
315
+ kwargs.get(
316
+ "wait_for_connection",
317
+ False,
318
+ ),
319
+ )
320
+ devices[dependent_name] = factory(
321
+ mock=mock,
322
+ connect_immediately=connect_immediately,
323
+ )
324
+ else:
325
+ devices[dependent_name] = factory(**params, **kwargs)
210
326
  except Exception as e:
211
327
  exceptions[dependent_name] = e
212
328
 
@@ -268,6 +384,8 @@ def collect_factories(
268
384
 
269
385
 
270
386
  def _is_device_skipped(func: AnyDeviceFactory) -> bool:
387
+ if isinstance(func, DeviceInitializationController):
388
+ return func.skip
271
389
  return getattr(func, "__skip__", False)
272
390
 
273
391
 
@@ -301,6 +419,37 @@ def is_v1_device_type(obj: type[Any]) -> bool:
301
419
  return is_class and follows_protocols and not is_v2_device_type(obj)
302
420
 
303
421
 
422
+ def filter_ophyd_devices(
423
+ devices: Mapping[str, AnyDevice],
424
+ ) -> tuple[Mapping[str, OphydV1Device], Mapping[str, OphydV2Device]]:
425
+ """
426
+ Split a dictionary of ophyd and ophyd-async devices
427
+ (i.e. the output of make_all_devices) into 2 separate dictionaries of the
428
+ different types. Useful when special handling is needed for each type of device.
429
+
430
+ Args:
431
+ devices: Dictionary of device name to ophyd or ophyd-async device.
432
+
433
+ Raises:
434
+ ValueError: If anything in the dictionary doesn't come from either library.
435
+
436
+ Returns:
437
+ Tuple of two dictionaries, one mapping names to ophyd devices and one mapping
438
+ names to ophyd-async devices.
439
+ """
440
+
441
+ ophyd_devices = {}
442
+ ophyd_async_devices = {}
443
+ for name, device in devices.items():
444
+ if isinstance(device, OphydV1Device):
445
+ ophyd_devices[name] = device
446
+ elif isinstance(device, OphydV2Device):
447
+ ophyd_async_devices[name] = device
448
+ else:
449
+ raise ValueError(f"{name}: {device} is not an ophyd or ophyd-async device")
450
+ return ophyd_devices, ophyd_async_devices
451
+
452
+
304
453
  def get_beamline_based_on_environment_variable() -> ModuleType:
305
454
  """
306
455
  Gets the dodal module for the current beamline, as specified by the