dls-dodal 1.68.0__py3-none-any.whl → 2.0.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.68.0.dist-info → dls_dodal-2.0.0.dist-info}/METADATA +1 -31
- dls_dodal-2.0.0.dist-info/RECORD +354 -0
- {dls_dodal-1.68.0.dist-info → dls_dodal-2.0.0.dist-info}/WHEEL +1 -1
- dodal/_version.py +2 -2
- dodal/beamlines/__init__.py +10 -17
- dodal/beamlines/adsim.py +40 -33
- dodal/beamlines/b01_1.py +11 -0
- dodal/beamlines/b07.py +17 -21
- dodal/beamlines/b07_1.py +20 -22
- dodal/beamlines/b07_shared.py +12 -0
- dodal/beamlines/b16.py +1 -1
- dodal/beamlines/b21.py +15 -6
- dodal/beamlines/i02_1.py +17 -45
- dodal/beamlines/i02_2.py +6 -12
- dodal/beamlines/i03.py +8 -5
- dodal/beamlines/i03_supervisor.py +19 -0
- dodal/beamlines/i04.py +87 -184
- dodal/beamlines/i05.py +9 -39
- dodal/beamlines/i05_1.py +4 -13
- dodal/beamlines/i05_shared.py +51 -0
- dodal/beamlines/i06_1.py +26 -0
- dodal/beamlines/{i06.py → i06_shared.py} +25 -14
- dodal/beamlines/i07.py +14 -16
- dodal/beamlines/i09.py +76 -29
- dodal/beamlines/i09_1.py +25 -56
- dodal/beamlines/i09_1_shared.py +61 -0
- dodal/beamlines/i09_2.py +6 -100
- dodal/beamlines/i09_2_shared.py +110 -0
- dodal/beamlines/i10.py +60 -54
- dodal/beamlines/i10_1.py +99 -10
- dodal/beamlines/{i10_optics.py → i10_shared.py} +80 -66
- dodal/beamlines/i11.py +31 -18
- dodal/beamlines/i13_1.py +1 -1
- dodal/beamlines/i15.py +6 -6
- dodal/beamlines/i15_1.py +6 -6
- dodal/beamlines/i16.py +11 -0
- dodal/beamlines/i17.py +37 -28
- dodal/beamlines/i18.py +3 -4
- dodal/beamlines/i19_1.py +95 -34
- dodal/beamlines/i19_2.py +68 -52
- dodal/beamlines/i19_optics.py +26 -13
- dodal/beamlines/i20_1.py +17 -11
- dodal/beamlines/i21.py +44 -29
- dodal/beamlines/i22.py +19 -4
- dodal/beamlines/i23.py +20 -27
- dodal/beamlines/i24.py +64 -113
- dodal/beamlines/k07.py +99 -5
- dodal/beamlines/p38.py +3 -3
- dodal/beamlines/p60.py +35 -14
- dodal/beamlines/p99.py +16 -15
- dodal/beamlines/training_rig.py +20 -12
- dodal/cli.py +36 -2
- dodal/common/__init__.py +2 -1
- dodal/common/beamlines/beamline_parameters.py +2 -1
- dodal/common/beamlines/beamline_utils.py +11 -9
- dodal/common/beamlines/commissioning_mode.py +6 -3
- dodal/common/coordination.py +12 -14
- dodal/common/crystal_metadata.py +5 -8
- dodal/common/device_utils.py +4 -3
- dodal/common/maths.py +87 -19
- dodal/common/udc_directory_provider.py +13 -8
- dodal/common/visit.py +18 -21
- dodal/common/watcher_utils.py +13 -12
- dodal/device_manager.py +94 -54
- dodal/devices/aperturescatterguard.py +26 -27
- dodal/devices/areadetector/plugins/cam.py +1 -3
- dodal/devices/areadetector/plugins/mjpg.py +6 -5
- dodal/devices/attenuator/attenuator.py +12 -11
- dodal/devices/beamlines/b07/__init__.py +3 -0
- dodal/devices/{b07_1 → beamlines/b07_1}/__init__.py +2 -2
- dodal/devices/{b07_1 → beamlines/b07_1}/ccmc.py +5 -10
- dodal/devices/{b16 → beamlines/b16}/detector.py +2 -3
- dodal/devices/{i02_1 → beamlines/i02_1}/fast_grid_scan.py +2 -3
- dodal/devices/{i02_1 → beamlines/i02_1}/sample_motors.py +1 -1
- dodal/devices/{i03 → beamlines/i03}/beamsize.py +11 -7
- dodal/devices/{i03 → beamlines/i03}/dcm.py +1 -2
- dodal/devices/{i03 → beamlines/i03}/undulator_dcm.py +4 -5
- dodal/devices/beamlines/i04/beam_centre.py +151 -0
- dodal/devices/{i04 → beamlines/i04}/beamsize.py +11 -7
- dodal/devices/beamlines/i04/max_pixel.py +25 -0
- dodal/devices/{i04 → beamlines/i04}/murko_results.py +23 -8
- dodal/devices/{i04 → beamlines/i04}/transfocator.py +10 -15
- dodal/devices/beamlines/i05/__init__.py +3 -0
- dodal/devices/beamlines/i06_shared/__init__.py +3 -0
- dodal/devices/beamlines/i06_shared/i06_enum.py +7 -0
- dodal/devices/{i07 → beamlines/i07}/dcm.py +2 -3
- dodal/devices/{i07 → beamlines/i07}/id.py +8 -9
- dodal/devices/beamlines/i09/__init__.py +3 -0
- dodal/devices/{i09_1_shared → beamlines/i09_1_shared}/hard_energy.py +5 -6
- dodal/devices/{i09_1_shared → beamlines/i09_1_shared}/hard_undulator_functions.py +19 -16
- dodal/devices/{i10 → beamlines/i10}/diagnostics.py +4 -3
- dodal/devices/{i10 → beamlines/i10}/i10_apple2.py +37 -51
- dodal/devices/{i10 → beamlines/i10}/rasor/rasor_current_amp.py +1 -24
- dodal/devices/{i10 → beamlines/i10}/rasor/rasor_motors.py +2 -2
- dodal/devices/{i10 → beamlines/i10}/slits.py +5 -3
- dodal/devices/beamlines/i10_1/__init__.py +9 -0
- dodal/devices/beamlines/i10_1/electromagnet/magnet.py +16 -0
- dodal/devices/beamlines/i10_1/electromagnet/stages.py +14 -0
- dodal/devices/beamlines/i10_1/scaler_cards.py +13 -0
- dodal/devices/{i11 → beamlines/i11}/cyberstar_blower.py +1 -1
- dodal/devices/{i11 → beamlines/i11}/diff_stages.py +4 -6
- dodal/devices/{i11 → beamlines/i11}/mythen.py +3 -4
- dodal/devices/{i11 → beamlines/i11}/nx100robot.py +6 -6
- dodal/devices/{i11 → beamlines/i11}/spinner.py +1 -1
- dodal/devices/{i13_1 → beamlines/i13_1}/merlin.py +1 -1
- dodal/devices/{i15 → beamlines/i15}/dcm.py +1 -2
- dodal/devices/{i15 → beamlines/i15}/focussing_mirror.py +5 -5
- dodal/devices/{i15 → beamlines/i15}/jack.py +2 -2
- dodal/devices/{i15 → beamlines/i15}/multilayer_mirror.py +1 -1
- dodal/devices/{i17 → beamlines/i17}/i17_apple2.py +16 -22
- dodal/devices/{i18 → beamlines/i18}/diode.py +1 -1
- dodal/devices/{i19 → beamlines/i19}/access_controlled/attenuator_motor_squad.py +12 -8
- dodal/devices/{i19 → beamlines/i19}/access_controlled/blueapi_device.py +16 -15
- dodal/devices/beamlines/i19/access_controlled/piezo_control.py +72 -0
- dodal/devices/{i19 → beamlines/i19}/access_controlled/shutter.py +11 -9
- dodal/devices/{i19 → beamlines/i19}/backlight.py +3 -1
- dodal/devices/{i19 → beamlines/i19}/mapt_configuration.py +2 -1
- dodal/devices/{i19 → beamlines/i19}/pin_col_stages.py +11 -8
- dodal/devices/beamlines/i19/pin_tip.py +32 -0
- dodal/devices/beamlines/i21/__init__.py +3 -0
- dodal/devices/{i22 → beamlines/i22}/dcm.py +1 -2
- dodal/devices/{i22 → beamlines/i22}/fswitch.py +1 -3
- dodal/devices/{i22 → beamlines/i22}/nxsas.py +5 -4
- dodal/devices/{i24 → beamlines/i24}/beam_center.py +1 -1
- dodal/devices/{i24 → beamlines/i24}/beamstop.py +2 -2
- dodal/devices/{i24 → beamlines/i24}/commissioning_jungfrau.py +12 -12
- dodal/devices/{i24 → beamlines/i24}/dcm.py +1 -3
- dodal/devices/{i24 → beamlines/i24}/dual_backlight.py +3 -3
- dodal/devices/{i24 → beamlines/i24}/pmac.py +9 -7
- dodal/devices/{p60 → beamlines/p60}/lab_xray_source.py +1 -1
- dodal/devices/beamlines/p99/__init__.py +0 -0
- dodal/devices/{p99 → beamlines/p99}/andor2_point.py +11 -15
- dodal/devices/bimorph_mirror.py +22 -20
- dodal/devices/collimation_table.py +3 -2
- dodal/devices/common_dcm.py +30 -20
- dodal/devices/controllers.py +2 -2
- dodal/devices/cryostream.py +8 -0
- dodal/devices/current_amplifiers/current_amplifier.py +16 -18
- dodal/devices/current_amplifiers/current_amplifier_detector.py +9 -10
- dodal/devices/current_amplifiers/femto.py +8 -9
- dodal/devices/current_amplifiers/sr570.py +16 -16
- dodal/devices/current_amplifiers/struck_scaler_counter.py +5 -5
- dodal/devices/detector/det_resolution.py +9 -8
- dodal/devices/detector/detector.py +4 -2
- dodal/devices/diamond_filter.py +3 -4
- dodal/devices/eiger.py +32 -17
- dodal/devices/eiger_odin.py +1 -1
- dodal/devices/electron_analyser/base/__init__.py +3 -3
- dodal/devices/electron_analyser/base/base_controller.py +32 -21
- dodal/devices/electron_analyser/base/base_detector.py +15 -20
- dodal/devices/electron_analyser/base/base_driver_io.py +39 -46
- dodal/devices/electron_analyser/base/base_enums.py +0 -5
- dodal/devices/electron_analyser/base/base_region.py +29 -31
- dodal/devices/electron_analyser/base/base_util.py +18 -16
- dodal/devices/electron_analyser/base/energy_sources.py +35 -40
- dodal/devices/electron_analyser/specs/specs_detector.py +7 -6
- dodal/devices/electron_analyser/vgscienta/vgscienta_detector.py +7 -6
- dodal/devices/eurotherm.py +3 -2
- dodal/devices/fast_grid_scan.py +31 -34
- dodal/devices/fast_shutter.py +125 -39
- dodal/devices/flux.py +1 -1
- dodal/devices/focusing_mirror.py +29 -11
- dodal/devices/hutch_shutter.py +6 -6
- dodal/devices/insertion_device/__init__.py +20 -8
- dodal/devices/insertion_device/apple2_controller.py +371 -0
- dodal/devices/insertion_device/apple2_undulator.py +184 -587
- dodal/devices/insertion_device/apple_knot_controller.py +222 -0
- dodal/devices/insertion_device/energy.py +161 -0
- dodal/devices/insertion_device/energy_motor_lookup.py +21 -28
- dodal/devices/insertion_device/lookup_table_models.py +47 -52
- dodal/devices/insertion_device/polarisation.py +36 -0
- dodal/devices/ipin.py +1 -1
- dodal/devices/linkam3.py +7 -5
- dodal/devices/motors.py +107 -19
- dodal/devices/mx_phase1/beamstop.py +2 -4
- dodal/devices/oav/oav_calculations.py +20 -13
- dodal/devices/oav/oav_detector.py +92 -22
- dodal/devices/oav/oav_parameters.py +4 -9
- dodal/devices/oav/oav_to_redis_forwarder.py +22 -18
- dodal/devices/oav/pin_image_recognition/__init__.py +4 -6
- dodal/devices/oav/pin_image_recognition/manual_test.py +1 -2
- dodal/devices/oav/pin_image_recognition/utils.py +30 -32
- dodal/devices/oav/snapshots/grid_overlay.py +10 -9
- dodal/devices/oav/snapshots/snapshot_image_processing.py +15 -13
- dodal/devices/oav/utils.py +20 -6
- dodal/devices/p45.py +3 -9
- dodal/devices/pgm.py +8 -14
- dodal/devices/pressure_jump_cell.py +93 -32
- dodal/devices/qbpm.py +1 -3
- dodal/devices/robot.py +45 -20
- dodal/devices/s4_slit_gaps.py +1 -1
- dodal/devices/selectable_source.py +41 -0
- dodal/devices/slits.py +2 -5
- dodal/devices/smargon.py +2 -3
- dodal/devices/temperture_controller/lakeshore/lakeshore.py +38 -64
- dodal/devices/temperture_controller/lakeshore/lakeshore_io.py +21 -35
- dodal/devices/tetramm.py +7 -7
- dodal/devices/turbo_slit.py +8 -7
- dodal/devices/undulator.py +42 -56
- dodal/devices/util/adjuster_plans.py +2 -3
- dodal/devices/util/epics_util.py +10 -10
- dodal/devices/util/lookup_tables.py +17 -18
- dodal/devices/v2f.py +2 -3
- dodal/devices/watsonmarlow323_pump.py +1 -1
- dodal/devices/xbpm_feedback.py +3 -2
- dodal/devices/xspress3/xspress3.py +8 -11
- dodal/devices/xspress3/xspress3_channel.py +3 -6
- dodal/devices/zebra/zebra.py +21 -7
- dodal/devices/zebra/zebra_constants_mapping.py +12 -7
- dodal/devices/zebra/zebra_controlled_shutter.py +2 -1
- dodal/devices/zocalo/zocalo_interaction.py +14 -14
- dodal/devices/zocalo/zocalo_results.py +33 -33
- dodal/log.py +23 -20
- dodal/plan_stubs/check_topup.py +15 -15
- dodal/plan_stubs/data_session.py +6 -6
- dodal/plan_stubs/motor_utils.py +22 -18
- dodal/plan_stubs/pressure_jump_cell.py +18 -0
- dodal/plan_stubs/wrapped.py +40 -55
- dodal/plans/bimorph.py +63 -52
- dodal/plans/configure_arm_trigger_and_disarm_detector.py +0 -1
- dodal/plans/device_setup_plans/__init__.py +5 -0
- dodal/plans/device_setup_plans/setup_pin_tip_params.py +63 -0
- dodal/plans/preprocessors/verify_undulator_gap.py +10 -8
- dodal/plans/spec_path.py +3 -5
- dodal/plans/verify_undulator_gap.py +1 -2
- dodal/plans/wrapped.py +4 -3
- dodal/testing/__init__.py +0 -0
- dodal/testing/electron_analyser/device_factory.py +5 -7
- dodal/testing/fixtures/devices/apple2.py +38 -0
- dodal/testing/fixtures/run_engine.py +3 -7
- dodal/testing/fixtures/utils.py +1 -2
- dodal/utils.py +60 -58
- dls_dodal-1.68.0.dist-info/RECORD +0 -330
- dodal/beamline_specific_utils/i05_shared.py +0 -14
- dodal/devices/b07/__init__.py +0 -3
- dodal/devices/i04/max_pixel.py +0 -38
- dodal/devices/i05/__init__.py +0 -3
- dodal/devices/i09/__init__.py +0 -3
- dodal/devices/i21/__init__.py +0 -5
- {dls_dodal-1.68.0.dist-info → dls_dodal-2.0.0.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.68.0.dist-info → dls_dodal-2.0.0.dist-info}/licenses/LICENSE +0 -0
- {dls_dodal-1.68.0.dist-info → dls_dodal-2.0.0.dist-info}/top_level.txt +0 -0
- /dodal/{beamline_specific_utils → devices/beamlines}/__init__.py +0 -0
- /dodal/devices/{b07 → beamlines/b07}/enums.py +0 -0
- /dodal/devices/{b07_1 → beamlines/b07_1}/enums.py +0 -0
- /dodal/devices/{b16 → beamlines/b16}/__init__.py +0 -0
- /dodal/devices/{i02_1 → beamlines/i02_1}/__init__.py +0 -0
- /dodal/devices/{i02_2 → beamlines/i02_2}/__init__.py +0 -0
- /dodal/devices/{i03 → beamlines/i03}/__init__.py +0 -0
- /dodal/devices/{i03 → beamlines/i03}/constants.py +0 -0
- /dodal/devices/{i04 → beamlines/i04}/__init__.py +0 -0
- /dodal/devices/{i04 → beamlines/i04}/constants.py +0 -0
- /dodal/devices/{i05 → beamlines/i05}/enums.py +0 -0
- /dodal/devices/{i07 → beamlines/i07}/__init__.py +0 -0
- /dodal/devices/{i09 → beamlines/i09}/enums.py +0 -0
- /dodal/devices/{i09_1 → beamlines/i09_1}/__init__.py +0 -0
- /dodal/devices/{i09_1 → beamlines/i09_1}/enums.py +0 -0
- /dodal/devices/{i09_1_shared → beamlines/i09_1_shared}/__init__.py +0 -0
- /dodal/devices/{i09_2_shared → beamlines/i09_2_shared}/__init__.py +0 -0
- /dodal/devices/{i09_2_shared → beamlines/i09_2_shared}/i09_apple2.py +0 -0
- /dodal/devices/{i10 → beamlines/i10}/__init__.py +0 -0
- /dodal/devices/{i10 → beamlines/i10}/i10_setting_data.py +0 -0
- /dodal/devices/{i10 → beamlines/i10}/mirrors.py +0 -0
- /dodal/devices/{i10 → beamlines/i10}/rasor/__init__.py +0 -0
- /dodal/devices/{i10 → beamlines/i10}/rasor/rasor_scaler_cards.py +0 -0
- /dodal/devices/{i11 → beamlines/i10_1/electromagnet}/__init__.py +0 -0
- /dodal/devices/{i13_1 → beamlines/i11}/__init__.py +0 -0
- /dodal/devices/{i15 → beamlines/i13_1}/__init__.py +0 -0
- /dodal/devices/{i13_1 → beamlines/i13_1}/merlin_controller.py +0 -0
- /dodal/devices/{i17 → beamlines/i15}/__init__.py +0 -0
- /dodal/devices/{i15 → beamlines/i15}/laue.py +0 -0
- /dodal/devices/{i15 → beamlines/i15}/motors.py +0 -0
- /dodal/devices/{i15 → beamlines/i15}/rail.py +0 -0
- /dodal/devices/{i18 → beamlines/i17}/__init__.py +0 -0
- /dodal/devices/{i19 → beamlines/i18}/__init__.py +0 -0
- /dodal/devices/{i18 → beamlines/i18}/kb_mirror.py +0 -0
- /dodal/devices/{i19/access_controlled → beamlines/i19}/__init__.py +0 -0
- /dodal/devices/{i20_1 → beamlines/i19/access_controlled}/__init__.py +0 -0
- /dodal/devices/{i19 → beamlines/i19}/access_controlled/hutch_access.py +0 -0
- /dodal/devices/{i19 → beamlines/i19}/beamstop.py +0 -0
- /dodal/devices/{i19 → beamlines/i19}/diffractometer.py +0 -0
- /dodal/devices/{i22 → beamlines/i20_1}/__init__.py +0 -0
- /dodal/devices/{i21 → beamlines/i21}/enums.py +0 -0
- /dodal/devices/{i24 → beamlines/i22}/__init__.py +0 -0
- /dodal/devices/{p99 → beamlines/i24}/__init__.py +0 -0
- /dodal/devices/{i24 → beamlines/i24}/aperture.py +0 -0
- /dodal/devices/{i24 → beamlines/i24}/focus_mirrors.py +0 -0
- /dodal/devices/{i24 → beamlines/i24}/vgonio.py +0 -0
- /dodal/devices/{p60 → beamlines/p60}/__init__.py +0 -0
- /dodal/devices/{p60 → beamlines/p60}/enums.py +0 -0
- /dodal/devices/{p99 → beamlines/p99}/sample_stage.py +0 -0
- /dodal/devices/insertion_device/{id_enum.py → enum.py} +0 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
from math import isclose
|
|
3
|
+
from typing import Generic, Protocol, TypeVar
|
|
4
|
+
|
|
5
|
+
from ophyd_async.core import (
|
|
6
|
+
Reference,
|
|
7
|
+
StandardReadable,
|
|
8
|
+
StandardReadableFormat,
|
|
9
|
+
derived_signal_rw,
|
|
10
|
+
soft_signal_r_and_setter,
|
|
11
|
+
soft_signal_rw,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from dodal.devices.insertion_device.apple2_undulator import (
|
|
15
|
+
Apple2,
|
|
16
|
+
Apple2PhasesVal,
|
|
17
|
+
Apple2Val,
|
|
18
|
+
PhaseAxesType,
|
|
19
|
+
UndulatorPhaseAxes,
|
|
20
|
+
)
|
|
21
|
+
from dodal.devices.insertion_device.energy_motor_lookup import (
|
|
22
|
+
EnergyMotorLookup,
|
|
23
|
+
)
|
|
24
|
+
from dodal.devices.insertion_device.enum import Pol
|
|
25
|
+
from dodal.log import LOGGER
|
|
26
|
+
|
|
27
|
+
T = TypeVar("T")
|
|
28
|
+
MAXIMUM_MOVE_TIME = 550 # There is no useful movements take longer than this.
|
|
29
|
+
ROW_PHASE_MOTOR_TOLERANCE = 0.004
|
|
30
|
+
MAXIMUM_ROW_PHASE_MOTOR_POSITION = 24.0
|
|
31
|
+
MAXIMUM_GAP_MOTOR_POSITION = 100
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class EnergyMotorConvertor(Protocol):
|
|
35
|
+
def __call__(self, energy: float, pol: Pol) -> float:
|
|
36
|
+
"""Protocol to provide energy to motor position conversion."""
|
|
37
|
+
...
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
Apple2Type = TypeVar("Apple2Type", bound=Apple2)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Apple2Controller(abc.ABC, StandardReadable, Generic[Apple2Type]):
|
|
44
|
+
"""Abstract base class for controlling an Apple2 undulator device.
|
|
45
|
+
|
|
46
|
+
This class manages the undulator's gap and phase motors, and provides an interface
|
|
47
|
+
for controlling polarisation and energy settings. It exposes derived signals for
|
|
48
|
+
energy and polarisation, and handles conversion between energy/polarisation and
|
|
49
|
+
motor positions via a user-supplied conversion callable.
|
|
50
|
+
|
|
51
|
+
Attributes:
|
|
52
|
+
apple2 (Reference[Apple2Type]): Reference to the Apple2 device containing gap
|
|
53
|
+
and phase motors.
|
|
54
|
+
energy (derived_signal_rw): Derived signal for moving and reading back energy.
|
|
55
|
+
polarisation_setpoint (SignalR): Soft signal for the polarisation setpoint.
|
|
56
|
+
polarisation (derived_signal_rw): Hardware-backed signal for polarisation
|
|
57
|
+
readback and control.
|
|
58
|
+
gap_energy_to_motor_converter (EnergyMotorConvertor): Callable that converts
|
|
59
|
+
energy and polarisation to gap motor positions.
|
|
60
|
+
phase_energy_to_motor_converter (EnergyMotorConvertor): Callable that converts
|
|
61
|
+
energy and polarisation to phase motor positions.
|
|
62
|
+
maximum_gap_motor_position (float): Maximum allowed position for the gap motor.
|
|
63
|
+
maximum_phase_motor_position (float): Maximum allowed position for the raw phase
|
|
64
|
+
motors.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
apple2 (Apple2): An Apple2 device.
|
|
68
|
+
gap_energy_motor_converter (EnergyMotorConvertor): The callable that handles
|
|
69
|
+
the gap look up table logic for the insertion device.
|
|
70
|
+
phase_energy_motor_converter (EnergyMotorConvertor): The callable that
|
|
71
|
+
handles the phase look up table logic for the insertion device.
|
|
72
|
+
maximum_gap_motor_position (float): Maximum allowed position for the gap motor.
|
|
73
|
+
maximum_phase_motor_position (float): Maximum allowed position for the raw phase
|
|
74
|
+
motors.
|
|
75
|
+
units (str): the units of this device. Defaults to eV.
|
|
76
|
+
name (str): Name of the device.
|
|
77
|
+
|
|
78
|
+
Abstract Methods:
|
|
79
|
+
_get_apple2_value(gap: float, phase: float) -> Apple2Val
|
|
80
|
+
Abstract method to return the Apple2Val used to set the apple2 with.
|
|
81
|
+
|
|
82
|
+
Notes:
|
|
83
|
+
- Subclasses must implement `_get_apple2_value` for beamline-specific logic.
|
|
84
|
+
- LH3 polarisation is indistinguishable from LH in hardware; special handling is
|
|
85
|
+
provided.
|
|
86
|
+
- Supports multiple polarisation modes, including linear horizontal (LH), linear
|
|
87
|
+
vertical (LV), positive circular (PC), negative circular (NC), and linear
|
|
88
|
+
arbitrary (LA).
|
|
89
|
+
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
def __init__(
|
|
93
|
+
self,
|
|
94
|
+
apple2: Apple2Type,
|
|
95
|
+
gap_energy_motor_converter: EnergyMotorConvertor,
|
|
96
|
+
phase_energy_motor_converter: EnergyMotorConvertor,
|
|
97
|
+
maximum_gap_motor_position: float = MAXIMUM_GAP_MOTOR_POSITION,
|
|
98
|
+
maximum_phase_motor_position: float = MAXIMUM_ROW_PHASE_MOTOR_POSITION,
|
|
99
|
+
units: str = "eV",
|
|
100
|
+
name: str = "",
|
|
101
|
+
) -> None:
|
|
102
|
+
self.apple2 = Reference(apple2)
|
|
103
|
+
self.gap_energy_motor_converter = gap_energy_motor_converter
|
|
104
|
+
self.phase_energy_motor_converter = phase_energy_motor_converter
|
|
105
|
+
|
|
106
|
+
self.maximum_gap_motor_position = maximum_gap_motor_position
|
|
107
|
+
self.maximum_phase_motor_position = maximum_phase_motor_position
|
|
108
|
+
# Store the set energy for readback.
|
|
109
|
+
self._energy, self._energy_set = soft_signal_r_and_setter(
|
|
110
|
+
float, initial_value=None, units=units
|
|
111
|
+
)
|
|
112
|
+
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
|
|
113
|
+
self.energy = derived_signal_rw(
|
|
114
|
+
raw_to_derived=self._read_energy,
|
|
115
|
+
set_derived=self._set_energy,
|
|
116
|
+
energy=self._energy,
|
|
117
|
+
derived_units=units,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Store the polarisation for setpoint. And provide readback for LH3.
|
|
121
|
+
# LH3 is a special case as it is indistinguishable from LH in the hardware.
|
|
122
|
+
self.polarisation_setpoint, self._polarisation_setpoint_set = (
|
|
123
|
+
soft_signal_r_and_setter(Pol)
|
|
124
|
+
)
|
|
125
|
+
phase = self.apple2().phase()
|
|
126
|
+
# check if undulator phase is unlocked.
|
|
127
|
+
if isinstance(phase, UndulatorPhaseAxes):
|
|
128
|
+
top_inner = phase.top_inner.user_readback
|
|
129
|
+
btm_outer = phase.btm_outer.user_readback
|
|
130
|
+
else:
|
|
131
|
+
# If locked phase axes make the locked phase 0.
|
|
132
|
+
top_inner = btm_outer = soft_signal_rw(float, initial_value=0.0)
|
|
133
|
+
|
|
134
|
+
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
|
|
135
|
+
# Hardware backed read/write for polarisation.
|
|
136
|
+
|
|
137
|
+
self.polarisation = derived_signal_rw(
|
|
138
|
+
raw_to_derived=self._read_pol,
|
|
139
|
+
set_derived=self._set_pol,
|
|
140
|
+
pol=self.polarisation_setpoint,
|
|
141
|
+
top_outer=phase.top_outer.user_readback,
|
|
142
|
+
top_inner=top_inner,
|
|
143
|
+
btm_inner=phase.btm_inner.user_readback,
|
|
144
|
+
btm_outer=btm_outer,
|
|
145
|
+
gap=self.apple2().gap().user_readback,
|
|
146
|
+
)
|
|
147
|
+
super().__init__(name)
|
|
148
|
+
|
|
149
|
+
@abc.abstractmethod
|
|
150
|
+
def _get_apple2_value(self, gap: float, phase: float, pol: Pol) -> Apple2Val:
|
|
151
|
+
"""This method should be implemented by the beamline specific ID class as the
|
|
152
|
+
motor positions will be different for each beamline depending on the
|
|
153
|
+
undulator design.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
async def _set_motors_from_energy_and_polarisation(
|
|
157
|
+
self, energy: float, pol: Pol
|
|
158
|
+
) -> None:
|
|
159
|
+
"""Set the undulator motors for a given energy and polarisation."""
|
|
160
|
+
gap = self.gap_energy_motor_converter(energy=energy, pol=pol)
|
|
161
|
+
phase = self.phase_energy_motor_converter(energy=energy, pol=pol)
|
|
162
|
+
apple2_val = self._get_apple2_value(gap, phase, pol)
|
|
163
|
+
LOGGER.info(f"Setting polarisation to {pol}, with values: {apple2_val}")
|
|
164
|
+
await self.apple2().set(id_motor_values=apple2_val)
|
|
165
|
+
|
|
166
|
+
async def _set_energy(self, energy: float) -> None:
|
|
167
|
+
pol = await self._check_and_get_pol_setpoint()
|
|
168
|
+
await self._set_motors_from_energy_and_polarisation(energy, pol)
|
|
169
|
+
self._energy_set(energy)
|
|
170
|
+
|
|
171
|
+
def _read_energy(self, energy: float) -> float:
|
|
172
|
+
"""Readback for energy is just the set value."""
|
|
173
|
+
return energy
|
|
174
|
+
|
|
175
|
+
async def _check_and_get_pol_setpoint(self) -> Pol:
|
|
176
|
+
"""Check the polarisation setpoint and if it is NONE try to read it from
|
|
177
|
+
hardware.
|
|
178
|
+
"""
|
|
179
|
+
pol = await self.polarisation_setpoint.get_value()
|
|
180
|
+
|
|
181
|
+
if pol == Pol.NONE:
|
|
182
|
+
LOGGER.warning(
|
|
183
|
+
"Found no setpoint for polarisation. Attempting to"
|
|
184
|
+
" determine polarisation from hardware..."
|
|
185
|
+
)
|
|
186
|
+
pol = await self.polarisation.get_value()
|
|
187
|
+
if pol == Pol.NONE:
|
|
188
|
+
raise ValueError(
|
|
189
|
+
f"Polarisation cannot be determined from hardware for {self.name}"
|
|
190
|
+
)
|
|
191
|
+
self._polarisation_setpoint_set(pol)
|
|
192
|
+
return pol
|
|
193
|
+
|
|
194
|
+
async def _set_pol(
|
|
195
|
+
self,
|
|
196
|
+
value: Pol,
|
|
197
|
+
) -> None:
|
|
198
|
+
# This changes the pol setpoint and then changes polarisation via set energy.
|
|
199
|
+
self._polarisation_setpoint_set(value)
|
|
200
|
+
await self.energy.set(await self.energy.get_value(), timeout=MAXIMUM_MOVE_TIME)
|
|
201
|
+
|
|
202
|
+
def _read_pol(
|
|
203
|
+
self,
|
|
204
|
+
pol: Pol,
|
|
205
|
+
top_outer: float,
|
|
206
|
+
top_inner: float,
|
|
207
|
+
btm_inner: float,
|
|
208
|
+
btm_outer: float,
|
|
209
|
+
gap: float,
|
|
210
|
+
) -> Pol:
|
|
211
|
+
LOGGER.info(
|
|
212
|
+
f"Reading polarisation setpoint from hardware: "
|
|
213
|
+
f"top_outer={top_outer}, top_inner={top_inner}, "
|
|
214
|
+
f"btm_inner={btm_inner}, btm_outer={btm_outer}, gap={gap}."
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
read_pol, _ = self.determine_phase_from_hardware(
|
|
218
|
+
top_outer, top_inner, btm_inner, btm_outer, gap
|
|
219
|
+
)
|
|
220
|
+
# LH3 is indistinguishable from LH see determine_phase_from_hardware's docString
|
|
221
|
+
# so we return LH3 if the setpoint is LH3 and the readback is LH.
|
|
222
|
+
if pol == Pol.LH3 and read_pol == Pol.LH:
|
|
223
|
+
LOGGER.info(
|
|
224
|
+
"The hardware cannot distinguish between LH and LH3."
|
|
225
|
+
" Returning the last commanded polarisation value"
|
|
226
|
+
)
|
|
227
|
+
return Pol.LH3
|
|
228
|
+
|
|
229
|
+
return read_pol
|
|
230
|
+
|
|
231
|
+
def determine_phase_from_hardware(
|
|
232
|
+
self,
|
|
233
|
+
top_outer: float,
|
|
234
|
+
top_inner: float,
|
|
235
|
+
btm_inner: float,
|
|
236
|
+
btm_outer: float,
|
|
237
|
+
gap: float,
|
|
238
|
+
) -> tuple[Pol, float]:
|
|
239
|
+
"""Determine polarisation and phase value using motor position patterns.
|
|
240
|
+
However there is no way to return lh3 polarisation or higher harmonic setting.
|
|
241
|
+
(May be for future one can use the inverse poly to work out the energy and try
|
|
242
|
+
to match it with the current energy to workout the polarisation but during my
|
|
243
|
+
test the inverse poly is too unstable for general use).
|
|
244
|
+
"""
|
|
245
|
+
if gap > self.maximum_gap_motor_position:
|
|
246
|
+
raise RuntimeError(
|
|
247
|
+
f"{self.name} is not in use, close gap or set polarisation to use this ID"
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
if all(
|
|
251
|
+
isclose(x, 0.0, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
252
|
+
for x in [top_outer, top_inner, btm_inner, btm_outer]
|
|
253
|
+
):
|
|
254
|
+
LOGGER.info("Determined polarisation: LH (Linear Horizontal).")
|
|
255
|
+
return Pol.LH, 0.0
|
|
256
|
+
if (
|
|
257
|
+
isclose(
|
|
258
|
+
top_outer,
|
|
259
|
+
btm_inner,
|
|
260
|
+
abs_tol=ROW_PHASE_MOTOR_TOLERANCE,
|
|
261
|
+
)
|
|
262
|
+
and isclose(top_inner, 0.0, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
263
|
+
and isclose(btm_outer, 0.0, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
264
|
+
and isclose(
|
|
265
|
+
abs(btm_inner),
|
|
266
|
+
self.maximum_phase_motor_position,
|
|
267
|
+
abs_tol=ROW_PHASE_MOTOR_TOLERANCE,
|
|
268
|
+
)
|
|
269
|
+
):
|
|
270
|
+
LOGGER.info("Determined polarisation: LV (Linear Vertical).")
|
|
271
|
+
return Pol.LV, self.maximum_phase_motor_position
|
|
272
|
+
if (
|
|
273
|
+
isclose(top_outer, btm_inner, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
274
|
+
and top_outer > 0.0
|
|
275
|
+
and isclose(top_inner, 0.0, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
276
|
+
and isclose(btm_outer, 0.0, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
277
|
+
):
|
|
278
|
+
LOGGER.info("Determined polarisation: PC (Positive Circular).")
|
|
279
|
+
return Pol.PC, top_outer
|
|
280
|
+
if (
|
|
281
|
+
isclose(top_outer, btm_inner, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
282
|
+
and top_outer < 0.0
|
|
283
|
+
and isclose(top_inner, 0.0, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
284
|
+
and isclose(btm_outer, 0.0, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
285
|
+
):
|
|
286
|
+
LOGGER.info("Determined polarisation: NC (Negative Circular).")
|
|
287
|
+
return Pol.NC, top_outer
|
|
288
|
+
if (
|
|
289
|
+
isclose(top_outer, -btm_inner, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
290
|
+
and isclose(top_inner, 0.0, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
291
|
+
and isclose(btm_outer, 0.0, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
292
|
+
):
|
|
293
|
+
LOGGER.info("Determined polarisation: LA (Positive Linear Arbitrary).")
|
|
294
|
+
return Pol.LA, top_outer
|
|
295
|
+
if (
|
|
296
|
+
isclose(top_inner, -btm_outer, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
297
|
+
and isclose(top_outer, 0.0, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
298
|
+
and isclose(btm_inner, 0.0, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
299
|
+
):
|
|
300
|
+
LOGGER.info("Determined polarisation: LA (Negative Linear Arbitrary).")
|
|
301
|
+
return Pol.LA, top_inner
|
|
302
|
+
|
|
303
|
+
LOGGER.warning("Unable to determine polarisation. Defaulting to NONE.")
|
|
304
|
+
return Pol.NONE, 0.0
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class Apple2EnforceLHMoveController(
|
|
308
|
+
Apple2Controller[Apple2[PhaseAxesType]], Generic[PhaseAxesType]
|
|
309
|
+
):
|
|
310
|
+
"""The latest Apple2 version allows unrestricted motor movement.
|
|
311
|
+
However, because of the high forces involved in polarization changes,
|
|
312
|
+
all movements must be performed using the Linear Horizontal (LH) mode.
|
|
313
|
+
A look-up table must also be used to determine the highest energy that can
|
|
314
|
+
be reached in LH mode.
|
|
315
|
+
"""
|
|
316
|
+
|
|
317
|
+
def __init__(
|
|
318
|
+
self,
|
|
319
|
+
apple2: Apple2[PhaseAxesType],
|
|
320
|
+
gap_energy_motor_lut: EnergyMotorLookup,
|
|
321
|
+
phase_energy_motor_lut: EnergyMotorLookup,
|
|
322
|
+
units: str = "eV",
|
|
323
|
+
name: str = "",
|
|
324
|
+
) -> None:
|
|
325
|
+
self.gap_energy_motor_lu = gap_energy_motor_lut
|
|
326
|
+
self.phase_energy_motor_lu = phase_energy_motor_lut
|
|
327
|
+
super().__init__(
|
|
328
|
+
apple2=apple2,
|
|
329
|
+
gap_energy_motor_converter=gap_energy_motor_lut.find_value_in_lookup_table,
|
|
330
|
+
phase_energy_motor_converter=phase_energy_motor_lut.find_value_in_lookup_table,
|
|
331
|
+
units=units,
|
|
332
|
+
name=name,
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
def _get_apple2_value(self, gap: float, phase: float, pol: Pol) -> Apple2Val:
|
|
336
|
+
apple2_val = Apple2Val(
|
|
337
|
+
gap=gap,
|
|
338
|
+
phase=Apple2PhasesVal(
|
|
339
|
+
top_outer=phase,
|
|
340
|
+
top_inner=0.0,
|
|
341
|
+
btm_inner=phase,
|
|
342
|
+
btm_outer=0.0,
|
|
343
|
+
),
|
|
344
|
+
)
|
|
345
|
+
LOGGER.info(f"Getting apple2 value for pol={pol}, gap={gap}, phase={phase}.")
|
|
346
|
+
LOGGER.info(f"Apple2 motor values: {apple2_val}.")
|
|
347
|
+
|
|
348
|
+
return apple2_val
|
|
349
|
+
|
|
350
|
+
async def _set_pol(
|
|
351
|
+
self,
|
|
352
|
+
value: Pol,
|
|
353
|
+
) -> None:
|
|
354
|
+
# I09/I21 require all polarisation change to go via LH.
|
|
355
|
+
current_pol = await self.polarisation.get_value()
|
|
356
|
+
if current_pol == value:
|
|
357
|
+
LOGGER.info(f"Polarisation already at {value}")
|
|
358
|
+
else:
|
|
359
|
+
target_energy = await self.energy.get_value()
|
|
360
|
+
if (value is not Pol.LH) and (current_pol is not Pol.LH):
|
|
361
|
+
self._polarisation_setpoint_set(Pol.LH)
|
|
362
|
+
max_lh_energy = float(
|
|
363
|
+
self.gap_energy_motor_lu.lut.root[Pol("lh")].max_energy
|
|
364
|
+
)
|
|
365
|
+
lh_setpoint = (
|
|
366
|
+
max_lh_energy if target_energy > max_lh_energy else target_energy
|
|
367
|
+
)
|
|
368
|
+
LOGGER.info(f"Changing polarisation to {value} via {Pol.LH}")
|
|
369
|
+
await self.energy.set(lh_setpoint, timeout=MAXIMUM_MOVE_TIME)
|
|
370
|
+
self._polarisation_setpoint_set(value)
|
|
371
|
+
await self.energy.set(target_energy, timeout=MAXIMUM_MOVE_TIME)
|