diff --git a/src/dodal/beamlines/i09_2.py b/src/dodal/beamlines/i09_2.py index d321c9fb02..0f3c3feeec 100644 --- a/src/dodal/beamlines/i09_2.py +++ b/src/dodal/beamlines/i09_2.py @@ -1,18 +1,28 @@ +from daq_config_server.client import ConfigServer + from dodal.common.beamlines.beamline_utils import ( device_factory, ) from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline from dodal.devices.apple2_undulator import ( Apple2, + BeamEnergy, + InsertionDeviceEnergy, + InsertionDevicePolarisation, UndulatorGap, UndulatorPhaseAxes, ) from dodal.devices.i09.enums import Grating +from dodal.devices.i09_2_shared.i09_apple2 import J09Apple2Controller from dodal.devices.pgm import PlaneGratingMonochromator from dodal.devices.synchrotron import Synchrotron from dodal.log import set_beamline as set_log_beamline from dodal.utils import BeamlinePrefix, get_beamline_name +J09_CONF_CLIENT = ConfigServer(url="https://daq-config.diamond.ac.uk") +LOOK_UPTABLE_DIR = "/dls_sw/i09-2/software/gda/workspace_git/gda-diamond.git/configurations/i09-2-shared/lookupTables/" + + BL = get_beamline_name("i09-2") PREFIX = BeamlinePrefix(BL, suffix="J") set_log_beamline(BL) @@ -55,3 +65,29 @@ def jid() -> Apple2: id_gap=jid_gap(), id_phase=jid_phase(), ) + + +@device_factory() +def jid_controller() -> J09Apple2Controller: + """J09 insertion device controller.""" + return J09Apple2Controller( + apple2=jid(), + lookuptable_dir=LOOK_UPTABLE_DIR, + config_client=J09_CONF_CLIENT, + ) + + +@device_factory() +def jid_energy() -> InsertionDeviceEnergy: + return InsertionDeviceEnergy(id_controller=jid_controller()) + + +@device_factory() +def jid_polarisation() -> InsertionDevicePolarisation: + return InsertionDevicePolarisation(id_controller=jid_controller()) + + +@device_factory() +def energy_jid() -> BeamEnergy: + """Beam energy.""" + return BeamEnergy(id_energy=jid_energy(), mono=pgm().energy) diff --git a/src/dodal/devices/apple2_undulator.py b/src/dodal/devices/apple2_undulator.py index 8032f4096a..634135e092 100644 --- a/src/dodal/devices/apple2_undulator.py +++ b/src/dodal/devices/apple2_undulator.py @@ -449,6 +449,7 @@ def __init__( self, apple2: Apple2Type, energy_to_motor_converter: EnergyMotorConvertor, + units: str = "eV", name: str = "", ) -> None: """ @@ -465,14 +466,14 @@ def __init__( # Store the set energy for readback. self._energy, self._energy_set = soft_signal_r_and_setter( - float, initial_value=None, units="eV" + float, initial_value=None, units=units ) with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL): self.energy = derived_signal_rw( raw_to_derived=self._read_energy, set_derived=self._set_energy, energy=self._energy, - derived_units="eV", + derived_units=units, ) # Store the polarisation for setpoint. And provide readback for LH3. diff --git a/src/dodal/devices/i09_2_shared/__init__.py b/src/dodal/devices/i09_2_shared/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/dodal/devices/i09_2_shared/i09_apple2.py b/src/dodal/devices/i09_2_shared/i09_apple2.py new file mode 100644 index 0000000000..f177c64ad4 --- /dev/null +++ b/src/dodal/devices/i09_2_shared/i09_apple2.py @@ -0,0 +1,166 @@ +from daq_config_server.client import ConfigServer + +from dodal.devices.apple2_undulator import ( + Apple2, + Apple2Controller, + Apple2PhasesVal, + Apple2Val, + Pol, +) +from dodal.devices.util.lookup_tables_apple2 import ( + BaseEnergyMotorLookup, + LookupPath, + LookupTableConfig, + make_phase_tables, +) +from dodal.log import LOGGER + +ROW_PHASE_MOTOR_TOLERANCE = 0.004 +ROW_PHASE_CIRCULAR = 15 +MAXIMUM_ROW_PHASE_MOTOR_POSITION = 24.0 +MAXIMUM_GAP_MOTOR_POSITION = 100 + +J09PhasePoly1dParameters = { + "lh": [0], + "lv": [MAXIMUM_ROW_PHASE_MOTOR_POSITION], + "pc": [ROW_PHASE_CIRCULAR], + "nc": [-ROW_PHASE_CIRCULAR], + "lh3": [0], +} + + +J09DefaultLookupTableConfig = LookupTableConfig( + path=LookupPath.create( + path="i09_apple2", + gap_file="i09_apple2/j09_energy2gap_calibrations.csv", + phase_file=None, + ), + mode="Mode", + min_energy="MinEnergy", + max_energy="MaxEnergy", + poly_deg=[ + "9th-order", + "8th-order", + "7th-order", + "6th-order", + "5th-order", + "4th-order", + "3rd-order", + "2nd-order", + "1st-order", + "0th-order", + ], +) + + +class J09EnergyMotorLookup(BaseEnergyMotorLookup): + """ + Handles lookup tables for I10 Apple2 ID, converting energy and polarisation to gap + and phase. Fetches and parses lookup tables from a config server, supports dynamic + updates, and validates input. + """ + + def __init__( + self, + config_client: ConfigServer, + lut_config: LookupTableConfig = J09DefaultLookupTableConfig, + ): + """Initialise the I10EnergyMotorLookup class with lookup table headers provided. + + Parameters + ---------- + look_up_table_dir: + The path to look up table. + source: + The column name and the name of the source in look up table. e.g. ( "source", "idu") + config_client: + The config server client to fetch the look up table. + mode: + The column name of the mode in look up table. + min_energy: + The column name that contain the maximum energy in look up table. + max_energy: + The column name that contain the maximum energy in look up table. + poly_deg: + The column names for the parameters for the energy conversion polynomial, starting with the least significant. + + """ + + super().__init__( + config_client=config_client, + lut_config=lut_config, + ) + + def update_lookuptable(self): + """ + Update lookup tables from files and validate their format. + """ + self.update_gap_lookuptable() + mix_energies = [] + max_energies = [] + pols = [] + poly1d_params = [] + for key in self.lookup_tables[LookupTableKeys.GAP].keys(): + if key is not None: + pols.append(Pol(key.lower())) + mix_energies.append( + self.lookup_tables[LookupTableKeys.GAP][key][LookupTableKeys.LIMIT][ + LookupTableKeys.MIN + ] + ) + max_energies.append( + self.lookup_tables[LookupTableKeys.GAP][key][LookupTableKeys.LIMIT][ + LookupTableKeys.MAX + ] + ) + poly1d_params.append(J09PhasePoly1dParameters[key]) + self.lookup_tables[LookupTableKeys.PHASE] = make_phase_tables( + pols=pols, + min_energies=mix_energies, + max_energies=max_energies, + poly1d_params=poly1d_params, + ) + Lookuptable.model_validate(self.lookup_tables[LookupTableKeys.PHASE]) + + +class J09Apple2Controller(Apple2Controller[Apple2]): + def __init__( + self, + apple2: Apple2, + lookuptable_dir: str, + config_client: ConfigServer, + poly_deg: list[str] | None = None, + units: str = "keV", + name: str = "", + ) -> None: + self.lookup_table_client = J09EnergyMotorLookup( + lookuptable_dir=lookuptable_dir, + config_client=config_client, + poly_deg=poly_deg, + ) + super().__init__( + apple2=apple2, + energy_to_motor_converter=self.lookup_table_client.get_motor_from_energy, + units=units, + name=name, + ) + + async def _set_motors_from_energy(self, value: float) -> None: + """ + Set the undulator motors for a given energy and polarisation. + """ + + pol = await self._check_and_get_pol_setpoint() + gap, phase = self.energy_to_motor(energy=value, pol=pol) + id_set_val = Apple2Val( + gap=f"{gap:.6f}", + phase=Apple2PhasesVal( + top_outer=f"{phase:.6f}", + top_inner="0.0", + btm_inner=f"{phase:.6f}", + btm_outer="0.0", + ), + ) + + LOGGER.info(f"Setting polarisation to {pol}, with values: {id_set_val}") + await self.apple2().set(id_motor_values=id_set_val) diff --git a/src/dodal/devices/i10/i10_apple2.py b/src/dodal/devices/i10/i10_apple2.py index d19861be4e..51bba73921 100644 --- a/src/dodal/devices/i10/i10_apple2.py +++ b/src/dodal/devices/i10/i10_apple2.py @@ -58,6 +58,8 @@ def update_lookuptable(self): self.available_pol = list(self.lookup_tables.gap.root.keys()) LOGGER.info("Updating lookup dictionary from file for phase.") + if self.lut_config.path.phase is None: + raise RuntimeError("Phase lookup table is required for I10 Apple2.") phase_csv_file = self.config_client.get_file_contents( self.lut_config.path.phase, reset_cached_result=True ) diff --git a/src/dodal/devices/util/lookup_tables_apple2.py b/src/dodal/devices/util/lookup_tables_apple2.py index c42da2edde..2ece230d7a 100644 --- a/src/dodal/devices/util/lookup_tables_apple2.py +++ b/src/dodal/devices/util/lookup_tables_apple2.py @@ -44,18 +44,19 @@ ) from dodal.devices.apple2_undulator import Pol +from dodal.log import LOGGER class LookupPath(BaseModel): gap: Path - phase: Path + phase: Path | None = None @classmethod def create( cls, path: str, gap_file: str = "IDEnergy2GapCalibrations.csv", - phase_file: str = "IDEnergy2PhaseCalibrations.csv", + phase_file: str | None = "IDEnergy2PhaseCalibrations.csv", ) -> "LookupPath": """ Factory method to easily create LookupPath using some default file names. @@ -71,7 +72,10 @@ def create( Returns: LookupPath instance. """ - return cls(gap=Path(path, gap_file), phase=Path(path, phase_file)) + return cls( + gap=Path(path, gap_file), + phase=Path(path, phase_file) if phase_file else None, + ) DEFAULT_POLY_DEG = [ @@ -233,7 +237,7 @@ def get_poly( Parameters: ----------- energy: - Energy value in the same units used to create the lookup table (eV). + Energy value in the same units used to create the lookup table. pol: Polarisation mode (Pol enum). lookup_table: @@ -350,6 +354,33 @@ def update_lookuptable(self): Update lookup tables from files and validate their format. """ + def update_gap_lookuptable(self): + """ + Update lookup tables from files and validate their format. + """ + LOGGER.info("Updating lookup dictionary from file for gap.") + gap_csv_file = self.config_client.get_file_contents( + self.lut_config.path.gap, reset_cached_result=True + ) + self.lookup_tables.gap = convert_csv_to_lookup( + file_contents=gap_csv_file, lut_config=self.lut_config + ) + self.available_pol = list(self.lookup_tables.gap.root.keys()) + + def update_phase_lookuptable(self): + """ + Update lookup tables from files and validate their format. + """ + LOGGER.info("Updating lookup dictionary from file for phase.") + if self.lut_config.path.phase is None: + raise RuntimeError("Phase lookup table path is not provided.") + phase_csv_file = self.config_client.get_file_contents( + self.lut_config.path.phase, reset_cached_result=True + ) + self.lookup_tables.phase = convert_csv_to_lookup( + file_contents=phase_csv_file, lut_config=self.lut_config + ) + def get_motor_from_energy(self, energy: float, pol: Pol) -> tuple[float, float]: """ Convert energy and polarisation to gap and phase motor positions. diff --git a/tests/devices/i09_2_shared/__init__.py b/tests/devices/i09_2_shared/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/devices/i09_2_shared/conftest.py b/tests/devices/i09_2_shared/conftest.py new file mode 100644 index 0000000000..7c1ccda64c --- /dev/null +++ b/tests/devices/i09_2_shared/conftest.py @@ -0,0 +1,7 @@ +from tests.devices.test_apple2_undulator import ( + mock_id_gap, + mock_phase_axes, +) +from tests.devices.util.test_lookup_tables_apple2 import mock_config_client + +__all__ = ["mock_id_gap", "mock_phase_axes", "mock_config_client"] diff --git a/tests/devices/i09_2_shared/test_data/ExpectedJ09EnergyMotorLookup.pkl b/tests/devices/i09_2_shared/test_data/ExpectedJ09EnergyMotorLookup.pkl new file mode 100644 index 0000000000..40976d9a68 Binary files /dev/null and b/tests/devices/i09_2_shared/test_data/ExpectedJ09EnergyMotorLookup.pkl differ diff --git a/tests/devices/i09_2_shared/test_data/JIDEnergy2GapCalibrations.csv b/tests/devices/i09_2_shared/test_data/JIDEnergy2GapCalibrations.csv new file mode 100644 index 0000000000..2aecdf51ab --- /dev/null +++ b/tests/devices/i09_2_shared/test_data/JIDEnergy2GapCalibrations.csv @@ -0,0 +1,7 @@ +#I09 Soft X-ray ID fitting parameters, created 9th Sept 2020,,,,,,,,,,, +Mode,MinEnergy,MaxEnergy,0th-order,1st-order,2nd-order,3rd-order,4th-order,5th-order,6th-order,7th-order,8th-order,9th-order +LH,0.104,1.2,0.52071,238.56372,-1169.06966,4273.03275,-10497.36261,17156.91928,-18309.05195,12222.50318,-4623.70738,755.90853 +LV,0.22,1,5.33595,72.53678,-133.96826,179.99229,-128.83048,39.34346,0,0,0,0 +CR,0.145,1.2,5.32869,101.28316,-192.74788,249.91788,-167.93323,47.22008,0,0,0,0 +CL,0.145,1.2,5.25639,101.22916,-192.74788,249.91788,-167.93323,47.22008,0,0,0,0 +LH3,0.7,2,10.98969,25.8301,-9.36535,1.74461,0,0,0,0,0,0 diff --git a/tests/devices/i09_2_shared/test_data/JIDEnergy2GapCalibrations.pkl b/tests/devices/i09_2_shared/test_data/JIDEnergy2GapCalibrations.pkl new file mode 100644 index 0000000000..7f4f2d485f Binary files /dev/null and b/tests/devices/i09_2_shared/test_data/JIDEnergy2GapCalibrations.pkl differ diff --git a/tests/devices/i09_2_shared/test_data/__init__.py b/tests/devices/i09_2_shared/test_data/__init__.py new file mode 100644 index 0000000000..20540c87e7 --- /dev/null +++ b/tests/devices/i09_2_shared/test_data/__init__.py @@ -0,0 +1,16 @@ +from os import fspath +from os.path import join +from pathlib import Path + +LOOKUP_TABLE_PATH = fspath(Path(__file__).parent) +TEST_SOFT_UNDULATOR_LUT = join(LOOKUP_TABLE_PATH, "JIDEnergy2GapCalibrations.csv") +TEST_EXPECTED_UNDULATOR_LUT = join(LOOKUP_TABLE_PATH, "JIDEnergy2GapCalibrations.pkl") +TEST_EXPECTED_ENERGY_MOTOR_LOOKUP = join( + LOOKUP_TABLE_PATH, "ExpectedJ09EnergyMotorLookup.pkl" +) +__all__ = [ + "LOOKUP_TABLE_PATH", + "TEST_SOFT_UNDULATOR_LUT", + "TEST_EXPECTED_UNDULATOR_LUT", + "TEST_EXPECTED_ENERGY_MOTOR_LOOKUP", +] diff --git a/tests/devices/i09_2_shared/test_i09_apple2.py b/tests/devices/i09_2_shared/test_i09_apple2.py new file mode 100644 index 0000000000..a7fa281a3d --- /dev/null +++ b/tests/devices/i09_2_shared/test_i09_apple2.py @@ -0,0 +1,244 @@ +import pickle + +import pytest +from daq_config_server.client import ConfigServer +from ophyd_async.core import init_devices +from ophyd_async.testing import ( + get_mock_put, + set_mock_value, +) + +from dodal.devices.apple2_undulator import ( + Apple2, + BeamEnergy, + InsertionDeviceEnergy, + InsertionDevicePolarisation, + Pol, +) +from dodal.devices.i09_2_shared.i09_apple2 import ( + MAXIMUM_ROW_PHASE_MOTOR_POSITION, + ROW_PHASE_CIRCULAR, + J09Apple2Controller, + J09EnergyMotorLookup, +) +from dodal.devices.pgm import PlaneGratingMonochromator +from dodal.devices.util.lookup_tables_apple2 import convert_csv_to_lookup +from tests.devices.i09_2_shared.test_data import ( + LOOKUP_TABLE_PATH, + TEST_EXPECTED_ENERGY_MOTOR_LOOKUP, + TEST_EXPECTED_UNDULATOR_LUT, + TEST_SOFT_UNDULATOR_LUT, +) + +POLY_DEG = [ + "9th-order", + "8th-order", + "7th-order", + "6th-order", + "5th-order", + "4th-order", + "3rd-order", + "2nd-order", + "1st-order", + "0th-order", +] + + +@pytest.fixture +def mock_j09_energy_motor_lookup(mock_config_client) -> J09EnergyMotorLookup: + return J09EnergyMotorLookup( + lookuptable_dir=LOOKUP_TABLE_PATH, + gap_file_name="JIDEnergy2GapCalibrations.csv", + config_client=mock_config_client, + ) + + +@pytest.fixture +async def mock_apple2(mock_id_gap, mock_phase_axes) -> Apple2: + async with init_devices(mock=True): + mock_apple2 = Apple2(id_gap=mock_id_gap, id_phase=mock_phase_axes) + return mock_apple2 + + +@pytest.fixture +async def mock_id_controller( + mock_apple2: Apple2, + mock_config_client: ConfigServer, +) -> J09Apple2Controller: + async with init_devices(mock=True): + mock_id_controller = J09Apple2Controller( + apple2=mock_apple2, + lookuptable_dir=LOOKUP_TABLE_PATH, + poly_deg=POLY_DEG, + config_client=mock_config_client, + ) + mock_id_controller._energy_set(0.5) + return mock_id_controller + + +@pytest.fixture +async def mock_id_energy( + mock_id_controller: J09Apple2Controller, +) -> InsertionDeviceEnergy: + async with init_devices(mock=True): + mock_id_energy = InsertionDeviceEnergy( + id_controller=mock_id_controller, + ) + + return mock_id_energy + + +@pytest.fixture +async def beam_energy( + mock_id_energy: InsertionDeviceEnergy, mock_pgm: PlaneGratingMonochromator +) -> BeamEnergy: + async with init_devices(mock=True): + beam_energy = BeamEnergy(id_energy=mock_id_energy, mono=mock_pgm.energy) + return beam_energy + + +@pytest.fixture +async def mock_id_pol( + mock_id_controller: J09Apple2Controller, +) -> InsertionDevicePolarisation: + async with init_devices(mock=True): + mock_id_pol = InsertionDevicePolarisation(id_controller=mock_id_controller) + + return mock_id_pol + + +def test_j09_energy_motor_lookup_convert_csv_to_lookup_success( + mock_j09_energy_motor_lookup: J09EnergyMotorLookup, +): + file = mock_j09_energy_motor_lookup.config_client.get_file_contents( + file_path=TEST_SOFT_UNDULATOR_LUT, reset_cached_result=True + ) + data = convert_csv_to_lookup( + file=file, + source=None, + poly_deg=POLY_DEG, + skip_line_start_with="#", + ) + + with open(TEST_EXPECTED_UNDULATOR_LUT, "rb") as f: + loaded_dict = pickle.load(f) + assert data == loaded_dict + + +def test_j09_energy_motor_lookup_update_lookuptable( + mock_j09_energy_motor_lookup: J09EnergyMotorLookup, +): + mock_j09_energy_motor_lookup.update_lookuptable() + with open(TEST_EXPECTED_ENERGY_MOTOR_LOOKUP, "rb") as f: + map_dict = pickle.load(f) + + assert mock_j09_energy_motor_lookup.lookup_tables == map_dict + + +@pytest.mark.parametrize( + "pol, top_outer_phase,top_inner_phase,btm_inner_phase, btm_outer_phase", + [ + (Pol.LH, 0, 0, 0, 0), + (Pol.LV, 24.0, 0, 24.0, 0), + (Pol.PC, 12, 0, 12, 0), + (Pol.NC, -12, 0, -12, 0), + (Pol.NONE, 8, 12, 2, -12), + ], +) +async def test_j09_apple2_controller_determine_pol( + mock_id_controller: J09Apple2Controller, + pol: Pol, + top_inner_phase: float, + top_outer_phase: float, + btm_inner_phase: float, + btm_outer_phase: float, +): + assert await mock_id_controller.polarisation_setpoint.get_value() == Pol.NONE + + set_mock_value( + mock_id_controller.apple2().phase().top_inner.user_readback, top_inner_phase + ) + set_mock_value( + mock_id_controller.apple2().phase().top_outer.user_readback, top_outer_phase + ) + set_mock_value( + mock_id_controller.apple2().phase().btm_inner.user_readback, btm_inner_phase + ) + set_mock_value( + mock_id_controller.apple2().phase().btm_outer.user_readback, btm_outer_phase + ) + if pol == Pol.NONE: + with pytest.raises(ValueError): + await mock_id_controller.energy.set(0.800) + else: + await mock_id_controller.energy.set(0.800) + assert await mock_id_controller.polarisation.get_value() == pol + + +@pytest.mark.parametrize( + "pol, top_outer_phase,top_inner_phase,btm_inner_phase, btm_outer_phase", + [ + ( + Pol.LH, + 0.0, + 0.0, + 0.0, + 0.0, + ), + ( + Pol.LV, + MAXIMUM_ROW_PHASE_MOTOR_POSITION, + 0.0, + MAXIMUM_ROW_PHASE_MOTOR_POSITION, + 0.0, + ), + (Pol.PC, ROW_PHASE_CIRCULAR, 0.0, ROW_PHASE_CIRCULAR, 0.0), + (Pol.NC, -ROW_PHASE_CIRCULAR, 0.0, -ROW_PHASE_CIRCULAR, 0.0), + ], +) +async def test_j09_apple2_controller_set_pol( + mock_id_controller: J09Apple2Controller, + pol: Pol, + top_inner_phase: float, + top_outer_phase: float, + btm_inner_phase: float, + btm_outer_phase: float, +): + await mock_id_controller.polarisation.set(pol) + get_mock_put( + mock_id_controller.apple2().phase().top_outer.user_setpoint + ).assert_called_once_with(f"{top_outer_phase:.6f}", wait=True) + get_mock_put( + mock_id_controller.apple2().phase().top_inner.user_setpoint + ).assert_called_once_with(f"{top_inner_phase}", wait=True) + get_mock_put( + mock_id_controller.apple2().phase().btm_inner.user_setpoint + ).assert_called_once_with(f"{btm_inner_phase:.6f}", wait=True) + get_mock_put( + mock_id_controller.apple2().phase().btm_outer.user_setpoint + ).assert_called_once_with(f"{btm_outer_phase}", wait=True) + + +@pytest.mark.parametrize( + "pol, energy, expected_gap", + [ + (Pol.LH, 0.3, 28), + (Pol.LV, 0.5, 23), + (Pol.PC, 1.1, 46), + (Pol.NC, 0.8, 37), + (Pol.LH3, 0.9, 28), + ], +) +async def test_j09_apple2_controller_set_energy( + mock_id_controller: J09Apple2Controller, + pol: Pol, + energy: float, + expected_gap: float, +): + mock_id_controller._polarisation_setpoint_set(pol) + + await mock_id_controller.energy.set(energy) + mock_gap_setpoint = get_mock_put(mock_id_controller.apple2().gap().user_setpoint) + assert float(mock_gap_setpoint.call_args_list[0].args[0]) == pytest.approx( + expected_gap, abs=1 + ) diff --git a/tests/devices/i10/conftest.py b/tests/devices/i10/conftest.py new file mode 100644 index 0000000000..6076f376f2 --- /dev/null +++ b/tests/devices/i10/conftest.py @@ -0,0 +1,8 @@ +from tests.devices.test_apple2_undulator import ( + mock_id_gap, + mock_jaw_phase, + mock_phase_axes, +) +from tests.devices.util.test_lookup_tables_apple2 import mock_config_client + +__all__ = ["mock_id_gap", "mock_jaw_phase", "mock_phase_axes", "mock_config_client"] diff --git a/tests/devices/i10/test_i10_apple2.py b/tests/devices/i10/test_i10_apple2.py index 976f8c75fd..c978e39136 100644 --- a/tests/devices/i10/test_i10_apple2.py +++ b/tests/devices/i10/test_i10_apple2.py @@ -2,7 +2,7 @@ import pickle from collections.abc import Mapping from unittest import mock -from unittest.mock import AsyncMock, MagicMock, Mock +from unittest.mock import AsyncMock, Mock import pytest from bluesky.plans import scan @@ -62,47 +62,6 @@ ] -@pytest.fixture -async def mock_id_gap(prefix: str = "BLXX-EA-DET-007:") -> UndulatorGap: - async with init_devices(mock=True): - mock_id_gap = UndulatorGap(prefix, "mock_id_gap") - assert mock_id_gap.name == "mock_id_gap" - set_mock_value(mock_id_gap.gate, UndulatorGateStatus.CLOSE) - set_mock_value(mock_id_gap.velocity, 1) - set_mock_value(mock_id_gap.user_readback, 20) - set_mock_value(mock_id_gap.user_setpoint, "20") - set_mock_value(mock_id_gap.status, EnabledDisabledUpper.ENABLED) - return mock_id_gap - - -@pytest.fixture -async def mock_phase_axes(prefix: str = "BLXX-EA-DET-007:") -> UndulatorPhaseAxes: - async with init_devices(mock=True): - mock_phase_axes = UndulatorPhaseAxes( - prefix=prefix, - top_outer="RPQ1", - top_inner="RPQ2", - btm_outer="RPQ3", - btm_inner="RPQ4", - ) - assert mock_phase_axes.name == "mock_phase_axes" - set_mock_value(mock_phase_axes.gate, UndulatorGateStatus.CLOSE) - set_mock_value(mock_phase_axes.top_outer.velocity, 2) - set_mock_value(mock_phase_axes.top_inner.velocity, 2) - set_mock_value(mock_phase_axes.btm_outer.velocity, 2) - set_mock_value(mock_phase_axes.btm_inner.velocity, 2) - set_mock_value(mock_phase_axes.top_outer.user_readback, 0) - set_mock_value(mock_phase_axes.top_inner.user_readback, 0) - set_mock_value(mock_phase_axes.btm_outer.user_readback, 0) - set_mock_value(mock_phase_axes.btm_inner.user_readback, 0) - set_mock_value(mock_phase_axes.top_outer.user_setpoint_readback, 0) - set_mock_value(mock_phase_axes.top_inner.user_setpoint_readback, 0) - set_mock_value(mock_phase_axes.btm_outer.user_setpoint_readback, 0) - set_mock_value(mock_phase_axes.btm_inner.user_setpoint_readback, 0) - set_mock_value(mock_phase_axes.status, EnabledDisabledUpper.ENABLED) - return mock_phase_axes - - @pytest.fixture async def mock_pgm(prefix: str = "BLXX-EA-DET-007:") -> PlaneGratingMonochromator: async with init_devices(mock=True): @@ -113,35 +72,6 @@ async def mock_pgm(prefix: str = "BLXX-EA-DET-007:") -> PlaneGratingMonochromato return mock_pgm -@pytest.fixture -async def mock_jaw_phase(prefix: str = "BLXX-EA-DET-007:") -> UndulatorJawPhase: - async with init_devices(mock=True): - mock_jaw_phase = UndulatorJawPhase( - prefix=prefix, move_pv="RPQ1", jaw_phase="JAW" - ) - set_mock_value(mock_jaw_phase.gate, UndulatorGateStatus.CLOSE) - set_mock_value(mock_jaw_phase.jaw_phase.velocity, 2) - set_mock_value(mock_jaw_phase.jaw_phase.user_readback, 0) - set_mock_value(mock_jaw_phase.status, EnabledDisabledUpper.ENABLED) - return mock_jaw_phase - - -@pytest.fixture -def mock_config_client() -> ConfigServer: - mock.patch("dodal.devices.i10.i10_apple2.ConfigServer") - mock_config_client = ConfigServer() - - mock_config_client.get_file_contents = MagicMock(spec=["get_file_contents"]) - - def my_side_effect(file_path, reset_cached_result) -> str: - assert reset_cached_result is True - with open(file_path) as f: - return f.read() - - mock_config_client.get_file_contents.side_effect = my_side_effect - return mock_config_client - - @pytest.fixture async def mock_id( mock_id_gap: UndulatorGap, @@ -169,17 +99,7 @@ async def mock_id_controller( ), config_client=mock_config_client, ) - set_mock_value(mock_id_controller.apple2().gap().gate, UndulatorGateStatus.CLOSE) - set_mock_value(mock_id_controller.apple2().phase().gate, UndulatorGateStatus.CLOSE) - set_mock_value( - mock_id_controller.apple2().jaw_phase().gate, UndulatorGateStatus.CLOSE - ) - set_mock_value(mock_id_controller.apple2().gap().velocity, 1) - set_mock_value(mock_id_controller.apple2().jaw_phase().jaw_phase.velocity, 1) - set_mock_value(mock_id_controller.apple2().phase().btm_inner.velocity, 1) - set_mock_value(mock_id_controller.apple2().phase().top_inner.velocity, 1) - set_mock_value(mock_id_controller.apple2().phase().btm_outer.velocity, 1) - set_mock_value(mock_id_controller.apple2().phase().top_outer.velocity, 1) + return mock_id_controller diff --git a/tests/devices/test_apple2_undulator.py b/tests/devices/test_apple2_undulator.py index 77f5c3a496..f656e09cfa 100644 --- a/tests/devices/test_apple2_undulator.py +++ b/tests/devices/test_apple2_undulator.py @@ -62,14 +62,6 @@ async def mock_phase_axes(prefix: str = "BLXX-EA-DET-007:") -> UndulatorPhaseAxe set_mock_value(mock_phase_axes.top_inner.velocity, 2) set_mock_value(mock_phase_axes.btm_outer.velocity, 2) set_mock_value(mock_phase_axes.btm_inner.velocity, 2) - set_mock_value(mock_phase_axes.top_outer.user_readback, 2) - set_mock_value(mock_phase_axes.top_inner.user_readback, 2) - set_mock_value(mock_phase_axes.btm_outer.user_readback, 2) - set_mock_value(mock_phase_axes.btm_inner.user_readback, 2) - set_mock_value(mock_phase_axes.top_outer.user_setpoint_readback, 2) - set_mock_value(mock_phase_axes.top_inner.user_setpoint_readback, 2) - set_mock_value(mock_phase_axes.btm_outer.user_setpoint_readback, 2) - set_mock_value(mock_phase_axes.btm_inner.user_setpoint_readback, 2) set_mock_value(mock_phase_axes.status, EnabledDisabledUpper.ENABLED) return mock_phase_axes diff --git a/tests/devices/util/test_lookup_tables_apple2.py b/tests/devices/util/test_lookup_tables_apple2.py index b6e78790b1..179dcfb4c3 100644 --- a/tests/devices/util/test_lookup_tables_apple2.py +++ b/tests/devices/util/test_lookup_tables_apple2.py @@ -2,6 +2,7 @@ import numpy as np import pytest +from daq_config_server.client import ConfigServer from dodal.devices.apple2_undulator import Pol from dodal.devices.util.lookup_tables_apple2 import ( @@ -16,6 +17,21 @@ ) +@pytest.fixture +def mock_config_client() -> ConfigServer: + mock_config_client = ConfigServer() + + mock_config_client.get_file_contents = MagicMock(spec=["get_file_contents"]) + + def my_side_effect(file_path, reset_cached_result) -> str: + assert reset_cached_result is True + with open(file_path) as f: + return f.read() + + mock_config_client.get_file_contents.side_effect = my_side_effect + return mock_config_client + + def test_generate_lookup_table_structure_and_poly(): min_e = 100.0 max_e = 200.0