From 62f923f63641b4ddbed1f333dfdf0ec3a03962ac Mon Sep 17 00:00:00 2001 From: Tamoor Shahid Date: Mon, 9 Feb 2026 16:29:04 +0000 Subject: [PATCH 01/10] Checks for 10 minutes left for countdown --- src/mx_bluesky/hyperion/baton_handler.py | 15 +++++++++++++++ uv.lock | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/mx_bluesky/hyperion/baton_handler.py b/src/mx_bluesky/hyperion/baton_handler.py index 2014929df3..2c2e555c3c 100644 --- a/src/mx_bluesky/hyperion/baton_handler.py +++ b/src/mx_bluesky/hyperion/baton_handler.py @@ -6,6 +6,7 @@ from bluesky.utils import MsgGenerator, RunEngineInterrupted from dodal.common.beamlines.commissioning_mode import set_commissioning_signal from dodal.devices.baton import Baton +from dodal.devices.synchrotron import Synchrotron from mx_bluesky.common.external_interaction.alerting import ( AlertService, @@ -85,6 +86,16 @@ def collect() -> MsgGenerator: baton: The baton device runner: The runner """ + + baton = _get_baton(context) + synchrotron = _get_synchrotron(context) + countdown = yield from bps.rd(synchrotron.machine_user_countdown) + + if countdown < 600: + _raise_udc_completed_alert(get_alerting_service()) + # Release the baton for orderly exit from the instruction loop + yield from _unrequest_baton(baton) + _raise_udc_start_alert(get_alerting_service()) yield from bpp.contingency_wrapper( runner.decode_and_execute( @@ -218,6 +229,10 @@ def _get_baton(context: BlueskyContext) -> Baton: return find_device_in_context(context, "baton", Baton) +def _get_synchrotron(context: BlueskyContext) -> Synchrotron: + return find_device_in_context(context, "synchrotron", Synchrotron) + + def _unrequest_baton(baton: Baton) -> MsgGenerator[str]: """Relinquish the requested user of the baton if it is not already requested by another user. diff --git a/uv.lock b/uv.lock index 45f4522617..df626f8fd9 100644 --- a/uv.lock +++ b/uv.lock @@ -802,8 +802,8 @@ wheels = [ [[package]] name = "dls-dodal" -version = "2.0.1.dev11+gebba33bcc" -source = { git = "https://github.com/DiamondLightSource/dodal.git?rev=main#ebba33bcc75315ab20e4c7de40e11adc4fcc5e41" } +version = "2.0.1.dev12+gfa22fd0ab" +source = { git = "https://github.com/DiamondLightSource/dodal.git?rev=main#fa22fd0ab55aa63445da1d6462ec02c82cd64611" } dependencies = [ { name = "aiofiles", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "aiohttp", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, From cebf30db60e8a5ae7f7fa6d7ad54a31c01a91120 Mon Sep 17 00:00:00 2001 From: olliesilvester <122091460+olliesilvester@users.noreply.github.com> Date: Mon, 9 Feb 2026 17:36:48 +0000 Subject: [PATCH 02/10] Fix devcontainer dodal and dlstbx (#1621) --- .devcontainer/devcontainer.json | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 38cb34a47d..13ef77a1b5 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -16,7 +16,8 @@ "UV_PROJECT_ENVIRONMENT": "/cache/venv-for${localWorkspaceFolder}", // Do the equivalent of "activate" the venv so we don't have to "uv run" everything "VIRTUAL_ENV": "/cache/venv-for${localWorkspaceFolder}", - "PATH": "/cache/venv-for${localWorkspaceFolder}/bin:${containerEnv:PATH}" + "PATH": "/cache/venv-for${localWorkspaceFolder}/bin:${containerEnv:PATH}", + "PYTHONPATH": "/dls_sw/apps/dials/latest/latest/modules/dlstbx/src:${containerEnv:PYTHONPATH}" }, "customizations": { "vscode": { @@ -75,7 +76,7 @@ // Allow the container to access the host X11 display and EPICS CA "--net=host", // Make sure SELinux does not disable with access to host filesystems like tmp - "--security-opt=label=disable" + "--security-opt=label=disable", ], "mounts": [ // Mount in the user terminal config folder so it can be edited @@ -84,6 +85,11 @@ "target": "/user-terminal-config", "type": "bind" }, + { + "source": "/dls_sw/apps/dials/latest/latest/modules/dlstbx/src", + "target": "/dls_sw/apps/dials/latest/latest/modules/dlstbx/src", + "type": "bind" + }, // Keep a persistent cross container cache for uv, pre-commit, and the venvs { "source": "devcontainer-shared-cache", @@ -93,9 +99,6 @@ ], // Mount the parent as /workspaces so we can pip install peers as editable "workspaceMount": "source=${localWorkspaceFolder}/..,target=/workspaces,type=bind", - "mounts": [ - "source=/dls_sw/apps/dials/latest/latest/modules/dlstbx/src/dlstbx/,target=/dls_sw/apps/dials/latest/latest/modules/dlstbx/src/dlstbx/,type=bind,consistency=cached" - ], // After the container is created, recreate the venv then make pre-commit first run faster - "postCreateCommand": "uv venv --clear && uv sync && pre-commit install --install-hooks" + "postCreateCommand": "uv venv --clear && uv sync && uv pip install -e /workspaces/dodal && pre-commit install --install-hooks" } From 586964572e5399098b3efaf51022055e89f0a4f4 Mon Sep 17 00:00:00 2001 From: olliesilvester <122091460+olliesilvester@users.noreply.github.com> Date: Wed, 11 Feb 2026 15:15:28 +0000 Subject: [PATCH 03/10] Use protocols instead of smargon type for FGS composite (#1619) * Use protocols instead of smargon type for FGS composite * Rename devices to gonio in all plans * Include generic type everywhere in plan --- pyproject.toml | 18 +++++----- .../beamlines/i04/callbacks/murko_callback.py | 4 +-- .../i04_grid_detect_then_xray_centre_plan.py | 6 ++-- src/mx_bluesky/beamlines/i04/thawing_plan.py | 8 ++--- .../change_aperture_then_move_plan.py | 4 ++- .../common_flyscan_xray_centre_plan.py | 13 ++++--- ...ommon_grid_detect_then_xray_centre_plan.py | 4 +-- .../oav_grid_detection_plan.py | 2 +- .../experiment_plans/oav_snapshot_plan.py | 8 ++--- .../common/grid_detection_callback.py | 14 ++++---- .../callbacks/common/ispyb_callback_base.py | 6 ++-- .../callbacks/xray_centre/ispyb_callback.py | 6 ++-- .../common/parameters/device_composites.py | 20 ++++++++--- .../hyperion/blueapi_plans/__init__.py | 4 +-- .../hyperion_flyscan_xray_centre_plan.py | 8 ++--- .../load_centre_collect_full_plan.py | 6 ++-- .../pin_centre_then_xray_centre_plan.py | 6 ++-- .../robot_load_and_change_energy.py | 6 ++-- .../robot_load_then_centre_plan.py | 4 +-- .../experiment_plans/rotation_scan_plan.py | 18 +++++----- .../experiment_plans/udc_default_state.py | 2 +- .../callbacks/rotation/ispyb_callback.py | 6 ++-- .../callbacks/snapshot_callback.py | 16 ++++----- src/mx_bluesky/hyperion/in_process_runner.py | 2 +- .../hyperion/parameters/device_composites.py | 7 ++-- tests/conftest.py | 34 +++++++++--------- .../hyperion/external_interaction/conftest.py | 6 ++-- .../test_load_centre_collect_full_plan.py | 10 +++--- .../i04/callbacks/test_murko_callback.py | 14 ++++---- ...t_i04_grid_detect_then_xray_centre_plan.py | 2 +- .../unit_tests/beamlines/i04/test_thawing.py | 6 ++-- .../test_robot_load_unload.py | 2 +- .../test_common_flyscan_xray_centre_plan.py | 6 ++-- .../test_grid_detection_plan.py | 12 +++---- .../test_oav_snapshot_plan.py | 10 +++--- tests/unit_tests/conftest.py | 14 ++++---- .../hyperion/experiment_plans/conftest.py | 6 ++-- .../test_hyperion_flyscan_xray_centre_plan.py | 6 ++-- .../test_load_centre_collect_full_plan.py | 36 +++++++------------ .../test_pin_centre_then_xray_centre_plan.py | 2 +- .../test_rotation_scan_plan.py | 30 +++++++--------- .../test_udc_default_state.py | 2 +- .../callbacks/rotation/test_ispyb_callback.py | 6 ++-- uv.lock | 6 ++-- 44 files changed, 199 insertions(+), 209 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index db988da13e..72e4832dd6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,9 +4,7 @@ build-backend = "setuptools.build_meta" [tool.uv] # Restrict lockfile to a sane subset of platforms -environments = [ - "sys_platform == 'linux' and platform_machine == 'x86_64'" -] +environments = ["sys_platform == 'linux' and platform_machine == 'x86_64'"] [project] name = "mx-bluesky" @@ -31,7 +29,7 @@ dependencies = [ "matplotlib", "nexgen >= 0.11.0", "numpy >= 2.3.5", - "opencv-python", # Needed for I24 ssx moveonclick. To be changed to headless once this is moved to separate ui. + "opencv-python", # Needed for I24 ssx moveonclick. To be changed to headless once this is moved to separate ui. "opentelemetry-distro", "opentelemetry-exporter-otlp", "pydantic", @@ -46,7 +44,7 @@ dependencies = [ "matplotlib", "cachetools", "bluesky-stomp >= 0.2.0", - "mysql-connector-python == 9.5.0", # Can unpin once https://github.com/DiamondLightSource/ispyb-api/pull/244 is merged and released + "mysql-connector-python == 9.5.0", # Can unpin once https://github.com/DiamondLightSource/ispyb-api/pull/244 is merged and released # # These dependencies may be issued as pre-release versions and should have a pin constraint @@ -57,7 +55,7 @@ dependencies = [ "ophyd >= 1.10.5", "ophyd-async >= 0.14.0", "bluesky >= 1.14.6", - "dls-dodal @ git+https://github.com/DiamondLightSource/dodal.git@main", + "dls-dodal @ git+https://github.com/DiamondLightSource/dodal.git@28c3f20f6108d2769e7995f1008e7d76446fb38b", ] @@ -269,10 +267,10 @@ env_dir = ".venv" skip_install = true commands_pre = [ [ - "python", - "{toxinidir}/utility_scripts/generate_plantuml.py", - "docs/developer/hyperion/reference/param_hierarchy.puml" - ] + "python", + "{toxinidir}/utility_scripts/generate_plantuml.py", + "docs/developer/hyperion/reference/param_hierarchy.puml", + ], ] commands = [ [ diff --git a/src/mx_bluesky/beamlines/i04/callbacks/murko_callback.py b/src/mx_bluesky/beamlines/i04/callbacks/murko_callback.py index 782a43e27c..4449ea385d 100644 --- a/src/mx_bluesky/beamlines/i04/callbacks/murko_callback.py +++ b/src/mx_bluesky/beamlines/i04/callbacks/murko_callback.py @@ -95,7 +95,7 @@ def event(self, doc: Event) -> Event: } ) - if (latest_omega := data.get("smargon-omega")) is not None: + if (latest_omega := data.get("gonio-omega")) is not None: if len(self.previous_omegas) <= 2 and self.last_uuid: # For the first few images there's not enough data to extrapolate so we # match them one to one @@ -103,7 +103,7 @@ def event(self, doc: Event) -> Event: self.previous_omegas.append( OmegaReading( value=latest_omega, - timestamp=doc["timestamps"]["smargon-omega"], + timestamp=doc["timestamps"]["gonio-omega"], ) ) elif (uuid := doc["data"].get("oav_to_redis_forwarder-uuid")) is not None: diff --git a/src/mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py b/src/mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py index 7d7421bcd3..38ddba25a8 100644 --- a/src/mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py +++ b/src/mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py @@ -140,7 +140,7 @@ def i04_default_grid_detect_and_xray_centre( eiger: EigerDetector = inject("eiger"), synchrotron: Synchrotron = inject("synchrotron"), zocalo: ZocaloResults = inject("zocalo"), - smargon: Smargon = inject("smargon"), + smargon: Smargon = inject("gonio"), detector_motion: DetectorMotion = inject("detector_motion"), transfocator: Transfocator = inject("transfocator"), oav_config: str = OavConstants.OAV_CONFIG_JSON, @@ -195,7 +195,7 @@ def tidy_beamline(): if not udc: yield from get_ready_for_oav_and_close_shutter( - composite.smargon, + composite.gonio, composite.backlight, composite.aperture_scatterguard, composite.detector_motion, @@ -296,7 +296,7 @@ def construct_i04_specific_features( xrc_composite.undulator.current_gap, xrc_composite.synchrotron.synchrotron_mode, xrc_composite.s4_slit_gaps, - xrc_composite.smargon, + xrc_composite.gonio, xrc_composite.dcm.energy_in_keV, ] diff --git a/src/mx_bluesky/beamlines/i04/thawing_plan.py b/src/mx_bluesky/beamlines/i04/thawing_plan.py index 13301bcef6..0f16fd71d0 100644 --- a/src/mx_bluesky/beamlines/i04/thawing_plan.py +++ b/src/mx_bluesky/beamlines/i04/thawing_plan.py @@ -18,7 +18,7 @@ def thaw( time_to_thaw: float, rotation: float = 360, thawer: Thawer = inject("thawer"), - smargon: Smargon = inject("smargon"), + smargon: Smargon = inject("gonio"), ) -> MsgGenerator: """Turns on the thawer and rotates the sample by {rotation} degrees to thaw it, then rotates {rotation} degrees back and turns the thawer off. The speed of the goniometer @@ -54,7 +54,7 @@ def thaw_and_murko_centre( rotation: float = 360, robot: BartRobot = inject("robot"), thawer: Thawer = inject("thawer"), - smargon: Smargon = inject("smargon"), + smargon: Smargon = inject("gonio"), murko_results: MurkoResultsDevice = inject("murko_results"), oav_to_redis_forwarder: OAVToRedisForwarder = inject("oav_to_redis_forwarder"), ) -> MsgGenerator: @@ -157,7 +157,7 @@ def thaw_and_stream_to_redis( rotation: float = 360, robot: BartRobot = inject("robot"), thawer: Thawer = inject("thawer"), - smargon: Smargon = inject("smargon"), + smargon: Smargon = inject("gonio"), oav_to_redis_forwarder: OAVToRedisForwarder = inject("oav_to_redis_forwarder"), ) -> MsgGenerator: """Turns on the thawer and rotates the sample by {rotation} degrees to thaw it, then @@ -243,7 +243,7 @@ def get_metadata_from_current_oav(): ) yield from get_metadata_from_current_oav() - yield from bps.monitor(smargon.omega.user_readback, name="smargon") + yield from bps.monitor(smargon.omega.user_readback, name="gonio") yield from bps.monitor(oav_to_redis_forwarder.uuid, name="oav") yield from bps.kickoff(oav_to_redis_forwarder, wait=True) diff --git a/src/mx_bluesky/common/experiment_plans/change_aperture_then_move_plan.py b/src/mx_bluesky/common/experiment_plans/change_aperture_then_move_plan.py index e27854682b..4130873d67 100644 --- a/src/mx_bluesky/common/experiment_plans/change_aperture_then_move_plan.py +++ b/src/mx_bluesky/common/experiment_plans/change_aperture_then_move_plan.py @@ -28,7 +28,9 @@ def get_results_then_change_aperture_and_move_to_xtal( "Flyscan result event not received or no crystal found and exception not raised" ) yield from change_aperture_then_move_to_xtal( - flyscan_results[0], composite.smargon, composite.aperture_scatterguard + flyscan_results[0], + composite.gonio, + composite.aperture_scatterguard, ) diff --git a/src/mx_bluesky/common/experiment_plans/common_flyscan_xray_centre_plan.py b/src/mx_bluesky/common/experiment_plans/common_flyscan_xray_centre_plan.py index 0c64076547..c820dcd10d 100644 --- a/src/mx_bluesky/common/experiment_plans/common_flyscan_xray_centre_plan.py +++ b/src/mx_bluesky/common/experiment_plans/common_flyscan_xray_centre_plan.py @@ -26,7 +26,10 @@ PlanGroupCheckpointConstants, PlanNameConstants, ) -from mx_bluesky.common.parameters.device_composites import FlyScanEssentialDevices +from mx_bluesky.common.parameters.device_composites import ( + FlyScanEssentialDevices, + GonioWithOmegaType, +) from mx_bluesky.common.parameters.gridscan import SpecifiedThreeDGridScan from mx_bluesky.common.utils.exceptions import ( SampleError, @@ -110,7 +113,7 @@ def construct_beamline_specific_fast_gridscan_features( def common_flyscan_xray_centre( - composite: FlyScanEssentialDevices, + composite: FlyScanEssentialDevices[GonioWithOmegaType], parameters: SpecifiedThreeDGridScan, beamline_specific: BeamlineSpecificFGSFeatures, ) -> MsgGenerator: @@ -152,7 +155,7 @@ def _decorated_flyscan(): ) @bpp.finalize_decorator(lambda: _overall_tidy()) def run_gridscan_and_tidy( - fgs_composite: FlyScanEssentialDevices, + fgs_composite: FlyScanEssentialDevices[GonioWithOmegaType], params: SpecifiedThreeDGridScan, beamline_specific: BeamlineSpecificFGSFeatures, ) -> MsgGenerator: @@ -170,13 +173,13 @@ def run_gridscan_and_tidy( def run_gridscan( - fgs_composite: FlyScanEssentialDevices, + fgs_composite: FlyScanEssentialDevices[GonioWithOmegaType], parameters: SpecifiedThreeDGridScan, beamline_specific: BeamlineSpecificFGSFeatures, ): # Currently gridscan only works for omega 0, see https://github.com/DiamondLightSource/mx-bluesky/issues/410 with TRACER.start_span("moving_omega_to_0"): - yield from bps.abs_set(fgs_composite.smargon.omega, 0) + yield from bps.abs_set(fgs_composite.gonio.omega, 0) with TRACER.start_span("ispyb_hardware_readings"): yield from beamline_specific.read_pre_flyscan_plan() diff --git a/src/mx_bluesky/common/experiment_plans/common_grid_detect_then_xray_centre_plan.py b/src/mx_bluesky/common/experiment_plans/common_grid_detect_then_xray_centre_plan.py index 79312c6184..6dec47c6cd 100644 --- a/src/mx_bluesky/common/experiment_plans/common_grid_detect_then_xray_centre_plan.py +++ b/src/mx_bluesky/common/experiment_plans/common_grid_detect_then_xray_centre_plan.py @@ -106,7 +106,7 @@ def detect_grid_and_do_gridscan( grid_params_callback = GridDetectionCallback() yield from setup_beamline_for_oav( - composite.smargon, + composite.gonio, composite.backlight, composite.aperture_scatterguard, wait=True, @@ -121,7 +121,7 @@ def run_grid_detection_plan( grid_detect_composite = OavGridDetectionComposite( backlight=composite.backlight, oav=composite.oav, - smargon=composite.smargon, + gonio=composite.gonio, pin_tip_detection=composite.pin_tip_detection, ) diff --git a/src/mx_bluesky/common/experiment_plans/oav_grid_detection_plan.py b/src/mx_bluesky/common/experiment_plans/oav_grid_detection_plan.py index b2e0e2ff94..39cdf723e5 100644 --- a/src/mx_bluesky/common/experiment_plans/oav_grid_detection_plan.py +++ b/src/mx_bluesky/common/experiment_plans/oav_grid_detection_plan.py @@ -81,7 +81,7 @@ def grid_detection_plan( box_size_um (float): The size of each box of the grid in microns """ oav: OAV = composite.oav - smargon: Smargon = composite.smargon + smargon: Smargon = composite.gonio pin_tip_detection: PinTipDetection = composite.pin_tip_detection LOGGER.info("OAV Centring: Starting grid detection centring") diff --git a/src/mx_bluesky/common/experiment_plans/oav_snapshot_plan.py b/src/mx_bluesky/common/experiment_plans/oav_snapshot_plan.py index ad558faba9..5d7b94e665 100644 --- a/src/mx_bluesky/common/experiment_plans/oav_snapshot_plan.py +++ b/src/mx_bluesky/common/experiment_plans/oav_snapshot_plan.py @@ -21,7 +21,7 @@ class OavSnapshotComposite(Protocol): - smargon: Smargon + gonio: Smargon oav: OAV aperture_scatterguard: ApertureScatterguard @@ -78,15 +78,13 @@ def _generate_oav_snapshots(composite: OavSnapshotComposite, params: WithSnapsho for _ in 0, 270: yield from bps.create(DocDescriptorNames.OAV_ROTATION_SNAPSHOT_TRIGGERED) yield from bps.read(composite.oav) - yield from bps.read(composite.smargon) + yield from bps.read(composite.gonio) yield from bps.save() def _take_oav_snapshot(composite: OavSnapshotComposite, omega: float): """Create new snapshots by triggering the OAV""" - yield from bps.abs_set( - composite.smargon.omega, omega, group=OAV_SNAPSHOT_SETUP_SHOT - ) + yield from bps.abs_set(composite.gonio.omega, omega, group=OAV_SNAPSHOT_SETUP_SHOT) filename = _snapshot_filename(omega) yield from bps.abs_set( composite.oav.snapshot.filename, diff --git a/src/mx_bluesky/common/external_interaction/callbacks/common/grid_detection_callback.py b/src/mx_bluesky/common/external_interaction/callbacks/common/grid_detection_callback.py index 2407a584fa..b683c6dd9b 100644 --- a/src/mx_bluesky/common/external_interaction/callbacks/common/grid_detection_callback.py +++ b/src/mx_bluesky/common/external_interaction/callbacks/common/grid_detection_callback.py @@ -68,10 +68,8 @@ def event(self, doc: Event): top_left_y_px = data["oav-grid_snapshot-top_left_y"] y_of_centre_of_first_box_px = top_left_y_px + box_width_px / 2 - smargon_omega = data["smargon-omega"] - current_xyz = np.array( - [data["smargon-x"], data["smargon-y"], data["smargon-z"]] - ) + gonio_omega = data["gonio-omega"] + current_xyz = np.array([data["gonio-x"], data["gonio-y"], data["gonio-z"]]) centre_of_first_box = ( x_of_centre_of_first_box_px, @@ -89,7 +87,7 @@ def event(self, doc: Event): position_grid_start_mm = calculate_x_y_z_of_pixel( current_xyz, - smargon_omega, + gonio_omega, centre_of_first_box, (beam_x, beam_y), (microns_per_pixel_x, microns_per_pixel_y), @@ -98,19 +96,19 @@ def event(self, doc: Event): LOGGER.info(f"Calculated start position {position_grid_start_mm}") # If data is taken at omega=~0 then it gives us x-y info, at omega=~-90 it is x-z - if abs(smargon_omega) < self.OMEGA_TOLERANCE: + if abs(gonio_omega) < self.OMEGA_TOLERANCE: self.start_positions_um["x"] = position_grid_start_mm[0] * 1000 self.start_positions_um["y"] = position_grid_start_mm[1] * 1000 self.box_numbers["x"] = data["oav-grid_snapshot-num_boxes_x"] self.box_numbers["y"] = data["oav-grid_snapshot-num_boxes_y"] - elif abs(smargon_omega + 90) < self.OMEGA_TOLERANCE: + elif abs(gonio_omega + 90) < self.OMEGA_TOLERANCE: self.start_positions_um["x"] = position_grid_start_mm[0] * 1000 self.start_positions_um["z"] = position_grid_start_mm[2] * 1000 self.box_numbers["x"] = data["oav-grid_snapshot-num_boxes_x"] self.box_numbers["z"] = data["oav-grid_snapshot-num_boxes_y"] else: raise ValueError( - f"Grid detection only works at omegas of 0 or -90, omega of {smargon_omega} given." + f"Grid detection only works at omegas of 0 or -90, omega of {gonio_omega} given." ) self.x_step_size_um = box_width_px * microns_per_pixel_x diff --git a/src/mx_bluesky/common/external_interaction/callbacks/common/ispyb_callback_base.py b/src/mx_bluesky/common/external_interaction/callbacks/common/ispyb_callback_base.py index 7727570cd9..028e630a88 100644 --- a/src/mx_bluesky/common/external_interaction/callbacks/common/ispyb_callback_base.py +++ b/src/mx_bluesky/common/external_interaction/callbacks/common/ispyb_callback_base.py @@ -145,9 +145,9 @@ def _handle_ispyb_hardware_read(self, doc) -> Sequence[ScanDataInfo]: doc, self.params.detector_params, hwscan_data_collection_info ) hwscan_position_info = DataCollectionPositionInfo( - pos_x=float(doc["data"]["smargon-x"]), - pos_y=float(doc["data"]["smargon-y"]), - pos_z=float(doc["data"]["smargon-z"]), + pos_x=float(doc["data"]["gonio-x"]), + pos_y=float(doc["data"]["gonio-y"]), + pos_z=float(doc["data"]["gonio-z"]), ) scan_data_infos = self.populate_info_for_update( hwscan_data_collection_info, hwscan_position_info, self.params diff --git a/src/mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_callback.py b/src/mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_callback.py index 82190e7c92..cdf6265576 100644 --- a/src/mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_callback.py +++ b/src/mx_bluesky/common/external_interaction/callbacks/xray_centre/ispyb_callback.py @@ -187,7 +187,7 @@ def _handle_oav_grid_snapshot_triggered(self, doc) -> Sequence[ScanDataInfo]: assert self.params, "ISPyB handler didn't receive parameters!" assert self.data_collection_group_info, "No data collection group" data = doc["data"] - omega = doc["data"]["smargon-omega"] + omega = doc["data"]["gonio-omega"] grid_plane = _smargon_omega_to_xyxz_plane(omega) ISPYB_ZOCALO_CALLBACK_LOGGER.info( f"Generating dc info for gridplane {grid_plane}, omega {omega}" @@ -255,13 +255,13 @@ def _handle_oav_grid_snapshot_triggered(self, doc) -> Sequence[ScanDataInfo]: return [scan_data_info] def _populate_axis_info(self, data_collection_info: DataCollectionInfo, doc: dict): - if (omega_start := doc.get("smargon-omega")) is not None: + if (omega_start := doc.get("gonio-omega")) is not None: omega_in_gda_space = -omega_start data_collection_info.omega_start = omega_in_gda_space data_collection_info.axis_start = omega_in_gda_space data_collection_info.axis_end = omega_in_gda_space data_collection_info.axis_range = 0 - if (chi_start := doc.get("smargon-chi")) is not None: + if (chi_start := doc.get("gonio-chi")) is not None: data_collection_info.chi_start = chi_start def populate_info_for_update( diff --git a/src/mx_bluesky/common/parameters/device_composites.py b/src/mx_bluesky/common/parameters/device_composites.py index 6dfc702d55..55bd7ffb42 100644 --- a/src/mx_bluesky/common/parameters/device_composites.py +++ b/src/mx_bluesky/common/parameters/device_composites.py @@ -1,3 +1,5 @@ +from typing import Generic, Protocol, TypeVar, runtime_checkable + import pydantic from dodal.devices.aperturescatterguard import ( ApertureScatterguard, @@ -24,13 +26,23 @@ from dodal.devices.zebra.zebra import Zebra from dodal.devices.zebra.zebra_controlled_shutter import ZebraShutter from dodal.devices.zocalo import ZocaloResults +from ophyd_async.epics.motor import Motor + + +# MX gridscans only uses the gonio to set omega to 0. Other motors are only accessed in the motion program +@runtime_checkable +class GonioWithOmega(Protocol): + omega: Motor + + +GonioWithOmegaType = TypeVar("GonioWithOmegaType", bound=GonioWithOmega) @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True}) -class FlyScanEssentialDevices: +class FlyScanEssentialDevices(Generic[GonioWithOmegaType]): eiger: EigerDetector synchrotron: Synchrotron - smargon: Smargon + gonio: GonioWithOmegaType @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True}) @@ -39,12 +51,12 @@ class OavGridDetectionComposite: backlight: Backlight oav: OAV - smargon: Smargon + gonio: Smargon pin_tip_detection: PinTipDetection @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True}) -class GridDetectThenXRayCentreComposite(FlyScanEssentialDevices): +class GridDetectThenXRayCentreComposite(FlyScanEssentialDevices[Smargon]): """All devices which are directly or indirectly required by this plan""" aperture_scatterguard: ApertureScatterguard diff --git a/src/mx_bluesky/hyperion/blueapi_plans/__init__.py b/src/mx_bluesky/hyperion/blueapi_plans/__init__.py index 93eb4b5c86..7bb0ee1372 100644 --- a/src/mx_bluesky/hyperion/blueapi_plans/__init__.py +++ b/src/mx_bluesky/hyperion/blueapi_plans/__init__.py @@ -60,7 +60,7 @@ def load_centre_collect( def robot_unload( visit: str, robot: BartRobot = inject("robot"), - smargon: Smargon = inject("smargon"), + smargon: Smargon = inject("gonio"), aperture_scatterguard: ApertureScatterguard = inject("aperture_scatterguard"), lower_gonio: XYZStage = inject("lower_gonio"), ) -> MsgGenerator: @@ -75,7 +75,7 @@ def clean_up_udc( visit: str, cleanup_group: str = "cleanup", robot: BartRobot = inject("robot"), - smargon: Smargon = inject("smargon"), + smargon: Smargon = inject("gonio"), aperture_scatterguard: ApertureScatterguard = inject("aperture_scatterguard"), lower_gonio: XYZStage = inject("lower_gonio"), detector_motion: DetectorMotion = inject("detector_motion"), diff --git a/src/mx_bluesky/hyperion/experiment_plans/hyperion_flyscan_xray_centre_plan.py b/src/mx_bluesky/hyperion/experiment_plans/hyperion_flyscan_xray_centre_plan.py index 2e3eba41cc..6c3378e910 100755 --- a/src/mx_bluesky/hyperion/experiment_plans/hyperion_flyscan_xray_centre_plan.py +++ b/src/mx_bluesky/hyperion/experiment_plans/hyperion_flyscan_xray_centre_plan.py @@ -50,7 +50,7 @@ def construct_hyperion_specific_features( xrc_composite.undulator.current_gap, xrc_composite.synchrotron.synchrotron_mode, xrc_composite.s4_slit_gaps, - xrc_composite.smargon, + xrc_composite.gonio, xrc_composite.dcm.energy_in_keV, ] @@ -129,9 +129,7 @@ def _panda_triggering_setup( time_between_x_steps_ms = (detector_deadtime_s + parameters.exposure_time_s) * 1e3 - smargon_speed_limit_mm_per_s = yield from bps.rd( - xrc_composite.smargon.x.max_velocity - ) + smargon_speed_limit_mm_per_s = yield from bps.rd(xrc_composite.gonio.x.max_velocity) sample_velocity_mm_per_s = ( parameters.panda_fast_gridscan_params.x_step_size_mm @@ -163,7 +161,7 @@ def _panda_triggering_setup( yield from setup_panda_for_flyscan( xrc_composite.panda, parameters.panda_fast_gridscan_params, - xrc_composite.smargon, + xrc_composite.gonio, parameters.exposure_time_s, time_between_x_steps_ms, sample_velocity_mm_per_s, diff --git a/src/mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py b/src/mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py index d5a9798493..00b52a9f5e 100644 --- a/src/mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +++ b/src/mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py @@ -161,9 +161,9 @@ def _samples_and_locations_to_collect( else: # If the xray centring hasn't found a result but has not thrown an error it # means that we do not need to recentre and can collect where we are - initial_x_mm = yield from bps.rd(composite.smargon.x.user_readback) - initial_y_mm = yield from bps.rd(composite.smargon.y.user_readback) - initial_z_mm = yield from bps.rd(composite.smargon.z.user_readback) + initial_x_mm = yield from bps.rd(composite.gonio.x.user_readback) + initial_y_mm = yield from bps.rd(composite.gonio.y.user_readback) + initial_z_mm = yield from bps.rd(composite.gonio.z.user_readback) return [ ( diff --git a/src/mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py b/src/mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py index 0cb8e74859..c0e6015495 100644 --- a/src/mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +++ b/src/mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py @@ -86,7 +86,7 @@ def pin_centre_then_xray_centre_plan( pin_tip_centring_composite = PinTipCentringComposite( oav=composite.oav, - gonio=composite.smargon, + gonio=composite.gonio, pin_tip_detection=composite.pin_tip_detection, ) @@ -96,11 +96,11 @@ def pin_centre_then_xray_centre_plan( @subs_decorator(flyscan_event_handler) def _pin_centre_then_gridscan_and_xrc(): yield from setup_beamline_for_oav( - composite.smargon, composite.backlight, composite.aperture_scatterguard + composite.gonio, composite.backlight, composite.aperture_scatterguard ) yield from move_phi_chi_omega( - composite.smargon, + composite.gonio, parameters.phi_start_deg, parameters.chi_start_deg, group=CONST.WAIT.READY_FOR_OAV, diff --git a/src/mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py b/src/mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py index 3c0fd49a21..a8148efc11 100644 --- a/src/mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +++ b/src/mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py @@ -52,7 +52,7 @@ class RobotLoadAndEnergyChangeComposite: lower_gonio: XYZStage thawer: Thawer oav: OAV - smargon: Smargon + gonio: Smargon aperture_scatterguard: ApertureScatterguard backlight: Backlight @@ -153,9 +153,7 @@ def robot_load_and_change_energy_plan( sample_location = SampleLocation(params.sample_puck, params.sample_pin) - yield from prepare_for_robot_load( - composite.aperture_scatterguard, composite.smargon - ) + yield from prepare_for_robot_load(composite.aperture_scatterguard, composite.gonio) yield from bpp.set_run_key_wrapper( bpp.run_wrapper( diff --git a/src/mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py b/src/mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py index c2e68c725e..0d17d2fd2b 100644 --- a/src/mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +++ b/src/mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py @@ -78,7 +78,7 @@ class RobotLoadThenCentreComposite: flux: Flux oav: OAV pin_tip_detection: PinTipDetection - smargon: Smargon + gonio: Smargon synchrotron: Synchrotron s4_slit_gaps: S4SlitGaps undulator: UndulatorInKeV @@ -152,7 +152,7 @@ def robot_load_then_xray_centre( yield from pin_already_loaded(composite.robot, sample_location) ) - current_chi = yield from bps.rd(composite.smargon.chi) + current_chi = yield from bps.rd(composite.gonio.chi) LOGGER.info(f"Read back current smargon chi of {current_chi} degrees.") doing_chi_change = parameters.chi_start_deg is not None and not isclose( current_chi, parameters.chi_start_deg, abs_tol=0.001 diff --git a/src/mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py b/src/mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py index e3583fae27..c85398099a 100644 --- a/src/mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +++ b/src/mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py @@ -83,7 +83,7 @@ class RotationScanComposite(OavSnapshotComposite): eiger: EigerDetector flux: Flux robot: BartRobot - smargon: Smargon + gonio: Smargon undulator: UndulatorInKeV synchrotron: Synchrotron s4_slit_gaps: S4SlitGaps @@ -120,7 +120,7 @@ def _rotation_scan_plan( motion_values: RotationMotionProfile, composite: RotationScanComposite, ): - axis = composite.smargon.omega + axis = composite.gonio.omega # can move to start as fast as possible yield from bps.abs_set( @@ -165,7 +165,7 @@ def _rotation_scan_plan( composite.synchrotron, composite.s4_slit_gaps, composite.dcm, - composite.smargon, + composite.gonio, ) # Get ready for the actual scan @@ -200,9 +200,9 @@ def _rotation_scan_plan( def _cleanup_plan(composite: RotationScanComposite, **kwargs): LOGGER.info("Cleaning up after rotation scan") - max_vel = yield from bps.rd(composite.smargon.omega.max_velocity) + max_vel = yield from bps.rd(composite.gonio.omega.max_velocity) yield from cleanup_sample_environment(composite.detector_motion, group="cleanup") - yield from bps.abs_set(composite.smargon.omega.velocity, max_vel, group="cleanup") + yield from bps.abs_set(composite.gonio.omega.velocity, max_vel, group="cleanup") yield from tidy_up_zebra_after_rotation_scan( composite.zebra, composite.sample_shutter, group="cleanup", wait=False ) @@ -214,8 +214,8 @@ def _move_and_rotation( params: SingleRotationScan, oav_params: OAVParameters, ): - motor_time_to_speed = yield from bps.rd(composite.smargon.omega.acceleration_time) - max_vel = yield from bps.rd(composite.smargon.omega.max_velocity) + motor_time_to_speed = yield from bps.rd(composite.gonio.omega.acceleration_time) + max_vel = yield from bps.rd(composite.gonio.omega.max_velocity) motion_values = calculate_motion_profile(params, motor_time_to_speed, max_vel) def _div_by_1000_if_not_none(num: float | None): @@ -223,7 +223,7 @@ def _div_by_1000_if_not_none(num: float | None): LOGGER.info("moving to position (if specified)") yield from bps.abs_set( - composite.smargon, + composite.gonio, CombinedMove( x=_div_by_1000_if_not_none(params.x_start_um), y=_div_by_1000_if_not_none(params.y_start_um), @@ -239,7 +239,7 @@ def _div_by_1000_if_not_none(num: float | None): if not params.use_grid_snapshots: yield from setup_beamline_for_oav( - composite.smargon, + composite.gonio, composite.backlight, composite.aperture_scatterguard, wait=True, diff --git a/src/mx_bluesky/hyperion/experiment_plans/udc_default_state.py b/src/mx_bluesky/hyperion/experiment_plans/udc_default_state.py index b8d9a78544..6aa6555f70 100644 --- a/src/mx_bluesky/hyperion/experiment_plans/udc_default_state.py +++ b/src/mx_bluesky/hyperion/experiment_plans/udc_default_state.py @@ -49,7 +49,7 @@ class UDCDefaultDevices(BeamstopCheckDevices): hutch_shutter: HutchShutter robot: BartRobot scintillator: Scintillator - smargon: Smargon + gonio: Smargon oav: OAV diff --git a/src/mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py b/src/mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py index e5d7790816..6b251c86d1 100644 --- a/src/mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +++ b/src/mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py @@ -136,9 +136,9 @@ def _handle_ispyb_hardware_read(self, doc: Event): """Use the hardware read values to create the ispyb comment""" scan_data_infos = super()._handle_ispyb_hardware_read(doc) motor_positions_mm = [ - doc["data"]["smargon-x"], - doc["data"]["smargon-y"], - doc["data"]["smargon-z"], + doc["data"]["gonio-x"], + doc["data"]["gonio-y"], + doc["data"]["gonio-z"], ] assert self.params, ( "handle_ispyb_hardware_read triggered before activity_gated_start" diff --git a/src/mx_bluesky/hyperion/external_interaction/callbacks/snapshot_callback.py b/src/mx_bluesky/hyperion/external_interaction/callbacks/snapshot_callback.py index e0bf242ad3..e6ea6b4e02 100644 --- a/src/mx_bluesky/hyperion/external_interaction/callbacks/snapshot_callback.py +++ b/src/mx_bluesky/hyperion/external_interaction/callbacks/snapshot_callback.py @@ -42,7 +42,7 @@ class BeamDrawingCallback(PlanReactiveCallback): """ Callback that monitors for OAV_ROTATION_SNAPSHOT_TRIGGERED events and draws a crosshair at the beam centre, saving the snapshot to a file. - The callback assumes an OAV device "oav" and Smargon "smargon" + The callback assumes an OAV device "oav" and Smargon "gonio" Examples: Take a rotation snapshot at the current location >>> from bluesky.run_engine import RunEngine @@ -144,11 +144,11 @@ def _extract_base_snapshot_params( ), snapshot_path=base_snapshot_path, sample_pos_mm=( - data.get("smargon-x", 0.0), - data.get("smargon-y", 0.0), - data.get("smargon-z", 0.0), + data.get("gonio-x", 0.0), + data.get("gonio-y", 0.0), + data.get("gonio-z", 0.0), ), - omega=round(data.get("smargon-omega", 0.0)), + omega=round(data.get("gonio-omega", 0.0)), ) def _handle_grid_snapshot(self, doc: Event): @@ -165,9 +165,9 @@ def _handle_rotation_snapshot(self, doc: Event) -> Event: "Insufficient base gridscan snapshots to generate required rotation snapshots" ) current_sample_pos_mm = ( - data["smargon-x"], - data["smargon-y"], - data["smargon-z"], + data["gonio-x"], + data["gonio-y"], + data["gonio-z"], ) CALLBACK_LOGGER.info( f"Generating snapshot at {current_sample_pos_mm} from base snapshot {snapshot_info}" diff --git a/src/mx_bluesky/hyperion/in_process_runner.py b/src/mx_bluesky/hyperion/in_process_runner.py index 8575632a3e..7f5264e9f5 100644 --- a/src/mx_bluesky/hyperion/in_process_runner.py +++ b/src/mx_bluesky/hyperion/in_process_runner.py @@ -117,7 +117,7 @@ def _runner_sleep(parameters: Wait) -> MsgGenerator: def _clean_up_udc(context: BlueskyContext, visit: str) -> MsgGenerator: robot = find_device_in_context(context, "robot", BartRobot) - smargon = find_device_in_context(context, "smargon", Smargon) + smargon = find_device_in_context(context, "gonio", Smargon) aperture_scatterguard = find_device_in_context( context, "aperture_scatterguard", ApertureScatterguard ) diff --git a/src/mx_bluesky/hyperion/parameters/device_composites.py b/src/mx_bluesky/hyperion/parameters/device_composites.py index 11bd9f98b1..9610a26087 100644 --- a/src/mx_bluesky/hyperion/parameters/device_composites.py +++ b/src/mx_bluesky/hyperion/parameters/device_composites.py @@ -16,6 +16,7 @@ from dodal.devices.flux import Flux from dodal.devices.robot import BartRobot from dodal.devices.s4_slit_gaps import S4SlitGaps +from dodal.devices.smargon import Smargon from dodal.devices.synchrotron import Synchrotron from dodal.devices.undulator import UndulatorInKeV from dodal.devices.xbpm_feedback import XBPMFeedback @@ -24,16 +25,14 @@ from dodal.devices.zocalo import ZocaloResults from ophyd_async.fastcs.panda import HDFPanda -from mx_bluesky.common.experiment_plans.common_flyscan_xray_centre_plan import ( - FlyScanEssentialDevices, -) from mx_bluesky.common.parameters.device_composites import ( + FlyScanEssentialDevices, GridDetectThenXRayCentreComposite, ) @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True}) -class HyperionFlyScanXRayCentreComposite(FlyScanEssentialDevices): +class HyperionFlyScanXRayCentreComposite(FlyScanEssentialDevices[Smargon]): """All devices which are directly or indirectly required by this plan""" aperture_scatterguard: ApertureScatterguard diff --git a/tests/conftest.py b/tests/conftest.py index 0efab4e664..2abbd2fb4f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -795,7 +795,7 @@ def fake_create_devices( devices = { "beamstop": beamstop_phase1, "eiger": eiger, - "smargon": smargon, + "gonio": smargon, "zebra": zebra, "detector_motion": detector_motion, "backlight": backlight, @@ -837,7 +837,7 @@ def fake_create_rotation_devices( detector_motion=detector_motion, eiger=eiger, flux=flux, - smargon=smargon, + gonio=smargon, undulator=undulator, aperture_scatterguard=aperture_scatterguard, synchrotron=synchrotron, @@ -981,7 +981,7 @@ async def hyperion_flyscan_xrc_composite( zebra_fast_grid_scan=fast_grid_scan, flux=i03.flux.build(connect_immediately=True, mock=True), s4_slit_gaps=s4_slit_gaps, - smargon=smargon, + gonio=smargon, undulator=i03.undulator.build(connect_immediately=True, mock=True), synchrotron=synchrotron, xbpm_feedback=xbpm_feedback, @@ -1017,7 +1017,7 @@ async def mock_complete(result): side_effect=partial(mock_complete, test_result) ) # type: ignore fake_composite.zocalo.timeout_s = 3 - set_mock_value(fake_composite.smargon.x.max_velocity, 10) + set_mock_value(fake_composite.gonio.x.max_velocity, 10) set_mock_value(fake_composite.robot.barcode, "BARCODE") @@ -1300,11 +1300,11 @@ class OavGridSnapshotTestEvents: "oav-grid_snapshot-last_path_full_overlay": "test_1_y", "oav-grid_snapshot-last_path_outer": "test_2_y", "oav-grid_snapshot-last_saved_path": "test_3_y", - "smargon-omega": 0, - "smargon-chi": 0, - "smargon-x": 0, - "smargon-y": 0, - "smargon-z": 0, + "gonio-omega": 0, + "gonio-chi": 0, + "gonio-x": 0, + "gonio-y": 0, + "gonio-z": 0, }, } test_event_document_oav_snapshot_xz: Event = { @@ -1329,11 +1329,11 @@ class OavGridSnapshotTestEvents: "oav-x_direction": -1, "oav-y_direction": -1, "oav-z_direction": 1, - "smargon-omega": -90, - "smargon-chi": 30, - "smargon-x": 0, - "smargon-y": 0, - "smargon-z": 0, + "gonio-omega": -90, + "gonio-chi": 30, + "gonio-x": 0, + "gonio-y": 0, + "gonio-z": 0, }, } @@ -1502,9 +1502,9 @@ def test_event_document_pre_data_collection(self) -> Event: "s4_slit_gaps-ygap": 0.2345, "synchrotron-synchrotron_mode": SynchrotronMode.USER, "undulator-current_gap": 1.234, - "smargon-x": 0.158435435, - "smargon-y": 0.023547354, - "smargon-z": 0.00345684712, + "gonio-x": 0.158435435, + "gonio-y": 0.023547354, + "gonio-z": 0.00345684712, "dcm-energy_in_keV": 11.105, }, "timestamps": {"det1": 1666604299.8220396, "det2": 1666604299.8235943}, diff --git a/tests/system_tests/hyperion/external_interaction/conftest.py b/tests/system_tests/hyperion/external_interaction/conftest.py index 501924d095..1fc532e8e5 100644 --- a/tests/system_tests/hyperion/external_interaction/conftest.py +++ b/tests/system_tests/hyperion/external_interaction/conftest.py @@ -288,7 +288,7 @@ def grid_detect_then_xray_centre_composite( backlight=backlight, beamstop=beamstop_phase1, panda_fast_grid_scan=panda_fast_grid_scan, - smargon=smargon, + gonio=smargon, undulator=undulator_for_system_test, synchrotron=synchrotron, s4_slit_gaps=s4_slit_gaps, @@ -364,7 +364,7 @@ def fgs_composite_for_fake_zocalo( hyperion_flyscan_xrc_composite.eiger.unstage = MagicMock( side_effect=lambda: completed_status() ) # type: ignore - hyperion_flyscan_xrc_composite.smargon.stub_offsets.set = MagicMock( + hyperion_flyscan_xrc_composite.gonio.stub_offsets.set = MagicMock( side_effect=lambda _: completed_status() ) # type: ignore callback_on_mock_put( @@ -449,7 +449,7 @@ def composite_for_rotation_scan( detector_motion=detector_motion, eiger=eiger, flux=flux, - smargon=smargon, + gonio=smargon, undulator=undulator_for_system_test, aperture_scatterguard=aperture_scatterguard, synchrotron=synchrotron, diff --git a/tests/system_tests/hyperion/external_interaction/test_load_centre_collect_full_plan.py b/tests/system_tests/hyperion/external_interaction/test_load_centre_collect_full_plan.py index 8d0ad11f62..a9619669b0 100644 --- a/tests/system_tests/hyperion/external_interaction/test_load_centre_collect_full_plan.py +++ b/tests/system_tests/hyperion/external_interaction/test_load_centre_collect_full_plan.py @@ -141,7 +141,7 @@ def load_centre_collect_composite( eiger=grid_detect_then_xray_centre_composite.eiger, flux=composite_for_rotation_scan.flux, robot=composite_for_rotation_scan.robot, - smargon=composite_for_rotation_scan.smargon, + gonio=composite_for_rotation_scan.gonio, undulator=composite_for_rotation_scan.undulator, synchrotron=composite_for_rotation_scan.synchrotron, s4_slit_gaps=composite_for_rotation_scan.s4_slit_gaps, @@ -466,7 +466,7 @@ def test_execute_load_centre_collect_full_triggers_zocalo_with_correct_grids( set_mock_value(load_centre_collect_composite.robot.current_puck, 2) def move_to_initial_omega(): - yield from bps.mv(load_centre_collect_composite.smargon.omega, initial_omega) + yield from bps.mv(load_centre_collect_composite.gonio.omega, initial_omega) run_engine(move_to_initial_omega()) ispyb_gridscan_cb = GridscanISPyBCallback( @@ -1079,11 +1079,11 @@ def patch_detect_grid_and_do_gridscan_with_detected_pin_position( # This is the base snapshot position def wrapper(*args, **kwargs): yield from bps.mv( - load_centre_collect_composite.smargon.x, + load_centre_collect_composite.gonio.x, -0.614, - load_centre_collect_composite.smargon.y, + load_centre_collect_composite.gonio.y, 0.0259, - load_centre_collect_composite.smargon.z, + load_centre_collect_composite.gonio.z, 0.250, ) diff --git a/tests/unit_tests/beamlines/i04/callbacks/test_murko_callback.py b/tests/unit_tests/beamlines/i04/callbacks/test_murko_callback.py index 524c089123..cb81c984fe 100644 --- a/tests/unit_tests/beamlines/i04/callbacks/test_murko_callback.py +++ b/tests/unit_tests/beamlines/i04/callbacks/test_murko_callback.py @@ -44,7 +44,7 @@ def event_template(data_dict: dict, timestamp=1666604299.0) -> Event: "oav-beam_centre_j": 342, } ) -test_smargon_event = event_template({"smargon-omega": test_smargon_data}) +test_smargon_event = event_template({"gonio-omega": test_smargon_data}) test_start_document = { "uid": "event_uuid", @@ -112,7 +112,7 @@ def test_given_image_data_when_first_two_sets_of_smargon_data_arrive_then_murko_ murko_with_mock_call.call_murko.reset_mock() # type: ignore - murko_with_mock_call.event(event_template({"smargon-omega": (second_omega := 30)})) + murko_with_mock_call.event(event_template({"gonio-omega": (second_omega := 30)})) murko_with_mock_call.call_murko.assert_called_once_with( # type: ignore test_oav_uuid, second_omega ) @@ -122,8 +122,8 @@ def test_given_two_sets_of_smargon_data_then_next_image_calls_murko_with_extrapo murko_with_mock_call: MurkoCallback, ): murko_with_mock_call.event(test_oav_full_screen_event) - murko_with_mock_call.event(event_template({"smargon-omega": 10}, 0)) - murko_with_mock_call.event(event_template({"smargon-omega": 15}, 5)) + murko_with_mock_call.event(event_template({"gonio-omega": 10}, 0)) + murko_with_mock_call.event(event_template({"gonio-omega": 15}, 5)) murko_with_mock_call.call_murko.reset_mock() # type:ignore @@ -138,9 +138,9 @@ def test_given_three_sets_of_smargon_data_then_next_image_calls_murko_with_extra murko_with_mock_call: MurkoCallback, ): murko_with_mock_call.event(test_oav_full_screen_event) - murko_with_mock_call.event(event_template({"smargon-omega": 10}, 0)) - murko_with_mock_call.event(event_template({"smargon-omega": 15}, 5)) - murko_with_mock_call.event(event_template({"smargon-omega": 17}, 6)) + murko_with_mock_call.event(event_template({"gonio-omega": 10}, 0)) + murko_with_mock_call.event(event_template({"gonio-omega": 15}, 5)) + murko_with_mock_call.event(event_template({"gonio-omega": 17}, 6)) murko_with_mock_call.call_murko.reset_mock() # type:ignore diff --git a/tests/unit_tests/beamlines/i04/test_i04_grid_detect_then_xray_centre_plan.py b/tests/unit_tests/beamlines/i04/test_i04_grid_detect_then_xray_centre_plan.py index 728b957ae5..e21a63aa11 100644 --- a/tests/unit_tests/beamlines/i04/test_i04_grid_detect_then_xray_centre_plan.py +++ b/tests/unit_tests/beamlines/i04/test_i04_grid_detect_then_xray_centre_plan.py @@ -136,7 +136,7 @@ def test_get_ready_for_oav_and_close_shutter_closes_shutter_and_calls_setup_for_ msgs = sim_run_engine.simulate_plan( get_ready_for_oav_and_close_shutter( - grid_detect_xrc_devices.smargon, + grid_detect_xrc_devices.gonio, grid_detect_xrc_devices.backlight, grid_detect_xrc_devices.aperture_scatterguard, grid_detect_xrc_devices.detector_motion, diff --git a/tests/unit_tests/beamlines/i04/test_thawing.py b/tests/unit_tests/beamlines/i04/test_thawing.py index 15e47c4486..8d641da9c6 100644 --- a/tests/unit_tests/beamlines/i04/test_thawing.py +++ b/tests/unit_tests/beamlines/i04/test_thawing.py @@ -65,7 +65,7 @@ async def oav_roi() -> OAV: @pytest.fixture async def smargon() -> Smargon: - smargon = Smargon(prefix="BL04I-MO-SGON-01:", name="smargon") + smargon = Smargon(prefix="BL04I-MO-SGON-01:", name="gonio") await smargon.connect(mock=True) set_mock_value(smargon.omega.user_readback, 0.0) @@ -249,7 +249,7 @@ def test_thaw_and_stream_adds_murko_callback_and_produces_expected_messages( oav_updates = [ e for e in event_params if "oav_to_redis_forwarder-uuid" in e["data"].keys() ] - smargon_updates = [e for e in event_params if "smargon-omega" in e["data"].keys()] + smargon_updates = [e for e in event_params if "gonio-omega" in e["data"].keys()] assert len(oav_updates) > 0 assert len(smargon_updates) > 0 @@ -307,7 +307,7 @@ def _test_plan_will_switch_murko_source_half_way_through_thaw( ) msgs = assert_message_and_return_remaining( msgs, - lambda msg: msg.command == "set" and msg.obj.name == "smargon-omega", + lambda msg: msg.command == "set" and msg.obj.name == "gonio-omega", ) msgs = assert_message_and_return_remaining( msgs, diff --git a/tests/unit_tests/common/device_setup_plans/test_robot_load_unload.py b/tests/unit_tests/common/device_setup_plans/test_robot_load_unload.py index 02b1ad3c64..6bb307cf6d 100644 --- a/tests/unit_tests/common/device_setup_plans/test_robot_load_unload.py +++ b/tests/unit_tests/common/device_setup_plans/test_robot_load_unload.py @@ -80,7 +80,7 @@ async def test_when_robot_unload_called_then_sample_area_prepared_before_load( assert_message_and_return_remaining( msgs, lambda msg: msg.command == "set" - and msg.obj.name == "smargon" + and msg.obj.name == "gonio" and msg.args[0] == CombinedMove(x=0, y=0, z=0, omega=0, chi=0, phi=0), ) diff --git a/tests/unit_tests/common/experiment_plans/test_common_flyscan_xray_centre_plan.py b/tests/unit_tests/common/experiment_plans/test_common_flyscan_xray_centre_plan.py index c1bf6d7b44..2537c8331f 100644 --- a/tests/unit_tests/common/experiment_plans/test_common_flyscan_xray_centre_plan.py +++ b/tests/unit_tests/common/experiment_plans/test_common_flyscan_xray_centre_plan.py @@ -126,7 +126,7 @@ def test_when_run_gridscan_called_ispyb_deposition_made_and_records_errors( run_engine.subscribe(ispyb_callback) error = None - with patch.object(fake_fgs_composite.smargon.omega, "set") as mock_set: + with patch.object(fake_fgs_composite.gonio.omega, "set") as mock_set: error = AssertionError("Test Exception") mock_set.side_effect = FailedStatus(error) with pytest.raises(FailedStatus): @@ -160,9 +160,9 @@ def test_results_passed_to_move_motors( np.array([1, 2, 3]) ) ) - run_engine(move_x_y_z(fake_fgs_composite.smargon, *motor_position)) + run_engine(move_x_y_z(fake_fgs_composite.gonio, *motor_position)) bps_abs_set.assert_called_with( - fake_fgs_composite.smargon, + fake_fgs_composite.gonio, CombinedMove(x=motor_position[0], y=motor_position[1], z=motor_position[2]), group="move_x_y_z", ) diff --git a/tests/unit_tests/common/experiment_plans/test_grid_detection_plan.py b/tests/unit_tests/common/experiment_plans/test_grid_detection_plan.py index 88ee27981c..dddc7e6533 100644 --- a/tests/unit_tests/common/experiment_plans/test_grid_detection_plan.py +++ b/tests/unit_tests/common/experiment_plans/test_grid_detection_plan.py @@ -86,7 +86,7 @@ def fake_devices( composite = OavGridDetectionComposite( backlight=backlight, oav=oav, - smargon=smargon, + gonio=smargon, pin_tip_detection=pin_tip_detection, ) @@ -324,7 +324,7 @@ def test_when_grid_detection_plan_run_with_different_omega_order_then_grid_detec composite, _ = fake_devices # This will cause the grid detect plan to take data at -90 first - set_mock_value(composite.smargon.omega.user_readback, -90) + set_mock_value(composite.gonio.omega.user_readback, -90) composite.pin_tip_detection._get_tip_and_edge_data = AsyncMock( side_effect=[X_Z_EDGE_DATA, X_Y_EDGE_DATA] ) @@ -372,10 +372,10 @@ def test_given_unexpected_omega_then_grid_detect_raises(tmp_path: Path): "oav-x_direction": -1, "oav-y_direction": -1, "oav-z_direction": 1, - "smargon-x": 100, - "smargon-y": 234, - "smargon-z": 467, - "smargon-omega": 45, + "gonio-x": 100, + "gonio-y": 234, + "gonio-z": 467, + "gonio-omega": 45, } } diff --git a/tests/unit_tests/common/experiment_plans/test_oav_snapshot_plan.py b/tests/unit_tests/common/experiment_plans/test_oav_snapshot_plan.py index 236a91ffc5..09b6f5bdcb 100644 --- a/tests/unit_tests/common/experiment_plans/test_oav_snapshot_plan.py +++ b/tests/unit_tests/common/experiment_plans/test_oav_snapshot_plan.py @@ -35,7 +35,7 @@ def oav_snapshot_params(tmp_path): @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True}) class CompositeImpl(OavSnapshotComposite): - smargon: Smargon + gonio: Smargon oav: OAV aperture_scatterguard: ApertureScatterguard backlight: Backlight @@ -44,7 +44,7 @@ class CompositeImpl(OavSnapshotComposite): @pytest.fixture def oav_snapshot_composite(smargon, oav, aperture_scatterguard, backlight): return CompositeImpl( - smargon=smargon, + gonio=smargon, oav=oav, aperture_scatterguard=aperture_scatterguard, backlight=backlight, @@ -118,7 +118,7 @@ def test_oav_snapshot_plan_issues_rotations_and_generates_events( msgs = assert_message_and_return_remaining( msgs, lambda msg: msg.command == "set" - and msg.obj.name == "smargon-omega" + and msg.obj.name == "gonio-omega" and msg.args[0] == expected["omega"] and msg.kwargs["group"] == OAV_SNAPSHOT_SETUP_SHOT, ) @@ -176,7 +176,7 @@ def test_oav_snapshot_plan_generates_snapshots_events_without_triggering_oav_whe assert not [ msg for msg in msgs - if msg.command == "set" and msg.obj is oav_snapshot_composite.smargon.omega + if msg.command == "set" and msg.obj is oav_snapshot_composite.gonio.omega ] expected_snapshot_directory = str(oav_snapshot_params.snapshot_directory) msgs = assert_message_and_return_remaining( @@ -201,7 +201,7 @@ def test_oav_snapshot_plan_generates_snapshots_events_without_triggering_oav_whe msgs = assert_message_and_return_remaining( msgs, lambda msg: msg.command == "read" - and msg.obj is oav_snapshot_composite.smargon, + and msg.obj is oav_snapshot_composite.gonio, ) msgs = assert_message_and_return_remaining( msgs, lambda msg: msg.command == "save" diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py index a409a0fee2..adbe3e43ca 100644 --- a/tests/unit_tests/conftest.py +++ b/tests/unit_tests/conftest.py @@ -45,7 +45,6 @@ from mx_bluesky.common.experiment_plans.beamstop_check import BeamstopCheckDevices from mx_bluesky.common.experiment_plans.common_flyscan_xray_centre_plan import ( BeamlineSpecificFGSFeatures, - FlyScanEssentialDevices, ) from mx_bluesky.common.external_interaction.callbacks.common.zocalo_callback import ( ZocaloCallback, @@ -70,6 +69,7 @@ PlanNameConstants, ) from mx_bluesky.common.parameters.device_composites import ( + FlyScanEssentialDevices, GridDetectThenXRayCentreComposite, ) from mx_bluesky.common.parameters.gridscan import GridCommon, SpecifiedThreeDGridScan @@ -144,9 +144,9 @@ async def fail_test_on_unclosed_tasks(request: FixtureRequest): "synchrotron-synchrotron_mode": SynchrotronMode.USER, "s4_slit_gaps-xgap": 0, "s4_slit_gaps-ygap": 0, - "smargon-x": 10.0, - "smargon-y": 20.0, - "smargon-z": 30.0, + "gonio-x": 10.0, + "gonio-y": 20.0, + "gonio-z": 30.0, } BASIC_POST_SETUP_DOC = { @@ -341,7 +341,7 @@ async def fake_fgs_composite( fake_composite = FlyScanEssentialDevices( # We don't use the eiger fixture here because .unstage() is used in some tests eiger=i03.eiger.build(mock=True), - smargon=smargon, + gonio=smargon, synchrotron=synchrotron, ) @@ -367,7 +367,7 @@ async def mock_complete(result): zocalo.trigger = MagicMock(side_effect=partial(mock_complete, test_result)) # type: ignore zocalo.timeout_s = 3 - set_mock_value(fake_composite.smargon.x.max_velocity, 10) + set_mock_value(fake_composite.gonio.x.max_velocity, 10) return fake_composite @@ -439,7 +439,7 @@ async def grid_detect_xrc_devices( flux=flux, oav=oav, pin_tip_detection=ophyd_pin_tip_detection, - smargon=smargon, + gonio=smargon, synchrotron=synchrotron, s4_slit_gaps=s4_slit_gaps, undulator=undulator, diff --git a/tests/unit_tests/hyperion/experiment_plans/conftest.py b/tests/unit_tests/hyperion/experiment_plans/conftest.py index d0e313157f..db7c63398d 100644 --- a/tests/unit_tests/hyperion/experiment_plans/conftest.py +++ b/tests/unit_tests/hyperion/experiment_plans/conftest.py @@ -78,7 +78,7 @@ def sim_run_engine_for_rotation(sim_run_engine): "synchrotron-top_up_start_countdown", ) sim_run_engine.add_handler( - "read", lambda msg: {"values": {"value": -1}}, "smargon_omega" + "read", lambda msg: {"values": {"value": -1}}, "gonio_omega" ) return sim_run_engine @@ -191,7 +191,7 @@ def robot_load_composite( flux=flux, oav=oav, pin_tip_detection=pin_tip_detection_with_found_pin, - smargon=smargon, + gonio=smargon, synchrotron=synchrotron, s4_slit_gaps=s4_slit_gaps, undulator=undulator, @@ -244,7 +244,7 @@ def robot_load_and_energy_change_composite( aperture_scatterguard, backlight, ) - composite.smargon.stub_offsets.set = MagicMock( + composite.gonio.stub_offsets.set = MagicMock( side_effect=lambda _: completed_status() ) composite.aperture_scatterguard.set = MagicMock( diff --git a/tests/unit_tests/hyperion/experiment_plans/test_hyperion_flyscan_xray_centre_plan.py b/tests/unit_tests/hyperion/experiment_plans/test_hyperion_flyscan_xray_centre_plan.py index b976d47f54..d2c2f6aae1 100644 --- a/tests/unit_tests/hyperion/experiment_plans/test_hyperion_flyscan_xray_centre_plan.py +++ b/tests/unit_tests/hyperion/experiment_plans/test_hyperion_flyscan_xray_centre_plan.py @@ -16,7 +16,6 @@ from mx_bluesky.common.experiment_plans.common_flyscan_xray_centre_plan import ( BeamlineSpecificFGSFeatures, - FlyScanEssentialDevices, common_flyscan_xray_centre, ) from mx_bluesky.common.external_interaction.callbacks.xray_centre.ispyb_callback import ( @@ -28,6 +27,7 @@ from mx_bluesky.common.parameters.constants import ( DeviceSettingsConstants, ) +from mx_bluesky.common.parameters.device_composites import FlyScanEssentialDevices from mx_bluesky.hyperion.experiment_plans.hyperion_flyscan_xray_centre_plan import ( SmargonSpeedError, ) @@ -124,7 +124,7 @@ def test_results_adjusted_and_passed_to_move_xyz( move_aperture.assert_has_calls([ap_call_large, ap_call_large, ap_call_medium]) mv_to_centre = call( - hyperion_flyscan_xrc_composite.smargon, + hyperion_flyscan_xrc_composite.gonio, 0.05, pytest.approx(0.15), 0.25, @@ -236,7 +236,7 @@ def test_flyscan_xray_centre_sets_directory_stages_arms_disarms_unstages_the_pan tmp_path: Path, ): sim_run_engine.add_read_handler_for( - fgs_composite_with_panda_pcap.smargon.x.max_velocity, 10 + fgs_composite_with_panda_pcap.gonio.x.max_velocity, 10 ) simulate_xrc_result( sim_run_engine, fgs_composite_with_panda_pcap.zocalo, TEST_RESULT_LARGE diff --git a/tests/unit_tests/hyperion/experiment_plans/test_load_centre_collect_full_plan.py b/tests/unit_tests/hyperion/experiment_plans/test_load_centre_collect_full_plan.py index f5e2532235..80f9e90130 100644 --- a/tests/unit_tests/hyperion/experiment_plans/test_load_centre_collect_full_plan.py +++ b/tests/unit_tests/hyperion/experiment_plans/test_load_centre_collect_full_plan.py @@ -126,24 +126,12 @@ def composite( minaxis = Location(setpoint=-2, readback=-2) maxaxis = Location(setpoint=2, readback=2) tip_x_px, tip_y_px, top_edge_array, bottom_edge_array = pin_tip_edge_data() - sim_run_engine.add_handler( - "locate", lambda _: minaxis, "smargon-x-low_limit_travel" - ) - sim_run_engine.add_handler( - "locate", lambda _: minaxis, "smargon-y-low_limit_travel" - ) - sim_run_engine.add_handler( - "locate", lambda _: minaxis, "smargon-z-low_limit_travel" - ) - sim_run_engine.add_handler( - "locate", lambda _: maxaxis, "smargon-x-high_limit_travel" - ) - sim_run_engine.add_handler( - "locate", lambda _: maxaxis, "smargon-y-high_limit_travel" - ) - sim_run_engine.add_handler( - "locate", lambda _: maxaxis, "smargon-z-high_limit_travel" - ) + sim_run_engine.add_handler("locate", lambda _: minaxis, "gonio-x-low_limit_travel") + sim_run_engine.add_handler("locate", lambda _: minaxis, "gonio-y-low_limit_travel") + sim_run_engine.add_handler("locate", lambda _: minaxis, "gonio-z-low_limit_travel") + sim_run_engine.add_handler("locate", lambda _: maxaxis, "gonio-x-high_limit_travel") + sim_run_engine.add_handler("locate", lambda _: maxaxis, "gonio-y-high_limit_travel") + sim_run_engine.add_handler("locate", lambda _: maxaxis, "gonio-z-high_limit_travel") sim_run_engine.add_read_handler_for( composite.synchrotron.synchrotron_mode, SynchrotronMode.USER ) @@ -400,19 +388,19 @@ def test_collect_full_plan_happy_path_invokes_all_steps_and_centres_on_best_flys # msgs = assert_message_and_return_remaining( # msgs, # lambda msg: msg.command == "set" - # and msg.obj.name == "smargon-x" + # and msg.obj.name == "gonio-x" # and msg.args[0] == 0.1, # ) # msgs = assert_message_and_return_remaining( # msgs, # lambda msg: msg.command == "set" - # and msg.obj.name == "smargon-y" + # and msg.obj.name == "gonio-y" # and msg.args[0] == 0.2, # ) # msgs = assert_message_and_return_remaining( # msgs, # lambda msg: msg.command == "set" - # and msg.obj.name == "smargon-z" + # and msg.obj.name == "gonio-z" # and msg.args[0] == 0.3, # ) msgs = assert_message_and_return_remaining( @@ -1100,9 +1088,9 @@ def test_load_centre_collect_full_collects_at_current_location_if_no_xray_centri oav_parameters_for_rotation: OAVParameters, sim_run_engine: RunEngineSimulator, ): - sim_run_engine.add_read_handler_for(composite.smargon.x, 1.1) - sim_run_engine.add_read_handler_for(composite.smargon.y, 2.2) - sim_run_engine.add_read_handler_for(composite.smargon.z, 3.3) + sim_run_engine.add_read_handler_for(composite.gonio.x, 1.1) + sim_run_engine.add_read_handler_for(composite.gonio.y, 2.2) + sim_run_engine.add_read_handler_for(composite.gonio.z, 3.3) sim_run_engine.simulate_plan( load_centre_collect_full( diff --git a/tests/unit_tests/hyperion/experiment_plans/test_pin_centre_then_xray_centre_plan.py b/tests/unit_tests/hyperion/experiment_plans/test_pin_centre_then_xray_centre_plan.py index 5d206428cc..26a8e0278d 100644 --- a/tests/unit_tests/hyperion/experiment_plans/test_pin_centre_then_xray_centre_plan.py +++ b/tests/unit_tests/hyperion/experiment_plans/test_pin_centre_then_xray_centre_plan.py @@ -396,7 +396,7 @@ def test_pin_centre_then_gridscan_plan_goes_to_the_starting_chi_and_phi( msgs = assert_message_and_return_remaining( msgs, lambda msg: msg.command == "set" - and msg.obj.name == "smargon" + and msg.obj.name == "gonio" and msg.args[0] == CombinedMove(phi=expected_phi, chi=expected_chi, omega=None) and msg.kwargs["group"] == CONST.WAIT.READY_FOR_OAV, ) diff --git a/tests/unit_tests/hyperion/experiment_plans/test_rotation_scan_plan.py b/tests/unit_tests/hyperion/experiment_plans/test_rotation_scan_plan.py index 1ab69a6f5b..55cc8612d7 100644 --- a/tests/unit_tests/hyperion/experiment_plans/test_rotation_scan_plan.py +++ b/tests/unit_tests/hyperion/experiment_plans/test_rotation_scan_plan.py @@ -143,7 +143,7 @@ def setup_and_run_rotation_plan_for_tests( return { "run_engine_with_subs": run_engine, "test_rotation_params": test_params, - "smargon": fake_create_rotation_devices.smargon, + "smargon": fake_create_rotation_devices.gonio, "zebra": fake_create_rotation_devices.zebra, } @@ -254,7 +254,7 @@ async def test_full_rotation_plan_smargon_settings( run_full_rotation_plan: RotationScanComposite, test_rotation_params: RotationScan, ) -> None: - smargon: Smargon = run_full_rotation_plan.smargon + smargon: Smargon = run_full_rotation_plan.gonio params: SingleRotationScan = next(test_rotation_params.single_rotation_scans) test_max_velocity = await smargon.omega.max_velocity.get_value() @@ -331,7 +331,7 @@ class MyTestError(Exception): side_effect=MyTestError("Experiment fails because this is a test") ) - with patch.object(fake_create_rotation_devices.smargon.omega, "set", failing_set): + with patch.object(fake_create_rotation_devices.gonio.omega, "set", failing_set): # check main subplan part fails params = next(test_rotation_params.single_rotation_scans) with pytest.raises(MyTestError): @@ -373,7 +373,7 @@ def test_rotation_plan_reads_hardware( ) msgs_in_event = list(takewhile(lambda msg: msg.command != "save", msgs)) assert_message_and_return_remaining( - msgs_in_event, lambda msg: msg.command == "read" and msg.obj.name == "smargon" + msgs_in_event, lambda msg: msg.command == "read" and msg.obj.name == "gonio" ) @@ -563,7 +563,7 @@ def test_rotation_scan_resets_omega_waits_for_sample_env_complete_after_snapshot msgs = assert_message_and_return_remaining( msgs, lambda msg: msg.command == "set" - and msg.obj.name == "smargon-omega" + and msg.obj.name == "gonio-omega" and msg.args[0] == params.omega_start_deg and msg.kwargs["group"] == CONST.WAIT.ROTATION_READY_FOR_DC, ) @@ -664,7 +664,7 @@ def _test_rotation_scan_skips_init_backlight_aperture_and_snapshots( msg for msg in msgs if msg.command == "set" - and msg.obj.name == "smargon-omega" + and msg.obj.name == "gonio-omega" and msg.kwargs["group"] == CONST.WAIT.ROTATION_READY_FOR_DC ] ) @@ -686,7 +686,7 @@ def _add_sim_handlers_for_normal_operation( "synchrotron-top_up_start_countdown", ) sim_run_engine.add_handler( - "read", lambda msg: {"smargon-omega": {"value": -1}}, "smargon-omega" + "read", lambda msg: {"gonio-omega": {"value": -1}}, "gonio-omega" ) @@ -763,7 +763,7 @@ def test_rotation_scan_arms_detector_and_takes_snapshots_whilst_arming( msgs = assert_message_and_return_remaining( msgs, lambda msg: msg.command == "set" - and msg.obj is composite.smargon.omega + and msg.obj is composite.gonio.omega and msg.args[0] == omega, ) msgs = assert_message_and_return_remaining( @@ -936,12 +936,8 @@ def test_rotation_scan_plan_with_omega_flip_inverts_motor_movements_but_not_even scan.omega_start_deg = 30 mock_callback = Mock(spec=RotationISPyBCallback) run_engine.subscribe(mock_callback) - omega_put = get_mock_put( - fake_create_rotation_devices.smargon.omega.user_setpoint - ) - set_mock_value( - fake_create_rotation_devices.smargon.omega.acceleration_time, 0.1 - ) + omega_put = get_mock_put(fake_create_rotation_devices.gonio.omega.user_setpoint) + set_mock_value(fake_create_rotation_devices.gonio.omega.acceleration_time, 0.1) with ( patch("bluesky.plan_stubs.wait", autospec=True), patch( @@ -1029,7 +1025,7 @@ async def test_multi_rotation_plan_runs_multiple_plans_in_one_arm( sim_run_engine_for_rotation: RunEngineSimulator, oav_parameters_for_rotation: OAVParameters, ): - smargon = fake_create_rotation_devices.smargon + smargon = fake_create_rotation_devices.gonio omega = smargon.omega set_mock_value( fake_create_rotation_devices.synchrotron.synchrotron_mode, SynchrotronMode.USER @@ -1083,7 +1079,7 @@ async def test_multi_rotation_plan_runs_multiple_plans_in_one_arm( assert_message_and_return_remaining( msgs_within_arming, lambda msg: msg.command == "set" - and msg.obj.name == "smargon-omega" + and msg.obj.name == "gonio-omega" and msg.args == ( (scan.scan_width_deg + motion_values.shutter_opening_deg) @@ -1158,7 +1154,7 @@ def test_full_multi_rotation_plan_docs_emitted( events, [ ["eiger_odin_file_writer_id"], - ["undulator-current_gap", "synchrotron-synchrotron_mode", "smargon-x"], + ["undulator-current_gap", "synchrotron-synchrotron_mode", "gonio-x"], [ "attenuator-actual_transmission", "flux-flux_reading", diff --git a/tests/unit_tests/hyperion/experiment_plans/test_udc_default_state.py b/tests/unit_tests/hyperion/experiment_plans/test_udc_default_state.py index f7f1294761..6ce937f68c 100644 --- a/tests/unit_tests/hyperion/experiment_plans/test_udc_default_state.py +++ b/tests/unit_tests/hyperion/experiment_plans/test_udc_default_state.py @@ -75,7 +75,7 @@ async def default_devices( hutch_shutter=hutch_shutter, robot=robot, scintillator=scintillator, - smargon=smargon, + gonio=smargon, oav=oav, **beamstop_check_devices.__dict__, ) diff --git a/tests/unit_tests/hyperion/external_interaction/callbacks/rotation/test_ispyb_callback.py b/tests/unit_tests/hyperion/external_interaction/callbacks/rotation/test_ispyb_callback.py index e5952dcf9d..4f76b4e73c 100644 --- a/tests/unit_tests/hyperion/external_interaction/callbacks/rotation/test_ispyb_callback.py +++ b/tests/unit_tests/hyperion/external_interaction/callbacks/rotation/test_ispyb_callback.py @@ -121,9 +121,9 @@ def test_hardware_read_events( create_position_request = mock_ispyb_conn.dc_calls_for(POSITION_RE)[0] assert create_position_request.dcid == TEST_DATA_COLLECTION_IDS[0] assert create_position_request.body == { - "posX": expected_data["smargon-x"], - "posY": expected_data["smargon-y"], - "posZ": expected_data["smargon-z"], + "posX": expected_data["gonio-x"], + "posY": expected_data["gonio-y"], + "posZ": expected_data["gonio-z"], } diff --git a/uv.lock b/uv.lock index df626f8fd9..373c55b392 100644 --- a/uv.lock +++ b/uv.lock @@ -802,8 +802,8 @@ wheels = [ [[package]] name = "dls-dodal" -version = "2.0.1.dev12+gfa22fd0ab" -source = { git = "https://github.com/DiamondLightSource/dodal.git?rev=main#fa22fd0ab55aa63445da1d6462ec02c82cd64611" } +version = "2.0.1.dev13+g28c3f20f6" +source = { git = "https://github.com/DiamondLightSource/dodal.git?rev=28c3f20f6108d2769e7995f1008e7d76446fb38b#28c3f20f6108d2769e7995f1008e7d76446fb38b" } dependencies = [ { name = "aiofiles", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "aiohttp", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, @@ -2108,7 +2108,7 @@ requires-dist = [ { name = "caproto" }, { name = "daq-config-server", specifier = ">=1.0.0rc2" }, { name = "deepdiff" }, - { name = "dls-dodal", git = "https://github.com/DiamondLightSource/dodal.git?rev=main" }, + { name = "dls-dodal", git = "https://github.com/DiamondLightSource/dodal.git?rev=28c3f20f6108d2769e7995f1008e7d76446fb38b" }, { name = "fastapi", extras = ["all"] }, { name = "flask-restful" }, { name = "jupyterlab" }, From 7ee2e454f1bdcc650279b60b3a69311f70564fe7 Mon Sep 17 00:00:00 2001 From: rtuck99 Date: Thu, 12 Feb 2026 11:41:53 +0000 Subject: [PATCH 04/10] Add skip for CI pre-commit for uv lock file update (#1625) * Add skip for CI pre-commit for uv lock file update --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 32a3d0d19e..c27a8d3cb3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: uses: ./.github/workflows/_tox.yml with: tox: pre-commit,type-checking - skip_hooks: local-install-dodal,type-checking + skip_hooks: local-install-dodal,type-checking,uv-sync test: strategy: From 12dfc4b51380ee52f326cad83cfa21df3b13a1f1 Mon Sep 17 00:00:00 2001 From: rtuck99 Date: Thu, 12 Feb 2026 13:22:54 +0000 Subject: [PATCH 05/10] Implement robot unload for udc default state (#1607) * Implement robot unload for udc default state --- .../device_setup_plans/robot_load_unload.py | 2 +- .../experiment_plans/udc_default_state.py | 27 ++++++-- .../callbacks/robot_actions/ispyb_callback.py | 34 ++++++---- .../test_udc_default_state.py | 65 ++++++++++++++++--- .../test_robot_load_ispyb_callback.py | 44 ++++++++++++- 5 files changed, 142 insertions(+), 30 deletions(-) diff --git a/src/mx_bluesky/common/device_setup_plans/robot_load_unload.py b/src/mx_bluesky/common/device_setup_plans/robot_load_unload.py index 5faf5b083d..5905ca06a3 100644 --- a/src/mx_bluesky/common/device_setup_plans/robot_load_unload.py +++ b/src/mx_bluesky/common/device_setup_plans/robot_load_unload.py @@ -73,7 +73,7 @@ def robot_unload( smargon: Smargon, aperture_scatterguard: ApertureScatterguard, lower_gonio: XYZStage, - visit: str, + visit: str | None, ): """Unloads the currently mounted pin into the location that it was loaded from. The loaded location is stored on the robot and so need not be provided. diff --git a/src/mx_bluesky/hyperion/experiment_plans/udc_default_state.py b/src/mx_bluesky/hyperion/experiment_plans/udc_default_state.py index 6aa6555f70..4777000a9c 100644 --- a/src/mx_bluesky/hyperion/experiment_plans/udc_default_state.py +++ b/src/mx_bluesky/hyperion/experiment_plans/udc_default_state.py @@ -4,7 +4,7 @@ from dodal.common.beamlines.beamline_parameters import ( get_beamline_parameters, ) -from dodal.devices.aperturescatterguard import ApertureValue +from dodal.devices.aperturescatterguard import ApertureScatterguard, ApertureValue from dodal.devices.collimation_table import CollimationTable from dodal.devices.cryostream import ( CryoStreamGantry, @@ -16,6 +16,7 @@ from dodal.devices.fluorescence_detector_motion import FluorescenceDetector from dodal.devices.fluorescence_detector_motion import InOut as FlouInOut from dodal.devices.hutch_shutter import HutchShutter, ShutterDemand +from dodal.devices.motors import XYZStage from dodal.devices.mx_phase1.beamstop import BeamstopPositions from dodal.devices.oav.oav_detector import OAV from dodal.devices.robot import BartRobot, PinMounted @@ -24,6 +25,7 @@ from dodal.devices.smargon import Smargon from dodal.devices.zebra.zebra_controlled_shutter import ZebraShutterState +from mx_bluesky.common.device_setup_plans.robot_load_unload import robot_unload from mx_bluesky.common.experiment_plans.beamstop_check import ( BeamstopCheckDevices, move_beamstop_in_and_verify_using_diode, @@ -47,6 +49,7 @@ class UDCDefaultDevices(BeamstopCheckDevices): cryostream_gantry: CryoStreamGantry fluorescence_det_motion: FluorescenceDetector hutch_shutter: HutchShutter + lower_gonio: XYZStage robot: BartRobot scintillator: Scintillator gonio: Smargon @@ -65,8 +68,6 @@ def move_to_udc_default_state(devices: UDCDefaultDevices): yield from _check_cryostream(devices) - yield from _verify_no_sample_present(devices.robot) - # Close fast shutter before opening hutch shutter yield from bps.abs_set(devices.sample_shutter, ZebraShutterState.CLOSE, wait=True) @@ -108,6 +109,13 @@ def move_to_udc_default_state(devices: UDCDefaultDevices): # Wait for all of the above to complete yield from bps.wait(group=_GROUP_PRE_BEAMSTOP_CHECK, timeout=10) + yield from _unload_sample_if_present( + devices.robot, + devices.gonio, + devices.aperture_scatterguard, + devices.lower_gonio, + ) + feature_flags: HyperionFeatureSettings = ( get_hyperion_config_client().get_feature_flags() ) @@ -175,11 +183,16 @@ def _verify_correct_cryostream_selected( ) -def _verify_no_sample_present(robot: BartRobot): +def _unload_sample_if_present( + robot: BartRobot, + smargon: Smargon, + aperture_scatterguard: ApertureScatterguard, + lower_gonio: XYZStage, +): pin_mounted = yield from bps.rd(robot.gonio_pin_sensor) if pin_mounted != PinMounted.NO_PIN_MOUNTED: - # Cannot unload this sample because we do not know the correct visit for it - raise UnexpectedSampleError( - "An unexpected sample was found, please unload the sample manually." + LOGGER.info("Pin detected, unloading sample...") + yield from robot_unload( + robot, smargon, aperture_scatterguard, lower_gonio, None ) diff --git a/src/mx_bluesky/hyperion/external_interaction/callbacks/robot_actions/ispyb_callback.py b/src/mx_bluesky/hyperion/external_interaction/callbacks/robot_actions/ispyb_callback.py index b1980c3661..14af22be60 100644 --- a/src/mx_bluesky/hyperion/external_interaction/callbacks/robot_actions/ispyb_callback.py +++ b/src/mx_bluesky/hyperion/external_interaction/callbacks/robot_actions/ispyb_callback.py @@ -51,20 +51,24 @@ def activity_gated_start(self, doc: RunStart): ISPYB_ZOCALO_CALLBACK_LOGGER.debug( f"ISPyB robot load callback received: {doc}" ) - self.run_uid = doc.get("uid") metadata = doc.get("metadata") assert isinstance(metadata, dict) - self._sample_id = metadata["sample_id"] - assert isinstance(self._sample_id, int) - proposal, session = get_proposal_and_session_from_visit_string( - metadata["visit"] - ) - self.action_id = self.expeye.start_robot_action( - "LOAD" if subplan == CONST.PLAN.ROBOT_LOAD else "UNLOAD", - proposal, - session, - self._sample_id, - ) + self._sample_id = metadata.get("sample_id") + if not isinstance(self._sample_id, int): + ISPYB_ZOCALO_CALLBACK_LOGGER.info( + "No sample ID provided, not recording robot action in ispyb." + ) + else: + self.run_uid = doc.get("uid") + proposal, session = get_proposal_and_session_from_visit_string( + metadata["visit"] + ) + self.action_id = self.expeye.start_robot_action( + "LOAD" if subplan == CONST.PLAN.ROBOT_LOAD else "UNLOAD", + proposal, + session, + self._sample_id, + ) return super().activity_gated_start(doc) def activity_gated_descriptor(self, doc: EventDescriptor) -> EventDescriptor | None: @@ -73,7 +77,11 @@ def activity_gated_descriptor(self, doc: EventDescriptor) -> EventDescriptor | N def activity_gated_event(self, doc: Event) -> Event | None: event_descriptor = self.descriptors.get(doc["descriptor"]) - if ( + if self._sample_id is None: + ISPYB_ZOCALO_CALLBACK_LOGGER.info( + "Ignoring robot update event as no sample ID provided." + ) + elif ( event_descriptor and event_descriptor.get("name") == CONST.DESCRIPTORS.ROBOT_UPDATE ): diff --git a/tests/unit_tests/hyperion/experiment_plans/test_udc_default_state.py b/tests/unit_tests/hyperion/experiment_plans/test_udc_default_state.py index 6ce937f68c..4622f867b4 100644 --- a/tests/unit_tests/hyperion/experiment_plans/test_udc_default_state.py +++ b/tests/unit_tests/hyperion/experiment_plans/test_udc_default_state.py @@ -17,6 +17,7 @@ from dodal.devices.fluorescence_detector_motion import FluorescenceDetector from dodal.devices.fluorescence_detector_motion import InOut as FlouInOut from dodal.devices.hutch_shutter import HutchShutter, ShutterDemand +from dodal.devices.motors import XYZStage from dodal.devices.mx_phase1.beamstop import BeamstopPositions from dodal.devices.robot import BartRobot, PinMounted from dodal.devices.scintillator import InOut, Scintillator @@ -27,7 +28,6 @@ from mx_bluesky.hyperion.experiment_plans.udc_default_state import ( CryoStreamError, UDCDefaultDevices, - UnexpectedSampleError, move_to_udc_default_state, ) from mx_bluesky.hyperion.parameters.constants import CONST, HyperionFeatureSettings @@ -64,6 +64,7 @@ async def default_devices( hutch_shutter = HutchShutter("") scintillator = Scintillator("", MagicMock(), MagicMock(), name="scin") collimation_table = CollimationTable("") + lower_gonio = XYZStage("") with patch("dodal.devices.hutch_shutter.TEST_MODE", True): devices = UDCDefaultDevices( @@ -73,6 +74,7 @@ async def default_devices( cryostream_gantry=cryostream_gantry, fluorescence_det_motion=fluo, hutch_shutter=hutch_shutter, + lower_gonio=lower_gonio, robot=robot, scintillator=scintillator, gonio=smargon, @@ -156,6 +158,10 @@ def test_udc_default_state_runs_in_real_run_engine( run_engine(move_to_udc_default_state(default_devices)) +@patch( + "mx_bluesky.hyperion.experiment_plans.udc_default_state._unload_sample_if_present", + MagicMock(return_value=iter([Msg("robot_unload")])), +) def test_beamstop_moved_to_data_collection_if_diode_check_not_enabled( sim_run_engine: RunEngineSimulator, default_devices: UDCDefaultDevices, @@ -166,13 +172,14 @@ def test_beamstop_moved_to_data_collection_if_diode_check_not_enabled( msgs, lambda msg: msg.command == "wait" and msg.kwargs["group"] == pre_beamstop_group, ) + assert msgs[1].command == "robot_unload" assert ( - msgs[1].command == "set" - and msgs[1].obj is default_devices.beamstop.selected_pos - and msgs[1].args[0] == BeamstopPositions.DATA_COLLECTION + msgs[2].command == "set" + and msgs[2].obj is default_devices.beamstop.selected_pos + and msgs[2].args[0] == BeamstopPositions.DATA_COLLECTION ) assert ( - msgs[2].command == "wait" and msgs[2].kwargs["group"] == msgs[1].kwargs["group"] + msgs[3].command == "wait" and msgs[3].kwargs["group"] == msgs[2].kwargs["group"] ) @@ -306,7 +313,19 @@ def test_udc_default_state_checks_cryostream_selection( run_engine(move_to_udc_default_state(default_devices)) -def test_udc_default_state_checks_that_pin_not_mounted( +@patch( + "mx_bluesky.hyperion.experiment_plans.udc_default_state._verify_correct_cryostream_selected", + MagicMock(return_value=iter([Msg("cryostream_gantry_check")])), +) +@patch( + "mx_bluesky.hyperion.experiment_plans.udc_default_state._check_cryostream", + MagicMock(return_value=iter([Msg("cryostream_check")])), +) +@patch( + "mx_bluesky.hyperion.experiment_plans.udc_default_state.robot_unload", + MagicMock(return_value=iter([Msg("unload")])), +) +def test_udc_default_state_unloads_if_sample_present( default_devices, sim_run_engine, beamline_parameters ): sim_run_engine.add_read_handler_for( @@ -316,8 +335,38 @@ def test_udc_default_state_checks_that_pin_not_mounted( "mx_bluesky.hyperion.experiment_plans.udc_default_state.get_beamline_parameters", return_value=beamline_parameters, ): - with pytest.raises(UnexpectedSampleError): - sim_run_engine.simulate_plan(move_to_udc_default_state(default_devices)) + msgs = sim_run_engine.simulate_plan(move_to_udc_default_state(default_devices)) + + msgs = assert_message_and_return_remaining( + msgs, lambda msg: msg.command == "cryostream_gantry_check" + ) + msgs = assert_message_and_return_remaining( + msgs, lambda msg: msg.command == "cryostream_check" + ) + msgs = assert_message_and_return_remaining( + msgs, + lambda msg: msg.command == "set" + and msg.obj == default_devices.scintillator.selected_pos + and msg.args[0] == InOut.OUT, + ) + msgs = assert_message_and_return_remaining( + msgs, lambda msg: msg.command == "unload" + ) + + +def test_udc_default_state_no_unload_if_no_sample_present( + default_devices, sim_run_engine, beamline_parameters +): + sim_run_engine.add_read_handler_for( + default_devices.robot.gonio_pin_sensor, PinMounted.NO_PIN_MOUNTED + ) + with patch( + "mx_bluesky.hyperion.experiment_plans.udc_default_state.get_beamline_parameters", + return_value=beamline_parameters, + ): + msgs = sim_run_engine.simulate_plan(move_to_udc_default_state(default_devices)) + + assert not [m for m in msgs if m.command == "unload"] def test_default_state_closes_sample_shutter_before_open_hutch_shutter( diff --git a/tests/unit_tests/hyperion/external_interaction/callbacks/robot_load/test_robot_load_ispyb_callback.py b/tests/unit_tests/hyperion/external_interaction/callbacks/robot_load/test_robot_load_ispyb_callback.py index ce0351dcf3..2ae8d2473d 100644 --- a/tests/unit_tests/hyperion/external_interaction/callbacks/robot_load/test_robot_load_ispyb_callback.py +++ b/tests/unit_tests/hyperion/external_interaction/callbacks/robot_load/test_robot_load_ispyb_callback.py @@ -1,4 +1,4 @@ -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock, Mock, patch import bluesky.plan_stubs as bps import bluesky.preprocessors as bpp @@ -35,6 +35,17 @@ ], } +metadata_unload_no_visit = { + "subplan_name": CONST.PLAN.ROBOT_UNLOAD, + "metadata": { + "sample_puck": SAMPLE_PUCK, + "sample_pin": SAMPLE_PIN, + }, + "activate_callbacks": [ + "RobotLoadISPyBCallback", + ], +} + update_doc_data = { "sampleBarcode": "BARCODE", "xtalSnapshotBefore": "test_webcam_snapshot", @@ -127,6 +138,15 @@ def unsuccessful_robot_load_plan(): raise AssertionError("Test failure") +@bpp.run_decorator(md=metadata_unload_no_visit) +def robot_unload_plan(robot: BartRobot, oav: OAV, webcam: Webcam): + yield from bps.create(name=CONST.DESCRIPTORS.ROBOT_UPDATE) + yield from bps.read(robot) + yield from bps.read(oav.snapshot) + yield from bps.read(webcam) + yield from bps.save() + + @patch( "mx_bluesky.hyperion.external_interaction.callbacks.robot_actions.ispyb_callback.ExpeyeInteraction", autospec=True, @@ -195,3 +215,25 @@ def test_robot_load_fails_triggers_bl_sample_status_error( mock_sample_handling.return_value.update_sample_status.assert_called_with( SAMPLE_ID, BLSampleStatus.ERROR_BEAMLINE ) + + +@patch( + "mx_bluesky.hyperion.external_interaction.callbacks.robot_actions.ispyb_callback.ExpeyeInteraction", + autospec=True, +) +def test_robot_unload_event_without_sample_id_and_visit_is_ignored( + mock_expeye: MagicMock, + run_engine: RunEngine, + robot: BartRobot, + oav: OAV, + webcam: Webcam, +): + unwrapped = RobotLoadISPyBCallback() + callback = Mock(wraps=unwrapped) + run_engine.subscribe(callback) + + run_engine(robot_unload_plan(robot, oav, webcam)) + mock_expeye.return_value.start_robot_action.assert_not_called() + mock_expeye.return_value.update_robot_action.assert_not_called() + mock_expeye.return_value.end_robot_action.assert_not_called() + mock_expeye.return_value.update_sample_status.assert_not_called() From 9619c8ae8b74b752abc2b22652747449d58fa8c1 Mon Sep 17 00:00:00 2001 From: Tamoor Shahid Date: Fri, 13 Feb 2026 15:43:16 +0000 Subject: [PATCH 06/10] Fixed failing tests and added new test to check for failure --- src/mx_bluesky/hyperion/baton_handler.py | 3 ++ .../unit_tests/hyperion/test_baton_handler.py | 44 ++++++++++++++++--- uv.lock | 4 +- 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/mx_bluesky/hyperion/baton_handler.py b/src/mx_bluesky/hyperion/baton_handler.py index 2c2e555c3c..3dde994616 100644 --- a/src/mx_bluesky/hyperion/baton_handler.py +++ b/src/mx_bluesky/hyperion/baton_handler.py @@ -91,10 +91,13 @@ def collect() -> MsgGenerator: synchrotron = _get_synchrotron(context) countdown = yield from bps.rd(synchrotron.machine_user_countdown) + LOGGER.info(f"Synchrotron beam countdown is {countdown} seconds") + if countdown < 600: _raise_udc_completed_alert(get_alerting_service()) # Release the baton for orderly exit from the instruction loop yield from _unrequest_baton(baton) + raise PlanError("Synchrotron machine countdown too low") _raise_udc_start_alert(get_alerting_service()) yield from bpp.contingency_wrapper( diff --git a/tests/unit_tests/hyperion/test_baton_handler.py b/tests/unit_tests/hyperion/test_baton_handler.py index 31520ea779..c270d295f5 100644 --- a/tests/unit_tests/hyperion/test_baton_handler.py +++ b/tests/unit_tests/hyperion/test_baton_handler.py @@ -15,6 +15,7 @@ from bluesky.simulators import RunEngineSimulator, assert_message_and_return_remaining from dodal.devices.baton import Baton from dodal.devices.detector.detector_motion import DetectorMotion +from dodal.devices.synchrotron import Synchrotron from dodal.utils import get_beamline_based_on_environment_variable from ophyd_async.core import get_mock_put, set_mock_value @@ -110,6 +111,7 @@ def bluesky_context( lower_gonio, baton, detector_motion, + synchrotron, use_beamline_t01, ): # Baton for real run engine @@ -125,24 +127,26 @@ def mock_load_module(module, **kwargs): lower_gonio, baton, detector_motion, + synchrotron, ] for device in devices: context.register_device(device) return {d.name: d for d in devices}, {} - context.with_device_manager( - get_beamline_based_on_environment_variable().devices, - mock=True, - ) - - baton_with_requested_user(context, HYPERION_USER) with patch.object(context, "with_device_manager", mock_load_module): + context.with_device_manager( + get_beamline_based_on_environment_variable().devices, + mock=True, + ) + synchrotron_with_countdown(context) + baton_with_requested_user(context, HYPERION_USER) yield context @pytest.fixture def bluesky_context_with_sim_run_engine(sim_run_engine: RunEngineSimulator): baton_requested_user = HYPERION_USER + countdown = 1200 # Baton for sim run engine def get_requested_user(msg): @@ -153,12 +157,20 @@ def set_requested_user(msg): nonlocal baton_requested_user baton_requested_user = msg.args[0] + def machine_user_countdown_read(msg): + return {msg.obj.name: {"value": countdown}} + sim_run_engine.add_handler("locate", get_requested_user, "baton-requested_user") sim_run_engine.add_handler( "set", set_requested_user, # type: ignore "baton-requested_user", ) + sim_run_engine.add_handler( + "read", + machine_user_countdown_read, + "synchrotron-machine_user_countdown", + ) msgs = [] @@ -224,6 +236,14 @@ def baton_with_requested_user( return baton +def synchrotron_with_countdown( + bluesky_context: BlueskyContext, seconds: int = 1200 +) -> Synchrotron: + synchrotron = find_device_in_context(bluesky_context, "synchrotron", Synchrotron) + set_mock_value(synchrotron.machine_user_countdown, seconds) + return synchrotron + + @pytest.fixture() def udc_runner(bluesky_context: BlueskyContext) -> PlanRunner: runner = InProcessRunner(bluesky_context, True) @@ -1032,3 +1052,15 @@ def test_hyperion_doesnt_exit_if_udc_default_state_fails_a_check( mock_move_to_udc_default_state.assert_called_once() assert get_mock_put(baton.requested_user).mock_calls[-1] == call(NO_USER, wait=True) assert get_mock_put(baton.current_user).mock_calls[-1] == call(NO_USER, wait=True) + + +def test_baton_handler_fails_if_synchrotron_machine_countdown_below_threshold( + bluesky_context: BlueskyContext, + udc_runner: PlanRunner, + dont_patch_clear_devices, +): + synchrotron = find_device_in_context(bluesky_context, "synchrotron", Synchrotron) + set_mock_value(synchrotron.machine_user_countdown, 5) + + with pytest.raises(PlanError, match="Synchrotron machine countdown too low"): + run_udc_when_requested(bluesky_context, udc_runner) diff --git a/uv.lock b/uv.lock index 373c55b392..4f733312f3 100644 --- a/uv.lock +++ b/uv.lock @@ -802,8 +802,8 @@ wheels = [ [[package]] name = "dls-dodal" -version = "2.0.1.dev13+g28c3f20f6" -source = { git = "https://github.com/DiamondLightSource/dodal.git?rev=28c3f20f6108d2769e7995f1008e7d76446fb38b#28c3f20f6108d2769e7995f1008e7d76446fb38b" } +version = "2.0.1.dev19+gf188f5797" +source = { git = "https://github.com/DiamondLightSource/dodal.git?rev=main#f188f5797f56ae82d79bf1f30f6ad73598a5681a" } dependencies = [ { name = "aiofiles", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "aiohttp", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, From 8ba2a10a3f538f943906eed4d610ce45be175ea9 Mon Sep 17 00:00:00 2001 From: Tamoor Shahid Date: Mon, 16 Feb 2026 15:10:12 +0000 Subject: [PATCH 07/10] Fixed test --- tests/unit_tests/hyperion/test_main_system.py | 3 +++ tests/unit_tests/t01.py | 9 +++++++++ uv.lock | 4 ++-- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests/hyperion/test_main_system.py b/tests/unit_tests/hyperion/test_main_system.py index 1a99886226..82fed037ac 100644 --- a/tests/unit_tests/hyperion/test_main_system.py +++ b/tests/unit_tests/hyperion/test_main_system.py @@ -18,6 +18,7 @@ from blueapi.core import BlueskyContext from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator from dodal.devices.baton import Baton +from dodal.devices.synchrotron import Synchrotron from dodal.devices.zebra.zebra import Zebra from flask.testing import FlaskClient from ophyd_async.core import set_mock_value @@ -766,7 +767,9 @@ def wait_for_udc_to_start_then_send_sigterm(): plan_runner = mock_create_udc_server.mock_calls[0].args[0] context = plan_runner.context baton = find_device_in_context(context, "baton", Baton) + synchrotron = find_device_in_context(context, "synchrotron", Synchrotron) set_mock_value(baton.requested_user, HYPERION_USER) + set_mock_value(synchrotron.machine_user_countdown, 1200) while len(mock_create_parameters_from_agamemnon.mock_calls) == 0: sleep(0.2) os.kill(os.getpid(), signal.SIGTERM) diff --git a/tests/unit_tests/t01.py b/tests/unit_tests/t01.py index 875dae44f6..45e2e122af 100644 --- a/tests/unit_tests/t01.py +++ b/tests/unit_tests/t01.py @@ -8,6 +8,7 @@ from dodal.device_manager import DeviceManager from dodal.devices.baton import Baton +from dodal.devices.synchrotron import Synchrotron from dodal.devices.xbpm_feedback import XBPMFeedback from dodal.utils import BeamlinePrefix, get_beamline_name @@ -25,6 +26,14 @@ def baton() -> Baton: return Baton(f"{PREFIX.beamline_prefix}-CS-BATON-01:") +@devices.factory() +def synchrotron() -> Synchrotron: + """Get the i03 Synchrotron device, instantiate it if it hasn't already been. + If this is called when already instantiated in i03, it will return the existing object. + """ + return Synchrotron() + + @devices.factory() def xbpm_feedback() -> XBPMFeedback: """Get the i03 XBPM feeback device, instantiate it if it hasn't already been. diff --git a/uv.lock b/uv.lock index 4f733312f3..373c55b392 100644 --- a/uv.lock +++ b/uv.lock @@ -802,8 +802,8 @@ wheels = [ [[package]] name = "dls-dodal" -version = "2.0.1.dev19+gf188f5797" -source = { git = "https://github.com/DiamondLightSource/dodal.git?rev=main#f188f5797f56ae82d79bf1f30f6ad73598a5681a" } +version = "2.0.1.dev13+g28c3f20f6" +source = { git = "https://github.com/DiamondLightSource/dodal.git?rev=28c3f20f6108d2769e7995f1008e7d76446fb38b#28c3f20f6108d2769e7995f1008e7d76446fb38b" } dependencies = [ { name = "aiofiles", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "aiohttp", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, From f1ef6952f28baebc9ffc85a55889adc0e8093331 Mon Sep 17 00:00:00 2001 From: Tamoor Shahid Date: Mon, 23 Feb 2026 13:03:36 +0000 Subject: [PATCH 08/10] Added constant and logging instead of raising error --- src/mx_bluesky/hyperion/baton_handler.py | 5 +++-- tests/unit_tests/hyperion/test_baton_handler.py | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/mx_bluesky/hyperion/baton_handler.py b/src/mx_bluesky/hyperion/baton_handler.py index 3dde994616..d8e3f95326 100644 --- a/src/mx_bluesky/hyperion/baton_handler.py +++ b/src/mx_bluesky/hyperion/baton_handler.py @@ -34,6 +34,7 @@ HYPERION_USER = "Hyperion" NO_USER = "None" +COUNTDOWN_THRESHOLD_SECONDS = 600 def run_forever(runner: PlanRunner): @@ -93,11 +94,11 @@ def collect() -> MsgGenerator: LOGGER.info(f"Synchrotron beam countdown is {countdown} seconds") - if countdown < 600: + if countdown < COUNTDOWN_THRESHOLD_SECONDS: _raise_udc_completed_alert(get_alerting_service()) # Release the baton for orderly exit from the instruction loop yield from _unrequest_baton(baton) - raise PlanError("Synchrotron machine countdown too low") + LOGGER.info("Synchrotron machine countdown too low") _raise_udc_start_alert(get_alerting_service()) yield from bpp.contingency_wrapper( diff --git a/tests/unit_tests/hyperion/test_baton_handler.py b/tests/unit_tests/hyperion/test_baton_handler.py index c270d295f5..5d2d3d557c 100644 --- a/tests/unit_tests/hyperion/test_baton_handler.py +++ b/tests/unit_tests/hyperion/test_baton_handler.py @@ -1058,9 +1058,12 @@ def test_baton_handler_fails_if_synchrotron_machine_countdown_below_threshold( bluesky_context: BlueskyContext, udc_runner: PlanRunner, dont_patch_clear_devices, + caplog, ): synchrotron = find_device_in_context(bluesky_context, "synchrotron", Synchrotron) set_mock_value(synchrotron.machine_user_countdown, 5) - with pytest.raises(PlanError, match="Synchrotron machine countdown too low"): + with caplog.at_level("INFO"): run_udc_when_requested(bluesky_context, udc_runner) + + assert "Synchrotron machine countdown too low" in caplog.text From 2e1b2226416042a81d0c39a0cb1156cc91e62da4 Mon Sep 17 00:00:00 2001 From: Tamoor Shahid Date: Mon, 23 Feb 2026 13:32:57 +0000 Subject: [PATCH 09/10] Combined alert and unrequest in one function --- src/mx_bluesky/hyperion/baton_handler.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/mx_bluesky/hyperion/baton_handler.py b/src/mx_bluesky/hyperion/baton_handler.py index d8e3f95326..a5a9f8cb00 100644 --- a/src/mx_bluesky/hyperion/baton_handler.py +++ b/src/mx_bluesky/hyperion/baton_handler.py @@ -95,9 +95,7 @@ def collect() -> MsgGenerator: LOGGER.info(f"Synchrotron beam countdown is {countdown} seconds") if countdown < COUNTDOWN_THRESHOLD_SECONDS: - _raise_udc_completed_alert(get_alerting_service()) - # Release the baton for orderly exit from the instruction loop - yield from _unrequest_baton(baton) + yield from _release_baton_on_completed_alert(baton) LOGGER.info("Synchrotron machine countdown too low") _raise_udc_start_alert(get_alerting_service()) @@ -195,9 +193,7 @@ def _fetch_and_process_agamemnon_instruction( current_visit, parameter_list ) else: - _raise_udc_completed_alert(get_alerting_service()) - # Release the baton for orderly exit from the instruction loop - yield from _unrequest_baton(baton) + yield from _release_baton_on_completed_alert(baton) return current_visit @@ -250,3 +246,8 @@ def _unrequest_baton(baton: Baton) -> MsgGenerator[str]: yield from bps.abs_set(baton.requested_user, NO_USER) return NO_USER return requested_user + + +def _release_baton_on_completed_alert(baton) -> MsgGenerator: + _raise_udc_completed_alert(get_alerting_service()) + yield from _unrequest_baton(baton) From b8cc377b374ba66ac3afe818d6771cda781a681c Mon Sep 17 00:00:00 2001 From: Tamoor Shahid Date: Mon, 23 Feb 2026 13:51:33 +0000 Subject: [PATCH 10/10] Refactored code into function and put into while loop for each collection --- src/mx_bluesky/hyperion/baton_handler.py | 29 ++++++++++++++++-------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/mx_bluesky/hyperion/baton_handler.py b/src/mx_bluesky/hyperion/baton_handler.py index a5a9f8cb00..4d393461c4 100644 --- a/src/mx_bluesky/hyperion/baton_handler.py +++ b/src/mx_bluesky/hyperion/baton_handler.py @@ -88,16 +88,6 @@ def collect() -> MsgGenerator: runner: The runner """ - baton = _get_baton(context) - synchrotron = _get_synchrotron(context) - countdown = yield from bps.rd(synchrotron.machine_user_countdown) - - LOGGER.info(f"Synchrotron beam countdown is {countdown} seconds") - - if countdown < COUNTDOWN_THRESHOLD_SECONDS: - yield from _release_baton_on_completed_alert(baton) - LOGGER.info("Synchrotron machine countdown too low") - _raise_udc_start_alert(get_alerting_service()) yield from bpp.contingency_wrapper( runner.decode_and_execute( @@ -116,6 +106,9 @@ def collect() -> MsgGenerator: baton = _get_baton(context) current_visit: str | None = None while (yield from _is_requesting_baton(baton)): + abort = yield from _abort_if_countdown_too_low(context, baton) + if abort: + return current_visit = yield from _fetch_and_process_agamemnon_instruction( baton, runner, current_visit ) @@ -248,6 +241,22 @@ def _unrequest_baton(baton: Baton) -> MsgGenerator[str]: return requested_user +def _abort_if_countdown_too_low( + context: BlueskyContext, baton: Baton +) -> MsgGenerator[bool]: + synchrotron = _get_synchrotron(context) + countdown = yield from bps.rd(synchrotron.machine_user_countdown) + + LOGGER.info(f"Synchrotron beam countdown is {countdown} seconds") + + if countdown < COUNTDOWN_THRESHOLD_SECONDS: + LOGGER.info("Synchrotron machine countdown too low") + yield from _release_baton_on_completed_alert(baton) + return True + + return False + + def _release_baton_on_completed_alert(baton) -> MsgGenerator: _raise_udc_completed_alert(get_alerting_service()) yield from _unrequest_baton(baton)