Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
2b6b6a3
switch to config_server for lut
Dec 18, 2025
af2d8c3
cast to np.ndarray
Dec 19, 2025
4cd8813
overwrite file
Dec 19, 2025
cb2b9f9
Merge branch 'main' into i09-1-add_lut_from_config_server
Villtord Jan 6, 2026
070dd75
Merge branch 'main' into i09-1-add_lut_from_config_server
Villtord Jan 7, 2026
4b8bd8f
fix mock side effect
Jan 7, 2026
758596f
fix lint
Jan 8, 2026
cafe52c
Merge branch 'main' into i09-1-add_lut_from_config_server
Villtord Jan 14, 2026
29f253c
small update to test failing certificate in daq-server
Jan 15, 2026
7941bde
change lut to lut_provider tp avoid call to daq-server in CI
Jan 15, 2026
01bff15
fix tests
Jan 15, 2026
b039205
Merge branch 'main' into i09-1-add_lut_from_config_server
Villtord Jan 15, 2026
bbe6ad4
switch to GenericLookupTable
Jan 16, 2026
c2eb47a
pin version daq-server to >=1.1.2
Jan 16, 2026
84886d9
Merge branch 'main' into i09-1-add_lut_from_config_server
Villtord Jan 16, 2026
76dc6a9
Merge branch 'main' into i09-1-add_lut_from_config_server
Villtord Jan 19, 2026
3524b72
Merge branch 'main' into i09-1-add_lut_from_config_server
Villtord Jan 20, 2026
69eefc1
Merge branch 'main' into i09-1-add_lut_from_config_server
Villtord Jan 23, 2026
480c763
merge main branch
Jan 23, 2026
1fd00ad
Merge branch 'main' into i09-1-add_lut_from_config_server
Villtord Jan 23, 2026
f816f60
Merge branch 'main' into i09-1-add_lut_from_config_server
Villtord Jan 29, 2026
f86aac3
Merge remote-tracking branch 'origin/main' into i09-1-add_lut_from_co…
Feb 6, 2026
d0c40a1
fix tests
Feb 6, 2026
61d00f0
Merge branch 'main' into i09-1-add_lut_from_config_server
Villtord Feb 6, 2026
5bae87d
Merge branch 'main' into i09-1-add_lut_from_config_server
Villtord Feb 9, 2026
ae4ed35
clean up functions, move logic get_file_content logic out of them
Feb 11, 2026
4048d69
rework classes
Feb 11, 2026
87690b8
Merge branch 'main' into i09-1-add_lut_from_config_server
Villtord Feb 11, 2026
df39eca
add more docstrings
Feb 11, 2026
3735d58
more docstrings
Feb 11, 2026
08c4856
docstring
Feb 11, 2026
d92bac6
Merge branch 'main' into i09-1-add_lut_from_config_server
Villtord Feb 11, 2026
6c1dd4a
Merge branch 'main' into i09-1-add_lut_from_config_server
Villtord Feb 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/dodal/beamlines/i09_1_shared.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
from daq_config_server.client import ConfigServer

