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,21 +1,19 @@
1
- from enum import Enum
2
-
3
1
  from ophyd_async.core import (
4
2
  AsyncStatus,
5
- ConfigSignal,
6
3
  Device,
7
4
  DeviceVector,
8
- HintedSignal,
9
5
  StandardReadable,
6
+ StandardReadableFormat,
7
+ StrictEnum,
10
8
  observe_value,
11
9
  soft_signal_r_and_setter,
12
10
  )
13
- from ophyd_async.epics.motor import Motor
14
- from ophyd_async.epics.signal import (
11
+ from ophyd_async.epics.core import (
15
12
  epics_signal_r,
16
13
  epics_signal_rw,
17
14
  epics_signal_x,
18
15
  )
16
+ from ophyd_async.epics.motor import Motor
19
17
 
20
18
  from dodal.log import LOGGER
21
19
 
@@ -25,20 +23,20 @@ VOLTAGE_POLLING_DELAY_S = 0.5
25
23
  DEFAULT_SETTLE_TIME_S = 60
26
24
 
27
25
 
28
- class MirrorType(str, Enum):
26
+ class MirrorType(StrictEnum):
29
27
  """See https://manual.nexusformat.org/classes/base_classes/NXmirror.html"""
30
28
 
31
29
  SINGLE = "single"
32
30
  MULTI = "multi"
33
31
 
34
32
 
35
- class MirrorStripe(str, Enum):
33
+ class MirrorStripe(StrictEnum):
36
34
  RHODIUM = "Rhodium"
37
35
  BARE = "Bare"
38
36
  PLATINUM = "Platinum"
39
37
 
40
38
 
41
- class MirrorVoltageDemand(str, Enum):
39
+ class MirrorVoltageDemand(StrictEnum):
42
40
  N_A = "N/A"
43
41
  OK = "OK"
44
42
  FAIL = "FAIL"
@@ -151,8 +149,11 @@ class FocusingMirror(StandardReadable):
151
149
  # regardless of orientation of the mirror
152
150
  self.incident_angle = Motor(prefix + "PITCH")
153
151
 
154
- self.add_readables([self.incident_angle.user_readback], wrapper=HintedSignal)
155
- self.add_readables([self.type], wrapper=ConfigSignal)
152
+ self.add_readables(
153
+ [self.incident_angle.user_readback],
154
+ format=StandardReadableFormat.HINTED_SIGNAL,
155
+ )
156
+ self.add_readables([self.type], format=StandardReadableFormat.CONFIG_SIGNAL)
156
157
  super().__init__(name)
157
158
 
158
159
 
@@ -1,13 +1,12 @@
1
- from enum import Enum
2
-
3
1
  from bluesky.protocols import Movable
4
2
  from ophyd_async.core import (
5
3
  DEFAULT_TIMEOUT,
6
4
  AsyncStatus,
7
5
  StandardReadable,
6
+ StrictEnum,
8
7
  wait_for_value,
9
8
  )
10
- from ophyd_async.epics.signal import epics_signal_r, epics_signal_w
9
+ from ophyd_async.epics.core import epics_signal_r, epics_signal_w
11
10
 
12
11
  HUTCH_SAFE_FOR_OPERATIONS = 0 # Hutch is locked and can't be entered
13
12
 
@@ -16,13 +15,13 @@ class ShutterNotSafeToOperateError(Exception):
16
15
  pass
17
16
 
18
17
 
19
- class ShutterDemand(str, Enum):
18
+ class ShutterDemand(StrictEnum):
20
19
  OPEN = "Open"
21
20
  CLOSE = "Close"
22
21
  RESET = "Reset"
23
22
 
24
23
 
25
- class ShutterState(str, Enum):
24
+ class ShutterState(StrictEnum):
26
25
  FAULT = "Fault"
27
26
  OPEN = "Open"
28
27
  OPENING = "Opening"
@@ -2,14 +2,15 @@ import asyncio
2
2
  import csv
3
3
  from dataclasses import dataclass
4
4
  from pathlib import Path
5
- from typing import Any
5
+ from typing import Any, SupportsFloat
6
6
 
7
7
  import numpy as np
8
8
  from bluesky.protocols import Movable
9
9
  from ophyd_async.core import (
10
10
  AsyncStatus,
11
- HintedSignal,
11
+ Reference,
12
12
  StandardReadable,
13
+ StandardReadableFormat,
13
14
  soft_signal_r_and_setter,
14
15
  soft_signal_rw,
15
16
  )
@@ -118,14 +119,15 @@ class I10Apple2(Apple2):
118
119
  name=name,
119
120
  )
