dls-dodal 1.67.0__py3-none-any.whl → 1.68.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 (57) hide show
  1. {dls_dodal-1.67.0.dist-info → dls_dodal-1.68.0.dist-info}/METADATA +2 -2
  2. {dls_dodal-1.67.0.dist-info → dls_dodal-1.68.0.dist-info}/RECORD +50 -50
  3. dodal/_version.py +2 -2
  4. dodal/beamlines/b07.py +1 -1
  5. dodal/beamlines/b07_1.py +1 -1
  6. dodal/beamlines/i05.py +1 -1
  7. dodal/beamlines/i06.py +1 -1
  8. dodal/beamlines/i09.py +1 -1
  9. dodal/beamlines/i09_1.py +1 -1
  10. dodal/beamlines/i09_2.py +5 -4
  11. dodal/beamlines/i10_optics.py +1 -1
  12. dodal/beamlines/i16.py +23 -0
  13. dodal/beamlines/i17.py +1 -1
  14. dodal/beamlines/i21.py +61 -2
  15. dodal/beamlines/p60.py +1 -1
  16. dodal/devices/eiger.py +15 -9
  17. dodal/devices/electron_analyser/__init__.py +0 -33
  18. dodal/devices/electron_analyser/base/__init__.py +58 -0
  19. dodal/devices/electron_analyser/base/base_controller.py +73 -0
  20. dodal/devices/electron_analyser/base/base_detector.py +214 -0
  21. dodal/devices/electron_analyser/{abstract → base}/base_driver_io.py +23 -42
  22. dodal/devices/electron_analyser/{abstract → base}/base_region.py +47 -11
  23. dodal/devices/electron_analyser/{util.py → base/base_util.py} +1 -1
  24. dodal/devices/electron_analyser/{energy_sources.py → base/energy_sources.py} +1 -1
  25. dodal/devices/electron_analyser/specs/__init__.py +4 -4
  26. dodal/devices/electron_analyser/specs/specs_detector.py +46 -0
  27. dodal/devices/electron_analyser/specs/{driver_io.py → specs_driver_io.py} +23 -26
  28. dodal/devices/electron_analyser/specs/{region.py → specs_region.py} +4 -3
  29. dodal/devices/electron_analyser/vgscienta/__init__.py +4 -4
  30. dodal/devices/electron_analyser/vgscienta/vgscienta_detector.py +52 -0
  31. dodal/devices/electron_analyser/vgscienta/{driver_io.py → vgscienta_driver_io.py} +25 -31
  32. dodal/devices/electron_analyser/vgscienta/{region.py → vgscienta_region.py} +6 -6
  33. dodal/devices/i09_2_shared/i09_apple2.py +0 -72
  34. dodal/devices/i10/i10_apple2.py +2 -2
  35. dodal/devices/i21/__init__.py +3 -1
  36. dodal/devices/insertion_device/__init__.py +58 -0
  37. dodal/devices/insertion_device/apple2_undulator.py +66 -16
  38. dodal/devices/insertion_device/energy_motor_lookup.py +1 -1
  39. dodal/devices/insertion_device/id_enum.py +17 -0
  40. dodal/devices/insertion_device/lookup_table_models.py +65 -35
  41. dodal/testing/electron_analyser/device_factory.py +4 -4
  42. dodal/testing/fixtures/devices/apple2.py +1 -1
  43. dodal/testing/fixtures/run_engine.py +4 -0
  44. dodal/devices/electron_analyser/abstract/__init__.py +0 -25
  45. dodal/devices/electron_analyser/abstract/base_detector.py +0 -63
  46. dodal/devices/electron_analyser/abstract/types.py +0 -12
  47. dodal/devices/electron_analyser/detector.py +0 -143
  48. dodal/devices/electron_analyser/specs/detector.py +0 -34
  49. dodal/devices/electron_analyser/types.py +0 -57
  50. dodal/devices/electron_analyser/vgscienta/detector.py +0 -48
  51. {dls_dodal-1.67.0.dist-info → dls_dodal-1.68.0.dist-info}/WHEEL +0 -0
  52. {dls_dodal-1.67.0.dist-info → dls_dodal-1.68.0.dist-info}/entry_points.txt +0 -0
  53. {dls_dodal-1.67.0.dist-info → dls_dodal-1.68.0.dist-info}/licenses/LICENSE +0 -0
  54. {dls_dodal-1.67.0.dist-info → dls_dodal-1.68.0.dist-info}/top_level.txt +0 -0
  55. /dodal/devices/electron_analyser/{enums.py → base/base_enums.py} +0 -0
  56. /dodal/devices/electron_analyser/specs/{enums.py → specs_enums.py} +0 -0
  57. /dodal/devices/electron_analyser/vgscienta/{enums.py → vgscienta_enums.py} +0 -0
