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.
- {dls_dodal-1.66.0.dist-info → dls_dodal-1.68.0.dist-info}/METADATA +2 -2
- {dls_dodal-1.66.0.dist-info → dls_dodal-1.68.0.dist-info}/RECORD +75 -65
- dodal/_version.py +2 -2
- dodal/beamlines/b07.py +1 -1
- dodal/beamlines/b07_1.py +1 -1
- dodal/beamlines/i03.py +92 -208
- dodal/beamlines/i04.py +22 -1
- dodal/beamlines/i05.py +1 -1
- dodal/beamlines/i06.py +1 -1
- dodal/beamlines/i09.py +1 -1
- dodal/beamlines/i09_1.py +27 -3
- dodal/beamlines/i09_2.py +58 -2
- dodal/beamlines/i10_optics.py +44 -25
- dodal/beamlines/i16.py +23 -0
- dodal/beamlines/i17.py +7 -3
- dodal/beamlines/i19_1.py +26 -14
- dodal/beamlines/i19_2.py +49 -38
- dodal/beamlines/i21.py +61 -2
- dodal/beamlines/i22.py +16 -1
- dodal/beamlines/p60.py +1 -1
- dodal/beamlines/training_rig.py +0 -16
- dodal/cli.py +26 -12
- dodal/common/coordination.py +3 -2
- dodal/device_manager.py +604 -0
- dodal/devices/cryostream.py +28 -57
- dodal/devices/eiger.py +41 -27
- dodal/devices/electron_analyser/__init__.py +0 -33
- dodal/devices/electron_analyser/base/__init__.py +58 -0
- dodal/devices/electron_analyser/base/base_controller.py +73 -0
- dodal/devices/electron_analyser/base/base_detector.py +214 -0
- dodal/devices/electron_analyser/{abstract → base}/base_driver_io.py +23 -42
- dodal/devices/electron_analyser/{abstract → base}/base_region.py +47 -11
- dodal/devices/electron_analyser/{util.py → base/base_util.py} +1 -1
- dodal/devices/electron_analyser/{energy_sources.py → base/energy_sources.py} +1 -1
- dodal/devices/electron_analyser/specs/__init__.py +4 -4
- dodal/devices/electron_analyser/specs/specs_detector.py +46 -0
- dodal/devices/electron_analyser/specs/{driver_io.py → specs_driver_io.py} +23 -26
- dodal/devices/electron_analyser/specs/{region.py → specs_region.py} +4 -3
- dodal/devices/electron_analyser/vgscienta/__init__.py +4 -4
- dodal/devices/electron_analyser/vgscienta/vgscienta_detector.py +52 -0
- dodal/devices/electron_analyser/vgscienta/{driver_io.py → vgscienta_driver_io.py} +25 -31
- dodal/devices/electron_analyser/vgscienta/{region.py → vgscienta_region.py} +6 -6
- dodal/devices/i04/max_pixel.py +38 -0
- dodal/devices/i09_1_shared/__init__.py +8 -1
- dodal/devices/i09_1_shared/hard_energy.py +112 -0
- dodal/devices/i09_2_shared/__init__.py +0 -0
- dodal/devices/i09_2_shared/i09_apple2.py +14 -0
- dodal/devices/i10/i10_apple2.py +24 -22
- dodal/devices/i17/i17_apple2.py +32 -20
- dodal/devices/i19/access_controlled/attenuator_motor_squad.py +61 -0
- dodal/devices/i19/access_controlled/blueapi_device.py +9 -1
- dodal/devices/i19/access_controlled/shutter.py +2 -4
- dodal/devices/i21/__init__.py +3 -1
- dodal/devices/insertion_device/__init__.py +58 -0
- dodal/devices/{apple2_undulator.py → insertion_device/apple2_undulator.py} +102 -44
- dodal/devices/insertion_device/energy_motor_lookup.py +88 -0
- dodal/devices/insertion_device/id_enum.py +17 -0
- dodal/devices/insertion_device/lookup_table_models.py +317 -0
- dodal/devices/motors.py +14 -0
- dodal/devices/robot.py +16 -11
- dodal/plans/__init__.py +1 -1
- dodal/plans/configure_arm_trigger_and_disarm_detector.py +2 -4
- dodal/testing/electron_analyser/device_factory.py +4 -4
- dodal/testing/fixtures/devices/__init__.py +0 -0
- dodal/testing/fixtures/devices/apple2.py +78 -0
- dodal/testing/fixtures/run_engine.py +4 -0
- dodal/utils.py +6 -3
- dodal/devices/electron_analyser/abstract/__init__.py +0 -25
- dodal/devices/electron_analyser/abstract/base_detector.py +0 -63
- dodal/devices/electron_analyser/abstract/types.py +0 -12
- dodal/devices/electron_analyser/detector.py +0 -143
- dodal/devices/electron_analyser/specs/detector.py +0 -34
- dodal/devices/electron_analyser/types.py +0 -57
- dodal/devices/electron_analyser/vgscienta/detector.py +0 -48
- dodal/devices/util/lookup_tables_apple2.py +0 -390
- {dls_dodal-1.66.0.dist-info → dls_dodal-1.68.0.dist-info}/WHEEL +0 -0
- {dls_dodal-1.66.0.dist-info → dls_dodal-1.68.0.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.66.0.dist-info → dls_dodal-1.68.0.dist-info}/licenses/LICENSE +0 -0
- {dls_dodal-1.66.0.dist-info → dls_dodal-1.68.0.dist-info}/top_level.txt +0 -0
- /dodal/devices/electron_analyser/{enums.py → base/base_enums.py} +0 -0
- /dodal/devices/electron_analyser/specs/{enums.py → specs_enums.py} +0 -0
- /dodal/devices/electron_analyser/vgscienta/{enums.py → vgscienta_enums.py} +0 -0
- /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) ->
|
|
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
|
-
|
|
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
|
-
|
|
435
|
-
Abstract method to
|
|
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 `
|
|
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
|
-
|
|
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=
|
|
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=
|
|
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(
|
|
485
|
-
top_inner =
|
|
486
|
-
btm_outer =
|
|
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=
|
|
487
|
+
top_outer=phase.top_outer.user_readback,
|
|
499
488
|
top_inner=top_inner,
|
|
500
|
-
btm_inner=
|
|
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
|
-
|
|
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
|
|
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.
|
|
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"
|