dls-dodal 1.55.1__py3-none-any.whl → 1.56.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 (98) hide show
  1. {dls_dodal-1.55.1.dist-info → dls_dodal-1.56.0.dist-info}/METADATA +2 -2
  2. {dls_dodal-1.55.1.dist-info → dls_dodal-1.56.0.dist-info}/RECORD +97 -83
  3. dodal/_version.py +16 -3
  4. dodal/beamlines/b01_1.py +6 -1
  5. dodal/beamlines/b07.py +2 -1
  6. dodal/beamlines/b07_1.py +2 -1
  7. dodal/beamlines/b21.py +4 -24
  8. dodal/beamlines/i03.py +53 -53
  9. dodal/beamlines/i04.py +16 -38
  10. dodal/beamlines/i09.py +3 -2
  11. dodal/beamlines/i09_1.py +2 -1
  12. dodal/beamlines/i11.py +143 -0
  13. dodal/beamlines/i19_1.py +1 -0
  14. dodal/beamlines/i19_2.py +7 -0
  15. dodal/beamlines/i22.py +2 -2
  16. dodal/beamlines/i23.py +3 -3
  17. dodal/beamlines/i24.py +6 -14
  18. dodal/beamlines/p38.py +1 -0
  19. dodal/beamlines/p60.py +3 -2
  20. dodal/cli.py +11 -1
  21. dodal/common/__init__.py +4 -0
  22. dodal/common/beamlines/beamline_parameters.py +1 -1
  23. dodal/common/beamlines/beamline_utils.py +5 -1
  24. dodal/common/enums.py +19 -0
  25. dodal/common/watcher_utils.py +83 -0
  26. dodal/devices/aithre_lasershaping/laser_robot.py +4 -9
  27. dodal/devices/aperturescatterguard.py +52 -12
  28. dodal/devices/apple2_undulator.py +0 -1
  29. dodal/devices/b16/detector.py +1 -10
  30. dodal/devices/backlight.py +8 -20
  31. dodal/devices/bimorph_mirror.py +4 -7
  32. dodal/devices/collimation_table.py +36 -0
  33. dodal/devices/controllers.py +21 -0
  34. dodal/devices/cryostream.py +97 -7
  35. dodal/devices/current_amplifiers/femto.py +1 -1
  36. dodal/devices/detector/detector_motion.py +1 -7
  37. dodal/devices/eiger.py +22 -8
  38. dodal/devices/eiger_odin.py +2 -0
  39. dodal/devices/electron_analyser/__init__.py +2 -1
  40. dodal/devices/electron_analyser/abstract/__init__.py +0 -1
  41. dodal/devices/electron_analyser/abstract/base_detector.py +3 -25
  42. dodal/devices/electron_analyser/abstract/base_driver_io.py +18 -9
  43. dodal/devices/electron_analyser/abstract/base_region.py +34 -3
  44. dodal/devices/electron_analyser/detector.py +24 -0
  45. dodal/devices/electron_analyser/enums.py +5 -0
  46. dodal/devices/electron_analyser/specs/detector.py +2 -1
  47. dodal/devices/electron_analyser/specs/driver_io.py +21 -26
  48. dodal/devices/electron_analyser/specs/region.py +1 -1
  49. dodal/devices/electron_analyser/util.py +20 -0
  50. dodal/devices/electron_analyser/vgscienta/__init__.py +3 -3
  51. dodal/devices/electron_analyser/vgscienta/detector.py +2 -1
  52. dodal/devices/electron_analyser/vgscienta/driver_io.py +24 -32
  53. dodal/devices/electron_analyser/vgscienta/enums.py +0 -8
  54. dodal/devices/electron_analyser/vgscienta/region.py +2 -31
  55. dodal/devices/eurotherm.py +126 -0
  56. dodal/devices/fluorescence_detector_motion.py +3 -10
  57. dodal/devices/focusing_mirror.py +1 -1
  58. dodal/devices/i03/undulator_dcm.py +0 -1
  59. dodal/devices/i09/enums.py +8 -8
  60. dodal/devices/i10/diagnostics.py +4 -4
  61. dodal/devices/i10/i10_apple2.py +3 -6
  62. dodal/devices/i11/cyberstar_blower.py +34 -0
  63. dodal/devices/i11/diff_stages.py +55 -0
  64. dodal/devices/i11/mythen.py +165 -0
  65. dodal/devices/i11/nx100robot.py +153 -0
  66. dodal/devices/i11/spinner.py +30 -0
  67. dodal/devices/i13_1/merlin_controller.py +4 -4
  68. dodal/devices/i19/diffractometer.py +34 -0
  69. dodal/devices/i19/shutter.py +11 -1
  70. dodal/devices/i22/dcm.py +1 -1
  71. dodal/devices/i22/fswitch.py +3 -12
  72. dodal/devices/i24/aperture.py +3 -3
  73. dodal/devices/i24/dcm.py +11 -15
  74. dodal/devices/i24/dual_backlight.py +11 -12
  75. dodal/devices/i24/pmac.py +8 -7
  76. dodal/devices/mx_phase1/beamstop.py +10 -11
  77. dodal/devices/oav/pin_image_recognition/__init__.py +0 -3
  78. dodal/devices/p60/enums.py +8 -8
  79. dodal/devices/p60/lab_xray_source.py +3 -2
  80. dodal/devices/pressure_jump_cell.py +77 -123
  81. dodal/devices/scintillator.py +76 -4
  82. dodal/devices/smargon.py +2 -2
  83. dodal/devices/synchrotron.py +1 -2
  84. dodal/devices/thawer.py +6 -11
  85. dodal/devices/undulator.py +3 -8
  86. dodal/devices/util/epics_util.py +1 -1
  87. dodal/devices/watsonmarlow323_pump.py +7 -7
  88. dodal/devices/xbpm_feedback.py +4 -6
  89. dodal/devices/xspress3/xspress3.py +0 -5
  90. dodal/devices/zocalo/zocalo_results.py +1 -3
  91. dodal/testing/__init__.py +0 -0
  92. dodal/testing/electron_analyser/__init__.py +6 -0
  93. dodal/testing/electron_analyser/device_factory.py +59 -0
  94. dodal/devices/CTAB.py +0 -41
  95. {dls_dodal-1.55.1.dist-info → dls_dodal-1.56.0.dist-info}/WHEEL +0 -0
  96. {dls_dodal-1.55.1.dist-info → dls_dodal-1.56.0.dist-info}/entry_points.txt +0 -0
  97. {dls_dodal-1.55.1.dist-info → dls_dodal-1.56.0.dist-info}/licenses/LICENSE +0 -0
  98. {dls_dodal-1.55.1.dist-info → dls_dodal-1.56.0.dist-info}/top_level.txt +0 -0