120
121
  with self.add_children_as_readables():
121
- self.id_jaw_phase = id_jaw_phase
122
+ self.id_jaw_phase = Reference(id_jaw_phase)
122
123
 
123
124
  @AsyncStatus.wrap
124
- async def set(self, value: float) -> None:
125
+ async def set(self, value: SupportsFloat) -> None:
125
126
  """
126
127
  Check polarisation state and use it together with the energy(value)
127
128
  to calculate the required gap and phases before setting it.
128
129
  """
130
+ value = float(value)
129
131
  if self.pol is None:
130
132
  LOGGER.warning("Polarisation not set attempting to read from hardware")
131
133
  pol, phase = await self.determinePhaseFromHardware()
@@ -146,8 +148,8 @@ class I10Apple2(Apple2):
146
148
  LOGGER.info(f"Setting polarisation to {self.pol}, with {id_set_val}")
147
149
  await self._set(value=id_set_val, energy=value)
148
150
  if self.pol != "la":
149
- await self.id_jaw_phase.set(0)
150
- await self.id_jaw_phase.set_move.set(1)
151
+ await self.id_jaw_phase().set(0)
152
+ await self.id_jaw_phase().set_move.set(1)
151
153
 
152
154
  def update_lookuptable(self):
153
155
  """
@@ -195,18 +197,17 @@ class I10Apple2PGM(StandardReadable, Movable):
195
197
  New device name.
196
198
  """
197
199
  super().__init__(name=name)
198
- with self.add_children_as_readables():
199
- self.id = id
200
- self.pgm = pgm
201
- with self.add_children_as_readables(HintedSignal):
200
+ self.id_ref = Reference(id)
201
+ self.pgm_ref = Reference(pgm)
202
+ with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
202
203
  self.energy_offset = soft_signal_rw(float, initial_value=0)
203
204
 
204
205
  @AsyncStatus.wrap
205
206
  async def set(self, value: float) -> None:
206
207
  LOGGER.info(f"Moving f{self.name} energy to {value}.")
207
208
  await asyncio.gather(
208
- self.id.set(value=value + await self.energy_offset.get_value()),
209
- self.pgm.energy.set(value),
209
+ self.id_ref().set(value=value + await self.energy_offset.get_value()),
210
+ self.pgm_ref().energy.set(value),
210
211
  )
211
212
 
212
213
 
@@ -273,22 +274,22 @@ class LinearArbitraryAngle(StandardReadable, Movable):
273
274
  polynomial parameters highest power first.