@@ -2,12 +2,13 @@ from typing import Generic
2
2
 
3
3
  from pydantic import Field
4
4
 
5
- from dodal.devices.electron_analyser.abstract.base_region import (
5
+ from dodal.devices.electron_analyser.base.base_region import (
6
6
  AbstractBaseRegion,
7
7
  AbstractBaseSequence,
8
+ TLensMode,
9
+ TPsuMode,
8
10
  )
9
- from dodal.devices.electron_analyser.abstract.types import TLensMode, TPsuMode
10
- from dodal.devices.electron_analyser.specs.enums import AcquisitionMode
11
+ from dodal.devices.electron_analyser.specs.specs_enums import AcquisitionMode
11
12
 
12
13
 
13
14
  class SpecsRegion(
@@ -1,7 +1,7 @@
1
- from .detector import VGScientaDetector
2
- from .driver_io import VGScientaAnalyserDriverIO
3
- from .enums import AcquisitionMode, DetectorMode
4
- from .region import VGScientaRegion, VGScientaSequence
1
+ from .vgscienta_detector import VGScientaDetector
2
+ from .vgscienta_driver_io import VGScientaAnalyserDriverIO
3
+ from .vgscienta_enums import AcquisitionMode, DetectorMode
4
+ from .vgscienta_region import VGScientaRegion, VGScientaSequence
5
5
 
6
6
  __all__ = [
7
7
  "VGScientaDetector",
@@ -0,0 +1,52 @@
1
+ from typing import Generic
2
+
3
+ from dodal.devices.electron_analyser.base.base_controller import (
4
+ ElectronAnalyserController,
5
+ )
6
+ from dodal.devices.electron_analyser.base.base_detector import ElectronAnalyserDetector
7
+ from dodal.devices.electron_analyser.base.base_region import TLensMode, TPsuMode
8
+ from dodal.devices.electron_analyser.base.energy_sources import (
9
+ DualEnergySource,
10
+ EnergySource,
11
+ )
12
+ from dodal.devices.electron_analyser.vgscienta.vgscienta_driver_io import (
13
+ VGScientaAnalyserDriverIO,
14
+ )
15
+ from dodal.devices.electron_analyser.vgscienta.vgscienta_region import (
16
+ TPassEnergyEnum,
17
+ VGScientaRegion,
18
+ VGScientaSequence,
19
+ )
20
+
21
+
22
+ class VGScientaDetector(
23
+ ElectronAnalyserDetector[
24
+ VGScientaSequence[TLensMode, TPsuMode, TPassEnergyEnum],
25
+ VGScientaAnalyserDriverIO[TLensMode, TPsuMode, TPassEnergyEnum],
26
+ VGScientaRegion[TLensMode, TPassEnergyEnum],
27
+ ],
28
+ Generic[TLensMode, TPsuMode, TPassEnergyEnum],
29
+ ):
30
+ def __init__(
31
+ self,
32
+ prefix: str,
33
+ lens_mode_type: type[TLensMode],
34
+ psu_mode_type: type[TPsuMode],
35
+ pass_energy_type: type[TPassEnergyEnum],
36
+ energy_source: DualEnergySource | EnergySource,
37
+ name: str = "",
38
+ ):
39
+ # Save to class so takes part with connect()
40
+ self.driver = VGScientaAnalyserDriverIO[TLensMode, TPsuMode, TPassEnergyEnum](
41
+ prefix, lens_mode_type, psu_mode_type, pass_energy_type
42
+ )
43
+
44
+ controller = ElectronAnalyserController[
45
+ VGScientaAnalyserDriverIO[TLensMode, TPsuMode, TPassEnergyEnum],
46
+ VGScientaRegion[TLensMode, TPassEnergyEnum],
47
+ ](self.driver, energy_source, 0)
48
+
49
+ sequence_class = VGScientaSequence[
50
+ lens_mode_type, psu_mode_type, pass_energy_type
51
+ ]
52
+ super().__init__(sequence_class, controller, name)
@@ -4,28 +4,22 @@ from typing import Generic
4
4
  import numpy as np
5
5
  from ophyd_async.core import (
6
6
  Array1D,
7
+ AsyncStatus,
7
8
  SignalR,
8
9
  StandardReadableFormat,
9
10
  )
10
11
  from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
11
12
 
12
- from dodal.devices.electron_analyser.abstract.base_driver_io import (
13
+ from dodal.devices.electron_analyser.base.base_driver_io import (
13
14
  AbstractAnalyserDriverIO,
14
15
  )
15
- from dodal.devices.electron_analyser.abstract.types import (
16
- TLensMode,
17
- TPassEnergyEnum,
18
- TPsuMode,
19
- )
20
- from dodal.devices.electron_analyser.energy_sources import (
21
- DualEnergySource,
22
- EnergySource,
23
- )
24
- from dodal.devices.electron_analyser.vgscienta.enums import (
16
+ from dodal.devices.electron_analyser.base.base_region import TLensMode, TPsuMode
17
+ from dodal.devices.electron_analyser.vgscienta.vgscienta_enums import (
25
18
  AcquisitionMode,
26
19
  DetectorMode,
27
20
  )
28
- from dodal.devices.electron_analyser.vgscienta.region import (
21
+ from dodal.devices.electron_analyser.vgscienta.vgscienta_region import (
22
+ TPassEnergyEnum,
29
23
  VGScientaRegion,
30
24
  )
31
25
 
@@ -46,7 +40,6 @@ class VGScientaAnalyserDriverIO(
46
40
  lens_mode_type: type[TLensMode],
47
41
  psu_mode_type: type[TPsuMode],
48
42
  pass_energy_type: type[TPassEnergyEnum],
49
- energy_source: EnergySource | DualEnergySource,
50
43
  name: str = "",
51
44
  ) -> None:
52
45
  with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
@@ -67,28 +60,29 @@ class VGScientaAnalyserDriverIO(
67
60
  lens_mode_type,
68
61
  psu_mode_type,
69
62
  pass_energy_type,
70
- energy_source,
71
63
  name,
72
64
  )
73
65
 
74
- async def _set_region(self, ke_region: VGScientaRegion[TLensMode, TPassEnergyEnum]):
66
+ @AsyncStatus.wrap
67
+ async def set(self, epics_region: VGScientaRegion[TLensMode, TPassEnergyEnum]):
75
68
  await asyncio.gather(
76
- self.region_name.set(ke_region.name),
77
- self.low_energy.set(ke_region.low_energy),
78
- self.centre_energy.set(ke_region.centre_energy),
79
- self.high_energy.set(ke_region.high_energy),
80
- self.slices.set(ke_region.slices),
81
- self.lens_mode.set(ke_region.lens_mode),
82
- self.pass_energy.set(ke_region.pass_energy),
83
- self.iterations.set(ke_region.iterations),
84
- self.acquire_time.set(ke_region.acquire_time),
85
- self.acquisition_mode.set(ke_region.acquisition_mode),
86
- self.energy_step.set(ke_region.energy_step),
87
- self.detector_mode.set(ke_region.detector_mode),
88
- self.region_min_x.set(ke_region.min_x),
89
- self.region_size_x.set(ke_region.size_x),
90
- self.region_min_y.set(ke_region.min_y),
91
- self.region_size_y.set(ke_region.size_y),
69
+ self.region_name.set(epics_region.name),
70
+ self.low_energy.set(epics_region.low_energy),
71
+ self.centre_energy.set(epics_region.centre_energy),
72
+ self.high_energy.set(epics_region.high_energy),
73
+ self.slices.set(epics_region.slices),
74
+ self.lens_mode.set(epics_region.lens_mode),
75
+ self.pass_energy.set(epics_region.pass_energy),
76
+ self.iterations.set(epics_region.iterations),
77
+ self.acquire_time.set(epics_region.acquire_time),
78
+ self.acquisition_mode.set(epics_region.acquisition_mode),
79
+ self.energy_step.set(epics_region.energy_step),
80
+ self.detector_mode.set(epics_region.detector_mode),
81
+ self.region_min_x.set(epics_region.min_x),
82
+ self.region_size_x.set(epics_region.size_x),
83
+ self.region_min_y.set(epics_region.min_y),
84
+ self.region_size_y.set(epics_region.size_y),
85
+ self.energy_mode.set(epics_region.energy_mode),
92
86
  )
93
87
 
94
88
  def _create_energy_axis_signal(self, prefix: str) -> SignalR[Array1D[np.float64]]:
@@ -1,22 +1,22 @@
1
1
  import uuid
2
- from typing import Generic
2
+ from typing import Generic, TypeVar
3
3
 
4
+ from ophyd_async.core import StrictEnum
4
5
  from pydantic import Field, field_validator
5
6
 
6
- from dodal.devices.electron_analyser.abstract.base_region import (
7
+ from dodal.devices.electron_analyser.base.base_region import (
7
8
  AbstractBaseRegion,
8
9
  AbstractBaseSequence,
9
- )
10
- from dodal.devices.electron_analyser.abstract.types import (
11
10
  TLensMode,
12
- TPassEnergyEnum,
13
11
  TPsuMode,
14
12
  )
15
- from dodal.devices.electron_analyser.vgscienta.enums import (
13
+ from dodal.devices.electron_analyser.vgscienta.vgscienta_enums import (
16
14
  AcquisitionMode,
17
15
  DetectorMode,
18
16
  )
19
17
 
18
+ TPassEnergyEnum = TypeVar("TPassEnergyEnum", bound=StrictEnum)
19
+
20
20
 
21
21
  class VGScientaRegion(
22
22
  AbstractBaseRegion[AcquisitionMode, TLensMode, TPassEnergyEnum],
@@ -1,14 +1,3 @@
1
- from dodal.devices.insertion_device.apple2_undulator import (
2
- MAXIMUM_MOVE_TIME,
3
- Apple2,
4
- Apple2Controller,
5
- Apple2PhasesVal,
6
- Apple2Val,
7
- Pol,
8
- UndulatorPhaseAxes,
9
- )
10
- from dodal.devices.insertion_device.energy_motor_lookup import EnergyMotorLookup
11
-
12
1
  J09_GAP_POLY_DEG_COLUMNS = [
13
2
  "9th-order",
14
3
  "8th-order",
@@ -23,64 +12,3 @@ J09_GAP_POLY_DEG_COLUMNS = [
23
12
  ]
24
13
 
25
14
  J09_PHASE_POLY_DEG_COLUMNS = ["0th-order"]
26
-
27
-
28
- class J09Apple2Controller(Apple2Controller[Apple2[UndulatorPhaseAxes]]):
29
- def __init__(
30
- self,
31
- apple2: Apple2[UndulatorPhaseAxes],
32
- gap_energy_motor_lut: EnergyMotorLookup,
33
- phase_energy_motor_lut: EnergyMotorLookup,
34
- units: str = "keV",
35
- name: str = "",
36
- ) -> None:
37
- """
38
- Parameters:
39
- -----------
40
- apple2 : Apple2
41
- An Apple2 device.
42
- gap_energy_motor_lut: EnergyMotorLookup
43
- The class that handles the gap look up table logic for the insertion device.
44
- phase_energy_motor_lut: EnergyMotorLookup
45
- The class that handles the phase look up table logic for the insertion device.
46
- units:
47
- the units of this device. Defaults to eV.
48
- name : str, optional
49
- New device name.
50
- """
51
- self.gap_energy_motor_lut = gap_energy_motor_lut
52
- self.phase_energy_motor_lut = phase_energy_motor_lut
53
- super().__init__(
54
- apple2=apple2,
55
- gap_energy_motor_converter=gap_energy_motor_lut.find_value_in_lookup_table,
56
- phase_energy_motor_converter=phase_energy_motor_lut.find_value_in_lookup_table,
57
- units=units,
58
- name=name,
59
- )
60
-
61
- def _get_apple2_value(self, gap: float, phase: float, pol: Pol) -> Apple2Val:
62
- return Apple2Val(
63
- gap=f"{gap:.6f}",
64
- phase=Apple2PhasesVal(
65
- top_outer=f"{phase:.6f}",
66
- top_inner=f"{0.0:.6f}",
67
- btm_inner=f"{phase:.6f}",
68
- btm_outer=f"{0.0:.6f}",
69
- ),
70
- )
71
-
72
- async def _set_pol(
73
- self,
74
- value: Pol,
75
- ) -> None:
76
- # I09 require all palarisation change to go via LH.
77
- target_energy = await self.energy.get_value()
78
- if value is not Pol.LH:
79
- self._polarisation_setpoint_set(Pol.LH)
80
- max_lh_energy = self.gap_energy_motor_lut.lut.root[Pol.LH].max_energy
81
- lh_setpoint = (
82
- max_lh_energy if target_energy > max_lh_energy else target_energy
83
- )
84
- await self.energy.set(lh_setpoint, timeout=MAXIMUM_MOVE_TIME)
85
- self._polarisation_setpoint_set(value)
86
- await self.energy.set(target_energy, timeout=MAXIMUM_MOVE_TIME)
@@ -11,18 +11,18 @@ from ophyd_async.core import (
11
11
  soft_signal_rw,
12
12
  )
13
13
 
14
- from dodal.devices.insertion_device.apple2_undulator import (
14
+ from dodal.devices.insertion_device import (
15
15
  MAXIMUM_MOVE_TIME,
16
16
  Apple2,
17
17
  Apple2Controller,
18
18
  Apple2PhasesVal,
19
19
  Apple2Val,
20
- Pol,
21
20
  UndulatorGap,
22
21
  UndulatorJawPhase,
23
22
  UndulatorPhaseAxes,
24
23
  )
25
24
  from dodal.devices.insertion_device.energy_motor_lookup import EnergyMotorLookup
25
+ from dodal.devices.insertion_device.id_enum import Pol
26
26
 
27
27
  ROW_PHASE_MOTOR_TOLERANCE = 0.004
28
28
  MAXIMUM_ROW_PHASE_MOTOR_POSITION = 24.0
@@ -1,3 +1,5 @@
1
1
  from dodal.devices.i21.enums import Grating
2
2
 
3
- __all__ = ["Grating"]
3
+ __all__ = [
4
+ "Grating",
5
+ ]
@@ -0,0 +1,58 @@
1
+ from .apple2_undulator import (
2
+ DEFAULT_MOTOR_MIN_TIMEOUT,
3
+ MAXIMUM_MOVE_TIME,
4
+ Apple2,
5
+ Apple2Controller,
6
+ Apple2EnforceLHMoveController,
7
+ Apple2LockedPhasesVal,
8
+ Apple2PhasesVal,
9
+ Apple2Val,
10
+ BeamEnergy,
11
+ EnabledDisabledUpper,
12
+ EnergyMotorConvertor,
13
+ InsertionDeviceEnergy,
14
+ InsertionDevicePolarisation,
15
+ UndulatorGap,
16
+ UndulatorJawPhase,
17
+ UndulatorLockedPhaseAxes,
18
+ UndulatorPhaseAxes,
19
+ )
20
+ from .energy_motor_lookup import (
21
+ ConfigServerEnergyMotorLookup,
22
+ EnergyMotorLookup,
23
+ )
24
+ from .id_enum import Pol, UndulatorGateStatus
25
+ from .lookup_table_models import (
26
+ EnergyCoverage,
27
+ LookupTable,
28
+ LookupTableColumnConfig,
29
+ convert_csv_to_lookup,
30
+ )
31
+
32
+ __all__ = [
33
+ "Apple2",
34
+ "Apple2Controller",
35
+ "Apple2EnforceLHMoveController",
36
+ "UndulatorGap",
37
+ "UndulatorPhaseAxes",
38
+ "UndulatorJawPhase",
39
+ "Apple2Val",
40
+ "Apple2PhasesVal",
41
+ "MAXIMUM_MOVE_TIME",
42
+ "LookupTable",
43
+ "LookupTableColumnConfig",
44
+ "convert_csv_to_lookup",
45
+ "InsertionDeviceEnergy",
46
+ "InsertionDevicePolarisation",
47
+ "BeamEnergy",
48
+ "UndulatorLockedPhaseAxes",
49
+ "EnergyCoverage",
50
+ "Pol",
51
+ "DEFAULT_MOTOR_MIN_TIMEOUT",
52
+ "EnabledDisabledUpper",
53
+ "UndulatorGateStatus",
54
+ "Apple2LockedPhasesVal",
55
+ "EnergyMotorLookup",
56
+ "ConfigServerEnergyMotorLookup",
57
+ "EnergyMotorConvertor",
58
+ ]
@@ -14,7 +14,6 @@ from ophyd_async.core import (
14
14
  SignalW,
15
15
  StandardReadable,
16
16
  StandardReadableFormat,
17
- StrictEnum,
18
17
  derived_signal_rw,
19
18
  soft_signal_r_and_setter,
20
19
  soft_signal_rw,
@@ -24,6 +23,8 @@ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw, epics_signal
24
23
  from ophyd_async.epics.motor import Motor
25
24
 
26
25
  from dodal.common.enums import EnabledDisabledUpper
26
+ from dodal.devices.insertion_device.energy_motor_lookup import EnergyMotorLookup
27
+ from dodal.devices.insertion_device.id_enum import Pol, UndulatorGateStatus
27
28
  from dodal.log import LOGGER
28
29
 
29
30
  T = TypeVar("T")
@@ -32,11 +33,6 @@ DEFAULT_MOTOR_MIN_TIMEOUT = 10
32
33
  MAXIMUM_MOVE_TIME = 550 # There is no useful movements take longer than this.
33
34
 
34
35
 
35
- class UndulatorGateStatus(StrictEnum):
36
- OPEN = "Open"
37
- CLOSE = "Closed"
38
-
39
-
40
36
  @dataclass
41
37
  class Apple2LockedPhasesVal:
42
38
  top_outer: str
@@ -58,16 +54,6 @@ class Apple2Val:
58
54
  return self.phase
59
55
 
60
56
 
61
- class Pol(StrictEnum):
62
- NONE = "None"
63
- LH = "lh"
64
- LV = "lv"
65
- PC = "pc"
66
- NC = "nc"
67
- LA = "la"
68
- LH3 = "lh3"
69
-
70
-
71
57
  ROW_PHASE_MOTOR_TOLERANCE = 0.004
72
58
  MAXIMUM_ROW_PHASE_MOTOR_POSITION = 24.0
73
59
  MAXIMUM_GAP_MOTOR_POSITION = 100
@@ -666,6 +652,70 @@ class Apple2Controller(abc.ABC, StandardReadable, Generic[Apple2Type]):
666
652
  return Pol.NONE, 0.0
667
653
 
668
654
 
655
+ class Apple2EnforceLHMoveController(Apple2Controller[Apple2]):
656
+ """The latest Apple2 version allows unrestricted motor movement.
657
+ However, because of the high forces involved in polarization changes,
658
+ all movements must be performed using the Linear Horizontal (LH) mode.
659
+ A look-up table must also be used to determine the highest energy that can
660
+ be reached in LH mode."""
661
+
662
+ def __init__(
663
+ self,
664
+ apple2: Apple2,
665
+ gap_energy_motor_lut: EnergyMotorLookup,
666
+ phase_energy_motor_lut: EnergyMotorLookup,
667
+ units: str = "eV",
668
+ name: str = "",
669
+ ) -> None:
670
+ self.gap_energy_motor_lu = gap_energy_motor_lut
671
+ self.phase_energy_motor_lu = phase_energy_motor_lut
672
+ super().__init__(
673
+ apple2=apple2,
674
+ gap_energy_motor_converter=gap_energy_motor_lut.find_value_in_lookup_table,
675
+ phase_energy_motor_converter=phase_energy_motor_lut.find_value_in_lookup_table,
676
+ units=units,
677
+ name=name,
678
+ )
679
+
680
+ def _get_apple2_value(self, gap: float, phase: float, pol: Pol) -> Apple2Val:
681
+ apple2_val = Apple2Val(
682
+ gap=f"{gap:.6f}",
683
+ phase=Apple2PhasesVal(
684
+ top_outer=f"{phase:.6f}",
685
+ top_inner=f"{0.0:.6f}",
686
+ btm_inner=f"{phase:.6f}",
687
+ btm_outer=f"{0.0:.6f}",
688
+ ),
689
+ )
690
+ LOGGER.info(f"Getting apple2 value for pol={pol}, gap={gap}, phase={phase}.")
691
+ LOGGER.info(f"Apple2 motor values: {apple2_val}.")
692
+
693
+ return apple2_val
694
+
695
+ async def _set_pol(
696
+ self,
697
+ value: Pol,
698
+ ) -> None:
699
+ # I09/I21 require all polarisation change to go via LH.
700
+ current_pol = await self.polarisation.get_value()
701
+ if current_pol == value:
702
+ LOGGER.info(f"Polarisation already at {value}")
703
+ else:
704
+ target_energy = await self.energy.get_value()
705
+ if (value is not Pol.LH) and (current_pol is not Pol.LH):
706
+ self._polarisation_setpoint_set(Pol.LH)
707
+ max_lh_energy = float(
708
+ self.gap_energy_motor_lu.lut.root[Pol("lh")].max_energy
709
+ )
710
+ lh_setpoint = (
711
+ max_lh_energy if target_energy > max_lh_energy else target_energy
712
+ )
713
+ LOGGER.info(f"Changing polarisation to {value} via {Pol.LH}")
714
+ await self.energy.set(lh_setpoint, timeout=MAXIMUM_MOVE_TIME)
715
+ self._polarisation_setpoint_set(value)
716
+ await self.energy.set(target_energy, timeout=MAXIMUM_MOVE_TIME)
717
+
718
+
669
719
  class InsertionDeviceEnergyBase(abc.ABC, StandardReadable, Movable):
670
720
  """Base class for ID energy movable device."""
671
721
 
@@ -2,7 +2,7 @@ from pathlib import Path
2
2
 
3
3
  from daq_config_server.client import ConfigServer
4
4
 
5
- from dodal.devices.insertion_device.apple2_undulator import Pol
5
+ from dodal.devices.insertion_device.id_enum import Pol
6
6
  from dodal.devices.insertion_device.lookup_table_models import (
7
7
  LookupTable,
8
8
  LookupTableColumnConfig,
@@ -0,0 +1,17 @@
1
+ from ophyd_async.core import StrictEnum
2
+
3
+
4
+ class Pol(StrictEnum):
5
+ NONE = "None"
6
+ LH = "lh"
7
+ LV = "lv"
8
+ PC = "pc"
9
+ NC = "nc"
10
+ LA = "la"
11
+ LH3 = "lh3"
12
+ LV3 = "lv3"
13
+
14
+
15
+ class UndulatorGateStatus(StrictEnum):
16
+ OPEN = "Open"
17
+ CLOSE = "Closed"