@@ -4,10 +4,30 @@ from dodal.devices.electron_analyser.enums import EnergyMode
4
4
  def to_kinetic_energy(
5
5
  value: float, value_mode: EnergyMode, excitation_energy: float
6
6
  ) -> float:
7
+ """
8
+ Convert a value that is binding energy to kinetic energy.
9
+ Parameters:
10
+ value: The value to convert.
11
+ value_mode: Energy mode of the value. If it is already kinetic, return the
12
+ same value. If it is binding, convert to kinetic.
13
+ excitation_energy: Value to calculate the conversion.
14
+ Returns:
15
+ Caluclated kinetic energy value
16
+ """
7
17
  return value if value_mode == EnergyMode.KINETIC else excitation_energy - value
8
18
 
9
19
 
10
20
  def to_binding_energy(
11
21
  value: float, value_mode: EnergyMode, excitation_energy: float
12
22
  ) -> float:
23
+ """
24
+ Convert a value that is kinetic energy to binding energy.
25
+ Parameters:
26
+ value: The value to convert.
27
+ value_mode: Energy mode of the value. If it is already binding, return the
28
+ same value. If it is kinetic, convert to binding.
29
+ excitation_energy: Value to calculate the conversion.
30
+ Returns:
31
+ Caluclated binding energy value
32
+ """
13
33
  return value if value_mode == EnergyMode.BINDING else excitation_energy - value
@@ -1,13 +1,13 @@
1
1
  from .detector import VGScientaDetector
2
2
  from .driver_io import VGScientaAnalyserDriverIO