274
275
  """
275
276
  super().__init__(name=name)
276
- with self.add_children_as_readables():
277
- self.id = id
277
+ self.id_ref = Reference(id)
278
278
  self.jaw_phase_from_angle = np.poly1d(jaw_phase_poly_param)
279
279
  self.angle_threshold_deg = angle_threshold_deg
280
280
  self.jaw_phase_limit = jaw_phase_limit
281
- with self.add_children_as_readables(HintedSignal):
281
+ with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
282
282
  self.angle, self._angle_set = soft_signal_r_and_setter(
283
283
  float, initial_value=None
284
284
  )
285
285
 
286
286
  @AsyncStatus.wrap
287
- async def set(self, value: float) -> None:
288
- pol = self.id.pol
287
+ async def set(self, value: SupportsFloat) -> None:
288
+ value = float(value)
289
+ pol = self.id_ref().pol
289
290
  if pol != "la":
290
291
  raise RuntimeError(
291
- f"Angle control is not available in polarisation {pol} with {self.id.name}"
292
+ f"Angle control is not available in polarisation {pol} with {self.id_ref().name}"
292
293
  )
293
294
  # Moving to real angle which is 210 to 30.
294
295
  alpha_real = value if value > self.angle_threshold_deg else value + ALPHA_OFFSET
@@ -298,7 +299,7 @@ class LinearArbitraryAngle(StandardReadable, Movable):
298
299
  f"jaw_phase position for angle ({value}) is outside permitted range"
299
300
  f" [-{self.jaw_phase_limit}, {self.jaw_phase_limit}]"
300
301
  )
301
- await self.id.id_jaw_phase.set(jaw_phase)
302
+ await self.id_ref().id_jaw_phase().set(jaw_phase)
302
303
  self._angle_set(value)
303
304
 
304
305
 
@@ -1,7 +1,7 @@
1
- from enum import Enum
1
+ from ophyd_async.core import StrictEnum
2
2
 
3
3
 
4
- class I10Grating(str, Enum):
4
+ class I10Grating(StrictEnum):
5
5
  Au400 = "400 line/mm Au"
6
6
  Si400 = "400 line/mm Si"
7
7
  Au1200 = "1200 line/mm Au"
dodal/devices/i22/dcm.py CHANGED
@@ -1,32 +1,24 @@
1
1
  import time
2
- from collections.abc import Sequence
3
- from dataclasses import dataclass
4
- from typing import Literal
5
2
 
3
+ import numpy as np
6
4
  from bluesky.protocols import Reading
7
5
  from event_model.documents.event_descriptor import DataKey
8
- from ophyd_async.core import ConfigSignal, StandardReadable, soft_signal_r_and_setter
6
+ from ophyd_async.core import (
7
+ Array1D,
8
+ StandardReadable,
9
+ StandardReadableFormat,
10
+ soft_signal_r_and_setter,
11
+ )
12
+ from ophyd_async.epics.core import epics_signal_r
9
13
  from ophyd_async.epics.motor import Motor
10
- from ophyd_async.epics.signal import epics_signal_r
14
+
15
+ from dodal.common.crystal_metadata import CrystalMetadata
11
16
 
12
17
  # Conversion constant for energy and wavelength, taken from the X-Ray data booklet
13
18
  # Converts between energy in KeV and wavelength in angstrom
14
19
  _CONVERSION_CONSTANT = 12.3984
15
20
 
16
21
 
17
- @dataclass(frozen=True, unsafe_hash=True)
18
- class CrystalMetadata:
19
- """
20
- Metadata used in the NeXus format,
21
- see https://manual.nexusformat.org/classes/base_classes/NXcrystal.html
22
- """
23
-
24
- usage: Literal["Bragg", "Laue"] | None = None
25
- type: str | None = None
26
- reflection: tuple[int, int, int] | None = None
27
- d_spacing: tuple[float, str] | None = None
28
-
29
-
30
22
  class DoubleCrystalMonochromator(StandardReadable):
31
23
  """
32
24
  A double crystal monochromator (DCM), used to select the energy of the beam.
@@ -40,8 +32,8 @@ class DoubleCrystalMonochromator(StandardReadable):
40
32
  def __init__(
41
33
  self,
42
34
  temperature_prefix: str,
43
- crystal_1_metadata: CrystalMetadata | None = None,
44
- crystal_2_metadata: CrystalMetadata | None = None,
35
+ crystal_1_metadata: CrystalMetadata,
36
+ crystal_2_metadata: CrystalMetadata,
45
37
  prefix: str = "",
46
38
  name: str = "",
47
39
  ) -> None:
@@ -69,61 +61,37 @@ class DoubleCrystalMonochromator(StandardReadable):
69
61
 
70
62
  # Soft metadata
71
63
  # If supplied include crystal details in output of read_configuration
