dls-dodal 1.50.0__py3-none-any.whl → 1.52.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. {dls_dodal-1.50.0.dist-info → dls_dodal-1.52.0.dist-info}/METADATA +5 -5
  2. {dls_dodal-1.50.0.dist-info → dls_dodal-1.52.0.dist-info}/RECORD +76 -68
  3. dodal/_version.py +2 -2
  4. dodal/beamlines/adsim.py +5 -3
  5. dodal/beamlines/b01_1.py +41 -5
  6. dodal/beamlines/b07.py +13 -2
  7. dodal/beamlines/b07_1.py +13 -2
  8. dodal/beamlines/b16.py +8 -4
  9. dodal/beamlines/b21.py +148 -0
  10. dodal/beamlines/i03.py +10 -12
  11. dodal/beamlines/i04.py +7 -7
  12. dodal/beamlines/i09.py +25 -2
  13. dodal/beamlines/i09_1.py +13 -2
  14. dodal/beamlines/i09_2.py +24 -0
  15. dodal/beamlines/i10.py +5 -6
  16. dodal/beamlines/i13_1.py +5 -5
  17. dodal/beamlines/i18.py +5 -6
  18. dodal/beamlines/i22.py +18 -1
  19. dodal/beamlines/i24.py +5 -5
  20. dodal/beamlines/p45.py +4 -3
  21. dodal/beamlines/p60.py +21 -2
  22. dodal/beamlines/p99.py +19 -5
  23. dodal/beamlines/training_rig.py +3 -3
  24. dodal/common/beamlines/beamline_utils.py +5 -2
  25. dodal/common/device_utils.py +45 -0
  26. dodal/devices/aithre_lasershaping/goniometer.py +4 -5
  27. dodal/devices/aperture.py +4 -7
  28. dodal/devices/aperturescatterguard.py +2 -2
  29. dodal/devices/attenuator/attenuator.py +5 -3
  30. dodal/devices/b07/__init__.py +3 -0
  31. dodal/devices/b07/enums.py +24 -0
  32. dodal/devices/b07_1/__init__.py +3 -0
  33. dodal/devices/b07_1/enums.py +18 -0
  34. dodal/devices/detector/detector_motion.py +19 -17
  35. dodal/devices/electron_analyser/abstract/__init__.py +4 -0
  36. dodal/devices/electron_analyser/abstract/base_driver_io.py +44 -28
  37. dodal/devices/electron_analyser/abstract/base_region.py +20 -7
  38. dodal/devices/electron_analyser/detector.py +3 -13
  39. dodal/devices/electron_analyser/specs/detector.py +24 -4
  40. dodal/devices/electron_analyser/specs/driver_io.py +20 -5
  41. dodal/devices/electron_analyser/specs/region.py +9 -5
  42. dodal/devices/electron_analyser/types.py +21 -5
  43. dodal/devices/electron_analyser/vgscienta/detector.py +22 -7
  44. dodal/devices/electron_analyser/vgscienta/driver_io.py +16 -8
  45. dodal/devices/electron_analyser/vgscienta/region.py +11 -6
  46. dodal/devices/fast_grid_scan.py +1 -2
  47. dodal/devices/i04/constants.py +1 -1
  48. dodal/devices/i09/__init__.py +4 -0
  49. dodal/devices/i09/dcm.py +26 -0
  50. dodal/devices/i09/enums.py +15 -0
  51. dodal/devices/i09_1/__init__.py +3 -0
  52. dodal/devices/i09_1/enums.py +19 -0
  53. dodal/devices/i10/mirrors.py +4 -6
  54. dodal/devices/i10/rasor/rasor_motors.py +0 -14
  55. dodal/devices/i19/beamstop.py +3 -7
  56. dodal/devices/i24/aperture.py +4 -6
  57. dodal/devices/i24/beamstop.py +5 -8
  58. dodal/devices/i24/pmac.py +4 -8
  59. dodal/devices/linkam3.py +25 -81
  60. dodal/devices/motors.py +92 -35
  61. dodal/devices/oav/pin_image_recognition/__init__.py +11 -14
  62. dodal/devices/p45.py +0 -12
  63. dodal/devices/p60/__init__.py +4 -0
  64. dodal/devices/p60/enums.py +10 -0
  65. dodal/devices/p60/lab_xray_source.py +21 -0
  66. dodal/devices/pgm.py +1 -1
  67. dodal/devices/robot.py +11 -7
  68. dodal/devices/smargon.py +8 -9
  69. dodal/devices/tetramm.py +134 -150
  70. dodal/devices/xbpm_feedback.py +6 -3
  71. dodal/devices/zocalo/zocalo_results.py +27 -78
  72. dodal/plans/configure_arm_trigger_and_disarm_detector.py +7 -5
  73. dodal/devices/adsim.py +0 -13
  74. dodal/devices/i18/table.py +0 -14
  75. dodal/devices/i18/thor_labs_stage.py +0 -12
  76. dodal/devices/i24/i24_detector_motion.py +0 -12
  77. dodal/devices/scatterguard.py +0 -11
  78. dodal/devices/training_rig/__init__.py +0 -0
  79. dodal/devices/training_rig/sample_stage.py +0 -10
  80. {dls_dodal-1.50.0.dist-info → dls_dodal-1.52.0.dist-info}/WHEEL +0 -0
  81. {dls_dodal-1.50.0.dist-info → dls_dodal-1.52.0.dist-info}/entry_points.txt +0 -0
  82. {dls_dodal-1.50.0.dist-info → dls_dodal-1.52.0.dist-info}/licenses/LICENSE +0 -0
  83. {dls_dodal-1.50.0.dist-info → dls_dodal-1.52.0.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,5 @@
1
+ from ophyd_async.core import StrictEnum, SupersetEnum
2
+
1
3
  from dodal.devices.electron_analyser.abstract.base_driver_io import (
2
4
  AbstractAnalyserDriverIO,
3
5
  )
@@ -18,15 +20,29 @@ from dodal.devices.electron_analyser.vgscienta.detector import (
18
20
  VGScientaDetector,
19
21
  )
20
22
 
21
- ElectronAnalyserDetectorImpl = VGScientaDetector | SpecsDetector
22
- ElectronAnalyserDriverImpl = VGScientaAnalyserDriverIO | SpecsAnalyserDriverIO
23
+ AnyAcqMode = StrictEnum
24
+ AnyLensMode = SupersetEnum | StrictEnum
25
+
26
+ # Electron analyser types that encompasses all implementations, useful for tests and
27
+ # plans
28
+ ElectronAnalyserDetectorImpl = (
29
+ VGScientaDetector[AnyLensMode] | SpecsDetector[AnyLensMode]
30
+ )
31
+ ElectronAnalyserDriverImpl = (
32
+ VGScientaAnalyserDriverIO[AnyLensMode] | SpecsAnalyserDriverIO[AnyLensMode]
33
+ )
23
34
 
35
+ # Short hand the type so less verbose
36
+ AbstractBaseRegion = AbstractBaseRegion[AnyAcqMode, AnyLensMode]
37
+
38
+ # Generic electron analyser types that supports full typing with the abstract classes.
24
39
  GenericElectronAnalyserDetector = ElectronAnalyserDetector[
25
- AbstractAnalyserDriverIO[AbstractBaseRegion],
26
- AbstractBaseSequence,
40
+ AbstractAnalyserDriverIO[AbstractBaseRegion, AnyAcqMode, AnyLensMode],
41
+ AbstractBaseSequence[AbstractBaseRegion, AnyLensMode],
27
42
  AbstractBaseRegion,
28
43
  ]
29
44
 
30
45
  GenericElectronAnalyserRegionDetector = ElectronAnalyserRegionDetector[
31
- AbstractAnalyserDriverIO[AbstractBaseRegion], AbstractBaseRegion
46
+ AbstractAnalyserDriverIO[AbstractBaseRegion, AnyAcqMode, AnyLensMode],
47
+ AbstractBaseRegion,
32
48
  ]
@@ -1,3 +1,9 @@
1
+ from collections.abc import Mapping
2
+ from typing import Generic
3
+
4
+ from ophyd_async.core import SignalR
5
+
6
+ from dodal.devices.electron_analyser.abstract.base_region import TLensMode
1
7
  from dodal.devices.electron_analyser.detector import (
2
8
  ElectronAnalyserDetector,
3
9
  )
@@ -12,11 +18,20 @@ from dodal.devices.electron_analyser.vgscienta.region import (
12
18
 
13
19
  class VGScientaDetector(
14
20
  ElectronAnalyserDetector[
15
- VGScientaAnalyserDriverIO,
16
- VGScientaSequence,
17
- VGScientaRegion,
18
- ]
21
+ VGScientaAnalyserDriverIO[TLensMode],
22
+ VGScientaSequence[TLensMode],
23
+ VGScientaRegion[TLensMode],
24
+ ],
25
+ Generic[TLensMode],
19
26
  ):
20
- def __init__(self, prefix: str, name: str = ""):
21
- driver = VGScientaAnalyserDriverIO(prefix)
22
- super().__init__(prefix, VGScientaSequence, driver, name)
27
+ def __init__(
28
+ self,
29
+ prefix: str,
30
+ lens_mode_type: type[TLensMode],
31
+ energy_sources: Mapping[str, SignalR[float]],
32
+ name: str = "",
33
+ ):
34
+ driver = VGScientaAnalyserDriverIO[TLensMode](
35
+ prefix, lens_mode_type, energy_sources
36
+ )
37
+ super().__init__(VGScientaSequence[lens_mode_type], driver, name)
@@ -1,4 +1,6 @@
1
1
  import asyncio
2
+ from collections.abc import Mapping
3
+ from typing import Generic
2
4
 
3
5
  import numpy as np
4
6
  from ophyd_async.core import (
@@ -13,6 +15,7 @@ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
13
15
  from dodal.devices.electron_analyser.abstract.base_driver_io import (
14
16
  AbstractAnalyserDriverIO,
15
17
  )
18
+ from dodal.devices.electron_analyser.abstract.base_region import TLensMode
16
19
  from dodal.devices.electron_analyser.util import to_kinetic_energy
17
20
  from dodal.devices.electron_analyser.vgscienta.enums import AcquisitionMode
18
21
  from dodal.devices.electron_analyser.vgscienta.region import (
@@ -21,8 +24,17 @@ from dodal.devices.electron_analyser.vgscienta.region import (
21
24
  )
22
25
 
23
26
 
24
- class VGScientaAnalyserDriverIO(AbstractAnalyserDriverIO[VGScientaRegion]):
25
- def __init__(self, prefix: str, name: str = "") -> None:
27
+ class VGScientaAnalyserDriverIO(
28
+ AbstractAnalyserDriverIO[VGScientaRegion, AcquisitionMode, TLensMode],
29
+ Generic[TLensMode],
30
+ ):
31
+ def __init__(
32
+ self,
33
+ prefix: str,
34
+ lens_mode_type: type[TLensMode],
35
+ energy_sources: Mapping[str, SignalR[float]],
36
+ name: str = "",
37
+ ) -> None:
26
38
  with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
27
39
  # Used for setting up region data acquisition.
28
40
  self.centre_energy = epics_signal_rw(float, prefix + "CENTRE_ENERGY")
@@ -32,14 +44,10 @@ class VGScientaAnalyserDriverIO(AbstractAnalyserDriverIO[VGScientaRegion]):
32
44
  self.y_channel_size = epics_signal_rw(int, prefix + "SizeY")
33
45
  self.detector_mode = epics_signal_rw(DetectorMode, prefix + "DETECTOR_MODE")
34
46
 
35
- with self.add_children_as_readables():
36
- # Used to read detector data after acqusition.
37
- self.external_io = epics_signal_r(Array1D[np.float64], prefix + "EXTIO")
38
-
39
- super().__init__(prefix, AcquisitionMode, name)
47
+ super().__init__(prefix, AcquisitionMode, lens_mode_type, energy_sources, name)
40
48
 
41
49
  @AsyncStatus.wrap
42
- async def set(self, region: VGScientaRegion):
50
+ async def set(self, region: VGScientaRegion[TLensMode]):
43
51
  await super().set(region)
44
52
 
45
53
  excitation_energy = await self.excitation_energy.get_value()
@@ -1,4 +1,5 @@
1
1
  import uuid
2
+ from typing import Generic
2
3
 
3
4
  from pydantic import Field
4
5
 
@@ -6,6 +7,7 @@ from dodal.devices.electron_analyser.abstract.base_region import (
6
7
  AbstractBaseRegion,
7
8
  AbstractBaseSequence,
8
9
  JavaToPythonModel,
10
+ TLensMode,
9
11
  )
10
12
  from dodal.devices.electron_analyser.vgscienta.enums import (
11
13
  AcquisitionMode,
@@ -14,9 +16,11 @@ from dodal.devices.electron_analyser.vgscienta.enums import (
14
16
  )
15
17
 
16
18
 
17
- class VGScientaRegion(AbstractBaseRegion[AcquisitionMode]):
19
+ class VGScientaRegion(
20
+ AbstractBaseRegion[AcquisitionMode, TLensMode], Generic[TLensMode]
21
+ ):
18
22
  # Override defaults of base region class
19
- lens_mode: str = "Angular45"
23
+ lens_mode: TLensMode
20
24
  pass_energy: int = 5
21
25
  acquisition_mode: AcquisitionMode = AcquisitionMode.SWEPT
22
26
  low_energy: float = 8.0
@@ -28,7 +32,6 @@ class VGScientaRegion(AbstractBaseRegion[AcquisitionMode]):
28
32
  fix_energy: float = 9.0
29
33
  total_steps: float = 13.0
30
34
  total_time: float = 13.0
31
- exposure_time: float = 1.0
32
35
  first_x_channel: int = 1
33
36
  last_x_channel: int = 1000
34
37
  first_y_channel: int = 101
@@ -49,15 +52,17 @@ class VGScientaExcitationEnergySource(JavaToPythonModel):
49
52
  value: float = 0
50
53
 
51
54
 
52
- class VGScientaSequence(AbstractBaseSequence[VGScientaRegion]):
55
+ class VGScientaSequence(
56
+ AbstractBaseSequence[VGScientaRegion, TLensMode], Generic[TLensMode]
57
+ ):
53
58
  element_set: str = Field(default="Unknown")
54
59
  excitation_energy_sources: list[VGScientaExcitationEnergySource] = Field(
55
60
  default_factory=lambda: []
56
61
  )
57
- regions: list[VGScientaRegion] = Field(default_factory=lambda: [])
62
+ regions: list[VGScientaRegion[TLensMode]] = Field(default_factory=lambda: [])
58
63
 
59
64
  def get_excitation_energy_source_by_region(
60
- self, region: VGScientaRegion
65
+ self, region: VGScientaRegion[TLensMode]
61
66
  ) -> VGScientaExcitationEnergySource:
62
67
  value = next(
63
68
  (
@@ -1,4 +1,3 @@
1
- import asyncio
2
1
  from abc import ABC, abstractmethod
3
2
  from typing import Generic, TypeVar
4
3
 
@@ -258,7 +257,7 @@ class FastGridScanCommon(StandardReadable, Flyable, ABC, Generic[ParamType]):
258
257
  async def complete(self):
259
258
  try:
260
259
  await wait_for_value(self.status, 0, self.COMPLETE_STATUS)
261
- except asyncio.TimeoutError:
260
+ except TimeoutError:
262
261
  LOGGER.error(
263
262
  "Hyperion timed out waiting for FGS motion to complete. This may have been caused by a goniometer stage getting stuck.\n\
264
263
  Forcibly stopping the FGS motion program..."
@@ -4,6 +4,6 @@ from dataclasses import dataclass
4
4
 
5
5
  @dataclass(frozen=True)
6
6
  class RedisConstants:
7
- REDIS_HOST = os.environ.get("VALKEY_PROD_SVC_SERVICE_HOST", "test_redis")
7
+ REDIS_HOST = os.environ.get("VALKEY_FOR_MURKO_PROD_SVC_SERVICE_HOST", "test_redis")
8
8
  REDIS_PASSWORD = os.environ.get("VALKEY_PASSWORD", "test_redis_password")
9
9
  MURKO_REDIS_DB = 7
@@ -0,0 +1,4 @@
1
+ from dodal.devices.i09.dcm import DCM
2
+ from dodal.devices.i09.enums import Grating, LensMode
3
+
4
+ __all__ = ["DCM", "Grating", "LensMode"]
@@ -0,0 +1,26 @@
1
+ from ophyd_async.core import derived_signal_r
2
+
3
+ from dodal.devices.common_dcm import BaseDCM, PitchAndRollCrystal, StationaryCrystal
4
+
5
+
6
+ class DCM(BaseDCM[PitchAndRollCrystal, StationaryCrystal]):
7
+ """
8
+ I09 double crystal monochromator (DCM), used to select the energy of the beam.
9
+ Differences:
10
+
11
+ 1. Can provide energy in eV via dcm.energy_in_ev read signal
12
+
13
+ This DCM is available on i09 and i09_1 endstations.
14
+ """
15
+
16
+ def __init__(self, prefix: str, name: str = "") -> None:
17
+ super().__init__(prefix, PitchAndRollCrystal, StationaryCrystal, name)
18
+ self.energy_in_ev = derived_signal_r(
19
+ self._convert_keV_to_eV, energy_signal=self.energy_in_kev.user_readback
20
+ )
21
+ # Set name so that new child signals get correct name
22
+ # need to do it until https://github.com/bluesky/ophyd-async/pull/899 merged
23
+ self.set_name(self.name)
24
+
25
+ def _convert_keV_to_eV(self, energy_signal: float) -> float:
26
+ return energy_signal * 1000
@@ -0,0 +1,15 @@
1
+ from ophyd_async.core import StrictEnum
2
+
3
+
4
+ class Grating(StrictEnum):
5
+ G_300 = "300 lines/mm"
6
+ G_400 = "400 lines/mm"
7
+ G_800 = "800 lines/mm"
8
+
9
+
10
+ class LensMode(StrictEnum):
11
+ TRANSMISSION = "Transmission"
12
+ ANGULAR45 = "Angular45"
13
+ ANGULAR60 = "Angular60"
14
+ ANGULAR56 = "Angular56"
15
+ ANGULAR45VUV = "Angular45VUV"
@@ -0,0 +1,3 @@
1
+ from .enums import LensMode
2
+
3
+ __all__ = ["LensMode"]
@@ -0,0 +1,19 @@
1
+ from ophyd_async.core import SupersetEnum
2
+
3
+
4
+ class LensMode(SupersetEnum):
5
+ LARGE_AREA = "LargeArea"
6
+ MEDIUM_ANGULAR_DISPERSION = "MediumAngularDispersion"
7
+ MEDIUM_AREA = "MediumArea"
8
+ SMALL_AREA = "SmallArea"
9
+ HIGH_MAGNIFICATION = "HighMagnification"
10
+ LOW_ANGULAR_DISPERSION = "LowAngularDispersion"
11
+ LOW_ANGULAR_DISPERSION2 = "LowAngularDispersion2"
12
+ HIGH_ANGULAR_DISPERSION = "HighAngularDispersion"
13
+ WIDE_ANGLE_MODE = "WideAngleMode"
14
+ MEDIUM_ANGLE_MODE = "MediumAngleMode"
15
+ MEDIUM_MAGNIFICATION = "MediumMagnification"
16
+ LOW_MAGNIFICATION = "LowMagnification"
17
+ HIGH_MAGNIFICATION2 = "HighMagnification2"
18
+ RAMP_MODE = "RampMode"
19
+ NOT_CONNECTED = "Not connected"
@@ -1,18 +1,16 @@
1
- from ophyd_async.core import StandardReadable
2
1
  from ophyd_async.epics.core import epics_signal_rw
3
2
  from ophyd_async.epics.motor import Motor
4
3
 
4
+ from dodal.devices.motors import XYZStage
5
5
 
6
- class PiezoMirror(StandardReadable):
6
+
7
+ class PiezoMirror(XYZStage):
7
8
  def __init__(
8
9
  self,
9
10
  prefix: str,
10
11
  name: str = "",
11
12
  ):
12
13
  with self.add_children_as_readables():
13
- self.x = Motor(prefix + "X")
14
- self.y = Motor(prefix + "Y")
15
- self.z = Motor(prefix + "Z")
16
14
  self.yaw = Motor(prefix + "YAW")
17
15
  self.pitch = Motor(prefix + "PITCH")
18
16
  self.roll = Motor(prefix + "ROLL")
@@ -21,4 +19,4 @@ class PiezoMirror(StandardReadable):
21
19
  read_pv=prefix + "FPITCH:RBV:AI",
22
20
  write_pv=prefix + "FPITCH:DMD:AO",
23
21
  )
24
- super().__init__(name=name)
22
+ super().__init__(prefix, name)
@@ -2,20 +2,6 @@ from ophyd_async.core import StandardReadable
2
2
  from ophyd_async.epics.motor import Motor
3
3
 
4
4
 
5
- class PinHole(StandardReadable):
6
- "Two motors stage for rasor pinhole"
7
-
8
- def __init__(
9
- self,
10
- prefix: str,
11
- name: str = "",
12
- ):
13
- with self.add_children_as_readables():
14
- self.x = Motor(prefix + "X")
15
- self.y = Motor(prefix + "Y")
16
- super().__init__(name=name)
17
-
18
-
19
5
  class Diffractometer(StandardReadable):
20
6
  def __init__(
21
7
  self,
@@ -1,6 +1,7 @@
1
1
  from ophyd_async.core import StandardReadable, StrictEnum
2
2
  from ophyd_async.epics.core import epics_signal_rw, epics_signal_x
3
- from ophyd_async.epics.motor import Motor
3
+
4
+ from dodal.devices.motors import XYZStage
4
5
 
5
6
 
6
7
  class HomeGroup(StrictEnum):
@@ -18,13 +19,8 @@ class HomingControl(StandardReadable):
18
19
  super().__init__(name)
19
20
 
20
21
 
21
- class BeamStop(StandardReadable):
22
+ class BeamStop(XYZStage):
22
23
  def __init__(self, prefix: str, name: str = "") -> None:
23
- with self.add_children_as_readables():
24
- self.x = Motor(f"{prefix}X")
25
- self.y = Motor(f"{prefix}Y")
26
- self.z = Motor(f"{prefix}Z")
27
-
28
24
  self.homing = HomingControl(f"{prefix}HM", name)
29
25
 
30
26
  super().__init__(name)
@@ -1,6 +1,7 @@
1
- from ophyd_async.core import StandardReadable, StrictEnum
1
+ from ophyd_async.core import StrictEnum
2
2
  from ophyd_async.epics.core import epics_signal_rw
3
- from ophyd_async.epics.motor import Motor
3
+
4
+ from dodal.devices.motors import XYStage
4
5
 
5
6
 
6
7
  class AperturePositions(StrictEnum):
@@ -10,7 +11,7 @@ class AperturePositions(StrictEnum):
10
11
  MANUAL = "Manual Mounting"
11
12
 
12
13
 
13
- class Aperture(StandardReadable):
14
+ class Aperture(XYStage):
14
15
  """Device to trigger the aperture motor move on I24.
15
16
 
16
17
  The aperture positioner has 4 possible positions: In, Out, Robot and Manual.
@@ -20,8 +21,5 @@ class Aperture(StandardReadable):
20
21
  """
21
22
 
22
23
  def __init__(self, prefix: str, name: str = "") -> None:
23
- self.x = Motor(prefix + "X")
24
- self.y = Motor(prefix + "Y")
25
-
26
24
  self.position = epics_signal_rw(AperturePositions, prefix + "MP:SELECT")
27
25
  super().__init__(name)
@@ -1,7 +1,9 @@
1
- from ophyd_async.core import StandardReadable, StrictEnum
1
+ from ophyd_async.core import StrictEnum
2
2
  from ophyd_async.epics.core import epics_signal_rw
3
3
  from ophyd_async.epics.motor import Motor
4
4
 
5
+ from dodal.devices.motors import XYZStage
6
+
5
7
 
6
8
  class BeamstopPositions(StrictEnum):
7
9
  CHECK_BEAM = "CheckBeam"
@@ -12,7 +14,7 @@ class BeamstopPositions(StrictEnum):
12
14
  ROBOT = "Robot"
13
15
 
14
16
 
15
- class Beamstop(StandardReadable):
17
+ class Beamstop(XYZStage):
16
18
  """Device to move the beamstop.
17
19
 
18
20
  The positioner moves the x,y,z motors when a position is selected.
@@ -25,12 +27,7 @@ class Beamstop(StandardReadable):
25
27
  """
26
28
 
27
29
  def __init__(self, prefix: str, name: str = "") -> None:
28
- self.x = Motor(prefix + "X")
29
- self.y = Motor(prefix + "Y")
30
- self.z = Motor(prefix + "Z")
31
-
32
30
  self.y_rotation = Motor(prefix + "ROTY")
33
-
34
31
  self.pos_select = epics_signal_rw(BeamstopPositions, prefix + "MP:SELECT")
35
32
 
36
- super().__init__(name)
33
+ super().__init__(prefix, name)
dodal/devices/i24/pmac.py CHANGED
@@ -9,13 +9,13 @@ from ophyd_async.core import (
9
9
  Reference,
10
10
  SignalR,
11
11
  SignalRW,
12
- StandardReadable,
13
12
  observe_signals_value,
14
13
  soft_signal_rw,
15
14
  wait_for_value,
16
15
  )
17
16
  from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
18
- from ophyd_async.epics.motor import Motor
17
+
18
+ from dodal.devices.motors import XYZStage
19
19
 
20
20
  HOME_STR = r"\#1hmz\#2hmz\#3hmz" # Command to home the PMAC motors
21
21
  ZERO_STR = "!x0y0z0" # Command to blend any ongoing move into new position
@@ -192,7 +192,7 @@ class ProgramAbort(Triggerable):
192
192
  )
193
193
 
194
194
 
195
- class PMAC(StandardReadable):
195
+ class PMAC(XYZStage):
196
196
  """Device to control the chip stage on I24."""
197
197
 
198
198
  def __init__(self, prefix: str, name: str = "") -> None:
@@ -209,10 +209,6 @@ class PMAC(StandardReadable):
209
209
  self.pmac_string,
210
210
  )
211
211
 
212
- self.x = Motor(prefix + "X")
213
- self.y = Motor(prefix + "Y")
214
- self.z = Motor(prefix + "Z")
215
-
216
212
  # These next signals are readback values on PVARS which are set by the motion
217
213
  # program.
218
214
  self.scanstatus = epics_signal_r(float, "BL24I-MO-STEP-14:signal:P2401")
@@ -232,4 +228,4 @@ class PMAC(StandardReadable):
232
228
  )
233
229
  self.abort_program = ProgramAbort(self.pmac_string, self.scanstatus)
234
230
 
235
- super().__init__(name)
231
+ super().__init__(prefix, name)
dodal/devices/linkam3.py CHANGED
@@ -1,14 +1,7 @@
1
- import asyncio
2
- import time
3
-
4
- from bluesky.protocols import Location
5
1
  from ophyd_async.core import (
6
2
  StandardReadable,
7
3
  StandardReadableFormat,
8
4
  StrictEnum,
9
- WatchableAsyncStatus,
10
- WatcherUpdate,
11
- observe_value,
12
5
  )
13
6
  from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
14
7
 
@@ -18,6 +11,8 @@ class PumpControl(StrictEnum):
18
11
  AUTO = "Auto"
19
12
 
20
13
 
14
+ # TODO: Make use of Status PV:
15
+ # https://github.com/DiamondLightSource/dodal/issues/338
21
16
  class Linkam3(StandardReadable):
22
17
  """Device to represent a Linkam3 temperature controller
23
18
 
@@ -34,81 +29,30 @@ class Linkam3(StandardReadable):
34
29
  settle_time: int = 0
35
30
 
36
31
  def __init__(self, prefix: str, name: str = ""):
37
- self.temp = epics_signal_r(float, prefix + "TEMP:")
38
- self.dsc = epics_signal_r(float, prefix + "DSC:")
39
- self.start_heat = epics_signal_rw(bool, prefix + "STARTHEAT:")
40
-
41
- self.ramp_rate = epics_signal_rw(
42
- float, prefix + "RAMPRATE:", prefix + "RAMPRATE:SET:"
43
- )
44
- self.ramp_time = epics_signal_r(float, prefix + "RAMPTIME:")
45
- self.set_point = epics_signal_rw(
46
- float, prefix + "SETPOINT:", prefix + "SETPOINT:SET:"
47
- )
48
- self.pump_control = epics_signal_r(
49
- PumpControl,
50
- prefix + "LNP_MODE:SET:",
51
- )
52
- self.speed = epics_signal_rw(
53
- float, prefix + "LNP_SPEED:", prefix + "LNP_SPEED:SET:"
54
- )
55
-
56
- self.chamber_vac = epics_signal_r(float, prefix + "VAC_CHAMBER:")
57
- self.sensor_vac = epics_signal_r(float, prefix + "VAC_DATA1:")
58
-
59
- self.error = epics_signal_r(str, prefix + "CTRLLR:ERR:")
60
-
61
- # status is a bitfield stored in a double?
62
- self.status = epics_signal_r(float, prefix + "STATUS:")
63
-
64
- self.add_readables((self.temp,), format=StandardReadableFormat.HINTED_SIGNAL)
65
- self.add_readables(
66
- (self.ramp_rate, self.speed, self.set_point),
67
- format=StandardReadableFormat.CONFIG_SIGNAL,
68
- )
69
-
70
- super().__init__(name=name)
71
-
72
- @WatchableAsyncStatus.wrap
73
- async def set(self, new_position: float, timeout: float | None = None):
74
- # time.monotonic won't go backwards in case of NTP corrections
75
- start = time.monotonic()
76
- old_position = await self.set_point.get_value()
77
- await self.set_point.set(new_position, wait=True)
78
- async for current_position in observe_value(self.temp):
79
- yield WatcherUpdate(
80
- name=self.name,
81
- current=current_position,
82
- initial=old_position,
83
- target=new_position,
84
- time_elapsed=time.monotonic() - start,
32
+ with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
33
+ self.temp = epics_signal_r(float, prefix + "TEMP")
34
+ with self.add_children_as_readables():
35
+ self.dsc = epics_signal_r(float, prefix + "DSC")
36
+ self.start_heat = epics_signal_rw(bool, prefix + "STARTHEAT")
37
+
38
+ self.ramp_rate = epics_signal_rw(
39
+ float, prefix + "RAMPRATE", prefix + "RAMPRATE:SET"
40
+ )
41
+ self.ramp_time = epics_signal_r(float, prefix + "RAMPTIME")
42
+ self.set_point = epics_signal_rw(
43
+ float, prefix + "SETPOINT", prefix + "SETPOINT:SET"
44
+ )
45
+ self.pump_control = epics_signal_r(
46
+ PumpControl,
47
+ prefix + "LNP_MODE:SET",
48
+ )
49
+ self.speed = epics_signal_rw(
50
+ float, prefix + "LNP_SPEED", prefix + "LNP_SPEED:SET"
85
51
  )
86
- if abs(current_position - new_position) < self.tolerance:
87
- await asyncio.sleep(self.settle_time)
88
- break
89
-
90
- # TODO: Make use of values in Status.
91
- # https://github.com/DiamondLightSource/dodal/issues/338
92
- async def _is_nth_bit_set(self, n: int) -> bool:
93
- return bool(int(await self.status.get_value()) & 1 << n)
94
-
95
- async def in_error(self) -> bool:
96
- return await self._is_nth_bit_set(0)
97
-
98
- async def at_setpoint(self) -> bool:
99
- return await self._is_nth_bit_set(1)
100
-
101
- async def heater_on(self) -> bool:
102
- return await self._is_nth_bit_set(2)
103
52
 
104
- async def pump_on(self) -> bool:
105
- return await self._is_nth_bit_set(3)
53
+ self.chamber_vac = epics_signal_r(float, prefix + "VAC_CHAMBER")
54
+ self.sensor_vac = epics_signal_r(float, prefix + "VAC_DATA1")
106
55
 
107
- async def pump_auto(self) -> bool:
108
- return await self._is_nth_bit_set(4)
56
+ self.error = epics_signal_r(str, prefix + "CTRLLR:ERR")
109
57
 
110
- async def locate(self) -> Location:
111
- return {
112
- "readback": await self.temp.get_value(),
113
- "setpoint": await self.set_point.get_value(),
114
- }
58
+ super().__init__(name=name)