from dodal.device_manager import DeviceManager
from dodal.devices.beamlines.i09_1_shared import (
HardEnergy,
HardInsertionDeviceEnergy,
calculate_energy_i09_hu,
calculate_gap_i09_hu,
)
from dodal.devices.beamlines.i09_1_shared.hard_energy import (
HardEnergy,
HardInsertionDeviceEnergy,
)
from dodal.devices.beamlines.i09_1_shared.hard_undulator_functions import (
calculate_energy_i09_hu,
calculate_gap_i09_hu,
)
from dodal.devices.common_dcm import (
DoubleCrystalMonochromatorWithDSpacing,
PitchAndRollCrystal,
Expand All @@ -18,6 +28,9 @@

devices = DeviceManager()

I09_1_CONF_CLIENT = ConfigServer()
LOOK_UPTABLE_FILE = "/dls_sw/i09-1/software/gda/workspace_git/gda-diamond.git/configurations/i09-1-shared/lookupTables/IIDCalibrationTable.txt"


@devices.factory()
def dcm() -> DoubleCrystalMonochromatorWithDSpacing[
Expand Down Expand Up @@ -45,7 +58,8 @@ def hu_id_energy(
return HardInsertionDeviceEnergy(
undulator_order=harmonics,
undulator=undulator,
lut={}, # ToDo https://github.com/DiamondLightSource/sm-bluesky/issues/239
config_server=I09_1_CONF_CLIENT,
filepath=LOOK_UPTABLE_FILE,
gap_to_energy_func=calculate_energy_i09_hu,
energy_to_gap_func=calculate_gap_i09_hu,
)
Expand Down
2 changes: 0 additions & 2 deletions src/dodal/devices/beamlines/i09_1_shared/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@
from .hard_undulator_functions import (
calculate_energy_i09_hu,
calculate_gap_i09_hu,
get_hu_lut_as_dict,
)

__all__ = [
"calculate_gap_i09_hu",
"get_hu_lut_as_dict",
"calculate_energy_i09_hu",
"HardInsertionDeviceEnergy",
"HardEnergy",
Expand Down
96 changes: 65 additions & 31 deletions src/dodal/devices/beamlines/i09_1_shared/hard_energy.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from asyncio import gather
from collections.abc import Callable
from typing import Protocol

from bluesky.protocols import Locatable, Location, Movable
from numpy import ndarray
from daq_config_server.client import ConfigServer
from daq_config_server.models.converters.lookup_tables import GenericLookupTable
from ophyd_async.core import (
AsyncStatus,
Reference,
Expand All @@ -12,33 +13,56 @@
soft_signal_rw,
)

from dodal.devices.beamlines.i09_1_shared.hard_undulator_functions import (
MAX_ENERGY_COLUMN,
MIN_ENERGY_COLUMN,
)
from dodal.devices.common_dcm import DoubleCrystalMonochromatorBase
from dodal.devices.undulator import UndulatorInMm, UndulatorOrder


class EnergyGapConvertor(Protocol):
def __call__(
self, look_up_table: GenericLookupTable, value: float, order: int
) -> float:
"""Protocol for a function to provide value conversion using lookup table."""
...


class HardInsertionDeviceEnergy(StandardReadable, Movable[float]):
"""Compound device to link hard x-ray undulator gap and order to photon energy.
"""Compound device to control isertion device energy.

This device link hard x-ray undulator gap and order to the required photon energy.
Setting the energy adjusts the undulator gap accordingly.

Attributes:
energy_demand (SignalRW[float]): The energy value that the user wants to set.
energy (SignalRW[float]): The actual energy of the insertion device.
"""

def __init__(
self,
undulator_order: UndulatorOrder,
undulator: UndulatorInMm,
lut: dict[int, ndarray],
gap_to_energy_func: Callable[..., float],
energy_to_gap_func: Callable[..., float],
config_server: ConfigServer,
filepath: str,
gap_to_energy_func: EnergyGapConvertor,
energy_to_gap_func: EnergyGapConvertor,
name: str = "",
) -> None:
self._lut = lut
self.gap_to_energy_func = gap_to_energy_func
self.energy_to_gap_func = energy_to_gap_func
"""Initialize the HardInsertionDeviceEnergy device.

Args:
undulator_order (UndulatorOrder): undulator order device.
undulator (UndulatorInMm): undulator device for gap control.
config_server (ConfigServer): Config server client to retrieve the lookup table.
filepath (str): File path to the lookup table on the config server.
gap_to_energy_func (EnergyGapConvertor): Function to convert gap to energy using the lookup table.
energy_to_gap_func (EnergyGapConvertor): Function to convert energy to gap using the lookup table.
name (str, optional): Name for the device. Defaults to empty string.
"""
self._undulator_order_ref = Reference(undulator_order)
self._undulator_ref = Reference(undulator)
self._config_server = config_server
self._filepath = filepath
self._gap_to_energy_func = gap_to_energy_func
self._energy_to_gap_func = energy_to_gap_func

self.add_readables([undulator_order, undulator.current_gap])
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
Expand All @@ -53,37 +77,40 @@ def __init__(
super().__init__(name=name)

def _read_energy(self, current_gap: float, current_order: int) -> float:
return self.gap_to_energy_func(
gap=current_gap,
look_up_table=self._lut,
order=current_order,
_lookup_table = self.get_look_up_table()
return self._gap_to_energy_func(
look_up_table=_lookup_table, value=current_gap, order=current_order
)

async def _set_energy(self, energy: float) -> None:
async def _set_energy(self, value: float) -> None:
current_order = await self._undulator_order_ref().value.get_value()
min_energy, max_energy = self._lut[current_order][
MIN_ENERGY_COLUMN : MAX_ENERGY_COLUMN + 1
]
if not (min_energy <= energy <= max_energy):
raise ValueError(
f"Requested energy {energy} keV is out of range for harmonic {current_order}: "
f"[{min_energy}, {max_energy}] keV"
)
_lookup_table = self.get_look_up_table()
target_gap = self._energy_to_gap_func(_lookup_table, value, current_order)
await self._undulator_ref().set(target_gap)

target_gap = self.energy_to_gap_func(
photon_energy_kev=energy, look_up_table=self._lut, order=current_order
def get_look_up_table(self) -> GenericLookupTable:
self._lut: GenericLookupTable = self._config_server.get_file_contents(
self._filepath,
desired_return_type=GenericLookupTable,
reset_cached_result=True,
)
await self._undulator_ref().set(target_gap)
return self._lut

@AsyncStatus.wrap
async def set(self, value: float) -> None:
"""Update energy demand and set energy to a given value in keV.

Args:
value (float): Energy in keV.
"""
self.energy_demand.set(value)
await self.energy.set(value)


class HardEnergy(StandardReadable, Locatable[float]):
"""Energy compound device that provides combined change of both DCM energy and
undulator gap accordingly.
"""Compound energy device.

This device changes both monochromator and insertion device energy.
"""

def __init__(
Expand All @@ -92,6 +119,13 @@ def __init__(
undulator_energy: HardInsertionDeviceEnergy,
name: str = "",
) -> None:
"""Initialize the HardEnergy device.

Args:
dcm (DoubleCrystalMonochromatorBase): Double crystal monochromator device.
undulator_energy (HardInsertionDeviceEnergy): Hard insertion device control.
name (str, optional): name for the device. Defaults to empty.
"""
self._dcm_ref = Reference(dcm)
self._undulator_energy_ref = Reference(undulator_energy)
self.add_readables([undulator_energy, dcm.energy_in_keV])
Expand Down
Loading