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
|
@@ -1,64 +1,58 @@
|
|
|
1
1
|
import abc
|
|
2
2
|
import asyncio
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
-
from
|
|
5
|
-
from typing import Generic, Protocol, TypeVar
|
|
4
|
+
from typing import Generic, TypeVar
|
|
6
5
|
|
|
7
6
|
import numpy as np
|
|
8
|
-
from bluesky.protocols import
|
|
7
|
+
from bluesky.protocols import Movable
|
|
9
8
|
from ophyd_async.core import (
|
|
9
|
+
DEFAULT_TIMEOUT,
|
|
10
10
|
AsyncStatus,
|
|
11
|
+
Device,
|
|
12
|
+
FlyMotorInfo,
|
|
11
13
|
Reference,
|
|
12
14
|
SignalR,
|
|
13
|
-
SignalRW,
|
|
14
15
|
SignalW,
|
|
15
16
|
StandardReadable,
|
|
16
17
|
StandardReadableFormat,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
WatchableAsyncStatus,
|
|
19
|
+
WatcherUpdate,
|
|
20
|
+
observe_value,
|
|
20
21
|
wait_for_value,
|
|
21
22
|
)
|
|
22
|
-
from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
|
|
23
|
+
from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
|
|
23
24
|
from ophyd_async.epics.motor import Motor
|
|
24
25
|
|
|
25
26
|
from dodal.common.enums import EnabledDisabledUpper
|
|
26
|
-
from dodal.devices.insertion_device.
|
|
27
|
-
from dodal.devices.insertion_device.id_enum import Pol, UndulatorGateStatus
|
|
27
|
+
from dodal.devices.insertion_device.enum import UndulatorGateStatus
|
|
28
28
|
from dodal.log import LOGGER
|
|
29
29
|
|
|
30
30
|
T = TypeVar("T")
|
|
31
31
|
|
|
32
32
|
DEFAULT_MOTOR_MIN_TIMEOUT = 10
|
|
33
|
-
MAXIMUM_MOVE_TIME = 550 # There is no useful movements take longer than this.
|
|
34
33
|
|
|
35
34
|
|
|
36
35
|
@dataclass
|
|
37
36
|
class Apple2LockedPhasesVal:
|
|
38
|
-
top_outer:
|
|
39
|
-
btm_inner:
|
|
37
|
+
top_outer: float
|
|
38
|
+
btm_inner: float
|
|
40
39
|
|
|
41
40
|
|
|
42
41
|
@dataclass
|
|
43
42
|
class Apple2PhasesVal(Apple2LockedPhasesVal):
|
|
44
|
-
top_inner:
|
|
45
|
-
btm_outer:
|
|
43
|
+
top_inner: float
|
|
44
|
+
btm_outer: float
|
|
46
45
|
|
|
47
46
|
|
|
48
47
|
@dataclass
|
|
49
48
|
class Apple2Val:
|
|
50
|
-
gap:
|
|
49
|
+
gap: float
|
|
51
50
|
phase: Apple2LockedPhasesVal | Apple2PhasesVal
|
|
52
51
|
|
|
53
52
|
def extract_phase_val(self):
|
|
54
53
|
return self.phase
|
|
55
54
|
|
|
56
55
|
|
|
57
|
-
ROW_PHASE_MOTOR_TOLERANCE = 0.004
|
|
58
|
-
MAXIMUM_ROW_PHASE_MOTOR_POSITION = 24.0
|
|
59
|
-
MAXIMUM_GAP_MOTOR_POSITION = 100
|
|
60
|
-
|
|
61
|
-
|
|
62
56
|
async def estimate_motor_timeout(
|
|
63
57
|
setpoint: SignalR, curr_pos: SignalR, velocity: SignalR
|
|
64
58
|
):
|
|
@@ -68,7 +62,35 @@ async def estimate_motor_timeout(
|
|
|
68
62
|
return abs((target_pos - cur_pos) * 2.0 / vel) + DEFAULT_MOTOR_MIN_TIMEOUT
|
|
69
63
|
|
|
70
64
|
|
|
71
|
-
class
|
|
65
|
+
class UndulatorBase(abc.ABC, Device, Generic[T]):
|
|
66
|
+
"""Abstract base class for Apple2 undulator devices.
|
|
67
|
+
This class provides common functionality for undulator devices, including
|
|
68
|
+
gate and status signal management, safety checks before motion, and abstract
|
|
69
|
+
methods for setting demand positions and estimating move timeouts.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(self, name: str = ""):
|
|
73
|
+
# Gate keeper open when move is requested, closed when move is completed
|
|
74
|
+
self.gate: SignalR[UndulatorGateStatus]
|
|
75
|
+
self.status: SignalR[EnabledDisabledUpper]
|
|
76
|
+
super().__init__(name=name)
|
|
77
|
+
|
|
78
|
+
@abc.abstractmethod
|
|
79
|
+
async def set_demand_positions(self, value: T) -> None:
|
|
80
|
+
"""Set the demand positions on the device without actually hitting move."""
|
|
81
|
+
|
|
82
|
+
@abc.abstractmethod
|
|
83
|
+
async def get_timeout(self) -> float:
|
|
84
|
+
"""Get the timeout for the move based on an estimate of how long it will take."""
|
|
85
|
+
|
|
86
|
+
async def raise_if_cannot_move(self) -> None:
|
|
87
|
+
if await self.status.get_value() is EnabledDisabledUpper.DISABLED:
|
|
88
|
+
raise RuntimeError(f"{self.name} is DISABLED and cannot move.")
|
|
89
|
+
if await self.gate.get_value() is UndulatorGateStatus.OPEN:
|
|
90
|
+
raise RuntimeError(f"{self.name} is already in motion.")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class SafeUndulatorMover(StandardReadable, UndulatorBase, Generic[T]):
|
|
72
94
|
"""A device that will check it's safe to move the undulator before moving it and
|
|
73
95
|
wait for the undulator to be safe again before calling the move complete.
|
|
74
96
|
"""
|
|
@@ -90,113 +112,128 @@ class SafeUndulatorMover(StandardReadable, Movable[T], Generic[T]):
|
|
|
90
112
|
await self.set_move.set(value=1, timeout=timeout)
|
|
91
113
|
await wait_for_value(self.gate, UndulatorGateStatus.CLOSE, timeout=timeout)
|
|
92
114
|
|
|
93
|
-
@abc.abstractmethod
|
|
94
|
-
async def set_demand_positions(self, value: T) -> None:
|
|
95
|
-
"""Set the demand positions on the device without actually hitting move."""
|
|
96
115
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
"""Get the timeout for the move based on an estimate of how long it will take."""
|
|
116
|
+
class UnstoppableMotor(Motor):
|
|
117
|
+
"""A motor that does not support stop."""
|
|
100
118
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if await self.gate.get_value() == UndulatorGateStatus.OPEN:
|
|
105
|
-
raise RuntimeError(f"{self.name} is already in motion.")
|
|
119
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
120
|
+
super().__init__(prefix=prefix, name=name)
|
|
121
|
+
del self.motor_stop # Remove motor_stop from the public interface
|
|
106
122
|
|
|
123
|
+
async def stop(self, success=False):
|
|
124
|
+
LOGGER.warning(f"Stopping {self.name} is not supported.")
|
|
107
125
|
|
|
108
|
-
class UndulatorGap(SafeUndulatorMover[float]):
|
|
109
|
-
"""A device with a collection of epics signals to set Apple 2 undulator gap motion.
|
|
110
|
-
Only PV used by beamline are added the full list is here:
|
|
111
|
-
/dls_sw/work/R3.14.12.7/support/insertionDevice/db/IDGapVelocityControl.template
|
|
112
|
-
/dls_sw/work/R3.14.12.7/support/insertionDevice/db/IDPhaseSoftMotor.template
|
|
113
|
-
"""
|
|
114
126
|
|
|
115
|
-
|
|
116
|
-
|
|
127
|
+
class GapSafeMotorNoStop(UnstoppableMotor, UndulatorBase[float]):
|
|
128
|
+
"""Update gap safe motor that checks it's safe to move before moving."""
|
|
117
129
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
130
|
+
def __init__(self, set_move: SignalW[int], prefix: str, name: str = ""):
|
|
131
|
+
# Gate keeper open when move is requested, closed when move is completed
|
|
132
|
+
self.gate = epics_signal_r(UndulatorGateStatus, prefix + "BLGATE")
|
|
133
|
+
self.status = epics_signal_r(EnabledDisabledUpper, prefix + "IDBLENA")
|
|
134
|
+
self.set_move = set_move
|
|
135
|
+
super().__init__(prefix=prefix + "BLGAPMTR", name=name)
|
|
136
|
+
|
|
137
|
+
@WatchableAsyncStatus.wrap
|
|
138
|
+
async def set(self, new_position: float, timeout=DEFAULT_TIMEOUT):
|
|
139
|
+
(
|
|
140
|
+
old_position,
|
|
141
|
+
units,
|
|
142
|
+
precision,
|
|
143
|
+
) = await asyncio.gather(
|
|
144
|
+
self.user_setpoint.get_value(),
|
|
145
|
+
self.motor_egu.get_value(),
|
|
146
|
+
self.precision.get_value(),
|
|
147
|
+
)
|
|
148
|
+
LOGGER.info(f"Setting {self.name} to {new_position}")
|
|
149
|
+
await self.raise_if_cannot_move()
|
|
150
|
+
await self.set_demand_positions(new_position)
|
|
151
|
+
timeout = await self.get_timeout()
|
|
152
|
+
LOGGER.info(f"Moving {self.name} to {new_position} with timeout = {timeout}")
|
|
126
153
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
154
|
+
await self.set_move.set(value=1, wait=True, timeout=timeout)
|
|
155
|
+
move_status = AsyncStatus(
|
|
156
|
+
wait_for_value(self.gate, UndulatorGateStatus.CLOSE, timeout=timeout)
|
|
130
157
|
)
|
|
131
|
-
|
|
158
|
+
|
|
159
|
+
async for current_position in observe_value(
|
|
160
|
+
self.user_readback, done_status=move_status
|
|
161
|
+
):
|
|
162
|
+
yield WatcherUpdate(
|
|
163
|
+
current=current_position,
|
|
164
|
+
initial=old_position,
|
|
165
|
+
target=new_position,
|
|
166
|
+
name=self.name,
|
|
167
|
+
unit=units,
|
|
168
|
+
precision=precision,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class UndulatorGap(GapSafeMotorNoStop):
|
|
173
|
+
"""Apple 2 undulator gap motor device. With PV corrections.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
prefix (str): Beamline specific part of the PV
|
|
177
|
+
name (str): Name of the Id device
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
132
181
|
self.set_move = epics_signal_rw(int, prefix + "BLGSETP")
|
|
182
|
+
# Nothing move until this is set to 1 and it will return to 0 when done.
|
|
183
|
+
super().__init__(self.set_move, prefix, name)
|
|
133
184
|
|
|
134
|
-
# These are gap velocity limit.
|
|
135
185
|
self.max_velocity = epics_signal_r(float, prefix + "BLGSETVEL.HOPR")
|
|
136
186
|
self.min_velocity = epics_signal_r(float, prefix + "BLGSETVEL.LOPR")
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
self.
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
self.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
self.motor_egu = epics_signal_r(str, prefix + "BLGAPMTR.EGU")
|
|
147
|
-
# Gap velocity
|
|
148
|
-
self.velocity = epics_signal_rw(float, prefix + "BLGSETVEL")
|
|
149
|
-
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
|
|
150
|
-
# Gap readback value
|
|
151
|
-
self.user_readback = epics_signal_r(float, prefix + "CURRGAPD")
|
|
152
|
-
super().__init__(self.set_move, prefix, name)
|
|
187
|
+
self.user_setpoint = epics_signal_rw(str, prefix + "BLGSET")
|
|
188
|
+
""" Clear the motor config_signal as we need new PV for velocity."""
|
|
189
|
+
self._describe_config_funcs = ()
|
|
190
|
+
self._read_config_funcs = ()
|
|
191
|
+
self.velocity = epics_signal_rw(float, prefix + "BLGSETVEL")
|
|
192
|
+
self.add_readables(
|
|
193
|
+
[self.velocity, self.motor_egu, self.offset],
|
|
194
|
+
format=StandardReadableFormat.CONFIG_SIGNAL,
|
|
195
|
+
)
|
|
153
196
|
|
|
154
|
-
|
|
155
|
-
|
|
197
|
+
@AsyncStatus.wrap
|
|
198
|
+
async def prepare(self, value: FlyMotorInfo) -> None:
|
|
199
|
+
"""Prepare for a fly scan by moving to the run-up position at max velocity.
|
|
200
|
+
Stores fly info for later use in kickoff.
|
|
201
|
+
"""
|
|
202
|
+
max_velocity, min_velocity, egu = await asyncio.gather(
|
|
203
|
+
self.max_velocity.get_value(),
|
|
204
|
+
self.min_velocity.get_value(),
|
|
205
|
+
self.motor_egu.get_value(),
|
|
206
|
+
)
|
|
207
|
+
velocity = abs(value.velocity)
|
|
208
|
+
if not (min_velocity <= velocity <= max_velocity):
|
|
209
|
+
raise ValueError(
|
|
210
|
+
f"Requested velocity {velocity} {egu}/s is out of bounds: "
|
|
211
|
+
f"must be between {min_velocity} and {max_velocity} {egu}/s."
|
|
212
|
+
)
|
|
213
|
+
await super().prepare(value)
|
|
156
214
|
|
|
157
215
|
async def get_timeout(self) -> float:
|
|
158
216
|
return await estimate_motor_timeout(
|
|
159
217
|
self.user_setpoint, self.user_readback, self.velocity
|
|
160
218
|
)
|
|
161
219
|
|
|
220
|
+
async def set_demand_positions(self, value: float) -> None:
|
|
221
|
+
await self.user_setpoint.set(str(value))
|
|
222
|
+
|
|
162
223
|
|
|
163
|
-
class UndulatorPhaseMotor(
|
|
164
|
-
"""
|
|
165
|
-
|
|
166
|
-
|
|
224
|
+
class UndulatorPhaseMotor(UnstoppableMotor):
|
|
225
|
+
"""Phase motor that will not stop.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
prefix (str): The setting prefix PV.
|
|
229
|
+
name (str, optional): Name of the Id phase device.
|
|
167
230
|
"""
|
|
168
231
|
|
|
169
|
-
def __init__(self, prefix: str,
|
|
170
|
-
""
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
prefix : str
|
|
175
|
-
The setting prefix PV.
|
|
176
|
-
infix: str
|
|
177
|
-
Collection of pv that are different between beamlines
|
|
178
|
-
name : str
|
|
179
|
-
Name of the Id phase device
|
|
180
|
-
"""
|
|
181
|
-
full_pv = f"{prefix}BL{infix}"
|
|
182
|
-
self.user_setpoint = epics_signal_w(str, full_pv + "SET")
|
|
183
|
-
self.user_setpoint_readback = epics_signal_r(float, full_pv + "DMD")
|
|
184
|
-
full_pv = full_pv + "MTR"
|
|
185
|
-
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
|
|
186
|
-
self.user_readback = epics_signal_r(float, full_pv + ".RBV")
|
|
187
|
-
|
|
188
|
-
with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
|
|
189
|
-
self.motor_egu = epics_signal_r(str, full_pv + ".EGU")
|
|
190
|
-
self.velocity = epics_signal_rw(float, full_pv + ".VELO")
|
|
191
|
-
|
|
192
|
-
self.max_velocity = epics_signal_r(float, full_pv + ".VMAX")
|
|
193
|
-
self.acceleration_time = epics_signal_rw(float, full_pv + ".ACCL")
|
|
194
|
-
self.precision = epics_signal_r(int, full_pv + ".PREC")
|
|
195
|
-
self.deadband = epics_signal_r(float, full_pv + ".RDBD")
|
|
196
|
-
self.motor_done_move = epics_signal_r(int, full_pv + ".DMOV")
|
|
197
|
-
self.low_limit_travel = epics_signal_rw(float, full_pv + ".LLM")
|
|
198
|
-
self.high_limit_travel = epics_signal_rw(float, full_pv + ".HLM")
|
|
199
|
-
super().__init__(name=name)
|
|
232
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
233
|
+
motor_pv = f"{prefix}MTR"
|
|
234
|
+
super().__init__(prefix=motor_pv, name=name)
|
|
235
|
+
self.user_setpoint = epics_signal_rw(str, prefix + "SET")
|
|
236
|
+
self.user_setpoint_readback = epics_signal_r(float, prefix + "DMD")
|
|
200
237
|
|
|
201
238
|
|
|
202
239
|
Apple2PhaseValType = TypeVar("Apple2PhaseValType", bound=Apple2LockedPhasesVal)
|
|
@@ -214,8 +251,8 @@ class UndulatorLockedPhaseAxes(SafeUndulatorMover[Apple2PhaseValType]):
|
|
|
214
251
|
):
|
|
215
252
|
# Gap demand set point and readback
|
|
216
253
|
with self.add_children_as_readables():
|
|
217
|
-
self.top_outer = UndulatorPhaseMotor(prefix=prefix
|
|
218
|
-
self.btm_inner = UndulatorPhaseMotor(prefix=prefix
|
|
254
|
+
self.top_outer = UndulatorPhaseMotor(prefix=f"{prefix}BL{top_outer}")
|
|
255
|
+
self.btm_inner = UndulatorPhaseMotor(prefix=f"{prefix}BL{btm_inner}")
|
|
219
256
|
# Nothing move until this is set to 1 and it will return to 0 when done.
|
|
220
257
|
self.set_move = epics_signal_rw(int, f"{prefix}BL{top_outer}" + "MOVE")
|
|
221
258
|
self.axes = [self.top_outer, self.btm_inner]
|
|
@@ -223,15 +260,14 @@ class UndulatorLockedPhaseAxes(SafeUndulatorMover[Apple2PhaseValType]):
|
|
|
223
260
|
|
|
224
261
|
async def set_demand_positions(self, value: Apple2PhaseValType) -> None:
|
|
225
262
|
await asyncio.gather(
|
|
226
|
-
self.top_outer.user_setpoint.set(value=value.top_outer),
|
|
227
|
-
self.btm_inner.user_setpoint.set(value=value.btm_inner),
|
|
263
|
+
self.top_outer.user_setpoint.set(value=str(value.top_outer)),
|
|
264
|
+
self.btm_inner.user_setpoint.set(value=str(value.btm_inner)),
|
|
228
265
|
)
|
|
229
266
|
|
|
230
267
|
async def get_timeout(self) -> float:
|
|
268
|
+
"""Get all motor speed, current positions and target positions to calculate
|
|
269
|
+
required timeout.
|
|
231
270
|
"""
|
|
232
|
-
Get all four motor speed, current positions and target positions to calculate required timeout.
|
|
233
|
-
"""
|
|
234
|
-
|
|
235
271
|
timeouts = await asyncio.gather(
|
|
236
272
|
*[
|
|
237
273
|
estimate_motor_timeout(
|
|
@@ -244,20 +280,19 @@ class UndulatorLockedPhaseAxes(SafeUndulatorMover[Apple2PhaseValType]):
|
|
|
244
280
|
)
|
|
245
281
|
"""A 2.0 multiplier is required to prevent premature motor timeouts in phase
|
|
246
282
|
axes as it is a master-slave system, where the slave's movement,
|
|
247
|
-
being dependent on the master, can take up to twice as long to complete.
|
|
248
|
-
"""
|
|
283
|
+
being dependent on the master, can take up to twice as long to complete."""
|
|
249
284
|
return np.max(timeouts) * 2.0
|
|
250
285
|
|
|
251
286
|
|
|
252
287
|
class UndulatorPhaseAxes(UndulatorLockedPhaseAxes[Apple2PhasesVal]):
|
|
253
|
-
"""
|
|
254
|
-
|
|
255
|
-
e.g. top_outer == Q1
|
|
256
|
-
top_inner == Q2
|
|
257
|
-
btm_inner == q3
|
|
258
|
-
btm_outer == q4
|
|
288
|
+
"""A collection of 4 phase Motor to make up the full id phase motion. We are using
|
|
289
|
+
the diamond PV convention. e.g.::
|
|
259
290
|
|
|
260
|
-
|
|
291
|
+
top_outer == Q1
|
|
292
|
+
top_inner == Q2
|
|
293
|
+
btm_inner == q3
|
|
294
|
+
btm_outer == q4
|
|
295
|
+
""" # noqa D415
|
|
261
296
|
|
|
262
297
|
def __init__(
|
|
263
298
|
self,
|
|
@@ -270,25 +305,24 @@ class UndulatorPhaseAxes(UndulatorLockedPhaseAxes[Apple2PhasesVal]):
|
|
|
270
305
|
):
|
|
271
306
|
# Gap demand set point and readback
|
|
272
307
|
with self.add_children_as_readables():
|
|
273
|
-
self.top_inner = UndulatorPhaseMotor(prefix=prefix
|
|
274
|
-
self.btm_outer = UndulatorPhaseMotor(prefix=prefix
|
|
308
|
+
self.top_inner = UndulatorPhaseMotor(prefix=f"{prefix}BL{top_inner}")
|
|
309
|
+
self.btm_outer = UndulatorPhaseMotor(prefix=f"{prefix}BL{btm_outer}")
|
|
275
310
|
|
|
276
311
|
super().__init__(prefix, top_outer=top_outer, btm_inner=btm_inner, name=name)
|
|
277
312
|
self.axes.extend([self.top_inner, self.btm_outer])
|
|
278
313
|
|
|
279
314
|
async def set_demand_positions(self, value: Apple2PhasesVal) -> None:
|
|
280
315
|
await asyncio.gather(
|
|
281
|
-
self.top_outer.user_setpoint.set(value=value.top_outer),
|
|
282
|
-
self.top_inner.user_setpoint.set(value=value.top_inner),
|
|
283
|
-
self.btm_inner.user_setpoint.set(value=value.btm_inner),
|
|
284
|
-
self.btm_outer.user_setpoint.set(value=value.btm_outer),
|
|
316
|
+
self.top_outer.user_setpoint.set(value=str(value.top_outer)),
|
|
317
|
+
self.top_inner.user_setpoint.set(value=str(value.top_inner)),
|
|
318
|
+
self.btm_inner.user_setpoint.set(value=str(value.btm_inner)),
|
|
319
|
+
self.btm_outer.user_setpoint.set(value=str(value.btm_outer)),
|
|
285
320
|
)
|
|
286
321
|
|
|
287
322
|
|
|
288
323
|
class UndulatorJawPhase(SafeUndulatorMover[float]):
|
|
289
|
-
"""
|
|
290
|
-
|
|
291
|
-
linear arbitrary polarisation but only on some of the beamline.
|
|
324
|
+
"""A JawPhase movable, this is use for moving the jaw phase which is use to control
|
|
325
|
+
the linear arbitrary polarisation but only on some of the beamline.
|
|
292
326
|
"""
|
|
293
327
|
|
|
294
328
|
def __init__(
|
|
@@ -300,7 +334,7 @@ class UndulatorJawPhase(SafeUndulatorMover[float]):
|
|
|
300
334
|
):
|
|
301
335
|
# Gap demand set point and readback
|
|
302
336
|
with self.add_children_as_readables():
|
|
303
|
-
self.jaw_phase = UndulatorPhaseMotor(prefix=prefix
|
|
337
|
+
self.jaw_phase = UndulatorPhaseMotor(prefix=f"{prefix}BL{jaw_phase}")
|
|
304
338
|
# Nothing move until this is set to 1 and it will return to 0 when done
|
|
305
339
|
self.set_move = epics_signal_rw(int, f"{prefix}BL{move_pv}" + "MOVE")
|
|
306
340
|
|
|
@@ -310,8 +344,8 @@ class UndulatorJawPhase(SafeUndulatorMover[float]):
|
|
|
310
344
|
await self.jaw_phase.user_setpoint.set(value=str(value))
|
|
311
345
|
|
|
312
346
|
async def get_timeout(self) -> float:
|
|
313
|
-
"""
|
|
314
|
-
|
|
347
|
+
"""Get motor speed, current position and target position to calculate required
|
|
348
|
+
timeout.
|
|
315
349
|
"""
|
|
316
350
|
return await estimate_motor_timeout(
|
|
317
351
|
self.jaw_phase.user_setpoint_readback,
|
|
@@ -324,29 +358,20 @@ PhaseAxesType = TypeVar("PhaseAxesType", bound=UndulatorLockedPhaseAxes)
|
|
|
324
358
|
|
|
325
359
|
|
|
326
360
|
class Apple2(StandardReadable, Movable[Apple2Val], Generic[PhaseAxesType]):
|
|
327
|
-
"""
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
361
|
+
"""Device representing the combined motor controls for an Apple2 undulator.
|
|
362
|
+
|
|
363
|
+
Attributes:
|
|
364
|
+
gap (UndulatorGap): The undulator gap motor device.
|
|
365
|
+
phase (UndulatorPhaseAxes): The undulator phase axes device, consisting of four
|
|
366
|
+
phase motors.
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
id_gap (UndulatorGap): An UndulatorGap device.
|
|
370
|
+
id_phase (UndulatorPhaseAxes): An UndulatorPhaseAxes device.
|
|
371
|
+
name (str, optional): Name of the device.
|
|
336
372
|
"""
|
|
337
373
|
|
|
338
374
|
def __init__(self, id_gap: UndulatorGap, id_phase: PhaseAxesType, name=""):
|
|
339
|
-
"""
|
|
340
|
-
Parameters
|
|
341
|
-
----------
|
|
342
|
-
|
|
343
|
-
id_gap: UndulatorGap
|
|
344
|
-
An UndulatorGap device.
|
|
345
|
-
id_phase: UndulatorPhaseAxes
|
|
346
|
-
An UndulatorPhaseAxes device.
|
|
347
|
-
name: str
|
|
348
|
-
Name of the device.
|
|
349
|
-
"""
|
|
350
375
|
with self.add_children_as_readables():
|
|
351
376
|
self.gap = Reference(id_gap)
|
|
352
377
|
self.phase = Reference(id_phase)
|
|
@@ -354,12 +379,10 @@ class Apple2(StandardReadable, Movable[Apple2Val], Generic[PhaseAxesType]):
|
|
|
354
379
|
|
|
355
380
|
@AsyncStatus.wrap
|
|
356
381
|
async def set(self, id_motor_values: Apple2Val) -> None:
|
|
382
|
+
"""Check ID is in a movable state and set all the demand value before moving
|
|
383
|
+
them all at the same time.
|
|
357
384
|
"""
|
|
358
|
-
|
|
359
|
-
all at the same time.
|
|
360
|
-
"""
|
|
361
|
-
|
|
362
|
-
# Only need to check gap as the phase motors share both fault and gate with gap.
|
|
385
|
+
# Only need to check gap as the phase motors share both status and gate with gap.
|
|
363
386
|
await self.gap().raise_if_cannot_move()
|
|
364
387
|
|
|
365
388
|
await asyncio.gather(
|
|
@@ -372,7 +395,7 @@ class Apple2(StandardReadable, Movable[Apple2Val], Generic[PhaseAxesType]):
|
|
|
372
395
|
await asyncio.gather(self.gap().get_timeout(), self.phase().get_timeout())
|
|
373
396
|
)
|
|
374
397
|
LOGGER.info(
|
|
375
|
-
f"Moving
|
|
398
|
+
f"Moving {self.name} apple2 motors to {id_motor_values}, timeout = {timeout}"
|
|
376
399
|
)
|
|
377
400
|
await asyncio.gather(
|
|
378
401
|
self.gap().set_move.set(value=1, wait=False, timeout=timeout),
|
|
@@ -381,429 +404,3 @@ class Apple2(StandardReadable, Movable[Apple2Val], Generic[PhaseAxesType]):
|
|
|
381
404
|
await wait_for_value(
|
|
382
405
|
self.gap().gate, UndulatorGateStatus.CLOSE, timeout=timeout
|
|
383
406
|
)
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
class EnergyMotorConvertor(Protocol):
|
|
387
|
-
def __call__(self, energy: float, pol: Pol) -> float:
|
|
388
|
-
"""Protocol to provide energy to motor position conversion"""
|
|
389
|
-
...
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
Apple2Type = TypeVar("Apple2Type", bound=Apple2)
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
class Apple2Controller(abc.ABC, StandardReadable, Generic[Apple2Type]):
|
|
396
|
-
"""
|
|
397
|
-
|
|
398
|
-
Abstract base class for controlling an Apple2 undulator device.
|
|
399
|
-
|
|
400
|
-
This class manages the undulator's gap and phase motors, and provides an interface
|
|
401
|
-
for controlling polarisation and energy settings. It exposes derived signals for
|
|
402
|
-
energy and polarisation, and handles conversion between energy/polarisation and
|
|
403
|
-
motor positions via a user-supplied conversion callable.
|
|
404
|
-
|
|
405
|
-
Attributes
|
|
406
|
-
----------
|
|
407
|
-
apple2 : Reference[Apple2Type]
|
|
408
|
-
Reference to the Apple2 device containing gap and phase motors.
|
|
409
|
-
energy : derived_signal_rw
|
|
410
|
-
Derived signal for moving and reading back energy.
|
|
411
|
-
polarisation_setpoint : SignalR
|
|
412
|
-
Soft signal for the polarisation setpoint.
|
|
413
|
-
polarisation : derived_signal_rw
|
|
414
|
-
Hardware-backed signal for polarisation readback and control.
|
|
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.
|
|
419
|
-
|
|
420
|
-
Abstract Methods
|
|
421
|
-
----------------
|
|
422
|
-
_get_apple2_value(gap: float, phase: float) -> Apple2Val
|
|
423
|
-
Abstract method to return the Apple2Val used to set the apple2 with.
|
|
424
|
-
Notes
|
|
425
|
-
-----
|
|
426
|
-
- Subclasses must implement `_get_apple2_value` for beamline-specific logic.
|
|
427
|
-
- LH3 polarisation is indistinguishable from LH in hardware; special handling is provided.
|
|
428
|
-
- Supports multiple polarisation modes, including linear horizontal (LH), linear vertical (LV),
|
|
429
|
-
positive circular (PC), negative circular (NC), and linear arbitrary (LA).
|
|
430
|
-
|
|
431
|
-
"""
|
|
432
|
-
|
|
433
|
-
def __init__(
|
|
434
|
-
self,
|
|
435
|
-
apple2: Apple2Type,
|
|
436
|
-
gap_energy_motor_converter: EnergyMotorConvertor,
|
|
437
|
-
phase_energy_motor_converter: EnergyMotorConvertor,
|
|
438
|
-
units: str = "eV",
|
|
439
|
-
name: str = "",
|
|
440
|
-
) -> None:
|
|
441
|
-
"""
|
|
442
|
-
|
|
443
|
-
Parameters
|
|
444
|
-
----------
|
|
445
|
-
apple2: Apple2
|
|
446
|
-
An Apple2 device.
|
|
447
|
-
name: str
|
|
448
|
-
Name of the device.
|
|
449
|
-
"""
|
|
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
|
|
453
|
-
|
|
454
|
-
# Store the set energy for readback.
|
|
455
|
-
self._energy, self._energy_set = soft_signal_r_and_setter(
|
|
456
|
-
float, initial_value=None, units=units
|
|
457
|
-
)
|
|
458
|
-
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
|
|
459
|
-
self.energy = derived_signal_rw(
|
|
460
|
-
raw_to_derived=self._read_energy,
|
|
461
|
-
set_derived=self._set_energy,
|
|
462
|
-
energy=self._energy,
|
|
463
|
-
derived_units=units,
|
|
464
|
-
)
|
|
465
|
-
|
|
466
|
-
# Store the polarisation for setpoint. And provide readback for LH3.
|
|
467
|
-
# LH3 is a special case as it is indistinguishable from LH in the hardware.
|
|
468
|
-
self.polarisation_setpoint, self._polarisation_setpoint_set = (
|
|
469
|
-
soft_signal_r_and_setter(Pol)
|
|
470
|
-
)
|
|
471
|
-
phase = self.apple2().phase()
|
|
472
|
-
# check if undulator phase is unlocked.
|
|
473
|
-
if isinstance(phase, UndulatorPhaseAxes):
|
|
474
|
-
top_inner = phase.top_inner.user_readback
|
|
475
|
-
btm_outer = phase.btm_outer.user_readback
|
|
476
|
-
else:
|
|
477
|
-
# If locked phase axes make the locked phase 0.
|
|
478
|
-
top_inner = btm_outer = soft_signal_rw(float, initial_value=0.0)
|
|
479
|
-
|
|
480
|
-
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
|
|
481
|
-
# Hardware backed read/write for polarisation.
|
|
482
|
-
|
|
483
|
-
self.polarisation = derived_signal_rw(
|
|
484
|
-
raw_to_derived=self._read_pol,
|
|
485
|
-
set_derived=self._set_pol,
|
|
486
|
-
pol=self.polarisation_setpoint,
|
|
487
|
-
top_outer=phase.top_outer.user_readback,
|
|
488
|
-
top_inner=top_inner,
|
|
489
|
-
btm_inner=phase.btm_inner.user_readback,
|
|
490
|
-
btm_outer=btm_outer,
|
|
491
|
-
gap=self.apple2().gap().user_readback,
|
|
492
|
-
)
|
|
493
|
-
super().__init__(name)
|
|
494
|
-
|
|
495
|
-
@abc.abstractmethod
|
|
496
|
-
def _get_apple2_value(self, gap: float, phase: float, pol: Pol) -> Apple2Val:
|
|
497
|
-
"""
|
|
498
|
-
This method should be implemented by the beamline specific ID class as the
|
|
499
|
-
motor positions will be different for each beamline depending on the
|
|
500
|
-
undulator design.
|
|
501
|
-
"""
|
|
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
|
-
|
|
513
|
-
async def _set_energy(self, energy: float) -> None:
|
|
514
|
-
pol = await self._check_and_get_pol_setpoint()
|
|
515
|
-
await self._set_motors_from_energy_and_polarisation(energy, pol)
|
|
516
|
-
self._energy_set(energy)
|
|
517
|
-
|
|
518
|
-
def _read_energy(self, energy: float) -> float:
|
|
519
|
-
"""Readback for energy is just the set value."""
|
|
520
|
-
return energy
|
|
521
|
-
|
|
522
|
-
async def _check_and_get_pol_setpoint(self) -> Pol:
|
|
523
|
-
"""
|
|
524
|
-
Check the polarisation setpoint and if it is NONE try to read it from
|
|
525
|
-
hardware.
|
|
526
|
-
"""
|
|
527
|
-
pol = await self.polarisation_setpoint.get_value()
|
|
528
|
-
|
|
529
|
-
if pol == Pol.NONE:
|
|
530
|
-
LOGGER.warning(
|
|
531
|
-
"Found no setpoint for polarisation. Attempting to"
|
|
532
|
-
" determine polarisation from hardware..."
|
|
533
|
-
)
|
|
534
|
-
pol = await self.polarisation.get_value()
|
|
535
|
-
if pol == Pol.NONE:
|
|
536
|
-
raise ValueError(
|
|
537
|
-
f"Polarisation cannot be determined from hardware for {self.name}"
|
|
538
|
-
)
|
|
539
|
-
self._polarisation_setpoint_set(pol)
|
|
540
|
-
return pol
|
|
541
|
-
|
|
542
|
-
async def _set_pol(
|
|
543
|
-
self,
|
|
544
|
-
value: Pol,
|
|
545
|
-
) -> None:
|
|
546
|
-
# This changes the pol setpoint and then changes polarisation via set energy.
|
|
547
|
-
self._polarisation_setpoint_set(value)
|
|
548
|
-
await self.energy.set(await self.energy.get_value(), timeout=MAXIMUM_MOVE_TIME)
|
|
549
|
-
|
|
550
|
-
def _read_pol(
|
|
551
|
-
self,
|
|
552
|
-
pol: Pol,
|
|
553
|
-
top_outer: float,
|
|
554
|
-
top_inner: float,
|
|
555
|
-
btm_inner: float,
|
|
556
|
-
btm_outer: float,
|
|
557
|
-
gap: float,
|
|
558
|
-
) -> Pol:
|
|
559
|
-
LOGGER.info(
|
|
560
|
-
f"Reading polarisation setpoint from hardware: "
|
|
561
|
-
f"top_outer={top_outer}, top_inner={top_inner}, "
|
|
562
|
-
f"btm_inner={btm_inner}, btm_outer={btm_outer}, gap={gap}."
|
|
563
|
-
)
|
|
564
|
-
|
|
565
|
-
read_pol, _ = self.determine_phase_from_hardware(
|
|
566
|
-
top_outer, top_inner, btm_inner, btm_outer, gap
|
|
567
|
-
)
|
|
568
|
-
# LH3 is indistinguishable from LH see determine_phase_from_hardware's docString
|
|
569
|
-
# so we return LH3 if the setpoint is LH3 and the readback is LH.
|
|
570
|
-
if pol == Pol.LH3 and read_pol == Pol.LH:
|
|
571
|
-
LOGGER.info(
|
|
572
|
-
"The hardware cannot distinguish between LH and LH3."
|
|
573
|
-
" Returning the last commanded polarisation value"
|
|
574
|
-
)
|
|
575
|
-
return Pol.LH3
|
|
576
|
-
|
|
577
|
-
return read_pol
|
|
578
|
-
|
|
579
|
-
def determine_phase_from_hardware(
|
|
580
|
-
self,
|
|
581
|
-
top_outer: float,
|
|
582
|
-
top_inner: float,
|
|
583
|
-
btm_inner: float,
|
|
584
|
-
btm_outer: float,
|
|
585
|
-
gap: float,
|
|
586
|
-
) -> tuple[Pol, float]:
|
|
587
|
-
"""
|
|
588
|
-
Determine polarisation and phase value using motor position patterns.
|
|
589
|
-
However there is no way to return lh3 polarisation or higher harmonic setting.
|
|
590
|
-
(May be for future one can use the inverse poly to work out the energy and try to match it with the current energy
|
|
591
|
-
to workout the polarisation but during my test the inverse poly is too unstable for general use.)
|
|
592
|
-
"""
|
|
593
|
-
if gap > MAXIMUM_GAP_MOTOR_POSITION:
|
|
594
|
-
raise RuntimeError(
|
|
595
|
-
f"{self.name} is not in use, close gap or set polarisation to use this ID"
|
|
596
|
-
)
|
|
597
|
-
|
|
598
|
-
if all(
|
|
599
|
-
isclose(x, 0.0, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
600
|
-
for x in [top_outer, top_inner, btm_inner, btm_outer]
|
|
601
|
-
):
|
|
602
|
-
LOGGER.info("Determined polarisation: LH (Linear Horizontal).")
|
|
603
|
-
return Pol.LH, 0.0
|
|
604
|
-
if (
|
|
605
|
-
isclose(
|
|
606
|
-
top_outer,
|
|
607
|
-
MAXIMUM_ROW_PHASE_MOTOR_POSITION,
|
|
608
|
-
abs_tol=ROW_PHASE_MOTOR_TOLERANCE,
|
|
609
|
-
)
|
|
610
|
-
and isclose(top_inner, 0.0, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
611
|
-
and isclose(
|
|
612
|
-
btm_inner,
|
|
613
|
-
MAXIMUM_ROW_PHASE_MOTOR_POSITION,
|
|
614
|
-
abs_tol=ROW_PHASE_MOTOR_TOLERANCE,
|
|
615
|
-
)
|
|
616
|
-
and isclose(btm_outer, 0.0, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
617
|
-
):
|
|
618
|
-
LOGGER.info("Determined polarisation: LV (Linear Vertical).")
|
|
619
|
-
return Pol.LV, MAXIMUM_ROW_PHASE_MOTOR_POSITION
|
|
620
|
-
if (
|
|
621
|
-
isclose(top_outer, btm_inner, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
622
|
-
and top_outer > 0.0
|
|
623
|
-
and isclose(top_inner, 0.0, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
624
|
-
and isclose(btm_outer, 0.0, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
625
|
-
):
|
|
626
|
-
LOGGER.info("Determined polarisation: PC (Positive Circular).")
|
|
627
|
-
return Pol.PC, top_outer
|
|
628
|
-
if (
|
|
629
|
-
isclose(top_outer, btm_inner, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
630
|
-
and top_outer < 0.0
|
|
631
|
-
and isclose(top_inner, 0.0, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
632
|
-
and isclose(btm_outer, 0.0, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
633
|
-
):
|
|
634
|
-
LOGGER.info("Determined polarisation: NC (Negative Circular).")
|
|
635
|
-
return Pol.NC, top_outer
|
|
636
|
-
if (
|
|
637
|
-
isclose(top_outer, -btm_inner, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
638
|
-
and isclose(top_inner, 0.0, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
639
|
-
and isclose(btm_outer, 0.0, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
640
|
-
):
|
|
641
|
-
LOGGER.info("Determined polarisation: LA (Positive Linear Arbitrary).")
|
|
642
|
-
return Pol.LA, top_outer
|
|
643
|
-
if (
|
|
644
|
-
isclose(top_inner, -btm_outer, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
645
|
-
and isclose(top_outer, 0.0, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
646
|
-
and isclose(btm_inner, 0.0, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
|
|
647
|
-
):
|
|
648
|
-
LOGGER.info("Determined polarisation: LA (Negative Linear Arbitrary).")
|
|
649
|
-
return Pol.LA, top_inner
|
|
650
|
-
|
|
651
|
-
LOGGER.warning("Unable to determine polarisation. Defaulting to NONE.")
|
|
652
|
-
return Pol.NONE, 0.0
|
|
653
|
-
|
|
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
|
-
|
|
719
|
-
class InsertionDeviceEnergyBase(abc.ABC, StandardReadable, Movable):
|
|
720
|
-
"""Base class for ID energy movable device."""
|
|
721
|
-
|
|
722
|
-
def __init__(self, name: str = "") -> None:
|
|
723
|
-
self.energy: Reference[SignalRW[float]]
|
|
724
|
-
super().__init__(name=name)
|
|
725
|
-
|
|
726
|
-
@abc.abstractmethod
|
|
727
|
-
@AsyncStatus.wrap
|
|
728
|
-
async def set(self, energy: float) -> None: ...
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
class BeamEnergy(StandardReadable, Movable[float]):
|
|
732
|
-
"""
|
|
733
|
-
Compound device to set both ID and energy motor at the same time with an option to add an offset.
|
|
734
|
-
"""
|
|
735
|
-
|
|
736
|
-
def __init__(
|
|
737
|
-
self, id_energy: InsertionDeviceEnergyBase, mono: Motor, name: str = ""
|
|
738
|
-
) -> None:
|
|
739
|
-
"""
|
|
740
|
-
Parameters
|
|
741
|
-
----------
|
|
742
|
-
|
|
743
|
-
id_energy: InsertionDeviceEnergy
|
|
744
|
-
An InsertionDeviceEnergy device.
|
|
745
|
-
mono: Motor
|
|
746
|
-
A Motor(energy) device.
|
|
747
|
-
name:
|
|
748
|
-
New device name.
|
|
749
|
-
"""
|
|
750
|
-
super().__init__(name=name)
|
|
751
|
-
self._id_energy = Reference(id_energy)
|
|
752
|
-
self._mono_energy = Reference(mono)
|
|
753
|
-
|
|
754
|
-
self.add_readables(
|
|
755
|
-
[
|
|
756
|
-
self._id_energy().energy(),
|
|
757
|
-
self._mono_energy().user_readback,
|
|
758
|
-
],
|
|
759
|
-
StandardReadableFormat.HINTED_SIGNAL,
|
|
760
|
-
)
|
|
761
|
-
|
|
762
|
-
with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
|
|
763
|
-
self.id_energy_offset = soft_signal_rw(float, initial_value=0)
|
|
764
|
-
|
|
765
|
-
@AsyncStatus.wrap
|
|
766
|
-
async def set(self, energy: float) -> None:
|
|
767
|
-
LOGGER.info(f"Moving f{self.name} energy to {energy}.")
|
|
768
|
-
await asyncio.gather(
|
|
769
|
-
self._id_energy().set(
|
|
770
|
-
energy=energy + await self.id_energy_offset.get_value()
|
|
771
|
-
),
|
|
772
|
-
self._mono_energy().set(energy),
|
|
773
|
-
)
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
class InsertionDeviceEnergy(InsertionDeviceEnergyBase):
|
|
777
|
-
"""Apple2 ID energy movable device."""
|
|
778
|
-
|
|
779
|
-
def __init__(self, id_controller: Apple2Controller, name: str = "") -> None:
|
|
780
|
-
self.energy = Reference(id_controller.energy)
|
|
781
|
-
super().__init__(name=name)
|
|
782
|
-
|
|
783
|
-
self.add_readables([self.energy()], StandardReadableFormat.HINTED_SIGNAL)
|
|
784
|
-
|
|
785
|
-
@AsyncStatus.wrap
|
|
786
|
-
async def set(self, energy: float) -> None:
|
|
787
|
-
await self.energy().set(energy, timeout=MAXIMUM_MOVE_TIME)
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
class InsertionDevicePolarisation(StandardReadable, Locatable[Pol]):
|
|
791
|
-
"""Apple2 ID polarisation movable device."""
|
|
792
|
-
|
|
793
|
-
def __init__(self, id_controller: Apple2Controller, name: str = "") -> None:
|
|
794
|
-
self.polarisation = Reference(id_controller.polarisation)
|
|
795
|
-
self.polarisation_setpoint = Reference(id_controller.polarisation_setpoint)
|
|
796
|
-
super().__init__(name=name)
|
|
797
|
-
|
|
798
|
-
self.add_readables([self.polarisation()], StandardReadableFormat.HINTED_SIGNAL)
|
|
799
|
-
|
|
800
|
-
@AsyncStatus.wrap
|
|
801
|
-
async def set(self, pol: Pol) -> None:
|
|
802
|
-
await self.polarisation().set(pol, timeout=MAXIMUM_MOVE_TIME)
|
|
803
|
-
|
|
804
|
-
async def locate(self) -> Location[Pol]:
|
|
805
|
-
"""Return the current polarisation"""
|
|
806
|
-
setpoint, readback = await asyncio.gather(
|
|
807
|
-
self.polarisation_setpoint().get_value(), self.polarisation().get_value()
|
|
808
|
-
)
|
|
809
|
-
return Location(setpoint=setpoint, readback=readback)
|