dls-dodal 1.48.0__py3-none-any.whl → 1.50.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 (41) hide show
  1. {dls_dodal-1.48.0.dist-info → dls_dodal-1.50.0.dist-info}/METADATA +2 -1
  2. {dls_dodal-1.48.0.dist-info → dls_dodal-1.50.0.dist-info}/RECORD +41 -30
  3. dodal/_version.py +2 -2
  4. dodal/beamlines/aithre.py +15 -0
  5. dodal/beamlines/b16.py +65 -0
  6. dodal/beamlines/b18.py +38 -0
  7. dodal/beamlines/i10.py +41 -233
  8. dodal/beamlines/k11.py +35 -0
  9. dodal/common/beamlines/device_helpers.py +1 -0
  10. dodal/devices/apple2_undulator.py +257 -136
  11. dodal/devices/b16/__init__.py +0 -0
  12. dodal/devices/b16/detector.py +34 -0
  13. dodal/devices/bimorph_mirror.py +29 -36
  14. dodal/devices/electron_analyser/__init__.py +12 -2
  15. dodal/devices/electron_analyser/abstract/base_detector.py +3 -128
  16. dodal/devices/electron_analyser/abstract/base_driver_io.py +8 -3
  17. dodal/devices/electron_analyser/abstract/base_region.py +6 -3
  18. dodal/devices/electron_analyser/detector.py +141 -0
  19. dodal/devices/electron_analyser/enums.py +6 -0
  20. dodal/devices/electron_analyser/specs/__init__.py +2 -0
  21. dodal/devices/electron_analyser/specs/detector.py +1 -1
  22. dodal/devices/electron_analyser/specs/driver_io.py +4 -5
  23. dodal/devices/electron_analyser/specs/enums.py +8 -0
  24. dodal/devices/electron_analyser/specs/region.py +3 -2
  25. dodal/devices/electron_analyser/types.py +30 -4
  26. dodal/devices/electron_analyser/util.py +1 -1
  27. dodal/devices/electron_analyser/vgscienta/__init__.py +2 -0
  28. dodal/devices/electron_analyser/vgscienta/detector.py +1 -1
  29. dodal/devices/electron_analyser/vgscienta/driver_io.py +2 -1
  30. dodal/devices/electron_analyser/vgscienta/enums.py +19 -0
  31. dodal/devices/electron_analyser/vgscienta/region.py +7 -22
  32. dodal/devices/hutch_shutter.py +6 -6
  33. dodal/devices/i10/__init__.py +0 -0
  34. dodal/devices/i10/i10_apple2.py +181 -126
  35. dodal/devices/i22/nxsas.py +1 -1
  36. dodal/devices/oav/oav_detector.py +45 -7
  37. dodal/plans/bimorph.py +333 -0
  38. {dls_dodal-1.48.0.dist-info → dls_dodal-1.50.0.dist-info}/WHEEL +0 -0
  39. {dls_dodal-1.48.0.dist-info → dls_dodal-1.50.0.dist-info}/entry_points.txt +0 -0
  40. {dls_dodal-1.48.0.dist-info → dls_dodal-1.50.0.dist-info}/licenses/LICENSE +0 -0
  41. {dls_dodal-1.48.0.dist-info → dls_dodal-1.50.0.dist-info}/top_level.txt +0 -0
@@ -1,10 +1,8 @@
1
1
  import asyncio
2
- from collections.abc import Mapping
3
2
  from typing import Annotated as A
4
3
 
5
4
  from bluesky.protocols import Movable
