11from asyncio import gather
2- from collections . abc import Callable
2+ from typing import Protocol
33
44from bluesky .protocols import Locatable , Location , Movable
5- from numpy import ndarray
5+ from daq_config_server import ConfigClient
6+ from daq_config_server .models .lookup_tables import GenericLookupTable
67from ophyd_async .core import (
78 AsyncStatus ,
89 Reference ,
1213 soft_signal_rw ,
1314)
1415
15- from dodal .devices .beamlines .i09_1_shared .hard_undulator_functions import (
16- MAX_ENERGY_COLUMN ,
17- MIN_ENERGY_COLUMN ,
18- )
1916from dodal .devices .common_dcm import DoubleCrystalMonochromatorBase
2017from dodal .devices .undulator import UndulatorInMm , UndulatorOrder
2118
2219
20+ class EnergyGapConvertor (Protocol ):
21+ def __call__ (
22+ self , look_up_table : GenericLookupTable , value : float , order : int
23+ ) -> float :
24+ """Protocol for a function to provide value conversion using lookup table."""
25+ ...
26+
27+
2328class HardInsertionDeviceEnergy (StandardReadable , Movable [float ]):
24- """Compound device to link hard x-ray undulator gap and order to photon energy.
29+ """Compound device to control insertion device energy.
30+
31+ This device link hard x-ray undulator gap and order to the required photon energy.
2532 Setting the energy adjusts the undulator gap accordingly.
33+
34+ Attributes:
35+ energy_demand (SignalRW[float]): The energy value that the user wants to set.
36+ energy (SignalRW[float]): The actual energy of the insertion device.
2637 """
2738
2839 def __init__ (
2940 self ,
3041 undulator_order : UndulatorOrder ,
3142 undulator : UndulatorInMm ,
32- lut : dict [int , ndarray ],
33- gap_to_energy_func : Callable [..., float ],
34- energy_to_gap_func : Callable [..., float ],
43+ config_server : ConfigClient ,
44+ filepath : str ,
45+ gap_to_energy_func : EnergyGapConvertor ,
46+ energy_to_gap_func : EnergyGapConvertor ,
3547 name : str = "" ,
3648 ) -> None :
37- self ._lut = lut
38- self .gap_to_energy_func = gap_to_energy_func
39- self .energy_to_gap_func = energy_to_gap_func
49+ """Initialize the HardInsertionDeviceEnergy device.
50+
51+ Args:
52+ undulator_order (UndulatorOrder): undulator order device.
53+ undulator (UndulatorInMm): undulator device for gap control.
54+ config_server (ConfigServer): Config server client to retrieve the lookup table.
55+ filepath (str): File path to the lookup table on the config server.
56+ gap_to_energy_func (EnergyGapConvertor): Function to convert gap to energy using the lookup table.
57+ energy_to_gap_func (EnergyGapConvertor): Function to convert energy to gap using the lookup table.
58+ name (str, optional): Name for the device. Defaults to empty string.
59+ """
4060 self ._undulator_order_ref = Reference (undulator_order )
4161 self ._undulator_ref = Reference (undulator )
62+ self ._config_server = config_server
63+ self ._filepath = filepath
64+ self ._gap_to_energy_func = gap_to_energy_func
65+ self ._energy_to_gap_func = energy_to_gap_func
4266
4367 self .add_readables ([undulator_order , undulator .current_gap ])
4468 with self .add_children_as_readables (StandardReadableFormat .HINTED_SIGNAL ):
@@ -53,37 +77,40 @@ def __init__(
5377 super ().__init__ (name = name )
5478
5579 def _read_energy (self , current_gap : float , current_order : int ) -> float :
56- return self .gap_to_energy_func (
57- gap = current_gap ,
58- look_up_table = self ._lut ,
59- order = current_order ,
80+ _lookup_table = self .get_look_up_table ()
81+ return self ._gap_to_energy_func (
82+ look_up_table = _lookup_table , value = current_gap , order = current_order
6083 )
6184
62- async def _set_energy (self , energy : float ) -> None :
85+ async def _set_energy (self , value : float ) -> None :
6386 current_order = await self ._undulator_order_ref ().value .get_value ()
64- min_energy , max_energy = self ._lut [current_order ][
65- MIN_ENERGY_COLUMN : MAX_ENERGY_COLUMN + 1
66- ]
67- if not (min_energy <= energy <= max_energy ):
68- raise ValueError (
69- f"Requested energy { energy } keV is out of range for harmonic { current_order } : "
70- f"[{ min_energy } , { max_energy } ] keV"
71- )
87+ _lookup_table = self .get_look_up_table ()
88+ target_gap = self ._energy_to_gap_func (_lookup_table , value , current_order )
89+ await self ._undulator_ref ().set (target_gap )
7290
73- target_gap = self .energy_to_gap_func (
74- photon_energy_kev = energy , look_up_table = self ._lut , order = current_order
91+ def get_look_up_table (self ) -> GenericLookupTable :
92+ self ._lut : GenericLookupTable = self ._config_server .get_file_contents (
93+ self ._filepath ,
94+ desired_return_type = GenericLookupTable ,
95+ reset_cached_result = True ,
7596 )
76- await self ._undulator_ref (). set ( target_gap )
97+ return self ._lut
7798
7899 @AsyncStatus .wrap
79100 async def set (self , value : float ) -> None :
101+ """Update energy demand and set energy to a given value in keV.
102+
103+ Args:
104+ value (float): Energy in keV.
105+ """
80106 self .energy_demand .set (value )
81107 await self .energy .set (value )
82108
83109
84110class HardEnergy (StandardReadable , Locatable [float ]):
85- """Energy compound device that provides combined change of both DCM energy and
86- undulator gap accordingly.
111+ """Compound energy device.
112+
113+ This device changes both monochromator and insertion device energy.
87114 """
88115
89116 def __init__ (
@@ -92,6 +119,13 @@ def __init__(
92119 undulator_energy : HardInsertionDeviceEnergy ,
93120 name : str = "" ,
94121 ) -> None :
122+ """Initialize the HardEnergy device.
123+
124+ Args:
125+ dcm (DoubleCrystalMonochromatorBase): Double crystal monochromator device.
126+ undulator_energy (HardInsertionDeviceEnergy): Hard insertion device control.
127+ name (str, optional): name for the device. Defaults to empty.
128+ """
95129 self ._dcm_ref = Reference (dcm )
96130 self ._undulator_energy_ref = Reference (undulator_energy )
97131 self .add_readables ([undulator_energy , dcm .energy_in_keV ])
0 commit comments