From b4dbd8e515cf46d7f51d65fad9bdc834ca396ffe Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Tue, 9 Dec 2025 09:37:13 +0000 Subject: [PATCH 01/39] Add mock config server fixture --- tests/conftest.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 0563b9cae39..00c15d98876 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ import importlib +import json import logging import os import sys @@ -8,6 +9,7 @@ from unittest.mock import MagicMock, patch import pytest +from daq_config_server.converters.models import ConfigModel from ophyd_async.core import ( PathProvider, ) @@ -167,3 +169,33 @@ def eiger_params(tmp_path: Path) -> DetectorParams: det_dist_to_beam_converter_path=TEST_LUT_TXT, detector_size_constants=EIGER2_X_16M_SIZE.det_type_string, # type: ignore ) + + +def _fake_config_server_get_file_contents( + filepath: str | Path, + desired_return_type: type[str] | type[dict] | ConfigModel = str, + reset_cached_result: bool = True, +): + filepath = Path(filepath) + # Minimal logic required for unit tests + with filepath.open("r") as f: + contents = f.read() + print(contents) + if desired_return_type is str: + return contents + elif desired_return_type is dict: + print("return type is dict") + return json.loads(contents) + elif issubclass(desired_return_type, ConfigModel): # type: ignore + return desired_return_type.model_validate(json.loads(contents)) + + +@pytest.fixture(autouse=True) +def mock_config_server(): + # Don't actually talk to central service during unit tests, and reset caches between test + + with patch( + "daq_config_server.client.ConfigServer.get_file_contents", + side_effect=_fake_config_server_get_file_contents, + ): + yield From bf08ced66ca14ee181900cf1ce6b0b8e2e0baa7f Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Tue, 9 Dec 2025 10:24:27 +0000 Subject: [PATCH 02/39] Use config server to read beamlineParmaeters --- .../common/beamlines/beamline_parameters.py | 39 +++---------------- 1 file changed, 6 insertions(+), 33 deletions(-) diff --git a/src/dodal/common/beamlines/beamline_parameters.py b/src/dodal/common/beamlines/beamline_parameters.py index fb66035e4f4..31f76b06c37 100644 --- a/src/dodal/common/beamlines/beamline_parameters.py +++ b/src/dodal/common/beamlines/beamline_parameters.py @@ -1,6 +1,8 @@ import ast from typing import Any, cast +from daq_config_server.client import ConfigServer + from dodal.log import LOGGER from dodal.utils import get_beamline_name @@ -24,41 +26,12 @@ def __repr__(self) -> str: def __getitem__(self, item: str): return self.params[item] - @classmethod - def from_lines(cls, file_name: str, config_lines: list[str]): - config_lines_nocomments = [line.split("#", 1)[0] for line in config_lines] - config_lines_sep_key_and_value = [ - # XXX removes all whitespace instead of just trim - line.translate(str.maketrans("", "", " \n\t\r")).split("=") - for line in config_lines_nocomments - ] - config_pairs: list[tuple[str, Any]] = [ - cast(tuple[str, Any], param) - for param in config_lines_sep_key_and_value - if len(param) == 2 - ] - for i, (param, value) in enumerate(config_pairs): - try: - # BEAMLINE_PARAMETER_KEYWORDS effectively raw string but whitespace removed - if value not in BEAMLINE_PARAMETER_KEYWORDS: - config_pairs[i] = ( - param, - cls.parse_value(value), - ) - except Exception as e: - LOGGER.warning(f"Unable to parse {file_name} line {i}: {e}") - - return cls(params=dict(config_pairs)) - @classmethod def from_file(cls, path: str): - with open(path) as f: - config_lines = f.readlines() - return cls.from_lines(path, config_lines) - - @classmethod - def parse_value(cls, value: str): - return ast.literal_eval(value.replace("Yes", "True").replace("No", "False")) + config_server = ConfigServer(url="https://daq-config.diamond.ac.uk") + return cls( + params=config_server.get_file_contents(path, dict, reset_cached_result=True) + ) def get_beamline_parameters(beamline_param_path: str | None = None): From 91fc322159056b8d52c4d22ed4654c92476efdef Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Tue, 9 Dec 2025 10:29:13 +0000 Subject: [PATCH 03/39] Remove tests for removed functions --- .../beamlines/test_beamline_parameters.py | 60 ------------------- 1 file changed, 60 deletions(-) diff --git a/tests/common/beamlines/test_beamline_parameters.py b/tests/common/beamlines/test_beamline_parameters.py index aeba8295f1d..3af042869fb 100644 --- a/tests/common/beamlines/test_beamline_parameters.py +++ b/tests/common/beamlines/test_beamline_parameters.py @@ -34,40 +34,6 @@ def test_i03_beamline_parameters(): ] -@patch("dodal.common.beamlines.beamline_parameters.LOGGER") -def test_parse_exception_causes_warning(mock_logger): - params = GDABeamlineParameters.from_file(BAD_BEAMLINE_PARAMETERS) - assert params["flux_predict_polynomial_coefficients_5"] == [ - -0.0000707134131045123, - 7.0205491504418, - -194299.6440518530, - 1835805807.3974800, - -3280251055671.100, - ] - mock_logger.warning.assert_called_once() - - params = GDABeamlineParameters.from_file(BAD_BEAMLINE_PARAMETERS) - assert params["flux_predict_polynomial_coefficients_5"] == [ - -0.0000707134131045123, - 7.0205491504418, - -194299.6440518530, - 1835805807.3974800, - -3280251055671.100, - ] - - -def test_parse_list(): - test_data = [([1, 2, 3], "[1, 2, 3]"), ([1, True, 3], "[1, Yes, 3]")] - for expected, input in test_data: - actual = GDABeamlineParameters.parse_value(input) - assert expected == actual, f"Actual:{actual}, expected: {expected}\n" - - -def test_parse_list_raises_exception(): - with pytest.raises(SyntaxError): - GDABeamlineParameters.parse_value("[1, 2") - - def test_get_beamline_parameters_works_with_no_environment_variable_set(): if environ.get("BEAMLINE"): del environ["BEAMLINE"] @@ -108,32 +74,6 @@ def test_get_beamline_parameters_raises_error_when_beamline_not_found( get_beamline_parameters() -def test_parse_nested_list(): - actual = GDABeamlineParameters.parse_value("[[1, 2], [3, 4]]") - expected = [[1, 2], [3, 4]] - assert actual == expected - - -def test_parse_nested_nested_list(): - actual = GDABeamlineParameters.parse_value("[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]") - expected = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] - assert actual == expected - - -def test_leading_comma_in_list_causes_error(): - with pytest.raises(SyntaxError): - GDABeamlineParameters.parse_value("[,1, 2, 3, 4]") - with pytest.raises(SyntaxError): - GDABeamlineParameters.parse_value("[[1, 2], [ ,3, 4]]") - - -def test_Yes_and_No_replaced_with_bool_values(): # noqa: N802 - value = "[Yes, No, True, False, 0, 1]" - expected = [True, False, True, False, 0, 1] - actual = GDABeamlineParameters.parse_value(value) - assert actual == expected - - @pytest.fixture(autouse=True) def i03_beamline_parameters(): with patch.dict( From 386b521eb5babc69cba1ec94e1f5437c3d9bc5d8 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Tue, 9 Dec 2025 10:29:42 +0000 Subject: [PATCH 04/39] Convert test beamlineParameters files --- .../domain/beamlineParameters | 216 ++--- .../plan_stubs/test_data/topup_long_delay.txt | 6 +- .../test_data/topup_short_params.txt | 6 +- tests/test_data/i04_beamlineParameters | 786 +++++++----------- tests/test_data/test_beamline_parameters.txt | 483 +++++------ 5 files changed, 553 insertions(+), 944 deletions(-) diff --git a/tests/devices/test_daq_configuration/domain/beamlineParameters b/tests/devices/test_daq_configuration/domain/beamlineParameters index 0a783cd2f81..030fbcc9423 100644 --- a/tests/devices/test_daq_configuration/domain/beamlineParameters +++ b/tests/devices/test_daq_configuration/domain/beamlineParameters @@ -1,139 +1,77 @@ -# -# -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 +{ + "BLSE": "FB", + "BPFB": "FULL", + "DCM_Perp_Offset_FIXED": 25.6, + "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, + "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, + "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, + "miniap_x_SMALL_APERTURE": 2.43, + "miniap_y_SMALL_APERTURE": 48.974, + "miniap_z_SMALL_APERTURE": 15.8, + "sg_x_SMALL_APERTURE": 5.3375, + "sg_y_SMALL_APERTURE": -3.55, + "miniap_x_ROBOT_LOAD": 2.386, + "miniap_y_ROBOT_LOAD": 31.4, + "miniap_z_ROBOT_LOAD": 15.8, + "sg_x_ROBOT_LOAD": 5.25, + "sg_y_ROBOT_LOAD": 4.43, + "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, + "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, + "gon_x_SCIN_OUT_DISTANCE": 1.0, + "gon_x_SCIN_OUT_DISTANCE_smargon": 1, + "gon_y_SCIN_OUT_DISTANCE": 2.0, + "gon_z_SCIN_OUT_DISTANCE": -0.5, + "StandardEnergy": 12700, + "keyence_max_attempts": 1, + "keyence_slopeYToX": 2.5, + "keyence_slopeYToY": -2.5, + "keyence_slopeXToZ": 3.23, + "YAGSamX": 1022, + "YAGSamY": -98.0, + "YAGSamZ": -147, + "YAGOmega": 0.0, + "ipin_threshold": 0.1, + "mirror_threshold_bare_rh": 6900, + "mirror_threshold_rh_pt": 30000, + "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, + "fluorescence_analyser_deadtimeThreshold": 0.002, + "fluorescence_spectrum_deadtimeThreshold": 0.0005, + "fluorescence_attenuation_low_roi": 100, + "fluorescence_attenuation_high_roi": 2048, + "attenuation_optimisation_optimisation_cycles": 10, + "attenuation_optimisation_start_transmission": 0.1, + "fluorescence_mca_sca_offset": 400, + "attenuation_optimisation_multiplier": 2, + "attenuation_optimisation_target_count": 2000, + "attenuation_optimisation_upper_limit": 50000, + "attenuation_optimisation_lower_limit": 20000 +} diff --git a/tests/plan_stubs/test_data/topup_long_delay.txt b/tests/plan_stubs/test_data/topup_long_delay.txt index 41eb4100971..2f66fe87f06 100644 --- a/tests/plan_stubs/test_data/topup_long_delay.txt +++ b/tests/plan_stubs/test_data/topup_long_delay.txt @@ -1,2 +1,4 @@ -dodal_topup_threshold_exposure_s = 30 -dodal_topup_end_delay_s = 19 +{ + "dodal_topup_threshold_exposure_s": 30, + "dodal_topup_end_delay_s": 19 +} \ No newline at end of file diff --git a/tests/plan_stubs/test_data/topup_short_params.txt b/tests/plan_stubs/test_data/topup_short_params.txt index 497b0d790e4..7707e91a61f 100644 --- a/tests/plan_stubs/test_data/topup_short_params.txt +++ b/tests/plan_stubs/test_data/topup_short_params.txt @@ -1,2 +1,4 @@ -dodal_topup_threshold_exposure_s = 35 -dodal_topup_end_delay_s = 1 +{ + "dodal_topup_threshold_exposure_s": 35, + "dodal_topup_end_delay_s": 1 +} \ No newline at end of file diff --git a/tests/test_data/i04_beamlineParameters b/tests/test_data/i04_beamlineParameters index 87beab7f2cf..3e24a3e69f5 100644 --- a/tests/test_data/i04_beamlineParameters +++ b/tests/test_data/i04_beamlineParameters @@ -1,503 +1,283 @@ -# -# -BeamLine BL04I - -## BLSE=FB switches between scan alignment and feedback alignment -## by creating bl energy scannable with beamLineSpecificEnergy_FB -## after changing you must restart servers or >>> reset_namespace -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 /dls_sw/i04/software/gda/mx-config/scripts/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 I04 XBPM feedback EDM screen in Synoptic - -DCM_Perp_Offset_FIXED = 25.75 - -# -# beamstop -# -parked_x = 4.98 #4.48 -parked_y =-49.1 -parked_z = -49.3 -parked_z_robot = 49.50 #55, 17/11/2020 value changed see Jira I04-421 - -in_beam_z_MIN_START_POS = 49.5 #40.0 - -in_beam_x_STANDARD = -2.7 #-2.8 -in_beam_y_STANDARD = 44.99 #44.98 #44.96 #44.95 #44.95 #44.64 #44.645 #44.63 #44.64 #44.68 #44.53 # 45.00 (11/Oct/2023) -in_beam_z_STANDARD = 25.0 - -in_beam_x_HIGHRES = -2.7 #2.50 #-3.84 -in_beam_y_HIGHRES = 44.99 #44.97 #44.96 #44.95 #44.95 #44.60 #44.61 #44.645 #44.63 #44.64 #44.68 #44.65(11/Oct/2023) -# #in_beam_z_HIGHRES = 12 -# # this is used for fluo spectra; original distance 0f 12.0 gives W contamination - in_beam_z_HIGHRES = 25.0 - - -in_beam_x_LOWRES = -2.75 -in_beam_y_LOWRES = 44.93 #44.92 #44.89 #44.90 #44.53 #44.55 #44.58 #474.57 #44.58 #44.61 #44.59 #44.48 (09/Oct/2023) -in_beam_z_LOWRES = 49.50 - - -## in_beam_col_tilt = -120.0 ## what is this????; This refers to the old end station and is no longer needed (RF) - -checkCryoy=Yes -#If is to be moved in by the script. If not Yes then control is handed to the robot on activate script -#To force the cryojet run hutch_utilities.hutch.forceCryoOut() -manualCryojet=Yes - -############################################################################### -# # -# 2015-07-03 - values to use during miniAPY failure # -# with no scatterguard or aperture during this period # -# # -############################################################################### -#Aperture - Scatterguard positions new block with 200, 20 and 10 micron ap's -#200 micron ap -#miniap_x_LARGE_APERTURE=-4.0 -#miniap_y_LARGE_APERTURE=-48.95 -#miniap_z_LARGE_APERTURE=-12.0 -#sg_x_LARGE_APERTURE=-3.0 -#sg_y_LARGE_APERTURE=-4.4 - - -# 20 micron ap - new block with 200, 20 and 10 micron ap's - -#miniap_x_MEDIUM_APERTURE=-4.0 -#miniap_y_MEDIUM_APERTURE=-48.95 -#miniap_z_MEDIUM_APERTURE=-12.0 -#sg_x_MEDIUM_APERTURE=-3.0 -#sg_y_MEDIUM_APERTURE=-4.4 - -# 10 micron ap - new block with 200, 20 and 10 micron ap's - REALLY 20 um as miniap_y cannot reach its position for 10 um -#miniap_x_SMALL_APERTURE=-4.0 -#miniap_y_SMALL_APERTURE=-48.95 -#miniap_z_SMALL_APERTURE=-12.0 -#sg_x_SMALL_APERTURE=-3.0 -#sg_y_SMALL_APERTURE=-4.4 - -# Robot load -#miniap_x_ROBOT_LOAD=-4.0 -#miniap_y_ROBOT_LOAD=-48.95 -#miniap_z_ROBOT_LOAD=-12.0 -#sg_x_ROBOT_LOAD=-3.0 -#sg_y_ROBOT_LOAD=-4.4 - -# manual mount -#miniap_x_MANUAL_LOAD=-4.0 -#miniap_y_MANUAL_LOAD=-48.95 -#miniap_z_MANUAL_LOAD=-12.0 -#sg_x_MANUAL_LOAD=-3.0 -#sg_y_MANUAL_LOAD=-4.4 - - - - -############################################################################### -# 2015-01-19 - 200,20, 10 CRLS - set so to use 200 micron all the time # -# # -# 2015-07-03 - commented out until miniapY is fixed - values above to work # -# with no scatterguard or aperture during this period # -# # -############################################################################### -#Aperture - Scatterguard positions new block with 200, 20 and 10 micron ap's -#200 micron ap updated 2023-04-26 -miniap_x_LARGE_APERTURE= 4.10 #4.13 # until March 2023 4.38 #4.34 #4.35 #4.34 #3.65 # 4.29 #4.500 #4.6843 #4.717 #4.7 -miniap_y_LARGE_APERTURE= 41.13 #41.14 #until March 2023 41.88 #41.81 #41.86 #41.88 #41.8184 #41.25 #41.5384 #41.801 #42.155 #40.7385 -miniap_z_LARGE_APERTURE=16.9 -sg_x_LARGE_APERTURE= 4.51 #4.66 #4.78 #4.800 #4.8 #4.4782 #4.85 #3.9 -sg_y_LARGE_APERTURE= 4.53 #4.637 #4.682 #4.137 #3.6589 #3.68 #3.4 - - -# 20 micron ap - new block with 200, 20 and 10 micron ap's - -miniap_x_MEDIUM_APERTURE=4.303 #4.65 #4.607 -miniap_y_MEDIUM_APERTURE=45.245 #46.168 #44.746 -miniap_z_MEDIUM_APERTURE=16.9 -sg_x_MEDIUM_APERTURE=4.04 #4.85 #3.88 -sg_y_MEDIUM_APERTURE=0.15 - -# 10 micron ap - new block with 200, 20 and 10 micron ap's - REALLY 20 um as miniap_y cannot reach its position for 10 um -miniap_x_SMALL_APERTURE=4.3 #4.605 #4.61 -miniap_y_SMALL_APERTURE=49.765 #50.13 -miniap_z_SMALL_APERTURE=16.9 -sg_x_SMALL_APERTURE=4.85 #3.9 -sg_y_SMALL_APERTURE=-4.25 #3.35 - - -# Robot load, see Jira ticket I04-421 -miniap_x_ROBOT_LOAD=-4.0 # -4.9 -miniap_y_ROBOT_LOAD=24.9 #0.0 #-48.95 #0.0 -miniap_z_ROBOT_LOAD=16.9 -sg_x_ROBOT_LOAD=-3.0 #-4.9 -sg_y_ROBOT_LOAD=-4.4 - -# manual mount -miniap_x_MANUAL_LOAD=-4.0 # -4.9 -miniap_y_MANUAL_LOAD=-48.95 #-49 -miniap_z_MANUAL_LOAD=-12. -sg_x_MANUAL_LOAD=-3.0 #-4.9 -sg_y_MANUAL_LOAD=-4.4 - -miniap_x_SCIN_MOVE=-4.0 # -4.9 -sg_x_SCIN_MOVE=-3.0 # -4.9 - -###I04 Scintillator### -scin_y_SCIN_IN= 97.45 #97.25 #97.1 #96.22 #93.42 #96.92 -scin_y_SCIN_OUT=-0.1 #-0.8 , 17/11/2020 value changed see Jira I04-421 -scin_z_SCIN_IN= 93.8 #93.81 #93.87 #93.97 # 15-11-22 Home done, scan scin z value -scin_z_SCIN_OUT=0.2 - -###Tomography Scintillator### -#scin_y_SCIN_IN=102.0 -#scin_y_SCIN_OUT=-0.1 -#scin_z_SCIN_IN=99.17 -#scin_z_SCIN_OUT=0.2 - - -#distance to move gonx,y,z when scintillator is put in with standard pins -#gon_x_SCIN_OUT_DISTANCE=0.5 -#use with mini kappa: -#gon_x_SCIN_OUT_DISTANCE_kappa = 1.5 - -# For SmarGon: -gon_x_SCIN_OUT_DISTANCE_smargon = 1 - -#Required for single axis because _smargon won't be used -#gon_x_SCIN_OUT_DISTANCE=1.0 - -# -gon_y_SCIN_OUT_DISTANCE=2 -gon_z_SCIN_OUT_DISTANCE=-1.5 - -# For SmarGon with EM Grid holder (13-03-2018): -#gon_x_SCIN_OUT_DISTANCE_smargon = 0 -## -#gon_y_SCIN_OUT_DISTANCE=0 -#gon_z_SCIN_OUT_DISTANCE=0 - - - -#distance to move gonx,y,z when scintillator is put in with crosshair wire mounted -#gon_x_SCIN_OUT_DISTANCE=-7 -#gon_y_SCIN_OUT_DISTANCE=0 -#gon_z_SCIN_OUT_DISTANCE=0 - - -#CASS motor position tolerances (mm) -miniap_x_tolerance=0.001 -miniap_y_tolerance=0.001 -miniap_z_tolerance=0.1 -sg_x_tolerance=0.1 -sg_y_tolerance=0.1 -scin_y_tolerance=1.2 -scin_z_tolerance=0.1 -gon_x_tolerance=0.01 -gon_y_tolerance=0.1 -gon_z_tolerance=0.001 -bs_x_tolerance=0.005 -bs_y_tolerance=0.005 -bs_z_tolerance=0.2 -crl_x_tolerance=0.01 -crl_y_tolerance=0.01 -crl_pitch_tolerance=0.01 -crl_yaw_tolerance=0.01 -sg_y_up_movement_tolerance=1.0 - -sg_x_timeout=10 -sg_y_timeout=10 -miniap_x_timeout=10 -miniap_y_timeout=80 -gon_x_timeout=60 -gon_y_timeout=30 -gon_z_timeout=30 -crl_x_timeout=120 -crl_y_timeout=10 -crl_pitch_timeout=10 -crl_yaw_timeout=10 - -## CRL positions for low and high energy lens sets. Should deliver beam to same position on scintillator. -## Normally should only adjust the low energy set to match the position of the high energy that you've -## already checked on the scintillator screen. - -#crl_x_LOWE=-7.337 -#crl_y_LOWE=0.785 -#crl_pitch_LOWE=3.030 -#crl_yaw_LOWE=7.245 - -############################################################################################ -# All values set to NOCRL position to avoid CRL being moved in beam when energy is changed -# until GDA bug is fixed -############################################################################################ - -crl_x_LOWE=0.0 -crl_y_LOWE=0.8277 -crl_pitch_LOWE=3.0065 -crl_yaw_LOWE=7.1015 - -crl_x_NOCRL = 0.0 -crl_y_NOCRL = 0.8277 -crl_pitch_NOCRL= 3.0065 -crl_yaw_NOCRL = 7.1015 - -crl_x_HIGHE=0.0 -crl_y_HIGHE=0.8277 -crl_pitch_HIGHE=3.0065 -crl_yaw_HIGHE=7.1015 - -### Positions with Mirrors #### -#crl_x_LOWE=-7.5 -#crl_y_LOWE=-1.65 -#crl_pitch_LOWE=1.4 -#crl_yaw_LOWE=0.04 -# -#crl_x_NOCRL = 0.0 -#crl_y_NOCRL = 0.8277 -#crl_pitch_NOCRL= 3.0065 -#crl_yaw_NOCRL = 7.1015 -# -#crl_x_HIGHE=6.4 -#crl_y_HIGHE=-1.55 -#crl_pitch_HIGHE=0.74 -#crl_yaw_HIGHE=-1.555 -################################# - - -#Beam visualisation parameters -MinBackStopZ = 10.0 -BackStopYsafe = 20.0 -BackStopXyag = -17.95 -BackStopYyag = 24.05 -BackStopZyag = 18.0 -SampleYnormal = 2.65 -SampleYshift = 2.0 -parked_fluo_x=1.1 -#in_beam_fluo_x=1.0086 -#in_beam_fluo_x=-35.0 -in_beam_fluo_x=-40.0 -move_fluo = Yes -safe_det_z_default=1000 -safe_det_z_sampleChanger=333 -store_data_collections_in_ispyb=Yes -TakePNGsOfSample=Yes - -#robot requires these values -gonio_parked_x=0.0 -gonio_parked_y=0.0 -gonio_parked_z=0.0 -gonio_parked_omega=0 -gonio_parked_kappa = -7.5 -gonio_parked_chi = 0 -gonio_parked_phi = 0 - -col_inbeam_tolerance = 1.0 - -#Run 3 2015 - Set offsets to 0 at 12658eV on 25/6/2015 - see standing instruction -col_parked_tolerance=1.0 -col_parked_upstream_x=0.0 -col_parked_downstream_x=0.0 -col_parked_upstream_y=0.0 -col_parked_inboard_y=0.0 -col_parked_outboard_y=0.0 - - -# The following used by setupBeamLine script -setupBeamLine_energyStart = 6000. -setupBeamLine_energyEnd = 18000. -setupBeamLine_energyStep = 500. -setupBeamLine_rollStart = -1.95 -setupBeamLine_rollEnd = -1.55 -setupBeamLine_rollSteps = 80 -setupBeamLine_pitchStart = -0.65 -setupBeamLine_pitchEnd = -0.45 -setupBeamLine_pitchSteps = 200 -#values below in microns -beamXCentre=0. -beamYCentre=0. -beamXYSettleTime=6.0 -beamXYTolerance=5.0 -DataCollection_TurboMode=Yes -#time in seconds. If not set then the default is 0.1 - -#The following are used by beamLineenergy script -beamLineEnergy_rollBeamX = 100 -beamLineEnergy_rollBeamY = 400 -beamLineEnergy__rollWidth = .075 -beamLineEnergy__rollStep = .005 -beamLineEnergy__pitchWidth = .025 -beamLineEnergy__pitchStep = .001 -beamLineEnergy__fpitchWidth = .02 -beamLineEnergy__fpitchStep = .001 -beamLineEnergy__adjustSlits=Yes - -# "Beam stabilising, data collection will resume in " ... -dataCollectionMinSampleCurrent=-100 -dataCollectionSampleCurrent XBPM1Intensity - -#Mark is using the following in some test scripts -MinIPin = 1.0 -YAGPin = 1 -RotationAxisPin = 2 -PtPin = 3 -PowderPin = 4 - -#################################################################### -# I04 standard use settings -# -# Do Not Edit/Delete - Ralf - 31/1/2013 -# -# iPin In positions, Mark is going to try and use these in scripts -iPinInDetX = 31.52 -iPinInDetYaw = 1.4542 -iPinInDetY = 93.0 -iPinInDetZ = 200.0 -###################################################################### - - -#################################################################### -# -# iPin Out positions - for diffraction data collection with ADSC with CRLS -# -#DataCollectionDetY = 58.7 -#DataCollectionDetX = -42.5498 -#DataCollectionDetXUpstream = -26.9237 -#DataCollectionDetXDownstream = -57.8741 -#DataCollectionDetYaw = -37.32719 -#################################################################### - -#################################################################### -# -# iPin Out positions - for diffraction data collection with ADSC with Mirrors -# -DataCollectionDetY = 89.7 -DataCollectionDetX = 27.4 -DataCollectionDetXUpstream = 26.4 -DataCollectionDetXDownstream = 28.402 -DataCollectionDetYaw = 2.4132 -#################################################################### - -#################################################################### -## I04 tomography settings - PCO camera -# -# values updated 07/07/12 -# iPin In positions, Mark is going to try and use these in scripts -#iPinInDetX = 8.854 -#iPinInDetYaw = -30.0909 -#iPinInDetY = 315.2 -#iPinInDetZ = 300.0 -#################################################################### - - -# StandardEnergy on i04 is 12658eV -StandardEnergy=12658 - - -keyence_max_attempts=1 -#Keyence on YtoX and YtoY needs changing is using single axis -#See comment in I04-532 for details -keyence_slopeYToX=6.78 -keyence_slopeYToY=-6.72 -keyence_slopeXToZ=8.37 - - -# WITH MIRRORS # -#hfm_bare_vert = 5.0 -#hfm_bare_yaw = 0.0 -#hfm_bare_roll = 0.0 -#hfm_rh_vert = 5.0 -#hfm_rh_yaw = 0.0 -#hfm_rh_roll = 0.0 -#hfm_pt_vert = 5.0 -#hfm_pt_yaw = 0.0 -#hfm_pt_roll = 0.0 - -#vfm_bare_lat = 2.000 -#vfm_bare_yaw = 0.0 -#vfm_bare_roll = 0.0 -#vfm_rh_lat = 15.00 -#vfm_rh_yaw = 0.0 -#vfm_rh_roll = 0.0 -#vfm_pt_lat = -10 -#vfm_pt_yaw = 0.0 -#vfm_pt_roll = 0.0 - -# WITH CRLS # -hfm_bare_vert = -30 -hfm_bare_yaw = -30.0 -hfm_bare_roll = -30.0 -hfm_rh_vert = -30.0 -hfm_rh_yaw = -30.0 -hfm_rh_roll = -30.0 -hfm_pt_vert = -30.0 -hfm_pt_yaw = -30.0 -hfm_pt_roll = -30.0 - -vfm_bare_lat = 15 -vfm_bare_yaw = 15 -vfm_bare_roll = 15 -vfm_rh_lat = 15 -vfm_rh_yaw = 15 -vfm_rh_roll = 15 -vfm_pt_lat = 15 -vfm_pt_yaw = 15 -vfm_pt_roll = 15 - -# 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.0 -flux_factor_LARGE_APERTURE = 1.0 -flux_factor_MEDIUM_APERTURE = 0.11765 -flux_factor_SMALL_APERTURE = 0.00914 -flux_scale_factor = 0.372 - -# assuming gain 10^3 -#pin_diode_factor = 3.2E12 original -#from cross-calibration with calibrated diode -pin_diode_factor = 2.83E12 - -#ipin value must be < ipin_threshold above background for data collection -ipin_threshold = 0.1 - -# Predict flux by energy and beamsize settings #I04-521 -# N.B. Left most coefficient (at index 0 in the collection / array) is the quartic term, the right most coefficient is the zeroth order "offset" term -# UPDATED 2022/Jul/15 with data from redis key i04:energy_flux:lookup:20220714 - -flux_predict_polynomial_coefficients_5 = [-0.0000707134131045123, 7.0205491504418, -194299.6440518530, 1835805807.3974800, -3280251055671.100] -flux_predict_polynomial_coefficients_10 = [-0.0000294993821003877, 5.2802845275010, -169996.5290700170, 1715224280.7823100, -3138739154146.230] -flux_predict_polynomial_coefficients_15 = [-0.000116949636502, 9.7753003322588, -254199.7776101, 2389060415.280310, -5025997585036.5] -flux_predict_polynomial_coefficients_20 = [-0.000148647038038, 11.2819868214984, -279103.295297639, 2545953771.80574, -5238247429860.13] -flux_predict_polynomial_coefficients_30 = [-0.000116165765376, 9.94125586103289, -260734.485522517, 2447741129.31429, -4986276938582.08] -flux_predict_polynomial_coefficients_40 = [-0.000343179106809, 21.5410025335892, -476062.885598809, 4148019661.82909, -9657928196914.84] -flux_predict_polynomial_coefficients_50 = [-0.000131960426420, 10.8653440810523, -280456.000029892, 2613195448.12884, -5280016683595.84] -flux_predict_polynomial_coefficients_75 = [-0.000391735497188, 24.7767312725528, -553079.202372348, 4894987195.36134, -11870695542358.4] -flux_predict_polynomial_coefficients_100 = [-0.000644176658542, 38.0955904622075, -809187.061558403, 6988666352.26412, -17740487002411.2] - -flux_predict_polynomial_coefficients_undulator_singularity = [0.0000155500286383152,-0.003037473267702,1.89061626835703] -flux_predict_polynomial_energyranges_undulator_singularity = [[7365,9275],[11080,12995]] - -# Fluorescence/Vortex detector settings -attenuation_optimisation_type = deadtime # deadtime or total_counts - -#Deadtime settings -fluorescence_analyser_deadtimeThreshold=0.0015 # used by edge scans -fluorescence_spectrum_deadtimeThreshold=0.0010 # used by spectrum - -#Other settings -fluorescence_attenuation_low_roi = 100 -fluorescence_attenuation_high_roi = 2047 -attenuation_optimisation_optimisation_cycles = 10 -attenuation_optimisation_start_transmission = 1 # per cent -fluorescence_mca_sca_offset = 200 - -#Total count settings -attenuation_optimisation_multiplier = 2 -attenuation_optimisation_target_count = 28000 -attenuation_optimisation_upper_limit = 50000 -attenuation_optimisation_lower_limit = 20000 +{ + "BLSE": "FB", + "BPFB": "FULL", + "DCM_Perp_Offset_FIXED": 25.75, + "parked_x": 4.98, + "parked_y": -49.1, + "parked_z": -49.3, + "parked_z_robot": 49.5, + "in_beam_z_MIN_START_POS": 49.5, + "in_beam_x_STANDARD": -2.7, + "in_beam_y_STANDARD": 44.99, + "in_beam_z_STANDARD": 25.0, + "in_beam_x_HIGHRES": -2.7, + "in_beam_y_HIGHRES": 44.99, + "in_beam_z_HIGHRES": 25.0, + "in_beam_x_LOWRES": -2.75, + "in_beam_y_LOWRES": 44.93, + "in_beam_z_LOWRES": 49.5, + "checkCryoy": true, + "manualCryojet": true, + "miniap_x_LARGE_APERTURE": 4.1, + "miniap_y_LARGE_APERTURE": 41.13, + "miniap_z_LARGE_APERTURE": 16.9, + "sg_x_LARGE_APERTURE": 4.51, + "sg_y_LARGE_APERTURE": 4.53, + "miniap_x_MEDIUM_APERTURE": 4.303, + "miniap_y_MEDIUM_APERTURE": 45.245, + "miniap_z_MEDIUM_APERTURE": 16.9, + "sg_x_MEDIUM_APERTURE": 4.04, + "sg_y_MEDIUM_APERTURE": 0.15, + "miniap_x_SMALL_APERTURE": 4.3, + "miniap_y_SMALL_APERTURE": 49.765, + "miniap_z_SMALL_APERTURE": 16.9, + "sg_x_SMALL_APERTURE": 4.85, + "sg_y_SMALL_APERTURE": -4.25, + "miniap_x_ROBOT_LOAD": -4.0, + "miniap_y_ROBOT_LOAD": 24.9, + "miniap_z_ROBOT_LOAD": 16.9, + "sg_x_ROBOT_LOAD": -3.0, + "sg_y_ROBOT_LOAD": -4.4, + "miniap_x_MANUAL_LOAD": -4.0, + "miniap_y_MANUAL_LOAD": -48.95, + "miniap_z_MANUAL_LOAD": -12.0, + "sg_x_MANUAL_LOAD": -3.0, + "sg_y_MANUAL_LOAD": -4.4, + "miniap_x_SCIN_MOVE": -4.0, + "sg_x_SCIN_MOVE": -3.0, + "scin_y_SCIN_IN": 97.45, + "scin_y_SCIN_OUT": -0.1, + "scin_z_SCIN_IN": 93.8, + "scin_z_SCIN_OUT": 0.2, + "gon_x_SCIN_OUT_DISTANCE_smargon": 1, + "gon_y_SCIN_OUT_DISTANCE": 2, + "gon_z_SCIN_OUT_DISTANCE": -1.5, + "miniap_x_tolerance": 0.001, + "miniap_y_tolerance": 0.001, + "miniap_z_tolerance": 0.1, + "sg_x_tolerance": 0.1, + "sg_y_tolerance": 0.1, + "scin_y_tolerance": 1.2, + "scin_z_tolerance": 0.1, + "gon_x_tolerance": 0.01, + "gon_y_tolerance": 0.1, + "gon_z_tolerance": 0.001, + "bs_x_tolerance": 0.005, + "bs_y_tolerance": 0.005, + "bs_z_tolerance": 0.2, + "crl_x_tolerance": 0.01, + "crl_y_tolerance": 0.01, + "crl_pitch_tolerance": 0.01, + "crl_yaw_tolerance": 0.01, + "sg_y_up_movement_tolerance": 1.0, + "sg_x_timeout": 10, + "sg_y_timeout": 10, + "miniap_x_timeout": 10, + "miniap_y_timeout": 80, + "gon_x_timeout": 60, + "gon_y_timeout": 30, + "gon_z_timeout": 30, + "crl_x_timeout": 120, + "crl_y_timeout": 10, + "crl_pitch_timeout": 10, + "crl_yaw_timeout": 10, + "crl_x_LOWE": 0.0, + "crl_y_LOWE": 0.8277, + "crl_pitch_LOWE": 3.0065, + "crl_yaw_LOWE": 7.1015, + "crl_x_NOCRL": 0.0, + "crl_y_NOCRL": 0.8277, + "crl_pitch_NOCRL": 3.0065, + "crl_yaw_NOCRL": 7.1015, + "crl_x_HIGHE": 0.0, + "crl_y_HIGHE": 0.8277, + "crl_pitch_HIGHE": 3.0065, + "crl_yaw_HIGHE": 7.1015, + "MinBackStopZ": 10.0, + "BackStopYsafe": 20.0, + "BackStopXyag": -17.95, + "BackStopYyag": 24.05, + "BackStopZyag": 18.0, + "SampleYnormal": 2.65, + "SampleYshift": 2.0, + "parked_fluo_x": 1.1, + "in_beam_fluo_x": -40.0, + "move_fluo": true, + "safe_det_z_default": 1000, + "safe_det_z_sampleChanger": 333, + "store_data_collections_in_ispyb": true, + "TakePNGsOfSample": true, + "gonio_parked_x": 0.0, + "gonio_parked_y": 0.0, + "gonio_parked_z": 0.0, + "gonio_parked_omega": 0, + "gonio_parked_kappa": -7.5, + "gonio_parked_chi": 0, + "gonio_parked_phi": 0, + "col_inbeam_tolerance": 1.0, + "col_parked_tolerance": 1.0, + "col_parked_upstream_x": 0.0, + "col_parked_downstream_x": 0.0, + "col_parked_upstream_y": 0.0, + "col_parked_inboard_y": 0.0, + "col_parked_outboard_y": 0.0, + "setupBeamLine_energyStart": 6000.0, + "setupBeamLine_energyEnd": 18000.0, + "setupBeamLine_energyStep": 500.0, + "setupBeamLine_rollStart": -1.95, + "setupBeamLine_rollEnd": -1.55, + "setupBeamLine_rollSteps": 80, + "setupBeamLine_pitchStart": -0.65, + "setupBeamLine_pitchEnd": -0.45, + "setupBeamLine_pitchSteps": 200, + "beamXCentre": 0.0, + "beamYCentre": 0.0, + "beamXYSettleTime": 6.0, + "beamXYTolerance": 5.0, + "DataCollection_TurboMode": true, + "beamLineEnergy_rollBeamX": 100, + "beamLineEnergy_rollBeamY": 400, + "beamLineEnergy__rollWidth": 0.075, + "beamLineEnergy__rollStep": 0.005, + "beamLineEnergy__pitchWidth": 0.025, + "beamLineEnergy__pitchStep": 0.001, + "beamLineEnergy__fpitchWidth": 0.02, + "beamLineEnergy__fpitchStep": 0.001, + "beamLineEnergy__adjustSlits": true, + "dataCollectionMinSampleCurrent": -100, + "MinIPin": 1.0, + "YAGPin": 1, + "RotationAxisPin": 2, + "PtPin": 3, + "PowderPin": 4, + "iPinInDetX": 31.52, + "iPinInDetYaw": 1.4542, + "iPinInDetY": 93.0, + "iPinInDetZ": 200.0, + "DataCollectionDetY": 89.7, + "DataCollectionDetX": 27.4, + "DataCollectionDetXUpstream": 26.4, + "DataCollectionDetXDownstream": 28.402, + "DataCollectionDetYaw": 2.4132, + "StandardEnergy": 12658, + "keyence_max_attempts": 1, + "keyence_slopeYToX": 6.78, + "keyence_slopeYToY": -6.72, + "keyence_slopeXToZ": 8.37, + "hfm_bare_vert": -30, + "hfm_bare_yaw": -30.0, + "hfm_bare_roll": -30.0, + "hfm_rh_vert": -30.0, + "hfm_rh_yaw": -30.0, + "hfm_rh_roll": -30.0, + "hfm_pt_vert": -30.0, + "hfm_pt_yaw": -30.0, + "hfm_pt_roll": -30.0, + "vfm_bare_lat": 15, + "vfm_bare_yaw": 15, + "vfm_bare_roll": 15, + "vfm_rh_lat": 15, + "vfm_rh_yaw": 15, + "vfm_rh_roll": 15, + "vfm_pt_lat": 15, + "vfm_pt_yaw": 15, + "vfm_pt_roll": 15, + "mirror_threshold_bare_rh": 6900, + "mirror_threshold_rh_pt": 30000, + "flux_factor_LARGE_APERTURE": 1.0, + "flux_factor_MEDIUM_APERTURE": 0.11765, + "flux_factor_SMALL_APERTURE": 0.00914, + "flux_scale_factor": 0.372, + "pin_diode_factor": 2830000000000.0, + "ipin_threshold": 0.1, + "flux_predict_polynomial_coefficients_5": [ + -7.07134131045123e-05, + 7.0205491504418, + -194299.644051853, + 1835805807.39748, + -3280251055671.1 + ], + "flux_predict_polynomial_coefficients_10": [ + -2.94993821003877e-05, + 5.280284527501, + -169996.529070017, + 1715224280.78231, + -3138739154146.23 + ], + "flux_predict_polynomial_coefficients_15": [ + -0.000116949636502, + 9.7753003322588, + -254199.7776101, + 2389060415.28031, + -5025997585036.5 + ], + "flux_predict_polynomial_coefficients_20": [ + -0.000148647038038, + 11.2819868214984, + -279103.295297639, + 2545953771.80574, + -5238247429860.13 + ], + "flux_predict_polynomial_coefficients_30": [ + -0.000116165765376, + 9.94125586103289, + -260734.485522517, + 2447741129.31429, + -4986276938582.08 + ], + "flux_predict_polynomial_coefficients_40": [ + -0.000343179106809, + 21.5410025335892, + -476062.885598809, + 4148019661.82909, + -9657928196914.84 + ], + "flux_predict_polynomial_coefficients_50": [ + -0.00013196042642, + 10.8653440810523, + -280456.000029892, + 2613195448.12884, + -5280016683595.84 + ], + "flux_predict_polynomial_coefficients_75": [ + -0.000391735497188, + 24.7767312725528, + -553079.202372348, + 4894987195.36134, + -11870695542358.4 + ], + "flux_predict_polynomial_coefficients_100": [ + -0.000644176658542, + 38.0955904622075, + -809187.061558403, + 6988666352.26412, + -17740487002411.2 + ], + "flux_predict_polynomial_coefficients_undulator_singularity": [ + 1.55500286383152e-05, + -0.003037473267702, + 1.89061626835703 + ], + "flux_predict_polynomial_energyranges_undulator_singularity": [ + [ + 7365, + 9275 + ], + [ + 11080, + 12995 + ] + ], + "attenuation_optimisation_type": "deadtime", + "fluorescence_analyser_deadtimeThreshold": 0.0015, + "fluorescence_spectrum_deadtimeThreshold": 0.001, + "fluorescence_attenuation_low_roi": 100, + "fluorescence_attenuation_high_roi": 2047, + "attenuation_optimisation_optimisation_cycles": 10, + "attenuation_optimisation_start_transmission": 1, + "fluorescence_mca_sca_offset": 200, + "attenuation_optimisation_multiplier": 2, + "attenuation_optimisation_target_count": 28000, + "attenuation_optimisation_upper_limit": 50000, + "attenuation_optimisation_lower_limit": 20000 +} diff --git a/tests/test_data/test_beamline_parameters.txt b/tests/test_data/test_beamline_parameters.txt index 5247b15d3fb..b757968398c 100644 --- a/tests/test_data/test_beamline_parameters.txt +++ b/tests/test_data/test_beamline_parameters.txt @@ -1,298 +1,185 @@ -# -# -BeamLine BL03I - -## BLSE=FB switches between scan alignment and feedback alignment -## by creating bl energy scannable with beamLineSpecificEnergy_FB -## after changing you must restart servers or >>> reset_namespace -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 - -in_beam_x_HIGHRES = 1.52 -in_beam_y_HIGHRES = 44.78 -in_beam_z_HIGHRES = 30.0 - -in_beam_x_STANDARD = 1.52 -in_beam_y_STANDARD = 44.78 -in_beam_z_STANDARD = 30.0 - -in_beam_x_LOWRES = 1.52 -in_beam_y_LOWRES = 44.78 -in_beam_z_LOWRES = 48 - -checkCryojet = No -#If is to be moved in by the script. If not Yes then control is handed to the robot on activate script -#To force the cryojet run hutch_utilities.hutch.forceCryoOut() -manualCryojet = Yes - -######################################################### -############# All these need checking! ############ -######################################################### - -#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 - -#CASS motor position tolerances (mm) -miniap_x_tolerance = 0.004 -miniap_y_tolerance = 0.1 -miniap_z_tolerance = 0.1 -sg_x_tolerance = 0.1 -sg_y_tolerance = 0.1 -scin_y_tolerance = 0.1 -scin_z_tolerance = 0.12 -gon_x_tolerance = 0.01 -gon_y_tolerance = 0.1 -gon_z_tolerance = 0.001 -bs_x_tolerance = 0.02 -bs_y_tolerance = 0.005 -bs_z_tolerance = 0.3 -crl_x_tolerance = 0.01 -crl_y_tolerance = 0.01 -crl_pitch_tolerance = 0.01 -crl_yaw_tolerance = 0.01 -sg_y_up_movement_tolerance = 1.0 - -sg_x_timeout = 10 -sg_y_timeout = 10 -miniap_x_timeout = 60 -miniap_y_timeout = 10 -gon_x_timeout = 60 -gon_y_timeout = 30 -gon_z_timeout = 30 -crl_x_timeout = 10 -crl_y_timeout = 10 -crl_pitch_timeout = 10 -crl_yaw_timeout = 10 - -col_inbeam_tolerance = 1.0 - -# robot load collimation table reference positions (mm) -col_parked_tolerance = 1.0 -col_parked_upstream_x = 0.0 -col_parked_downstream_x = 0.0 -col_parked_upstream_y = 0.0 -col_parked_inboard_y = 0.0 -col_parked_outboard_y = 0.0 - -## CRL positions for low and high energy lens sets. Should deliver beam to same position on scintillator. -## Normally should only adjust the low energy set to match the position of the high energy that you've -## already checked on the scintillator screen. - -crl_x_LOWE = -11.78 -crl_y_LOWE = -4.3 -crl_pitch_LOWE = -4.75 -crl_yaw_LOWE = -1.0 - -crl_x_HIGHE = 2.22 -crl_y_HIGHE = -4.30 -crl_pitch_HIGHE = -2.75 -crl_yaw_HIGHE = 0 - - -######################################################### -########## End of new parameters ########### -######################################################### - - -#Beam visualisation parameters -MinBackStopZ = 30.0 -BackStopYsafe = 20.0 -BackStopXyag = -4.8 -BackStopYyag = 17.20 -BackStopZyag = 19.1 -SampleYnormal = 2.65 -SampleYshift = 2.0 -parked_fluo_x = -18.0 -in_beam_fluo_x = 12.0 -move_fluo = Yes -safe_det_z_default = 900 -safe_det_z_sampleChanger = 337 -store_data_collections_in_ispyb = Yes -TakePNGsOfSample = Yes - -#robot requires these values -gonio_parked_x = 0.0 -gonio_parked_y = 0.0 -gonio_parked_z = 0.0 -gonio_parked_omega = 0 -gonio_parked_chi = 0 -gonio_parked_phi = 0 - -# The following used by setupBeamLine script -setupBeamLine_energyStart = 7000.0 -setupBeamLine_energyEnd = 17000.0 -setupBeamLine_energyStep = 500 -setupBeamLine_rollStart = -4 -setupBeamLine_rollEnd = 4 -setupBeamLine_rollSteps = 21 -setupBeamLine_pitchStart = -3.7 -setupBeamLine_pitchEnd = -3.5 -setupBeamLine_pitchSteps = 200 -#values below in microns -beamXCentre = 0 -beamYCentre = 0 -beamXYSettleTime = 6.0 -beamXYTolerance = 5.0 -DataCollection_TurboMode = Yes -#time in seconds. If not set then the default is 0.1 - -#The following are used by beamLineenergy script -beamLineEnergy_rollBeamX 50 -beamLineEnergy_rollBeamY 200 -beamLineEnergy__rollWidth = .2 -beamLineEnergy__rollStep = .02 -beamLineEnergy__pitchWidth = .02 -beamLineEnergy__pitchStep = .002 -beamLineEnergy__fpitchWidth = .02 -beamLineEnergy__fpitchStep = .001 -beamLineEnergy__adjustSlits = No -#dataCollectionMinSampleCurrent = 0.245 -dataCollectionMinSampleCurrent = 0.000 -dataCollectionSampleCurrent qbpm3 - -#Mark is using the following in some test scripts -MinIPin = 1.0 -YAGPin = 1 -RotationAxisPin = 2 -PtPin = 3 -PowderPin = 4 - -iPinInDetZ = 340.0 - -DataCollectionDetX = -7.8504 -DataCollectionDetYaw = 6.499 -DataCollectionDetY = 48.0 - -# 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 - -# assuming gain 10^3 -pin_diode_factor = 2.66E19 - -# Fluorescence/Vortex detector settings -attenuation_optimisation_type = deadtime # deadtime or total_counts - -#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 +{ + "BLSE": "FB", + "BPFB": "FULL", + "DCM_Perp_Offset_FIXED": 25.6, + "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, + "in_beam_x_HIGHRES": 1.52, + "in_beam_y_HIGHRES": 44.78, + "in_beam_z_HIGHRES": 30.0, + "in_beam_x_STANDARD": 1.52, + "in_beam_y_STANDARD": 44.78, + "in_beam_z_STANDARD": 30.0, + "in_beam_x_LOWRES": 1.52, + "in_beam_y_LOWRES": 44.78, + "in_beam_z_LOWRES": 48, + "checkCryojet": false, + "manualCryojet": true, + "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, + "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, + "miniap_x_SMALL_APERTURE": 2.43, + "miniap_y_SMALL_APERTURE": 48.974, + "miniap_z_SMALL_APERTURE": 15.8, + "sg_x_SMALL_APERTURE": 5.3375, + "sg_y_SMALL_APERTURE": -3.55, + "miniap_x_ROBOT_LOAD": 2.386, + "miniap_y_ROBOT_LOAD": 31.4, + "miniap_z_ROBOT_LOAD": 15.8, + "sg_x_ROBOT_LOAD": 5.25, + "sg_y_ROBOT_LOAD": 4.43, + "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, + "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, + "gon_x_SCIN_OUT_DISTANCE": 1.0, + "gon_x_SCIN_OUT_DISTANCE_smargon": 1, + "gon_y_SCIN_OUT_DISTANCE": 2.0, + "gon_z_SCIN_OUT_DISTANCE": -0.5, + "miniap_x_tolerance": 0.004, + "miniap_y_tolerance": 0.1, + "miniap_z_tolerance": 0.1, + "sg_x_tolerance": 0.1, + "sg_y_tolerance": 0.1, + "scin_y_tolerance": 0.1, + "scin_z_tolerance": 0.12, + "gon_x_tolerance": 0.01, + "gon_y_tolerance": 0.1, + "gon_z_tolerance": 0.001, + "bs_x_tolerance": 0.02, + "bs_y_tolerance": 0.005, + "bs_z_tolerance": 0.3, + "crl_x_tolerance": 0.01, + "crl_y_tolerance": 0.01, + "crl_pitch_tolerance": 0.01, + "crl_yaw_tolerance": 0.01, + "sg_y_up_movement_tolerance": 1.0, + "sg_x_timeout": 10, + "sg_y_timeout": 10, + "miniap_x_timeout": 60, + "miniap_y_timeout": 10, + "gon_x_timeout": 60, + "gon_y_timeout": 30, + "gon_z_timeout": 30, + "crl_x_timeout": 10, + "crl_y_timeout": 10, + "crl_pitch_timeout": 10, + "crl_yaw_timeout": 10, + "col_inbeam_tolerance": 1.0, + "col_parked_tolerance": 1.0, + "col_parked_upstream_x": 0.0, + "col_parked_downstream_x": 0.0, + "col_parked_upstream_y": 0.0, + "col_parked_inboard_y": 0.0, + "col_parked_outboard_y": 0.0, + "crl_x_LOWE": -11.78, + "crl_y_LOWE": -4.3, + "crl_pitch_LOWE": -4.75, + "crl_yaw_LOWE": -1.0, + "crl_x_HIGHE": 2.22, + "crl_y_HIGHE": -4.3, + "crl_pitch_HIGHE": -2.75, + "crl_yaw_HIGHE": 0, + "MinBackStopZ": 30.0, + "BackStopYsafe": 20.0, + "BackStopXyag": -4.8, + "BackStopYyag": 17.2, + "BackStopZyag": 19.1, + "SampleYnormal": 2.65, + "SampleYshift": 2.0, + "parked_fluo_x": -18.0, + "in_beam_fluo_x": 12.0, + "move_fluo": true, + "safe_det_z_default": 900, + "safe_det_z_sampleChanger": 337, + "store_data_collections_in_ispyb": true, + "TakePNGsOfSample": true, + "gonio_parked_x": 0.0, + "gonio_parked_y": 0.0, + "gonio_parked_z": 0.0, + "gonio_parked_omega": 0, + "gonio_parked_chi": 0, + "gonio_parked_phi": 0, + "setupBeamLine_energyStart": 7000.0, + "setupBeamLine_energyEnd": 17000.0, + "setupBeamLine_energyStep": 500, + "setupBeamLine_rollStart": -4, + "setupBeamLine_rollEnd": 4, + "setupBeamLine_rollSteps": 21, + "setupBeamLine_pitchStart": -3.7, + "setupBeamLine_pitchEnd": -3.5, + "setupBeamLine_pitchSteps": 200, + "beamXCentre": 0, + "beamYCentre": 0, + "beamXYSettleTime": 6.0, + "beamXYTolerance": 5.0, + "DataCollection_TurboMode": true, + "beamLineEnergy__rollWidth": 0.2, + "beamLineEnergy__rollStep": 0.02, + "beamLineEnergy__pitchWidth": 0.02, + "beamLineEnergy__pitchStep": 0.002, + "beamLineEnergy__fpitchWidth": 0.02, + "beamLineEnergy__fpitchStep": 0.001, + "beamLineEnergy__adjustSlits": false, + "dataCollectionMinSampleCurrent": 0.0, + "MinIPin": 1.0, + "YAGPin": 1, + "RotationAxisPin": 2, + "PtPin": 3, + "PowderPin": 4, + "iPinInDetZ": 340.0, + "DataCollectionDetX": -7.8504, + "DataCollectionDetYaw": 6.499, + "DataCollectionDetY": 48.0, + "StandardEnergy": 12700, + "keyence_max_attempts": 1, + "keyence_slopeYToX": 2.5, + "keyence_slopeYToY": -2.5, + "keyence_slopeXToZ": 3.23, + "YAGSamX": 1022, + "YAGSamY": -98.0, + "YAGSamZ": -147, + "YAGOmega": 0.0, + "ipin_threshold": 0.1, + "mirror_threshold_bare_rh": 6900, + "mirror_threshold_rh_pt": 30000, + "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, + "pin_diode_factor": 2.66e+19, + "attenuation_optimisation_type": "deadtime", + "fluorescence_analyser_deadtimeThreshold": 0.002, + "fluorescence_spectrum_deadtimeThreshold": 0.0005, + "fluorescence_attenuation_low_roi": 100, + "fluorescence_attenuation_high_roi": 2048, + "attenuation_optimisation_optimisation_cycles": 10, + "attenuation_optimisation_start_transmission": 0.1, + "fluorescence_mca_sca_offset": 400, + "attenuation_optimisation_multiplier": 2, + "attenuation_optimisation_target_count": 2000, + "attenuation_optimisation_upper_limit": 50000, + "attenuation_optimisation_lower_limit": 20000 +} From acd5805f0e9eb2c9bb6b17a2be06bdd0a6b7bae8 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Tue, 9 Dec 2025 10:29:52 +0000 Subject: [PATCH 05/39] Fix test --- tests/plan_stubs/test_topup_plan.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/plan_stubs/test_topup_plan.py b/tests/plan_stubs/test_topup_plan.py index 5be58415278..5ca1e7711b7 100644 --- a/tests/plan_stubs/test_topup_plan.py +++ b/tests/plan_stubs/test_topup_plan.py @@ -26,6 +26,10 @@ async def synchrotron() -> Synchrotron: @patch("dodal.plan_stubs.check_topup.wait_for_topup_complete") @patch("dodal.plan_stubs.check_topup.bps.sleep") +@patch( + "dodal.common.beamlines.beamline_parameters.BEAMLINE_PARAMETER_PATHS", + {"i03": TEST_BEAMLINE_PARAMETERS_TXT}, +) def test_when_topup_before_end_of_collection_wait( fake_sleep: MagicMock, fake_wait: MagicMock, From 28b7823a597aa95279a2e4f95d9f5387d9e0d11d Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Tue, 9 Dec 2025 10:53:55 +0000 Subject: [PATCH 06/39] Fix lint --- src/dodal/common/beamlines/beamline_parameters.py | 4 +--- tests/common/beamlines/test_beamline_parameters.py | 1 - tests/plan_stubs/test_data/topup_long_delay.txt | 2 +- tests/plan_stubs/test_data/topup_short_params.txt | 2 +- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/dodal/common/beamlines/beamline_parameters.py b/src/dodal/common/beamlines/beamline_parameters.py index 31f76b06c37..a29856f0460 100644 --- a/src/dodal/common/beamlines/beamline_parameters.py +++ b/src/dodal/common/beamlines/beamline_parameters.py @@ -1,9 +1,7 @@ -import ast -from typing import Any, cast +from typing import Any from daq_config_server.client import ConfigServer -from dodal.log import LOGGER from dodal.utils import get_beamline_name BEAMLINE_PARAMETER_KEYWORDS = ["FB", "FULL", "deadtime"] diff --git a/tests/common/beamlines/test_beamline_parameters.py b/tests/common/beamlines/test_beamline_parameters.py index 3af042869fb..b0e6132ed6a 100644 --- a/tests/common/beamlines/test_beamline_parameters.py +++ b/tests/common/beamlines/test_beamline_parameters.py @@ -8,7 +8,6 @@ get_beamline_parameters, ) from tests.test_data import ( - BAD_BEAMLINE_PARAMETERS, I04_BEAMLINE_PARAMETERS, TEST_BEAMLINE_PARAMETERS_TXT, ) diff --git a/tests/plan_stubs/test_data/topup_long_delay.txt b/tests/plan_stubs/test_data/topup_long_delay.txt index 2f66fe87f06..9a036944631 100644 --- a/tests/plan_stubs/test_data/topup_long_delay.txt +++ b/tests/plan_stubs/test_data/topup_long_delay.txt @@ -1,4 +1,4 @@ { "dodal_topup_threshold_exposure_s": 30, "dodal_topup_end_delay_s": 19 -} \ No newline at end of file +} diff --git a/tests/plan_stubs/test_data/topup_short_params.txt b/tests/plan_stubs/test_data/topup_short_params.txt index 7707e91a61f..0bbcbd9fa23 100644 --- a/tests/plan_stubs/test_data/topup_short_params.txt +++ b/tests/plan_stubs/test_data/topup_short_params.txt @@ -1,4 +1,4 @@ { "dodal_topup_threshold_exposure_s": 35, "dodal_topup_end_delay_s": 1 -} \ No newline at end of file +} From 84f08367d135255521fc59601f9fc2b6786f2896 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Mon, 12 Jan 2026 16:33:11 +0000 Subject: [PATCH 07/39] Fix --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 00c15d98876..eeede34f328 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,7 +9,7 @@ from unittest.mock import MagicMock, patch import pytest -from daq_config_server.converters.models import ConfigModel +from daq_config_server.models import ConfigModel from ophyd_async.core import ( PathProvider, ) From afd4fbc552b8a44a8bf2ceae1f5a5d7892077182 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Mon, 12 Jan 2026 16:47:39 +0000 Subject: [PATCH 08/39] Require latest daq-config-server --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 545b7399444..15f0a841825 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ dependencies = [ "scanspec>=0.7.3", "pyzmq==26.3.0", # Until we can move to RHEL 8 https://github.com/DiamondLightSource/mx-bluesky/issues/1139 "deepdiff", - "daq-config-server>=v1.0.0", # For getting Configuration settings. + "daq-config-server>=v1.1.2", # For getting Configuration settings. ] dynamic = ["version"] From 2d92790b1133ee47e9fea1d036608dbb751fee7a Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Tue, 20 Jan 2026 15:42:16 +0000 Subject: [PATCH 09/39] PR comments WIP --- src/dodal/common/beamlines/beamline_parameters.py | 6 ++++-- tests/conftest.py | 2 -- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dodal/common/beamlines/beamline_parameters.py b/src/dodal/common/beamlines/beamline_parameters.py index a29856f0460..d6632013a92 100644 --- a/src/dodal/common/beamlines/beamline_parameters.py +++ b/src/dodal/common/beamlines/beamline_parameters.py @@ -4,8 +4,10 @@ from dodal.utils import get_beamline_name -BEAMLINE_PARAMETER_KEYWORDS = ["FB", "FULL", "deadtime"] - +BEAMLINE_CONFIG_SERVER_ENDPOINTS = { + "i03": "https://daq-config.diamond.ac.uk", + "i04": "https://daq-config.diamond.ac.uk", +} BEAMLINE_PARAMETER_PATHS = { "i03": "/dls_sw/i03/software/daq_configuration/domain/beamlineParameters", "i04": "/dls_sw/i04/software/daq_configuration/domain/beamlineParameters", diff --git a/tests/conftest.py b/tests/conftest.py index eeede34f328..708c4aff340 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -180,11 +180,9 @@ def _fake_config_server_get_file_contents( # Minimal logic required for unit tests with filepath.open("r") as f: contents = f.read() - print(contents) if desired_return_type is str: return contents elif desired_return_type is dict: - print("return type is dict") return json.loads(contents) elif issubclass(desired_return_type, ConfigModel): # type: ignore return desired_return_type.model_validate(json.loads(contents)) From e3f6fadbf2f2be36c182dea394089fda17c3cdaa Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Fri, 23 Jan 2026 13:35:31 +0000 Subject: [PATCH 10/39] Parameterise config server URL --- src/dodal/common/beamlines/beamline_parameters.py | 11 +++++++---- tests/common/beamlines/test_beamline_parameters.py | 4 ++-- tests/devices/mx_phase1/test_beamstop.py | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/dodal/common/beamlines/beamline_parameters.py b/src/dodal/common/beamlines/beamline_parameters.py index d6632013a92..d472cd6663f 100644 --- a/src/dodal/common/beamlines/beamline_parameters.py +++ b/src/dodal/common/beamlines/beamline_parameters.py @@ -27,8 +27,8 @@ def __getitem__(self, item: str): return self.params[item] @classmethod - def from_file(cls, path: str): - config_server = ConfigServer(url="https://daq-config.diamond.ac.uk") + def from_server(cls, path: str, url="https://daq-config.diamond.ac.uk"): + config_server = ConfigServer(url=url) return cls( params=config_server.get_file_contents(path, dict, reset_cached_result=True) ) @@ -37,11 +37,14 @@ def from_file(cls, path: str): def get_beamline_parameters(beamline_param_path: str | None = None): """Loads the beamline parameters from the specified path, or according to the environment variable if none is given""" + beamline_name = get_beamline_name("i03") + config_server_url = BEAMLINE_CONFIG_SERVER_ENDPOINTS.get( + beamline_name, "https://daq-config.diamond.ac.uk" + ) if not beamline_param_path: - beamline_name = get_beamline_name("i03") 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) + return GDABeamlineParameters.from_server(beamline_param_path, config_server_url) diff --git a/tests/common/beamlines/test_beamline_parameters.py b/tests/common/beamlines/test_beamline_parameters.py index b0e6132ed6a..401272b604e 100644 --- a/tests/common/beamlines/test_beamline_parameters.py +++ b/tests/common/beamlines/test_beamline_parameters.py @@ -14,7 +14,7 @@ def test_beamline_parameters(): - params = GDABeamlineParameters.from_file(TEST_BEAMLINE_PARAMETERS_TXT) + params = GDABeamlineParameters.from_server(TEST_BEAMLINE_PARAMETERS_TXT) assert params["sg_x_MEDIUM_APERTURE"] == 5.285 assert params["col_parked_downstream_x"] == 0 assert params["beamLineEnergy__pitchStep"] == 0.002 @@ -23,7 +23,7 @@ def test_beamline_parameters(): def test_i03_beamline_parameters(): - params = GDABeamlineParameters.from_file(I04_BEAMLINE_PARAMETERS) + params = GDABeamlineParameters.from_server(I04_BEAMLINE_PARAMETERS) assert params["flux_predict_polynomial_coefficients_5"] == [ -0.0000707134131045123, 7.0205491504418, diff --git a/tests/devices/mx_phase1/test_beamstop.py b/tests/devices/mx_phase1/test_beamstop.py index 2b47ae91d61..155502b1cbc 100644 --- a/tests/devices/mx_phase1/test_beamstop.py +++ b/tests/devices/mx_phase1/test_beamstop.py @@ -15,7 +15,7 @@ @pytest.fixture def beamline_parameters() -> GDABeamlineParameters: - return GDABeamlineParameters.from_file(TEST_BEAMLINE_PARAMETERS_TXT) + return GDABeamlineParameters.from_server(TEST_BEAMLINE_PARAMETERS_TXT) @pytest.mark.parametrize( From 3dbb8815bf9b871335b277bf6ad9f9dc25d95cbb Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Fri, 23 Jan 2026 16:59:24 +0000 Subject: [PATCH 11/39] Refactor get_beamline_parameters --- src/dodal/beamlines/i03.py | 6 +- src/dodal/beamlines/i04.py | 6 +- .../common/beamlines/beamline_parameters.py | 43 +++-------- src/dodal/common/beamlines/config_client.py | 13 ++++ src/dodal/devices/aperturescatterguard.py | 8 +- src/dodal/devices/i03/undulator_dcm.py | 4 +- src/dodal/devices/mx_phase1/beamstop.py | 5 +- src/dodal/devices/scintillator.py | 4 +- src/dodal/plan_stubs/check_topup.py | 5 +- .../beamlines/test_beamline_parameters.py | 33 ++------- tests/devices/conftest.py | 73 +++++++++---------- tests/devices/mx_phase1/test_beamstop.py | 15 ++-- tests/devices/test_scintillator.py | 24 +++--- 13 files changed, 101 insertions(+), 138 deletions(-) create mode 100644 src/dodal/common/beamlines/config_client.py diff --git a/src/dodal/beamlines/i03.py b/src/dodal/beamlines/i03.py index 3987b4563d5..4a69d108328 100644 --- a/src/dodal/beamlines/i03.py +++ b/src/dodal/beamlines/i03.py @@ -95,7 +95,7 @@ def daq_configuration_path() -> str: @devices.factory() def aperture_scatterguard() -> ApertureScatterguard: - params = get_beamline_parameters() + params = get_beamline_parameters(BL) return ApertureScatterguard( aperture_prefix=f"{PREFIX.beamline_prefix}-MO-MAPT-01:", scatterguard_prefix=f"{PREFIX.beamline_prefix}-MO-SCAT-01:", @@ -116,7 +116,7 @@ def attenuator() -> BinaryFilterAttenuator: def beamstop() -> Beamstop: return Beamstop( prefix=f"{PREFIX.beamline_prefix}-MO-BS-01:", - beamline_parameters=get_beamline_parameters(), + beamline_parameters=get_beamline_parameters(BL), ) @@ -346,7 +346,7 @@ def scintillator(aperture_scatterguard: ApertureScatterguard) -> Scintillator: return Scintillator( f"{PREFIX.beamline_prefix}-MO-SCIN-01:", Reference(aperture_scatterguard), - get_beamline_parameters(), + get_beamline_parameters(BL), ) diff --git a/src/dodal/beamlines/i04.py b/src/dodal/beamlines/i04.py index 18cc611344b..c6ed15de735 100644 --- a/src/dodal/beamlines/i04.py +++ b/src/dodal/beamlines/i04.py @@ -99,7 +99,7 @@ def ipin() -> IPin: def beamstop() -> Beamstop: return Beamstop( f"{PREFIX.beamline_prefix}-MO-BS-01:", - beamline_parameters=get_beamline_parameters(), + beamline_parameters=get_beamline_parameters(BL), ) @@ -148,7 +148,7 @@ def backlight() -> Backlight: @devices.factory() def aperture_scatterguard() -> ApertureScatterguard: - params = get_beamline_parameters() + params = get_beamline_parameters(BL) return ApertureScatterguard( aperture_prefix=f"{PREFIX.beamline_prefix}-MO-MAPT-01:", scatterguard_prefix=f"{PREFIX.beamline_prefix}-MO-SCAT-01:", @@ -281,7 +281,7 @@ def scintillator(aperture_scatterguard: ApertureScatterguard) -> Scintillator: return Scintillator( f"{PREFIX.beamline_prefix}-MO-SCIN-01:", Reference(aperture_scatterguard), - get_beamline_parameters(), + get_beamline_parameters(BL), ) diff --git a/src/dodal/common/beamlines/beamline_parameters.py b/src/dodal/common/beamlines/beamline_parameters.py index d472cd6663f..77614acbcc6 100644 --- a/src/dodal/common/beamlines/beamline_parameters.py +++ b/src/dodal/common/beamlines/beamline_parameters.py @@ -1,50 +1,25 @@ from typing import Any -from daq_config_server.client import ConfigServer +from dodal.common.beamlines.config_client import get_config_client -from dodal.utils import get_beamline_name - -BEAMLINE_CONFIG_SERVER_ENDPOINTS = { - "i03": "https://daq-config.diamond.ac.uk", - "i04": "https://daq-config.diamond.ac.uk", -} BEAMLINE_PARAMETER_PATHS = { "i03": "/dls_sw/i03/software/daq_configuration/domain/beamlineParameters", "i04": "/dls_sw/i04/software/daq_configuration/domain/beamlineParameters", } -class GDABeamlineParameters: - params: dict[str, Any] - - def __init__(self, params: dict[str, Any]): - self.params = params - - def __repr__(self) -> str: - return repr(self.params) - - def __getitem__(self, item: str): - return self.params[item] - - @classmethod - def from_server(cls, path: str, url="https://daq-config.diamond.ac.uk"): - config_server = ConfigServer(url=url) - return cls( - params=config_server.get_file_contents(path, dict, reset_cached_result=True) - ) - - -def get_beamline_parameters(beamline_param_path: str | None = None): +def get_beamline_parameters( + beamline: str, beamline_param_path: str | None = None +) -> dict[str, Any]: """Loads the beamline parameters from the specified path, or according to the environment variable if none is given""" - beamline_name = get_beamline_name("i03") - config_server_url = BEAMLINE_CONFIG_SERVER_ENDPOINTS.get( - beamline_name, "https://daq-config.diamond.ac.uk" - ) if not beamline_param_path: - beamline_param_path = BEAMLINE_PARAMETER_PATHS.get(beamline_name) + beamline_param_path = BEAMLINE_PARAMETER_PATHS.get(beamline) if beamline_param_path is None: raise KeyError( "No beamline parameter path found, maybe 'BEAMLINE' environment variable is not set!" ) - return GDABeamlineParameters.from_server(beamline_param_path, config_server_url) + config_client = get_config_client(beamline) + return config_client.get_file_contents( + beamline_param_path, dict, reset_cached_result=True + ) diff --git a/src/dodal/common/beamlines/config_client.py b/src/dodal/common/beamlines/config_client.py new file mode 100644 index 00000000000..266c8b0214b --- /dev/null +++ b/src/dodal/common/beamlines/config_client.py @@ -0,0 +1,13 @@ +from daq_config_server.client import ConfigServer + +BEAMLINE_CONFIG_SERVER_ENDPOINTS = { + "i03": "https://daq-config.diamond.ac.uk", + "i04": "https://daq-config.diamond.ac.uk", +} + + +def get_config_client(beamline: str) -> ConfigServer: + url = BEAMLINE_CONFIG_SERVER_ENDPOINTS.get( + beamline, "https://daq-config.diamond.ac.uk" + ) + return ConfigServer(url=url) diff --git a/src/dodal/devices/aperturescatterguard.py b/src/dodal/devices/aperturescatterguard.py index 403000fb7da..5920976785f 100644 --- a/src/dodal/devices/aperturescatterguard.py +++ b/src/dodal/devices/aperturescatterguard.py @@ -2,6 +2,7 @@ import asyncio from math import inf +from typing import Any from bluesky.protocols import Preparable from ophyd_async.core import ( @@ -14,7 +15,6 @@ ) from pydantic import BaseModel, Field -from dodal.common.beamlines.beamline_parameters import GDABeamlineParameters from dodal.devices.aperture import Aperture from dodal.devices.motors import XYStage @@ -65,7 +65,7 @@ def values(self) -> tuple[float, float, float, float, float]: @staticmethod def tolerances_from_gda_params( - params: GDABeamlineParameters, + params: dict[str, Any], ) -> AperturePosition: return AperturePosition( aperture_x=params["miniap_x_tolerance"], @@ -79,7 +79,7 @@ def tolerances_from_gda_params( def from_gda_params( name: _GDAParamApertureValue, diameter: float, - params: GDABeamlineParameters, + params: dict[str, Any], ) -> AperturePosition: return AperturePosition( aperture_x=params[f"miniap_x_{name.value}"], @@ -109,7 +109,7 @@ def __str__(self): def load_positions_from_beamline_parameters( - params: GDABeamlineParameters, + params: dict[str, Any], ) -> dict[ApertureValue, AperturePosition]: return { ApertureValue.OUT_OF_BEAM: AperturePosition.from_gda_params( diff --git a/src/dodal/devices/i03/undulator_dcm.py b/src/dodal/devices/i03/undulator_dcm.py index 0973df2698d..1e4990b8060 100644 --- a/src/dodal/devices/i03/undulator_dcm.py +++ b/src/dodal/devices/i03/undulator_dcm.py @@ -7,6 +7,7 @@ from dodal.devices.i03.dcm import DCM from dodal.devices.undulator import UndulatorInKeV from dodal.log import LOGGER +from dodal.utils import get_beamline_name ENERGY_TIMEOUT_S: float = 30.0 @@ -49,7 +50,8 @@ def __init__( # 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.dcm_fixed_offset_mm = get_beamline_parameters( - daq_configuration_path + "/domain/beamlineParameters" + get_beamline_name("i03"), + daq_configuration_path + "/domain/beamlineParameters", )["DCM_Perp_Offset_FIXED"] super().__init__(name) diff --git a/src/dodal/devices/mx_phase1/beamstop.py b/src/dodal/devices/mx_phase1/beamstop.py index ede8f25a2ee..409065b8f79 100644 --- a/src/dodal/devices/mx_phase1/beamstop.py +++ b/src/dodal/devices/mx_phase1/beamstop.py @@ -1,5 +1,6 @@ import asyncio from math import isclose +from typing import Any from ophyd_async.core import ( StandardReadable, @@ -8,8 +9,6 @@ ) from ophyd_async.epics.motor import Motor -from dodal.common.beamlines.beamline_parameters import GDABeamlineParameters - _BEAMSTOP_OUT_DELTA_Y_MM = -2 @@ -48,7 +47,7 @@ class Beamstop(StandardReadable): def __init__( self, prefix: str, - beamline_parameters: GDABeamlineParameters, + beamline_parameters: dict[str, Any], name: str = "", ): with self.add_children_as_readables(): diff --git a/src/dodal/devices/scintillator.py b/src/dodal/devices/scintillator.py index bfa4506b1e7..baa6890abe6 100644 --- a/src/dodal/devices/scintillator.py +++ b/src/dodal/devices/scintillator.py @@ -1,9 +1,9 @@ from math import isclose +from typing import Any from ophyd_async.core import Reference, StandardReadable, StrictEnum, derived_signal_rw from ophyd_async.epics.motor import Motor -from dodal.common.beamlines.beamline_parameters import GDABeamlineParameters from dodal.devices.aperturescatterguard import ApertureScatterguard, ApertureValue @@ -29,7 +29,7 @@ def __init__( self, prefix: str, aperture_scatterguard: Reference[ApertureScatterguard], - beamline_parameters: GDABeamlineParameters, + beamline_parameters: dict[str, Any], name: str = "", ): with self.add_children_as_readables(): diff --git a/src/dodal/plan_stubs/check_topup.py b/src/dodal/plan_stubs/check_topup.py index 61a0f905dc8..f56a536e66a 100644 --- a/src/dodal/plan_stubs/check_topup.py +++ b/src/dodal/plan_stubs/check_topup.py @@ -7,6 +7,7 @@ ) from dodal.devices.synchrotron import Synchrotron, SynchrotronMode from dodal.log import LOGGER +from dodal.utils import get_beamline_name ALLOWED_MODES = [SynchrotronMode.USER, SynchrotronMode.SPECIAL] DECAY_MODE_COUNTDOWN = -1 # Value of the start_countdown PV when in decay mode @@ -133,5 +134,5 @@ def check_topup_and_wait_if_necessary( def _load_topup_configuration_from_properties_file() -> dict[str, Any]: - params = get_beamline_parameters() - return params.params + params = get_beamline_parameters(get_beamline_name("i03")) + return params diff --git a/tests/common/beamlines/test_beamline_parameters.py b/tests/common/beamlines/test_beamline_parameters.py index 401272b604e..af0d2ca84fc 100644 --- a/tests/common/beamlines/test_beamline_parameters.py +++ b/tests/common/beamlines/test_beamline_parameters.py @@ -4,7 +4,6 @@ import pytest from dodal.common.beamlines.beamline_parameters import ( - GDABeamlineParameters, get_beamline_parameters, ) from tests.test_data import ( @@ -12,9 +11,11 @@ TEST_BEAMLINE_PARAMETERS_TXT, ) +BL = "i03" + def test_beamline_parameters(): - params = GDABeamlineParameters.from_server(TEST_BEAMLINE_PARAMETERS_TXT) + params = get_beamline_parameters(BL, TEST_BEAMLINE_PARAMETERS_TXT) assert params["sg_x_MEDIUM_APERTURE"] == 5.285 assert params["col_parked_downstream_x"] == 0 assert params["beamLineEnergy__pitchStep"] == 0.002 @@ -23,7 +24,7 @@ def test_beamline_parameters(): def test_i03_beamline_parameters(): - params = GDABeamlineParameters.from_server(I04_BEAMLINE_PARAMETERS) + params = get_beamline_parameters(BL, I04_BEAMLINE_PARAMETERS) assert params["flux_predict_polynomial_coefficients_5"] == [ -0.0000707134131045123, 7.0205491504418, @@ -36,41 +37,19 @@ def test_i03_beamline_parameters(): def test_get_beamline_parameters_works_with_no_environment_variable_set(): if environ.get("BEAMLINE"): del environ["BEAMLINE"] - assert get_beamline_parameters() + assert get_beamline_parameters(BL) def test_get_beamline_parameters(): - original_beamline = environ.get("BEAMLINE") - environ["BEAMLINE"] = "i03" with patch.dict( "dodal.common.beamlines.beamline_parameters.BEAMLINE_PARAMETER_PATHS", {"i03": TEST_BEAMLINE_PARAMETERS_TXT}, ): - params = get_beamline_parameters() + params = get_beamline_parameters("i03") assert params["col_parked_downstream_x"] == 0 assert params["BackStopZyag"] == 19.1 assert params["store_data_collections_in_ispyb"] is True assert params["attenuation_optimisation_type"] == "deadtime" - if original_beamline: - environ["BEAMLINE"] = original_beamline - else: - del environ["BEAMLINE"] - - -@patch("dodal.common.beamlines.beamline_parameters.get_beamline_name") -def test_get_beamline_parameters_raises_error_when_beamline_not_set(get_beamline_name): - get_beamline_name.return_value = None - with pytest.raises(KeyError): - get_beamline_parameters() - - -@patch("dodal.common.beamlines.beamline_parameters.get_beamline_name") -def test_get_beamline_parameters_raises_error_when_beamline_not_found( - get_beamline_name, -): - get_beamline_name.return_value = "invalid_beamline" - with pytest.raises(KeyError): - get_beamline_parameters() @pytest.fixture(autouse=True) diff --git a/tests/devices/conftest.py b/tests/devices/conftest.py index 33c2a241210..5e42b83b22c 100644 --- a/tests/devices/conftest.py +++ b/tests/devices/conftest.py @@ -3,7 +3,6 @@ import pytest from ophyd_async.core import init_devices -from dodal.common.beamlines.beamline_parameters import GDABeamlineParameters from dodal.devices.aperturescatterguard import ( AperturePosition, ApertureScatterguard, @@ -15,50 +14,46 @@ @pytest.fixture def aperture_positions() -> dict[ApertureValue, AperturePosition]: return load_positions_from_beamline_parameters( - GDABeamlineParameters( - params={ - "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, - "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, - "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, - "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, - "miniap_x_MANUAL_LOAD": -4.91, - "miniap_y_MANUAL_LOAD": -48.70, - "miniap_z_MANUAL_LOAD": -10.0, - "sg_x_MANUAL_LOAD": -4.7, - "sg_y_MANUAL_LOAD": 1.8, - } - ) + { + "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, + "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, + "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, + "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, + "miniap_x_MANUAL_LOAD": -4.91, + "miniap_y_MANUAL_LOAD": -48.70, + "miniap_z_MANUAL_LOAD": -10.0, + "sg_x_MANUAL_LOAD": -4.7, + "sg_y_MANUAL_LOAD": 1.8, + } ) @pytest.fixture def aperture_tolerances(): return AperturePosition.tolerances_from_gda_params( - GDABeamlineParameters( - { - "miniap_x_tolerance": 0.004, - "miniap_y_tolerance": 0.1, - "miniap_z_tolerance": 0.1, - "sg_x_tolerance": 0.1, - "sg_y_tolerance": 0.1, - } - ) + { + "miniap_x_tolerance": 0.004, + "miniap_y_tolerance": 0.1, + "miniap_z_tolerance": 0.1, + "sg_x_tolerance": 0.1, + "sg_y_tolerance": 0.1, + } ) diff --git a/tests/devices/mx_phase1/test_beamstop.py b/tests/devices/mx_phase1/test_beamstop.py index 155502b1cbc..ecccb050edb 100644 --- a/tests/devices/mx_phase1/test_beamstop.py +++ b/tests/devices/mx_phase1/test_beamstop.py @@ -1,4 +1,5 @@ from itertools import dropwhile +from typing import Any from unittest.mock import MagicMock, Mock, call import pytest @@ -8,14 +9,14 @@ from bluesky.run_engine import RunEngine from ophyd_async.core import get_mock, get_mock_put, set_mock_value -from dodal.common.beamlines.beamline_parameters import GDABeamlineParameters +from dodal.common.beamlines.beamline_parameters import get_beamline_parameters from dodal.devices.i03 import Beamstop, BeamstopPositions from tests.common.beamlines.test_beamline_parameters import TEST_BEAMLINE_PARAMETERS_TXT @pytest.fixture -def beamline_parameters() -> GDABeamlineParameters: - return GDABeamlineParameters.from_server(TEST_BEAMLINE_PARAMETERS_TXT) +def beamline_parameters() -> dict[str, Any]: + return get_beamline_parameters("i03", TEST_BEAMLINE_PARAMETERS_TXT) @pytest.mark.parametrize( @@ -31,7 +32,7 @@ def beamline_parameters() -> GDABeamlineParameters: ], ) async def test_beamstop_pos_read_selected_pos( - beamline_parameters: GDABeamlineParameters, + beamline_parameters: dict[str, Any], run_engine: RunEngine, x: float, y: float, @@ -77,7 +78,7 @@ def check_in_beam(): async def test_set_beamstop_position_to_data_collection_moves_beamstop( demanded_pos: BeamstopPositions, expected_coords: tuple[float, float, float], - beamline_parameters: GDABeamlineParameters, + beamline_parameters: dict[str, Any], run_engine: RunEngine, ): beamstop = Beamstop("-MO-BS-01:", beamline_parameters, name="beamstop") @@ -102,7 +103,7 @@ async def test_set_beamstop_position_to_data_collection_moves_beamstop( async def test_set_beamstop_position_to_unknown_raises_error( - beamline_parameters: GDABeamlineParameters, run_engine: RunEngine + beamline_parameters: dict[str, Any], run_engine: RunEngine ): beamstop = Beamstop("-MO-BS-01:", beamline_parameters, name="beamstop") await beamstop.connect(mock=True) @@ -114,7 +115,7 @@ async def test_set_beamstop_position_to_unknown_raises_error( async def test_beamstop_select_pos_moves_z_axis_first( - run_engine: RunEngine, beamline_parameters: GDABeamlineParameters + run_engine: RunEngine, beamline_parameters: dict[str, Any] ): beamstop = Beamstop("-MO-BS-01:", beamline_parameters, name="beamstop") await beamstop.connect(mock=True) diff --git a/tests/devices/test_scintillator.py b/tests/devices/test_scintillator.py index ce1579bc1ba..e65586c2c30 100644 --- a/tests/devices/test_scintillator.py +++ b/tests/devices/test_scintillator.py @@ -1,31 +1,29 @@ +from typing import Any from unittest.mock import AsyncMock, MagicMock import pytest from ophyd_async.core import get_mock_put, init_devices from ophyd_async.testing import assert_value -from dodal.common.beamlines.beamline_parameters import GDABeamlineParameters from dodal.devices.aperturescatterguard import ApertureScatterguard, ApertureValue from dodal.devices.scintillator import InOut, Scintillator @pytest.fixture -def mock_beamline_parameters() -> GDABeamlineParameters: - return GDABeamlineParameters( - params={ - "scin_y_SCIN_IN": 100.855, - "scin_y_SCIN_OUT": -0.02, - "scin_z_SCIN_IN": 101.5115, - "scin_z_SCIN_OUT": 0.1, - "scin_y_tolerance": 0.1, - "scin_z_tolerance": 0.12, - } - ) +def mock_beamline_parameters() -> dict[str, Any]: + return { + "scin_y_SCIN_IN": 100.855, + "scin_y_SCIN_OUT": -0.02, + "scin_z_SCIN_IN": 101.5115, + "scin_z_SCIN_OUT": 0.1, + "scin_y_tolerance": 0.1, + "scin_z_tolerance": 0.12, + } @pytest.fixture async def scintillator_and_ap_sg( - mock_beamline_parameters: GDABeamlineParameters, + mock_beamline_parameters: dict[str, Any], ) -> tuple[Scintillator, MagicMock]: async with init_devices(mock=True): mock_ap_sg = MagicMock() From 9517b6a909aefbb874ebf952515516c202f1e9fc Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Fri, 23 Jan 2026 17:36:51 +0000 Subject: [PATCH 12/39] Update lockfile --- uv.lock | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/uv.lock b/uv.lock index eca15cb4b9a..297e2bf7747 100644 --- a/uv.lock +++ b/uv.lock @@ -644,7 +644,7 @@ wheels = [ [[package]] name = "daq-config-server" -version = "1.0.0" +version = "1.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cachetools" }, @@ -657,10 +657,11 @@ dependencies = [ { name = "requests" }, { name = "urllib3" }, { name = "uvicorn" }, + { name = "xmltodict" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/84/649f039a658994fdbe6ecf53e92ba01b65c53635965fe2f4c0c64fbb21a4/daq_config_server-1.0.0.tar.gz", hash = "sha256:63b4989c563520683fbda12aaa42ffeab5fcccc9cc2b25953fd6bc673ab91afd", size = 113002, upload-time = "2025-12-04T17:01:19.77Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5d/1e/37373402723769ced976215ffb1716353d89c0772d5bec8116d9f981d095/daq_config_server-1.1.2.tar.gz", hash = "sha256:2f8c9e43a41534d90512be7ecab37de0fdf33d4b00a6b61bd04e3c82d886062f", size = 126067, upload-time = "2026-01-12T11:28:21.034Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/05/5edbc534abf7edbd9cdd971f25809f7ae916e0d417756cb96fb11c49252c/daq_config_server-1.0.0-py3-none-any.whl", hash = "sha256:cfea960c4b6652784f598ce784fcd236f5fd1234f483c2c287b3a4ab96efb3c8", size = 19808, upload-time = "2025-12-04T17:01:18.392Z" }, + { url = "https://files.pythonhosted.org/packages/13/9f/b8df9fad0b1005e95460ee92092a1da33c282c158336bc23bd7635981f29/daq_config_server-1.1.2-py3-none-any.whl", hash = "sha256:c2bca297feb01a51883e3df7d6ade11af9a3f311602e5584320b1e12881cf636", size = 29273, upload-time = "2026-01-12T11:28:19.635Z" }, ] [[package]] @@ -763,7 +764,7 @@ requires-dist = [ { name = "aiohttp" }, { name = "bluesky", specifier = ">=1.14.5" }, { name = "click" }, - { name = "daq-config-server", specifier = ">=1.0.0" }, + { name = "daq-config-server", specifier = ">=1.1.2" }, { name = "deepdiff" }, { name = "graypy" }, { name = "numpy" }, @@ -3831,6 +3832,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8f/47/7902c3cea79f6a1964fac59b97fb9f11e5ea85e0c0582cc89b2c3193ea48/workflows-3.2-py3-none-any.whl", hash = "sha256:38eed7d209d626b371277bcbcd9c3d476bce9945467d1341c578b1c21ff4eec3", size = 66736, upload-time = "2025-02-27T17:37:40.441Z" }, ] +[[package]] +name = "xmltodict" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/aa/917ceeed4dbb80d2f04dbd0c784b7ee7bba8ae5a54837ef0e5e062cd3cfb/xmltodict-1.0.2.tar.gz", hash = "sha256:54306780b7c2175a3967cad1db92f218207e5bc1aba697d887807c0fb68b7649", size = 25725, upload-time = "2025-09-17T21:59:26.459Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/20/69a0e6058bc5ea74892d089d64dfc3a62ba78917ec5e2cfa70f7c92ba3a5/xmltodict-1.0.2-py3-none-any.whl", hash = "sha256:62d0fddb0dcbc9f642745d8bbf4d81fd17d6dfaec5a15b5c1876300aad92af0d", size = 13893, upload-time = "2025-09-17T21:59:24.859Z" }, +] + [[package]] name = "yarl" version = "1.22.0" From bc858b25cf99624597a189e1f24cd19f9e5e4499 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Thu, 29 Jan 2026 16:51:51 +0000 Subject: [PATCH 13/39] Use server deployed on i03 beamline cluster for i03 config --- src/dodal/common/beamlines/config_client.py | 2 +- tests/common/beamlines/test_config_client.py | 29 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 tests/common/beamlines/test_config_client.py diff --git a/src/dodal/common/beamlines/config_client.py b/src/dodal/common/beamlines/config_client.py index 266c8b0214b..a5479760bdc 100644 --- a/src/dodal/common/beamlines/config_client.py +++ b/src/dodal/common/beamlines/config_client.py @@ -1,7 +1,7 @@ from daq_config_server.client import ConfigServer BEAMLINE_CONFIG_SERVER_ENDPOINTS = { - "i03": "https://daq-config.diamond.ac.uk", + "i03": "https://i03-daq-config.diamond.ac.uk", "i04": "https://daq-config.diamond.ac.uk", } diff --git a/tests/common/beamlines/test_config_client.py b/tests/common/beamlines/test_config_client.py new file mode 100644 index 00000000000..5958760b641 --- /dev/null +++ b/tests/common/beamlines/test_config_client.py @@ -0,0 +1,29 @@ +from unittest.mock import MagicMock, patch + +from dodal.common.beamlines.config_client import get_config_client + + +@patch("dodal.common.beamlines.config_client.ConfigServer") +def test_by_default_get_config_client_uses_centrally_deployed_config_server( + mock_config_server: MagicMock, +): + get_config_client("") + mock_config_server.assert_called_once_with(url="https://daq-config.diamond.ac.uk") + + +@patch("dodal.common.beamlines.config_client.ConfigServer") +def test_get_config_client_uses_i03_beamline_cluster_server_for_i03( + mock_config_server: MagicMock, +): + get_config_client("i03") + mock_config_server.assert_called_once_with( + url="https://i03-daq-config.diamond.ac.uk" + ) + + +@patch("dodal.common.beamlines.config_client.ConfigServer") +def test_get_config_client_uses_entrally_deployed_config_server_for_i04( + mock_config_server: MagicMock, +): + get_config_client("i04") + mock_config_server.assert_called_once_with(url="https://daq-config.diamond.ac.uk") From 9529dc1e87b05c38a323d791e7cac0b9275eb666 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Wed, 4 Feb 2026 15:55:34 +0000 Subject: [PATCH 14/39] typo --- tests/common/beamlines/test_config_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/common/beamlines/test_config_client.py b/tests/common/beamlines/test_config_client.py index 5958760b641..99857012dc6 100644 --- a/tests/common/beamlines/test_config_client.py +++ b/tests/common/beamlines/test_config_client.py @@ -22,7 +22,7 @@ def test_get_config_client_uses_i03_beamline_cluster_server_for_i03( @patch("dodal.common.beamlines.config_client.ConfigServer") -def test_get_config_client_uses_entrally_deployed_config_server_for_i04( +def test_get_config_client_uses_centrally_deployed_config_server_for_i04( mock_config_server: MagicMock, ): get_config_client("i04") From eaa20a22c6c73dfaf3876ce0a0ab1b614e0afb9e Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Wed, 11 Feb 2026 16:22:22 +0000 Subject: [PATCH 15/39] WIP --- .../common/beamlines/beamline_parameters.py | 28 +++++++------- src/dodal/common/beamlines/config_client.py | 3 ++ .../devices/beamlines/i03/undulator_dcm.py | 7 ++-- src/dodal/testing/fixtures/config_server.py | 37 +++++++++++++++++++ src/dodal/utils.py | 2 +- .../beamlines/test_beamline_parameters.py | 36 +++++++++--------- tests/common/beamlines/test_config_client.py | 35 ++++++++++++++++-- tests/conftest.py | 36 +++--------------- tests/devices/beamlines/i03/conftest.py | 6 +++ .../beamlines/i03/test_undulator_dcm.py | 10 +++++ tests/devices/mx_phase1/test_beamstop.py | 3 +- 11 files changed, 130 insertions(+), 73 deletions(-) create mode 100644 src/dodal/testing/fixtures/config_server.py create mode 100644 tests/devices/beamlines/i03/conftest.py diff --git a/src/dodal/common/beamlines/beamline_parameters.py b/src/dodal/common/beamlines/beamline_parameters.py index 61dcdcefae2..b6f984a8655 100644 --- a/src/dodal/common/beamlines/beamline_parameters.py +++ b/src/dodal/common/beamlines/beamline_parameters.py @@ -8,19 +8,19 @@ } -def get_beamline_parameters( - beamline: str, beamline_param_path: str | None = None -) -> dict[str, Any]: - """Loads the beamline parameters from the specified path, or according to the - environment variable if none is given. +def get_beamline_parameters(beamline: str) -> dict[str, Any]: + """Loads the beamline parameters for a specified beamline from the config server. + + Args: + beamline (str): The beamline for which beamline parameters will be retrieved. + + Returns: + dict[str, Any]: Dict of beamline parameters. """ - if not beamline_param_path: - beamline_param_path = BEAMLINE_PARAMETER_PATHS.get(beamline) - if beamline_param_path is None: - raise KeyError( - "No beamline parameter path found, maybe 'BEAMLINE' environment variable is not set!" - ) + beamline_param_path = BEAMLINE_PARAMETER_PATHS.get(beamline) + if beamline_param_path is None: + raise KeyError( + "No beamline parameter path found, maybe 'BEAMLINE' environment variable is not set!" + ) config_client = get_config_client(beamline) - return config_client.get_file_contents( - beamline_param_path, dict, reset_cached_result=True - ) + return config_client.get_file_contents(beamline_param_path, dict) diff --git a/src/dodal/common/beamlines/config_client.py b/src/dodal/common/beamlines/config_client.py index a5479760bdc..23ace33f862 100644 --- a/src/dodal/common/beamlines/config_client.py +++ b/src/dodal/common/beamlines/config_client.py @@ -1,3 +1,5 @@ +from functools import cache + from daq_config_server.client import ConfigServer BEAMLINE_CONFIG_SERVER_ENDPOINTS = { @@ -6,6 +8,7 @@ } +@cache def get_config_client(beamline: str) -> ConfigServer: url = BEAMLINE_CONFIG_SERVER_ENDPOINTS.get( beamline, "https://daq-config.diamond.ac.uk" diff --git a/src/dodal/devices/beamlines/i03/undulator_dcm.py b/src/dodal/devices/beamlines/i03/undulator_dcm.py index 5ecf7d31233..80c9b227e7c 100644 --- a/src/dodal/devices/beamlines/i03/undulator_dcm.py +++ b/src/dodal/devices/beamlines/i03/undulator_dcm.py @@ -48,10 +48,9 @@ def __init__( ) # 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.dcm_fixed_offset_mm = get_beamline_parameters( - get_beamline_name("i03"), - daq_configuration_path + "/domain/beamlineParameters", - )["DCM_Perp_Offset_FIXED"] + self.dcm_fixed_offset_mm = get_beamline_parameters(get_beamline_name())[ + "DCM_Perp_Offset_FIXED" + ] super().__init__(name) diff --git a/src/dodal/testing/fixtures/config_server.py b/src/dodal/testing/fixtures/config_server.py new file mode 100644 index 00000000000..1092cb120b9 --- /dev/null +++ b/src/dodal/testing/fixtures/config_server.py @@ -0,0 +1,37 @@ +import json +from pathlib import Path +from typing import TypeVar +from unittest.mock import patch + +import pytest +from daq_config_server.models import ConfigModel + +T = TypeVar("T", str, dict, ConfigModel) + + +def fake_config_server_get_file_contents( + filepath: str | Path, + desired_return_type: type[T] = str, + reset_cached_result: bool = True, +) -> T: + filepath = Path(filepath) + # Minimal logic required for unit tests + with filepath.open("r") as f: + contents = f.read() + if desired_return_type is str: + return contents + elif desired_return_type is dict: + return json.loads(contents) + elif issubclass(desired_return_type, ConfigModel): # type: ignore + return desired_return_type.model_validate(json.loads(contents)) + + +@pytest.fixture(autouse=True) +def mock_config_server(): + # Don't actually talk to central service during unit tests, and reset caches between test + + with patch( + "daq_config_server.client.ConfigServer.get_file_contents", + side_effect=fake_config_server_get_file_contents, + ): + yield diff --git a/src/dodal/utils.py b/src/dodal/utils.py index aa10ff5ea03..f2c602427a3 100644 --- a/src/dodal/utils.py +++ b/src/dodal/utils.py @@ -66,7 +66,7 @@ AnyDeviceFactory: TypeAlias = V1DeviceFactory | V2DeviceFactory -def get_beamline_name(default: str) -> str: +def get_beamline_name(default: str | None = None) -> str: return environ.get("BEAMLINE") or default diff --git a/tests/common/beamlines/test_beamline_parameters.py b/tests/common/beamlines/test_beamline_parameters.py index af0d2ca84fc..f33683afc72 100644 --- a/tests/common/beamlines/test_beamline_parameters.py +++ b/tests/common/beamlines/test_beamline_parameters.py @@ -11,11 +11,22 @@ TEST_BEAMLINE_PARAMETERS_TXT, ) -BL = "i03" + +@pytest.fixture(autouse=True) +def patch_beamline_parameter_paths(): + with patch( + "dodal.common.beamlines.beamline_parameters.BEAMLINE_PARAMETER_PATHS", + { + "test": TEST_BEAMLINE_PARAMETERS_TXT, + "i04": I04_BEAMLINE_PARAMETERS, + "i03": I04_BEAMLINE_PARAMETERS, + }, + ): + yield def test_beamline_parameters(): - params = get_beamline_parameters(BL, TEST_BEAMLINE_PARAMETERS_TXT) + params = get_beamline_parameters("test") assert params["sg_x_MEDIUM_APERTURE"] == 5.285 assert params["col_parked_downstream_x"] == 0 assert params["beamLineEnergy__pitchStep"] == 0.002 @@ -24,7 +35,7 @@ def test_beamline_parameters(): def test_i03_beamline_parameters(): - params = get_beamline_parameters(BL, I04_BEAMLINE_PARAMETERS) + params = get_beamline_parameters("i03") assert params["flux_predict_polynomial_coefficients_5"] == [ -0.0000707134131045123, 7.0205491504418, @@ -34,28 +45,15 @@ def test_i03_beamline_parameters(): ] -def test_get_beamline_parameters_works_with_no_environment_variable_set(): +def test_get_beamline_parameters_errors_with_no_environment_variable_set(): if environ.get("BEAMLINE"): del environ["BEAMLINE"] - assert get_beamline_parameters(BL) + assert get_beamline_parameters("i03") def test_get_beamline_parameters(): - with patch.dict( - "dodal.common.beamlines.beamline_parameters.BEAMLINE_PARAMETER_PATHS", - {"i03": TEST_BEAMLINE_PARAMETERS_TXT}, - ): - params = get_beamline_parameters("i03") + params = get_beamline_parameters("test") assert params["col_parked_downstream_x"] == 0 assert params["BackStopZyag"] == 19.1 assert params["store_data_collections_in_ispyb"] is True assert params["attenuation_optimisation_type"] == "deadtime" - - -@pytest.fixture(autouse=True) -def i03_beamline_parameters(): - with patch.dict( - "dodal.common.beamlines.beamline_parameters.BEAMLINE_PARAMETER_PATHS", - {"i03": TEST_BEAMLINE_PARAMETERS_TXT}, - ) as params: - yield params diff --git a/tests/common/beamlines/test_config_client.py b/tests/common/beamlines/test_config_client.py index 99857012dc6..648edb2d294 100644 --- a/tests/common/beamlines/test_config_client.py +++ b/tests/common/beamlines/test_config_client.py @@ -1,11 +1,18 @@ from unittest.mock import MagicMock, patch +import pytest + from dodal.common.beamlines.config_client import get_config_client +@pytest.fixture() +def clear_cache(): + get_config_client.cache_clear() + + @patch("dodal.common.beamlines.config_client.ConfigServer") def test_by_default_get_config_client_uses_centrally_deployed_config_server( - mock_config_server: MagicMock, + mock_config_server: MagicMock, clear_cache ): get_config_client("") mock_config_server.assert_called_once_with(url="https://daq-config.diamond.ac.uk") @@ -13,7 +20,7 @@ def test_by_default_get_config_client_uses_centrally_deployed_config_server( @patch("dodal.common.beamlines.config_client.ConfigServer") def test_get_config_client_uses_i03_beamline_cluster_server_for_i03( - mock_config_server: MagicMock, + mock_config_server: MagicMock, clear_cache ): get_config_client("i03") mock_config_server.assert_called_once_with( @@ -23,7 +30,29 @@ def test_get_config_client_uses_i03_beamline_cluster_server_for_i03( @patch("dodal.common.beamlines.config_client.ConfigServer") def test_get_config_client_uses_centrally_deployed_config_server_for_i04( - mock_config_server: MagicMock, + mock_config_server: MagicMock, clear_cache +): + get_config_client("i04") + mock_config_server.assert_called_once_with(url="https://daq-config.diamond.ac.uk") + + +@patch("dodal.common.beamlines.config_client.ConfigServer") +def test_get_config_client_caches_if_called_with_same_beamline( + mock_config_server: MagicMock, clear_cache +): + get_config_client("i04") + mock_config_server.assert_called_once() + get_config_client("i04") + mock_config_server.assert_called_once() + + +@patch("dodal.common.beamlines.config_client.ConfigServer") +def test_get_config_client_resets_cache_if_called_with_same_beamline( + mock_config_server: MagicMock, clear_cache ): + assert mock_config_server.assert_not_called get_config_client("i04") mock_config_server.assert_called_once_with(url="https://daq-config.diamond.ac.uk") + get_config_client("i03") + assert mock_config_server.call_count == 2 + mock_config_server.assert_called_with(url="https://i03-daq-config.diamond.ac.uk") diff --git a/tests/conftest.py b/tests/conftest.py index 8af64fa9791..66d3a524151 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,4 @@ import importlib -import json import logging import os import sys @@ -9,7 +8,6 @@ from unittest.mock import MagicMock, patch import pytest -from daq_config_server.models import ConfigModel from ophyd_async.core import ( PathProvider, ) @@ -59,7 +57,11 @@ # Add run_engine and util fixtures to be used in tests -pytest_plugins = ["dodal.testing.fixtures.run_engine", "dodal.testing.fixtures.utils"] +pytest_plugins = [ + "dodal.testing.fixtures.run_engine", + "dodal.testing.fixtures.utils", + "dodal.testing.fixtures.config_server", +] @pytest.fixture(autouse=True) @@ -169,31 +171,3 @@ def eiger_params(tmp_path: Path) -> DetectorParams: det_dist_to_beam_converter_path=TEST_LUT_TXT, detector_size_constants=EIGER2_X_16M_SIZE.det_type_string, # type: ignore ) - - -def _fake_config_server_get_file_contents( - filepath: str | Path, - desired_return_type: type[str] | type[dict] | ConfigModel = str, - reset_cached_result: bool = True, -): - filepath = Path(filepath) - # Minimal logic required for unit tests - with filepath.open("r") as f: - contents = f.read() - if desired_return_type is str: - return contents - elif desired_return_type is dict: - return json.loads(contents) - elif issubclass(desired_return_type, ConfigModel): # type: ignore - return desired_return_type.model_validate(json.loads(contents)) - - -@pytest.fixture(autouse=True) -def mock_config_server(): - # Don't actually talk to central service during unit tests, and reset caches between test - - with patch( - "daq_config_server.client.ConfigServer.get_file_contents", - side_effect=_fake_config_server_get_file_contents, - ): - yield diff --git a/tests/devices/beamlines/i03/conftest.py b/tests/devices/beamlines/i03/conftest.py new file mode 100644 index 00000000000..c116593b798 --- /dev/null +++ b/tests/devices/beamlines/i03/conftest.py @@ -0,0 +1,6 @@ +import pytest + + +@pytest.fixture(autouse=True) +def use_beamline_i03(monkeypatch): + monkeypatch.setenv("BEAMLINE", "i03") diff --git a/tests/devices/beamlines/i03/test_undulator_dcm.py b/tests/devices/beamlines/i03/test_undulator_dcm.py index 9bcf7bd435e..859b58477b0 100644 --- a/tests/devices/beamlines/i03/test_undulator_dcm.py +++ b/tests/devices/beamlines/i03/test_undulator_dcm.py @@ -19,6 +19,16 @@ from tests.devices.test_data import ( TEST_BEAMLINE_UNDULATOR_TO_GAP_LUT, ) +from tests.test_data import TEST_BEAMLINE_PARAMETERS_TXT + + +@pytest.fixture(autouse=True) +def patch_beamline_parameter_paths(): + with patch( + "dodal.common.beamlines.beamline_parameters.BEAMLINE_PARAMETER_PATHS", + {"i03": TEST_BEAMLINE_PARAMETERS_TXT}, + ): + yield @pytest.fixture(autouse=True) diff --git a/tests/devices/mx_phase1/test_beamstop.py b/tests/devices/mx_phase1/test_beamstop.py index 4494a20be11..5ca3d626c1d 100644 --- a/tests/devices/mx_phase1/test_beamstop.py +++ b/tests/devices/mx_phase1/test_beamstop.py @@ -11,12 +11,13 @@ from dodal.common.beamlines.beamline_parameters import get_beamline_parameters from dodal.devices.beamlines.i03 import Beamstop, BeamstopPositions +from dodal.testing.fixtures.config_server import fake_config_server_get_file_contents from tests.common.beamlines.test_beamline_parameters import TEST_BEAMLINE_PARAMETERS_TXT @pytest.fixture def beamline_parameters() -> dict[str, Any]: - return get_beamline_parameters("i03", TEST_BEAMLINE_PARAMETERS_TXT) + return fake_config_server_get_file_contents(TEST_BEAMLINE_PARAMETERS_TXT, dict) @pytest.mark.parametrize( From 0d29dbe46a175ff0675f4e65b0b68b3a89952d9a Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Wed, 11 Feb 2026 16:29:16 +0000 Subject: [PATCH 16/39] Reset cache between tests --- tests/common/beamlines/test_config_client.py | 17 +++++------------ tests/conftest.py | 6 ++++++ 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/tests/common/beamlines/test_config_client.py b/tests/common/beamlines/test_config_client.py index 648edb2d294..303b1c2110f 100644 --- a/tests/common/beamlines/test_config_client.py +++ b/tests/common/beamlines/test_config_client.py @@ -1,18 +1,11 @@ from unittest.mock import MagicMock, patch -import pytest - from dodal.common.beamlines.config_client import get_config_client -@pytest.fixture() -def clear_cache(): - get_config_client.cache_clear() - - @patch("dodal.common.beamlines.config_client.ConfigServer") def test_by_default_get_config_client_uses_centrally_deployed_config_server( - mock_config_server: MagicMock, clear_cache + mock_config_server: MagicMock, ): get_config_client("") mock_config_server.assert_called_once_with(url="https://daq-config.diamond.ac.uk") @@ -20,7 +13,7 @@ def test_by_default_get_config_client_uses_centrally_deployed_config_server( @patch("dodal.common.beamlines.config_client.ConfigServer") def test_get_config_client_uses_i03_beamline_cluster_server_for_i03( - mock_config_server: MagicMock, clear_cache + mock_config_server: MagicMock, ): get_config_client("i03") mock_config_server.assert_called_once_with( @@ -30,7 +23,7 @@ def test_get_config_client_uses_i03_beamline_cluster_server_for_i03( @patch("dodal.common.beamlines.config_client.ConfigServer") def test_get_config_client_uses_centrally_deployed_config_server_for_i04( - mock_config_server: MagicMock, clear_cache + mock_config_server: MagicMock, ): get_config_client("i04") mock_config_server.assert_called_once_with(url="https://daq-config.diamond.ac.uk") @@ -38,7 +31,7 @@ def test_get_config_client_uses_centrally_deployed_config_server_for_i04( @patch("dodal.common.beamlines.config_client.ConfigServer") def test_get_config_client_caches_if_called_with_same_beamline( - mock_config_server: MagicMock, clear_cache + mock_config_server: MagicMock, ): get_config_client("i04") mock_config_server.assert_called_once() @@ -48,7 +41,7 @@ def test_get_config_client_caches_if_called_with_same_beamline( @patch("dodal.common.beamlines.config_client.ConfigServer") def test_get_config_client_resets_cache_if_called_with_same_beamline( - mock_config_server: MagicMock, clear_cache + mock_config_server: MagicMock, ): assert mock_config_server.assert_not_called get_config_client("i04") diff --git a/tests/conftest.py b/tests/conftest.py index 66d3a524151..7c83dd8a692 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,6 +14,7 @@ from dodal.common.beamlines import beamline_parameters, beamline_utils from dodal.common.beamlines.beamline_utils import clear_path_provider +from dodal.common.beamlines.config_client import get_config_client from dodal.common.visit import ( DirectoryServiceClient, LocalDirectoryServiceClient, @@ -171,3 +172,8 @@ def eiger_params(tmp_path: Path) -> DetectorParams: det_dist_to_beam_converter_path=TEST_LUT_TXT, detector_size_constants=EIGER2_X_16M_SIZE.det_type_string, # type: ignore ) + + +@pytest.fixture(autouse=True) +def clear_cache(): + get_config_client.cache_clear() From d4fda7b822c9e2398060eb52a15c2691a16eed47 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Wed, 11 Feb 2026 16:56:23 +0000 Subject: [PATCH 17/39] Fix --- src/dodal/utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/dodal/utils.py b/src/dodal/utils.py index f2c602427a3..08c0fb16bde 100644 --- a/src/dodal/utils.py +++ b/src/dodal/utils.py @@ -67,7 +67,10 @@ def get_beamline_name(default: str | None = None) -> str: - return environ.get("BEAMLINE") or default + beamline_name = environ.get("BEAMLINE") or default + if beamline_name is None: + raise ValueError("Set BEAMLINE environment variable or provide default.") + return beamline_name def is_test_mode() -> bool: From a947f607330f0252321771697f1d430a2a9d3791 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Wed, 11 Feb 2026 16:59:02 +0000 Subject: [PATCH 18/39] Fix lint --- tests/devices/mx_phase1/test_beamstop.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/devices/mx_phase1/test_beamstop.py b/tests/devices/mx_phase1/test_beamstop.py index 5ca3d626c1d..312ecab3f96 100644 --- a/tests/devices/mx_phase1/test_beamstop.py +++ b/tests/devices/mx_phase1/test_beamstop.py @@ -9,7 +9,6 @@ from bluesky.run_engine import RunEngine from ophyd_async.core import get_mock, get_mock_put, set_mock_value -from dodal.common.beamlines.beamline_parameters import get_beamline_parameters from dodal.devices.beamlines.i03 import Beamstop, BeamstopPositions from dodal.testing.fixtures.config_server import fake_config_server_get_file_contents from tests.common.beamlines.test_beamline_parameters import TEST_BEAMLINE_PARAMETERS_TXT From d5bd077d96cdb62240ee8ac549ef2b5a502ffe2c Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Thu, 12 Feb 2026 09:44:07 +0000 Subject: [PATCH 19/39] Fix lint --- src/dodal/testing/fixtures/config_server.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/dodal/testing/fixtures/config_server.py b/src/dodal/testing/fixtures/config_server.py index 1092cb120b9..d9193980886 100644 --- a/src/dodal/testing/fixtures/config_server.py +++ b/src/dodal/testing/fixtures/config_server.py @@ -18,12 +18,13 @@ def fake_config_server_get_file_contents( # Minimal logic required for unit tests with filepath.open("r") as f: contents = f.read() - if desired_return_type is str: - return contents - elif desired_return_type is dict: - return json.loads(contents) - elif issubclass(desired_return_type, ConfigModel): # type: ignore - return desired_return_type.model_validate(json.loads(contents)) + if desired_return_type is str: + return contents # type: ignore + elif desired_return_type is dict: + return json.loads(contents) + elif issubclass(desired_return_type, ConfigModel): + return desired_return_type.model_validate(json.loads(contents)) + raise ValueError("Invalid return type requested") @pytest.fixture(autouse=True) From 357a6c9dd0efcf7bd72ef384097627e9a68a5db9 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Fri, 13 Feb 2026 11:26:39 +0000 Subject: [PATCH 20/39] PR comments and coverage --- tests/common/beamlines/test_beamline_parameters.py | 7 +++---- tests/test_utils.py | 9 +++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/common/beamlines/test_beamline_parameters.py b/tests/common/beamlines/test_beamline_parameters.py index f33683afc72..3bd680699a7 100644 --- a/tests/common/beamlines/test_beamline_parameters.py +++ b/tests/common/beamlines/test_beamline_parameters.py @@ -45,10 +45,9 @@ def test_i03_beamline_parameters(): ] -def test_get_beamline_parameters_errors_with_no_environment_variable_set(): - if environ.get("BEAMLINE"): - del environ["BEAMLINE"] - assert get_beamline_parameters("i03") +def test_get_beamline_parameters_errors_if_no_filepath_found_for_beamline(): + with pytest.raises(KeyError): + assert get_beamline_parameters("not a key") def test_get_beamline_parameters(): diff --git a/tests/test_utils.py b/tests/test_utils.py index 9dd86e1cbce..d4877ab2a4b 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -18,6 +18,7 @@ collect_factories, filter_ophyd_devices, get_beamline_based_on_environment_variable, + get_beamline_name, get_hostname, get_run_number, is_v2_device_type, @@ -484,3 +485,11 @@ def test_filter_ophyd_devices_raises_for_extra_types(): ) def test_is_v2_device_type(input: Any, expected_result: bool): assert is_v2_device_type(input) == expected_result + + +def test_get_beamline_name_raises_error_if_environment_variable_not_set_and_no_default_given( + monkeypatch, +): + monkeypatch.delenv("BEAMLINE", raising=False) + with pytest.raises(ValueError): + get_beamline_name() From 286a17cc0592ecb5242a78b44a618a2e6db3c260 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Fri, 13 Feb 2026 11:35:21 +0000 Subject: [PATCH 21/39] Lint --- tests/common/beamlines/test_beamline_parameters.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/common/beamlines/test_beamline_parameters.py b/tests/common/beamlines/test_beamline_parameters.py index 3bd680699a7..40a86350cdf 100644 --- a/tests/common/beamlines/test_beamline_parameters.py +++ b/tests/common/beamlines/test_beamline_parameters.py @@ -1,4 +1,3 @@ -from os import environ from unittest.mock import patch import pytest From 6c8fbfda530b218d93f26d90c42e09fe18454195 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Fri, 14 Nov 2025 16:42:40 +0000 Subject: [PATCH 22/39] Use config server for OAV config json --- src/dodal/devices/oav/oav_parameters.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/dodal/devices/oav/oav_parameters.py b/src/dodal/devices/oav/oav_parameters.py index 7b2925398a9..94b2664cd56 100644 --- a/src/dodal/devices/oav/oav_parameters.py +++ b/src/dodal/devices/oav/oav_parameters.py @@ -1,4 +1,3 @@ -import json from abc import abstractmethod from collections import ChainMap from dataclasses import dataclass @@ -6,6 +5,8 @@ from xml.etree import ElementTree from xml.etree.ElementTree import Element +from daq_config_server.client import ConfigServer + # GDA currently assumes this aspect ratio for the OAV window size. # For some beamline this doesn't affect anything as the actual OAV aspect ratio # matches. Others need to take it into account to rescale the values stored in @@ -34,20 +35,24 @@ def __init__( ): self.oav_config_json: str = oav_config_json self.context = context + config_server = ConfigServer(url="https://daq-config.diamond.ac.uk") - self.global_params, self.context_dicts = self.load_json(self.oav_config_json) + self.global_params, self.context_dicts = self.load_json( + config_server, self.oav_config_json + ) self.active_params: ChainMap = ChainMap( self.context_dicts[self.context], self.global_params ) self.update_self_from_current_context() @staticmethod - def load_json(filename: str) -> tuple[dict[str, Any], dict[str, dict]]: - """Loads the json from the specified file, and returns a dict with all the + def load_json( + config_server: ConfigServer, filename: str + ) -> tuple[dict[str, Any], dict[str, dict]]: + """Loads the specified file from the config server, and returns a dict with all the individual top-level k-v pairs, and one with all the subdicts. """ - with open(filename) as f: - raw_params: dict[str, Any] = json.load(f) + raw_params: dict[str, Any] = config_server.get_file_contents(filename, dict) global_params = { k: raw_params.pop(k) for k, v in list(raw_params.items()) From 8268b278c12336c0b39c87f8d9559f50f20a430d Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Fri, 14 Nov 2025 16:43:47 +0000 Subject: [PATCH 23/39] Add fixture for config server --- tests/devices/oav/test_oav_parameters.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/devices/oav/test_oav_parameters.py b/tests/devices/oav/test_oav_parameters.py index ee856c1d784..8eaeb3a9dd9 100644 --- a/tests/devices/oav/test_oav_parameters.py +++ b/tests/devices/oav/test_oav_parameters.py @@ -15,6 +15,16 @@ TEST_OAV_ZOOM_LEVELS_XML, ) +# @pytest.fixture(autouse=True) +# def mock_config_server(): +# # Don't actually talk to central service during unit tests, and reset caches between test + +# with patch( +# "dodal.devices.oav.oav_parameters.ConfigServer.get_file_contents", +# side_effect=_fake_config_server_read, +# ): +# yield + @pytest.fixture def mock_parameters(): From 4bd6ac7bc9192f3207460896d355f1ebc5c682b4 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Fri, 21 Nov 2025 17:21:17 +0000 Subject: [PATCH 24/39] Convert OAV test config files to json --- system_tests/test_oav_system.py | 6 +-- tests/conftest.py | 4 +- tests/devices/oav/conftest.py | 8 +-- tests/devices/oav/test_oav.py | 6 +-- tests/devices/oav/test_oav_parameters.py | 16 ++---- tests/test_data/__init__.py | 6 +-- tests/test_data/test_display.configuration | 42 ---------------- .../test_data/test_display_configuration.json | 50 +++++++++++++++++++ tests/test_data/test_oav_zoom_levels.json | 45 +++++++++++++++++ tests/test_data/test_oav_zoom_levels.xml | 42 ---------------- 10 files changed, 113 insertions(+), 112 deletions(-) delete mode 100644 tests/test_data/test_display.configuration create mode 100644 tests/test_data/test_display_configuration.json create mode 100644 tests/test_data/test_oav_zoom_levels.json delete mode 100644 tests/test_data/test_oav_zoom_levels.xml diff --git a/system_tests/test_oav_system.py b/system_tests/test_oav_system.py index bfecb9622b3..0941389cc3a 100644 --- a/system_tests/test_oav_system.py +++ b/system_tests/test_oav_system.py @@ -4,7 +4,7 @@ from ophyd_async.core import init_devices from tests.test_data import ( TEST_DISPLAY_CONFIG, - TEST_OAV_ZOOM_LEVELS_XML, + TEST_OAV_ZOOM_LEVELS, ) from dodal.devices.oav.oav_detector import OAV, OAVConfig @@ -18,7 +18,7 @@ @pytest.fixture async def oav() -> OAV: - oav_config = OAVConfig(TEST_OAV_ZOOM_LEVELS_XML, TEST_DISPLAY_CONFIG) + oav_config = OAVConfig(TEST_OAV_ZOOM_LEVELS, TEST_DISPLAY_CONFIG) async with init_devices(connect=True): oav = OAV("", config=oav_config, name="oav") return oav @@ -39,7 +39,7 @@ def take_snapshot_with_grid(oav: OAV, snapshot_filename, snapshot_directory): @pytest.mark.skip(reason="Don't want to actually take snapshots during testing.") def test_grid_overlay(run_engine: RunEngine): beamline = "BL03I" - oav_params = OAVConfig(TEST_OAV_ZOOM_LEVELS_XML, TEST_DISPLAY_CONFIG) + oav_params = OAVConfig(TEST_OAV_ZOOM_LEVELS, TEST_DISPLAY_CONFIG) oav = OAV(name="oav", prefix=f"{beamline}", config=oav_params) snapshot_filename = "snapshot" snapshot_directory = "." diff --git a/tests/conftest.py b/tests/conftest.py index 7c83dd8a692..08c054091ff 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -35,12 +35,12 @@ from tests.test_data import ( I04_BEAMLINE_PARAMETERS, TEST_DISPLAY_CONFIG, - TEST_OAV_ZOOM_LEVELS_XML, + TEST_OAV_ZOOM_LEVELS, ) MOCK_PATHS = [ ("DAQ_CONFIGURATION_PATH", MOCK_DAQ_CONFIG_PATH), - ("ZOOM_PARAMS_FILE", TEST_OAV_ZOOM_LEVELS_XML), + ("ZOOM_PARAMS_FILE", TEST_OAV_ZOOM_LEVELS), ("DISPLAY_CONFIG", TEST_DISPLAY_CONFIG), ("LOOK_UPTABLE_DIR", LOOKUP_TABLE_PATH), ] diff --git a/tests/devices/oav/conftest.py b/tests/devices/oav/conftest.py index 579d6578ff5..1a28f5cd788 100644 --- a/tests/devices/oav/conftest.py +++ b/tests/devices/oav/conftest.py @@ -5,12 +5,12 @@ from dodal.devices.oav.oav_detector import OAVBeamCentreFile, OAVBeamCentrePV from dodal.devices.oav.oav_parameters import OAVConfig, OAVConfigBeamCentre -from tests.test_data import TEST_DISPLAY_CONFIG, TEST_OAV_ZOOM_LEVELS_XML +from tests.test_data import TEST_DISPLAY_CONFIG, TEST_OAV_ZOOM_LEVELS @pytest.fixture async def oav() -> OAVBeamCentreFile: - oav_config = OAVConfigBeamCentre(TEST_OAV_ZOOM_LEVELS_XML, TEST_DISPLAY_CONFIG) + oav_config = OAVConfigBeamCentre(TEST_OAV_ZOOM_LEVELS, TEST_DISPLAY_CONFIG) async with init_devices(mock=True, connect=True): oav = OAVBeamCentreFile("", config=oav_config, name="oav") zoom_levels_list = ["1.0x", "3.0x", "5.0x", "7.5x", "10.0x"] @@ -25,7 +25,7 @@ async def oav() -> OAVBeamCentreFile: @pytest.fixture async def oav_beam_centre_pv_roi() -> OAVBeamCentrePV: - oav_config = OAVConfig(TEST_OAV_ZOOM_LEVELS_XML) + oav_config = OAVConfig(TEST_OAV_ZOOM_LEVELS) async with init_devices(mock=True, connect=True): oav = OAVBeamCentrePV("", config=oav_config, name="oav") zoom_levels_list = ["1.0x", "3.0x", "5.0x", "7.5x", "10.0x"] @@ -40,7 +40,7 @@ async def oav_beam_centre_pv_roi() -> OAVBeamCentrePV: @pytest.fixture async def oav_beam_centre_pv_fs() -> OAVBeamCentrePV: - oav_config = OAVConfig(TEST_OAV_ZOOM_LEVELS_XML) + oav_config = OAVConfig(TEST_OAV_ZOOM_LEVELS) async with init_devices(mock=True, connect=True): oav = OAVBeamCentrePV( "", config=oav_config, name="oav", mjpeg_prefix="XTAL", overlay_channel=3 diff --git a/tests/devices/oav/test_oav.py b/tests/devices/oav/test_oav.py index 93e340cd645..469b876f922 100644 --- a/tests/devices/oav/test_oav.py +++ b/tests/devices/oav/test_oav.py @@ -14,7 +14,7 @@ ) from tests.test_data import ( TEST_DISPLAY_CONFIG, - TEST_OAV_ZOOM_LEVELS_XML, + TEST_OAV_ZOOM_LEVELS, ) @@ -167,7 +167,7 @@ async def test_beam_centre_signals_have_same_names( async def test_oav_with_null_zoom_controller(null_controller: NullZoomController): - oav_config = OAVConfigBeamCentre(TEST_OAV_ZOOM_LEVELS_XML, TEST_DISPLAY_CONFIG) + oav_config = OAVConfigBeamCentre(TEST_OAV_ZOOM_LEVELS, TEST_DISPLAY_CONFIG) oav = OAVBeamCentreFile("", oav_config, "", zoom_controller=null_controller) assert await oav.zoom_controller.level.get_value() == "1.0x" @@ -193,7 +193,7 @@ async def test_oav_with_null_zoom_controller_set_zoom_level_other_than_1( ["MJPG", "XTAL"], ) async def test_setting_mjpeg_prefix_changes_stream_url(mjpeg_prefix): - oav_config = OAVConfigBeamCentre(TEST_OAV_ZOOM_LEVELS_XML, TEST_DISPLAY_CONFIG) + oav_config = OAVConfigBeamCentre(TEST_OAV_ZOOM_LEVELS, TEST_DISPLAY_CONFIG) async with init_devices(mock=True, connect=True): oav = OAVBeamCentreFile( "", config=oav_config, name="oav", mjpeg_prefix=mjpeg_prefix diff --git a/tests/devices/oav/test_oav_parameters.py b/tests/devices/oav/test_oav_parameters.py index 8eaeb3a9dd9..7be33590e6e 100644 --- a/tests/devices/oav/test_oav_parameters.py +++ b/tests/devices/oav/test_oav_parameters.py @@ -12,19 +12,9 @@ from tests.devices.oav.test_data import TEST_OAV_CENTRING_JSON from tests.test_data import ( TEST_DISPLAY_CONFIG, - TEST_OAV_ZOOM_LEVELS_XML, + TEST_OAV_ZOOM_LEVELS, ) -# @pytest.fixture(autouse=True) -# def mock_config_server(): -# # Don't actually talk to central service during unit tests, and reset caches between test - -# with patch( -# "dodal.devices.oav.oav_parameters.ConfigServer.get_file_contents", -# side_effect=_fake_config_server_read, -# ): -# yield - @pytest.fixture def mock_parameters(): @@ -36,13 +26,13 @@ def mock_parameters(): @pytest.fixture def mock_config() -> dict[str, ZoomParams]: - return OAVConfig(TEST_OAV_ZOOM_LEVELS_XML).get_parameters() + return OAVConfig(TEST_OAV_ZOOM_LEVELS).get_parameters() @pytest.fixture def mock_config_with_beam_centre() -> dict[str, ZoomParamsCrosshair]: config = OAVConfigBeamCentre( - TEST_OAV_ZOOM_LEVELS_XML, TEST_DISPLAY_CONFIG + TEST_OAV_ZOOM_LEVELS, TEST_DISPLAY_CONFIG ).get_parameters() config = cast(dict[str, ZoomParamsCrosshair], config) return config diff --git a/tests/test_data/__init__.py b/tests/test_data/__init__.py index 2b2948e8f65..64cd7a5a988 100644 --- a/tests/test_data/__init__.py +++ b/tests/test_data/__init__.py @@ -5,13 +5,13 @@ BAD_BEAMLINE_PARAMETERS = join(TEST_DATA_PATH, "bad_beamlineParameters") I04_BEAMLINE_PARAMETERS = join(TEST_DATA_PATH, "i04_beamlineParameters") TEST_BEAMLINE_PARAMETERS_TXT = join(TEST_DATA_PATH, "test_beamline_parameters.txt") -TEST_DISPLAY_CONFIG = join(TEST_DATA_PATH, "test_display.configuration") -TEST_OAV_ZOOM_LEVELS_XML = join(TEST_DATA_PATH, "test_oav_zoom_levels.xml") +TEST_DISPLAY_CONFIG = join(TEST_DATA_PATH, "test_display_configuration.json") +TEST_OAV_ZOOM_LEVELS = join(TEST_DATA_PATH, "test_oav_zoom_levels.json") __all__ = [ "BAD_BEAMLINE_PARAMETERS", "I04_BEAMLINE_PARAMETERS", "TEST_BEAMLINE_PARAMETERS_TXT", "TEST_DISPLAY_CONFIG", - "TEST_OAV_ZOOM_LEVELS_XML", + "TEST_OAV_ZOOM_LEVELS", ] diff --git a/tests/test_data/test_display.configuration b/tests/test_data/test_display.configuration deleted file mode 100644 index dfb01954ac9..00000000000 --- a/tests/test_data/test_display.configuration +++ /dev/null @@ -1,42 +0,0 @@ -zoomLevel = 1.0 -crosshairX = 477 -crosshairY = 359 -topLeftX = 383 -topLeftY = 253 -bottomRightX = 410 -bottomRightY = 278 -zoomLevel = 2.5 -crosshairX = 493 -crosshairY = 355 -topLeftX = 340 -topLeftY = 283 -bottomRightX = 388 -bottomRightY = 322 -zoomLevel = 5.0 -crosshairX = 517 -crosshairY = 350 -topLeftX = 268 -topLeftY = 326 -bottomRightX = 354 -bottomRightY = 387 -zoomLevel = 7.5 -crosshairX = 549 -crosshairY = 347 -topLeftX = 248 -topLeftY = 394 -bottomRightX = 377 -bottomRightY = 507 -zoomLevel = 10.0 -crosshairX = 613 -crosshairY = 344 -topLeftX = 2 -topLeftY = 489 -bottomRightX = 206 -bottomRightY = 630 -zoomLevel = 15.0 -crosshairX = 693 -crosshairY = 339 -topLeftX = 1 -topLeftY = 601 -bottomRightX = 65 -bottomRightY = 767 diff --git a/tests/test_data/test_display_configuration.json b/tests/test_data/test_display_configuration.json new file mode 100644 index 00000000000..30a5bce92a8 --- /dev/null +++ b/tests/test_data/test_display_configuration.json @@ -0,0 +1,50 @@ +{ + "1.0": { + "crosshairX": 477, + "crosshairY": 359, + "topLeftX": 383, + "topLeftY": 253, + "bottomRightX": 410, + "bottomRightY": 278 + }, + "2.5": { + "crosshairX": 493, + "crosshairY": 355, + "topLeftX": 340, + "topLeftY": 283, + "bottomRightX": 388, + "bottomRightY": 322 + }, + "5.0": { + "crosshairX": 517, + "crosshairY": 350, + "topLeftX": 268, + "topLeftY": 326, + "bottomRightX": 354, + "bottomRightY": 387 + }, + "7.5": { + "crosshairX": 549, + "crosshairY": 347, + "topLeftX": 248, + "topLeftY": 394, + "bottomRightX": 377, + "bottomRightY": 507 + }, + "10.0": { + "crosshairX": 613, + "crosshairY": 344, + "topLeftX": 2, + "topLeftY": 489, + "bottomRightX": 206, + "bottomRightY": 630 + }, + "15.0": { + "crosshairX": 693, + "crosshairY": 339, + "topLeftX": 1, + "topLeftY": 601, + "bottomRightX": 65, + "bottomRightY": 767 + } +} diff --git a/tests/test_data/test_oav_zoom_levels.json b/tests/test_data/test_oav_zoom_levels.json new file mode 100644 index 00000000000..35030dc6b07 --- /dev/null +++ b/tests/test_data/test_oav_zoom_levels.json @@ -0,0 +1,45 @@ +{ + "JCameraManSettings": { + "levels": { + "zoomLevel": [ + { + "level": "1.0", + "position": "0", + "micronsPerXPixel": "2.87", + "micronsPerYPixel": "2.87" + }, + { + "level": "2.5", + "position": "10", + "micronsPerXPixel": "2.31", + "micronsPerYPixel": "2.31" + }, + { + "level": "5.0", + "position": "25", + "micronsPerXPixel": "1.58", + "micronsPerYPixel": "1.58" + }, + { + "level": "7.5", + "position": "50", + "micronsPerXPixel": "0.806", + "micronsPerYPixel": "0.806" + }, + { + "level": "10.0", + "position": "75", + "micronsPerXPixel": "0.438", + "micronsPerYPixel": "0.438" + }, + { + "level": "15.0", + "position": "90", + "micronsPerXPixel": "0.302", + "micronsPerYPixel": "0.302" + } + ] + }, + "tolerance": "1.0" + } +} \ No newline at end of file diff --git a/tests/test_data/test_oav_zoom_levels.xml b/tests/test_data/test_oav_zoom_levels.xml deleted file mode 100644 index d751fd69747..00000000000 --- a/tests/test_data/test_oav_zoom_levels.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - 1.0 - 0 - 2.87 - 2.87 - - - 2.5 - 10 - 2.31 - 2.31 - - - 5.0 - 25 - 1.58 - 1.58 - - - 7.5 - 50 - 0.806 - 0.806 - - - 10.0 - 75 - 0.438 - 0.438 - - - 15.0 - 90 - 0.302 - 0.302 - - -1.0 - From 77ed7ab0ff7587da4f2f1541582cb6d1b6039b36 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Fri, 21 Nov 2025 18:11:16 +0000 Subject: [PATCH 25/39] Read OAV config correctly --- src/dodal/devices/oav/oav_parameters.py | 36 ++++++++++++------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/dodal/devices/oav/oav_parameters.py b/src/dodal/devices/oav/oav_parameters.py index 94b2664cd56..e42ed30c858 100644 --- a/src/dodal/devices/oav/oav_parameters.py +++ b/src/dodal/devices/oav/oav_parameters.py @@ -1,7 +1,7 @@ from abc import abstractmethod from collections import ChainMap from dataclasses import dataclass -from typing import Any, Generic, TypeVar +from typing import Any, Generic, Literal, TypedDict, TypeVar from xml.etree import ElementTree from xml.etree.ElementTree import Element @@ -124,18 +124,21 @@ class OAVConfigBase(Generic[ParamType]): def __init__(self, zoom_params_file: str): self.zoom_params = self._get_zoom_params(zoom_params_file) - def _get_zoom_params(self, zoom_params_file: str): - tree = ElementTree.parse(zoom_params_file) - root = tree.getroot() - return root.findall(".//zoomLevel") + def _get_zoom_params(self, zoom_params_file: str) -> dict: + config_server = ConfigServer(url="https://daq-config.diamond.ac.uk") + return config_server.get_file_contents(zoom_params_file, dict)[ + "JCameraManSettings" + ] def _read_zoom_params(self) -> dict: um_per_pix = {} - for node in self.zoom_params: - zoom = str(_get_element_as_float(node, "level")) - um_pix_x = _get_element_as_float(node, "micronsPerXPixel") - um_pix_y = _get_element_as_float(node, "micronsPerYPixel") - um_per_pix[zoom] = (um_pix_x, um_pix_y) + zoom_levels: list[dict] = self.zoom_params["levels"]["zoomLevel"] + for level in zoom_levels: + zoom = level["level"] + um_per_pix[zoom] = ( + float(level["micronsPerXPixel"]), + float(level["micronsPerYPixel"]), + ) return um_per_pix @abstractmethod @@ -167,18 +170,13 @@ def __init__( super().__init__(zoom_params_file) def _get_display_config(self, display_config_file: str): - with open(display_config_file) as f: - file_lines = f.readlines() - return file_lines + config_server = ConfigServer(url="https://daq-config.diamond.ac.uk") + return config_server.get_file_contents(display_config_file, dict) def _read_display_config(self) -> dict: crosshairs = {} - for i in range(len(self.display_config)): - if self.display_config[i].startswith("zoomLevel"): - zoom = self.display_config[i].split(" = ")[1].strip() - x = int(self.display_config[i + 1].split(" = ")[1]) - y = int(self.display_config[i + 2].split(" = ")[1]) - crosshairs[zoom] = (x, y) + for zoom, values in self.display_config.items(): + crosshairs[zoom] = (values["crosshairX"], values["crosshairY"]) return crosshairs def get_parameters(self) -> dict[str, ZoomParamsCrosshair]: From cb965ae5e0209eff177dd95282343595c1ec1785 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Mon, 1 Dec 2025 16:10:35 +0000 Subject: [PATCH 26/39] Use pydantic models for config where appropriate --- src/dodal/devices/oav/oav_parameters.py | 9 +- tests/conftest.py | 4 +- .../test_data/test_display_configuration.json | 98 ++++++++++--------- tests/test_data/test_oav_zoom_levels.json | 2 +- 4 files changed, 57 insertions(+), 56 deletions(-) diff --git a/src/dodal/devices/oav/oav_parameters.py b/src/dodal/devices/oav/oav_parameters.py index e42ed30c858..7d622b7a56c 100644 --- a/src/dodal/devices/oav/oav_parameters.py +++ b/src/dodal/devices/oav/oav_parameters.py @@ -6,6 +6,7 @@ from xml.etree.ElementTree import Element from daq_config_server.client import ConfigServer +from daq_config_server.converters.models import DisplayConfig # GDA currently assumes this aspect ratio for the OAV window size. # For some beamline this doesn't affect anything as the actual OAV aspect ratio @@ -169,14 +170,14 @@ def __init__( self.display_config = self._get_display_config(display_config_file) super().__init__(zoom_params_file) - def _get_display_config(self, display_config_file: str): + def _get_display_config(self, display_config_file: str) -> DisplayConfig: config_server = ConfigServer(url="https://daq-config.diamond.ac.uk") - return config_server.get_file_contents(display_config_file, dict) + return config_server.get_file_contents(display_config_file, DisplayConfig) def _read_display_config(self) -> dict: crosshairs = {} - for zoom, values in self.display_config.items(): - crosshairs[zoom] = (values["crosshairX"], values["crosshairY"]) + for zoom, values in self.display_config.zoom_levels.items(): + crosshairs[str(zoom)] = (values.crosshairX, values.crosshairY) return crosshairs def get_parameters(self) -> dict[str, ZoomParamsCrosshair]: diff --git a/tests/conftest.py b/tests/conftest.py index 08c054091ff..b812641a0ce 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,9 +8,7 @@ from unittest.mock import MagicMock, patch import pytest -from ophyd_async.core import ( - PathProvider, -) +from ophyd_async.core import PathProvider from dodal.common.beamlines import beamline_parameters, beamline_utils from dodal.common.beamlines.beamline_utils import clear_path_provider diff --git a/tests/test_data/test_display_configuration.json b/tests/test_data/test_display_configuration.json index 30a5bce92a8..d05476cdc62 100644 --- a/tests/test_data/test_display_configuration.json +++ b/tests/test_data/test_display_configuration.json @@ -1,50 +1,52 @@ -{ - "1.0": { - "crosshairX": 477, - "crosshairY": 359, - "topLeftX": 383, - "topLeftY": 253, - "bottomRightX": 410, - "bottomRightY": 278 - }, - "2.5": { - "crosshairX": 493, - "crosshairY": 355, - "topLeftX": 340, - "topLeftY": 283, - "bottomRightX": 388, - "bottomRightY": 322 - }, - "5.0": { - "crosshairX": 517, - "crosshairY": 350, - "topLeftX": 268, - "topLeftY": 326, - "bottomRightX": 354, - "bottomRightY": 387 - }, - "7.5": { - "crosshairX": 549, - "crosshairY": 347, - "topLeftX": 248, - "topLeftY": 394, - "bottomRightX": 377, - "bottomRightY": 507 - }, - "10.0": { - "crosshairX": 613, - "crosshairY": 344, - "topLeftX": 2, - "topLeftY": 489, - "bottomRightX": 206, - "bottomRightY": 630 - }, - "15.0": { - "crosshairX": 693, - "crosshairY": 339, - "topLeftX": 1, - "topLeftY": 601, - "bottomRightX": 65, - "bottomRightY": 767 +{ + "zoom_levels": { + "1.0": { + "crosshairX": 477, + "crosshairY": 359, + "topLeftX": 383, + "topLeftY": 253, + "bottomRightX": 410, + "bottomRightY": 278 + }, + "2.5": { + "crosshairX": 493, + "crosshairY": 355, + "topLeftX": 340, + "topLeftY": 283, + "bottomRightX": 388, + "bottomRightY": 322 + }, + "5.0": { + "crosshairX": 517, + "crosshairY": 350, + "topLeftX": 268, + "topLeftY": 326, + "bottomRightX": 354, + "bottomRightY": 387 + }, + "7.5": { + "crosshairX": 549, + "crosshairY": 347, + "topLeftX": 248, + "topLeftY": 394, + "bottomRightX": 377, + "bottomRightY": 507 + }, + "10.0": { + "crosshairX": 613, + "crosshairY": 344, + "topLeftX": 2, + "topLeftY": 489, + "bottomRightX": 206, + "bottomRightY": 630 + }, + "15.0": { + "crosshairX": 693, + "crosshairY": 339, + "topLeftX": 1, + "topLeftY": 601, + "bottomRightX": 65, + "bottomRightY": 767 + } } } diff --git a/tests/test_data/test_oav_zoom_levels.json b/tests/test_data/test_oav_zoom_levels.json index 35030dc6b07..843414a3b86 100644 --- a/tests/test_data/test_oav_zoom_levels.json +++ b/tests/test_data/test_oav_zoom_levels.json @@ -42,4 +42,4 @@ }, "tolerance": "1.0" } -} \ No newline at end of file +} From 19525bed5d1826b04cd95e01da1f0ecddff55827 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Thu, 11 Dec 2025 14:24:52 +0000 Subject: [PATCH 27/39] Add mock config server --- src/dodal/testing/__init__.py | 3 + .../testing/mock_config_server/__init__.py | 0 .../mock_config_server/mock_config_server.py | 47 +++++ .../mock_config_server/mock_oav_config.py | 182 ++++++++++++++++++ tests/conftest.py | 18 ++ 5 files changed, 250 insertions(+) create mode 100644 src/dodal/testing/mock_config_server/__init__.py create mode 100644 src/dodal/testing/mock_config_server/mock_config_server.py create mode 100644 src/dodal/testing/mock_config_server/mock_oav_config.py diff --git a/src/dodal/testing/__init__.py b/src/dodal/testing/__init__.py index e69de29bb2d..28899173041 100644 --- a/src/dodal/testing/__init__.py +++ b/src/dodal/testing/__init__.py @@ -0,0 +1,3 @@ +from .mock_config_server.mock_config_server import MockConfigServer + +__all__ = ["MockConfigServer"] diff --git a/src/dodal/testing/mock_config_server/__init__.py b/src/dodal/testing/mock_config_server/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/dodal/testing/mock_config_server/mock_config_server.py b/src/dodal/testing/mock_config_server/mock_config_server.py new file mode 100644 index 00000000000..f50aeb449e2 --- /dev/null +++ b/src/dodal/testing/mock_config_server/mock_config_server.py @@ -0,0 +1,47 @@ +import json +from pathlib import Path +from typing import Any + +from daq_config_server.client import ConfigServer +from daq_config_server.converters.models import ( + ConfigModel, +) + +from .mock_oav_config import MockOavConfig + + +class MockConfigServer(ConfigServer): + def get_file_contents( + self, + file_path: str | Path, + desired_return_type: type[Any] = str, + reset_cached_result: bool = False, + ) -> Any: + print() + if str(file_path).endswith("display.configuration"): + return MockOavConfig.get_display_config() + elif str(file_path).endswith("jCameraManZoomLevels.xml"): + return MockOavConfig.get_zoom_params_file() + elif str(file_path).endswith("OAVCentring.json"): + return MockOavConfig.get_oav_config_json() + else: + return self._fake_config_server_read( + file_path, desired_return_type, reset_cached_result + ) + + def _fake_config_server_read( + self, + filepath: str | Path, + desired_return_type: type[str] | type[dict] | ConfigModel = str, + reset_cached_result: bool = False, + ) -> Any: + filepath = Path(filepath) + # Minimal logic required for unit tests + with filepath.open("r") as f: + contents = f.read() + if desired_return_type is str: + return contents + elif desired_return_type is dict: + return json.loads(contents) + elif issubclass(desired_return_type, ConfigModel): # type: ignore + return desired_return_type.model_validate(json.loads(contents)) diff --git a/src/dodal/testing/mock_config_server/mock_oav_config.py b/src/dodal/testing/mock_config_server/mock_oav_config.py new file mode 100644 index 00000000000..98a031f95a4 --- /dev/null +++ b/src/dodal/testing/mock_config_server/mock_oav_config.py @@ -0,0 +1,182 @@ +from daq_config_server.converters.models import ( + DisplayConfig, + DisplayConfigData, +) + + +class MockOavConfig: + @staticmethod + def get_oav_config_json(): + return { + "exposure": 0.075, + "acqPeriod": 0.05, + "gain": 1.0, + "minheight": 70, + "oav": "OAV", + "mxsc_input": "CAM", + "min_callback_time": 0.080, + "close_ksize": 11, + "direction": 0, + "pinTipCentring": { + "zoom": 1.0, + "preprocess": 8, + "preProcessKSize": 21, + "CannyEdgeUpperThreshold": 20.0, + "CannyEdgeLowerThreshold": 5.0, + "brightness": 20, + "max_tip_distance": 300, + "mxsc_input": "proc", + "minheight": 10, + "min_callback_time": 0.15, + "filename": "/dls_sw/prod/R3.14.12.7/support/adPython/2-1-11/adPythonApp/scripts/adPythonMxSampleDetect.py", + }, + "loopCentring": { + "zoom": 5.0, + "preprocess": 8, + "preProcessKSize": 21, + "CannyEdgeUpperThreshold": 20.0, + "CannyEdgeLowerThreshold": 5.0, + "brightness": 20, + "filename": "/dls_sw/prod/R3.14.12.7/support/adPython/2-1-11/adPythonApp/scripts/adPythonMxSampleDetect.py", + "max_tip_distance": 300, + "minheight": 10, + }, + "xrayCentring": { + "zoom": 7.5, + "preprocess": 8, + "preProcessKSize": 31, + "CannyEdgeUpperThreshold": 30.0, + "CannyEdgeLowerThreshold": 5.0, + "close_ksize": 3, + "filename": "/dls_sw/prod/R3.14.12.7/support/adPython/2-1-11/adPythonApp/scripts/adPythonMxSampleDetect.py", + "brightness": 80, + }, + "rotationAxisAlign": { + "zoom": 10.0, + "preprocess": 8, + "preProcessKSize": 21, + "CannyEdgeUpperThreshold": 20.0, + "CannyEdgeLowerThreshold": 5.0, + "filename": "/dls_sw/prod/R3.14.12.7/support/adPython/2-1-11/adPythonApp/scripts/adPythonMxSampleDetect.py", + "brightness": 100, + }, + "SmargonOffsets1": { + "zoom": 1.0, + "preprocess": 8, + "preProcessKSize": 21, + "CannyEdgeUpperThreshold": 50.0, + "CannyEdgeLowerThreshold": 5.0, + "brightness": 80, + }, + "SmargonOffsets2": { + "zoom": 5.0, + "preprocess": 8, + "preProcessKSize": 11, + "CannyEdgeUpperThreshold": 50.0, + "CannyEdgeLowerThreshold": 5.0, + "brightness": 90, + }, + } + + @staticmethod + def get_zoom_params_file(): + return { + "JCameraManSettings": { + "levels": { + "zoomLevel": [ + { + "level": "1.0", + "position": "0", + "micronsPerXPixel": "2.87", + "micronsPerYPixel": "2.87", + }, + { + "level": "2.5", + "position": "10", + "micronsPerXPixel": "2.31", + "micronsPerYPixel": "2.31", + }, + { + "level": "5.0", + "position": "25", + "micronsPerXPixel": "1.58", + "micronsPerYPixel": "1.58", + }, + { + "level": "7.5", + "position": "50", + "micronsPerXPixel": "0.806", + "micronsPerYPixel": "0.806", + }, + { + "level": "10.0", + "position": "75", + "micronsPerXPixel": "0.438", + "micronsPerYPixel": "0.438", + }, + { + "level": "15.0", + "position": "90", + "micronsPerXPixel": "0.302", + "micronsPerYPixel": "0.302", + }, + ] + }, + "tolerance": "1.0", + } + } + + @staticmethod + def get_display_config(): + return DisplayConfig( + zoom_levels={ + 1.0: DisplayConfigData( + crosshairX=397, + crosshairY=373, + topLeftX=383, + topLeftY=253, + bottomRightX=410, + bottomRightY=278, + ), + 2.5: DisplayConfigData( + crosshairX=413, + crosshairY=368, + topLeftX=340, + topLeftY=283, + bottomRightX=388, + bottomRightY=322, + ), + 5.0: DisplayConfigData( + crosshairX=517, + crosshairY=350, + topLeftX=268, + topLeftY=326, + bottomRightX=354, + bottomRightY=387, + ), + 7.5: DisplayConfigData( + crosshairX=452, + crosshairY=345, + topLeftX=248, + topLeftY=394, + bottomRightX=377, + bottomRightY=507, + ), + 10.0: DisplayConfigData( + crosshairX=502, + crosshairY=350, + topLeftX=2, + topLeftY=489, + bottomRightX=206, + bottomRightY=630, + ), + 15.0: DisplayConfigData( + crosshairX=642, + crosshairY=310, + topLeftX=1, + topLeftY=601, + bottomRightX=65, + bottomRightY=767, + ), + }, + ) diff --git a/tests/conftest.py b/tests/conftest.py index b812641a0ce..430c4abb4f5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -22,6 +22,7 @@ from dodal.devices.detector import DetectorParams from dodal.devices.detector.det_dim_constants import EIGER2_X_16M_SIZE from dodal.log import LOGGER, GELFTCPHandler, set_up_all_logging_handlers +from dodal.testing import MockConfigServer from dodal.utils import ( DeviceInitializationController, collect_factories, @@ -175,3 +176,20 @@ def eiger_params(tmp_path: Path) -> DetectorParams: @pytest.fixture(autouse=True) def clear_cache(): get_config_client.cache_clear() + + +IMPLEMENTED_CONFIG_CLIENTS = [] + + +@pytest.fixture(autouse=True) +def mock_config_server(): + # Don't actually talk to central service during unit tests, and reset caches between test + + for client in IMPLEMENTED_CONFIG_CLIENTS: + client.cache_clear() # type: ignore - currently no option for "cachable" static type + + with patch( + "daq_config_server.client.ConfigServer.get_file_contents", + side_effect=MockConfigServer().get_file_contents, + ): + yield From d095ba8c56702ea7ea094387510ae1f795fd71bd Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Thu, 11 Dec 2025 15:28:38 +0000 Subject: [PATCH 28/39] Use mock config server in tests and delete test config files --- src/dodal/testing/__init__.py | 3 +- .../oav/test_data/test_OAVCentring.json | 70 ------------------- tests/test_data/__init__.py | 4 +- .../test_data/test_display_configuration.json | 52 -------------- tests/test_data/test_oav_zoom_levels.json | 45 ------------ 5 files changed, 4 insertions(+), 170 deletions(-) delete mode 100644 tests/devices/oav/test_data/test_OAVCentring.json delete mode 100644 tests/test_data/test_display_configuration.json delete mode 100644 tests/test_data/test_oav_zoom_levels.json diff --git a/src/dodal/testing/__init__.py b/src/dodal/testing/__init__.py index 28899173041..bbe02de4674 100644 --- a/src/dodal/testing/__init__.py +++ b/src/dodal/testing/__init__.py @@ -1,3 +1,4 @@ from .mock_config_server.mock_config_server import MockConfigServer +from .mock_config_server.mock_oav_config import MockOavConfig -__all__ = ["MockConfigServer"] +__all__ = ["MockConfigServer", "MockOavConfig"] diff --git a/tests/devices/oav/test_data/test_OAVCentring.json b/tests/devices/oav/test_data/test_OAVCentring.json deleted file mode 100644 index d4002b0eff1..00000000000 --- a/tests/devices/oav/test_data/test_OAVCentring.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "exposure": 0.075, - "acqPeriod": 0.05, - "gain": 1.0, - "minheight": 70, - "oav": "OAV", - "mxsc_input": "CAM", - "min_callback_time": 0.080, - "close_ksize": 11, - "direction": 0, - "pinTipCentring": { - "zoom": 1.0, - "preprocess": 8, - "preProcessKSize": 21, - "CannyEdgeUpperThreshold": 20.0, - "CannyEdgeLowerThreshold": 5.0, - "brightness": 20, - "max_tip_distance": 300, - "mxsc_input": "proc", - "minheight": 10, - "min_callback_time": 0.15, - "filename": "/dls_sw/prod/R3.14.12.7/support/adPython/2-1-11/adPythonApp/scripts/adPythonMxSampleDetect.py" - }, - "loopCentring": { - "zoom": 5.0, - "preprocess": 8, - "preProcessKSize": 21, - "CannyEdgeUpperThreshold": 20.0, - "CannyEdgeLowerThreshold": 5.0, - "brightness": 20, - "filename": "/dls_sw/prod/R3.14.12.7/support/adPython/2-1-11/adPythonApp/scripts/adPythonMxSampleDetect.py", - "max_tip_distance": 300, - "minheight": 10 - }, - "xrayCentring": { - "zoom": 7.5, - "preprocess": 8, - "preProcessKSize": 31, - "CannyEdgeUpperThreshold": 30.0, - "CannyEdgeLowerThreshold": 5.0, - "close_ksize": 3, - "filename": "/dls_sw/prod/R3.14.12.7/support/adPython/2-1-11/adPythonApp/scripts/adPythonMxSampleDetect.py", - "brightness": 80 - }, - "rotationAxisAlign": { - "zoom": 10.0, - "preprocess": 8, - "preProcessKSize": 21, - "CannyEdgeUpperThreshold": 20.0, - "CannyEdgeLowerThreshold": 5.0, - "filename": "/dls_sw/prod/R3.14.12.7/support/adPython/2-1-11/adPythonApp/scripts/adPythonMxSampleDetect.py", - "brightness": 100 - }, - "SmargonOffsets1": { - "zoom": 1.0, - "preprocess": 8, - "preProcessKSize": 21, - "CannyEdgeUpperThreshold": 50.0, - "CannyEdgeLowerThreshold": 5.0, - "brightness": 80 - }, - "SmargonOffsets2": { - "zoom": 5.0, - "preprocess": 8, - "preProcessKSize": 11, - "CannyEdgeUpperThreshold": 50.0, - "CannyEdgeLowerThreshold": 5.0, - "brightness": 90 - } -} diff --git a/tests/test_data/__init__.py b/tests/test_data/__init__.py index 64cd7a5a988..a4650faa744 100644 --- a/tests/test_data/__init__.py +++ b/tests/test_data/__init__.py @@ -5,8 +5,8 @@ BAD_BEAMLINE_PARAMETERS = join(TEST_DATA_PATH, "bad_beamlineParameters") I04_BEAMLINE_PARAMETERS = join(TEST_DATA_PATH, "i04_beamlineParameters") TEST_BEAMLINE_PARAMETERS_TXT = join(TEST_DATA_PATH, "test_beamline_parameters.txt") -TEST_DISPLAY_CONFIG = join(TEST_DATA_PATH, "test_display_configuration.json") -TEST_OAV_ZOOM_LEVELS = join(TEST_DATA_PATH, "test_oav_zoom_levels.json") +TEST_DISPLAY_CONFIG = "test_display.configuration" +TEST_OAV_ZOOM_LEVELS = "jCameraManZoomLevels.xml" __all__ = [ "BAD_BEAMLINE_PARAMETERS", diff --git a/tests/test_data/test_display_configuration.json b/tests/test_data/test_display_configuration.json deleted file mode 100644 index d05476cdc62..00000000000 --- a/tests/test_data/test_display_configuration.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "zoom_levels": { - "1.0": { - "crosshairX": 477, - "crosshairY": 359, - "topLeftX": 383, - "topLeftY": 253, - "bottomRightX": 410, - "bottomRightY": 278 - }, - "2.5": { - "crosshairX": 493, - "crosshairY": 355, - "topLeftX": 340, - "topLeftY": 283, - "bottomRightX": 388, - "bottomRightY": 322 - }, - "5.0": { - "crosshairX": 517, - "crosshairY": 350, - "topLeftX": 268, - "topLeftY": 326, - "bottomRightX": 354, - "bottomRightY": 387 - }, - "7.5": { - "crosshairX": 549, - "crosshairY": 347, - "topLeftX": 248, - "topLeftY": 394, - "bottomRightX": 377, - "bottomRightY": 507 - }, - "10.0": { - "crosshairX": 613, - "crosshairY": 344, - "topLeftX": 2, - "topLeftY": 489, - "bottomRightX": 206, - "bottomRightY": 630 - }, - "15.0": { - "crosshairX": 693, - "crosshairY": 339, - "topLeftX": 1, - "topLeftY": 601, - "bottomRightX": 65, - "bottomRightY": 767 - } - } -} diff --git a/tests/test_data/test_oav_zoom_levels.json b/tests/test_data/test_oav_zoom_levels.json deleted file mode 100644 index 843414a3b86..00000000000 --- a/tests/test_data/test_oav_zoom_levels.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "JCameraManSettings": { - "levels": { - "zoomLevel": [ - { - "level": "1.0", - "position": "0", - "micronsPerXPixel": "2.87", - "micronsPerYPixel": "2.87" - }, - { - "level": "2.5", - "position": "10", - "micronsPerXPixel": "2.31", - "micronsPerYPixel": "2.31" - }, - { - "level": "5.0", - "position": "25", - "micronsPerXPixel": "1.58", - "micronsPerYPixel": "1.58" - }, - { - "level": "7.5", - "position": "50", - "micronsPerXPixel": "0.806", - "micronsPerYPixel": "0.806" - }, - { - "level": "10.0", - "position": "75", - "micronsPerXPixel": "0.438", - "micronsPerYPixel": "0.438" - }, - { - "level": "15.0", - "position": "90", - "micronsPerXPixel": "0.302", - "micronsPerYPixel": "0.302" - } - ] - }, - "tolerance": "1.0" - } -} From 8718ae3f2bb4db43a2bd5861177bc9ab5a162faa Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Thu, 11 Dec 2025 16:07:04 +0000 Subject: [PATCH 29/39] Fix tests --- .../mock_config_server/mock_oav_config.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/dodal/testing/mock_config_server/mock_oav_config.py b/src/dodal/testing/mock_config_server/mock_oav_config.py index 98a031f95a4..6e1ba5860f0 100644 --- a/src/dodal/testing/mock_config_server/mock_oav_config.py +++ b/src/dodal/testing/mock_config_server/mock_oav_config.py @@ -131,16 +131,16 @@ def get_display_config(): return DisplayConfig( zoom_levels={ 1.0: DisplayConfigData( - crosshairX=397, - crosshairY=373, + crosshairX=477, + crosshairY=359, topLeftX=383, topLeftY=253, bottomRightX=410, bottomRightY=278, ), 2.5: DisplayConfigData( - crosshairX=413, - crosshairY=368, + crosshairX=493, + crosshairY=355, topLeftX=340, topLeftY=283, bottomRightX=388, @@ -155,24 +155,24 @@ def get_display_config(): bottomRightY=387, ), 7.5: DisplayConfigData( - crosshairX=452, - crosshairY=345, + crosshairX=549, + crosshairY=437, topLeftX=248, topLeftY=394, bottomRightX=377, bottomRightY=507, ), 10.0: DisplayConfigData( - crosshairX=502, - crosshairY=350, + crosshairX=613, + crosshairY=344, topLeftX=2, topLeftY=489, bottomRightX=206, bottomRightY=630, ), 15.0: DisplayConfigData( - crosshairX=642, - crosshairY=310, + crosshairX=693, + crosshairY=339, topLeftX=1, topLeftY=601, bottomRightX=65, From d5374a31398fe6d9eb5df2402b2272bef147cc0a Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Thu, 11 Dec 2025 16:17:47 +0000 Subject: [PATCH 30/39] tidy --- tests/devices/oav/test_data/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/devices/oav/test_data/__init__.py b/tests/devices/oav/test_data/__init__.py index 204c5af0f2e..3411c4666cc 100644 --- a/tests/devices/oav/test_data/__init__.py +++ b/tests/devices/oav/test_data/__init__.py @@ -2,7 +2,7 @@ from pathlib import Path TEST_DATA_PATH = Path(__file__).parent -TEST_OAV_CENTRING_JSON = join(TEST_DATA_PATH, "test_OAVCentring.json") +TEST_OAV_CENTRING_JSON = "test_OAVCentring.json" OAV_SNAPSHOT_TEST_PNG = join(TEST_DATA_PATH, "oav_snapshot_test.png") OAV_SNAPSHOT_EXPECTED_PNG = join(TEST_DATA_PATH, "oav_snapshot_expected.png") From 9c3ba7ba476ad74e8c2e09f110c0315f75d6da1f Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Fri, 12 Dec 2025 10:37:07 +0000 Subject: [PATCH 31/39] Fix imports --- src/dodal/devices/oav/oav_parameters.py | 2 +- src/dodal/testing/mock_config_server/mock_config_server.py | 2 +- src/dodal/testing/mock_config_server/mock_oav_config.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dodal/devices/oav/oav_parameters.py b/src/dodal/devices/oav/oav_parameters.py index 7d622b7a56c..fd29e587020 100644 --- a/src/dodal/devices/oav/oav_parameters.py +++ b/src/dodal/devices/oav/oav_parameters.py @@ -6,7 +6,7 @@ from xml.etree.ElementTree import Element from daq_config_server.client import ConfigServer -from daq_config_server.converters.models import DisplayConfig +from daq_config_server.models import DisplayConfig # GDA currently assumes this aspect ratio for the OAV window size. # For some beamline this doesn't affect anything as the actual OAV aspect ratio diff --git a/src/dodal/testing/mock_config_server/mock_config_server.py b/src/dodal/testing/mock_config_server/mock_config_server.py index f50aeb449e2..1868b352e2a 100644 --- a/src/dodal/testing/mock_config_server/mock_config_server.py +++ b/src/dodal/testing/mock_config_server/mock_config_server.py @@ -3,7 +3,7 @@ from typing import Any from daq_config_server.client import ConfigServer -from daq_config_server.converters.models import ( +from daq_config_server.models import ( ConfigModel, ) diff --git a/src/dodal/testing/mock_config_server/mock_oav_config.py b/src/dodal/testing/mock_config_server/mock_oav_config.py index 6e1ba5860f0..cab53d42d6d 100644 --- a/src/dodal/testing/mock_config_server/mock_oav_config.py +++ b/src/dodal/testing/mock_config_server/mock_oav_config.py @@ -1,4 +1,4 @@ -from daq_config_server.converters.models import ( +from daq_config_server.models import ( DisplayConfig, DisplayConfigData, ) From 4f76bcfa7fada5441d2a642ed894ded8a9f763c2 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Fri, 16 Jan 2026 16:25:46 +0000 Subject: [PATCH 32/39] Fix for github.com/DiamondLightSource/daq-config-server/pull/156 --- src/dodal/devices/oav/oav_parameters.py | 2 +- .../mock_config_server/mock_oav_config.py | 72 +++++++++---------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/dodal/devices/oav/oav_parameters.py b/src/dodal/devices/oav/oav_parameters.py index fd29e587020..31c9fe42aad 100644 --- a/src/dodal/devices/oav/oav_parameters.py +++ b/src/dodal/devices/oav/oav_parameters.py @@ -177,7 +177,7 @@ def _get_display_config(self, display_config_file: str) -> DisplayConfig: def _read_display_config(self) -> dict: crosshairs = {} for zoom, values in self.display_config.zoom_levels.items(): - crosshairs[str(zoom)] = (values.crosshairX, values.crosshairY) + crosshairs[str(zoom)] = (values.crosshair_x, values.crosshair_y) return crosshairs def get_parameters(self) -> dict[str, ZoomParamsCrosshair]: diff --git a/src/dodal/testing/mock_config_server/mock_oav_config.py b/src/dodal/testing/mock_config_server/mock_oav_config.py index cab53d42d6d..636fb8581fd 100644 --- a/src/dodal/testing/mock_config_server/mock_oav_config.py +++ b/src/dodal/testing/mock_config_server/mock_oav_config.py @@ -131,52 +131,52 @@ def get_display_config(): return DisplayConfig( zoom_levels={ 1.0: DisplayConfigData( - crosshairX=477, - crosshairY=359, - topLeftX=383, - topLeftY=253, - bottomRightX=410, - bottomRightY=278, + crosshair_x=477, + crosshair_y=359, + top_left_x=383, + top_left_y=253, + bottom_right_x=410, + bottom_right_y=278, ), 2.5: DisplayConfigData( - crosshairX=493, - crosshairY=355, - topLeftX=340, - topLeftY=283, - bottomRightX=388, - bottomRightY=322, + crosshair_x=493, + crosshair_y=355, + top_left_x=340, + top_left_y=283, + bottom_right_x=388, + bottom_right_y=322, ), 5.0: DisplayConfigData( - crosshairX=517, - crosshairY=350, - topLeftX=268, - topLeftY=326, - bottomRightX=354, - bottomRightY=387, + crosshair_x=517, + crosshair_y=350, + top_left_x=268, + top_left_y=326, + bottom_right_x=354, + bottom_right_y=387, ), 7.5: DisplayConfigData( - crosshairX=549, - crosshairY=437, - topLeftX=248, - topLeftY=394, - bottomRightX=377, - bottomRightY=507, + crosshair_x=549, + crosshair_y=437, + top_left_x=248, + top_left_y=394, + bottom_right_x=377, + bottom_right_y=507, ), 10.0: DisplayConfigData( - crosshairX=613, - crosshairY=344, - topLeftX=2, - topLeftY=489, - bottomRightX=206, - bottomRightY=630, + crosshair_x=613, + crosshair_y=344, + top_left_x=2, + top_left_y=489, + bottom_right_x=206, + bottom_right_y=630, ), 15.0: DisplayConfigData( - crosshairX=693, - crosshairY=339, - topLeftX=1, - topLeftY=601, - bottomRightX=65, - bottomRightY=767, + crosshair_x=693, + crosshair_y=339, + top_left_x=1, + top_left_y=601, + bottom_right_x=65, + bottom_right_y=767, ), }, ) From 2743475bd9c6084f326d6e34770e3a8e8a43e242 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Fri, 13 Mar 2026 11:09:41 +0000 Subject: [PATCH 33/39] Move config files back --- src/dodal/testing/__init__.py | 4 - .../testing/mock_config_server/__init__.py | 0 .../mock_config_server/mock_config_server.py | 47 ----- .../mock_config_server/mock_oav_config.py | 182 ------------------ system_tests/test_oav_system.py | 5 +- tests/conftest.py | 21 +- tests/devices/oav/conftest.py | 2 +- tests/devices/oav/test_data/__init__.py | 4 +- .../oav/test_data/jCameraManZoomLevels.xml | 45 +++++ .../oav/test_data/test_OAVCentring.json | 70 +++++++ .../oav/test_data/test_display.configuration | 52 +++++ tests/devices/oav/test_oav.py | 5 +- tests/devices/oav/test_oav_parameters.py | 4 +- tests/test_data/__init__.py | 5 +- uv.lock | 6 +- 15 files changed, 180 insertions(+), 272 deletions(-) delete mode 100644 src/dodal/testing/mock_config_server/__init__.py delete mode 100644 src/dodal/testing/mock_config_server/mock_config_server.py delete mode 100644 src/dodal/testing/mock_config_server/mock_oav_config.py create mode 100644 tests/devices/oav/test_data/jCameraManZoomLevels.xml create mode 100644 tests/devices/oav/test_data/test_OAVCentring.json create mode 100644 tests/devices/oav/test_data/test_display.configuration diff --git a/src/dodal/testing/__init__.py b/src/dodal/testing/__init__.py index bbe02de4674..e69de29bb2d 100644 --- a/src/dodal/testing/__init__.py +++ b/src/dodal/testing/__init__.py @@ -1,4 +0,0 @@ -from .mock_config_server.mock_config_server import MockConfigServer -from .mock_config_server.mock_oav_config import MockOavConfig - -__all__ = ["MockConfigServer", "MockOavConfig"] diff --git a/src/dodal/testing/mock_config_server/__init__.py b/src/dodal/testing/mock_config_server/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/dodal/testing/mock_config_server/mock_config_server.py b/src/dodal/testing/mock_config_server/mock_config_server.py deleted file mode 100644 index 1868b352e2a..00000000000 --- a/src/dodal/testing/mock_config_server/mock_config_server.py +++ /dev/null @@ -1,47 +0,0 @@ -import json -from pathlib import Path -from typing import Any - -from daq_config_server.client import ConfigServer -from daq_config_server.models import ( - ConfigModel, -) - -from .mock_oav_config import MockOavConfig - - -class MockConfigServer(ConfigServer): - def get_file_contents( - self, - file_path: str | Path, - desired_return_type: type[Any] = str, - reset_cached_result: bool = False, - ) -> Any: - print() - if str(file_path).endswith("display.configuration"): - return MockOavConfig.get_display_config() - elif str(file_path).endswith("jCameraManZoomLevels.xml"): - return MockOavConfig.get_zoom_params_file() - elif str(file_path).endswith("OAVCentring.json"): - return MockOavConfig.get_oav_config_json() - else: - return self._fake_config_server_read( - file_path, desired_return_type, reset_cached_result - ) - - def _fake_config_server_read( - self, - filepath: str | Path, - desired_return_type: type[str] | type[dict] | ConfigModel = str, - reset_cached_result: bool = False, - ) -> Any: - filepath = Path(filepath) - # Minimal logic required for unit tests - with filepath.open("r") as f: - contents = f.read() - if desired_return_type is str: - return contents - elif desired_return_type is dict: - return json.loads(contents) - elif issubclass(desired_return_type, ConfigModel): # type: ignore - return desired_return_type.model_validate(json.loads(contents)) diff --git a/src/dodal/testing/mock_config_server/mock_oav_config.py b/src/dodal/testing/mock_config_server/mock_oav_config.py deleted file mode 100644 index 636fb8581fd..00000000000 --- a/src/dodal/testing/mock_config_server/mock_oav_config.py +++ /dev/null @@ -1,182 +0,0 @@ -from daq_config_server.models import ( - DisplayConfig, - DisplayConfigData, -) - - -class MockOavConfig: - @staticmethod - def get_oav_config_json(): - return { - "exposure": 0.075, - "acqPeriod": 0.05, - "gain": 1.0, - "minheight": 70, - "oav": "OAV", - "mxsc_input": "CAM", - "min_callback_time": 0.080, - "close_ksize": 11, - "direction": 0, - "pinTipCentring": { - "zoom": 1.0, - "preprocess": 8, - "preProcessKSize": 21, - "CannyEdgeUpperThreshold": 20.0, - "CannyEdgeLowerThreshold": 5.0, - "brightness": 20, - "max_tip_distance": 300, - "mxsc_input": "proc", - "minheight": 10, - "min_callback_time": 0.15, - "filename": "/dls_sw/prod/R3.14.12.7/support/adPython/2-1-11/adPythonApp/scripts/adPythonMxSampleDetect.py", - }, - "loopCentring": { - "zoom": 5.0, - "preprocess": 8, - "preProcessKSize": 21, - "CannyEdgeUpperThreshold": 20.0, - "CannyEdgeLowerThreshold": 5.0, - "brightness": 20, - "filename": "/dls_sw/prod/R3.14.12.7/support/adPython/2-1-11/adPythonApp/scripts/adPythonMxSampleDetect.py", - "max_tip_distance": 300, - "minheight": 10, - }, - "xrayCentring": { - "zoom": 7.5, - "preprocess": 8, - "preProcessKSize": 31, - "CannyEdgeUpperThreshold": 30.0, - "CannyEdgeLowerThreshold": 5.0, - "close_ksize": 3, - "filename": "/dls_sw/prod/R3.14.12.7/support/adPython/2-1-11/adPythonApp/scripts/adPythonMxSampleDetect.py", - "brightness": 80, - }, - "rotationAxisAlign": { - "zoom": 10.0, - "preprocess": 8, - "preProcessKSize": 21, - "CannyEdgeUpperThreshold": 20.0, - "CannyEdgeLowerThreshold": 5.0, - "filename": "/dls_sw/prod/R3.14.12.7/support/adPython/2-1-11/adPythonApp/scripts/adPythonMxSampleDetect.py", - "brightness": 100, - }, - "SmargonOffsets1": { - "zoom": 1.0, - "preprocess": 8, - "preProcessKSize": 21, - "CannyEdgeUpperThreshold": 50.0, - "CannyEdgeLowerThreshold": 5.0, - "brightness": 80, - }, - "SmargonOffsets2": { - "zoom": 5.0, - "preprocess": 8, - "preProcessKSize": 11, - "CannyEdgeUpperThreshold": 50.0, - "CannyEdgeLowerThreshold": 5.0, - "brightness": 90, - }, - } - - @staticmethod - def get_zoom_params_file(): - return { - "JCameraManSettings": { - "levels": { - "zoomLevel": [ - { - "level": "1.0", - "position": "0", - "micronsPerXPixel": "2.87", - "micronsPerYPixel": "2.87", - }, - { - "level": "2.5", - "position": "10", - "micronsPerXPixel": "2.31", - "micronsPerYPixel": "2.31", - }, - { - "level": "5.0", - "position": "25", - "micronsPerXPixel": "1.58", - "micronsPerYPixel": "1.58", - }, - { - "level": "7.5", - "position": "50", - "micronsPerXPixel": "0.806", - "micronsPerYPixel": "0.806", - }, - { - "level": "10.0", - "position": "75", - "micronsPerXPixel": "0.438", - "micronsPerYPixel": "0.438", - }, - { - "level": "15.0", - "position": "90", - "micronsPerXPixel": "0.302", - "micronsPerYPixel": "0.302", - }, - ] - }, - "tolerance": "1.0", - } - } - - @staticmethod - def get_display_config(): - return DisplayConfig( - zoom_levels={ - 1.0: DisplayConfigData( - crosshair_x=477, - crosshair_y=359, - top_left_x=383, - top_left_y=253, - bottom_right_x=410, - bottom_right_y=278, - ), - 2.5: DisplayConfigData( - crosshair_x=493, - crosshair_y=355, - top_left_x=340, - top_left_y=283, - bottom_right_x=388, - bottom_right_y=322, - ), - 5.0: DisplayConfigData( - crosshair_x=517, - crosshair_y=350, - top_left_x=268, - top_left_y=326, - bottom_right_x=354, - bottom_right_y=387, - ), - 7.5: DisplayConfigData( - crosshair_x=549, - crosshair_y=437, - top_left_x=248, - top_left_y=394, - bottom_right_x=377, - bottom_right_y=507, - ), - 10.0: DisplayConfigData( - crosshair_x=613, - crosshair_y=344, - top_left_x=2, - top_left_y=489, - bottom_right_x=206, - bottom_right_y=630, - ), - 15.0: DisplayConfigData( - crosshair_x=693, - crosshair_y=339, - top_left_x=1, - top_left_y=601, - bottom_right_x=65, - bottom_right_y=767, - ), - }, - ) diff --git a/system_tests/test_oav_system.py b/system_tests/test_oav_system.py index 0941389cc3a..2e4c57602ec 100644 --- a/system_tests/test_oav_system.py +++ b/system_tests/test_oav_system.py @@ -2,10 +2,7 @@ import pytest from bluesky.run_engine import RunEngine from ophyd_async.core import init_devices -from tests.test_data import ( - TEST_DISPLAY_CONFIG, - TEST_OAV_ZOOM_LEVELS, -) +from tests.devices.oav.test_data import TEST_DISPLAY_CONFIG, TEST_OAV_ZOOM_LEVELS from dodal.devices.oav.oav_detector import OAV, OAVConfig diff --git a/tests/conftest.py b/tests/conftest.py index 430c4abb4f5..8dd32cecefb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -22,19 +22,17 @@ from dodal.devices.detector import DetectorParams from dodal.devices.detector.det_dim_constants import EIGER2_X_16M_SIZE from dodal.log import LOGGER, GELFTCPHandler, set_up_all_logging_handlers -from dodal.testing import MockConfigServer from dodal.utils import ( DeviceInitializationController, collect_factories, make_all_devices, ) from tests.devices.beamlines.i10.test_data import LOOKUP_TABLE_PATH +from tests.devices.oav.test_data import TEST_DISPLAY_CONFIG, TEST_OAV_ZOOM_LEVELS from tests.devices.test_daq_configuration import MOCK_DAQ_CONFIG_PATH from tests.devices.test_data import TEST_LUT_TXT from tests.test_data import ( I04_BEAMLINE_PARAMETERS, - TEST_DISPLAY_CONFIG, - TEST_OAV_ZOOM_LEVELS, ) MOCK_PATHS = [ @@ -176,20 +174,3 @@ def eiger_params(tmp_path: Path) -> DetectorParams: @pytest.fixture(autouse=True) def clear_cache(): get_config_client.cache_clear() - - -IMPLEMENTED_CONFIG_CLIENTS = [] - - -@pytest.fixture(autouse=True) -def mock_config_server(): - # Don't actually talk to central service during unit tests, and reset caches between test - - for client in IMPLEMENTED_CONFIG_CLIENTS: - client.cache_clear() # type: ignore - currently no option for "cachable" static type - - with patch( - "daq_config_server.client.ConfigServer.get_file_contents", - side_effect=MockConfigServer().get_file_contents, - ): - yield diff --git a/tests/devices/oav/conftest.py b/tests/devices/oav/conftest.py index 1a28f5cd788..b5db2cb4b33 100644 --- a/tests/devices/oav/conftest.py +++ b/tests/devices/oav/conftest.py @@ -5,7 +5,7 @@ from dodal.devices.oav.oav_detector import OAVBeamCentreFile, OAVBeamCentrePV from dodal.devices.oav.oav_parameters import OAVConfig, OAVConfigBeamCentre -from tests.test_data import TEST_DISPLAY_CONFIG, TEST_OAV_ZOOM_LEVELS +from tests.devices.oav.test_data import TEST_DISPLAY_CONFIG, TEST_OAV_ZOOM_LEVELS @pytest.fixture diff --git a/tests/devices/oav/test_data/__init__.py b/tests/devices/oav/test_data/__init__.py index 3411c4666cc..1800629c068 100644 --- a/tests/devices/oav/test_data/__init__.py +++ b/tests/devices/oav/test_data/__init__.py @@ -2,7 +2,9 @@ from pathlib import Path TEST_DATA_PATH = Path(__file__).parent -TEST_OAV_CENTRING_JSON = "test_OAVCentring.json" +TEST_OAV_CENTRING_JSON = join(TEST_DATA_PATH, "test_OAVCentring.json") +TEST_DISPLAY_CONFIG = join(TEST_DATA_PATH, "test_display.configuration") +TEST_OAV_ZOOM_LEVELS = join(TEST_DATA_PATH, "jCameraManZoomLevels.xml") OAV_SNAPSHOT_TEST_PNG = join(TEST_DATA_PATH, "oav_snapshot_test.png") OAV_SNAPSHOT_EXPECTED_PNG = join(TEST_DATA_PATH, "oav_snapshot_expected.png") diff --git a/tests/devices/oav/test_data/jCameraManZoomLevels.xml b/tests/devices/oav/test_data/jCameraManZoomLevels.xml new file mode 100644 index 00000000000..843414a3b86 --- /dev/null +++ b/tests/devices/oav/test_data/jCameraManZoomLevels.xml @@ -0,0 +1,45 @@ +{ + "JCameraManSettings": { + "levels": { + "zoomLevel": [ + { + "level": "1.0", + "position": "0", + "micronsPerXPixel": "2.87", + "micronsPerYPixel": "2.87" + }, + { + "level": "2.5", + "position": "10", + "micronsPerXPixel": "2.31", + "micronsPerYPixel": "2.31" + }, + { + "level": "5.0", + "position": "25", + "micronsPerXPixel": "1.58", + "micronsPerYPixel": "1.58" + }, + { + "level": "7.5", + "position": "50", + "micronsPerXPixel": "0.806", + "micronsPerYPixel": "0.806" + }, + { + "level": "10.0", + "position": "75", + "micronsPerXPixel": "0.438", + "micronsPerYPixel": "0.438" + }, + { + "level": "15.0", + "position": "90", + "micronsPerXPixel": "0.302", + "micronsPerYPixel": "0.302" + } + ] + }, + "tolerance": "1.0" + } +} diff --git a/tests/devices/oav/test_data/test_OAVCentring.json b/tests/devices/oav/test_data/test_OAVCentring.json new file mode 100644 index 00000000000..fa6b5aa7ff4 --- /dev/null +++ b/tests/devices/oav/test_data/test_OAVCentring.json @@ -0,0 +1,70 @@ +{ + "exposure": 0.075, + "acqPeriod": 0.05, + "gain": 1.0, + "minheight": 70, + "oav": "OAV", + "mxsc_input": "CAM", + "min_callback_time": 0.080, + "close_ksize": 11, + "direction": 0, + "pinTipCentring": { + "zoom": 1.0, + "preprocess": 8, + "preProcessKSize": 21, + "CannyEdgeUpperThreshold": 20.0, + "CannyEdgeLowerThreshold": 5.0, + "brightness": 20, + "max_tip_distance": 300, + "mxsc_input": "proc", + "minheight": 10, + "min_callback_time": 0.15, + "filename": "/dls_sw/prod/R3.14.12.7/support/adPython/2-1-11/adPythonApp/scripts/adPythonMxSampleDetect.py" + }, + "loopCentring": { + "zoom": 5.0, + "preprocess": 8, + "preProcessKSize": 21, + "CannyEdgeUpperThreshold": 20.0, + "CannyEdgeLowerThreshold": 5.0, + "brightness": 20, + "filename": "/dls_sw/prod/R3.14.12.7/support/adPython/2-1-11/adPythonApp/scripts/adPythonMxSampleDetect.py", + "max_tip_distance": 300, + "minheight": 10 + }, + "xrayCentring": { + "zoom": 7.5, + "preprocess": 8, + "preProcessKSize": 31, + "CannyEdgeUpperThreshold": 30.0, + "CannyEdgeLowerThreshold": 5.0, + "close_ksize": 3, + "filename": "/dls_sw/prod/R3.14.12.7/support/adPython/2-1-11/adPythonApp/scripts/adPythonMxSampleDetect.py", + "brightness": 80 + }, + "rotationAxisAlign": { + "zoom": 10.0, + "preprocess": 8, + "preProcessKSize": 21, + "CannyEdgeUpperThreshold": 20.0, + "CannyEdgeLowerThreshold": 5.0, + "filename": "/dls_sw/prod/R3.14.12.7/support/adPython/2-1-11/adPythonApp/scripts/adPythonMxSampleDetect.py", + "brightness": 100 + }, + "SmargonOffsets1": { + "zoom": 1.0, + "preprocess": 8, + "preProcessKSize": 21, + "CannyEdgeUpperThreshold": 50.0, + "CannyEdgeLowerThreshold": 5.0, + "brightness": 80 + }, + "SmargonOffsets2": { + "zoom": 5.0, + "preprocess": 8, + "preProcessKSize": 11, + "CannyEdgeUpperThreshold": 50.0, + "CannyEdgeLowerThreshold": 5.0, + "brightness": 90 + } +} diff --git a/tests/devices/oav/test_data/test_display.configuration b/tests/devices/oav/test_data/test_display.configuration new file mode 100644 index 00000000000..cc987f65e2e --- /dev/null +++ b/tests/devices/oav/test_data/test_display.configuration @@ -0,0 +1,52 @@ +{ + "zoom_levels": { + "1.0": { + "crosshairX": 477, + "crosshairY": 359, + "topLeftX": 383, + "topLeftY": 253, + "bottomRightX": 410, + "bottomRightY": 278 + }, + "2.5": { + "crosshairX": 493, + "crosshairY": 355, + "topLeftX": 340, + "topLeftY": 283, + "bottomRightX": 388, + "bottomRightY": 322 + }, + "5.0": { + "crosshairX": 517, + "crosshairY": 350, + "topLeftX": 268, + "topLeftY": 326, + "bottomRightX": 354, + "bottomRightY": 387 + }, + "7.5": { + "crosshairX": 549, + "crosshairY": 437, + "topLeftX": 248, + "topLeftY": 394, + "bottomRightX": 377, + "bottomRightY": 507 + }, + "10.0": { + "crosshairX": 613, + "crosshairY": 344, + "topLeftX": 2, + "topLeftY": 489, + "bottomRightX": 206, + "bottomRightY": 630 + }, + "15.0": { + "crosshairX": 693, + "crosshairY": 339, + "topLeftX": 1, + "topLeftY": 601, + "bottomRightX": 65, + "bottomRightY": 767 + } + } +} diff --git a/tests/devices/oav/test_oav.py b/tests/devices/oav/test_oav.py index 469b876f922..eb552fa6449 100644 --- a/tests/devices/oav/test_oav.py +++ b/tests/devices/oav/test_oav.py @@ -12,10 +12,7 @@ OAVConfigBeamCentre, ZoomController, ) -from tests.test_data import ( - TEST_DISPLAY_CONFIG, - TEST_OAV_ZOOM_LEVELS, -) +from tests.devices.oav.test_data import TEST_DISPLAY_CONFIG, TEST_OAV_ZOOM_LEVELS @pytest.fixture() diff --git a/tests/devices/oav/test_oav_parameters.py b/tests/devices/oav/test_oav_parameters.py index 7be33590e6e..9e4baa743cf 100644 --- a/tests/devices/oav/test_oav_parameters.py +++ b/tests/devices/oav/test_oav_parameters.py @@ -9,9 +9,9 @@ ZoomParams, ZoomParamsCrosshair, ) -from tests.devices.oav.test_data import TEST_OAV_CENTRING_JSON -from tests.test_data import ( +from tests.devices.oav.test_data import ( TEST_DISPLAY_CONFIG, + TEST_OAV_CENTRING_JSON, TEST_OAV_ZOOM_LEVELS, ) diff --git a/tests/test_data/__init__.py b/tests/test_data/__init__.py index a4650faa744..3b6829187c1 100644 --- a/tests/test_data/__init__.py +++ b/tests/test_data/__init__.py @@ -5,13 +5,10 @@ BAD_BEAMLINE_PARAMETERS = join(TEST_DATA_PATH, "bad_beamlineParameters") I04_BEAMLINE_PARAMETERS = join(TEST_DATA_PATH, "i04_beamlineParameters") TEST_BEAMLINE_PARAMETERS_TXT = join(TEST_DATA_PATH, "test_beamline_parameters.txt") -TEST_DISPLAY_CONFIG = "test_display.configuration" -TEST_OAV_ZOOM_LEVELS = "jCameraManZoomLevels.xml" + __all__ = [ "BAD_BEAMLINE_PARAMETERS", "I04_BEAMLINE_PARAMETERS", "TEST_BEAMLINE_PARAMETERS_TXT", - "TEST_DISPLAY_CONFIG", - "TEST_OAV_ZOOM_LEVELS", ] diff --git a/uv.lock b/uv.lock index 57e41e6ea29..2f1699f5f35 100644 --- a/uv.lock +++ b/uv.lock @@ -640,7 +640,7 @@ wheels = [ [[package]] name = "daq-config-server" -version = "1.1.2" +version = "1.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cachetools" }, @@ -655,9 +655,9 @@ dependencies = [ { name = "uvicorn" }, { name = "xmltodict" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5d/1e/37373402723769ced976215ffb1716353d89c0772d5bec8116d9f981d095/daq_config_server-1.1.2.tar.gz", hash = "sha256:2f8c9e43a41534d90512be7ecab37de0fdf33d4b00a6b61bd04e3c82d886062f", size = 126067, upload-time = "2026-01-12T11:28:21.034Z" } +sdist = { url = "https://files.pythonhosted.org/packages/60/78/98b706a3e630452fe308e180faed618eecbfe0d5f14261a2a2400dde64e3/daq_config_server-1.2.0.tar.gz", hash = "sha256:33f83537b63c3ea42adaa35a108492616601e16d27b2dd7ef44fd3fd41e504ff", size = 213224, upload-time = "2026-03-03T15:39:56.308Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/9f/b8df9fad0b1005e95460ee92092a1da33c282c158336bc23bd7635981f29/daq_config_server-1.1.2-py3-none-any.whl", hash = "sha256:c2bca297feb01a51883e3df7d6ade11af9a3f311602e5584320b1e12881cf636", size = 29273, upload-time = "2026-01-12T11:28:19.635Z" }, + { url = "https://files.pythonhosted.org/packages/87/ea/1bc90a539a987245142fa1b18fa5add3f6ca169da2b6f84375fce78d276a/daq_config_server-1.2.0-py3-none-any.whl", hash = "sha256:81473a26723d7b2cc55369899ad8e22df4c4ad3e4e3f51f0151fd67048844642", size = 29701, upload-time = "2026-03-03T15:39:55.238Z" }, ] [[package]] From c0544350f915d8ec9cedc8bab408d7b8382e52e6 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Fri, 13 Mar 2026 11:17:20 +0000 Subject: [PATCH 34/39] Fix display config --- tests/devices/oav/test_data/__init__.py | 4 +- .../oav/test_data/test_display.configuration | 72 +++++++++---------- 2 files changed, 39 insertions(+), 37 deletions(-) diff --git a/tests/devices/oav/test_data/__init__.py b/tests/devices/oav/test_data/__init__.py index 1800629c068..58a0f5e99f7 100644 --- a/tests/devices/oav/test_data/__init__.py +++ b/tests/devices/oav/test_data/__init__.py @@ -9,7 +9,9 @@ OAV_SNAPSHOT_EXPECTED_PNG = join(TEST_DATA_PATH, "oav_snapshot_expected.png") __all__ = [ + "TEST_OAV_CENTRING_JSON", + "TEST_DISPLAY_CONFIG", + "TEST_OAV_ZOOM_LEVELS", "OAV_SNAPSHOT_EXPECTED_PNG", "OAV_SNAPSHOT_TEST_PNG", - "TEST_OAV_CENTRING_JSON", ] diff --git a/tests/devices/oav/test_data/test_display.configuration b/tests/devices/oav/test_data/test_display.configuration index cc987f65e2e..d7d65ae4e65 100644 --- a/tests/devices/oav/test_data/test_display.configuration +++ b/tests/devices/oav/test_data/test_display.configuration @@ -1,52 +1,52 @@ { "zoom_levels": { "1.0": { - "crosshairX": 477, - "crosshairY": 359, - "topLeftX": 383, - "topLeftY": 253, - "bottomRightX": 410, - "bottomRightY": 278 + "crosshair_x": 477, + "crosshair_y": 359, + "top_left_x": 383, + "top_left_y": 253, + "bottom_right_x": 410, + "bottom_right_y": 278 }, "2.5": { - "crosshairX": 493, - "crosshairY": 355, - "topLeftX": 340, - "topLeftY": 283, - "bottomRightX": 388, - "bottomRightY": 322 + "crosshair_x": 493, + "crosshair_y": 355, + "top_left_x": 340, + "top_left_y": 283, + "bottom_right_x": 388, + "bottom_right_y": 322 }, "5.0": { - "crosshairX": 517, - "crosshairY": 350, - "topLeftX": 268, - "topLeftY": 326, - "bottomRightX": 354, - "bottomRightY": 387 + "crosshair_x": 517, + "crosshair_y": 350, + "top_left_x": 268, + "top_left_y": 326, + "bottom_right_x": 354, + "bottom_right_y": 387 }, "7.5": { - "crosshairX": 549, - "crosshairY": 437, - "topLeftX": 248, - "topLeftY": 394, - "bottomRightX": 377, - "bottomRightY": 507 + "crosshair_x": 549, + "crosshair_y": 437, + "top_left_x": 248, + "top_left_y": 394, + "bottom_right_x": 377, + "bottom_right_y": 507 }, "10.0": { - "crosshairX": 613, - "crosshairY": 344, - "topLeftX": 2, - "topLeftY": 489, - "bottomRightX": 206, - "bottomRightY": 630 + "crosshair_x": 613, + "crosshair_y": 344, + "top_left_x": 2, + "top_left_y": 489, + "bottom_right_x": 206, + "bottom_right_y": 630 }, "15.0": { - "crosshairX": 693, - "crosshairY": 339, - "topLeftX": 1, - "topLeftY": 601, - "bottomRightX": 65, - "bottomRightY": 767 + "crosshair_x": 693, + "crosshair_y": 339, + "top_left_x": 1, + "top_left_y": 601, + "bottom_right_x": 65, + "bottom_right_y": 767 } } } From e2eaa15088b090165bd7a337bb7d1375a980c6fb Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Fri, 13 Mar 2026 14:14:18 +0000 Subject: [PATCH 35/39] Fix system tests --- pyproject.toml | 2 +- system_tests/conftest.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d78ba68e3b5..db7e72a46f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ dependencies = [ "scanspec>=0.7.3", "pyzmq==27.1.0", "deepdiff", - "daq-config-server>=v1.1.2", # For getting Configuration settings. + "daq-config-server>=v1.2.0", # For getting Configuration settings. "mysql-connector-python == 9.5.0", # Can unpin once https://github.com/DiamondLightSource/ispyb-api/pull/244 is merged and released ] diff --git a/system_tests/conftest.py b/system_tests/conftest.py index d3df62cbd5d..06fee26e905 100644 --- a/system_tests/conftest.py +++ b/system_tests/conftest.py @@ -1,2 +1,5 @@ # Add run_engine to be used in tests -pytest_plugins = ["dodal.testing.fixtures.run_engine"] +pytest_plugins = [ + "dodal.testing.fixtures.run_engine", + "dodal.testing.fixtures.config_server", +] From 20d30e12838c9344ab059ed20bc3b762b0183792 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Fri, 13 Mar 2026 14:15:02 +0000 Subject: [PATCH 36/39] Fix lint --- src/dodal/devices/oav/oav_parameters.py | 3 +-- uv.lock | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/dodal/devices/oav/oav_parameters.py b/src/dodal/devices/oav/oav_parameters.py index 31c9fe42aad..b87d51cf180 100644 --- a/src/dodal/devices/oav/oav_parameters.py +++ b/src/dodal/devices/oav/oav_parameters.py @@ -1,8 +1,7 @@ from abc import abstractmethod from collections import ChainMap from dataclasses import dataclass -from typing import Any, Generic, Literal, TypedDict, TypeVar -from xml.etree import ElementTree +from typing import Any, Generic, TypeVar from xml.etree.ElementTree import Element from daq_config_server.client import ConfigServer diff --git a/uv.lock b/uv.lock index 2f1699f5f35..e3392350297 100644 --- a/uv.lock +++ b/uv.lock @@ -761,7 +761,7 @@ requires-dist = [ { name = "aiohttp" }, { name = "bluesky", specifier = ">=1.14.5" }, { name = "click" }, - { name = "daq-config-server", specifier = ">=1.1.2" }, + { name = "daq-config-server", specifier = ">=1.2.0" }, { name = "deepdiff" }, { name = "graypy" }, { name = "mysql-connector-python", specifier = "==9.5.0" }, From dcb61d1172a7127481f67621bd8224575f6fbaad Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Fri, 27 Mar 2026 15:01:01 +0000 Subject: [PATCH 37/39] Fix lint --- src/dodal/devices/beamlines/i03/undulator_dcm.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/dodal/devices/beamlines/i03/undulator_dcm.py b/src/dodal/devices/beamlines/i03/undulator_dcm.py index 1a78b108762..8c3c8cd5438 100644 --- a/src/dodal/devices/beamlines/i03/undulator_dcm.py +++ b/src/dodal/devices/beamlines/i03/undulator_dcm.py @@ -11,7 +11,6 @@ from dodal.devices.beamlines.i03.dcm import DCM from dodal.devices.undulator import UndulatorInKeV from dodal.log import LOGGER -from dodal.utils import get_beamline_name ENERGY_TIMEOUT_S: float = 30.0 From 1eeb26516f40ad90c989ee3366aa646cbb20505d Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Mon, 30 Mar 2026 13:44:43 +0100 Subject: [PATCH 38/39] Inject config client into OAVConfig and OAVConfigBeamCentre --- src/dodal/beamlines/aithre.py | 18 ++++++++++++++++-- src/dodal/beamlines/i03.py | 4 +++- src/dodal/beamlines/i04.py | 12 ++++++++---- src/dodal/beamlines/i19_1.py | 12 +++++++++++- src/dodal/beamlines/i24.py | 10 ++++++++-- src/dodal/devices/oav/oav_parameters.py | 19 +++++++------------ system_tests/test_oav_system.py | 7 ++++--- tests/devices/oav/conftest.py | 9 ++++++--- tests/devices/oav/test_oav.py | 9 +++++++-- tests/devices/oav/test_oav_parameters.py | 5 +++-- 10 files changed, 73 insertions(+), 32 deletions(-) diff --git a/src/dodal/beamlines/aithre.py b/src/dodal/beamlines/aithre.py index d3ce9ec5c6e..8770a0cfaf3 100644 --- a/src/dodal/beamlines/aithre.py +++ b/src/dodal/beamlines/aithre.py @@ -1,3 +1,7 @@ +from functools import cache + +from daq_config_server import ConfigClient + from dodal.device_manager import DeviceManager from dodal.devices.aithre_lasershaping.goniometer import Goniometer from dodal.devices.aithre_lasershaping.laser_robot import LaserRobot @@ -14,6 +18,12 @@ devices = DeviceManager() +@devices.fixture +@cache +def config_client() -> ConfigClient: + return ConfigClient() + + @devices.factory() def goniometer() -> Goniometer: return Goniometer( @@ -31,12 +41,16 @@ def robot() -> LaserRobot: @devices.factory() -def oav(params: OAVConfigBeamCentre | None = None) -> OAVBeamCentreFile: +def oav( + config_client: ConfigClient, params: OAVConfigBeamCentre | None = None +) -> OAVBeamCentreFile: config = ( params if params is not None else OAVConfigBeamCentre( - zoom_params_file=ZOOM_PARAMS_FILE, display_config_file=DISPLAY_CONFIG + zoom_params_file=ZOOM_PARAMS_FILE, + display_config_file=DISPLAY_CONFIG, + config_client=config_client, ) ) return OAVBeamCentreFile( diff --git a/src/dodal/beamlines/i03.py b/src/dodal/beamlines/i03.py index 79631b15b2e..41a6b678de0 100644 --- a/src/dodal/beamlines/i03.py +++ b/src/dodal/beamlines/i03.py @@ -205,11 +205,13 @@ def panda_fast_grid_scan() -> PandAFastGridScan: @devices.factory() def oav( + config_client: ConfigClient, params: OAVConfigBeamCentre | None = None, ) -> OAVBeamCentreFile: return OAVBeamCentreFile( prefix=f"{PREFIX.beamline_prefix}-DI-OAV-01:", - config=params or OAVConfigBeamCentre(ZOOM_PARAMS_FILE, DISPLAY_CONFIG), + config=params + or OAVConfigBeamCentre(ZOOM_PARAMS_FILE, DISPLAY_CONFIG, config_client), ) diff --git a/src/dodal/beamlines/i04.py b/src/dodal/beamlines/i04.py index abf8384c9b0..b2f57c086a1 100644 --- a/src/dodal/beamlines/i04.py +++ b/src/dodal/beamlines/i04.py @@ -216,18 +216,22 @@ def zebra() -> Zebra: @devices.factory() -def oav(params: OAVConfig | None = None) -> OAVBeamCentrePV: +def oav( + config_client: ConfigClient, params: OAVConfig | None = None +) -> OAVBeamCentrePV: return OAVBeamCentrePV( prefix=f"{PREFIX.beamline_prefix}-DI-OAV-01:", - config=params or OAVConfig(ZOOM_PARAMS_FILE), + config=params or OAVConfig(ZOOM_PARAMS_FILE, config_client), ) @devices.factory() -def oav_full_screen(params: OAVConfig | None = None) -> OAVBeamCentrePV: +def oav_full_screen( + config_client: ConfigClient, params: OAVConfig | None = None +) -> OAVBeamCentrePV: return OAVBeamCentrePV( prefix=f"{PREFIX.beamline_prefix}-DI-OAV-01:", - config=params or OAVConfig(ZOOM_PARAMS_FILE), + config=params or OAVConfig(ZOOM_PARAMS_FILE, config_client), overlay_channel=3, mjpeg_prefix="XTAL", ) diff --git a/src/dodal/beamlines/i19_1.py b/src/dodal/beamlines/i19_1.py index 3e8ef966622..883bc6cacf5 100644 --- a/src/dodal/beamlines/i19_1.py +++ b/src/dodal/beamlines/i19_1.py @@ -1,3 +1,7 @@ +from functools import cache + +from daq_config_server import ConfigClient + from dodal.common.beamlines.beamline_utils import ( set_beamline as set_utils_beamline, ) @@ -48,6 +52,12 @@ devices = DeviceManager() +@devices.fixture +@cache +def config_client() -> ConfigClient: + return ConfigClient() + + @devices.factory() def attenuator_motor_squad() -> AttenuatorMotorSquad: return AttenuatorMotorSquad( @@ -62,7 +72,7 @@ def beamstop() -> BeamStop: @devices.fixture def oav_config() -> OAVConfigBeamCentre: - return OAVConfigBeamCentre(ZOOM_PARAMS_FILE, DISPLAY_CONFIG) + return OAVConfigBeamCentre(ZOOM_PARAMS_FILE, DISPLAY_CONFIG, config_client()) @devices.factory() diff --git a/src/dodal/beamlines/i24.py b/src/dodal/beamlines/i24.py index fdfb219727a..92d7bb2dd1b 100644 --- a/src/dodal/beamlines/i24.py +++ b/src/dodal/beamlines/i24.py @@ -68,6 +68,12 @@ def path_provider() -> PathProvider: ) +@devices.fixture +@cache +def config_client() -> ConfigClient: + return ConfigClient() + + @devices.factory() def attenuator() -> EnumFilterAttenuator: return EnumFilterAttenuator( @@ -110,10 +116,10 @@ def pmac() -> PMAC: @devices.factory() -def oav() -> OAVBeamCentreFile: +def oav(config_client) -> OAVBeamCentreFile: return OAVBeamCentreFile( prefix=f"{PREFIX.beamline_prefix}-DI-OAV-01:", - config=OAVConfigBeamCentre(ZOOM_PARAMS_FILE, DISPLAY_CONFIG), + config=OAVConfigBeamCentre(ZOOM_PARAMS_FILE, DISPLAY_CONFIG, config_client), ) diff --git a/src/dodal/devices/oav/oav_parameters.py b/src/dodal/devices/oav/oav_parameters.py index 53311fb289a..403d23cf599 100644 --- a/src/dodal/devices/oav/oav_parameters.py +++ b/src/dodal/devices/oav/oav_parameters.py @@ -121,12 +121,8 @@ class ZoomParamsCrosshair(ZoomParams): class OAVConfigBase(Generic[ParamType]): - def __init__(self, zoom_params_file: str): - self.zoom_params = self._get_zoom_params(zoom_params_file) - - def _get_zoom_params(self, zoom_params_file: str) -> dict: - config_server = ConfigClient(url="https://daq-config.diamond.ac.uk") - return config_server.get_file_contents(zoom_params_file, dict)[ + def __init__(self, zoom_params_file: str, config_client: ConfigClient): + self.zoom_params = config_client.get_file_contents(zoom_params_file, dict)[ "JCameraManSettings" ] @@ -165,13 +161,12 @@ def __init__( self, zoom_params_file: str, display_config_file: str, + config_client: ConfigClient, ): - self.display_config = self._get_display_config(display_config_file) - super().__init__(zoom_params_file) - - def _get_display_config(self, display_config_file: str) -> DisplayConfig: - config_server = ConfigClient(url="https://daq-config.diamond.ac.uk") - return config_server.get_file_contents(display_config_file, DisplayConfig) + self.display_config = config_client.get_file_contents( + display_config_file, DisplayConfig + ) + super().__init__(zoom_params_file, config_client) def _read_display_config(self) -> dict: crosshairs = {} diff --git a/system_tests/test_oav_system.py b/system_tests/test_oav_system.py index 2e4c57602ec..30f04a2fc0a 100644 --- a/system_tests/test_oav_system.py +++ b/system_tests/test_oav_system.py @@ -1,8 +1,9 @@ import bluesky.plan_stubs as bps import pytest from bluesky.run_engine import RunEngine +from daq_config_server import ConfigClient from ophyd_async.core import init_devices -from tests.devices.oav.test_data import TEST_DISPLAY_CONFIG, TEST_OAV_ZOOM_LEVELS +from tests.devices.oav.test_data import TEST_OAV_ZOOM_LEVELS from dodal.devices.oav.oav_detector import OAV, OAVConfig @@ -15,7 +16,7 @@ @pytest.fixture async def oav() -> OAV: - oav_config = OAVConfig(TEST_OAV_ZOOM_LEVELS, TEST_DISPLAY_CONFIG) + oav_config = OAVConfig(TEST_OAV_ZOOM_LEVELS, ConfigClient("")) async with init_devices(connect=True): oav = OAV("", config=oav_config, name="oav") return oav @@ -36,7 +37,7 @@ def take_snapshot_with_grid(oav: OAV, snapshot_filename, snapshot_directory): @pytest.mark.skip(reason="Don't want to actually take snapshots during testing.") def test_grid_overlay(run_engine: RunEngine): beamline = "BL03I" - oav_params = OAVConfig(TEST_OAV_ZOOM_LEVELS, TEST_DISPLAY_CONFIG) + oav_params = OAVConfig(TEST_OAV_ZOOM_LEVELS, ConfigClient("")) oav = OAV(name="oav", prefix=f"{beamline}", config=oav_params) snapshot_filename = "snapshot" snapshot_directory = "." diff --git a/tests/devices/oav/conftest.py b/tests/devices/oav/conftest.py index b5db2cb4b33..8da3a5512f5 100644 --- a/tests/devices/oav/conftest.py +++ b/tests/devices/oav/conftest.py @@ -1,6 +1,7 @@ from unittest.mock import AsyncMock import pytest +from daq_config_server import ConfigClient from ophyd_async.core import init_devices, set_mock_value from dodal.devices.oav.oav_detector import OAVBeamCentreFile, OAVBeamCentrePV @@ -10,7 +11,9 @@ @pytest.fixture async def oav() -> OAVBeamCentreFile: - oav_config = OAVConfigBeamCentre(TEST_OAV_ZOOM_LEVELS, TEST_DISPLAY_CONFIG) + oav_config = OAVConfigBeamCentre( + TEST_OAV_ZOOM_LEVELS, TEST_DISPLAY_CONFIG, ConfigClient("") + ) async with init_devices(mock=True, connect=True): oav = OAVBeamCentreFile("", config=oav_config, name="oav") zoom_levels_list = ["1.0x", "3.0x", "5.0x", "7.5x", "10.0x"] @@ -25,7 +28,7 @@ async def oav() -> OAVBeamCentreFile: @pytest.fixture async def oav_beam_centre_pv_roi() -> OAVBeamCentrePV: - oav_config = OAVConfig(TEST_OAV_ZOOM_LEVELS) + oav_config = OAVConfig(TEST_OAV_ZOOM_LEVELS, ConfigClient("")) async with init_devices(mock=True, connect=True): oav = OAVBeamCentrePV("", config=oav_config, name="oav") zoom_levels_list = ["1.0x", "3.0x", "5.0x", "7.5x", "10.0x"] @@ -40,7 +43,7 @@ async def oav_beam_centre_pv_roi() -> OAVBeamCentrePV: @pytest.fixture async def oav_beam_centre_pv_fs() -> OAVBeamCentrePV: - oav_config = OAVConfig(TEST_OAV_ZOOM_LEVELS) + oav_config = OAVConfig(TEST_OAV_ZOOM_LEVELS, ConfigClient("")) async with init_devices(mock=True, connect=True): oav = OAVBeamCentrePV( "", config=oav_config, name="oav", mjpeg_prefix="XTAL", overlay_channel=3 diff --git a/tests/devices/oav/test_oav.py b/tests/devices/oav/test_oav.py index eb552fa6449..5c4987e2c63 100644 --- a/tests/devices/oav/test_oav.py +++ b/tests/devices/oav/test_oav.py @@ -1,6 +1,7 @@ from unittest.mock import AsyncMock, patch import pytest +from daq_config_server import ConfigClient from ophyd_async.core import init_devices, set_mock_value from dodal.devices.oav.oav_detector import ( @@ -164,7 +165,9 @@ async def test_beam_centre_signals_have_same_names( async def test_oav_with_null_zoom_controller(null_controller: NullZoomController): - oav_config = OAVConfigBeamCentre(TEST_OAV_ZOOM_LEVELS, TEST_DISPLAY_CONFIG) + oav_config = OAVConfigBeamCentre( + TEST_OAV_ZOOM_LEVELS, TEST_DISPLAY_CONFIG, ConfigClient("") + ) oav = OAVBeamCentreFile("", oav_config, "", zoom_controller=null_controller) assert await oav.zoom_controller.level.get_value() == "1.0x" @@ -190,7 +193,9 @@ async def test_oav_with_null_zoom_controller_set_zoom_level_other_than_1( ["MJPG", "XTAL"], ) async def test_setting_mjpeg_prefix_changes_stream_url(mjpeg_prefix): - oav_config = OAVConfigBeamCentre(TEST_OAV_ZOOM_LEVELS, TEST_DISPLAY_CONFIG) + oav_config = OAVConfigBeamCentre( + TEST_OAV_ZOOM_LEVELS, TEST_DISPLAY_CONFIG, ConfigClient("") + ) async with init_devices(mock=True, connect=True): oav = OAVBeamCentreFile( "", config=oav_config, name="oav", mjpeg_prefix=mjpeg_prefix diff --git a/tests/devices/oav/test_oav_parameters.py b/tests/devices/oav/test_oav_parameters.py index 9e4baa743cf..49bf8159d84 100644 --- a/tests/devices/oav/test_oav_parameters.py +++ b/tests/devices/oav/test_oav_parameters.py @@ -1,6 +1,7 @@ from typing import cast import pytest +from daq_config_server import ConfigClient from dodal.devices.oav.oav_parameters import ( OAVConfig, @@ -26,13 +27,13 @@ def mock_parameters(): @pytest.fixture def mock_config() -> dict[str, ZoomParams]: - return OAVConfig(TEST_OAV_ZOOM_LEVELS).get_parameters() + return OAVConfig(TEST_OAV_ZOOM_LEVELS, ConfigClient("")).get_parameters() @pytest.fixture def mock_config_with_beam_centre() -> dict[str, ZoomParamsCrosshair]: config = OAVConfigBeamCentre( - TEST_OAV_ZOOM_LEVELS, TEST_DISPLAY_CONFIG + TEST_OAV_ZOOM_LEVELS, TEST_DISPLAY_CONFIG, ConfigClient("") ).get_parameters() config = cast(dict[str, ZoomParamsCrosshair], config) return config From 80d90229469da1b65ba9d9427d74f487ac9b921d Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Mon, 30 Mar 2026 13:50:46 +0100 Subject: [PATCH 39/39] Rename test file --- tests/devices/oav/test_data/__init__.py | 2 +- .../{jCameraManZoomLevels.xml => jCameraManZoomLevels.json} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename tests/devices/oav/test_data/{jCameraManZoomLevels.xml => jCameraManZoomLevels.json} (100%) diff --git a/tests/devices/oav/test_data/__init__.py b/tests/devices/oav/test_data/__init__.py index 58a0f5e99f7..71b9722652c 100644 --- a/tests/devices/oav/test_data/__init__.py +++ b/tests/devices/oav/test_data/__init__.py @@ -4,7 +4,7 @@ TEST_DATA_PATH = Path(__file__).parent TEST_OAV_CENTRING_JSON = join(TEST_DATA_PATH, "test_OAVCentring.json") TEST_DISPLAY_CONFIG = join(TEST_DATA_PATH, "test_display.configuration") -TEST_OAV_ZOOM_LEVELS = join(TEST_DATA_PATH, "jCameraManZoomLevels.xml") +TEST_OAV_ZOOM_LEVELS = join(TEST_DATA_PATH, "jCameraManZoomLevels.json") OAV_SNAPSHOT_TEST_PNG = join(TEST_DATA_PATH, "oav_snapshot_test.png") OAV_SNAPSHOT_EXPECTED_PNG = join(TEST_DATA_PATH, "oav_snapshot_expected.png") diff --git a/tests/devices/oav/test_data/jCameraManZoomLevels.xml b/tests/devices/oav/test_data/jCameraManZoomLevels.json similarity index 100% rename from tests/devices/oav/test_data/jCameraManZoomLevels.xml rename to tests/devices/oav/test_data/jCameraManZoomLevels.json