6
5
  from ophyd_async.core import (
7
- DEFAULT_TIMEOUT,
8
6
  AsyncStatus,
9
7
  DeviceVector,
10
8
  SignalR,
@@ -12,6 +10,7 @@ from ophyd_async.core import (
12
10
  SignalW,
13
11
  StandardReadable,
14
12
  StrictEnum,
13
+ set_and_wait_for_other_value,
15
14
  wait_for_value,
16
15
  )
17
16
  from ophyd_async.core import StandardReadableFormat as Format
@@ -23,6 +22,8 @@ from ophyd_async.epics.core import (
23
22
  epics_signal_x,
24
23
  )
25
24
 
25
+ DEFAULT_TIMEOUT = 60
26
+
26
27
 
27
28
  class BimorphMirrorOnOff(StrictEnum):
28
29
  ON = "ON"
@@ -41,7 +42,7 @@ class BimorphMirrorStatus(StrictEnum):
41
42
  ERROR = "Error"
42
43
 
43
44
 
44
- class BimorphMirrorChannel(StandardReadable, Movable[float], EpicsDevice):
45
+ class BimorphMirrorChannel(StandardReadable, EpicsDevice):
45
46
  """Collection of PVs comprising a single bimorph channel.
46
47
 
47
48
  Attributes:
@@ -56,23 +57,13 @@ class BimorphMirrorChannel(StandardReadable, Movable[float], EpicsDevice):
56
57
  status: A[SignalR[BimorphMirrorOnOff], PvSuffix("STATUS"), Format.CONFIG_SIGNAL]
57
58
  shift: A[SignalW[float], PvSuffix("SHIFT")]
58
59
 
59
- @AsyncStatus.wrap
60
- async def set(self, value: float):
61
- """Sets channel's VOUT to given value.
62
-
63
- Args:
64
- value: float to set VOUT to
65
- """
66
- await self.output_voltage.set(value)
67
-
68
60
 
69
- class BimorphMirror(StandardReadable, Movable[Mapping[int, float]]):
61
+ class BimorphMirror(StandardReadable, Movable[list[float]]):
70
62
  """Class to represent CAENels Bimorph Mirrors.
71
63
 
72
64
  Attributes:
73
65
  channels: DeviceVector of BimorphMirrorChannel, indexed from 1, for each channel
74
66
  enabled: Writeable BimorphOnOff
75
- commit_target_voltages: Procable signal that writes values in each channel's VTRGT to VOUT
76
67
  status: Readable BimorphMirrorStatus Busy/Idle status
77
68
  err: Alarm status"""
78
69
 
@@ -103,49 +94,51 @@ class BimorphMirror(StandardReadable, Movable[Mapping[int, float]]):
103
94
  super().__init__(name=name)
104
95
 
105
96
  @AsyncStatus.wrap
106
- async def set(self, value: Mapping[int, float], tolerance: float = 0.0001) -> None:
107
- """Sets bimorph voltages in parrallel via target voltage and all proc.
97
+ async def set(self, value: list[float]) -> None:
98
+ """Sets bimorph voltages in parallel via target voltage and all proc.
108
99
 
109
100
  Args:
110
- value: Dict of channel numbers to target voltages
101
+ value: List of float target voltages
111
102
 
112
103
  Raises:
113
104
  ValueError: On set to non-existent channel"""
114
105
 
115
- if any(key not in self.channels for key in value):
106
+ if len(value) != len(self.channels):
116
107
  raise ValueError(
117
- f"Attempting to put to non-existent channels: {[key for key in value if (key not in self.channels)]}"
108
+ f"Length of value input array does not match number of \
109
+ channels: {len(value)} and {len(self.channels)}"
118
110
  )
119
111
 
120
- # Write target voltages:
121
- await asyncio.gather(
122
- *[
123
- self.channels[i].target_voltage.set(target, wait=True)
124
- for i, target in value.items()
125
- ]
126
- )
112
+ # Write target voltages in serial
113
+ # Voltages are written in serial as bimorph PSU cannot handle simultaneous sets
114
+ for i, target in enumerate(value):
115
+ await wait_for_value(
116
+ self.status, BimorphMirrorStatus.IDLE, timeout=DEFAULT_TIMEOUT
117
+ )
118
+ await set_and_wait_for_other_value(
119
+ self.channels[i + 1].target_voltage,
120
+ target,
121
+ self.status,
122
+ BimorphMirrorStatus.BUSY,
123
+ )
127
124
 
128
125
  # Trigger set target voltages:
126
+ await wait_for_value(
127
+ self.status, BimorphMirrorStatus.IDLE, timeout=DEFAULT_TIMEOUT
128
+ )
129
129
  await self.commit_target_voltages.trigger()
130
130
 
131
131
  # Wait for values to propogate to voltage out rbv:
132
132
  await asyncio.gather(
133
133
  *[
134
134
  wait_for_value(
135
- self.channels[i].output_voltage,
136
- tolerance_func_builder(tolerance, target),
135
+ self.channels[i + 1].output_voltage,
136
+ target,
137
137
  timeout=DEFAULT_TIMEOUT,
138
138
  )
139
- for i, target in value.items()
139
+ for i, target in enumerate(value)
140
140
  ],
141
141
  wait_for_value(
142
142
  self.status, BimorphMirrorStatus.IDLE, timeout=DEFAULT_TIMEOUT
143
143
  ),
144
144
  )
145
-
146
-
147
- def tolerance_func_builder(tolerance: float, target_value: float):
148
- def is_within_value(x):
149
- return abs(x - target_value) <= tolerance
150
-
151
- return is_within_value
@@ -1,10 +1,16 @@
1
- from .abstract.base_detector import (
1
+ from .detector import (
2
2
  ElectronAnalyserDetector,
3
3
  ElectronAnalyserRegionDetector,
4
4
  TElectronAnalyserDetector,
5
5
  TElectronAnalyserRegionDetector,
6
6
  )
7
- from .types import EnergyMode
7
+ from .enums import EnergyMode
8
+ from .types import (
9
+ ElectronAnalyserDetectorImpl,
10
+ ElectronAnalyserDriverImpl,
11
+ GenericElectronAnalyserDetector,
12
+ GenericElectronAnalyserRegionDetector,
13
+ )
8
14
  from .util import to_binding_energy, to_kinetic_energy
9
15
 
10
16
  __all__ = [
@@ -12,7 +18,11 @@ __all__ = [
12
18
  "to_kinetic_energy",
13
19
  "EnergyMode",
14
20
  "ElectronAnalyserDetector",
21
+ "ElectronAnalyserDetectorImpl",
22
+ "ElectronAnalyserDriverImpl",
15
23
  "TElectronAnalyserDetector",
16
24
  "ElectronAnalyserRegionDetector",
17
25
  "TElectronAnalyserRegionDetector",
26
+ "GenericElectronAnalyserDetector",
27
+ "GenericElectronAnalyserRegionDetector",
18
28
  ]
@@ -1,30 +1,23 @@
1
1
  import asyncio
2
2
  from abc import abstractmethod
3
- from typing import Generic, TypeVar
3
+ from typing import Generic
4
4
 
5
- from bluesky.protocols import Preparable, Reading, Stageable, Triggerable
5
+ from bluesky.protocols import Reading, Stageable, Triggerable
6
6
  from event_model import DataKey
7
7
  from ophyd_async.core import (
8
8
  AsyncConfigurable,
9
9
  AsyncReadable,
10
10
  AsyncStatus,
11
11
  Device,
12
- Reference,
13
12
  )
14
13
  from ophyd_async.epics.adcore import (
15
14
  ADBaseController,
16
15
  )
17
- from ophyd_async.epics.motor import Motor
18
16
 
19
- from dodal.common.data_util import load_json_file_to_class
20
17
  from dodal.devices.electron_analyser.abstract.base_driver_io import (
21
18
  AbstractAnalyserDriverIO,
22
19
  TAbstractAnalyserDriverIO,
23
20
  )
24
- from dodal.devices.electron_analyser.abstract.base_region import (
25
- TAbstractBaseRegion,
26
- TAbstractBaseSequence,
27
- )
28
21
 
29
22
 
30
23
  class ElectronAnalyserController(ADBaseController[AbstractAnalyserDriverIO]):
@@ -51,8 +44,8 @@ class AbstractElectronAnalyserDetector(
51
44
 
52
45
  def __init__(
53
46
  self,
54
- name: str,
55
47
  driver: TAbstractAnalyserDriverIO,
48
+ name: str = "",
56
49
  ):
57
50
  self.controller: ElectronAnalyserController = ElectronAnalyserController(
58
51
  driver=driver
@@ -103,121 +96,3 @@ class AbstractElectronAnalyserDetector(
103
96
  Returns:
104
97
  instance of the driver.
105
98
  """
106
-
107
-
108
- class ElectronAnalyserRegionDetector(
109
- AbstractElectronAnalyserDetector[TAbstractAnalyserDriverIO],
110
- Preparable,
111
- Generic[TAbstractAnalyserDriverIO, TAbstractBaseRegion],
112
- ):
113
- """
114
- Extends electron analyser detector to configure specific region settings before data
115
- acqusition. This object must be passed in a driver and store it as a reference. It
116
- is designed to only exist inside a plan.
117
- """
118
-
119
- def __init__(
120
- self, name: str, driver: TAbstractAnalyserDriverIO, region: TAbstractBaseRegion
121
- ):
122
- self._driver_ref = Reference(driver)
123
- self.region = region
124
- super().__init__(name, driver)
125
-
126
- @property
127
- def driver(self) -> TAbstractAnalyserDriverIO:
128
- # Store as a reference, this implementation will be given a driver so needs to
129
- # make sure we don't get conflicting parents.
130
- return self._driver_ref()
131
-
132
- @AsyncStatus.wrap
133
- async def prepare(self, value: Motor) -> None:
134
- """
135
- Prepare driver with the region stored and energy_source motor.
136
-
137
- Args:
138
- value: The excitation energy source that the region has selected.
139
- """
140
- excitation_energy_source = value
141
- await self.driver.prepare(excitation_energy_source)
142
- await self.driver.set(self.region)
143
-
144
-
145
- TElectronAnalyserRegionDetector = TypeVar(
146
- "TElectronAnalyserRegionDetector",
147
- bound=ElectronAnalyserRegionDetector,
148
- )
149
-
150
-
151
- class ElectronAnalyserDetector(
152
- AbstractElectronAnalyserDetector[TAbstractAnalyserDriverIO],
153
- Generic[
154
- TAbstractAnalyserDriverIO,
155
- TAbstractBaseSequence,
156
- TAbstractBaseRegion,
157
- ],
158
- ):
159
- """
160
- Electron analyser detector with the additional functionality to load a sequence file
161
- and create a list of temporary ElectronAnalyserRegionDetector objects. These will
162
- setup configured region settings before data acquisition.
163
- """
164
-
165
- def __init__(
166
- self,
167
- prefix: str,
168
- sequence_class: type[TAbstractBaseSequence],
169
- driver: TAbstractAnalyserDriverIO,
170
- name: str = "",
171
- ):
172
- # Pass in driver
173
- self._driver = driver
174
- self._sequence_class = sequence_class
175
- super().__init__(name, self.driver)
176
-
177
- @property
178
- def driver(self) -> TAbstractAnalyserDriverIO:
179
- # This implementation creates the driver and wants this to be the parent so it
180
- # can be used with connect() method.
181
- return self._driver
182
-
183
- def load_sequence(self, filename: str) -> TAbstractBaseSequence:
184
- """
185
- Load the sequence data from a provided json file into a sequence class.
186
-
187
- Args:
188
- filename: Path to the sequence file containing the region data.
189
-
190
- Returns:
191
- Pydantic model representing the sequence file.
192
- """
193
- return load_json_file_to_class(self._sequence_class, filename)
194
-
195
- def create_region_detector_list(
196
- self, filename: str, enabled_only=True
197
- ) -> list[
198
- ElectronAnalyserRegionDetector[TAbstractAnalyserDriverIO, TAbstractBaseRegion]
199
- ]:
200
- """
201
- Create a list of detectors equal to the number of regions in a sequence file.
202
- Each detector is responsible for setting up a specific region.
203
-
204
- Args:
205
- filename: Path to the sequence file containing the region data.
206
- enabled_only: If true, only include the region if enabled is True.
207
-
208
- Returns:
209
- List of ElectronAnalyserRegionDetector, equal to the number of regions in
210
- the sequence file.
211
- """
212
- seq = self.load_sequence(filename)
213
- regions = seq.get_enabled_regions() if enabled_only else seq.regions
214
- return [
215
- ElectronAnalyserRegionDetector(self.name + "_" + r.name, self.driver, r)
216
- for r in regions
217
- ]
218
-
219
-
220
- TElectronAnalyserDetector = TypeVar(
221
- "TElectronAnalyserDetector",
222
- bound=ElectronAnalyserDetector,
223
- )
@@ -10,6 +10,7 @@ from ophyd_async.core import (
10
10
  SignalR,
11
11
  StandardReadable,
12
12
  StandardReadableFormat,
13
+ StrictEnum,
13
14
  derived_signal_r,
14
15
  soft_signal_rw,
15
16
  )
@@ -20,7 +21,7 @@ from ophyd_async.epics.motor import Motor
20
21
  from dodal.devices.electron_analyser.abstract.base_region import (
21
22
  TAbstractBaseRegion,
22
23
  )
23
- from dodal.devices.electron_analyser.types import EnergyMode
24
+ from dodal.devices.electron_analyser.enums import EnergyMode
24
25
  from dodal.devices.electron_analyser.util import to_binding_energy, to_kinetic_energy
25
26
 
26
27
 
@@ -37,7 +38,9 @@ class AbstractAnalyserDriverIO(
37
38
  Electron analysers should inherit from this class for further specialisation.
38
39
  """
39
40
 
40
- def __init__(self, prefix: str, name: str = "") -> None:
41
+ def __init__(
42
+ self, prefix: str, acquisition_mode_type: type[StrictEnum], name: str = ""
43
+ ) -> None:
41
44
  with self.add_children_as_readables():
42
45
  self.image = epics_signal_r(Array1D[np.float64], prefix + "IMAGE")
43
46
  self.spectrum = epics_signal_r(Array1D[np.float64], prefix + "INT_SPECTRUM")
@@ -61,7 +64,9 @@ class AbstractAnalyserDriverIO(
61
64
  )
62
65
  self.energy_step = epics_signal_rw(float, prefix + "STEP_SIZE")
63
66
  self.iterations = epics_signal_rw(int, prefix + "NumExposures")
64
- self.acquisition_mode = epics_signal_rw(str, prefix + "ACQ_MODE")
67
+ self.acquisition_mode = epics_signal_rw(
68
+ acquisition_mode_type, prefix + "ACQ_MODE"
69
+ )
65
70
  self.excitation_energy_source = soft_signal_rw(str, initial_value="")
66
71
 
67
72
  with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
@@ -3,9 +3,12 @@ from abc import ABC
3
3
  from collections.abc import Callable
4
4
  from typing import Generic, TypeVar
5
5
 
6
+ from ophyd_async.core import StrictEnum
6
7
  from pydantic import BaseModel, Field, model_validator
7
8
 
8
- from dodal.devices.electron_analyser.types import EnergyMode
9
+ from dodal.devices.electron_analyser.enums import EnergyMode
10
+
11
+ TStrictEnum = TypeVar("TStrictEnum", bound=StrictEnum)
9
12
 
10
13
 
11
14
  def java_to_python_case(java_str: str) -> str:
@@ -43,7 +46,7 @@ def energy_mode_validation(data: dict) -> dict:
43
46
  return data
44
47
 
45
48
 
46
- class AbstractBaseRegion(ABC, JavaToPythonModel):
49
+ class AbstractBaseRegion(ABC, JavaToPythonModel, Generic[TStrictEnum]):
47
50
  """
48
51
  Generic region model that holds the data. Specialised region models should inherit
49
52
  this to extend functionality. All energy units are assumed to be in eV.
@@ -57,7 +60,7 @@ class AbstractBaseRegion(ABC, JavaToPythonModel):
57
60
  # These ones we need subclasses to provide default values
58
61
  lens_mode: str
59
62
  pass_energy: int
60
- acquisition_mode: str
63
+ acquisition_mode: TStrictEnum
61
64
  low_energy: float
62
65
  high_energy: float
63
66
  step_time: float
@@ -0,0 +1,141 @@
1
+ from typing import Generic, TypeVar
2
+
3
+ from bluesky.protocols import Preparable
4
+ from ophyd_async.core import (
5
+ AsyncStatus,
6
+ Reference,
7
+ )
8
+ from ophyd_async.epics.motor import Motor
9
+
10
+ from dodal.common.data_util import load_json_file_to_class
11
+ from dodal.devices.electron_analyser.abstract.base_detector import (
12
+ AbstractElectronAnalyserDetector,
13
+ )
14
+ from dodal.devices.electron_analyser.abstract.base_driver_io import (
15
+ TAbstractAnalyserDriverIO,
16
+ )
17
+ from dodal.devices.electron_analyser.abstract.base_region import (
18
+ TAbstractBaseRegion,
19
+ TAbstractBaseSequence,
20
+ )
21
+
22
+
23
+ class ElectronAnalyserRegionDetector(
24
+ AbstractElectronAnalyserDetector[TAbstractAnalyserDriverIO],
25
+ Preparable,
26
+ Generic[TAbstractAnalyserDriverIO, TAbstractBaseRegion],
27
+ ):
28
+ """
29
+ Extends electron analyser detector to configure specific region settings before data
30
+ acqusition. This object must be passed in a driver and store it as a reference. It
31
+ is designed to only exist inside a plan.
32
+ """
33
+
34
+ def __init__(
35
+ self,
36
+ driver: TAbstractAnalyserDriverIO,
37
+ region: TAbstractBaseRegion,
38
+ name: str = "",
39
+ ):
40
+ self._driver_ref = Reference(driver)
41
+ self.region = region
42
+ super().__init__(driver, name)
43
+
44
+ @property
45
+ def driver(self) -> TAbstractAnalyserDriverIO:
46
+ # Store as a reference, this implementation will be given a driver so needs to
47
+ # make sure we don't get conflicting parents.
48
+ return self._driver_ref()
49
+
50
+ @AsyncStatus.wrap
51
+ async def prepare(self, value: Motor) -> None:
52
+ """
53
+ Prepare driver with the region stored and energy_source motor.
54
+
55
+ Args:
56
+ value: The excitation energy source that the region has selected.
57
+ """
58
+ excitation_energy_source = value
59
+ await self.driver.prepare(excitation_energy_source)
60
+ await self.driver.set(self.region)
61
+
62
+
63
+ TElectronAnalyserRegionDetector = TypeVar(
64
+ "TElectronAnalyserRegionDetector",
65
+ bound=ElectronAnalyserRegionDetector,
66
+ )
67
+
68
+
69
+ class ElectronAnalyserDetector(
70
+ AbstractElectronAnalyserDetector[TAbstractAnalyserDriverIO],
71
+ Generic[
72
+ TAbstractAnalyserDriverIO,
73
+ TAbstractBaseSequence,
74
+ TAbstractBaseRegion,
75
+ ],
76
+ ):
77
+ """
78
+ Electron analyser detector with the additional functionality to load a sequence file
79
+ and create a list of temporary ElectronAnalyserRegionDetector objects. These will
80
+ setup configured region settings before data acquisition.
81
+ """
82
+
83
+ def __init__(
84
+ self,
85
+ prefix: str,
86
+ sequence_class: type[TAbstractBaseSequence],
87
+ driver: TAbstractAnalyserDriverIO,
88
+ name: str = "",
89
+ ):
90
+ # Pass in driver
91
+ self._driver = driver
92
+ self._sequence_class = sequence_class
93
+ super().__init__(self.driver, name)
94
+
95
+ @property
96
+ def driver(self) -> TAbstractAnalyserDriverIO:
97
+ # This implementation creates the driver and wants this to be the parent so it
98
+ # can be used with connect() method.
99
+ return self._driver
100
+
101
+ def load_sequence(self, filename: str) -> TAbstractBaseSequence:
102
+ """
103
+ Load the sequence data from a provided json file into a sequence class.
104
+
105
+ Args:
106
+ filename: Path to the sequence file containing the region data.
107
+
108
+ Returns:
109
+ Pydantic model representing the sequence file.
110
+ """
111
+ return load_json_file_to_class(self._sequence_class, filename)
112
+
113
+ def create_region_detector_list(
114
+ self, filename: str, enabled_only=True
115
+ ) -> list[
116
+ ElectronAnalyserRegionDetector[TAbstractAnalyserDriverIO, TAbstractBaseRegion]
117
+ ]:
118
+ """
119
+ Create a list of detectors equal to the number of regions in a sequence file.
120
+ Each detector is responsible for setting up a specific region.
121
+
122
+ Args:
123
+ filename: Path to the sequence file containing the region data.
124
+ enabled_only: If true, only include the region if enabled is True.
125
+
126
+ Returns:
127
+ List of ElectronAnalyserRegionDetector, equal to the number of regions in
128
+ the sequence file.
129
+ """
130
+ seq = self.load_sequence(filename)
131
+ regions = seq.get_enabled_regions() if enabled_only else seq.regions
132
+ return [
133
+ ElectronAnalyserRegionDetector(self.driver, r, self.name + "_" + r.name)
134
+ for r in regions
135
+ ]
136
+
137
+
138
+ TElectronAnalyserDetector = TypeVar(
139
+ "TElectronAnalyserDetector",
140
+ bound=ElectronAnalyserDetector,
141
+ )
@@ -0,0 +1,6 @@
1
+ from ophyd_async.core import StrictEnum
2
+
3
+
4
+ class EnergyMode(StrictEnum):
5
+ KINETIC = "Kinetic"
6
+ BINDING = "Binding"
@@ -1,10 +1,12 @@
1
1
  from .detector import SpecsDetector
2
2
  from .driver_io import SpecsAnalyserDriverIO
3
+ from .enums import AcquisitionMode
3
4
  from .region import SpecsRegion, SpecsSequence
4
5
 
5
6
  __all__ = [
6
7
  "SpecsDetector",
7
8
  "SpecsAnalyserDriverIO",
9
+ "AcquisitionMode",
8
10
  "SpecsRegion",
9
11
  "SpecsSequence",
10
12
  ]
@@ -1,4 +1,4 @@
1
- from dodal.devices.electron_analyser.abstract.base_detector import (
1
+ from dodal.devices.electron_analyser.detector import (
2
2
  ElectronAnalyserDetector,
3
3
  )
4
4
  from dodal.devices.electron_analyser.specs.driver_io import SpecsAnalyserDriverIO
@@ -13,6 +13,7 @@ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
13
13
  from dodal.devices.electron_analyser.abstract.base_driver_io import (
14
14
  AbstractAnalyserDriverIO,
15
15
  )
16
+ from dodal.devices.electron_analyser.specs.enums import AcquisitionMode
16
17
  from dodal.devices.electron_analyser.specs.region import SpecsRegion
17
18
 
18
19
 
@@ -28,7 +29,7 @@ class SpecsAnalyserDriverIO(AbstractAnalyserDriverIO[SpecsRegion]):
28
29
  self.min_angle_axis = epics_signal_r(float, prefix + "Y_MIN_RBV")
29
30
  self.max_angle_axis = epics_signal_r(float, prefix + "Y_MAX_RBV")
30
31
 
31
- super().__init__(prefix, name)
32
+ super().__init__(prefix, AcquisitionMode, name)
32
33
 
33
34
  @AsyncStatus.wrap
34
35
  async def set(self, region: SpecsRegion):
@@ -38,12 +39,10 @@ class SpecsAnalyserDriverIO(AbstractAnalyserDriverIO[SpecsRegion]):
38
39
  self.snapshot_values.set(region.values),
39
40
  self.psu_mode.set(region.psu_mode),
40
41
  )
41
- # ToDo - This needs to be changed to an Enum
42
- # https://github.com/DiamondLightSource/dodal/issues/1258
43
- if region.acquisition_mode == "Fixed Transmission":
42
+ if region.acquisition_mode == AcquisitionMode.FIXED_TRANSMISSION:
44
43
  await self.centre_energy.set(region.centre_energy)
45
44
 
46
- if self.acquisition_mode == "Fixed Energy":
45
+ if self.acquisition_mode == AcquisitionMode.FIXED_ENERGY:
47
46
  await self.energy_step.set(region.energy_step)
48
47
 
49
48
  def _create_angle_axis_signal(self, prefix: str) -> SignalR[Array1D[np.float64]]:
@@ -0,0 +1,8 @@
1
+ from ophyd_async.core import StrictEnum
2
+
3
+
4
+ class AcquisitionMode(StrictEnum):
5
+ FIXED_TRANSMISSION = "Fixed Transmission"
6
+ SNAPSHOT = "Snapshot"
7
+ FIXED_RETARDING_RATIO = "Fixed Retarding Ratio"
8
+ FIXED_ENERGY = "Fixed Energy"
@@ -4,13 +4,14 @@ from dodal.devices.electron_analyser.abstract.base_region import (
4
4
  AbstractBaseRegion,
5
5
  AbstractBaseSequence,
6
6
  )
7
+ from dodal.devices.electron_analyser.specs.enums import AcquisitionMode
7
8
 
8
9
 
9
- class SpecsRegion(AbstractBaseRegion):
10
+ class SpecsRegion(AbstractBaseRegion[AcquisitionMode]):
10
11
  # Override base class with defaults
11
12
  lens_mode: str = "SmallArea"
12
13
  pass_energy: int = 5
13
- acquisition_mode: str = "Fixed Transmission"
14
+ acquisition_mode: AcquisitionMode = AcquisitionMode.FIXED_TRANSMISSION
14
15
  low_energy: float = Field(default=800, alias="start_energy")
15
16
  high_energy: float = Field(default=850, alias="end_energy")
16
17
  step_time: float = Field(default=1.0, alias="exposure_time")
@@ -1,6 +1,32 @@
1
- from ophyd_async.core import StrictEnum
1
+ from dodal.devices.electron_analyser.abstract.base_driver_io import (
2
+ AbstractAnalyserDriverIO,
3
+ )
4
+ from dodal.devices.electron_analyser.abstract.base_region import (
5
+ AbstractBaseRegion,
6
+ AbstractBaseSequence,
7
+ )
8
+ from dodal.devices.electron_analyser.detector import (
9
+ ElectronAnalyserDetector,
10
+ ElectronAnalyserRegionDetector,
11
+ )
12
+ from dodal.devices.electron_analyser.specs.detector import (
13
+ SpecsAnalyserDriverIO,
14
+ SpecsDetector,
15
+ )
16
+ from dodal.devices.electron_analyser.vgscienta.detector import (
17
+ VGScientaAnalyserDriverIO,
18
+ VGScientaDetector,
19
+ )
2
20
 
21
+ ElectronAnalyserDetectorImpl = VGScientaDetector | SpecsDetector
22
+ ElectronAnalyserDriverImpl = VGScientaAnalyserDriverIO | SpecsAnalyserDriverIO
3
23
 
4
- class EnergyMode(StrictEnum):
5
- KINETIC = "Kinetic"
6
- BINDING = "Binding"
24
+ GenericElectronAnalyserDetector = ElectronAnalyserDetector[
25
+ AbstractAnalyserDriverIO[AbstractBaseRegion],
26
+ AbstractBaseSequence,
27
+ AbstractBaseRegion,
28
+ ]
29
+
30
+ GenericElectronAnalyserRegionDetector = ElectronAnalyserRegionDetector[
31
+ AbstractAnalyserDriverIO[AbstractBaseRegion], AbstractBaseRegion
32
+ ]
@@ -1,4 +1,4 @@
1
- from dodal.devices.electron_analyser.types import EnergyMode
1
+ from dodal.devices.electron_analyser.enums import EnergyMode
2
2
 
3
3
 
4
4
  def to_kinetic_energy(