dls-dodal 1.66.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 (83) hide show
  1. {dls_dodal-1.66.0.dist-info → dls_dodal-1.68.0.dist-info}/METADATA +2 -2
  2. {dls_dodal-1.66.0.dist-info → dls_dodal-1.68.0.dist-info}/RECORD +75 -65
  3. dodal/_version.py +2 -2
  4. dodal/beamlines/b07.py +1 -1
  5. dodal/beamlines/b07_1.py +1 -1
  6. dodal/beamlines/i03.py +92 -208
  7. dodal/beamlines/i04.py +22 -1
  8. dodal/beamlines/i05.py +1 -1
  9. dodal/beamlines/i06.py +1 -1
  10. dodal/beamlines/i09.py +1 -1
  11. dodal/beamlines/i09_1.py +27 -3
  12. dodal/beamlines/i09_2.py +58 -2
  13. dodal/beamlines/i10_optics.py +44 -25
  14. dodal/beamlines/i16.py +23 -0
  15. dodal/beamlines/i17.py +7 -3
  16. dodal/beamlines/i19_1.py +26 -14
  17. dodal/beamlines/i19_2.py +49 -38
  18. dodal/beamlines/i21.py +61 -2
  19. dodal/beamlines/i22.py +16 -1
  20. dodal/beamlines/p60.py +1 -1
  21. dodal/beamlines/training_rig.py +0 -16
  22. dodal/cli.py +26 -12
  23. dodal/common/coordination.py +3 -2
  24. dodal/device_manager.py +604 -0
  25. dodal/devices/cryostream.py +28 -57
  26. dodal/devices/eiger.py +41 -27
  27. dodal/devices/electron_analyser/__init__.py +0 -33
  28. dodal/devices/electron_analyser/base/__init__.py +58 -0
  29. dodal/devices/electron_analyser/base/base_controller.py +73 -0
  30. dodal/devices/electron_analyser/base/base_detector.py +214 -0
  31. dodal/devices/electron_analyser/{abstract → base}/base_driver_io.py +23 -42
  32. dodal/devices/electron_analyser/{abstract → base}/base_region.py +47 -11
  33. dodal/devices/electron_analyser/{util.py → base/base_util.py} +1 -1
  34. dodal/devices/electron_analyser/{energy_sources.py → base/energy_sources.py} +1 -1
  35. dodal/devices/electron_analyser/specs/__init__.py +4 -4
  36. dodal/devices/electron_analyser/specs/specs_detector.py +46 -0
  37. dodal/devices/electron_analyser/specs/{driver_io.py → specs_driver_io.py} +23 -26
  38. dodal/devices/electron_analyser/specs/{region.py → specs_region.py} +4 -3
  39. dodal/devices/electron_analyser/vgscienta/__init__.py +4 -4
  40. dodal/devices/electron_analyser/vgscienta/vgscienta_detector.py +52 -0
  41. dodal/devices/electron_analyser/vgscienta/{driver_io.py → vgscienta_driver_io.py} +25 -31
  42. dodal/devices/electron_analyser/vgscienta/{region.py → vgscienta_region.py} +6 -6
  43. dodal/devices/i04/max_pixel.py +38 -0
  44. dodal/devices/i09_1_shared/__init__.py +8 -1
  45. dodal/devices/i09_1_shared/hard_energy.py +112 -0
  46. dodal/devices/i09_2_shared/__init__.py +0 -0
  47. dodal/devices/i09_2_shared/i09_apple2.py +14 -0
  48. dodal/devices/i10/i10_apple2.py +24 -22
  49. dodal/devices/i17/i17_apple2.py +32 -20
  50. dodal/devices/i19/access_controlled/attenuator_motor_squad.py +61 -0
  51. dodal/devices/i19/access_controlled/blueapi_device.py +9 -1
  52. dodal/devices/i19/access_controlled/shutter.py +2 -4
  53. dodal/devices/i21/__init__.py +3 -1
  54. dodal/devices/insertion_device/__init__.py +58 -0
  55. dodal/devices/{apple2_undulator.py → insertion_device/apple2_undulator.py} +102 -44
  56. dodal/devices/insertion_device/energy_motor_lookup.py +88 -0
  57. dodal/devices/insertion_device/id_enum.py +17 -0
  58. dodal/devices/insertion_device/lookup_table_models.py +317 -0
  59. dodal/devices/motors.py +14 -0
  60. dodal/devices/robot.py +16 -11
  61. dodal/plans/__init__.py +1 -1
  62. dodal/plans/configure_arm_trigger_and_disarm_detector.py +2 -4
  63. dodal/testing/electron_analyser/device_factory.py +4 -4
  64. dodal/testing/fixtures/devices/__init__.py +0 -0
  65. dodal/testing/fixtures/devices/apple2.py +78 -0
  66. dodal/testing/fixtures/run_engine.py +4 -0
  67. dodal/utils.py +6 -3
  68. dodal/devices/electron_analyser/abstract/__init__.py +0 -25
  69. dodal/devices/electron_analyser/abstract/base_detector.py +0 -63
  70. dodal/devices/electron_analyser/abstract/types.py +0 -12
  71. dodal/devices/electron_analyser/detector.py +0 -143
  72. dodal/devices/electron_analyser/specs/detector.py +0 -34
  73. dodal/devices/electron_analyser/types.py +0 -57
  74. dodal/devices/electron_analyser/vgscienta/detector.py +0 -48
  75. dodal/devices/util/lookup_tables_apple2.py +0 -390
  76. {dls_dodal-1.66.0.dist-info → dls_dodal-1.68.0.dist-info}/WHEEL +0 -0
  77. {dls_dodal-1.66.0.dist-info → dls_dodal-1.68.0.dist-info}/entry_points.txt +0 -0
  78. {dls_dodal-1.66.0.dist-info → dls_dodal-1.68.0.dist-info}/licenses/LICENSE +0 -0
  79. {dls_dodal-1.66.0.dist-info → dls_dodal-1.68.0.dist-info}/top_level.txt +0 -0
  80. /dodal/devices/electron_analyser/{enums.py → base/base_enums.py} +0 -0
  81. /dodal/devices/electron_analyser/specs/{enums.py → specs_enums.py} +0 -0
  82. /dodal/devices/electron_analyser/vgscienta/{enums.py → vgscienta_enums.py} +0 -0
  83. /dodal/plans/{scanspec.py → spec_path.py} +0 -0