72
- crystal_1_metadata = crystal_1_metadata or CrystalMetadata()
73
- crystal_2_metadata = crystal_2_metadata or CrystalMetadata()
74
- with self.add_children_as_readables(ConfigSignal):
75
- if crystal_1_metadata.usage is not None:
76
- self.crystal_1_usage, _ = soft_signal_r_and_setter(
77
- str, initial_value=crystal_1_metadata.usage
78
- )
79
- else:
80
- self.crystal_1_usage = None
81
- if crystal_1_metadata.type is not None:
82
- self.crystal_1_type, _ = soft_signal_r_and_setter(
83
- str, initial_value=crystal_1_metadata.type
84
- )
85
- else:
86
- self.crystal_1_type = None
87
- if crystal_1_metadata.reflection is not None:
88
- self.crystal_1_reflection, _ = soft_signal_r_and_setter(
89
- Sequence[int], initial_value=list(crystal_1_metadata.reflection)
90
- )
91
- else:
92
- self.crystal_1_reflection = None
93
- if crystal_1_metadata.d_spacing is not None:
94
- self.crystal_1_d_spacing, _ = soft_signal_r_and_setter(
95
- float,
96
- initial_value=crystal_1_metadata.d_spacing[0],
97
- units=crystal_1_metadata.d_spacing[1],
98
- )
99
- else:
100
- self.crystal_1_d_spacing = None
101
- if crystal_2_metadata.usage is not None:
102
- self.crystal_2_usage, _ = soft_signal_r_and_setter(
103
- str, initial_value=crystal_2_metadata.usage
104
- )
105
- else:
106
- self.crystal_2_usage = None
107
- if crystal_2_metadata.type is not None:
108
- self.crystal_2_type, _ = soft_signal_r_and_setter(
109
- str, initial_value=crystal_2_metadata.type
110
- )
111
- else:
112
- self.crystal_2_type = None
113
- if crystal_2_metadata.reflection is not None:
114
- self.crystal_2_reflection, _ = soft_signal_r_and_setter(
115
- Sequence[int], initial_value=list(crystal_2_metadata.reflection)
116
- )
117
- else:
118
- self.crystal_2_reflection = None
119
- if crystal_2_metadata.d_spacing is not None:
120
- self.crystal_2_d_spacing, _ = soft_signal_r_and_setter(
121
- float,
122
- initial_value=crystal_2_metadata.d_spacing[0],
123
- units=crystal_2_metadata.d_spacing[1],
124
- )
125
- else:
126
- self.crystal_2_d_spacing = None
64
+ with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
65
+ self.crystal_1_usage, _ = soft_signal_r_and_setter(
66
+ str, initial_value=crystal_1_metadata.usage
67
+ )
68
+ self.crystal_1_type, _ = soft_signal_r_and_setter(
69
+ str, initial_value=crystal_1_metadata.type
70
+ )
71
+ self.crystal_1_reflection, _ = soft_signal_r_and_setter(
72
+ Array1D[np.int32],
73
+ initial_value=np.array(crystal_1_metadata.reflection),
74
+ )
75
+ self.crystal_1_d_spacing, _ = soft_signal_r_and_setter(
76
+ float,
77
+ initial_value=crystal_1_metadata.d_spacing[0],
78
+ units=crystal_1_metadata.d_spacing[1],
79
+ )
80
+ self.crystal_2_usage, _ = soft_signal_r_and_setter(
81
+ str, initial_value=crystal_2_metadata.usage
82
+ )
83
+ self.crystal_2_type, _ = soft_signal_r_and_setter(
84
+ str, initial_value=crystal_2_metadata.type
85
+ )
86
+ self.crystal_2_reflection, _ = soft_signal_r_and_setter(
87
+ Array1D[np.int32],
88
+ initial_value=np.array(crystal_2_metadata.reflection),
89
+ )
90
+ self.crystal_2_d_spacing, _ = soft_signal_r_and_setter(
91
+ float,
92
+ initial_value=crystal_2_metadata.d_spacing[0],
93
+ units=crystal_2_metadata.d_spacing[1],
94
+ )
127
95
 
128
96
  super().__init__(name)
129
97
 
@@ -1,19 +1,19 @@
1
1
  import asyncio
2
2
  import time
3
- from enum import Enum
4
3
 
5
4
  from bluesky.protocols import Reading
6
5
  from event_model import DataKey
7
6
  from ophyd_async.core import (
8
- ConfigSignal,
9
7
  DeviceVector,
10
8
  StandardReadable,
9
+ StandardReadableFormat,
10
+ StrictEnum,
11
11
  soft_signal_r_and_setter,
12
12
  )
13
- from ophyd_async.epics.signal import epics_signal_r
13
+ from ophyd_async.epics.core import epics_signal_r
14
14
 
15
15
 
