diff --git a/src/dodal/beamlines/beamline_parameters.py b/src/dodal/beamlines/beamline_parameters.py index d680b81459e..ae3a64e6aa8 100644 --- a/src/dodal/beamlines/beamline_parameters.py +++ b/src/dodal/beamlines/beamline_parameters.py @@ -1,4 +1,4 @@ -from typing import Any, Tuple, cast +from typing import Any, Optional, Tuple, cast from dodal.log import LOGGER from dodal.utils import get_beamline_name @@ -87,11 +87,14 @@ def parse_list(cls, value: str): return list_output -def get_beamline_parameters(): - beamline_name = get_beamline_name("s03") - beamline_param_path = BEAMLINE_PARAMETER_PATHS.get(beamline_name) - if beamline_param_path is None: - raise KeyError( - "No beamline parameter path found, maybe 'BEAMLINE' environment variable is not set!" - ) +def get_beamline_parameters(beamline_param_path: Optional[str] = None): + """Loads the beamline parameters from the specified path, or according to the + environment variable if none is given""" + if not beamline_param_path: + beamline_name = get_beamline_name("s03") + beamline_param_path = BEAMLINE_PARAMETER_PATHS.get(beamline_name) + if beamline_param_path is None: + raise KeyError( + "No beamline parameter path found, maybe 'BEAMLINE' environment variable is not set!" + ) return GDABeamlineParameters.from_file(beamline_param_path) diff --git a/src/dodal/beamlines/i24.py b/src/dodal/beamlines/i24.py index 9d5660a5699..4c0e54a683e 100644 --- a/src/dodal/beamlines/i24.py +++ b/src/dodal/beamlines/i24.py @@ -10,14 +10,14 @@ from dodal.devices.i24.pmac import PMAC from dodal.devices.oav.oav_detector import OAV, OAVConfigParams from dodal.devices.zebra import Zebra -from dodal.log import set_beamline +from dodal.log import set_beamline as set_log_beamline from dodal.utils import get_beamline_name, skip_device ZOOM_PARAMS_FILE = "/dls_sw/i24/software/gda/config/xml/jCameraManZoomLevels.xml" DISPLAY_CONFIG = "/dls_sw/i24/software/gda_versions/var/display.configuration" BL = get_beamline_name("s24") -set_beamline(BL) +set_log_beamline(BL) set_utils_beamline(BL) @@ -108,7 +108,9 @@ def oav(wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False) -> @skip_device(lambda: BL == "s24") -def vgonio(wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False) -> OAV: +def vgonio( + wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False +) -> VGonio: """Get the i24 vgonio device, instantiate it if it hasn't already been. If this is called when already instantiated, it will return the existing object. """ diff --git a/src/dodal/devices/DCM.py b/src/dodal/devices/DCM.py index 706981f6009..41b26cbab42 100644 --- a/src/dodal/devices/DCM.py +++ b/src/dodal/devices/DCM.py @@ -15,7 +15,9 @@ def __init__(self, *args, daq_configuration_path: str, **kwargs): ) # I03 configures the DCM Perp as a side effect of applying this fixed value to the DCM Offset after an energy change # Nb this parameter is misleadingly named to confuse you - self.fixed_offset_mm = get_beamline_parameters()["DCM_Perp_Offset_FIXED"] + self.fixed_offset_mm = get_beamline_parameters( + daq_configuration_path + "/domain/beamlineParameters" + )["DCM_Perp_Offset_FIXED"] """ A double crystal monochromator (DCM), used to select the energy of the beam. diff --git a/src/dodal/utils.py b/src/dodal/utils.py index 893ccc61d80..2becfb17553 100644 --- a/src/dodal/utils.py +++ b/src/dodal/utils.py @@ -110,7 +110,7 @@ def wrapper(*args, **kwds) -> T: def make_all_devices( - module: Union[str, ModuleType, None] = None, **kwargs + module: Union[str, ModuleType, None] = None, include_skipped: bool = False, **kwargs ) -> Dict[str, AnyDevice]: """Makes all devices in the given beamline module. @@ -126,7 +126,7 @@ def make_all_devices( """ if isinstance(module, str) or module is None: module = import_module(module or __name__) - factories = collect_factories(module) + factories = collect_factories(module, include_skipped) devices: dict[str, AnyDevice] = invoke_factories(factories, **kwargs) return devices @@ -167,11 +167,17 @@ def extract_dependencies( yield name -def collect_factories(module: ModuleType) -> dict[str, AnyDeviceFactory]: +def collect_factories( + module: ModuleType, include_skipped: bool = False +) -> dict[str, AnyDeviceFactory]: factories: dict[str, AnyDeviceFactory] = {} for var in module.__dict__.values(): - if callable(var) and is_any_device_factory(var) and not _is_device_skipped(var): + if ( + callable(var) + and is_any_device_factory(var) + and (include_skipped or not _is_device_skipped(var)) + ): factories[var.__name__] = var return factories diff --git a/tests/beamlines/__init__.py b/tests/beamlines/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/beamlines/unit_tests/__init__.py b/tests/beamlines/unit_tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/beamlines/unit_tests/test_beamline_utils.py b/tests/beamlines/unit_tests/test_beamline_utils.py index 7477287ad2a..87ef6e66aa4 100644 --- a/tests/beamlines/unit_tests/test_beamline_utils.py +++ b/tests/beamlines/unit_tests/test_beamline_utils.py @@ -13,6 +13,14 @@ from dodal.devices.zebra import Zebra from dodal.utils import make_all_devices +from ...conftest import mock_beamline_module_filepaths + + +@pytest.fixture +def reset_i03(): + beamline_utils.clear_devices() + mock_beamline_module_filepaths("i03", i03) + def test_instantiate_function_makes_supplied_device(): device_types = [Zebra, ApertureScatterguard, Smargon] @@ -24,8 +32,7 @@ def test_instantiate_function_makes_supplied_device(): assert isinstance(dev, device) -def test_instantiating_different_device_with_same_name(): - beamline_utils.clear_devices() +def test_instantiating_different_device_with_same_name(reset_i03): dev1 = beamline_utils.device_instantiation( # noqa Zebra, "device", "", False, False, None ) @@ -43,8 +50,7 @@ def test_instantiating_different_device_with_same_name(): assert dev2 in beamline_utils.ACTIVE_DEVICES.values() -def test_instantiate_function_fake_makes_fake(): - beamline_utils.clear_devices() +def test_instantiate_function_fake_makes_fake(reset_i03): fake_zeb: Zebra = beamline_utils.device_instantiation( i03.Zebra, "zebra", "", True, True, None ) @@ -52,8 +58,8 @@ def test_instantiate_function_fake_makes_fake(): assert isinstance(fake_zeb.pc.arm_source, FakeEpicsSignal) -def test_clear_devices(RE): - beamline_utils.clear_devices() +def test_clear_devices(RE, reset_i03): + mock_beamline_module_filepaths("i03", i03) devices = make_all_devices(i03, fake_with_ophyd_sim=True) assert len(beamline_utils.ACTIVE_DEVICES) == len(devices.keys()) beamline_utils.clear_devices() @@ -61,8 +67,6 @@ def test_clear_devices(RE): def test_device_is_new_after_clearing(RE): - beamline_utils.clear_devices() - def _make_devices_and_get_id(): return [ id(device) diff --git a/tests/beamlines/unit_tests/test_device_instantiation.py b/tests/beamlines/unit_tests/test_device_instantiation.py index 83b6e9a1179..c8330d860e8 100644 --- a/tests/beamlines/unit_tests/test_device_instantiation.py +++ b/tests/beamlines/unit_tests/test_device_instantiation.py @@ -1,36 +1,61 @@ +import importlib +import os from typing import Any +from unittest.mock import patch -from dodal.beamlines import beamline_utils, i03, i04, i04_1, i23, i24, p38, p45 +import pytest + +from dodal.beamlines import beamline_utils from dodal.utils import BLUESKY_PROTOCOLS, make_all_devices -ALL_BEAMLINES = {i03, i04, i04_1, i23, i24, p38, p45} +from ...conftest import mock_beamline_module_filepaths + +ALL_BEAMLINES = {"i03", "i04", "i04_1", "i23", "i24", "p38", "p45"} def follows_bluesky_protocols(obj: Any) -> bool: return any((isinstance(obj, protocol) for protocol in BLUESKY_PROTOCOLS)) -def test_device_creation(RE): +def mock_bl(beamline): + bl_mod = importlib.import_module("dodal.beamlines." + beamline) + mock_beamline_module_filepaths(beamline, bl_mod) + return bl_mod + + +@pytest.mark.parametrize("beamline", ALL_BEAMLINES) +def test_device_creation(RE, beamline): """ Ensures that for every beamline all device factories are using valid args and creating types that conform to Bluesky protocols. """ - for beamline in ALL_BEAMLINES: - devices = make_all_devices(beamline, fake_with_ophyd_sim=True) + with patch.dict(os.environ, {"BEAMLINE": beamline}, clear=True): + bl_mod = mock_bl(beamline) + devices = make_all_devices( + bl_mod, include_skipped=True, fake_with_ophyd_sim=True + ) for device_name, device in devices.items(): assert device_name in beamline_utils.ACTIVE_DEVICES assert follows_bluesky_protocols(device) assert len(beamline_utils.ACTIVE_DEVICES) == len(devices) beamline_utils.clear_devices() + del bl_mod -def test_devices_are_identical(RE): +@pytest.mark.parametrize("beamline", ALL_BEAMLINES) +def test_devices_are_identical(RE, beamline): """ Ensures that for every beamline all device functions prevent duplicate instantiation. """ - for beamline in ALL_BEAMLINES: - devices_a = make_all_devices(beamline, fake_with_ophyd_sim=True) - devices_b = make_all_devices(beamline, fake_with_ophyd_sim=True) + with patch.dict(os.environ, {"BEAMLINE": beamline}, clear=True): + bl_mod = mock_bl(beamline) + devices_a = make_all_devices( + bl_mod, include_skipped=True, fake_with_ophyd_sim=True + ) + devices_b = make_all_devices( + bl_mod, include_skipped=True, fake_with_ophyd_sim=True + ) for device_name in devices_a.keys(): assert devices_a[device_name] is devices_b[device_name] beamline_utils.clear_devices() + del bl_mod diff --git a/tests/beamlines/unit_tests/test_i24.py b/tests/beamlines/unit_tests/test_i24.py index 2be285dc73d..500ef771c32 100644 --- a/tests/beamlines/unit_tests/test_i24.py +++ b/tests/beamlines/unit_tests/test_i24.py @@ -13,13 +13,14 @@ def setup_module(): def test_device_creation(): + i24.BL = "i24" devices = make_all_devices(i24, fake_with_ophyd_sim=True) assert len(devices) > 0 for device_name in devices.keys(): assert device_name in beamline_utils.ACTIVE_DEVICES assert len(beamline_utils.ACTIVE_DEVICES) == len(devices) - vgonio: VGonio = beamline_utils.ACTIVE_DEVICES["vgonio"] + vgonio: VGonio = beamline_utils.ACTIVE_DEVICES["vgonio"] # type: ignore assert vgonio.prefix == "BL24I-MO-VGON-01:" assert vgonio.kappa.prefix == "BL24I-MO-VGON-01:KAPPA" diff --git a/tests/conftest.py b/tests/conftest.py index 815a14d4ed6..3830a24463b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,6 +12,24 @@ from dodal.devices.focusing_mirror import VFMMirrorVoltages from dodal.log import LOGGER, GELFTCPHandler, set_up_logging_handlers +MOCK_DAQ_CONFIG_PATH = "tests/devices/unit_tests/test_daq_configuration" +mock_paths = [ + ("DAQ_CONFIGURATION_PATH", MOCK_DAQ_CONFIG_PATH), + ("ZOOM_PARAMS_FILE", "tests/devices/unit_tests/test_jCameraManZoomLevels.xml"), + ("DISPLAY_CONFIG", "tests/devices/unit_tests/test_display.configuration"), +] +mock_attributes_table = { + "i03": mock_paths, + "s03": mock_paths, + "i04": mock_paths, + "s04": mock_paths, +} + + +def mock_beamline_module_filepaths(bl_name, bl_module): + if mock_attributes := mock_attributes_table.get(bl_name): + [bl_module.__setattr__(attr[0], attr[1]) for attr in mock_attributes] + def pytest_runtest_setup(item): beamline_utils.clear_devices() diff --git a/tests/devices/i04/__init__.py b/tests/devices/i04/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/devices/unit_tests/i24/__init__.py b/tests/devices/unit_tests/i24/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/devices/unit_tests/test_daq_configuration/domain/beamlineParameters b/tests/devices/unit_tests/test_daq_configuration/domain/beamlineParameters new file mode 100644 index 00000000000..df3951b8fcf --- /dev/null +++ b/tests/devices/unit_tests/test_daq_configuration/domain/beamlineParameters @@ -0,0 +1,140 @@ +# +# +BeamLine BL03S + +## Test data for device instantiation +BLSE=FB + +## BPFB (Beam Position FeedBack) +## HALF (default) only off during data collection +## FULL only off for XBPM2 during attenuation optimisation, fluo when trans < 2% and wedged MAD +## UNAVAILABLE (not default) prevents xbpm_feedback.py trying to access EPICS IOC that may not be running +BPFB=FULL +## Note: only beamline scientists control whether feedback is enabled +## via the XBPM feedback EDM screen in Synoptic + +# DCM parameters +DCM_Perp_Offset_FIXED = 25.6 +# +# beamstop +# +parked_x = 4.49 +parked_y = -50.0 +parked_y_plate = -50.5 +parked_z = -49.5 +parked_z_robot = 30.0 + +in_beam_z_MIN_START_POS = 60.0 + + +#Aperture - Scatterguard positions +# 100 micron ap +miniap_x_LARGE_APERTURE = 2.389 +miniap_y_LARGE_APERTURE = 40.986 +miniap_z_LARGE_APERTURE = 15.8 + +sg_x_LARGE_APERTURE = 5.25 +sg_y_LARGE_APERTURE = 4.43 + +# 50 micron ap +miniap_x_MEDIUM_APERTURE = 2.384 +miniap_y_MEDIUM_APERTURE = 44.967 +miniap_z_MEDIUM_APERTURE = 15.8 +sg_x_MEDIUM_APERTURE = 5.285 +sg_y_MEDIUM_APERTURE = 0.46 + +# 20 micron ap +miniap_x_SMALL_APERTURE = 2.430 +miniap_y_SMALL_APERTURE = 48.974 +miniap_z_SMALL_APERTURE = 15.8 +sg_x_SMALL_APERTURE = 5.3375 +sg_y_SMALL_APERTURE = -3.55 + +# Robot load +miniap_x_ROBOT_LOAD = 2.386 +miniap_y_ROBOT_LOAD = 31.40 +miniap_z_ROBOT_LOAD = 15.8 +sg_x_ROBOT_LOAD = 5.25 +sg_y_ROBOT_LOAD = 4.43 + +# manual mount +miniap_x_MANUAL_LOAD = -4.91 +miniap_y_MANUAL_LOAD = -49.0 +miniap_z_MANUAL_LOAD = -10.0 + +sg_x_MANUAL_LOAD = -4.7 +sg_y_MANUAL_LOAD = 1.8 + +miniap_x_SCIN_MOVE = -4.91 +# prion setting +#miniap_x_SCIN_MOVE = 0.0 +sg_x_SCIN_MOVE = -4.75 + +scin_y_SCIN_IN = 100.855 +scin_y_SCIN_OUT = -0.02 +scin_z_SCIN_IN = 101.5115 + + +scin_z_SCIN_OUT = 0.1 + +#distance to move gonx,y,z when scintillator is put in with standard pins +# For old gonio: +gon_x_SCIN_OUT_DISTANCE = 1.0 +# For SmarGon: +gon_x_SCIN_OUT_DISTANCE_smargon = 1 + +gon_y_SCIN_OUT_DISTANCE = 2.0 +gon_z_SCIN_OUT_DISTANCE = -0.5 + +# StandardEnergy on i03 is 12700eV +StandardEnergy = 12700 + +keyence_max_attempts = 1 +# Move gonio 100 microns, see difference in keyence values +# Then do 100/difference, put that number below +# Sign may change between Smargon and MiniKappa +keyence_slopeYToX = 2.5 +keyence_slopeYToY = -2.5 +keyence_slopeXToZ = 3.23 + +YAGSamX = 1022 +YAGSamY = -98.0 +YAGSamZ = -147 +YAGOmega = 0.0 + +#ipin value must be < ipin_threshold above background for data collection +ipin_threshold = 0.1 + +# energy thresholds for mirror stripes +# - first threshold is between bare/Rh stripes (e.g. 7000) +# - second threshold is between Rh/Pt stripes (e.g. 18000) +mirror_threshold_bare_rh = 6900 +mirror_threshold_rh_pt = 30000 + +# flux conversion factors +flux_factor_no_aperture = 1 +flux_factor_LARGE_APERTURE = 0.738 +flux_factor_MEDIUM_APERTURE = 0.36 +flux_factor_SMALL_APERTURE = 0.084 +flux_factor_no_aperture_plate = 1 +flux_factor_LARGE_APERTURE_plate = 0.738 +flux_factor_MEDIUM_APERTURE_plate = 0.36 +flux_factor_SMALL_APERTURE_plate = 0.084 + +#Deadtime settings +fluorescence_analyser_deadtimeThreshold=0.002 # used by edge scans +fluorescence_spectrum_deadtimeThreshold=0.0005 # used by spectrum + +#Other settings +fluorescence_attenuation_low_roi = 100 +fluorescence_attenuation_high_roi = 2048 +attenuation_optimisation_optimisation_cycles = 10 +attenuation_optimisation_start_transmission = 0.1 # per cent +fluorescence_mca_sca_offset = 400 + +#Total count settings +attenuation_optimisation_multiplier = 2 +attenuation_optimisation_target_count = 2000 +attenuation_optimisation_upper_limit = 50000 +attenuation_optimisation_lower_limit = 20000 + diff --git a/tests/devices/unit_tests/test_daq_configuration/lookup/BeamLineEnergy_DCM_Pitch_converter.txt b/tests/devices/unit_tests/test_daq_configuration/lookup/BeamLineEnergy_DCM_Pitch_converter.txt new file mode 100644 index 00000000000..449e920f738 --- /dev/null +++ b/tests/devices/unit_tests/test_daq_configuration/lookup/BeamLineEnergy_DCM_Pitch_converter.txt @@ -0,0 +1,25 @@ +# Bragg pitch +# Degree values for pitch are interpreted as mrad +# The values cannot change direction. +# last update 2023/06/26 NP +Units Deg mrad +Units Deg Deg +19.24347 -0.79775 +16.40949 -0.78679 +14.31123 -0.77838 +12.69287 -0.77276 +11.40555 -0.77276 +10.35662 -0.77031 +9.48522 -0.76693 +8.95826 -0.76387 +8.74953 -0.76387 +8.12020 -0.76387 +7.57556 -0.76354 +7.09950 -0.76166 +6.67997 -0.76044 +6.30732 -0.75953 +5.97411 -0.75845 +5.67434 -0.75796 +5.40329 -0.75789 +5.15700 -0.75551 +4.93218 -0.75513 diff --git a/tests/devices/unit_tests/test_daq_configuration/lookup/BeamLineEnergy_DCM_Roll_converter.txt b/tests/devices/unit_tests/test_daq_configuration/lookup/BeamLineEnergy_DCM_Roll_converter.txt new file mode 100644 index 00000000000..329f29f0990 --- /dev/null +++ b/tests/devices/unit_tests/test_daq_configuration/lookup/BeamLineEnergy_DCM_Roll_converter.txt @@ -0,0 +1,7 @@ +#Bragg angle against roll( absolute number) +#reloadLookupTables() +# last update 2023/01/19 NP +Units Deg mrad +26.4095 -0.2799 +6.3075 -0.2799 + diff --git a/tests/devices/unit_tests/test_undulator_dcm.py b/tests/devices/unit_tests/test_undulator_dcm.py index 5893b291257..69679936d42 100644 --- a/tests/devices/unit_tests/test_undulator_dcm.py +++ b/tests/devices/unit_tests/test_undulator_dcm.py @@ -5,7 +5,6 @@ from ophyd.sim import make_fake_device from ophyd.status import Status -from dodal.beamlines.i03 import DAQ_CONFIGURATION_PATH from dodal.devices.DCM import DCM from dodal.devices.undulator import Undulator, UndulatorGapAccess from dodal.devices.undulator_dcm import ( @@ -15,6 +14,8 @@ _get_energy_distance_table, ) +from ...conftest import MOCK_DAQ_CONFIG_PATH + @pytest.fixture def fake_undulator_dcm() -> UndulatorDCM: @@ -23,7 +24,7 @@ def fake_undulator_dcm() -> UndulatorDCM: lookup_table_path="./tests/devices/unit_tests/test_beamline_undulator_to_gap_lookup_table.txt", ) dcm: DCM = make_fake_device(DCM)( - name="dcm", daq_configuration_path=DAQ_CONFIGURATION_PATH + name="dcm", daq_configuration_path=MOCK_DAQ_CONFIG_PATH ) undulator_dcm: UndulatorDCM = make_fake_device(UndulatorDCM)( undulator, dcm, name="undulator_dcm" diff --git a/tests/unit_tests/__init__.py b/tests/unit_tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d