@@ -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
@@ -398,7 +384,7 @@ class Apple2(StandardReadable, Movable[Apple2Val], Generic[PhaseAxesType]):
398
384
 
399
385
 
400
386
  class EnergyMotorConvertor(Protocol):
401
- def __call__(self, energy: float, pol: Pol) -> tuple[float, float]:
387
+ def __call__(self, energy: float, pol: Pol) -> float:
402
388
  """Protocol to provide energy to motor position conversion"""
403
389
  ...
404
390
 
@@ -426,19 +412,18 @@ class Apple2Controller(abc.ABC, StandardReadable, Generic[Apple2Type]):
426
412
  Soft signal for the polarisation setpoint.
427
413
  polarisation : derived_signal_rw
428
414
  Hardware-backed signal for polarisation readback and control.
429
- energy_to_motor : EnergyMotorConvertor
430
- Callable that converts energy and polarisation to motor positions.
415
+ gap_energy_to_motor_converter : EnergyMotorConvertor
416
+ Callable that converts energy and polarisation to gap motor positions.
417
+ phase_energy_to_motor_converter : EnergyMotorConvertor
418
+ Callable that converts energy and polarisation to phase motor positions.
431
419
 
432
420
  Abstract Methods
433
421
  ----------------
434
- _set_motors_from_energy(value: float) -> None
435
- Abstract method to set motor positions for a given energy and polarisation.
436
- energy_to_motor : EnergyMotorConvertor
437
- A callable that converts energy and polarisation to motor positions.
438
-
422
+ _get_apple2_value(gap: float, phase: float) -> Apple2Val
423
+ Abstract method to return the Apple2Val used to set the apple2 with.
439
424
  Notes
440
425
  -----
441
- - Subclasses must implement `_set_motors_from_energy` for beamline-specific logic.
426
+ - Subclasses must implement `_get_apple2_value` for beamline-specific logic.
442
427
  - LH3 polarisation is indistinguishable from LH in hardware; special handling is provided.
443
428
  - Supports multiple polarisation modes, including linear horizontal (LH), linear vertical (LV),
444
429
  positive circular (PC), negative circular (NC), and linear arbitrary (LA).
@@ -448,7 +433,9 @@ class Apple2Controller(abc.ABC, StandardReadable, Generic[Apple2Type]):
448
433
  def __init__(
449
434
  self,
450
435
  apple2: Apple2Type,
451
- energy_to_motor_converter: EnergyMotorConvertor,
436
+ gap_energy_motor_converter: EnergyMotorConvertor,
437
+ phase_energy_motor_converter: EnergyMotorConvertor,
438
+ units: str = "eV",
452
439
  name: str = "",
453
440
  ) -> None:
454
441
  """
@@ -460,19 +447,20 @@ class Apple2Controller(abc.ABC, StandardReadable, Generic[Apple2Type]):
460
447
  name: str
461
448
  Name of the device.
462
449
  """
463
- self.energy_to_motor = energy_to_motor_converter
464
450
  self.apple2 = Reference(apple2)
451
+ self.gap_energy_motor_converter = gap_energy_motor_converter
452
+ self.phase_energy_motor_converter = phase_energy_motor_converter
465
453
 
466
454
  # Store the set energy for readback.
467
455
  self._energy, self._energy_set = soft_signal_r_and_setter(
468
- float, initial_value=None, units="eV"
456
+ float, initial_value=None, units=units
469
457
  )
470
458
  with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
471
459
  self.energy = derived_signal_rw(
472
460
  raw_to_derived=self._read_energy,
473
461
  set_derived=self._set_energy,
474
462
  energy=self._energy,
475
- derived_units="eV",
463
+ derived_units=units,
476
464
  )
477
465
 
478
466
  # Store the polarisation for setpoint. And provide readback for LH3.
@@ -480,10 +468,11 @@ class Apple2Controller(abc.ABC, StandardReadable, Generic[Apple2Type]):
480
468
  self.polarisation_setpoint, self._polarisation_setpoint_set = (
481
469
  soft_signal_r_and_setter(Pol)
482
470
  )
471
+ phase = self.apple2().phase()
483
472
  # check if undulator phase is unlocked.
484
- if isinstance(self.apple2().phase(), UndulatorPhaseAxes):
485
- top_inner = self.apple2().phase().top_inner.user_readback
486
- btm_outer = self.apple2().phase().btm_outer.user_readback
473
+ if isinstance(phase, UndulatorPhaseAxes):
474
+ top_inner = phase.top_inner.user_readback
475
+ btm_outer = phase.btm_outer.user_readback
487
476
  else:
488
477
  # If locked phase axes make the locked phase 0.
489
478
  top_inner = btm_outer = soft_signal_rw(float, initial_value=0.0)
@@ -495,24 +484,35 @@ class Apple2Controller(abc.ABC, StandardReadable, Generic[Apple2Type]):
495
484
  raw_to_derived=self._read_pol,
496
485
  set_derived=self._set_pol,
497
486
  pol=self.polarisation_setpoint,
498
- top_outer=self.apple2().phase().top_outer.user_readback,
487
+ top_outer=phase.top_outer.user_readback,
499
488
  top_inner=top_inner,
500
- btm_inner=self.apple2().phase().btm_inner.user_readback,
489
+ btm_inner=phase.btm_inner.user_readback,
501
490
  btm_outer=btm_outer,
502
491
  gap=self.apple2().gap().user_readback,
503
492
  )
504
493
  super().__init__(name)
505
494
 
506
495
  @abc.abstractmethod
507
- async def _set_motors_from_energy(self, value: float) -> None:
496
+ def _get_apple2_value(self, gap: float, phase: float, pol: Pol) -> Apple2Val:
508
497
  """
509
498
  This method should be implemented by the beamline specific ID class as the
510
499
  motor positions will be different for each beamline depending on the
511
- undulator design and the lookup table used.
500
+ undulator design.
512
501
  """
513
502
 
503
+ async def _set_motors_from_energy_and_polarisation(
504
+ self, energy: float, pol: Pol
505
+ ) -> None:
506
+ """Set the undulator motors for a given energy and polarisation."""
507
+ gap = self.gap_energy_motor_converter(energy=energy, pol=pol)
508
+ phase = self.phase_energy_motor_converter(energy=energy, pol=pol)
509
+ apple2_val = self._get_apple2_value(gap, phase, pol)
510
+ LOGGER.info(f"Setting polarisation to {pol}, with values: {apple2_val}")
511
+ await self.apple2().set(id_motor_values=apple2_val)
512
+
514
513
  async def _set_energy(self, energy: float) -> None:
515
- await self._set_motors_from_energy(energy)
514
+ pol = await self._check_and_get_pol_setpoint()
515
+ await self._set_motors_from_energy_and_polarisation(energy, pol)
516
516
  self._energy_set(energy)
517
517
 
518
518
  def _read_energy(self, energy: float) -> float:
@@ -524,7 +524,6 @@ class Apple2Controller(abc.ABC, StandardReadable, Generic[Apple2Type]):
524
524
  Check the polarisation setpoint and if it is NONE try to read it from
525
525
  hardware.
526
526
  """
527
-
528
527
  pol = await self.polarisation_setpoint.get_value()
529
528
 
530
529
  if pol == Pol.NONE:
@@ -653,6 +652,70 @@ class Apple2Controller(abc.ABC, StandardReadable, Generic[Apple2Type]):
653
652
  return Pol.NONE, 0.0
654
653
 
655
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
+
656
719
  class InsertionDeviceEnergyBase(abc.ABC, StandardReadable, Movable):
657
720
  """Base class for ID energy movable device."""
658
721
 
@@ -717,12 +780,7 @@ class InsertionDeviceEnergy(InsertionDeviceEnergyBase):
717
780
  self.energy = Reference(id_controller.energy)
718
781
  super().__init__(name=name)
719
782
 
720
- self.add_readables(
721
- [
722
- self.energy(),
723
- ],
724
- StandardReadableFormat.HINTED_SIGNAL,
725
- )
783
+ self.add_readables([self.energy()], StandardReadableFormat.HINTED_SIGNAL)
726
784
 
727
785
  @AsyncStatus.wrap
728
786
  async def set(self, energy: float) -> None:
@@ -0,0 +1,88 @@
1
+ from pathlib import Path
2
+
3
+ from daq_config_server.client import ConfigServer
4
+
5
+ from dodal.devices.insertion_device.id_enum import Pol
6
+ from dodal.devices.insertion_device.lookup_table_models import (
7
+ LookupTable,
8
+ LookupTableColumnConfig,
9
+ convert_csv_to_lookup,
10
+ )
11
+
12
+
13
+ class EnergyMotorLookup:
14
+ """
15
+ Handles a lookup table for Apple2 ID, converting energy/polarisation to a motor
16
+ position.
17
+
18
+ After update_lookup_table() has populated the lookup table, `find_value_in_lookup_table()`
19
+ can be used to compute gap / phase for a requested energy / polarisation pair.
20
+ """
21
+
22
+ def __init__(self, lut: LookupTable | None = None):
23
+ if lut is None:
24
+ lut = LookupTable()
25
+ self.lut = lut
26
+
27
+ def update_lookup_table(self) -> None:
28
+ """Do nothing by default. Sub classes may override this method to provide logic
29
+ on what updating lookup table does."""
30
+ pass
31
+
32
+ def find_value_in_lookup_table(self, energy: float, pol: Pol) -> float:
33
+ """
34
+ Convert energy and polarisation to a value from the lookup table.
35
+
36
+ Parameters:
37
+ -----------
38
+ energy : float
39
+ Desired energy.
40
+ pol : Pol
41
+ Polarisation mode.
42
+
43
+ Returns:
44
+ ----------
45
+ float
46
+ gap / phase motor position from the lookup table.
47
+ """
48
+ # if lut is empty, force an update to pull updated lut incase subclasses have
49
+ # implemented it.
50
+ if not self.lut.root:
51
+ self.update_lookup_table()
52
+ poly = self.lut.get_poly(energy=energy, pol=pol)
53
+ return poly(energy)
54
+
55
+
56
+ class ConfigServerEnergyMotorLookup(EnergyMotorLookup):
57
+ """Fetches and parses lookup table (csv) from a config server, supports dynamic
58
+ updates, and validates input."""
59
+
60
+ def __init__(
61
+ self,
62
+ config_client: ConfigServer,
63
+ lut_config: LookupTableColumnConfig,
64
+ path: Path,
65
+ ):
66
+ """
67
+ Parameters:
68
+ -----------
69
+ config_client:
70
+ The config server client to fetch the look up table data.
71
+ lut_config:
72
+ Configuration that defines how to process file contents into a LookupTable
73
+ path:
74
+ File path to the lookup table.
75
+ """
76
+ self.path = path
77
+ self.config_client = config_client
78
+ self.lut_config = lut_config
79
+ super().__init__()
80
+
81
+ def read_lut(self) -> LookupTable:
82
+ file_contents = self.config_client.get_file_contents(
83
+ self.path, reset_cached_result=True
84
+ )
85
+ return convert_csv_to_lookup(file_contents, lut_config=self.lut_config)
86
+
87
+ def update_lookup_table(self) -> None:
88
+ self.lut = self.read_lut()
@@ -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"