16
- class FilterState(str, Enum):
16
+ class FilterState(StrictEnum):
17
17
  """
18
18
  Note that the in/out here refers to the internal rocker
19
19
  position so a PV value of IN implies a filter OUT of beam
@@ -54,7 +54,7 @@ class FSwitch(StandardReadable):
54
54
  for i in range(FSwitch.NUM_FILTERS)
55
55
  }
56
56
  )
57
- with self.add_children_as_readables(ConfigSignal):
57
+ with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
58
58
  if lens_geometry is not None:
59
59
  self.lens_geometry, _ = soft_signal_r_and_setter(
60
60
  str, initial_value=lens_geometry
@@ -1,11 +1,9 @@
1
- from enum import Enum
2
-
3
- from ophyd_async.core import StandardReadable
1
+ from ophyd_async.core import StandardReadable, StrictEnum
2
+ from ophyd_async.epics.core import epics_signal_rw
4
3
  from ophyd_async.epics.motor import Motor
5
- from ophyd_async.epics.signal import epics_signal_rw
6
4
 
7
5
 
8
- class AperturePositions(str, Enum):
6
+ class AperturePositions(StrictEnum):
9
7
  IN = "In"
10
8
  OUT = "Out"
11
9
  ROBOT = "Robot"
@@ -1,11 +1,9 @@
1
- from enum import Enum
2
-
3
- from ophyd_async.core import StandardReadable
1
+ from ophyd_async.core import StandardReadable, StrictEnum
2
+ from ophyd_async.epics.core import epics_signal_rw
4
3
  from ophyd_async.epics.motor import Motor
5
- from ophyd_async.epics.signal import epics_signal_rw
6
4
 
7
5
 
8
- class BeamstopPositions(str, Enum):
6
+ class BeamstopPositions(StrictEnum):
9
7
  CHECK_BEAM = "CheckBeam"
10
8
  DATA_COLLECTION = "Data Collection"
11
9
  DATA_COLLECTION_FAR = "Data Collection Far"
dodal/devices/i24/dcm.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from ophyd_async.core import StandardReadable
2
+ from ophyd_async.epics.core import epics_signal_r
2
3
  from ophyd_async.epics.motor import Motor
3
- from ophyd_async.epics.signal import epics_signal_r
4
4
 
5
5
 
6
6
  class DCM(StandardReadable):
@@ -1,10 +1,8 @@
1
- from enum import Enum
1
+ from ophyd_async.core import AsyncStatus, StandardReadable, StrictEnum
2
+ from ophyd_async.epics.core import epics_signal_rw
2
3
 
3
- from ophyd_async.core import AsyncStatus, StandardReadable
4
- from ophyd_async.epics.signal import epics_signal_rw
5
4
 
6
-
7
- class BacklightPositions(str, Enum):
5
+ class BacklightPositions(StrictEnum):
8
6
  OUT = "Out"
9
7
  IN = "In"
10
8
  LOAD_CHECK = "LoadCheck"
@@ -12,7 +10,7 @@ class BacklightPositions(str, Enum):
12
10
  DIODE = "Diode"
13
11
 
14
12
 
15
- class LEDStatus(str, Enum):
13
+ class LEDStatus(StrictEnum):
16
14
  OFF = "OFF"
17
15
  ON = "ON"
18
16
 
dodal/devices/i24/pmac.py CHANGED
@@ -1,21 +1,20 @@
1
1
  from asyncio import sleep
2
2
  from enum import Enum, IntEnum
3
3
 
4
- from bluesky.protocols import Flyable, Triggerable
4
+ from bluesky.protocols import Flyable, Movable, Triggerable
5
5
  from ophyd_async.core import (
6
- CALCULATE_TIMEOUT,
7
6
  DEFAULT_TIMEOUT,
8
7
  AsyncStatus,
9
- SignalBackend,
8
+ Device,
9
+ Reference,
10
10
  SignalR,
11
11
  SignalRW,
12
- SoftSignalBackend,
13
12
  StandardReadable,
14
13
  soft_signal_rw,
15
14
  wait_for_value,
16
15
  )
16
+ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
17
17
  from ophyd_async.epics.motor import Motor
18
- from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw
19
18
 
20
19
  HOME_STR = r"\#1hmz\#2hmz\#3hmz" # Command to home the PMAC motors
21
20
  ZERO_STR = "!x0y0z0" # Command to blend any ongoing move into new position
@@ -64,61 +63,53 @@ class PMACStringMove(Triggerable):
64
63
  pmac_str_sig: SignalRW,
65
64
  string_to_send: str,
66
65
  ) -> None:
67
- self.signal = pmac_str_sig
66
+ self.signal_ref = Reference(pmac_str_sig)
68
67
  self.cmd_string = string_to_send
69
68
 
70
69
  @AsyncStatus.wrap
71
70
  async def trigger(self):
72
- await self.signal.set(self.cmd_string, wait=True)
71
+ await self.signal_ref().set(self.cmd_string, wait=True)
73
72
 
74
73
 
75
- class PMACStringLaser(SignalRW):
74
+ class PMACStringLaser(Device, Movable):
76
75
  """Set the pmac_string to control the laser."""
77
76
 
78
77
  def __init__(
79
78
  self,
80
79
  pmac_str_sig: SignalRW,
81
- backend: SignalBackend,
82
- timeout: float | None = DEFAULT_TIMEOUT,
83
80
  name: str = "",
84
81
  ) -> None:
85
- self.signal = pmac_str_sig
86
- super().__init__(backend, timeout, name)
82
+ self._signal_ref = Reference(pmac_str_sig)
83
+ super().__init__(name)
87
84
 
88
85
  @AsyncStatus.wrap
89
86
  async def set(
90
87
  self,
91
88
  value: LaserSettings,
92
- wait=True,
93
- timeout=CALCULATE_TIMEOUT,
94
89
  ):
95
- await self.signal.set(value.value, wait, timeout)
90
+ await self._signal_ref().set(value.value)
96
91
 
97
92
 
98
- class PMACStringEncReset(SignalRW):
93
+ class PMACStringEncReset(Device, Movable):
99
94
  """Set a pmac_string to control the encoder channels in the controller."""
100
95
 
101
96
  def __init__(
102
97
  self,
103
98
  pmac_str_sig: SignalRW,
104
- backend: SignalBackend,
105
- timeout: float | None = DEFAULT_TIMEOUT,
106
99
  name: str = "",
107
100
  ) -> None:
108
- self.signal = pmac_str_sig
109
- super().__init__(backend, timeout, name)
101
+ self._signal_ref = Reference(pmac_str_sig)
102
+ super().__init__(name)
110
103
 
111
104
  @AsyncStatus.wrap
112
105
  async def set(
113
106
  self,
114
107
  value: EncReset,
115
- wait=True,
116
- timeout=CALCULATE_TIMEOUT,
117
108
  ):
118
- await self.signal.set(value.value, wait, timeout)
109
+ await self._signal_ref().set(value.value)
119
110
 
120
111
 
121
- class ProgramRunner(SignalRW, Flyable):
112
+ class ProgramRunner(Device, Flyable):
122
113
  """Run the collection by setting the program number on the PMAC string.