3
- from .enums import AcquisitionMode
4
- from .region import VGScientaExcitationEnergySource, VGScientaRegion, VGScientaSequence
3
+ from .enums import AcquisitionMode, DetectorMode
4
+ from .region import VGScientaRegion, VGScientaSequence
5
5
 
6
6
  __all__ = [
7
7
  "VGScientaDetector",
8
8
  "VGScientaAnalyserDriverIO",
9
9
  "AcquisitionMode",
10
- "VGScientaExcitationEnergySource",
10
+ "DetectorMode",
11
11
  "VGScientaRegion",
12
12
  "VGScientaSequence",
13
13
  ]
@@ -11,6 +11,7 @@ from dodal.devices.electron_analyser.abstract.types import (
11
11
  from dodal.devices.electron_analyser.detector import (
12
12
  ElectronAnalyserDetector,
13
13
  )
14
+ from dodal.devices.electron_analyser.enums import SelectedSource
14
15
  from dodal.devices.electron_analyser.vgscienta.driver_io import (
15
16
  VGScientaAnalyserDriverIO,
16
17
  )
@@ -34,7 +35,7 @@ class VGScientaDetector(
34
35
  lens_mode_type: type[TLensMode],
35
36
  psu_mode_type: type[TPsuMode],
36
37
  pass_energy_type: type[TPassEnergyEnum],
37
- energy_sources: Mapping[str, SignalR[float]],
38
+ energy_sources: Mapping[SelectedSource, SignalR[float]],
38
39
  name: str = "",
39
40
  ):
40
41
  driver = VGScientaAnalyserDriverIO[TLensMode, TPsuMode, TPassEnergyEnum](
@@ -9,7 +9,6 @@ from ophyd_async.core import (
9
9
  SignalR,
10
10
  StandardReadableFormat,
11
11
  )
12
- from ophyd_async.epics.adcore import ADImageMode
13
12
  from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
14
13
 
15
14
  from dodal.devices.electron_analyser.abstract.base_driver_io import (
@@ -20,7 +19,7 @@ from dodal.devices.electron_analyser.abstract.types import (
20
19
  TPassEnergyEnum,
21
20
  TPsuMode,
22
21
  )
23
- from dodal.devices.electron_analyser.util import to_kinetic_energy
22
+ from dodal.devices.electron_analyser.enums import EnergyMode, SelectedSource
24
23
  from dodal.devices.electron_analyser.vgscienta.enums import (
25
24
  AcquisitionMode,
26
25
  DetectorMode,
@@ -46,7 +45,7 @@ class VGScientaAnalyserDriverIO(
46
45
  lens_mode_type: type[TLensMode],
47
46
  psu_mode_type: type[TPsuMode],
48
47
  pass_energy_type: type[TPassEnergyEnum],
49
- energy_sources: Mapping[str, SignalR[float]],
48
+ energy_sources: Mapping[SelectedSource, SignalR[float]],
50
49
  name: str = "",
51
50
  ) -> None:
52
51
  with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
@@ -55,11 +54,11 @@ class VGScientaAnalyserDriverIO(
55
54
 
56
55
  self.region_min_x = epics_signal_rw(int, prefix + "MinX")
57
56
  self.region_size_x = epics_signal_rw(int, prefix + "SizeX")
58
- self.sensor_max_size_x = epics_signal_rw(int, prefix + "MaxSizeX")
57
+ self.sensor_max_size_x = epics_signal_r(int, prefix + "MaxSizeX_RBV")
59
58
 
60
59
  self.region_min_y = epics_signal_rw(int, prefix + "MinY")
61
60
  self.region_size_y = epics_signal_rw(int, prefix + "SizeY")
62
- self.sensor_max_size_y = epics_signal_rw(int, prefix + "MaxSizeY")
61
+ self.sensor_max_size_y = epics_signal_r(int, prefix + "MaxSizeY_RBV")
63
62
 
64
63
  super().__init__(
65
64
  prefix,
@@ -75,36 +74,29 @@ class VGScientaAnalyserDriverIO(
75
74
  async def set(self, region: VGScientaRegion[TLensMode, TPassEnergyEnum]):
76
75
  source = self._get_energy_source(region.excitation_energy_source)
77
76
  excitation_energy = await source.get_value() # eV
78
-
79
- low_energy = to_kinetic_energy(
80
- region.low_energy, region.energy_mode, excitation_energy
81
- )
82
- centre_energy = to_kinetic_energy(
83
- region.centre_energy, region.energy_mode, excitation_energy
84
- )
85
- high_energy = to_kinetic_energy(
86
- region.high_energy, region.energy_mode, excitation_energy
87
- )
77
+ # Copy region so doesn't alter the actual region and switch to kinetic energy
78
+ ke_region = region.model_copy()
79
+ ke_region.switch_energy_mode(EnergyMode.KINETIC, excitation_energy)
88
80
  await asyncio.gather(
89
- self.region_name.set(region.name),
90
- self.energy_mode.set(region.energy_mode),
91
- self.low_energy.set(low_energy),
92
- self.centre_energy.set(centre_energy),
93
- self.high_energy.set(high_energy),
94
- self.slices.set(region.slices),
95
- self.lens_mode.set(region.lens_mode),
96
- self.pass_energy.set(region.pass_energy),
97
- self.iterations.set(region.iterations),
98
- self.acquisition_mode.set(region.acquisition_mode),
81
+ self.region_name.set(ke_region.name),
82
+ self.energy_mode.set(ke_region.energy_mode),
83
+ self.low_energy.set(ke_region.low_energy),
84
+ self.centre_energy.set(ke_region.centre_energy),
85
+ self.high_energy.set(ke_region.high_energy),
86
+ self.slices.set(ke_region.slices),
87
+ self.lens_mode.set(ke_region.lens_mode),
88
+ self.pass_energy.set(ke_region.pass_energy),
89
+ self.iterations.set(ke_region.iterations),
90
+ self.acquire_time.set(ke_region.acquire_time),
91
+ self.acquisition_mode.set(ke_region.acquisition_mode),
99
92
  self.excitation_energy.set(excitation_energy),
100
93
  self.excitation_energy_source.set(source.name),
101
- self.energy_step.set(region.energy_step),
102
- self.image_mode.set(ADImageMode.SINGLE),
103
- self.detector_mode.set(region.detector_mode),
104
- self.region_min_x.set(region.min_x),
105
- self.region_size_x.set(region.size_x),
106
- self.region_min_y.set(region.min_y),
107
- self.region_size_y.set(region.size_y),
94
+ self.energy_step.set(ke_region.energy_step),
95
+ self.detector_mode.set(ke_region.detector_mode),
96
+ self.region_min_x.set(ke_region.min_x),
97
+ self.region_size_x.set(ke_region.size_x),
98
+ self.region_min_y.set(ke_region.min_y),
99
+ self.region_size_y.set(ke_region.size_y),
108
100
  )
109
101
 
110
102
  def _create_energy_axis_signal(self, prefix: str) -> SignalR[Array1D[np.float64]]:
@@ -1,14 +1,6 @@
1
1
  from ophyd_async.core import StrictEnum
2
2
 
3
3
 
4
- class Status(StrictEnum):
5
- READY = "Ready"
6
- RUNNING = "Running"
7
- COMPLETED = "Completed"
8
- INVALID = "Invalid"
9
- ABORTED = "Aborted"
10
-
11
-
12
4
  class DetectorMode(StrictEnum):
13
5
  ADC = "ADC"
14
6
  PULSE_COUNTING = "Pulse Counting"
@@ -6,7 +6,6 @@ from pydantic import Field, field_validator
6
6
  from dodal.devices.electron_analyser.abstract.base_region import (
7
7
  AbstractBaseRegion,
8
8
  AbstractBaseSequence,
9
- JavaToPythonModel,
10
9
  )
11
10
  from dodal.devices.electron_analyser.abstract.types import (
12
11
  TLensMode,
@@ -16,7 +15,6 @@ from dodal.devices.electron_analyser.abstract.types import (
16
15
  from dodal.devices.electron_analyser.vgscienta.enums import (
17
16
  AcquisitionMode,
18
17
  DetectorMode,
19
- Status,
20
18
  )
21
19
 
22
20
 
@@ -29,10 +27,10 @@ class VGScientaRegion(
29
27
  pass_energy: TPassEnergyEnum
30
28
  acquisition_mode: AcquisitionMode = AcquisitionMode.SWEPT
31
29
  low_energy: float = 8.0
30
+ centre_energy: float = Field(alias="fix_energy", default=9)
32
31
  high_energy: float = 10.0
33
- step_time: float = 1.0
32
+ acquire_time: float = Field(default=1.0, alias="step_time")
34
33
  energy_step: float = Field(default=200.0)
35
- centre_energy: float = Field(alias="fix_energy", default=9)
36
34
  # Specific to this class
37
35
  id: str = Field(default=str(uuid.uuid4()), alias="region_id")
38
36
  total_steps: float = 13.0
@@ -42,7 +40,6 @@ class VGScientaRegion(
42
40
  min_y: int = Field(alias="first_y_channel", default=101)
43
41
  sensor_max_size_y: int = Field(alias="last_y_channel", default=800)
44
42
  detector_mode: DetectorMode = DetectorMode.ADC
45
- status: Status = Status.READY
46
43
 
47
44
  @property
48
45
  def size_x(self) -> int:
@@ -60,37 +57,11 @@ class VGScientaRegion(
60
57
  return str(val)
61
58
 
62
59
 
63
- class VGScientaExcitationEnergySource(JavaToPythonModel):
64
- name: str = "source1"
65
- device_name: str = Field(default="", alias="scannable_name")
66
- value: float = 0
67
-
68
-
69
60
  class VGScientaSequence(
70
61
  AbstractBaseSequence[VGScientaRegion[TLensMode, TPassEnergyEnum]],
71
62
  Generic[TLensMode, TPsuMode, TPassEnergyEnum],
72
63
  ):
73
64
  psu_mode: TPsuMode = Field(alias="element_set")
74
- excitation_energy_sources: list[VGScientaExcitationEnergySource] = Field(
75
- default_factory=lambda: []
76
- )
77
65
  regions: list[VGScientaRegion[TLensMode, TPassEnergyEnum]] = Field(
78
66
  default_factory=lambda: []
79
67
  )
80
-
81
- def get_excitation_energy_source_by_region(
82
- self, region: VGScientaRegion[TLensMode, TPassEnergyEnum]
83
- ) -> VGScientaExcitationEnergySource:
84
- value = next(
85
- (
86
- e
87
- for e in self.excitation_energy_sources
88
- if region.excitation_energy_source == e.name
89
- ),
90
- None,
91
- )
92
- if value is None:
93
- raise ValueError(
94
- f'Unable to find excitation energy source using region "{region.name}"'
95
- )
96
- return value
@@ -0,0 +1,126 @@
1
+ from typing import Generic, TypeVar
2
+
3
+ from bluesky.protocols import Locatable, Location
4
+ from ophyd_async.core import (
5
+ AsyncStatus,
6
+ OnOff,
7
+ StandardReadable,
8
+ StandardReadableFormat,
9
+ StrictEnum,
10
+ )
11
+ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw, epics_signal_rw_rbv
12
+
13
+ """
14
+ Note: See i11 cyberstar blower for implementation of Eurotherm Controller
15
+ """
16
+
17
+
18
+ class AutoManual(StrictEnum):
19
+ AUTO = "Automatic"
20
+ MANUAL = "Manual"
21
+
22
+
23
+ class EurothermUpdate(StrictEnum):
24
+ PASSIVE = "Passive"
25
+ EVENT = "Event"
26
+ IO = "I/O Intr"
27
+ S10 = "10 second"
28
+ S5 = "5 second"
29
+ S2 = "2 second"
30
+ S1 = "1 second"
31
+ S0_5 = ".5 second"
32
+ S0_2 = ".2 second"
33
+ S0_1 = ".1 second"
34
+
35
+
36
+ _EUROTHERM_RBV: str = ":RBV"
37
+
38
+
39
+ class EurothermPID(StandardReadable):
40
+ """The class for the Eurotherm PID values"""
41
+
42
+ def __init__(
43
+ self,
44
+ prefix: str,
45
+ name: str = "",
46
+ ):
47
+ with self.add_children_as_readables():
48
+ self.P = epics_signal_rw_rbv(float, f"{prefix}P", _EUROTHERM_RBV)
49
+ self.I = epics_signal_rw_rbv(float, f"{prefix}I", _EUROTHERM_RBV)
50
+ self.D = epics_signal_rw_rbv(float, f"{prefix}D", _EUROTHERM_RBV)
51
+
52
+ super().__init__(name)
53
+
54
+
55
+ class UpdatingEurothermPID(EurothermPID):
56
+ """A Eurotherm PID controller that updates the PID values."""
57
+
58
+ def __init__(self, prefix: str, name: str = ""):
59
+ with self.add_children_as_readables():
60
+ self.update = epics_signal_rw(EurothermUpdate, f"{prefix}UPDATE.SCAN")
61
+
62
+ super().__init__(prefix=prefix, name=name)
63
+
64
+
65
+ P = TypeVar("P", bound=EurothermPID)
66
+
67
+
68
+ class EurothermGeneral(StandardReadable, Locatable[float], Generic[P]):
69
+ """A base class for any eurotherm controller."""
70
+
71
+ def __init__(
72
+ self,
73
+ prefix: str,
74
+ name: str = "",
75
+ pid_class: type[P] = EurothermPID,
76
+ temp_suffix: str = "PV:RBV",
77
+ ):
78
+ with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
79
+ self.temp = epics_signal_r(float, f"{prefix}{temp_suffix}")
80
+
81
+ with self.add_children_as_readables():
82
+ self.setpoint = epics_signal_rw_rbv(float, f"{prefix}SP", _EUROTHERM_RBV)
83
+ self.ramprate = epics_signal_rw_rbv(float, f"{prefix}RR", _EUROTHERM_RBV)
84
+ self.output = epics_signal_rw_rbv(float, f"{prefix}O", _EUROTHERM_RBV)
85
+ self.mode = epics_signal_rw_rbv(AutoManual, f"{prefix}MAN", _EUROTHERM_RBV)
86
+
87
+ self.tune = pid_class(prefix=prefix)
88
+
89
+ super().__init__(name)
90
+
91
+ @AsyncStatus.wrap
92
+ async def set(self, value: float):
93
+ """Set the blower to a specific temperature."""
94
+ await self.setpoint.set(value, wait=True)
95
+
96
+ async def locate(self) -> Location[float]:
97
+ setpoint = await self.setpoint.get_value()
98
+ current_temp = await self.temp.get_value()
99
+ location = Location(setpoint=setpoint, readback=current_temp)
100
+ return location
101
+
102
+
103
+ class UpdatingEurothermGeneral(EurothermGeneral):
104
+ """A Eurotherm controller that updates the setpoint and readback."""
105
+
106
+ def __init__(self, prefix: str, name: str = ""):
107
+ self.update = epics_signal_rw(EurothermUpdate, f"{prefix}UPDATE.SCAN")
108
+
109
+ super().__init__(prefix=prefix, name=name, pid_class=UpdatingEurothermPID)
110
+
111
+
112
+ class EurothermAutotune(StandardReadable):
113
+ """Newer versions of Eurotherm controllers have the ability to Autotune the
114
+ PID values, and this is the device"""
115
+
116
+ def __init__(
117
+ self,
118
+ prefix: str,
119
+ name: str = "",
120
+ ):
121
+ with self.add_children_as_readables():
122
+ self.control = epics_signal_rw(OnOff, f"{prefix}AUTOTUNE")
123
+ self.high_limit = epics_signal_rw(float, f"{prefix}OUTPHI")
124
+ self.low_limit = epics_signal_rw(float, f"{prefix}OUTPLO")
125
+
126
+ super().__init__(name)
@@ -1,16 +1,9 @@
1
- from ophyd_async.core import StandardReadable, StrictEnum
2
- from ophyd_async.epics.core import epics_signal_r
3
-
4
-
5
- class FluorescenceDetectorControlState(StrictEnum):
6
- OUT = "Out"
7
- IN = "In"
1
+ from ophyd_async.core import InOut, StandardReadable
2
+ from ophyd_async.epics.core import epics_signal_rw
8
3
 
9
4
 
10
5
  class FluorescenceDetector(StandardReadable):
11
6
  def __init__(self, prefix: str, name: str = ""):
12
7
  with self.add_children_as_readables():
13
- self.pos = epics_signal_r(
14
- FluorescenceDetectorControlState, prefix + "-EA-FLU-01:CTRL"
15
- )
8
+ self.pos = epics_signal_rw(InOut, f"{prefix}CTRL")
16
9
  super().__init__(name)
@@ -56,7 +56,7 @@ class SingleMirrorVoltage(Device):
56
56
  the demanded voltage setpoint is accepted, without blocking the caller as this process can take significant time.
57
57
  """
58
58
 
59
- def __init__(self, name: str = "", prefix: str = ""):
59
+ def __init__(self, prefix: str, name: str = ""):
60
60
  self._actual_v = epics_signal_r(int, prefix + "R")
61
61
  self._setpoint_v = epics_signal_rw(int, prefix + "D")
62
62
  self._demand_accepted = epics_signal_r(MirrorVoltageDemand, prefix + "DSEV")
@@ -34,7 +34,6 @@ class UndulatorDCM(StandardReadable, Movable[float]):
34
34
  undulator: Undulator,
35
35
  dcm: DCM,
36
36
  daq_configuration_path: str,
37
- prefix: str = "",
38
37
  name: str = "",
39
38
  ):
40
39
  self.undulator_ref = Reference(undulator)
@@ -21,11 +21,11 @@ class PsuMode(StrictEnum):
21
21
 
22
22
 
23
23
  class PassEnergy(StrictEnum):
24
- E5 = 5
25
- E10 = 10
26
- E20 = 20
27
- E50 = 50
28
- E70 = 70
29
- E100 = 100
30
- E200 = 200
31
- E500 = 500
24
+ E5 = "5"
25
+ E10 = "10"
26
+ E20 = "20"
27
+ E50 = "50"
28
+ E70 = "70"
29
+ E100 = "100"
30
+ E200 = "200"
31
+ E500 = "500"
@@ -54,13 +54,13 @@ class D7Position(StrictEnum):
54
54
  SHUTTER = "Shutter"
55
55
 
56
56
 
57
- class InOutTable(StrictEnum):
57
+ class InStateTable(StrictEnum):
58
58
  MOVE_IN = "Move In"
59
59
  MOVE_OUT = "Move Out"
60
60
  RESET = "Reset"
61
61
 
62
62
 
63
- class InOutReadBackTable(StrictEnum):
63
+ class InStateReadBackTable(StrictEnum):
64
64
  MOVE_IN = "Moving In"
65
65
  MOVE_OUT = "Moving Out"
66
66
  IN_BEAM = "In Beam"
@@ -80,11 +80,11 @@ class I10PneumaticStage(StandardReadable):
80
80
  ) -> None:
81
81
  with self.add_children_as_readables(Format.HINTED_SIGNAL):
82
82
  self.stage_position_set = epics_signal_rw(
83
- InOutTable,
83
+ InStateTable,
84
84
  read_pv=prefix + "CON",
85
85
  )
86
86
  self.stage_position_readback = epics_signal_r(
87
- InOutReadBackTable,
87
+ InStateReadBackTable,
88
88
  read_pv=prefix + "STA",
89
89
  )
90
90
  super().__init__(name=name)
@@ -69,7 +69,7 @@ class I10Apple2(Apple2):
69
69
  self,
70
70
  look_up_table_dir: str,
71
71
  source: tuple[str, str],
72
- prefix: str = "",
72
+ prefix: str,
73
73
  mode: str = "Mode",
74
74
  min_energy: str = "MinEnergy",
75
75
  max_energy: str = "MaxEnergy",
@@ -97,11 +97,9 @@ class I10Apple2(Apple2):
97
97
  Name of the device
98
98
  """
99
99
 
100
- energy_gap_table_path = Path(
101
- look_up_table_dir + "IDEnergy2GapCalibrations.csv",
102
- )
100
+ energy_gap_table_path = Path(look_up_table_dir, "IDEnergy2GapCalibrations.csv")
103
101
  energy_phase_table_path = Path(
104
- look_up_table_dir + "IDEnergy2PhaseCalibrations.csv",
102
+ look_up_table_dir, "IDEnergy2PhaseCalibrations.csv"
105
103
  )
106
104
  # A dataclass contains the path to the look up table and the expected column names.
107
105
  self.lookup_table_config = LookupTableConfig(
@@ -124,7 +122,6 @@ class I10Apple2(Apple2):
124
122
  btm_inner="RPQ3",
125
123
  btm_outer="RPQ4",
126
124
  ),
127
- prefix=prefix,
128
125
  name=name,
129
126
  )
130
127
  self.id_jaw_phase = UndulatorJawPhase(
@@ -0,0 +1,34 @@
1
+ from typing import Generic, TypeVar
2
+
3
+ from ophyd_async.core import EnabledDisabled, StandardReadable
4
+ from ophyd_async.epics.core import epics_signal_rw
5
+
6
+ from dodal.devices.eurotherm import EurothermAutotune, EurothermGeneral
7
+
8
+ EU = TypeVar("EU", bound=EurothermGeneral)
9
+
10
+
11
+ class CyberstarBlower(StandardReadable, Generic[EU]):
12
+ """This is a specific device that uses a Eurotherm controller"""
13
+
14
+ def __init__(
15
+ self,
16
+ prefix: str,
17
+ name: str = "",
18
+ controller_type: type[EU] = EurothermGeneral,
19
+ ):
20
+ self.enable = epics_signal_rw(EnabledDisabled, f"{prefix}DISABLE")
21
+ self.controller = controller_type(prefix, name)
22
+
23
+ super().__init__(name=name)
24
+
25
+
26
+ class AutotunedCyberstarBlower(Generic[EU], CyberstarBlower[EU]):
27
+ def __init__(
28
+ self,
29
+ prefix: str,
30
+ name: str = "",
31
+ controller_type: type[EU] = EurothermGeneral,
32
+ ):
33
+ self.autotune = EurothermAutotune(prefix)
34
+ super().__init__(prefix, name=name, controller_type=controller_type)
@@ -0,0 +1,55 @@
1
+ from ophyd_async.epics.motor import Motor
2
+
3
+ from dodal.devices.motors import Stage
4
+
5
+
6
+ class DiffractometerStage(Stage):
7
+ """
8
+ This is the diffractometer stage which contains both detectors,
9
+ it allows for rotations and also sample position. Contains:
10
+ theta, delta, two_theta, sample_position
11
+ """
12
+
13
+ def __init__(
14
+ self,
15
+ prefix: str,
16
+ name: str = "",
17
+ theta_suffix: str = "THETA",
18
+ delta_suffix: str = "DELTA",
19
+ two_theta_suffix: str = "2THETA",
20
+ sample_pos_suffix: str = "SPOS",
21
+ ):
22
+ with self.add_children_as_readables():
23
+ self.theta = Motor(prefix + theta_suffix)
24
+ self.delta = Motor(prefix + delta_suffix)
25
+ self.two_theta = Motor(prefix + two_theta_suffix)
26
+ self.sample_position = Motor(prefix + sample_pos_suffix)
27
+
28
+ super().__init__(name=name)
29
+
30
+
31
+ class DiffractometerBase(Stage):
32
+ """
33
+ This is the diffractometer stage which contains both detectors,
34
+ it allows for translation about x and y and also sample position. Contains:
35
+ x1, x2, y1, y2, y3. Used for aligning the detector to the beam/sample
36
+ """
37
+
38
+ def __init__(
39
+ self,
40
+ prefix: str,
41
+ name: str = "",
42
+ x1_suffix: str = "X1",
43
+ x2_suffix: str = "X2",
44
+ y1_suffix: str = "Y1",
45
+ y2_suffix: str = "Y2",
46
+ y3_suffix: str = "Y3",
47
+ ):
48
+ with self.add_children_as_readables():
49
+ self.x1 = Motor(prefix + x1_suffix)
50
+ self.x2 = Motor(prefix + x2_suffix)
51
+ self.y1 = Motor(prefix + y1_suffix)
52
+ self.y2 = Motor(prefix + y2_suffix)
53
+ self.y3 = Motor(prefix + y3_suffix)
54
+
55
+ super().__init__(name=name)