dls-dodal 1.51.0__py3-none-any.whl → 1.53.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 (49) hide show
  1. {dls_dodal-1.51.0.dist-info → dls_dodal-1.53.0.dist-info}/METADATA +3 -2
  2. {dls_dodal-1.51.0.dist-info → dls_dodal-1.53.0.dist-info}/RECORD +46 -42
  3. dodal/_version.py +2 -2
  4. dodal/beamlines/adsim.py +12 -11
  5. dodal/beamlines/b07.py +7 -6
  6. dodal/beamlines/b07_1.py +7 -6
  7. dodal/beamlines/i03.py +4 -1
  8. dodal/beamlines/i04.py +2 -2
  9. dodal/beamlines/i09.py +7 -5
  10. dodal/beamlines/i09_1.py +6 -3
  11. dodal/beamlines/i09_2.py +2 -2
  12. dodal/beamlines/i22.py +11 -0
  13. dodal/beamlines/i24.py +2 -2
  14. dodal/beamlines/p60.py +6 -4
  15. dodal/beamlines/p99.py +14 -0
  16. dodal/common/device_utils.py +45 -0
  17. dodal/devices/attenuator/attenuator.py +5 -3
  18. dodal/devices/b07/__init__.py +2 -2
  19. dodal/devices/b07/enums.py +24 -0
  20. dodal/devices/b07_1/__init__.py +2 -2
  21. dodal/devices/b07_1/enums.py +18 -0
  22. dodal/devices/electron_analyser/abstract/__init__.py +4 -0
  23. dodal/devices/electron_analyser/abstract/base_driver_io.py +21 -4
  24. dodal/devices/electron_analyser/abstract/base_region.py +20 -7
  25. dodal/devices/electron_analyser/detector.py +1 -1
  26. dodal/devices/electron_analyser/specs/detector.py +18 -4
  27. dodal/devices/electron_analyser/specs/driver_io.py +17 -5
  28. dodal/devices/electron_analyser/specs/region.py +9 -5
  29. dodal/devices/electron_analyser/types.py +21 -5
  30. dodal/devices/electron_analyser/vgscienta/detector.py +16 -7
  31. dodal/devices/electron_analyser/vgscienta/driver_io.py +13 -4
  32. dodal/devices/electron_analyser/vgscienta/region.py +11 -5
  33. dodal/devices/i09/__init__.py +2 -2
  34. dodal/devices/i09/enums.py +15 -0
  35. dodal/devices/i09_1/__init__.py +3 -0
  36. dodal/devices/i09_1/enums.py +19 -0
  37. dodal/devices/linkam3.py +25 -81
  38. dodal/devices/oav/pin_image_recognition/__init__.py +11 -14
  39. dodal/devices/p60/__init__.py +3 -2
  40. dodal/devices/p60/enums.py +10 -0
  41. dodal/devices/tetramm.py +134 -150
  42. dodal/devices/xbpm_feedback.py +6 -3
  43. dodal/devices/b07/grating.py +0 -9
  44. dodal/devices/b07_1/grating.py +0 -10
  45. dodal/devices/i09/grating.py +0 -7
  46. {dls_dodal-1.51.0.dist-info → dls_dodal-1.53.0.dist-info}/WHEEL +0 -0
  47. {dls_dodal-1.51.0.dist-info → dls_dodal-1.53.0.dist-info}/entry_points.txt +0 -0
  48. {dls_dodal-1.51.0.dist-info → dls_dodal-1.53.0.dist-info}/licenses/LICENSE +0 -0
  49. {dls_dodal-1.51.0.dist-info → dls_dodal-1.53.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,18 @@
1
+ from ophyd_async.core import StrictEnum, SupersetEnum
2
+
3
+
4
+ class Grating(StrictEnum):
5
+ AU_400 = "400 l/mm Au"
6
+ AU_600 = "600 l/mm Au"
7
+ PT_600 = "600 l/mm Pt"
8
+ AU_1200 = "1200 l/mm Au"
9
+ ML_1200 = "1200 l/mm ML"
10
+ NO_GRATING = "No Grating"
11
+
12
+
13
+ class LensMode(SupersetEnum):
14
+ SMALL_AREA = "SmallArea"
15
+ ANGLE_RESOLVED_MODE_22 = "AngleResolvedMode22"
16
+ ANGLE_RESOLVED_MODE_30 = "AngleResolvedMode30"
17
+ LARGE_AREA = "LargeArea"
18
+ NOT_CONNECTED = "Not connected"
@@ -8,6 +8,8 @@ from .base_region import (
8
8
  AbstractBaseSequence,
9
9
  TAbstractBaseRegion,
10
10
  TAbstractBaseSequence,
11
+ TAcquisitionMode,
12
+ TLensMode,
11
13
  )
12
14
 
13
15
  __all__ = [
@@ -15,6 +17,8 @@ __all__ = [
15
17
  "AbstractBaseSequence",
16
18
  "TAbstractBaseRegion",
17
19
  "TAbstractBaseSequence",
20
+ "TAcquisitionMode",
21
+ "TLensMode",
18
22
  "AbstractAnalyserDriverIO",
19
23
  "AbstractElectronAnalyserDetector",
20
24
  "AbstractAnalyserDriverIO",
@@ -11,7 +11,6 @@ from ophyd_async.core import (
11
11
  SignalR,
12
12
  StandardReadable,
13
13
  StandardReadableFormat,
14
- StrictEnum,
15
14
  derived_signal_r,
16
15
  soft_signal_rw,
17
16
  )
@@ -20,6 +19,8 @@ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
20
19
 
21
20
  from dodal.devices.electron_analyser.abstract.base_region import (
22
21
  TAbstractBaseRegion,
22
+ TAcquisitionMode,
23
+ TLensMode,
23
24
  )
24
25
  from dodal.devices.electron_analyser.enums import EnergyMode
25
26
  from dodal.devices.electron_analyser.util import to_binding_energy, to_kinetic_energy
@@ -30,7 +31,7 @@ class AbstractAnalyserDriverIO(
30
31
  StandardReadable,
31
32
  ADBaseIO,
32
33
  Movable[TAbstractBaseRegion],
33
- Generic[TAbstractBaseRegion],
34
+ Generic[TAbstractBaseRegion, TAcquisitionMode, TLensMode],
34
35
  ):
35
36
  """
36
37
  Generic device to configure electron analyser with new region settings.
@@ -40,11 +41,27 @@ class AbstractAnalyserDriverIO(
40
41
  def __init__(
41
42
  self,
42
43
  prefix: str,
43
- acquisition_mode_type: type[StrictEnum],
44
+ acquisition_mode_type: type[TAcquisitionMode],
45
+ lens_mode_type: type[TLensMode],
44
46
  energy_sources: Mapping[str, SignalR[float]],
45
47
  name: str = "",
46
48
  ) -> None:
49
+ """
50
+ Constructor method for setting up electron analyser.
51
+
52
+ Args:
53
+ prefix: Base PV to connect to EPICS for this device.
54
+ acquisition_mode_type: Enum that determines the available acquisition modes
55
+ for this device.
56
+ lens_mode_type: Enum that determines the available lens mode for this
57
+ device.
58
+ energy_sources: Map that pairs a source name to an energy value signal
59
+ (in eV).
60
+ name: Name of the device.
61
+ """
47
62
  self.energy_sources = energy_sources
63
+ self.acquisition_mode_type = acquisition_mode_type
64
+ self.lens_mode_type = lens_mode_type
48
65
 
49
66
  with self.add_children_as_readables():
50
67
  self.image = epics_signal_r(Array1D[np.float64], prefix + "IMAGE")
@@ -63,7 +80,7 @@ class AbstractAnalyserDriverIO(
63
80
  self.low_energy = epics_signal_rw(float, prefix + "LOW_ENERGY")
64
81
  self.high_energy = epics_signal_rw(float, prefix + "HIGH_ENERGY")
65
82
  self.slices = epics_signal_rw(int, prefix + "SLICES")
66
- self.lens_mode = epics_signal_rw(str, prefix + "LENS_MODE")
83
+ self.lens_mode = epics_signal_rw(lens_mode_type, prefix + "LENS_MODE")
67
84
  self.pass_energy = epics_signal_rw(
68
85
  self.pass_energy_type, prefix + "PASS_ENERGY"
69
86
  )
@@ -3,13 +3,11 @@ 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
+ from ophyd_async.core import StrictEnum, SupersetEnum
7
7
  from pydantic import BaseModel, Field, model_validator
8
8
 
9
9
  from dodal.devices.electron_analyser.enums import EnergyMode
10
10
 
11
- TStrictEnum = TypeVar("TStrictEnum", bound=StrictEnum)
12
-
13
11
 
14
12
  def java_to_python_case(java_str: str) -> str:
15
13
  """
@@ -46,7 +44,18 @@ def energy_mode_validation(data: dict) -> dict:
46
44
  return data
47
45
 
48
46
 
49
- class AbstractBaseRegion(ABC, JavaToPythonModel, Generic[TStrictEnum]):
47
+ TAcquisitionMode = TypeVar("TAcquisitionMode", bound=StrictEnum)
48
+ # Allow SupersetEnum. Specs analysers can connect to Lens mode separately to the
49
+ # analyser which leaves the enum to either be "Not connected" OR the available enums
50
+ # when connected.
51
+ TLensMode = TypeVar("TLensMode", bound=SupersetEnum | StrictEnum)
52
+
53
+
54
+ class AbstractBaseRegion(
55
+ ABC,
56
+ JavaToPythonModel,
57
+ Generic[TAcquisitionMode, TLensMode],
58
+ ):
50
59
  """
51
60
  Generic region model that holds the data. Specialised region models should inherit
52
61
  this to extend functionality. All energy units are assumed to be in eV.
@@ -58,9 +67,9 @@ class AbstractBaseRegion(ABC, JavaToPythonModel, Generic[TStrictEnum]):
58
67
  iterations: int = 1
59
68
  excitation_energy_source: str = "source1"
60
69
  # These ones we need subclasses to provide default values
61
- lens_mode: str
70
+ lens_mode: TLensMode
62
71
  pass_energy: int
63
- acquisition_mode: TStrictEnum
72
+ acquisition_mode: TAcquisitionMode
64
73
  low_energy: float
65
74
  high_energy: float
66
75
  step_time: float
@@ -83,7 +92,11 @@ class AbstractBaseRegion(ABC, JavaToPythonModel, Generic[TStrictEnum]):
83
92
  TAbstractBaseRegion = TypeVar("TAbstractBaseRegion", bound=AbstractBaseRegion)
84
93
 
85
94
 
86
- class AbstractBaseSequence(ABC, JavaToPythonModel, Generic[TAbstractBaseRegion]):
95
+ class AbstractBaseSequence(
96
+ ABC,
97
+ JavaToPythonModel,
98
+ Generic[TAbstractBaseRegion, TLensMode],
99
+ ):
87
100
  """
88
101
  Generic sequence model that holds the list of region data. Specialised sequence
89
102
  models should inherit this to extend functionality and define type of region to
@@ -48,7 +48,7 @@ class ElectronAnalyserRegionDetector(
48
48
  async def trigger(self) -> None:
49
49
  # Configure region parameters on the driver first before data collection.
50
50
  await self.driver.set(self.region)
51
- super().trigger()
51
+ await super().trigger()
52
52
 
53
53
 
54
54
  TElectronAnalyserRegionDetector = TypeVar(
@@ -1,7 +1,9 @@
1
1
  from collections.abc import Mapping
2
+ from typing import Generic
2
3
 
3
4
  from ophyd_async.core import SignalR
4
5
 
6
+ from dodal.devices.electron_analyser.abstract.base_driver_io import TLensMode
5
7
  from dodal.devices.electron_analyser.detector import (
6
8
  ElectronAnalyserDetector,
7
9
  )
@@ -10,10 +12,22 @@ from dodal.devices.electron_analyser.specs.region import SpecsRegion, SpecsSeque
10
12
 
11
13
 
12
14
  class SpecsDetector(
13
- ElectronAnalyserDetector[SpecsAnalyserDriverIO, SpecsSequence, SpecsRegion]
15
+ ElectronAnalyserDetector[
16
+ SpecsAnalyserDriverIO[TLensMode],
17
+ SpecsSequence[TLensMode],
18
+ SpecsRegion[TLensMode],
19
+ ],
20
+ Generic[TLensMode],
14
21
  ):
15
22
  def __init__(
16
- self, prefix: str, energy_sources: Mapping[str, SignalR[float]], name: str = ""
23
+ self,
24
+ prefix: str,
25
+ lens_mode_type: type[TLensMode],
26
+ energy_sources: Mapping[str, SignalR[float]],
27
+ name: str = "",
17
28
  ):
18
- driver = SpecsAnalyserDriverIO(prefix, energy_sources)
19
- super().__init__(SpecsSequence, driver, name)
29
+ driver = SpecsAnalyserDriverIO[TLensMode](
30
+ prefix, lens_mode_type, energy_sources
31
+ )
32
+ seq = SpecsSequence[lens_mode_type]
33
+ super().__init__(seq, driver, name)
@@ -1,5 +1,6 @@
1
1
  import asyncio
2
2
  from collections.abc import Mapping
3
+ from typing import Generic
3
4
 
4
5
  import numpy as np
5
6
  from ophyd_async.core import (
@@ -14,13 +15,21 @@ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
14
15
  from dodal.devices.electron_analyser.abstract.base_driver_io import (
15
16
  AbstractAnalyserDriverIO,
16
17
  )
18
+ from dodal.devices.electron_analyser.abstract.base_region import TLensMode
17
19
  from dodal.devices.electron_analyser.specs.enums import AcquisitionMode
18
20
  from dodal.devices.electron_analyser.specs.region import SpecsRegion
19
21
 
20
22
 
21
- class SpecsAnalyserDriverIO(AbstractAnalyserDriverIO[SpecsRegion]):
23
+ class SpecsAnalyserDriverIO(
24
+ AbstractAnalyserDriverIO[SpecsRegion, AcquisitionMode, TLensMode],
25
+ Generic[TLensMode],
26
+ ):
22
27
  def __init__(
23
- self, prefix: str, energy_sources: Mapping[str, SignalR[float]], name: str = ""
28
+ self,
29
+ prefix: str,
30
+ lens_mode_type: type[TLensMode],
31
+ energy_sources: Mapping[str, SignalR[float]],
32
+ name: str = "",
24
33
  ) -> None:
25
34
  with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
26
35
  # Used for setting up region data acquisition.
@@ -31,11 +40,14 @@ class SpecsAnalyserDriverIO(AbstractAnalyserDriverIO[SpecsRegion]):
31
40
  # Used to read detector data after acqusition.
32
41
  self.min_angle_axis = epics_signal_r(float, prefix + "Y_MIN_RBV")
33
42
  self.max_angle_axis = epics_signal_r(float, prefix + "Y_MAX_RBV")
43
+ self.energy_channels = epics_signal_r(
44
+ int, prefix + "TOTAL_POINTS_ITERATION_RBV"
45
+ )
34
46
 
35
- super().__init__(prefix, AcquisitionMode, energy_sources, name)
47
+ super().__init__(prefix, AcquisitionMode, lens_mode_type, energy_sources, name)
36
48
 
37
49
  @AsyncStatus.wrap
38
- async def set(self, region: SpecsRegion):
50
+ async def set(self, region: SpecsRegion[TLensMode]):
39
51
  await super().set(region)
40
52
 
41
53
  await asyncio.gather(
@@ -73,7 +85,7 @@ class SpecsAnalyserDriverIO(AbstractAnalyserDriverIO[SpecsRegion]):
73
85
  "eV",
74
86
  min_energy=self.low_energy,
75
87
  max_energy=self.high_energy,
76
- total_points_iterations=self.slices,
88
+ total_points_iterations=self.energy_channels,
77
89
  )
78
90
  return energy_axis
79
91
 
@@ -1,15 +1,18 @@
1
+ from typing import Generic
2
+
1
3
  from pydantic import Field
2
4
 
3
5
  from dodal.devices.electron_analyser.abstract.base_region import (
4
6
  AbstractBaseRegion,
5
7
  AbstractBaseSequence,
8
+ TLensMode,
6
9
  )
7
10
  from dodal.devices.electron_analyser.specs.enums import AcquisitionMode
8
11
 
9
12
 
10
- class SpecsRegion(AbstractBaseRegion[AcquisitionMode]):
13
+ class SpecsRegion(AbstractBaseRegion[AcquisitionMode, TLensMode], Generic[TLensMode]):
11
14
  # Override base class with defaults
12
- lens_mode: str = "SmallArea"
15
+ lens_mode: TLensMode
13
16
  pass_energy: int = 5
14
17
  acquisition_mode: AcquisitionMode = AcquisitionMode.FIXED_TRANSMISSION
15
18
  low_energy: float = Field(default=800, alias="start_energy")
@@ -19,9 +22,10 @@ class SpecsRegion(AbstractBaseRegion[AcquisitionMode]):
19
22
  # Specific to this class
20
23
  values: int = 1
21
24
  centre_energy: float = 0
22
- psu_mode: str = "1.5keV"
25
+ # ToDo - Update to an enum https://github.com/DiamondLightSource/dodal/issues/1328
26
+ psu_mode: str = "1.5kV"
23
27
  estimated_time_in_ms: float = 0
24
28
 
25
29
 
26
- class SpecsSequence(AbstractBaseSequence[SpecsRegion]):
27
- regions: list[SpecsRegion] = Field(default_factory=lambda: [])
30
+ class SpecsSequence(AbstractBaseSequence[SpecsRegion, TLensMode], Generic[TLensMode]):
31
+ regions: list[SpecsRegion[TLensMode]] = Field(default_factory=lambda: [])
@@ -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,7 +1,9 @@
1
1
  from collections.abc import Mapping
2
+ from typing import Generic
2
3
 
3
4
  from ophyd_async.core import SignalR
4
5
 
6
+ from dodal.devices.electron_analyser.abstract.base_region import TLensMode
5
7
  from dodal.devices.electron_analyser.detector import (
6
8
  ElectronAnalyserDetector,
7
9
  )
@@ -16,13 +18,20 @@ from dodal.devices.electron_analyser.vgscienta.region import (
16
18
 
17
19
  class VGScientaDetector(
18
20
  ElectronAnalyserDetector[
19
- VGScientaAnalyserDriverIO,
20
- VGScientaSequence,
21
- VGScientaRegion,
22
- ]
21
+ VGScientaAnalyserDriverIO[TLensMode],
22
+ VGScientaSequence[TLensMode],
23
+ VGScientaRegion[TLensMode],
24
+ ],
25
+ Generic[TLensMode],
23
26
  ):
24
27
  def __init__(
25
- self, prefix: str, energy_sources: Mapping[str, SignalR], name: str = ""
28
+ self,
29
+ prefix: str,
30
+ lens_mode_type: type[TLensMode],
31
+ energy_sources: Mapping[str, SignalR[float]],
32
+ name: str = "",
26
33
  ):
27
- driver = VGScientaAnalyserDriverIO(prefix, energy_sources)
28
- super().__init__(VGScientaSequence, driver, name)
34
+ driver = VGScientaAnalyserDriverIO[TLensMode](
35
+ prefix, lens_mode_type, energy_sources
36
+ )
37
+ super().__init__(VGScientaSequence[lens_mode_type], driver, name)
@@ -1,5 +1,6 @@
1
1
  import asyncio
2
2
  from collections.abc import Mapping
3
+ from typing import Generic
3
4
 
4
5
  import numpy as np
5
6
  from ophyd_async.core import (
@@ -14,6 +15,7 @@ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
14
15
  from dodal.devices.electron_analyser.abstract.base_driver_io import (
15
16
  AbstractAnalyserDriverIO,
16
17
  )
18
+ from dodal.devices.electron_analyser.abstract.base_region import TLensMode
17
19
  from dodal.devices.electron_analyser.util import to_kinetic_energy
18
20
  from dodal.devices.electron_analyser.vgscienta.enums import AcquisitionMode
19
21
  from dodal.devices.electron_analyser.vgscienta.region import (
@@ -22,9 +24,16 @@ from dodal.devices.electron_analyser.vgscienta.region import (
22
24
  )
23
25
 
24
26
 
25
- class VGScientaAnalyserDriverIO(AbstractAnalyserDriverIO[VGScientaRegion]):
27
+ class VGScientaAnalyserDriverIO(
28
+ AbstractAnalyserDriverIO[VGScientaRegion, AcquisitionMode, TLensMode],
29
+ Generic[TLensMode],
30
+ ):
26
31
  def __init__(
27
- self, prefix: str, energy_sources: Mapping[str, SignalR[float]], name: str = ""
32
+ self,
33
+ prefix: str,
34
+ lens_mode_type: type[TLensMode],
35
+ energy_sources: Mapping[str, SignalR[float]],
36
+ name: str = "",
28
37
  ) -> None:
29
38
  with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
30
39
  # Used for setting up region data acquisition.
@@ -35,10 +44,10 @@ class VGScientaAnalyserDriverIO(AbstractAnalyserDriverIO[VGScientaRegion]):
35
44
  self.y_channel_size = epics_signal_rw(int, prefix + "SizeY")
36
45
  self.detector_mode = epics_signal_rw(DetectorMode, prefix + "DETECTOR_MODE")
37
46
 
38
- super().__init__(prefix, AcquisitionMode, energy_sources, name)
47
+ super().__init__(prefix, AcquisitionMode, lens_mode_type, energy_sources, name)
39
48
 
40
49
  @AsyncStatus.wrap
41
- async def set(self, region: VGScientaRegion):
50
+ async def set(self, region: VGScientaRegion[TLensMode]):
42
51
  await super().set(region)
43
52
 
44
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
@@ -48,15 +52,17 @@ class VGScientaExcitationEnergySource(JavaToPythonModel):
48
52
  value: float = 0
49
53
 
50
54
 
51
- class VGScientaSequence(AbstractBaseSequence[VGScientaRegion]):
55
+ class VGScientaSequence(
56
+ AbstractBaseSequence[VGScientaRegion, TLensMode], Generic[TLensMode]
57
+ ):
52
58
  element_set: str = Field(default="Unknown")
53
59
  excitation_energy_sources: list[VGScientaExcitationEnergySource] = Field(
54
60
  default_factory=lambda: []
55
61
  )
56
- regions: list[VGScientaRegion] = Field(default_factory=lambda: [])
62
+ regions: list[VGScientaRegion[TLensMode]] = Field(default_factory=lambda: [])
57
63
 
58
64
  def get_excitation_energy_source_by_region(
59
- self, region: VGScientaRegion
65
+ self, region: VGScientaRegion[TLensMode]
60
66
  ) -> VGScientaExcitationEnergySource:
61
67
  value = next(
62
68
  (
@@ -1,4 +1,4 @@
1
1
  from dodal.devices.i09.dcm import DCM
2
- from dodal.devices.i09.grating import I09Grating
2
+ from dodal.devices.i09.enums import Grating, LensMode
3
3
 
4
- __all__ = ["DCM", "I09Grating"]
4
+ __all__ = ["DCM", "Grating", "LensMode"]
@@ -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"
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)