123
114
 
124
115
  Once the program number has been set, wait for the collection to be complete.
@@ -131,21 +122,18 @@ class ProgramRunner(SignalRW, Flyable):
131
122
  status_sig: SignalR,
132
123
  prog_num_sig: SignalRW,
133
124
  collection_time_sig: SignalRW,
134
- backend: SignalBackend,
135
- timeout: float | None = DEFAULT_TIMEOUT,
136
125
  name: str = "",
137
126
  ) -> None:
138
- self.signal = pmac_str_sig
139
- self.status = status_sig
140
- self.prog_num = prog_num_sig
127
+ self._signal_ref = Reference(pmac_str_sig)
128
+ self._status_ref = Reference(status_sig)
129
+ self._prog_num_ref = Reference(prog_num_sig)
141
130
 
142
- self.collection_time = collection_time_sig
143
- self.KICKOFF_TIMEOUT = timeout
131
+ self._collection_time_ref = Reference(collection_time_sig)
144
132
 
145
- super().__init__(backend, timeout, name)
133
+ super().__init__(name)
146
134
 
147
135
  async def _get_prog_number_string(self) -> str:
148
- prog_num = await self.prog_num.get_value()
136
+ prog_num = await self._prog_num_ref().get_value()
149
137
  return f"&2b{prog_num}r"
150
138
 
151
139
  @AsyncStatus.wrap
@@ -154,11 +142,11 @@ class ProgramRunner(SignalRW, Flyable):
154
142
  wait for the scan status PV to go to 1.
155
143
  """
156
144
  prog_num_str = await self._get_prog_number_string()
157
- await self.signal.set(prog_num_str, wait=True)
145
+ await self._signal_ref().set(prog_num_str, wait=True)
158
146
  await wait_for_value(
159
- self.status,
147
+ self._status_ref(),
160
148
  ScanState.RUNNING,
161
- timeout=self.KICKOFF_TIMEOUT,
149
+ timeout=DEFAULT_TIMEOUT,
162
150
  )
163
151
 
164
152
  @AsyncStatus.wrap
@@ -169,8 +157,10 @@ class ProgramRunner(SignalRW, Flyable):
169
157
  complete_time (float): total time required by the collection to \
170
158
  finish correctly.
171
159
  """
172
- scan_complete_time = await self.collection_time.get_value()
173
- await wait_for_value(self.status, ScanState.DONE, timeout=scan_complete_time)
160
+ scan_complete_time = await self._collection_time_ref().get_value()
161
+ await wait_for_value(
162
+ self._status_ref(), ScanState.DONE, timeout=scan_complete_time
163
+ )
174
164
 
175
165
 
176
166
  class ProgramAbort(Triggerable):
@@ -183,16 +173,16 @@ class ProgramAbort(Triggerable):
183
173
  pmac_str_sig: SignalRW,
184
174
  status_sig: SignalR,
185
175
  ) -> None:
186
- self.signal = pmac_str_sig
187
- self.status = status_sig
176
+ self._signal_ref = Reference(pmac_str_sig)
177
+ self._status_ref = Reference(status_sig)
188
178
 
189
179
  @AsyncStatus.wrap
190
180
  async def trigger(self):
191
- await self.signal.set("A", wait=True)
181
+ await self._signal_ref().set("A", wait=True)
192
182
  await sleep(1.0) # TODO Check with scientist what this sleep is really for.
193
- await self.signal.set("P2401=0", wait=True)
183
+ await self._signal_ref().set("P2401=0", wait=True)
194
184
  await wait_for_value(
195
- self.status,
185
+ self._status_ref(),
196
186
  ScanState.DONE,
197
187
  timeout=DEFAULT_TIMEOUT,
198
188
  )
@@ -209,10 +199,10 @@ class PMAC(StandardReadable):
209
199
  )
210
200
  self.to_xyz_zero = PMACStringMove(self.pmac_string, ZERO_STR)
211
201
 
212
- self.laser = PMACStringLaser(self.pmac_string, backend=SoftSignalBackend(str))
202
+ self.laser = PMACStringLaser(self.pmac_string)
213
203
 
214
204
  self.enc_reset = PMACStringEncReset(
215
- self.pmac_string, backend=SoftSignalBackend(str)
205
+ self.pmac_string,
216
206
  )
217
207
 
218
208
  self.x = Motor(prefix + "X")
@@ -234,7 +224,6 @@ class PMAC(StandardReadable):
234
224
  self.scanstatus,
235
225
  self.program_number,
236
226
  self.collection_time,
237
- backend=SoftSignalBackend(str),
238
227
  )
239
228
  self.abort_program = ProgramAbort(self.pmac_string, self.scanstatus)
240
229
 
@@ -0,0 +1,16 @@
1
+ from ophyd_async.core import StandardReadable
2
+ from ophyd_async.epics.motor import Motor
3
+
4
+
5
+ class VerticalGoniometer(StandardReadable):
6
+ def __init__(self, prefix: str, name: str = "") -> None:
7
+ self.x = Motor(prefix + "PINX")
8
+ self.z = Motor(prefix + "PINZ")
9
+ self.yh = Motor(prefix + "PINYH")
10
+ self.omega = Motor(prefix + "OMEGA")
11
+
12
+ self.real_x = Motor(prefix + "PINXS")
13
+ self.real_z = Motor(prefix + "PINZS")
14
+ self.fast_y = Motor(prefix + "PINYS")
15
+
16
+ super().__init__(name)