diff --git a/pyproject.toml b/pyproject.toml index 88d1719de..ed5693a70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,12 +40,12 @@ dependencies = [ # These dependencies may be issued as pre-release versions and should have a pin constraint # as by default pip-install will not upgrade to a pre-release. # - "blueapi == 0.4.5a1", + "blueapi >= 0.5.0", "daq-config-server >= 0.1.1", "ophyd == 1.9.0", "ophyd-async >= 0.3a5", "bluesky >= 1.13.0a4", - "dls-dodal @ git+https://github.com/DiamondLightSource/dodal.git", + "dls-dodal @ git+https://github.com/DiamondLightSource/dodal.git@5e8b6fc2933a2b2b2e788d432c8408317ac4a5cc", ] @@ -58,7 +58,8 @@ requires-python = ">=3.11" dev = [ "black", "build", - "copier", + # Commented out due to dependency version conflict with pydantic 1.x + # "copier", "diff-cover", "GitPython", "ipython", @@ -120,10 +121,7 @@ addopts = """ # https://iscinumpy.gitlab.io/post/bound-version-constraints/#watch-for-warnings filterwarnings = [ "error", - # Ignore deprecation warning from zocalo (https://github.com/DiamondLightSource/python-zocalo/issues/256) - "ignore::DeprecationWarning:zocalo", - "ignore::DeprecationWarning:pkg_resources", - # Ignore incorrect errors from bluesky (remove when https://github.com/bluesky/bluesky/issues/1797 is released) + # Ignore incorrect errors from bluesky (remove when https://github.com/bluesky/bluesky/issues/1797 is released) "ignore:((.|\n)*)was never iterated.*:RuntimeWarning", # Ignore deprecation warning from sqlalchemy "ignore::sqlalchemy.exc.MovedIn20Warning", @@ -135,6 +133,8 @@ filterwarnings = [ "ignore:(.*)unclosed file(.*)name='(.*)dodal.log'(.*):ResourceWarning", "ignore:(.*)unclosed MsgGenerator: @log.log_on_entry -def upload_parameters( - chipid: str = "oxford", pmac: PMAC = inject("pmac"), width: int | None = None -) -> MsgGenerator: +def upload_parameters(pmac: PMAC = inject("pmac")) -> MsgGenerator: setup_logging() - logger.info("Uploading Parameters to the GeoBrick") - if chipid == "oxford": - caput(CHIPTYPE_PV, 0) - width = 8 - else: - if width is None: - raise Exception("Supply a width if chipid is not oxford") + logger.info("Uploading Parameters for Oxford Chip to the GeoBrick") + caput(CHIPTYPE_PV, 0) + width = 8 map_file: Path = LITEMAP_PATH / "currentchip.map" if not map_file.exists(): raise FileNotFoundError(f"The file {map_file} has not yet been created") with open(map_file) as f: - logger.info(f"Chipid {chipid}") + logger.info(f"Chipid {ChipType.Oxford}") logger.info(f"width {width}") x = 1 for line in f.readlines()[: width**2]: diff --git a/src/mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py b/src/mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py index c9bb0dbd9..0f4445248 100755 --- a/src/mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +++ b/src/mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py @@ -132,10 +132,7 @@ def update_ui(oav, frame): cv.imshow("OAV1view", frame) -def start_viewer(oav1: str = OAV1_CAM): - # Get devices out of dodal - oav: OAV = i24.oav() - pmac: PMAC = i24.pmac() +def start_viewer(oav: OAV, pmac: PMAC, oav1: str = OAV1_CAM): # Create a video caputure from OAV1 cap = cv.VideoCapture(oav1) @@ -198,4 +195,7 @@ def start_viewer(oav1: str = OAV1_CAM): if __name__ == "__main__": RE = RunEngine() - RE(start_viewer()) + # Get devices out of dodal + oav: OAV = i24.oav() + pmac: PMAC = i24.pmac() + RE(start_viewer(oav, pmac)) diff --git a/src/mx_bluesky/beamlines/i24/serial/log.py b/src/mx_bluesky/beamlines/i24/serial/log.py index f0c71e02f..91d2601ec 100644 --- a/src/mx_bluesky/beamlines/i24/serial/log.py +++ b/src/mx_bluesky/beamlines/i24/serial/log.py @@ -4,13 +4,12 @@ from os import environ from pathlib import Path -from bluesky.log import logger as bluesky_logger from dodal.log import ( ERROR_LOG_BUFFER_LINES, + integrate_bluesky_and_ophyd_logging, set_up_all_logging_handlers, ) from dodal.log import LOGGER as dodal_logger -from ophyd_async.log import logger as ophyd_async_logger VISIT_PATH = Path("/dls_sw/i24/etc/ssx_current_visit.txt") @@ -88,13 +87,6 @@ def _get_logging_file_path() -> Path: return logging_path -def integrate_bluesky_and_ophyd_logging(parent_logger: logging.Logger): - """Integrate only bluesky and ophyd_async loggers.""" - for logger in [bluesky_logger, ophyd_async_logger]: - logger.parent = parent_logger - logger.setLevel(logging.DEBUG) - - def default_logging_setup(dev_mode: bool = False): """ Default log setup for i24 serial. diff --git a/src/mx_bluesky/beamlines/i24/serial/parameters/experiment_parameters.py b/src/mx_bluesky/beamlines/i24/serial/parameters/experiment_parameters.py index 923b29afa..a72f665a0 100644 --- a/src/mx_bluesky/beamlines/i24/serial/parameters/experiment_parameters.py +++ b/src/mx_bluesky/beamlines/i24/serial/parameters/experiment_parameters.py @@ -2,7 +2,7 @@ from pathlib import Path from typing import Literal -from pydantic import BaseModel, ConfigDict, validator +from pydantic import BaseModel, field_validator from mx_bluesky.beamlines.i24.serial.fixed_target.ft_utils import ( ChipType, @@ -21,7 +21,8 @@ class SerialExperiment(BaseModel): detector_distance_mm: float detector_name: Literal["eiger", "pilatus"] - @validator("visit", pre=True) + @field_validator("visit", mode="before") + @classmethod def _parse_visit(cls, visit: str | Path): if isinstance(visit, str): return Path(visit) @@ -56,8 +57,6 @@ def from_file(cls, filename: str | Path): class ChipDescription(BaseModel): """Parameters defining the chip in use for FT collection.""" - model_config = ConfigDict(use_enum_values=True) - chip_type: ChipType x_num_steps: int y_num_steps: int @@ -68,13 +67,6 @@ class ChipDescription(BaseModel): b2b_horz: float b2b_vert: float - @validator("chip_type", pre=True) - def _parse_chip(cls, chip_type: str | int): - if isinstance(chip_type, str): - return ChipType[chip_type] - else: - return ChipType(chip_type) - @property def chip_format(self) -> list[int]: return [self.x_blocks, self.y_blocks, self.x_num_steps, self.y_num_steps] @@ -97,8 +89,6 @@ def y_block_size(self) -> float: class FixedTargetParameters(SerialExperiment, LaserExperiment): """Fixed target parameter model.""" - model_config = ConfigDict(use_enum_values=True) - num_exposures: int chip: ChipDescription map_type: MappingType @@ -106,17 +96,6 @@ class FixedTargetParameters(SerialExperiment, LaserExperiment): checker_pattern: bool = False total_num_images: int = 0 # Calculated in the code for now - @validator("map_type", pre=True) - def _parse_map(cls, map_type: str | int): - if isinstance(map_type, str): - return MappingType[map_type] - else: - return MappingType(map_type) - - @validator("pump_repeat", pre=True) - def _parse_pump(cls, pump_repeat: int): - return PumpProbeSetting(pump_repeat) - @classmethod def from_file(cls, filename: str | Path): with open(filename) as fh: diff --git a/src/mx_bluesky/hyperion/__main__.py b/src/mx_bluesky/hyperion/__main__.py index dae8ffc3d..41c9ecd4d 100755 --- a/src/mx_bluesky/hyperion/__main__.py +++ b/src/mx_bluesky/hyperion/__main__.py @@ -71,9 +71,13 @@ def __init__(self, status: Status, message: str = "") -> None: class ErrorStatusAndMessage(StatusAndMessage): exception_type: str = "" - def __init__(self, exception: Exception) -> None: - super().__init__(Status.FAILED, repr(exception)) - self.exception_type = type(exception).__name__ + +def make_error_status_and_message(exception: Exception): + return ErrorStatusAndMessage( + status=Status.FAILED.value, + message=repr(exception), + exception_type=type(exception).__name__, + ) class BlueskyRunner: @@ -119,7 +123,7 @@ def start( plan_name: str, callbacks: CallbacksFactory | None, ) -> StatusAndMessage: - LOGGER.info(f"Started with parameters: {parameters.json(indent=2)}") + LOGGER.info(f"Started with parameters: {parameters.model_dump_json(indent=2)}") devices: Any = PLAN_REGISTRY[plan_name]["setup"](self.context) @@ -146,7 +150,7 @@ def stopping_thread(self): self.RE.abort() self.current_status = StatusAndMessage(Status.IDLE) except Exception as e: - self.current_status = ErrorStatusAndMessage(e) + self.current_status = make_error_status_and_message(e) def stop(self) -> StatusAndMessage: if self.current_status.status == Status.IDLE.value: @@ -197,7 +201,7 @@ def wait_on_queue(self): self.last_run_aborted = False except WarningException as exception: LOGGER.warning("Warning Exception", exc_info=True) - self.current_status = ErrorStatusAndMessage(exception) + self.current_status = make_error_status_and_message(exception) except Exception as exception: LOGGER.error("Exception on running plan", exc_info=True) @@ -205,7 +209,7 @@ def wait_on_queue(self): # Aborting will cause an exception here that we want to swallow self.last_run_aborted = False else: - self.current_status = ErrorStatusAndMessage(exception) + self.current_status = make_error_status_and_message(exception) finally: [ self.RE.unsubscribe(cb) @@ -231,6 +235,8 @@ def compose_start_args(context: BlueskyContext, plan_name: str, action: Actions) ) try: parameters = experiment_internal_param_type(**json.loads(request.data)) + if parameters.model_extra: + raise ValueError(f"Extra fields not allowed {parameters.model_extra}") except Exception as e: raise ValueError( f"Supplied parameters don't match the plan for this endpoint {request.data}" @@ -255,7 +261,7 @@ def put(self, plan_name: str, action: Actions): plan, params, plan_name, callback_type ) except Exception as e: - status_and_message = ErrorStatusAndMessage(e) + status_and_message = make_error_status_and_message(e) LOGGER.error(format_exception(e)) elif action == Actions.STOP.value: diff --git a/src/mx_bluesky/hyperion/device_setup_plans/manipulate_sample.py b/src/mx_bluesky/hyperion/device_setup_plans/manipulate_sample.py index e0d3177e8..56fa9d55f 100644 --- a/src/mx_bluesky/hyperion/device_setup_plans/manipulate_sample.py +++ b/src/mx_bluesky/hyperion/device_setup_plans/manipulate_sample.py @@ -2,8 +2,8 @@ import bluesky.plan_stubs as bps from dodal.devices.aperturescatterguard import ( - AperturePositionGDANames, ApertureScatterguard, + ApertureValue, ) from dodal.devices.attenuator import Attenuator from dodal.devices.backlight import Backlight, BacklightPosition @@ -16,52 +16,49 @@ def begin_sample_environment_setup( - detector_motion: DetectorMotion, attenuator: Attenuator, transmission_fraction: float, - detector_distance: float, group="setup_senv", ): """Start all sample environment changes that can be initiated before OAV snapshots are taken""" - yield from bps.abs_set(detector_motion.shutter, 1, group=group) - yield from bps.abs_set(detector_motion.z, detector_distance, group=group) yield from bps.abs_set(attenuator, transmission_fraction, group=group) def setup_sample_environment( aperture_scatterguard: ApertureScatterguard, - aperture_position_gda_name: AperturePositionGDANames | None, + aperture_position_gda_name: str | None, backlight: Backlight, group="setup_senv", ): """Move the aperture into required position, move out the backlight.""" - + aperture_value = ( + None + if not aperture_position_gda_name + else ApertureValue(aperture_position_gda_name) + ) yield from move_aperture_if_required( - aperture_scatterguard, aperture_position_gda_name, group=group + aperture_scatterguard, aperture_value, group=group ) yield from bps.abs_set(backlight, BacklightPosition.OUT, group=group) def move_aperture_if_required( aperture_scatterguard: ApertureScatterguard, - aperture_position_gda_name: AperturePositionGDANames | None, + aperture_value: ApertureValue | None, group="move_aperture", ): - if not aperture_position_gda_name: + if not aperture_value: previous_aperture_position = yield from bps.rd(aperture_scatterguard) - assert isinstance(previous_aperture_position, dict) + assert isinstance(previous_aperture_position, ApertureValue) LOGGER.info( - f"Using previously set aperture position {previous_aperture_position['name']}" + f"Using previously set aperture position {previous_aperture_position}" ) else: - aperture_position = aperture_scatterguard.get_position_from_gda_aperture_name( - aperture_position_gda_name - ) - LOGGER.info(f"Setting aperture position to {aperture_position}") + LOGGER.info(f"Setting aperture position to {aperture_value}") yield from bps.abs_set( aperture_scatterguard, - aperture_position, + aperture_value, group=group, ) diff --git a/src/mx_bluesky/hyperion/device_setup_plans/setup_panda.py b/src/mx_bluesky/hyperion/device_setup_plans/setup_panda.py index 038301440..df8e93602 100644 --- a/src/mx_bluesky/hyperion/device_setup_plans/setup_panda.py +++ b/src/mx_bluesky/hyperion/device_setup_plans/setup_panda.py @@ -5,10 +5,10 @@ import bluesky.plan_stubs as bps from blueapi.core import MsgGenerator -from dodal.common.beamlines.beamline_utils import get_directory_provider +from dodal.common.beamlines.beamline_utils import get_path_provider from dodal.devices.fast_grid_scan import PandAGridScanParams from ophyd_async.core import load_device -from ophyd_async.panda import ( +from ophyd_async.fastcs.panda import ( HDFPanda, SeqTable, SeqTableRow, @@ -212,6 +212,6 @@ def set_panda_directory(panda_directory: Path) -> MsgGenerator: suffix = datetime.now().strftime("_%Y%m%d%H%M%S") async def set_panda_dir(): - await get_directory_provider().update(directory=panda_directory, suffix=suffix) + await get_path_provider().update(directory=panda_directory, suffix=suffix) yield from bps.wait_for([set_panda_dir]) diff --git a/src/mx_bluesky/hyperion/device_setup_plans/setup_zebra.py b/src/mx_bluesky/hyperion/device_setup_plans/setup_zebra.py index c62950d1a..bf15546bd 100644 --- a/src/mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +++ b/src/mx_bluesky/hyperion/device_setup_plans/setup_zebra.py @@ -5,23 +5,24 @@ import bluesky.preprocessors as bpp from blueapi.core import MsgGenerator from dodal.devices.zebra import ( + AUTO_SHUTTER_GATE, + AUTO_SHUTTER_INPUT, DISCONNECT, IN1_TTL, IN3_TTL, IN4_TTL, - OR1, + PC_GATE, PC_PULSE, TTL_DETECTOR, TTL_PANDA, - TTL_SHUTTER, TTL_XSPRESS3, ArmDemand, EncEnum, I03Axes, RotationDirection, - SoftInState, Zebra, ) +from dodal.devices.zebra_controlled_shutter import ZebraShutter, ZebraShutterControl from mx_bluesky.hyperion.log import LOGGER @@ -57,13 +58,34 @@ def arm_zebra(zebra: Zebra): yield from bps.abs_set(zebra.pc.arm, ArmDemand.ARM, wait=True) -def disarm_zebra(zebra: Zebra): - yield from bps.abs_set(zebra.pc.arm, ArmDemand.DISARM, wait=True) +def tidy_up_zebra_after_rotation_scan( + zebra: Zebra, + zebra_shutter: ZebraShutter, + group="tidy_up_zebra_after_rotation", + wait=True, +): + yield from bps.abs_set(zebra.pc.arm, ArmDemand.DISARM, group=group) + yield from bps.abs_set( + zebra_shutter.control_mode, ZebraShutterControl.MANUAL, group=group + ) + if wait: + yield from bps.wait(group, timeout=ZEBRA_STATUS_TIMEOUT) + + +def set_shutter_auto_input(zebra: Zebra, input: int, group="set_shutter_trigger"): + """Set the input that the shutter uses when set to auto. + + For more details see the ZebraShutter device.""" + auto_shutter_control = zebra.logic_gates.and_gates[AUTO_SHUTTER_GATE] + yield from bps.abs_set( + auto_shutter_control.sources[AUTO_SHUTTER_INPUT], input, group + ) @bluesky_retry def setup_zebra_for_rotation( zebra: Zebra, + zebra_shutter: ZebraShutter, axis: EncEnum = I03Axes.OMEGA, start_angle: float = 0, scan_width: float = 360, @@ -100,8 +122,6 @@ def setup_zebra_for_rotation( ) yield from bps.abs_set(zebra.pc.dir, direction.value, group=group) LOGGER.info("ZEBRA SETUP: START") - # must be on for shutter trigger to be enabled - yield from bps.abs_set(zebra.inputs.soft_in_1, SoftInState.YES, group=group) # Set gate start, adjust for shutter opening time if necessary LOGGER.info(f"ZEBRA SETUP: degrees to adjust for shutter = {shutter_opening_deg}") LOGGER.info(f"ZEBRA SETUP: start angle start: {start_angle}") @@ -117,8 +137,11 @@ def setup_zebra_for_rotation( yield from bps.abs_set(zebra.pc.pulse_start, abs(shutter_opening_s), group=group) # Set gate position to be angle of interest yield from bps.abs_set(zebra.pc.gate_trigger, axis.value, group=group) - # Trigger the shutter with the gate (from PC_GATE & SOFTIN1 -> OR1) - yield from bps.abs_set(zebra.output.out_pvs[TTL_SHUTTER], OR1, group=group) + # Trigger the shutter with the gate + yield from bps.abs_set( + zebra_shutter.control_mode, ZebraShutterControl.AUTO, group=group + ) + yield from set_shutter_auto_input(zebra, PC_GATE, group=group) # Trigger the detector with a pulse yield from bps.abs_set(zebra.output.out_pvs[TTL_DETECTOR], PC_PULSE, group=group) # Don't use the fluorescence detector @@ -130,9 +153,17 @@ def setup_zebra_for_rotation( @bluesky_retry -def setup_zebra_for_gridscan(zebra: Zebra, group="setup_zebra_for_gridscan", wait=True): +def setup_zebra_for_gridscan( + zebra: Zebra, + zebra_shutter: ZebraShutter, + group="setup_zebra_for_gridscan", + wait=True, +): yield from bps.abs_set(zebra.output.out_pvs[TTL_DETECTOR], IN3_TTL, group=group) - yield from bps.abs_set(zebra.output.out_pvs[TTL_SHUTTER], IN4_TTL, group=group) + yield from bps.abs_set( + zebra_shutter.control_mode, ZebraShutterControl.AUTO, group=group + ) + yield from set_shutter_auto_input(zebra, IN4_TTL, group=group) yield from bps.abs_set(zebra.output.out_pvs[TTL_XSPRESS3], DISCONNECT, group=group) yield from bps.abs_set(zebra.output.pulse_1.input, DISCONNECT, group=group) @@ -141,34 +172,39 @@ def setup_zebra_for_gridscan(zebra: Zebra, group="setup_zebra_for_gridscan", wai @bluesky_retry -def set_zebra_shutter_to_manual( - zebra: Zebra, group="set_zebra_shutter_to_manual", wait=True +def tidy_up_zebra_after_gridscan( + zebra: Zebra, + zebra_shutter: ZebraShutter, + group="tidy_up_zebra_after_gridscan", + wait=True, ) -> MsgGenerator: yield from bps.abs_set(zebra.output.out_pvs[TTL_DETECTOR], PC_PULSE, group=group) - yield from bps.abs_set(zebra.output.out_pvs[TTL_SHUTTER], OR1, group=group) + yield from bps.abs_set( + zebra_shutter.control_mode, ZebraShutterControl.MANUAL, group=group + ) + yield from set_shutter_auto_input(zebra, PC_GATE, group=group) if wait: yield from bps.wait(group, timeout=ZEBRA_STATUS_TIMEOUT) -@bluesky_retry -def make_trigger_safe(zebra: Zebra, group="make_zebra_safe", wait=True): - yield from bps.abs_set( - zebra.inputs.soft_in_1, SoftInState.NO, wait=wait, group=group - ) - - @bluesky_retry def setup_zebra_for_panda_flyscan( - zebra: Zebra, group="setup_zebra_for_panda_flyscan", wait=True + zebra: Zebra, + zebra_shutter: ZebraShutter, + group="setup_zebra_for_panda_flyscan", + wait=True, ): # Forwards eiger trigger signal from panda yield from bps.abs_set(zebra.output.out_pvs[TTL_DETECTOR], IN1_TTL, group=group) # Forwards signal from PPMAC to fast shutter. High while panda PLC is running - yield from bps.abs_set(zebra.output.out_pvs[TTL_SHUTTER], IN4_TTL, group=group) + yield from bps.abs_set( + zebra_shutter.control_mode, ZebraShutterControl.AUTO, group=group + ) + yield from set_shutter_auto_input(zebra, IN4_TTL, group=group) - yield from bps.abs_set(zebra.output.out_pvs[3], DISCONNECT, group=group) + yield from bps.abs_set(zebra.output.out_pvs[TTL_XSPRESS3], DISCONNECT, group=group) yield from bps.abs_set( zebra.output.out_pvs[TTL_PANDA], IN3_TTL, group=group diff --git a/src/mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py b/src/mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py index 7f498cdbb..b025c46e5 100755 --- a/src/mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +++ b/src/mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py @@ -12,8 +12,8 @@ import numpy as np from blueapi.core import BlueskyContext, MsgGenerator from dodal.devices.aperturescatterguard import ( - AperturePosition, ApertureScatterguard, + ApertureValue, ) from dodal.devices.attenuator import Attenuator from dodal.devices.backlight import Backlight @@ -35,6 +35,7 @@ from dodal.devices.undulator import Undulator from dodal.devices.xbpm_feedback import XBPMFeedback from dodal.devices.zebra import Zebra +from dodal.devices.zebra_controlled_shutter import ZebraShutter from dodal.devices.zocalo.zocalo_results import ( ZOCALO_READING_PLAN_NAME, ZOCALO_STAGE_GROUP, @@ -42,7 +43,7 @@ get_processing_result, ) from dodal.plans.check_topup import check_topup_and_wait_if_necessary -from ophyd_async.panda import HDFPanda +from ophyd_async.fastcs.panda import HDFPanda from scanspec.core import AxesPoints, Axis from mx_bluesky.hyperion.device_setup_plans.manipulate_sample import move_x_y_z @@ -57,9 +58,9 @@ setup_panda_for_flyscan, ) from mx_bluesky.hyperion.device_setup_plans.setup_zebra import ( - set_zebra_shutter_to_manual, setup_zebra_for_gridscan, setup_zebra_for_panda_flyscan, + tidy_up_zebra_after_gridscan, ) from mx_bluesky.hyperion.device_setup_plans.xbpm_feedback import ( transmission_and_xbpm_feedback_for_collection_decorator, @@ -97,6 +98,7 @@ class FlyScanXRayCentreComposite: panda: HDFPanda panda_fast_grid_scan: PandAFastGridScan robot: BartRobot + sample_shutter: ZebraShutter @property def sample_motors(self) -> Smargon: @@ -136,7 +138,7 @@ def flyscan_xray_centre( "subplan_name": CONST.PLAN.GRIDSCAN_OUTER, CONST.TRIGGER.ZOCALO: CONST.PLAN.DO_FGS, "zocalo_environment": parameters.zocalo_environment, - "hyperion_parameters": parameters.json(), + "hyperion_parameters": parameters.model_dump_json(), "activate_callbacks": [ "GridscanNexusFileCallback", ], @@ -366,9 +368,8 @@ def set_aperture_for_bbox_size( ): # bbox_size is [x,y,z], for i03 we only care about x new_selected_aperture = ( - AperturePosition.MEDIUM if bbox_size[0] < 2 else AperturePosition.LARGE + ApertureValue.MEDIUM if bbox_size[0] < 2 else ApertureValue.LARGE ) - gda_name = aperture_device.get_gda_name_for_position(new_selected_aperture) LOGGER.info( f"Setting aperture to {new_selected_aperture} based on bounding box size {bbox_size}." ) @@ -377,7 +378,7 @@ def set_aperture_for_bbox_size( @bpp.run_decorator( md={ "subplan_name": "change_aperture", - "aperture_size": gda_name, + "aperture_size": new_selected_aperture.value, } ) def set_aperture(): @@ -439,7 +440,9 @@ def _generic_tidy( fgs_composite: FlyScanXRayCentreComposite, group, wait=True ) -> MsgGenerator: LOGGER.info("Tidying up Zebra") - yield from set_zebra_shutter_to_manual(fgs_composite.zebra, group=group, wait=wait) + yield from tidy_up_zebra_after_gridscan( + fgs_composite.zebra, fgs_composite.sample_shutter, group=group, wait=wait + ) LOGGER.info("Tidying up Zocalo") # make sure we don't consume any other results yield from bps.unstage(fgs_composite.zocalo, group=group, wait=wait) @@ -459,7 +462,9 @@ def _zebra_triggering_setup( parameters: ThreeDGridScan, initial_xyz: np.ndarray, ): - yield from setup_zebra_for_gridscan(fgs_composite.zebra, wait=True) + yield from setup_zebra_for_gridscan( + fgs_composite.zebra, fgs_composite.sample_shutter, wait=True + ) def _panda_triggering_setup( @@ -517,4 +522,6 @@ def _panda_triggering_setup( ) LOGGER.info("Setting up Zebra for panda flyscan") - yield from setup_zebra_for_panda_flyscan(fgs_composite.zebra, wait=True) + yield from setup_zebra_for_panda_flyscan( + fgs_composite.zebra, fgs_composite.sample_shutter, wait=True + ) diff --git a/src/mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py b/src/mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py index 27b34f032..88ec8c161 100644 --- a/src/mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +++ b/src/mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py @@ -24,8 +24,9 @@ from dodal.devices.undulator import Undulator from dodal.devices.xbpm_feedback import XBPMFeedback from dodal.devices.zebra import Zebra +from dodal.devices.zebra_controlled_shutter import ZebraShutter from dodal.devices.zocalo import ZocaloResults -from ophyd_async.panda import HDFPanda +from ophyd_async.fastcs.panda import HDFPanda from mx_bluesky.hyperion.device_setup_plans.manipulate_sample import ( move_aperture_if_required, @@ -83,6 +84,7 @@ class GridDetectThenXRayCentreComposite: panda: HDFPanda panda_fast_grid_scan: PandAFastGridScan robot: BartRobot + sample_shutter: ZebraShutter def create_devices(context: BlueskyContext) -> GridDetectThenXRayCentreComposite: @@ -93,7 +95,7 @@ def create_parameters_for_flyscan_xray_centre( grid_scan_with_edge_params: GridScanWithEdgeDetect, grid_parameters: GridParamUpdate, ) -> ThreeDGridScan: - params_json = grid_scan_with_edge_params.dict() + params_json = grid_scan_with_edge_params.model_dump() params_json.update(grid_parameters) flyscan_xray_centre_parameters = ThreeDGridScan(**params_json) LOGGER.info(f"Parameters for FGS: {flyscan_xray_centre_parameters}") @@ -136,7 +138,9 @@ def run_grid_detection_plan( parameters.snapshot_directory, ) - yield from bps.abs_set(composite.backlight, BacklightPosition.OUT) + yield from bps.abs_set( + composite.backlight, BacklightPosition.OUT, group=CONST.WAIT.GRID_READY_FOR_DC + ) yield from move_aperture_if_required( composite.aperture_scatterguard, @@ -163,6 +167,7 @@ def run_grid_detection_plan( zebra_fast_grid_scan=composite.zebra_fast_grid_scan, dcm=composite.dcm, robot=composite.robot, + sample_shutter=composite.sample_shutter, ), create_parameters_for_flyscan_xray_centre( parameters, grid_params_callback.get_grid_parameters() diff --git a/src/mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py b/src/mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py index 3d371d6fa..5aa4e839a 100644 --- a/src/mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +++ b/src/mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py @@ -3,7 +3,7 @@ from blueapi.core import MsgGenerator from bluesky import plan_stubs as bps -from dodal.devices.aperturescatterguard import AperturePosition, ApertureScatterguard +from dodal.devices.aperturescatterguard import ApertureScatterguard, ApertureValue from dodal.devices.backlight import Backlight, BacklightPosition from dodal.devices.oav.oav_detector import OAV from dodal.devices.oav.oav_parameters import OAVParameters @@ -41,7 +41,7 @@ def setup_oav_snapshot_plan( ) yield from bps.abs_set( composite.aperture_scatterguard, - AperturePosition.ROBOT_LOAD, + ApertureValue.ROBOT_LOAD, group=OAV_SNAPSHOT_SETUP_GROUP, ) 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 96e3aad29..ab6b67956 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 @@ -39,11 +39,11 @@ def create_devices(context: BlueskyContext) -> GridDetectThenXRayCentreComposite def create_parameters_for_grid_detection( pin_centre_parameters: PinTipCentreThenXrayCentre, ) -> GridScanWithEdgeDetect: - params_json = json.loads(pin_centre_parameters.json()) + params_json = json.loads(pin_centre_parameters.model_dump_json()) del params_json["tip_offset_um"] grid_detect_and_xray_centre = GridScanWithEdgeDetect(**params_json) LOGGER.info( - f"Parameters for grid detect and xray centre: {grid_detect_and_xray_centre.json(indent=2)}" + f"Parameters for grid detect and xray centre: {grid_detect_and_xray_centre.model_dump_json(indent=2)}" ) return grid_detect_and_xray_centre 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 1a45cb74c..1a208ef53 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 @@ -8,7 +8,7 @@ import bluesky.plan_stubs as bps import bluesky.preprocessors as bpp from blueapi.core import BlueskyContext, MsgGenerator -from dodal.devices.aperturescatterguard import AperturePosition, ApertureScatterguard +from dodal.devices.aperturescatterguard import ApertureScatterguard, ApertureValue from dodal.devices.attenuator import Attenuator from dodal.devices.backlight import Backlight from dodal.devices.dcm import DCM @@ -32,7 +32,7 @@ from dodal.devices.zebra import Zebra from dodal.devices.zocalo import ZocaloResults from dodal.plans.motor_util_plans import MoveTooLarge, home_and_reset_wrapper -from ophyd_async.panda import HDFPanda +from ophyd_async.fastcs.panda import HDFPanda from mx_bluesky.hyperion.device_setup_plans.utils import ( start_preparing_data_collection_then_do_plan, @@ -130,7 +130,7 @@ def take_robot_snapshots(oav: OAV, webcam: Webcam, directory: Path): def prepare_for_robot_load(composite: RobotLoadThenCentreComposite): yield from bps.abs_set( composite.aperture_scatterguard, - AperturePosition.ROBOT_LOAD, + ApertureValue.ROBOT_LOAD, group="prepare_robot_load", ) @@ -194,7 +194,7 @@ def robot_load_then_centre_plan( md={ "subplan_name": CONST.PLAN.ROBOT_LOAD, "metadata": { - "visit_path": params.ispyb_params.visit_path, + "visit_path": str(params.visit_directory), "sample_id": params.sample_id, "sample_puck": params.sample_puck, "sample_pin": params.sample_pin, 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 5df5ad7fc..9d26fdb5f 100644 --- a/src/mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +++ b/src/mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py @@ -20,6 +20,7 @@ from dodal.devices.synchrotron import Synchrotron from dodal.devices.undulator import Undulator from dodal.devices.zebra import RotationDirection, Zebra +from dodal.devices.zebra_controlled_shutter import ZebraShutter from dodal.plans.check_topup import check_topup_and_wait_if_necessary from mx_bluesky.hyperion.device_setup_plans.manipulate_sample import ( @@ -36,9 +37,11 @@ ) from mx_bluesky.hyperion.device_setup_plans.setup_zebra import ( arm_zebra, - disarm_zebra, - make_trigger_safe, setup_zebra_for_rotation, + tidy_up_zebra_after_rotation_scan, +) +from mx_bluesky.hyperion.device_setup_plans.utils import ( + start_preparing_data_collection_then_do_plan, ) from mx_bluesky.hyperion.experiment_plans.oav_snapshot_plan import ( OavSnapshotComposite, @@ -70,6 +73,7 @@ class RotationScanComposite(OavSnapshotComposite): undulator: Undulator synchrotron: Synchrotron s4_slit_gaps: S4SlitGaps + sample_shutter: ZebraShutter zebra: Zebra oav: OAV @@ -211,32 +215,31 @@ def _rotation_scan_plan( yield from bps.abs_set( axis, motion_values.start_motion_deg, - group="move_to_rotation_start", - wait=True, + group=CONST.WAIT.ROTATION_READY_FOR_DC, ) yield from setup_zebra_for_rotation( composite.zebra, + composite.sample_shutter, start_angle=motion_values.start_scan_deg, scan_width=motion_values.scan_width_deg, direction=motion_values.direction, shutter_opening_deg=motion_values.shutter_opening_deg, shutter_opening_s=motion_values.shutter_time_s, group="setup_zebra", - wait=True, ) yield from setup_sample_environment( composite.aperture_scatterguard, params.selected_aperture, composite.backlight, + group=CONST.WAIT.ROTATION_READY_FOR_DC, ) LOGGER.info("Wait for any previous moves...") # wait for all the setup tasks at once + yield from bps.wait(CONST.WAIT.ROTATION_READY_FOR_DC) yield from bps.wait(CONST.WAIT.MOVE_GONIO_TO_START) - yield from bps.wait("setup_senv") - yield from bps.wait("move_to_rotation_start") # get some information for the ispyb deposition and trigger the callback yield from read_hardware_for_zocalo(composite.eiger) @@ -283,8 +286,10 @@ def _cleanup_plan(composite: RotationScanComposite, **kwargs): max_vel = yield from bps.rd(composite.smargon.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 make_trigger_safe(composite.zebra, group="cleanup") - yield from bpp.finalize_wrapper(disarm_zebra(composite.zebra), bps.wait("cleanup")) + yield from tidy_up_zebra_after_rotation_scan( + composite.zebra, composite.sample_shutter, group="cleanup", wait=False + ) + yield from bps.wait("cleanup") def _move_and_rotation( @@ -339,7 +344,7 @@ def rotation_scan( md={ "subplan_name": CONST.PLAN.ROTATION_OUTER, CONST.TRIGGER.ZOCALO: CONST.PLAN.ROTATION_MAIN, - "hyperion_parameters": parameters.json(), + "hyperion_parameters": parameters.model_dump_json, "activate_callbacks": [ "RotationISPyBCallback", "RotationNexusFileCallback", @@ -352,21 +357,26 @@ def rotation_scan_plan_with_stage_and_cleanup( eiger: EigerDetector = composite.eiger eiger.set_detector_parameters(params.detector_params) - @bpp.stage_decorator([eiger]) @bpp.finalize_decorator(lambda: _cleanup_plan(composite)) def rotation_with_cleanup_and_stage(params: RotationScan): LOGGER.info("setting up sample environment...") yield from begin_sample_environment_setup( - composite.detector_motion, composite.attenuator, params.transmission_frac, - params.detector_params.detector_distance, + group=CONST.WAIT.ROTATION_READY_FOR_DC, ) yield from _move_and_rotation(composite, params, oav_params) LOGGER.info("setting up and staging eiger...") - yield from rotation_with_cleanup_and_stage(params) + yield from start_preparing_data_collection_then_do_plan( + eiger, + composite.detector_motion, + params.detector_distance_mm, + rotation_with_cleanup_and_stage(params), + group=CONST.WAIT.ROTATION_READY_FOR_DC, + ) + yield from bps.unstage(eiger) yield from rotation_scan_plan_with_stage_and_cleanup(parameters) @@ -382,10 +392,9 @@ def multi_rotation_scan( eiger.set_detector_parameters(parameters.detector_params) LOGGER.info("setting up sample environment...") yield from begin_sample_environment_setup( - composite.detector_motion, composite.attenuator, parameters.transmission_frac, - parameters.detector_params.detector_distance, + group=CONST.WAIT.ROTATION_READY_FOR_DC, ) @bpp.set_run_key_decorator("multi_rotation_scan") @@ -410,7 +419,7 @@ def _multi_rotation_scan(): md={ "subplan_name": CONST.PLAN.ROTATION_OUTER, CONST.TRIGGER.ZOCALO: CONST.PLAN.ROTATION_MAIN, - "hyperion_parameters": single_scan.json(), + "hyperion_parameters": single_scan.model_dump_json(), } ) def rotation_scan_core( @@ -421,4 +430,10 @@ def rotation_scan_core( yield from rotation_scan_core(single_scan) LOGGER.info("setting up and staging eiger...") - yield from _multi_rotation_scan() + yield from start_preparing_data_collection_then_do_plan( + eiger, + composite.detector_motion, + parameters.detector_distance_mm, + _multi_rotation_scan(), + group=CONST.WAIT.ROTATION_READY_FOR_DC, + ) diff --git a/src/mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py b/src/mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py index 911c99a4d..feb2087d3 100644 --- a/src/mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py +++ b/src/mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py @@ -2,13 +2,10 @@ import re -from dodal.devices.detector import DetectorParams - from mx_bluesky.hyperion.external_interaction.ispyb.data_model import ( DataCollectionGroupInfo, DataCollectionInfo, ) -from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_dataclass import IspybParams from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_store import ( EIGER_FILE_SUFFIX, I03_EIGER_DETECTOR, @@ -71,16 +68,3 @@ def get_proposal_and_session_from_visit_string(visit_string: str) -> tuple[str, def get_visit_string_from_path(path: str | None) -> str | None: match = re.search(VISIT_PATH_REGEX, path) if path else None return str(match.group(1)) if match else None - - -def get_visit_string(ispyb_params: IspybParams, detector_params: DetectorParams) -> str: - assert ispyb_params and detector_params, "StoreInISPyB didn't acquire params" - visit_path_match = get_visit_string_from_path(ispyb_params.visit_path) - if visit_path_match: - return visit_path_match - visit_path_match = get_visit_string_from_path(detector_params.directory) - if not visit_path_match: - raise ValueError( - f"Visit not found from {ispyb_params.visit_path} or {detector_params.directory}" - ) - return visit_path_match diff --git a/src/mx_bluesky/hyperion/external_interaction/callbacks/ispyb_callback_base.py b/src/mx_bluesky/hyperion/external_interaction/callbacks/ispyb_callback_base.py index e35512361..3bd77cb91 100644 --- a/src/mx_bluesky/hyperion/external_interaction/callbacks/ispyb_callback_base.py +++ b/src/mx_bluesky/hyperion/external_interaction/callbacks/ispyb_callback_base.py @@ -5,7 +5,6 @@ from typing import TYPE_CHECKING, Any, TypeVar, cast from dodal.beamline_specific_utils.i03 import beam_size_from_aperture -from dodal.devices.aperturescatterguard import SingleAperturePosition from dodal.devices.detector.det_resolution import resolution from dodal.devices.synchrotron import SynchrotronMode @@ -123,10 +122,9 @@ def _handle_ispyb_hardware_read(self, doc) -> Sequence[ScanDataInfo]: def _handle_ispyb_transmission_flux_read(self, doc) -> Sequence[ScanDataInfo]: assert self.params - aperture_size = SingleAperturePosition( - **doc["data"]["aperture_scatterguard-selected_aperture"] - ) - beamsize = beam_size_from_aperture(aperture_size) + aperture = doc["data"]["aperture_scatterguard-selected_aperture"] + aperture_radius = doc["data"]["aperture_scatterguard-radius"] + beamsize = beam_size_from_aperture(aperture_radius) beamsize_x_mm = beamsize.x_um / 1000 if beamsize.x_um else None beamsize_y_mm = beamsize.y_um / 1000 if beamsize.y_um else None hwscan_data_collection_info = DataCollectionInfo( @@ -153,7 +151,7 @@ def _handle_ispyb_transmission_flux_read(self, doc) -> Sequence[ScanDataInfo]: hwscan_data_collection_info, None, self.params ) ISPYB_LOGGER.info("Updating ispyb data collection after flux read.") - self.append_to_comment(f"Aperture: {aperture_size.name}. ") + self.append_to_comment(f"Aperture: {aperture}. ") return scan_data_infos @abstractmethod diff --git a/src/mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py b/src/mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py index ee9c44a9f..1f203b950 100644 --- a/src/mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py +++ b/src/mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py @@ -2,6 +2,7 @@ from mx_bluesky.hyperion.external_interaction.ispyb.data_model import DataCollectionInfo from mx_bluesky.hyperion.log import ISPYB_LOGGER +from mx_bluesky.hyperion.parameters.components import TemporaryIspybExtras from mx_bluesky.hyperion.parameters.rotation import RotationScan @@ -20,17 +21,19 @@ def populate_data_collection_info_for_rotation(params: RotationScan): info.xtal_snapshot2, info.xtal_snapshot3, info.xtal_snapshot4, - ) = get_xtal_snapshots(params.ispyb_params) + ) = get_xtal_snapshots(params.ispyb_extras) return info -def get_xtal_snapshots(ispyb_params): - if ispyb_params.xtal_snapshots_omega_start: - xtal_snapshots = ispyb_params.xtal_snapshots_omega_start[:4] +def get_xtal_snapshots(ispyb_extras: TemporaryIspybExtras | None): + if ispyb_extras and ispyb_extras.xtal_snapshots_omega_start: + xtal_snapshots = ispyb_extras.xtal_snapshots_omega_start[:4] ISPYB_LOGGER.info( f"Using rotation scan snapshots {xtal_snapshots} for ISPyB deposition" ) else: - ISPYB_LOGGER.warning("No xtal snapshot paths sent to ISPyB!") + ISPYB_LOGGER.info( + "No xtal snapshot paths sent to ISPyB from GDA, will take own snapshots" + ) xtal_snapshots = [] return xtal_snapshots + [None] * (4 - len(xtal_snapshots)) diff --git a/src/mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py b/src/mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py index 0045d84aa..ded95095c 100644 --- a/src/mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +++ b/src/mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py @@ -29,9 +29,9 @@ DataCollectionGridInfo, DataCollectionInfo, DataCollectionPositionInfo, + Orientation, ScanDataInfo, ) -from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_dataclass import Orientation from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_store import ( IspybIds, StoreInIspyb, @@ -53,7 +53,7 @@ def ispyb_activation_wrapper(plan_generator: MsgGenerator, parameters): md={ "activate_callbacks": ["GridscanISPyBCallback"], "subplan_name": CONST.PLAN.GRID_DETECT_AND_DO_GRIDSCAN, - "hyperion_parameters": parameters.json(), + "hyperion_parameters": parameters.model_dump_json(), }, ) @@ -93,9 +93,7 @@ def activity_gated_start(self, doc: RunStart): "ISPyB callback received start document with experiment parameters and " f"uid: {self.uid_to_finalize_on}" ) - self.params = GridCommon.from_json( - doc.get("hyperion_parameters"), allow_extras=True - ) + self.params = GridCommon.from_json(doc.get("hyperion_parameters")) self.ispyb = StoreInIspyb(self.ispyb_config) data_collection_group_info = populate_data_collection_group(self.params) diff --git a/src/mx_bluesky/hyperion/external_interaction/ispyb/data_model.py b/src/mx_bluesky/hyperion/external_interaction/ispyb/data_model.py index 89d54c8ed..01542ca9a 100644 --- a/src/mx_bluesky/hyperion/external_interaction/ispyb/data_model.py +++ b/src/mx_bluesky/hyperion/external_interaction/ispyb/data_model.py @@ -1,6 +1,10 @@ from dataclasses import asdict, dataclass +from enum import Enum -from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_dataclass import Orientation + +class Orientation(Enum): + HORIZONTAL = "horizontal" + VERTICAL = "vertical" @dataclass() diff --git a/src/mx_bluesky/hyperion/external_interaction/ispyb/ispyb_dataclass.py b/src/mx_bluesky/hyperion/external_interaction/ispyb/ispyb_dataclass.py deleted file mode 100644 index 39b6146d4..000000000 --- a/src/mx_bluesky/hyperion/external_interaction/ispyb/ispyb_dataclass.py +++ /dev/null @@ -1,65 +0,0 @@ -import builtins -from enum import Enum -from typing import Any - -import numpy as np -from pydantic import BaseModel, validator - -GRIDSCAN_ISPYB_PARAM_DEFAULTS = { - "sample_id": None, - "visit_path": "", - "position": None, - "comment": "Descriptive comment.", -} - - -class IspybParams(BaseModel): - visit_path: str - position: np.ndarray | None - comment: str - sample_id: int | None = None - - # Optional from GDA as populated by Ophyd - xtal_snapshots_omega_start: list[str] | None = None - ispyb_experiment_type: str | None = None - - class Config: - arbitrary_types_allowed = True - json_encoders = {np.ndarray: lambda a: a.tolist()} - - def dict(self, **kwargs): - as_dict = super().dict(**kwargs) - as_dict["position"] = ( - as_dict["position"].tolist() if as_dict["position"] is not None else None - ) - return as_dict - - @validator("position", pre=True) - def _parse_position( - cls, - position: list[int | float] | np.ndarray | None, - values: builtins.dict[str, Any], - ) -> np.ndarray | None: - if position is None: - return None - assert len(position) == 3 - if isinstance(position, np.ndarray): - return position - return np.array(position) - - -class RobotLoadIspybParams(IspybParams): ... - - -class RotationIspybParams(IspybParams): ... - - -class GridscanIspybParams(IspybParams): - def dict(self, **kwargs): - as_dict = super().dict(**kwargs) - return as_dict - - -class Orientation(Enum): - HORIZONTAL = "horizontal" - VERTICAL = "vertical" diff --git a/src/mx_bluesky/hyperion/parameters/components.py b/src/mx_bluesky/hyperion/parameters/components.py index 2a750a0ec..78dc3c9a9 100644 --- a/src/mx_bluesky/hyperion/parameters/components.py +++ b/src/mx_bluesky/hyperion/parameters/components.py @@ -8,20 +8,23 @@ from pathlib import Path from typing import SupportsInt, TypeVar -from dodal.devices.aperturescatterguard import AperturePositionGDANames +from dodal.devices.aperturescatterguard import ApertureValue from dodal.devices.detector import ( DetectorParams, TriggerMode, ) -from numpy.typing import NDArray -from pydantic import BaseModel, Extra, Field, root_validator, validator +from pydantic import ( + BaseModel, + ConfigDict, + Field, + field_serializer, + field_validator, + model_validator, +) from scanspec.core import AxesPoints from semver import Version from mx_bluesky.hyperion.external_interaction.config_server import FeatureFlags -from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_dataclass import ( - IspybParams, -) from mx_bluesky.hyperion.parameters.constants import CONST T = TypeVar("T") @@ -34,16 +37,6 @@ def _parse(cls, version): return version return cls.parse(version) - @classmethod - def __get_validators__(cls): - """Return a list of validator methods for pydantic models.""" - yield cls._parse - - @classmethod - def __modify_schema__(cls, field_schema): - """Inject/mutate the pydantic field schema in-place.""" - field_schema.update(examples=["1.0.2", "2.15.3-alpha", "21.3.15-beta+12345"]) - PARAMETER_VERSION = ParameterVersion.parse("5.0.0") @@ -107,13 +100,10 @@ class IspybExperimentType(StrEnum): class HyperionParameters(BaseModel): - class Config: - arbitrary_types_allowed = True - extra = Extra.forbid - json_encoders = { - ParameterVersion: lambda pv: str(pv), - NDArray: lambda a: a.tolist(), - } + model_config = ConfigDict( + arbitrary_types_allowed=True, + extra="allow", + ) def __hash__(self) -> int: return self.json().__hash__() @@ -121,8 +111,14 @@ def __hash__(self) -> int: features: FeatureFlags = Field(default=FeatureFlags()) parameter_model_version: ParameterVersion - @validator("parameter_model_version") - def _validate_version(cls, version: ParameterVersion): + @field_serializer("parameter_model_version") + def serialize_parameter_version(self, version: ParameterVersion): + return str(version) + + @field_validator("parameter_model_version", mode="before") + @classmethod + def _validate_version(cls, version_str: str): + version = ParameterVersion.parse(version_str) assert ( version >= ParameterVersion(major=PARAMETER_VERSION.major) ), f"Parameter version too old! This version of hyperion uses {PARAMETER_VERSION}" @@ -132,18 +128,14 @@ def _validate_version(cls, version: ParameterVersion): return version @classmethod - def from_json(cls, input: str | None, *, allow_extras: bool = False): + def from_json(cls, input: str | None): assert input is not None - if allow_extras: - cls.Config.extra = Extra.ignore # type: ignore - params = cls(**json.loads(input)) - cls.Config.extra = Extra.forbid - return params + return cls(**json.loads(input)) class WithSnapshot(BaseModel): snapshot_directory: Path - snapshot_omegas_deg: list[float] | None + snapshot_omegas_deg: list[float] | None = None @property def take_snapshots(self) -> bool: @@ -154,7 +146,7 @@ class DiffractionExperiment(HyperionParameters, WithSnapshot): """For all experiments which use beam""" visit: str = Field(min_length=1) - file_name: str = Field(pattern=r"[\w]{2}[\d]+-[\d]+") + file_name: str exposure_time_s: float = Field(gt=0) comment: str = Field(default="") beamline: str = Field(default=CONST.I03.BEAMLINE, pattern=r"BL\d{2}[BIJS]") @@ -169,12 +161,13 @@ class DiffractionExperiment(HyperionParameters, WithSnapshot): detector_distance_mm: float | None = Field(default=None, gt=0) demand_energy_ev: float | None = Field(default=None, gt=0) run_number: int | None = Field(default=None, ge=0) - selected_aperture: AperturePositionGDANames | None = Field(default=None) + selected_aperture: ApertureValue | None = Field(default=None) transmission_frac: float = Field(default=0.1) ispyb_experiment_type: IspybExperimentType storage_directory: str - @root_validator(pre=True) + @model_validator(mode="before") + @classmethod def validate_snapshot_directory(cls, values): snapshot_dir = values.get( "snapshot_directory", Path(values["storage_directory"], "snapshots") @@ -198,11 +191,6 @@ def num_images(self) -> int: @abstractmethod def detector_params(self) -> DetectorParams: ... - @property - @abstractmethod - def ispyb_params(self) -> IspybParams: # Soon to remove - ... - class WithScan(BaseModel): """For experiments where the scan is known""" @@ -266,9 +254,6 @@ class OptionalGonioAngleStarts(BaseModel): class TemporaryIspybExtras(BaseModel): - # for while we still need ISpyB params - to be removed in #1277 and/or #43 - class Config: - arbitrary_types_allowed = True - extra = Extra.forbid + model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid") xtal_snapshots_omega_start: list[str] | None = None diff --git a/src/mx_bluesky/hyperion/parameters/constants.py b/src/mx_bluesky/hyperion/parameters/constants.py index 98539b5c3..1bdf10954 100644 --- a/src/mx_bluesky/hyperion/parameters/constants.py +++ b/src/mx_bluesky/hyperion/parameters/constants.py @@ -38,8 +38,8 @@ class PlanNameConstants: @dataclass(frozen=True) class PlanGroupCheckpointConstants: # For places to synchronise / stop and wait in plans, use as bluesky group names - # Gridscan - GRID_READY_FOR_DC = "ready_for_data_collection" + GRID_READY_FOR_DC = "grid_ready_for_data_collection" + ROTATION_READY_FOR_DC = "rotation_ready_for_data_collection" MOVE_GONIO_TO_START = "move_gonio_to_start" @@ -70,7 +70,7 @@ class TriggerConstants: @dataclass(frozen=True) class GridscanParamConstants: WIDTH_UM = 600.0 - EXPOSURE_TIME_S = 0.02 + EXPOSURE_TIME_S = 0.004 USE_ROI = True BOX_WIDTH_UM = 20.0 OMEGA_1 = 0.0 diff --git a/src/mx_bluesky/hyperion/parameters/gridscan.py b/src/mx_bluesky/hyperion/parameters/gridscan.py index a48cd5b4f..39b613889 100644 --- a/src/mx_bluesky/hyperion/parameters/gridscan.py +++ b/src/mx_bluesky/hyperion/parameters/gridscan.py @@ -2,9 +2,8 @@ import os -from dodal.devices.aperturescatterguard import AperturePositionGDANames +from dodal.devices.aperturescatterguard import ApertureValue from dodal.devices.detector import ( - DetectorDistanceToBeamXYConverter, DetectorParams, ) from dodal.devices.fast_grid_scan import ( @@ -15,9 +14,6 @@ from scanspec.core import Path as ScanPath from scanspec.specs import Line, Static -from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_dataclass import ( - GridscanIspybParams, -) from mx_bluesky.hyperion.parameters.components import ( DiffractionExperimentWithSample, IspybExperimentType, @@ -44,19 +40,7 @@ class GridCommon( ispyb_experiment_type: IspybExperimentType = Field( default=IspybExperimentType.GRIDSCAN_3D ) - selected_aperture: AperturePositionGDANames | None = Field( - default=AperturePositionGDANames.SMALL_APERTURE - ) - - @property - def ispyb_params(self): - return GridscanIspybParams( - visit_path=str(self.visit_directory), - comment=self.comment, - sample_id=self.sample_id, - ispyb_experiment_type=self.ispyb_experiment_type, - position=None, - ) + selected_aperture: ApertureValue | None = Field(default=ApertureValue.SMALL) @property def detector_params(self): @@ -85,9 +69,6 @@ def detector_params(self): use_roi_mode=self.use_roi_mode, det_dist_to_beam_converter_path=self.det_dist_to_beam_converter_path, trigger_mode=self.trigger_mode, - beam_xy_converter=DetectorDistanceToBeamXYConverter( - self.det_dist_to_beam_converter_path - ), enable_dev_shm=self.use_gpu, **optional_args, ) @@ -104,7 +85,7 @@ class RobotLoadThenCentre(GridCommon): thawing_time: float = Field(default=CONST.I03.THAWING_TIME) def pin_centre_then_xray_centre_params(self): - my_params = self.dict() + my_params = self.model_dump() del my_params["thawing_time"] return PinTipCentreThenXrayCentre(**my_params) diff --git a/src/mx_bluesky/hyperion/parameters/rotation.py b/src/mx_bluesky/hyperion/parameters/rotation.py index 63a472dac..8fd1c37af 100644 --- a/src/mx_bluesky/hyperion/parameters/rotation.py +++ b/src/mx_bluesky/hyperion/parameters/rotation.py @@ -3,24 +3,18 @@ import os from collections.abc import Iterator from itertools import accumulate -from typing import Annotated +from typing import Annotated, Any from annotated_types import Len from dodal.devices.detector import DetectorParams -from dodal.devices.detector.det_dist_to_beam_converter import ( - DetectorDistanceToBeamXYConverter, -) from dodal.devices.zebra import ( RotationDirection, ) -from pydantic import Field, root_validator +from pydantic import Field, model_validator from scanspec.core import AxesPoints from scanspec.core import Path as ScanPath from scanspec.specs import Line -from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_dataclass import ( - RotationIspybParams, -) from mx_bluesky.hyperion.parameters.components import ( DiffractionExperimentWithSample, IspybExperimentType, @@ -40,7 +34,7 @@ class RotationScanPerSweep(OptionalGonioAngleStarts, OptionalXyzStarts): scan_width_deg: float = Field(default=360, gt=0) rotation_direction: RotationDirection = Field(default=RotationDirection.NEGATIVE) nexus_vds_start_img: int = Field(default=0, ge=0) - ispyb_extras: TemporaryIspybExtras | None + ispyb_extras: TemporaryIspybExtras | None = None class RotationExperiment(DiffractionExperimentWithSample): @@ -73,28 +67,11 @@ def _detector_params(self, omega_start_deg: float): num_triggers=1, use_roi_mode=False, det_dist_to_beam_converter_path=self.det_dist_to_beam_converter_path, - beam_xy_converter=DetectorDistanceToBeamXYConverter( - self.det_dist_to_beam_converter_path - ), **optional_args, ) class RotationScan(WithScan, RotationScanPerSweep, RotationExperiment): - @property - def ispyb_params(self): # pyright: ignore - return RotationIspybParams( - visit_path=str(self.visit_directory), - comment=self.comment, - xtal_snapshots_omega_start=( - self.ispyb_extras.xtal_snapshots_omega_start - if self.ispyb_extras - else [] - ), - ispyb_experiment_type=self.ispyb_experiment_type, - position=None, - ) - @property def detector_params(self): return self._detector_params(self.omega_start_deg) @@ -123,19 +100,21 @@ class MultiRotationScan(RotationExperiment, SplitScan): def _single_rotation_scan(self, scan: RotationScanPerSweep) -> RotationScan: # self has everything from RotationExperiment - params = self.dict() + params = self.model_dump() del params["rotation_scans"] # provided `scan` has everything from RotationScanPerSweep - params.update(scan.dict()) + params.update(scan.model_dump()) # together they have everything for RotationScan return RotationScan(**params) - @root_validator(pre=False) # type: ignore - def validate_snapshot_directory(cls, values): - start_img = 0 - for scan in values["rotation_scans"]: - scan.nexus_vds_start_img = start_img - start_img += scan.scan_width_deg / values["rotation_increment_deg"] + @model_validator(mode="after") + @classmethod + def correct_start_vds(cls, values: Any) -> Any: + assert isinstance(values, MultiRotationScan) + start_img = 0.0 + for scan in values.rotation_scans: + scan.nexus_vds_start_img = int(start_img) + start_img += scan.scan_width_deg / values.rotation_increment_deg return values @property diff --git a/src/mx_bluesky/hyperion/utils/validation.py b/src/mx_bluesky/hyperion/utils/validation.py index b8171fadf..096a56c47 100644 --- a/src/mx_bluesky/hyperion/utils/validation.py +++ b/src/mx_bluesky/hyperion/utils/validation.py @@ -62,7 +62,7 @@ def fake_rotation_scan( @bpp.run_decorator( # attach experiment metadata to the start document md={ "subplan_name": CONST.PLAN.ROTATION_OUTER, - "hyperion_parameters": parameters.json(), + "hyperion_parameters": parameters.model_dump_json(), "activate_callbacks": "RotationNexusFileCallback", } ) @@ -118,6 +118,7 @@ def fake_create_rotation_devices(): zebra=zebra, robot=robot, oav=oav, + sample_shutter=i03.sample_shutter(fake_with_ophyd_sim=True), ) diff --git a/tests/conftest.py b/tests/conftest.py index e1d065cc0..3e0faca1e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -22,11 +22,9 @@ ) from dodal.common.beamlines.beamline_utils import clear_devices from dodal.devices.aperturescatterguard import ( - ApertureFiveDimensionalLocation, AperturePosition, ApertureScatterguard, - ApertureScatterguardTolerances, - SingleAperturePosition, + ApertureValue, ) from dodal.devices.attenuator import Attenuator from dodal.devices.backlight import Backlight @@ -46,14 +44,20 @@ from dodal.devices.util.test_utils import patch_motor as oa_patch_motor from dodal.devices.webcam import Webcam from dodal.devices.zebra import Zebra +from dodal.devices.zebra_controlled_shutter import ZebraShutter from dodal.log import LOGGER as dodal_logger from dodal.log import set_up_all_logging_handlers from ophyd.sim import NullStatus -from ophyd_async.core import Device, DeviceVector, callback_on_mock_put, set_mock_value -from ophyd_async.core.async_status import AsyncStatus -from ophyd_async.epics.motion.motor import Motor +from ophyd_async.core import ( + AsyncStatus, + Device, + DeviceVector, + callback_on_mock_put, + set_mock_value, +) +from ophyd_async.epics.motor import Motor from ophyd_async.epics.signal import epics_signal_rw -from ophyd_async.panda._common_blocks import DatasetTable +from ophyd_async.fastcs.panda import DatasetTable from scanspec.core import Path as ScanPath from scanspec.specs import Line @@ -456,32 +460,45 @@ def thawer(RE) -> Generator[Thawer, Any, Any]: yield i03.thawer(fake_with_ophyd_sim=True) +@pytest.fixture +def sample_shutter(RE) -> Generator[ZebraShutter, Any, Any]: + yield i03.sample_shutter(fake_with_ophyd_sim=True) + + @pytest.fixture def aperture_scatterguard(RE): positions = { - AperturePosition.LARGE: SingleAperturePosition( - location=ApertureFiveDimensionalLocation(0, 1, 2, 3, 4), - name="Large", - GDA_name="LARGE_APERTURE", - radius_microns=100, + ApertureValue.LARGE: AperturePosition( + aperture_x=0, + aperture_y=1, + aperture_z=2, + scatterguard_x=3, + scatterguard_y=4, + radius=100, ), - AperturePosition.MEDIUM: SingleAperturePosition( - location=ApertureFiveDimensionalLocation(5, 6, 2, 8, 9), - name="Medium", - GDA_name="MEDIUM_APERTURE", - radius_microns=50, + ApertureValue.MEDIUM: AperturePosition( + aperture_x=5, + aperture_y=6, + aperture_z=2, + scatterguard_x=8, + scatterguard_y=9, + radius=50, ), - AperturePosition.SMALL: SingleAperturePosition( - location=ApertureFiveDimensionalLocation(10, 11, 2, 13, 14), - name="Small", - GDA_name="SMALL_APERTURE", - radius_microns=20, + ApertureValue.SMALL: AperturePosition( + aperture_x=10, + aperture_y=11, + aperture_z=2, + scatterguard_x=13, + scatterguard_y=14, + radius=20, ), - AperturePosition.ROBOT_LOAD: SingleAperturePosition( - location=ApertureFiveDimensionalLocation(15, 16, 2, 18, 19), - name="Robot_load", - GDA_name="ROBOT_LOAD", - radius_microns=None, + ApertureValue.ROBOT_LOAD: AperturePosition( + aperture_x=15, + aperture_y=16, + aperture_z=2, + scatterguard_x=18, + scatterguard_y=19, + radius=None, ), } with ( @@ -490,21 +507,27 @@ def aperture_scatterguard(RE): return_value=positions, ), patch( - "dodal.beamlines.i03.load_tolerances_from_beamline_params", - return_value=ApertureScatterguardTolerances(0.1, 0.1, 0.1, 0.1, 0.1), + "dodal.beamlines.i03.AperturePosition.tolerances_from_gda_params", + return_value=AperturePosition( + aperture_x=0.1, + aperture_y=0.1, + aperture_z=0.1, + scatterguard_x=0.1, + scatterguard_y=0.1, + ), ), ): ap_sg = i03.aperture_scatterguard(fake_with_ophyd_sim=True) with ( - patch_async_motor(ap_sg._aperture.x), - patch_async_motor(ap_sg._aperture.y), - patch_async_motor(ap_sg._aperture.z, 2), - patch_async_motor(ap_sg._scatterguard.x), - patch_async_motor(ap_sg._scatterguard.y), + patch_async_motor(ap_sg.aperture.x), + patch_async_motor(ap_sg.aperture.y), + patch_async_motor(ap_sg.aperture.z, 2), + patch_async_motor(ap_sg.scatterguard.x), + patch_async_motor(ap_sg.scatterguard.y), ): - RE(bps.abs_set(ap_sg, AperturePosition.SMALL)) + RE(bps.abs_set(ap_sg, ApertureValue.SMALL)) - set_mock_value(ap_sg._aperture.small, 1) + set_mock_value(ap_sg.aperture.small, 1) yield ap_sg @@ -565,6 +588,7 @@ def fake_create_rotation_devices( dcm: DCM, robot: BartRobot, oav: OAV, + sample_shutter: ZebraShutter, ): set_mock_value(smargon.omega.max_velocity, 131) oav.zoom_controller.onst.sim_put("1.0x") # type: ignore @@ -585,6 +609,7 @@ def fake_create_rotation_devices( zebra=zebra, robot=robot, oav=oav, + sample_shutter=sample_shutter, ) @@ -699,6 +724,7 @@ async def fake_fgs_composite( panda=panda, panda_fast_grid_scan=i03.panda_fast_grid_scan(fake_with_ophyd_sim=True), robot=i03.robot(fake_with_ophyd_sim=True), + sample_shutter=i03.sample_shutter(fake_with_ophyd_sim=True), ) fake_composite.eiger.stage = MagicMock(return_value=done_status) @@ -860,5 +886,5 @@ def assert_events_and_data_in_order( @pytest.fixture def feature_flags(): return FeatureFlags( - **{field_name: False for field_name in FeatureFlags.__fields__.keys()} + **{field_name: False for field_name in FeatureFlags.model_fields.keys()} ) diff --git a/tests/system_tests/hyperion/experiment_plans/test_fgs_plan.py b/tests/system_tests/hyperion/experiment_plans/test_fgs_plan.py index ea25ca3f5..b23fe4203 100644 --- a/tests/system_tests/hyperion/experiment_plans/test_fgs_plan.py +++ b/tests/system_tests/hyperion/experiment_plans/test_fgs_plan.py @@ -8,7 +8,7 @@ import pytest_asyncio from bluesky.run_engine import RunEngine from dodal.beamlines import i03 -from dodal.devices.aperturescatterguard import AperturePosition +from dodal.devices.aperturescatterguard import ApertureValue from dodal.devices.smargon import Smargon from ophyd.sim import NullStatus from ophyd_async.core import set_mock_value @@ -94,13 +94,14 @@ async def fxc_composite(): xbpm_feedback=i03.xbpm_feedback(fake_with_ophyd_sim=True), zebra=i03.zebra(), zocalo=zocalo, + sample_shutter=i03.sample_shutter(fake_with_ophyd_sim=True), ) await composite.robot.barcode._backend.put("ABCDEFGHIJ") # type: ignore composite.dcm.energy_in_kev.user_readback.sim_put(12.345) # type: ignore - large = composite.aperture_scatterguard._loaded_positions[AperturePosition.LARGE] - await composite.aperture_scatterguard._set_raw_unsafe(large.location) + large = composite.aperture_scatterguard._loaded_positions[ApertureValue.LARGE] + await composite.aperture_scatterguard._set_raw_unsafe(large) composite.eiger.cam.manual_trigger.put("Yes") composite.eiger.odin.check_odin_initialised = lambda: (True, "") composite.eiger.stage = MagicMock(return_value=NullStatus()) @@ -179,7 +180,7 @@ def decorated_plan(): autospec=True, ) @patch( - "mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan.set_zebra_shutter_to_manual", + "mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan.tidy_up_zebra_after_gridscan", autospec=True, ) def test_full_plan_tidies_at_end( @@ -214,7 +215,7 @@ def test_full_plan_tidies_at_end( autospec=True, ) @patch( - "mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan.set_zebra_shutter_to_manual", + "mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan.tidy_up_zebra_after_gridscan", autospec=True, ) def test_full_plan_tidies_at_end_when_plan_fails( diff --git a/tests/system_tests/hyperion/experiment_plans/test_plan_system.py b/tests/system_tests/hyperion/experiment_plans/test_plan_system.py index c17b8f44c..f7f699c68 100644 --- a/tests/system_tests/hyperion/experiment_plans/test_plan_system.py +++ b/tests/system_tests/hyperion/experiment_plans/test_plan_system.py @@ -7,9 +7,9 @@ GDABeamlineParameters, ) from dodal.devices.aperturescatterguard import ( + AperturePosition, ApertureScatterguard, load_positions_from_beamline_parameters, - load_tolerances_from_beamline_params, ) from dodal.devices.s4_slit_gaps import S4SlitGaps from dodal.devices.undulator import Undulator @@ -36,7 +36,7 @@ async def test_getting_data_for_ispyb(): prefix="BL03S", name="ap_sg", loaded_positions=load_positions_from_beamline_parameters(params), - tolerances=load_tolerances_from_beamline_params(params), + tolerances=AperturePosition.tolerances_from_gda_params(params), ) smargon = i03.smargon(fake_with_ophyd_sim=True) eiger = i03.eiger(fake_with_ophyd_sim=True) diff --git a/tests/system_tests/hyperion/external_interaction/callbacks/test_external_callbacks.py b/tests/system_tests/hyperion/external_interaction/callbacks/test_external_callbacks.py index 5a7443784..47c6077ed 100644 --- a/tests/system_tests/hyperion/external_interaction/callbacks/test_external_callbacks.py +++ b/tests/system_tests/hyperion/external_interaction/callbacks/test_external_callbacks.py @@ -153,9 +153,7 @@ async def test_external_callbacks_handle_gridscan_ispyb_and_zocalo( RE = RE_with_external_callbacks test_fgs_params.zocalo_environment = "dev_artemis" - set_mock_value( - fake_fgs_composite.aperture_scatterguard._aperture.z.user_setpoint, 2 - ) + set_mock_value(fake_fgs_composite.aperture_scatterguard.aperture.z.user_setpoint, 2) fake_fgs_composite.eiger.unstage = MagicMock(return_value=done_status) # type: ignore fake_fgs_composite.smargon.stub_offsets.set = MagicMock(return_value=done_status) # type: ignore fake_fgs_composite.zocalo = zocalo_device @@ -204,6 +202,7 @@ def test_remote_callbacks_write_to_dev_ispyb_for_rotation( robot, aperture_scatterguard, fake_create_devices, + sample_shutter, ): test_wl = 0.71 test_bs_x = 0.023 @@ -230,6 +229,7 @@ def test_remote_callbacks_write_to_dev_ispyb_for_rotation( zebra=fake_create_devices["zebra"], robot=robot, oav=fake_create_devices["oav"], + sample_shutter=sample_shutter, ) with patch("bluesky.preprocessors.__read_and_stash_a_motor", fake_read): diff --git a/tests/system_tests/hyperion/external_interaction/test_ispyb_dev_connection.py b/tests/system_tests/hyperion/external_interaction/test_ispyb_dev_connection.py index a9429285d..676bbe8e7 100644 --- a/tests/system_tests/hyperion/external_interaction/test_ispyb_dev_connection.py +++ b/tests/system_tests/hyperion/external_interaction/test_ispyb_dev_connection.py @@ -42,9 +42,9 @@ ) from mx_bluesky.hyperion.external_interaction.ispyb.data_model import ( DataCollectionGridInfo, + Orientation, ScanDataInfo, ) -from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_dataclass import Orientation from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_store import ( IspybIds, StoreInIspyb, @@ -281,9 +281,6 @@ def grid_detect_then_xray_centre_composite( dcm=dcm, flux=flux, ) - eiger.odin.fan.consumers_connected.sim_put(True) - eiger.odin.fan.on.sim_put(True) - eiger.odin.meta.initialised.sim_put(True) oav.zoom_controller.zrst.set("1.0x") oav.cam.array_size.array_size_x.sim_put(1024) oav.cam.array_size.array_size_y.sim_put(768) @@ -295,10 +292,6 @@ def grid_detect_then_xray_centre_composite( set_mock_value(undulator.current_gap, 1.11) unpatched_method = oav.parameters.load_microns_per_pixel - eiger.stale_params.sim_put(0) - eiger.odin.meta.ready.sim_put(1) - eiger.odin.meta.active.sim_put(1) - eiger.odin.fan.ready.sim_put(1) unpatched_snapshot_trigger = oav.grid_snapshot.trigger @@ -339,17 +332,11 @@ def mock_pin_tip_detect(_): yield from [] return tip_x_px, tip_y_px - def mock_set_file_name(val, timeout): - eiger.odin.meta.file_name.sim_put(val) # type: ignore - eiger.odin.file_writer.id.sim_put(val) # type: ignore - return NullStatus() - @AsyncStatus.wrap async def mock_complete_status(): pass with ( - patch.object(eiger.odin.nodes, "get_init_state", return_value=True), patch.object(eiger, "wait_on_arming_if_started"), # xsize, ysize will always be wrong since computed as 0 before we get here # patch up load_microns_per_pixel connect to receive non-zero values @@ -367,11 +354,6 @@ async def mock_complete_status(): patch("dodal.devices.areadetector.plugins.MJPG.Image.open"), patch.object(oav.grid_snapshot, "post_processing"), patch.object(oav.grid_snapshot, "trigger", side_effect=mock_snapshot_trigger), - patch.object( - eiger.odin.file_writer.file_name, - "set", - side_effect=mock_set_file_name, - ), patch.object(fast_grid_scan, "kickoff", return_value=NullStatus()), patch.object(fast_grid_scan, "complete", return_value=NullStatus()), patch.object(zocalo, "trigger", return_value=NullStatus()), @@ -413,7 +395,6 @@ def scan_data_infos_for_update_3d( dummy_params.detector_params ) - assert dummy_params.ispyb_params is not None assert dummy_params is not None data_collection_grid_info = DataCollectionGridInfo( dx_in_mm=dummy_params.x_step_size_um, diff --git a/tests/system_tests/hyperion/external_interaction/test_nexgen.py b/tests/system_tests/hyperion/external_interaction/test_nexgen.py index 3d6247fa0..b8f07ab4a 100644 --- a/tests/system_tests/hyperion/external_interaction/test_nexgen.py +++ b/tests/system_tests/hyperion/external_interaction/test_nexgen.py @@ -157,7 +157,7 @@ def _fake_rotation_scan( @bpp.run_decorator( # attach experiment metadata to the start document md={ "subplan_name": CONST.PLAN.ROTATION_OUTER, - "hyperion_parameters": parameters.json(), + "hyperion_parameters": parameters.model_dump_json(), "activate_callbacks": "RotationNexusFileCallback", } ) diff --git a/tests/system_tests/hyperion/external_interaction/test_zocalo_system.py b/tests/system_tests/hyperion/external_interaction/test_zocalo_system.py index e96f17de0..f8a54d2c9 100644 --- a/tests/system_tests/hyperion/external_interaction/test_zocalo_system.py +++ b/tests/system_tests/hyperion/external_interaction/test_zocalo_system.py @@ -75,7 +75,7 @@ def trigger_zocalo_after_fast_grid_scan(): md={ "subplan_name": CONST.PLAN.GRIDSCAN_OUTER, CONST.TRIGGER.ZOCALO: CONST.PLAN.DO_FGS, - "hyperion_parameters": dummy_params.json(), + "hyperion_parameters": dummy_params.model_dump_json(), } ) def inner_plan(): diff --git a/tests/system_tests/hyperion/test_aperturescatterguard_system.py b/tests/system_tests/hyperion/test_aperturescatterguard_system.py index 6e51bb774..e50999e3c 100644 --- a/tests/system_tests/hyperion/test_aperturescatterguard_system.py +++ b/tests/system_tests/hyperion/test_aperturescatterguard_system.py @@ -4,9 +4,9 @@ GDABeamlineParameters, ) from dodal.devices.aperturescatterguard import ( + AperturePosition, ApertureScatterguard, load_positions_from_beamline_parameters, - load_tolerances_from_beamline_params, ) from ophyd_async.core import DeviceCollector @@ -19,7 +19,7 @@ def ap_sg(): prefix="BL03S", name="ap_sg", loaded_positions=load_positions_from_beamline_parameters(params), - tolerances=load_tolerances_from_beamline_params(params), + tolerances=AperturePosition.tolerances_from_gda_params(params), ) return ap_sg diff --git a/tests/system_tests/hyperion/test_device_setups_and_cleanups.py b/tests/system_tests/hyperion/test_device_setups_and_cleanups.py index cbec7448b..2500bc0f8 100644 --- a/tests/system_tests/hyperion/test_device_setups_and_cleanups.py +++ b/tests/system_tests/hyperion/test_device_setups_and_cleanups.py @@ -12,9 +12,9 @@ ) from mx_bluesky.hyperion.device_setup_plans.setup_zebra import ( - set_zebra_shutter_to_manual, setup_zebra_for_gridscan, setup_zebra_for_rotation, + tidy_up_zebra_after_gridscan, ) @@ -42,6 +42,6 @@ async def test_zebra_set_up_for_rotation(RE, connected_zebra: Zebra): @pytest.mark.s03 async def test_zebra_cleanup(RE, connected_zebra: Zebra): - RE(set_zebra_shutter_to_manual(connected_zebra, wait=True)) + RE(tidy_up_zebra_after_gridscan(connected_zebra, wait=True)) assert await connected_zebra.output.out_pvs[TTL_DETECTOR].get_value() == PC_PULSE assert await connected_zebra.output.out_pvs[TTL_SHUTTER].get_value() == OR1 diff --git a/tests/unit_tests/beamlines/i04/test_thawing.py b/tests/unit_tests/beamlines/i04/test_thawing.py index 6030a0223..32f7df699 100644 --- a/tests/unit_tests/beamlines/i04/test_thawing.py +++ b/tests/unit_tests/beamlines/i04/test_thawing.py @@ -7,18 +7,20 @@ from dodal.beamlines import i04 from dodal.devices.oav.oav_detector import OAV from dodal.devices.oav.oav_to_redis_forwarder import OAVToRedisForwarder +from dodal.devices.robot import BartRobot from dodal.devices.smargon import Smargon from dodal.devices.thawer import Thawer, ThawerStates from ophyd.sim import NullStatus, instantiate_fake_device from ophyd_async.core import ( + AsyncStatus, DeviceCollector, callback_on_mock_put, get_mock_put, set_mock_value, ) -from ophyd_async.epics.motion import Motor +from ophyd_async.epics.motor import Motor -from mx_bluesky.beamlines.i04.thawing_plan import thaw, thaw_and_center +from mx_bluesky.beamlines.i04.thawing_plan import thaw, thaw_and_stream_to_redis DISPLAY_CONFIGURATION = "tests/devices/unit_tests/test_display.configuration" ZOOM_LEVELS_XML = "tests/devices/unit_tests/test_jCameraManZoomLevels.xml" @@ -73,9 +75,22 @@ async def oav_forwarder(RE: RunEngine) -> OAVToRedisForwarder: oav_forwarder = OAVToRedisForwarder( "prefix", "host", "password", name="oav_to_redis_forwarder" ) + + # Replace when https://github.com/bluesky/ophyd-async/issues/521 is released + @AsyncStatus.wrap + async def completed_status(): + pass + + oav_forwarder.kickoff = MagicMock(side_effect=completed_status) + oav_forwarder.complete = MagicMock(side_effect=completed_status) return oav_forwarder +@pytest.fixture +async def robot(RE: RunEngine) -> BartRobot: + return i04.robot(fake_with_ophyd_sim=True) + + def _do_thaw_and_confirm_cleanup( move_mock: MagicMock, smargon: Smargon, thawer: Thawer, do_thaw_func ): @@ -149,7 +164,7 @@ def test_given_different_rotations_then_motor_moved_relative( expected_end: float, RE: RunEngine, ): - set_mock_value(smargon.omega.user_readback, start_pos) + set_mock_value(smargon.omega.user_setpoint, start_pos) RE(thaw(10, rotation, thawer=thawer, smargon=smargon)) assert get_mock_put(smargon.omega.user_setpoint).call_args_list == [ call(expected_end, wait=ANY, timeout=ANY), @@ -158,7 +173,34 @@ def test_given_different_rotations_then_motor_moved_relative( @patch("mx_bluesky.beamlines.i04.thawing_plan.MurkoCallback") -def test_thaw_and_centre_adds_murko_callback_and_produces_expected_messages( +async def test_thaw_and_stream_sets_sample_id_and_kicks_off_forwarder( + patch_murko_callback: MagicMock, + smargon: Smargon, + thawer: Thawer, + oav_forwarder: OAVToRedisForwarder, + oav: OAV, + robot: BartRobot, + RE: RunEngine, +): + set_mock_value(robot.sample_id, 100) + RE( + thaw_and_stream_to_redis( + 10, + 360, + thawer=thawer, + smargon=smargon, + oav=oav, + robot=robot, + oav_to_redis_forwarder=oav_forwarder, + ) + ) + assert await oav_forwarder.sample_id.get_value() == 100 + oav_forwarder.kickoff.assert_called_once() # type: ignore + oav_forwarder.complete.assert_called_once() # type: ignore + + +@patch("mx_bluesky.beamlines.i04.thawing_plan.MurkoCallback") +def test_thaw_and_stream_adds_murko_callback_and_produces_expected_messages( patch_murko_callback: MagicMock, smargon: Smargon, thawer: Thawer, @@ -168,7 +210,7 @@ def test_thaw_and_centre_adds_murko_callback_and_produces_expected_messages( ): patch_murko_instance = patch_murko_callback.return_value RE( - thaw_and_center( + thaw_and_stream_to_redis( 10, 360, thawer=thawer, @@ -193,7 +235,7 @@ def test_thaw_and_centre_adds_murko_callback_and_produces_expected_messages( @patch("mx_bluesky.beamlines.i04.thawing_plan.MurkoCallback.call_murko") -def test_thaw_and_centre_will_produce_events_that_call_murko( +def test_thaw_and_stream_will_produce_events_that_call_murko( patch_murko_call: MagicMock, smargon: Smargon, thawer: Thawer, @@ -202,7 +244,7 @@ def test_thaw_and_centre_will_produce_events_that_call_murko( RE: RunEngine, ): RE( - thaw_and_center( + thaw_and_stream_to_redis( 10, 360, thawer=thawer, diff --git a/tests/unit_tests/beamlines/i24/serial/conftest.py b/tests/unit_tests/beamlines/i24/serial/conftest.py index cc5aa1e6b..0734e7895 100644 --- a/tests/unit_tests/beamlines/i24/serial/conftest.py +++ b/tests/unit_tests/beamlines/i24/serial/conftest.py @@ -17,7 +17,7 @@ from dodal.devices.i24.pmac import PMAC from dodal.devices.zebra import Zebra from ophyd_async.core import callback_on_mock_put, set_mock_value -from ophyd_async.epics.motion import Motor +from ophyd_async.epics.motor import Motor def patch_motor(motor: Motor, initial_position: float = 0): diff --git a/tests/unit_tests/beamlines/i24/serial/fixed_target/conftest.py b/tests/unit_tests/beamlines/i24/serial/fixed_target/conftest.py index 84a7d145a..c0fe2b574 100644 --- a/tests/unit_tests/beamlines/i24/serial/fixed_target/conftest.py +++ b/tests/unit_tests/beamlines/i24/serial/fixed_target/conftest.py @@ -18,7 +18,7 @@ def dummy_params_without_pp(): "detector_distance_mm": 100, "detector_name": "eiger", "num_exposures": 1, - "chip": oxford_defaults.dict(), + "chip": oxford_defaults.model_dump(), "map_type": 1, "pump_repeat": 0, "checker_pattern": False, diff --git a/tests/unit_tests/beamlines/i24/serial/fixed_target/test_ft_collect.py b/tests/unit_tests/beamlines/i24/serial/fixed_target/test_ft_collect.py index 618d707d5..28c561477 100644 --- a/tests/unit_tests/beamlines/i24/serial/fixed_target/test_ft_collect.py +++ b/tests/unit_tests/beamlines/i24/serial/fixed_target/test_ft_collect.py @@ -93,7 +93,11 @@ def test_get_prog_number(chip_type, map_type, pump_repeat, expected_prog): ), # Map irrelevant, pp to Medium1, checker disabled ], ) +@patch( + "mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Collect_py3v1.bps.sleep" +) def test_load_motion_program_data( + mock_sleep, map_type: int, pump_repeat: int, checker: bool, @@ -205,7 +209,10 @@ def test_finish_i24( @patch("mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Collect_py3v1.DCID") -def test_run_aborted_plan(fake_dcid: MagicMock, pmac: PMAC, RE): +@patch( + "mx_bluesky.beamlines.i24.serial.fixed_target.i24ssx_Chip_Collect_py3v1.bps.sleep" +) +def test_run_aborted_plan(mock_sleep, fake_dcid: MagicMock, pmac: PMAC, RE): RE(run_aborted_plan(pmac, fake_dcid)) mock_pmac_string = get_mock_put(pmac.pmac_string) diff --git a/tests/unit_tests/beamlines/i24/serial/parameters/test_utils.py b/tests/unit_tests/beamlines/i24/serial/parameters/test_utils.py index a0690dd79..463bc8d3c 100644 --- a/tests/unit_tests/beamlines/i24/serial/parameters/test_utils.py +++ b/tests/unit_tests/beamlines/i24/serial/parameters/test_utils.py @@ -16,7 +16,7 @@ def test_get_chip_format_for_oxford_chips( expected_step_size: float, expected_num_steps: int, ): - test_defaults = get_chip_format(ChipType(chip_type)).dict() + test_defaults = get_chip_format(ChipType(chip_type)).model_dump() assert ( test_defaults["x_num_steps"] == expected_num_steps @@ -30,7 +30,7 @@ def test_get_chip_format_for_oxford_chips( def test_get_chip_format_for_custom_chips(fake_caget: MagicMock): fake_caget.side_effect = ["10", "2", "0.2", "0.2"] test_chip_type = ChipType(2) - test_defaults = get_chip_format(test_chip_type).dict() + test_defaults = get_chip_format(test_chip_type).model_dump() assert test_defaults["x_num_steps"] == 10 assert test_defaults["y_num_steps"] == 2 diff --git a/tests/unit_tests/beamlines/i24/serial/setup_beamline/test_setup_beamline.py b/tests/unit_tests/beamlines/i24/serial/setup_beamline/test_setup_beamline.py index c39085ddb..694b210b6 100644 --- a/tests/unit_tests/beamlines/i24/serial/setup_beamline/test_setup_beamline.py +++ b/tests/unit_tests/beamlines/i24/serial/setup_beamline/test_setup_beamline.py @@ -9,8 +9,9 @@ from mx_bluesky.beamlines.i24.serial.setup_beamline import setup_beamline +@patch("mx_bluesky.beamlines.i24.serial.setup_beamline.setup_beamline.bps.sleep") async def test_setup_beamline_for_collection_plan( - aperture: Aperture, backlight: DualBacklight, beamstop: Beamstop, RE + _, aperture: Aperture, backlight: DualBacklight, beamstop: Beamstop, RE ): RE(setup_beamline.setup_beamline_for_collection_plan(aperture, backlight, beamstop)) @@ -37,7 +38,8 @@ def test_pilatus_raises_error_if_fastchip_and_no_args_list(fake_caget, fake_capu @patch("mx_bluesky.beamlines.i24.serial.setup_beamline.setup_beamline.caput") @patch("mx_bluesky.beamlines.i24.serial.setup_beamline.setup_beamline.caget") -def test_pilatus_quickshot(fake_caget, fake_caput): +@patch("mx_bluesky.beamlines.i24.serial.setup_beamline.setup_beamline.sleep") +def test_pilatus_quickshot(_, fake_caget, fake_caput): setup_beamline.pilatus("quickshot", ["", "", 1, 0.1]) assert fake_caput.call_count == 12 assert fake_caget.call_count == 2 @@ -52,6 +54,7 @@ def test_eiger_raises_error_if_quickshot_and_no_args_list(fake_caget, fake_caput @patch("mx_bluesky.beamlines.i24.serial.setup_beamline.setup_beamline.caput") @patch("mx_bluesky.beamlines.i24.serial.setup_beamline.setup_beamline.caget") -def test_eiger_quickshot(fake_caget, fake_caput): +@patch("mx_bluesky.beamlines.i24.serial.setup_beamline.setup_beamline.sleep") +def test_eiger_quickshot(_, fake_caget, fake_caput): setup_beamline.eiger("quickshot", ["", "", "1", "0.1"]) assert fake_caput.call_count == 32 diff --git a/tests/unit_tests/hyperion/device_setup_plans/test_manipulate_sample.py b/tests/unit_tests/hyperion/device_setup_plans/test_manipulate_sample.py index eaa9152f9..232b1e471 100644 --- a/tests/unit_tests/hyperion/device_setup_plans/test_manipulate_sample.py +++ b/tests/unit_tests/hyperion/device_setup_plans/test_manipulate_sample.py @@ -1,11 +1,7 @@ from unittest.mock import patch from bluesky.run_engine import RunEngine -from dodal.devices.aperturescatterguard import ( - AperturePosition, - AperturePositionGDANames, - ApertureScatterguard, -) +from dodal.devices.aperturescatterguard import ApertureScatterguard, ApertureValue from mx_bluesky.hyperion.device_setup_plans.manipulate_sample import ( move_aperture_if_required, @@ -16,13 +12,9 @@ async def test_move_aperture_goes_to_correct_position( aperture_scatterguard: ApertureScatterguard, RE: RunEngine ): with patch.object(aperture_scatterguard, "set") as mock_set: - RE( - move_aperture_if_required( - aperture_scatterguard, AperturePositionGDANames.LARGE_APERTURE - ) - ) + RE(move_aperture_if_required(aperture_scatterguard, ApertureValue.LARGE)) mock_set.assert_called_once_with( - AperturePosition.LARGE, + ApertureValue.LARGE, ) diff --git a/tests/unit_tests/hyperion/device_setup_plans/test_setup_panda.py b/tests/unit_tests/hyperion/device_setup_plans/test_setup_panda.py index 20821726d..3c347fd38 100644 --- a/tests/unit_tests/hyperion/device_setup_plans/test_setup_panda.py +++ b/tests/unit_tests/hyperion/device_setup_plans/test_setup_panda.py @@ -7,9 +7,9 @@ from bluesky.plan_stubs import null from bluesky.run_engine import RunEngine from bluesky.simulators import RunEngineSimulator, assert_message_and_return_remaining -from dodal.common.types import UpdatingDirectoryProvider +from dodal.common.types import UpdatingPathProvider from dodal.devices.fast_grid_scan import PandAGridScanParams -from ophyd_async.panda import SeqTrigger +from ophyd_async.fastcs.panda import SeqTrigger from mx_bluesky.hyperion.device_setup_plans.setup_panda import ( MM_TO_ENCODER_COUNTS, @@ -229,16 +229,16 @@ def test_disarm_panda_disables_correct_blocks(sim_run_engine): assert num_of_waits == 1 -@patch("mx_bluesky.hyperion.device_setup_plans.setup_panda.get_directory_provider") +@patch("mx_bluesky.hyperion.device_setup_plans.setup_panda.get_path_provider") @patch("mx_bluesky.hyperion.device_setup_plans.setup_panda.datetime", spec=datetime) def test_set_panda_directory( - mock_datetime, mock_get_directory_provider: MagicMock, tmp_path, RE + mock_datetime, mock_get_path_provider: MagicMock, tmp_path, RE ): - mock_directory_provider = MagicMock(spec=UpdatingDirectoryProvider) + mock_directory_provider = MagicMock(spec=UpdatingPathProvider) mock_datetime.now = MagicMock( return_value=datetime.fromisoformat("2024-08-11T15:59:23") ) - mock_get_directory_provider.return_value = mock_directory_provider + mock_get_path_provider.return_value = mock_directory_provider RE(set_panda_directory(tmp_path)) mock_directory_provider.update.assert_called_with( diff --git a/tests/unit_tests/hyperion/device_setup_plans/test_zebra_setup.py b/tests/unit_tests/hyperion/device_setup_plans/test_zebra_setup.py index d7056ce70..e2ccf0338 100644 --- a/tests/unit_tests/hyperion/device_setup_plans/test_zebra_setup.py +++ b/tests/unit_tests/hyperion/device_setup_plans/test_zebra_setup.py @@ -2,49 +2,74 @@ import pytest from bluesky import plan_stubs as bps -from bluesky.run_engine import RunEngine from dodal.beamlines import i03 from dodal.devices.zebra import ( + AUTO_SHUTTER_GATE, + AUTO_SHUTTER_INPUT, + IN1_TTL, IN3_TTL, IN4_TTL, - OR1, + PC_GATE, PC_PULSE, TTL_DETECTOR, - TTL_SHUTTER, + TTL_PANDA, I03Axes, Zebra, ) +from dodal.devices.zebra_controlled_shutter import ZebraShutter from mx_bluesky.hyperion.device_setup_plans.setup_zebra import ( bluesky_retry, - set_zebra_shutter_to_manual, setup_zebra_for_gridscan, + setup_zebra_for_panda_flyscan, setup_zebra_for_rotation, + tidy_up_zebra_after_gridscan, ) @pytest.fixture -def zebra(): - RunEngine() +def zebra(RE): return i03.zebra(fake_with_ophyd_sim=True) -async def test_zebra_set_up_for_gridscan(RE, zebra: Zebra): - RE(setup_zebra_for_gridscan(zebra, wait=True)) +@pytest.fixture +def zebra_shutter(RE): + return i03.sample_shutter(fake_with_ophyd_sim=True) + + +async def _get_shutter_input(zebra: Zebra): + return ( + await zebra.logic_gates.and_gates[AUTO_SHUTTER_GATE] + .sources[AUTO_SHUTTER_INPUT] + .get_value() + ) + + +async def test_zebra_set_up_for_panda_gridscan( + RE, zebra: Zebra, zebra_shutter: ZebraShutter +): + RE(setup_zebra_for_panda_flyscan(zebra, zebra_shutter, wait=True)) + assert await zebra.output.out_pvs[TTL_DETECTOR].get_value() == IN1_TTL + assert await zebra.output.out_pvs[TTL_PANDA].get_value() == IN3_TTL + assert await _get_shutter_input(zebra) == IN4_TTL + + +async def test_zebra_set_up_for_gridscan(RE, zebra: Zebra, zebra_shutter: ZebraShutter): + RE(setup_zebra_for_gridscan(zebra, zebra_shutter, wait=True)) assert await zebra.output.out_pvs[TTL_DETECTOR].get_value() == IN3_TTL - assert await zebra.output.out_pvs[TTL_SHUTTER].get_value() == IN4_TTL + assert await _get_shutter_input(zebra) == IN4_TTL -async def test_zebra_set_up_for_rotation(RE, zebra: Zebra): - RE(setup_zebra_for_rotation(zebra, wait=True)) +async def test_zebra_set_up_for_rotation(RE, zebra: Zebra, zebra_shutter: ZebraShutter): + RE(setup_zebra_for_rotation(zebra, zebra_shutter, wait=True)) assert await zebra.pc.gate_trigger.get_value() == I03Axes.OMEGA.value assert await zebra.pc.gate_width.get_value() == pytest.approx(360, 0.01) -async def test_zebra_cleanup(RE, zebra: Zebra): - RE(set_zebra_shutter_to_manual(zebra, wait=True)) +async def test_zebra_cleanup(RE, zebra: Zebra, zebra_shutter: ZebraShutter): + RE(tidy_up_zebra_after_gridscan(zebra, zebra_shutter, wait=True)) assert await zebra.output.out_pvs[TTL_DETECTOR].get_value() == PC_PULSE - assert await zebra.output.out_pvs[TTL_SHUTTER].get_value() == OR1 + assert await _get_shutter_input(zebra) == PC_GATE class MyException(Exception): diff --git a/tests/unit_tests/hyperion/experiment_plans/conftest.py b/tests/unit_tests/hyperion/experiment_plans/conftest.py index 78075a63c..25d4b0750 100644 --- a/tests/unit_tests/hyperion/experiment_plans/conftest.py +++ b/tests/unit_tests/hyperion/experiment_plans/conftest.py @@ -4,13 +4,13 @@ import pytest from bluesky.utils import Msg +from dodal.devices.aperturescatterguard import ApertureValue from dodal.devices.fast_grid_scan import ZebraFastGridScan from dodal.devices.oav.oav_detector import OAVConfigParams from dodal.devices.synchrotron import SynchrotronMode from dodal.devices.zocalo import ZocaloResults, ZocaloTrigger from event_model import Event -from ophyd_async.core import DeviceCollector -from ophyd_async.core.async_status import AsyncStatus +from ophyd_async.core import AsyncStatus, DeviceCollector from mx_bluesky.hyperion.external_interaction.callbacks.common.callback_util import ( create_gridscan_callbacks, @@ -48,12 +48,13 @@ def make_event_doc(data, descriptor="abc123") -> Event: } BASIC_POST_SETUP_DOC = { - "aperture_scatterguard-selected_aperture": { - "name": "Robot_load", - "GDA_name": "ROBOT_LOAD", - "radius_microns": None, - "location": (15, 16, 2, 18, 19), - }, + "aperture_scatterguard-selected_aperture": ApertureValue.ROBOT_LOAD, + "aperture_scatterguard-radius": None, + "aperture_scatterguard-aperture-x": 15, + "aperture_scatterguard-aperture-y": 16, + "aperture_scatterguard-aperture-z": 2, + "aperture_scatterguard-scatterguard-x": 18, + "aperture_scatterguard-scatterguard-y": 19, "attenuator-actual_transmission": 0, "flux_flux_reading": 10, "dcm-energy_in_kev": 11.105, @@ -97,7 +98,7 @@ def run_generic_ispyb_handler_setup( ispyb_handler.activity_gated_start( { "subplan_name": CONST.PLAN.GRIDSCAN_OUTER, - "hyperion_parameters": params.json(), + "hyperion_parameters": params.model_dump_json(), } # type: ignore ) ispyb_handler.activity_gated_descriptor( diff --git a/tests/unit_tests/hyperion/experiment_plans/test_flyscan_xray_centre_plan.py b/tests/unit_tests/hyperion/experiment_plans/test_flyscan_xray_centre_plan.py index 624f61e95..223add5b1 100644 --- a/tests/unit_tests/hyperion/experiment_plans/test_flyscan_xray_centre_plan.py +++ b/tests/unit_tests/hyperion/experiment_plans/test_flyscan_xray_centre_plan.py @@ -12,7 +12,7 @@ from bluesky.utils import FailedStatus, Msg from dodal.beamlines import i03 from dodal.common.beamlines.beamline_utils import clear_device -from dodal.devices.aperturescatterguard import AperturePosition +from dodal.devices.aperturescatterguard import AperturePosition, ApertureValue from dodal.devices.detector.det_dim_constants import ( EIGER_TYPE_EIGER2_X_16M, ) @@ -22,7 +22,7 @@ from ophyd.sim import NullStatus from ophyd.status import Status from ophyd_async.core import set_mock_value -from ophyd_async.panda._table import DatasetTable +from ophyd_async.fastcs.panda import DatasetTable from mx_bluesky.hyperion.device_setup_plans.read_hardware_for_setup import ( read_hardware_during_collection, @@ -120,7 +120,7 @@ def ispyb_plan(test_fgs_params: ThreeDGridScan): @bpp.run_decorator( # attach experiment metadata to the start document md={ "subplan_name": CONST.PLAN.GRIDSCAN_OUTER, - "hyperion_parameters": test_fgs_params.json(), + "hyperion_parameters": test_fgs_params.model_dump_json(), } ) def standalone_read_hardware_for_ispyb( @@ -234,12 +234,14 @@ def test_read_hardware_for_ispyb_updates_from_ophyd_devices( xgap_test_value = 0.1234 ygap_test_value = 0.2345 - ap_sg_test_value = { - "name": "Small", - "GDA_name": "SMALL_APERTURE", - "radius_microns": 20, - "location": (10, 11, 2, 13, 14), - } + ap_sg_test_value = AperturePosition( + aperture_x=10, + aperture_y=11, + aperture_z=2, + scatterguard_x=13, + scatterguard_y=14, + radius=20, + ) fake_fgs_composite.s4_slit_gaps.xgap.user_readback.sim_put(xgap_test_value) # type: ignore fake_fgs_composite.s4_slit_gaps.ygap.user_readback.sim_put(ygap_test_value) # type: ignore flux_test_value = 10.0 @@ -248,7 +250,7 @@ def test_read_hardware_for_ispyb_updates_from_ophyd_devices( RE( bps.abs_set( fake_fgs_composite.aperture_scatterguard, - AperturePosition.LARGE, + ApertureValue.SMALL, ) ) @@ -296,7 +298,13 @@ def test_read_hardware_for_ispyb_updates_from_ophyd_devices( assert_event( test_ispyb_callback.activity_gated_event.mock_calls[1], # pyright: ignore { - 'aperture_scatterguard-selected_aperture': ap_sg_test_value, + "aperture_scatterguard-selected_aperture": ApertureValue.SMALL, + "aperture_scatterguard-aperture-x": ap_sg_test_value.aperture_x, + "aperture_scatterguard-aperture-y": ap_sg_test_value.aperture_y, + "aperture_scatterguard-aperture-z": ap_sg_test_value.aperture_z, + "aperture_scatterguard-scatterguard-x": ap_sg_test_value.scatterguard_x, + "aperture_scatterguard-scatterguard-y": ap_sg_test_value.scatterguard_y, + "aperture_scatterguard-radius": ap_sg_test_value.radius, "attenuator-actual_transmission": transmission_test_value, "flux_flux_reading": flux_test_value, "dcm-energy_in_kev": current_energy_kev_test_value, @@ -360,10 +368,10 @@ def test_results_adjusted_and_passed_to_move_xyz( ) aperture_scatterguard = fgs_composite_with_panda_pcap.aperture_scatterguard - large = aperture_scatterguard._loaded_positions[AperturePosition.LARGE] - medium = aperture_scatterguard._loaded_positions[AperturePosition.MEDIUM] - ap_call_large = call(large.location) - ap_call_medium = call(medium.location) + large = aperture_scatterguard._loaded_positions[ApertureValue.LARGE] + medium = aperture_scatterguard._loaded_positions[ApertureValue.MEDIUM] + ap_call_large = call(large, ApertureValue.LARGE) + ap_call_medium = call(medium, ApertureValue.MEDIUM) move_aperture.assert_has_calls( [ap_call_large, ap_call_large, ap_call_medium], any_order=True @@ -673,8 +681,13 @@ def wrapped_gridscan_and_move(): "mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan.move_x_y_z", autospec=True, ) + @patch( + "mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan.check_topup_and_wait_if_necessary", + autospec=True, + ) def test_GIVEN_no_results_from_zocalo_WHEN_communicator_wait_for_results_called_THEN_fallback_centre_used( self, + mock_topup, move_xyz: MagicMock, mock_mv: MagicMock, mock_kickoff: MagicMock, @@ -869,8 +882,13 @@ def test_GIVEN_scan_not_valid_THEN_wait_for_GRIDSCAN_raises_and_sleeps_called( autospec=True, spec_set=True, ) + @patch( + "mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan.check_topup_and_wait_if_necessary", + autospec=True, + ) def test_when_grid_scan_ran_then_eiger_disarmed_before_zocalo_end( self, + mock_check_topup, nexuswriter, wait_for_valid, mock_mv, @@ -985,8 +1003,13 @@ def test_flyscan_xray_centre_sets_directory_stages_arms_disarms_unstages_the_pan "mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan.bps.kickoff", autospec=True, ) + @patch( + "mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan.check_topup_and_wait_if_necessary", + autospec=True, + ) def test_fgs_arms_eiger_without_grid_detect( self, + mock_topup, mock_kickoff, mock_complete, mock_wait, @@ -1019,8 +1042,13 @@ def test_fgs_arms_eiger_without_grid_detect( "mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan.bps.complete", autospec=True, ) + @patch( + "mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan.check_topup_and_wait_if_necessary", + autospec=True, + ) def test_when_grid_scan_fails_then_detector_disarmed_and_correct_exception_returned( self, + mock_topup, mock_complete, mock_wait, mock_kickoff, @@ -1072,8 +1100,13 @@ class CompleteException(Exception): "mx_bluesky.hyperion.external_interaction.callbacks.zocalo_callback.ZocaloTrigger", autospec=True, ) + @patch( + "mx_bluesky.hyperion.experiment_plans.flyscan_xray_centre_plan.check_topup_and_wait_if_necessary", + autospec=True, + ) def test_kickoff_and_complete_gridscan_triggers_zocalo( self, + mock_topup, mock_zocalo_trigger_class: MagicMock, mock_complete: MagicMock, mock_kickoff: MagicMock, diff --git a/tests/unit_tests/hyperion/experiment_plans/test_grid_detect_then_xray_centre_plan.py b/tests/unit_tests/hyperion/experiment_plans/test_grid_detect_then_xray_centre_plan.py index ea088621e..30cece989 100644 --- a/tests/unit_tests/hyperion/experiment_plans/test_grid_detect_then_xray_centre_plan.py +++ b/tests/unit_tests/hyperion/experiment_plans/test_grid_detect_then_xray_centre_plan.py @@ -8,7 +8,7 @@ from bluesky.simulators import RunEngineSimulator, assert_message_and_return_remaining from bluesky.utils import Msg from dodal.beamlines import i03 -from dodal.devices.aperturescatterguard import AperturePosition +from dodal.devices.aperturescatterguard import ApertureValue from dodal.devices.backlight import BacklightPosition from dodal.devices.eiger import EigerDetector from dodal.devices.oav.oav_detector import OAVConfigParams @@ -88,6 +88,7 @@ def grid_detect_devices( panda_fast_grid_scan=MagicMock(), dcm=MagicMock(), robot=MagicMock(), + sample_shutter=MagicMock(), ) @@ -164,7 +165,7 @@ async def test_detect_grid_and_do_gridscan( ) # Check aperture was changed to SMALL - mock_aperture_scatterguard.assert_called_once_with(AperturePosition.SMALL) + mock_aperture_scatterguard.assert_called_once_with(ApertureValue.SMALL) # Check we called out to underlying fast grid scan plan mock_flyscan_xray_centre_plan.assert_called_once_with(ANY, ANY) @@ -192,13 +193,10 @@ def test_when_full_grid_scan_run_then_parameters_sent_to_fgs_as_expected( mock_grid_detection_plan.side_effect = _fake_grid_detection - with ( - patch.object(eiger.do_arm, "set", MagicMock()), - patch.object( - grid_detect_devices_with_oav_config_params.aperture_scatterguard, - "set", - MagicMock(), - ), + with patch.object( + grid_detect_devices_with_oav_config_params.aperture_scatterguard, + "set", + MagicMock(), ): RE( ispyb_activation_wrapper( @@ -219,7 +217,7 @@ def test_when_full_grid_scan_run_then_parameters_sent_to_fgs_as_expected( assert params.FGS_params.y_axis.end == pytest.approx(1.511, 0.001) # Parameters can be serialized - params.json() + params.model_dump_json() @patch( diff --git a/tests/unit_tests/hyperion/experiment_plans/test_grid_detection_plan.py b/tests/unit_tests/hyperion/experiment_plans/test_grid_detection_plan.py index caf49d310..f84033744 100644 --- a/tests/unit_tests/hyperion/experiment_plans/test_grid_detection_plan.py +++ b/tests/unit_tests/hyperion/experiment_plans/test_grid_detection_plan.py @@ -73,9 +73,10 @@ def fake_devices( oav.zoom_controller.fvst.set("9.0x") with ( - patch("dodal.devices.areadetector.plugins.MJPG.requests"), + patch("dodal.devices.areadetector.plugins.MJPG.requests") as patch_requests, patch("dodal.devices.areadetector.plugins.MJPG.Image") as mock_image_class, ): + patch_requests.get.return_value.content = b"" mock_image = MagicMock() mock_image_class.open.return_value.__enter__.return_value = mock_image diff --git a/tests/unit_tests/hyperion/experiment_plans/test_multi_rotation_scan_plan.py b/tests/unit_tests/hyperion/experiment_plans/test_multi_rotation_scan_plan.py index e8ca6417b..b8b7b36d0 100644 --- a/tests/unit_tests/hyperion/experiment_plans/test_multi_rotation_scan_plan.py +++ b/tests/unit_tests/hyperion/experiment_plans/test_multi_rotation_scan_plan.py @@ -149,7 +149,12 @@ def _run_multi_rotation_plan( RE(multi_rotation_scan(devices, params, oav_params)) +@patch( + "mx_bluesky.hyperion.experiment_plans.rotation_scan_plan.check_topup_and_wait_if_necessary", + autospec=True, +) def test_full_multi_rotation_plan_docs_emitted( + _, RE: RunEngine, test_multi_rotation_params: MultiRotationScan, fake_create_rotation_devices: RotationScanComposite, @@ -219,7 +224,12 @@ def test_full_multi_rotation_plan_docs_emitted( @patch( "mx_bluesky.hyperion.external_interaction.callbacks.rotation.nexus_callback.NexusWriter" ) +@patch( + "mx_bluesky.hyperion.experiment_plans.rotation_scan_plan.check_topup_and_wait_if_necessary", + autospec=True, +) def test_full_multi_rotation_plan_nexus_writer_called_correctly( + _, mock_nexus_writer: MagicMock, RE: RunEngine, test_multi_rotation_params: MultiRotationScan, @@ -253,7 +263,12 @@ def test_full_multi_rotation_plan_nexus_writer_called_correctly( } +@patch( + "mx_bluesky.hyperion.experiment_plans.rotation_scan_plan.check_topup_and_wait_if_necessary", + autospec=True, +) def test_full_multi_rotation_plan_nexus_files_written_correctly( + _, RE: RunEngine, test_multi_rotation_params: MultiRotationScan, fake_create_rotation_devices: RotationScanComposite, @@ -370,7 +385,12 @@ def _expected_dset_number(image_number: int): @patch( "mx_bluesky.hyperion.external_interaction.callbacks.rotation.ispyb_callback.StoreInIspyb" ) +@patch( + "mx_bluesky.hyperion.experiment_plans.rotation_scan_plan.check_topup_and_wait_if_necessary", + autospec=True, +) def test_full_multi_rotation_plan_ispyb_called_correctly( + _, mock_ispyb_store: MagicMock, RE: RunEngine, test_multi_rotation_params: MultiRotationScan, @@ -403,7 +423,12 @@ def test_full_multi_rotation_plan_ispyb_called_correctly( assert ispyb_store_calls[3][0] == "end_deposition" +@patch( + "mx_bluesky.hyperion.experiment_plans.rotation_scan_plan.check_topup_and_wait_if_necessary", + autospec=True, +) def test_full_multi_rotation_plan_ispyb_interaction_end_to_end( + _, mock_ispyb_conn_multiscan, RE: RunEngine, test_multi_rotation_params: MultiRotationScan, diff --git a/tests/unit_tests/hyperion/experiment_plans/test_optimise_attenuation_plan.py b/tests/unit_tests/hyperion/experiment_plans/test_optimise_attenuation_plan.py index 8b3c14dac..546870f9a 100644 --- a/tests/unit_tests/hyperion/experiment_plans/test_optimise_attenuation_plan.py +++ b/tests/unit_tests/hyperion/experiment_plans/test_optimise_attenuation_plan.py @@ -7,8 +7,7 @@ from bluesky.run_engine import RunEngine from dodal.beamlines import i03 from ophyd.status import Status -from ophyd_async.core import set_mock_value -from ophyd_async.core.async_status import AsyncStatus +from ophyd_async.core import AsyncStatus, set_mock_value from mx_bluesky.hyperion.experiment_plans import optimise_attenuation_plan from mx_bluesky.hyperion.experiment_plans.optimise_attenuation_plan import ( 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 9eaf992bf..9e8112a4c 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 @@ -7,12 +7,14 @@ import pytest from bluesky.run_engine import RunEngine from bluesky.simulators import RunEngineSimulator, assert_message_and_return_remaining -from dodal.devices.aperturescatterguard import AperturePosition, ApertureScatterguard +from dodal.devices.aperturescatterguard import ApertureScatterguard, ApertureValue from dodal.devices.backlight import BacklightPosition +from dodal.devices.detector.detector_motion import ShutterState from dodal.devices.oav.oav_parameters import OAVParameters from dodal.devices.smargon import Smargon from dodal.devices.synchrotron import SynchrotronMode -from dodal.devices.zebra import Zebra +from dodal.devices.zebra import PC_GATE, Zebra +from dodal.devices.zebra_controlled_shutter import ZebraShutterControl from ophyd_async.core import get_mock_put from mx_bluesky.hyperion.experiment_plans.oav_snapshot_plan import ( @@ -167,7 +169,7 @@ def test_rotation_scan( composite = fake_create_rotation_devices RE(rotation_scan(composite, test_rotation_params, oav_parameters_for_rotation)) - composite.eiger.stage.assert_called() # type: ignore + composite.eiger.do_arm.set.assert_called() # type: ignore composite.eiger.unstage.assert_called() # type: ignore @@ -231,7 +233,7 @@ async def test_rotation_plan_moves_aperture_correctly( ) assert ( await aperture_scatterguard.get_current_aperture_position() - == aperture_scatterguard._loaded_positions[AperturePosition.SMALL] + == ApertureValue.SMALL ) @@ -296,7 +298,6 @@ class MyTestException(Exception): def test_rotation_plan_reads_hardware( - RE: RunEngine, fake_create_rotation_devices: RotationScanComposite, test_rotation_params, motion_values, @@ -328,7 +329,8 @@ def test_rotation_plan_reads_hardware( ) -def test_rotation_scan_initialises_detector_distance_shutter_and_tx_fraction( +@pytest.fixture +def rotation_scan_simulated_messages( sim_run_engine: RunEngineSimulator, fake_create_rotation_devices: RotationScanComposite, test_rotation_params: RotationScan, @@ -336,62 +338,59 @@ def test_rotation_scan_initialises_detector_distance_shutter_and_tx_fraction( ): _add_sim_handlers_for_normal_operation(fake_create_rotation_devices, sim_run_engine) - msgs = sim_run_engine.simulate_plan( + return sim_run_engine.simulate_plan( rotation_scan( fake_create_rotation_devices, test_rotation_params, oav_parameters_for_rotation, ) ) + + +def test_rotation_scan_initialises_detector_distance_shutter_and_tx_fraction( + rotation_scan_simulated_messages, + test_rotation_params: RotationScan, +): msgs = assert_message_and_return_remaining( - msgs, + rotation_scan_simulated_messages, lambda msg: msg.command == "set" - and msg.args[0] == 1 - and msg.obj.name == "detector_motion-shutter" - and msg.kwargs["group"] == "setup_senv", + and msg.args[0] == test_rotation_params.detector_distance_mm + and msg.obj.name == "detector_motion-z" + and msg.kwargs["group"] == CONST.WAIT.ROTATION_READY_FOR_DC, ) msgs = assert_message_and_return_remaining( msgs, lambda msg: msg.command == "set" - and msg.args[0] == test_rotation_params.detector_distance_mm - and msg.obj.name == "detector_motion-z" - and msg.kwargs["group"] == "setup_senv", + and msg.args[0] == ShutterState.OPEN + and msg.obj.name == "detector_motion-shutter" + and msg.kwargs["group"] == CONST.WAIT.ROTATION_READY_FOR_DC, ) msgs = assert_message_and_return_remaining( msgs, lambda msg: msg.command == "set" and msg.obj.name == "attenuator" and msg.args[0] == test_rotation_params.transmission_frac - and msg.kwargs["group"] == "setup_senv", + and msg.kwargs["group"] == CONST.WAIT.ROTATION_READY_FOR_DC, ) msgs = assert_message_and_return_remaining( msgs, lambda msg: msg.command == "set" and msg.obj.name == "attenuator" and msg.args[0] == test_rotation_params.transmission_frac - and msg.kwargs["group"] == "setup_senv", + and msg.kwargs["group"] == CONST.WAIT.ROTATION_READY_FOR_DC, ) assert_message_and_return_remaining( - msgs, lambda msg: msg.command == "wait" and msg.kwargs["group"] == "setup_senv" + msgs, + lambda msg: msg.command == "wait" + and msg.kwargs["group"] == CONST.WAIT.ROTATION_READY_FOR_DC, ) def test_rotation_scan_moves_gonio_to_start_before_snapshots( - fake_create_rotation_devices: RotationScanComposite, - sim_run_engine: RunEngineSimulator, - test_rotation_params: RotationScan, - oav_parameters_for_rotation: OAVParameters, + rotation_scan_simulated_messages, ): - _add_sim_handlers_for_normal_operation(fake_create_rotation_devices, sim_run_engine) - msgs = sim_run_engine.simulate_plan( - rotation_scan( - fake_create_rotation_devices, - test_rotation_params, - oav_parameters_for_rotation, - ) - ) msgs = assert_message_and_return_remaining( - msgs, + rotation_scan_simulated_messages, lambda msg: msg.command == "wait" and msg.kwargs["group"] == CONST.WAIT.MOVE_GONIO_TO_START, ) @@ -403,21 +402,10 @@ def test_rotation_scan_moves_gonio_to_start_before_snapshots( def test_rotation_scan_moves_aperture_in_backlight_out_after_snapshots_before_rotation( - fake_create_rotation_devices: RotationScanComposite, - sim_run_engine: RunEngineSimulator, - test_rotation_params: RotationScan, - oav_parameters_for_rotation: OAVParameters, + rotation_scan_simulated_messages, ): - _add_sim_handlers_for_normal_operation(fake_create_rotation_devices, sim_run_engine) - msgs = sim_run_engine.simulate_plan( - rotation_scan( - fake_create_rotation_devices, - test_rotation_params, - oav_parameters_for_rotation, - ) - ) msgs = assert_message_and_return_remaining( - msgs, + rotation_scan_simulated_messages, lambda msg: msg.command == "create" and msg.kwargs["name"] == DocDescriptorNames.OAV_ROTATION_SNAPSHOT_TRIGGERED, ) @@ -426,37 +414,28 @@ def test_rotation_scan_moves_aperture_in_backlight_out_after_snapshots_before_ro msgs, lambda msg: msg.command == "set" and msg.obj.name == "aperture_scatterguard" - and msg.args[0] == AperturePosition.SMALL - and msg.kwargs["group"] == "setup_senv", + and msg.args[0] == ApertureValue.SMALL + and msg.kwargs["group"] == CONST.WAIT.ROTATION_READY_FOR_DC, ) msgs = assert_message_and_return_remaining( msgs, lambda msg: msg.command == "set" and msg.obj.name == "backlight" and msg.args[0] == BacklightPosition.OUT - and msg.kwargs["group"] == "setup_senv", + and msg.kwargs["group"] == CONST.WAIT.ROTATION_READY_FOR_DC, ) assert_message_and_return_remaining( - msgs, lambda msg: msg.command == "wait" and msg.kwargs["group"] == "setup_senv" + msgs, + lambda msg: msg.command == "wait" + and msg.kwargs["group"] == CONST.WAIT.ROTATION_READY_FOR_DC, ) def test_rotation_scan_resets_omega_waits_for_sample_env_complete_after_snapshots_before_hw_read( - fake_create_rotation_devices: RotationScanComposite, - sim_run_engine: RunEngineSimulator, - test_rotation_params: RotationScan, - oav_parameters_for_rotation: OAVParameters, + test_rotation_params: RotationScan, rotation_scan_simulated_messages ): - _add_sim_handlers_for_normal_operation(fake_create_rotation_devices, sim_run_engine) - msgs = sim_run_engine.simulate_plan( - rotation_scan( - fake_create_rotation_devices, - test_rotation_params, - oav_parameters_for_rotation, - ) - ) msgs = assert_message_and_return_remaining( - msgs, + rotation_scan_simulated_messages, lambda msg: msg.command == "create" and msg.kwargs["name"] == DocDescriptorNames.OAV_ROTATION_SNAPSHOT_TRIGGERED, ) @@ -466,20 +445,12 @@ def test_rotation_scan_resets_omega_waits_for_sample_env_complete_after_snapshot lambda msg: msg.command == "set" and msg.obj.name == "smargon-omega" and msg.args[0] == test_rotation_params.omega_start_deg - and msg.kwargs["group"] == "move_to_rotation_start", + and msg.kwargs["group"] == CONST.WAIT.ROTATION_READY_FOR_DC, ) msgs = assert_message_and_return_remaining( msgs, lambda msg: msg.command == "wait" - and msg.kwargs["group"] == "move_to_rotation_start", - ) - assert_message_and_return_remaining( - msgs, lambda msg: msg.command == "wait" and msg.kwargs["group"] == "setup_senv" - ) - assert_message_and_return_remaining( - msgs, - lambda msg: msg.command == "wait" - and msg.kwargs["group"] == "move_to_rotation_start", + and msg.kwargs["group"] == CONST.WAIT.ROTATION_READY_FOR_DC, ) assert_message_and_return_remaining( msgs, @@ -489,21 +460,10 @@ def test_rotation_scan_resets_omega_waits_for_sample_env_complete_after_snapshot def test_rotation_snapshot_setup_called_to_move_backlight_in_aperture_out_before_triggering( - fake_create_rotation_devices: RotationScanComposite, - sim_run_engine: RunEngineSimulator, - test_rotation_params: RotationScan, - oav_parameters_for_rotation: OAVParameters, + rotation_scan_simulated_messages, ): - _add_sim_handlers_for_normal_operation(fake_create_rotation_devices, sim_run_engine) - msgs = sim_run_engine.simulate_plan( - rotation_scan( - fake_create_rotation_devices, - test_rotation_params, - oav_parameters_for_rotation, - ) - ) msgs = assert_message_and_return_remaining( - msgs, + rotation_scan_simulated_messages, lambda msg: msg.command == "set" and msg.obj.name == "backlight" and msg.args[0] == BacklightPosition.IN @@ -513,7 +473,7 @@ def test_rotation_snapshot_setup_called_to_move_backlight_in_aperture_out_before msgs, lambda msg: msg.command == "set" and msg.obj.name == "aperture_scatterguard" - and msg.args[0] == AperturePosition.ROBOT_LOAD + and msg.args[0] == ApertureValue.ROBOT_LOAD and msg.kwargs["group"] == OAV_SNAPSHOT_SETUP_GROUP, ) msgs = assert_message_and_return_remaining( @@ -555,7 +515,7 @@ def test_rotation_scan_skips_init_backlight_aperture_and_snapshots_if_snapshot_p for msg in msgs if msg.command == "set" and msg.obj.name == "smargon-omega" - and msg.kwargs["group"] == "move_to_rotation_start" + and msg.kwargs["group"] == CONST.WAIT.ROTATION_READY_FOR_DC ] ) == 1 @@ -578,3 +538,57 @@ def _add_sim_handlers_for_normal_operation( sim_run_engine.add_handler( "read", lambda msg: {"smargon-omega": {"value": -1}}, "smargon-omega" ) + + +def test_rotation_scan_turns_shutter_to_auto_with_pc_gate_then_back_to_manual( + fake_create_rotation_devices: RotationScanComposite, + sim_run_engine: RunEngineSimulator, + test_rotation_params: RotationScan, + oav_parameters_for_rotation: OAVParameters, +): + _add_sim_handlers_for_normal_operation(fake_create_rotation_devices, sim_run_engine) + msgs = sim_run_engine.simulate_plan( + rotation_scan( + fake_create_rotation_devices, + test_rotation_params, + oav_parameters_for_rotation, + ) + ) + msgs = assert_message_and_return_remaining( + msgs, + lambda msg: msg.command == "set" + and msg.obj.name == "sample_shutter-control_mode" + and msg.args[0] == ZebraShutterControl.AUTO, # type:ignore + ) + msgs = assert_message_and_return_remaining( + msgs, + lambda msg: msg.command == "set" + and msg.obj.name == "zebra-logic_gates-and_gates-2-sources-1" + and msg.args[0] == PC_GATE, # type:ignore + ) + msgs = assert_message_and_return_remaining( + msgs, + lambda msg: msg.command == "set" + and msg.obj.name == "sample_shutter-control_mode" + and msg.args[0] == ZebraShutterControl.MANUAL, # type:ignore + ) + + +def test_rotation_scan_arms_detector_and_takes_snapshots_whilst_arming( + rotation_scan_simulated_messages, +): + msgs = assert_message_and_return_remaining( + rotation_scan_simulated_messages, + lambda msg: msg.command == "set" + and msg.obj.name == "eiger_do_arm" + and msg.args[0] == 1 + and msg.kwargs["group"] == CONST.WAIT.ROTATION_READY_FOR_DC, + ) + msgs = assert_message_and_return_remaining( + msgs, lambda msg: msg.command == "trigger" and msg.obj.name == "oav_snapshot" + ) + msgs = assert_message_and_return_remaining( + msgs, + lambda msg: msg.command == "wait" + and msg.kwargs["group"] == CONST.WAIT.ROTATION_READY_FOR_DC, + ) diff --git a/tests/unit_tests/hyperion/experiment_plans/test_wait_for_robot_load_then_centre.py b/tests/unit_tests/hyperion/experiment_plans/test_wait_for_robot_load_then_centre.py index 254caf554..551ff28fc 100644 --- a/tests/unit_tests/hyperion/experiment_plans/test_wait_for_robot_load_then_centre.py +++ b/tests/unit_tests/hyperion/experiment_plans/test_wait_for_robot_load_then_centre.py @@ -6,7 +6,7 @@ from bluesky.run_engine import RunEngine from bluesky.simulators import RunEngineSimulator, assert_message_and_return_remaining from bluesky.utils import Msg -from dodal.devices.aperturescatterguard import AperturePosition +from dodal.devices.aperturescatterguard import ApertureValue from dodal.devices.oav.oav_detector import OAV from dodal.devices.smargon import StubPosition from dodal.devices.webcam import Webcam @@ -136,8 +136,8 @@ def test_robot_load_then_centre_doesnt_set_energy_if_not_specified_and_current_e ): robot_load_composite.eiger.set_detector_parameters = MagicMock() sim_run_engine.add_handler( - "read", - lambda msg: {"dcm-energy_in_kev": {"value": 11.105}}, + "locate", + lambda msg: {"readback": 11.105}, "dcm-energy_in_kev", ) messages = sim_run_engine.simulate_plan( @@ -165,8 +165,8 @@ def return_not_disabled_after_reads(_): return {"values": {"value": int(num_of_reads < total_disabled_reads)}} sim_run_engine.add_handler( - "read", - lambda msg: {"dcm-energy_in_kev": {"value": 11.105}}, + "locate", + lambda msg: {"readback": 11.105}, "dcm-energy_in_kev", ) sim_run_engine.add_handler( @@ -289,7 +289,7 @@ async def test_when_prepare_for_robot_load_called_then_moves_as_expected( assert await smargon.omega.user_readback.get_value() == 0 smargon.stub_offsets.set.assert_called_once_with(StubPosition.RESET_TO_ROBOT_LOAD) # type: ignore - aperture_scatterguard.set.assert_called_once_with(AperturePosition.ROBOT_LOAD) # type: ignore + aperture_scatterguard.set.assert_called_once_with(ApertureValue.ROBOT_LOAD) # type: ignore @patch( @@ -370,11 +370,11 @@ def test_given_lower_gonio_moved_when_robot_load_then_lower_gonio_moved_to_home_ initial_values = {"x": 0.11, "y": 0.12, "z": 0.13} def get_read(axis, msg): - return {f"lower_gonio-{axis}": {"value": initial_values[axis]}} + return {"readback": initial_values[axis]} for axis in initial_values.keys(): sim_run_engine.add_handler( - "read", partial(get_read, axis), f"lower_gonio-{axis}" + "locate", partial(get_read, axis), f"lower_gonio-{axis}" ) messages = sim_run_engine.simulate_plan( @@ -417,11 +417,11 @@ def test_when_plan_run_then_lower_gonio_moved_before_robot_loads_and_back_after_ initial_values = {"x": 0.11, "y": 0.12, "z": 0.13} def get_read(axis, msg): - return {f"lower_gonio-{axis}": {"value": initial_values[axis]}} + return {"readback": initial_values[axis]} for axis in initial_values.keys(): sim_run_engine.add_handler( - "read", partial(get_read, axis), f"lower_gonio-{axis}" + "locate", partial(get_read, axis), f"lower_gonio-{axis}" ) messages = sim_run_engine.simulate_plan( diff --git a/tests/unit_tests/hyperion/external_interaction/callbacks/conftest.py b/tests/unit_tests/hyperion/external_interaction/callbacks/conftest.py index 95c9bf4c8..e016c31b1 100644 --- a/tests/unit_tests/hyperion/external_interaction/callbacks/conftest.py +++ b/tests/unit_tests/hyperion/external_interaction/callbacks/conftest.py @@ -1,4 +1,5 @@ import pytest +from dodal.devices.aperturescatterguard import ApertureValue from dodal.devices.synchrotron import SynchrotronMode from dodal.devices.zocalo.zocalo_results import ZOCALO_READING_PLAN_NAME from event_model.documents import Event, EventDescriptor, RunStart, RunStop @@ -29,7 +30,7 @@ def test_rotation_start_outer_document(dummy_rotation_params): return { "uid": "d8bee3ee-f614-4e7a-a516-25d6b9e87ef3", "subplan_name": CONST.PLAN.ROTATION_OUTER, - "hyperion_parameters": dummy_rotation_params.json(), + "hyperion_parameters": dummy_rotation_params.model_dump_json(), } @@ -46,7 +47,7 @@ class TestData(OavGridSnapshotTestEvents): "plan_name": CONST.PLAN.GRIDSCAN_OUTER, "subplan_name": CONST.PLAN.GRIDSCAN_OUTER, CONST.TRIGGER.ZOCALO: CONST.PLAN.DO_FGS, - "hyperion_parameters": dummy_params().json(), + "hyperion_parameters": dummy_params().model_dump_json(), } test_gridscan3d_start_document: RunStart = { # type: ignore "uid": "d8bee3ee-f614-4e7a-a516-25d6b9e87ef3", @@ -56,7 +57,7 @@ class TestData(OavGridSnapshotTestEvents): "plan_type": "generator", "plan_name": "test", "subplan_name": CONST.PLAN.GRID_DETECT_AND_DO_GRIDSCAN, - "hyperion_parameters": dummy_params().json(), + "hyperion_parameters": dummy_params().model_dump_json(), } test_gridscan2d_start_document = { "uid": "d8bee3ee-f614-4e7a-a516-25d6b9e87ef3", @@ -66,7 +67,7 @@ class TestData(OavGridSnapshotTestEvents): "plan_type": "generator", "plan_name": "test", "subplan_name": CONST.PLAN.GRID_DETECT_AND_DO_GRIDSCAN, - "hyperion_parameters": dummy_params_2d().json(), + "hyperion_parameters": dummy_params_2d().model_dump_json(), } test_rotation_start_main_document = { "uid": "2093c941-ded1-42c4-ab74-ea99980fbbfd", @@ -83,18 +84,19 @@ class TestData(OavGridSnapshotTestEvents): "subplan_name": CONST.PLAN.GRIDSCAN_OUTER, "zocalo_environment": "dev_artemis", CONST.TRIGGER.ZOCALO: CONST.PLAN.DO_FGS, - "hyperion_parameters": dummy_params().json(), + "hyperion_parameters": dummy_params().model_dump_json(), } test_rotation_event_document_during_data_collection: Event = { "descriptor": "bd45c2e5-2b85-4280-95d7-a9a15800a78b", "time": 2666604299.928203, "data": { - "aperture_scatterguard-selected_aperture": { - "name": "Medium", - "GDA_name": "MEDIUM", - "radius_microns": 50, - "location": (15, 16, 2, 18, 19), - }, + "aperture_scatterguard-aperture-x": 15, + "aperture_scatterguard-aperture-y": 16, + "aperture_scatterguard-aperture-z": 2, + "aperture_scatterguard-scatterguard-x": 18, + "aperture_scatterguard-scatterguard-y": 19, + "aperture_scatterguard-selected_aperture": ApertureValue.MEDIUM, + "aperture_scatterguard-radius": 50, "attenuator-actual_transmission": 0.98, "flux_flux_reading": 9.81, "dcm-energy_in_kev": 11.105, @@ -180,12 +182,13 @@ class TestData(OavGridSnapshotTestEvents): "descriptor": "bd45c2e5-2b85-4280-95d7-a9a15800a78b", "time": 2666604299.928203, "data": { - "aperture_scatterguard-selected_aperture": { - "name": "Medium", - "GDA_name": "MEDIUM", - "radius_microns": 50, - "location": (15, 16, 2, 18, 19), - }, + "aperture_scatterguard-aperture-x": 15, + "aperture_scatterguard-aperture-y": 16, + "aperture_scatterguard-aperture-z": 2, + "aperture_scatterguard-scatterguard-x": 18, + "aperture_scatterguard-scatterguard-y": 19, + "aperture_scatterguard-selected_aperture": ApertureValue.MEDIUM, + "aperture_scatterguard-radius": 50, "attenuator-actual_transmission": 1, "flux_flux_reading": 10, "dcm-energy_in_kev": 11.105, 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 893ec75fd..79fca9973 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 @@ -53,7 +53,7 @@ def rotation_start_outer_doc_without_snapshots( ): dummy_rotation_params.ispyb_extras.xtal_snapshots_omega_start = None test_rotation_start_outer_document["hyperion_parameters"] = ( - dummy_rotation_params.json() + dummy_rotation_params.model_dump_json() ) return test_rotation_start_outer_document @@ -301,7 +301,7 @@ def test_comment_correct_after_hardware_read( test_rotation_start_outer_document["hyperion_parameters"] = ( test_rotation_start_outer_document[ "hyperion_parameters" - ].replace('"comment": "test"', '"comment": "a lovely unit test"') + ].replace('"comment":"test"', '"comment":"a lovely unit test"') ) callback.activity_gated_start(test_rotation_start_outer_document) # pyright: ignore callback.activity_gated_start( diff --git a/tests/unit_tests/hyperion/external_interaction/callbacks/rotation/test_ispyb_mapping.py b/tests/unit_tests/hyperion/external_interaction/callbacks/rotation/test_ispyb_mapping.py deleted file mode 100644 index dbf73a2c7..000000000 --- a/tests/unit_tests/hyperion/external_interaction/callbacks/rotation/test_ispyb_mapping.py +++ /dev/null @@ -1,16 +0,0 @@ -from unittest.mock import patch - -from mx_bluesky.hyperion.external_interaction.callbacks.rotation.ispyb_mapping import ( - populate_data_collection_info_for_rotation, -) - - -def test_populate_data_collection_info_for_rotation_checks_snapshots( - dummy_rotation_params, -): - with patch( - "mx_bluesky.hyperion.log.ISPYB_LOGGER.warning", autospec=True - ) as warning: - dummy_rotation_params.ispyb_extras.xtal_snapshots_omega_start = None - populate_data_collection_info_for_rotation(dummy_rotation_params) - warning.assert_called_once_with("No xtal snapshot paths sent to ISPyB!") diff --git a/tests/unit_tests/hyperion/external_interaction/callbacks/test_rotation_callbacks.py b/tests/unit_tests/hyperion/external_interaction/callbacks/test_rotation_callbacks.py index 7be099150..e995e7c38 100644 --- a/tests/unit_tests/hyperion/external_interaction/callbacks/test_rotation_callbacks.py +++ b/tests/unit_tests/hyperion/external_interaction/callbacks/test_rotation_callbacks.py @@ -58,7 +58,7 @@ def params(): def test_outer_start_doc(params: RotationScan): return { "subplan_name": CONST.PLAN.ROTATION_OUTER, - "hyperion_parameters": params.json(), + "hyperion_parameters": params.model_dump_json(), } @@ -97,7 +97,7 @@ def fake_rotation_scan( @bpp.run_decorator( # attach experiment metadata to the start document md={ "subplan_name": CONST.PLAN.ROTATION_OUTER, - "hyperion_parameters": params.json(), + "hyperion_parameters": params.model_dump_json(), CONST.TRIGGER.ZOCALO: CONST.PLAN.ROTATION_MAIN, "zocalo_environment": params.zocalo_environment, } @@ -153,7 +153,7 @@ def test_nexus_handler_gets_documents_in_mock_plan( assert nexus_handler.activity_gated_start.call_count == 2 # type: ignore call_content_outer = nexus_handler.activity_gated_start.call_args_list[0].args[0] # type: ignore - assert call_content_outer["hyperion_parameters"] == params.json() + assert call_content_outer["hyperion_parameters"] == params.model_dump_json() call_content_inner = nexus_handler.activity_gated_start.call_args_list[1].args[0] # type: ignore assert call_content_inner["subplan_name"] == CONST.PLAN.ROTATION_MAIN @@ -414,7 +414,7 @@ def test_ispyb_handler_stores_sampleid_for_full_collection_not_screening( params.ispyb_experiment_type = IspybExperimentType.CHARACTERIZATION assert params.num_images == n_images doc["subplan_name"] = CONST.PLAN.ROTATION_OUTER # type: ignore - doc["hyperion_parameters"] = params.json() # type: ignore + doc["hyperion_parameters"] = params.model_dump_json() # type: ignore cb.start(doc) assert (cb.last_sample_id == 987678) is store_id diff --git a/tests/unit_tests/hyperion/external_interaction/callbacks/xray_centre/test_ispyb_mapping.py b/tests/unit_tests/hyperion/external_interaction/callbacks/xray_centre/test_ispyb_mapping.py index 9aef93d41..7b5b60d31 100644 --- a/tests/unit_tests/hyperion/external_interaction/callbacks/xray_centre/test_ispyb_mapping.py +++ b/tests/unit_tests/hyperion/external_interaction/callbacks/xray_centre/test_ispyb_mapping.py @@ -7,8 +7,8 @@ ) from mx_bluesky.hyperion.external_interaction.ispyb.data_model import ( DataCollectionGridInfo, + Orientation, ) -from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_dataclass import Orientation from mx_bluesky.hyperion.parameters.gridscan import ThreeDGridScan from ...conftest import ( diff --git a/tests/unit_tests/hyperion/external_interaction/ispyb/conftest.py b/tests/unit_tests/hyperion/external_interaction/ispyb/conftest.py index 28478d1f8..0ae1f79c9 100644 --- a/tests/unit_tests/hyperion/external_interaction/ispyb/conftest.py +++ b/tests/unit_tests/hyperion/external_interaction/ispyb/conftest.py @@ -5,9 +5,9 @@ from mx_bluesky.hyperion.external_interaction.ispyb.data_model import ( DataCollectionGridInfo, DataCollectionPositionInfo, + Orientation, ScanDataInfo, ) -from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_dataclass import Orientation from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_store import StoreInIspyb from mx_bluesky.hyperion.parameters.constants import CONST from mx_bluesky.hyperion.parameters.gridscan import ThreeDGridScan diff --git a/tests/unit_tests/hyperion/external_interaction/ispyb/test_gridscan_ispyb_store_3d.py b/tests/unit_tests/hyperion/external_interaction/ispyb/test_gridscan_ispyb_store_3d.py index 6c5211dfc..f5fd08375 100644 --- a/tests/unit_tests/hyperion/external_interaction/ispyb/test_gridscan_ispyb_store_3d.py +++ b/tests/unit_tests/hyperion/external_interaction/ispyb/test_gridscan_ispyb_store_3d.py @@ -8,9 +8,9 @@ DataCollectionGroupInfo, DataCollectionInfo, DataCollectionPositionInfo, + Orientation, ScanDataInfo, ) -from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_dataclass import Orientation from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_store import ( IspybIds, StoreInIspyb, diff --git a/tests/unit_tests/hyperion/external_interaction/test_ispyb_dataclass.py b/tests/unit_tests/hyperion/external_interaction/test_ispyb_dataclass.py deleted file mode 100644 index 08d2af8f9..000000000 --- a/tests/unit_tests/hyperion/external_interaction/test_ispyb_dataclass.py +++ /dev/null @@ -1,28 +0,0 @@ -from copy import deepcopy - -import numpy as np - -from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_dataclass import ( - GRIDSCAN_ISPYB_PARAM_DEFAULTS, - IspybParams, -) - - -def test_given_position_as_list_when_ispyb_params_created_then_converted_to_numpy_array(): - params = deepcopy(GRIDSCAN_ISPYB_PARAM_DEFAULTS) - params["position"] = [1, 2, 3] - - ispyb_params = IspybParams(**params) - - assert isinstance(ispyb_params.position, np.ndarray) - assert np.array_equal(ispyb_params.position, [1, 2, 3]) - - -def test_given_ispyb_params_when_converted_to_dict_then_position_is_a_list(): - params = deepcopy(GRIDSCAN_ISPYB_PARAM_DEFAULTS) - params["position"] = [1, 2, 3] - - ispyb_params_dict = IspybParams(**params).dict() - - assert isinstance(ispyb_params_dict["position"], list) - assert ispyb_params_dict["position"] == [1, 2, 3] diff --git a/tests/unit_tests/hyperion/external_interaction/test_write_rotation_nexus.py b/tests/unit_tests/hyperion/external_interaction/test_write_rotation_nexus.py index 7b6717735..984cd98da 100644 --- a/tests/unit_tests/hyperion/external_interaction/test_write_rotation_nexus.py +++ b/tests/unit_tests/hyperion/external_interaction/test_write_rotation_nexus.py @@ -59,7 +59,7 @@ def fake_rotation_scan( @bpp.run_decorator( # attach experiment metadata to the start document md={ "subplan_name": CONST.PLAN.ROTATION_OUTER, - "hyperion_parameters": parameters.json(), + "hyperion_parameters": parameters.model_dump_json(), "activate_callbacks": "RotationNexusFileCallback", } ) diff --git a/tests/unit_tests/hyperion/parameters/test_parameter_model.py b/tests/unit_tests/hyperion/parameters/test_parameter_model.py index abf7d5d9a..f13d070c3 100644 --- a/tests/unit_tests/hyperion/parameters/test_parameter_model.py +++ b/tests/unit_tests/hyperion/parameters/test_parameter_model.py @@ -4,6 +4,7 @@ import pytest from pydantic import ValidationError +from mx_bluesky.hyperion.parameters.constants import GridscanParamConstants from mx_bluesky.hyperion.parameters.gridscan import ( OddYStepsException, RobotLoadThenCentre, @@ -38,7 +39,7 @@ def test_minimal_3d_gridscan_params(minimal_3d_gridscan_params): assert {"sam_x", "sam_y", "sam_z"} == set(test_params.scan_points.keys()) assert test_params.scan_indices == [0, 35] assert test_params.num_images == (5 * 7 + 5 * 9) - assert test_params.exposure_time_s == 0.02 + assert test_params.exposure_time_s == GridscanParamConstants.EXPOSURE_TIME_S def test_cant_do_panda_fgs_with_odd_y_steps(minimal_3d_gridscan_params): @@ -50,7 +51,7 @@ def test_cant_do_panda_fgs_with_odd_y_steps(minimal_3d_gridscan_params): def test_serialise_deserialise(minimal_3d_gridscan_params): test_params = ThreeDGridScan(**minimal_3d_gridscan_params) - serialised = json.loads(test_params.json()) + serialised = json.loads(test_params.model_dump_json()) deserialised = ThreeDGridScan(**serialised) assert deserialised.demand_energy_ev is None assert deserialised.visit == "cm12345" diff --git a/tests/unit_tests/hyperion/test_main_system.py b/tests/unit_tests/hyperion/test_main_system.py index 5885afc99..1e6efc197 100644 --- a/tests/unit_tests/hyperion/test_main_system.py +++ b/tests/unit_tests/hyperion/test_main_system.py @@ -300,6 +300,21 @@ def test_start_with_json_file_gives_success(test_env: ClientAndRunEngine): check_status_in_response(response, Status.SUCCESS) +def test_start_with_json_file_with_extras_gives_error(test_env: ClientAndRunEngine): + test_env.mock_run_engine.RE_takes_time = False + + with open( + "tests/test_data/parameter_json_files/good_test_parameters.json" + ) as test_params_file: + test_params = test_params_file.read() + + params = json.loads(test_params) + params["extra_param"] = "test" + test_params = json.dumps(params) + response = test_env.client.put(START_ENDPOINT, data=test_params) + check_status_in_response(response, Status.FAILED) + + test_argument_combinations = [ ( [ diff --git a/utility_scripts/deploy/deploy_edm_for_ssx.sh b/utility_scripts/deploy/deploy_edm_for_ssx.sh index cbf44b067..029639a58 100755 --- a/utility_scripts/deploy/deploy_edm_for_ssx.sh +++ b/utility_scripts/deploy/deploy_edm_for_ssx.sh @@ -4,7 +4,7 @@ # Make a copy of them in edm/ and replace paths current=$( realpath "$( dirname "$0" )" ) -base=$(dirname $current) +base=$(dirname $(dirname $current)) edm_build="$base/edm_serial" @@ -24,7 +24,7 @@ mkdir $ex_edm mkdir $ft_edm scripts_placeholder="SCRIPTS_LOCATION" -scripts_loc="$base/src/mx_bluesky/i24/serial" +scripts_loc="$base/src/mx_bluesky/beamlines/i24/serial" # Add blueapi configuration file to get stomp # See https://github.com/DiamondLightSource/blueapi/issues/485 diff --git a/utility_scripts/deploy/deploy_mxbluesky.py b/utility_scripts/deploy/deploy_mxbluesky.py index 6b92facd3..70d8e9112 100644 --- a/utility_scripts/deploy/deploy_mxbluesky.py +++ b/utility_scripts/deploy/deploy_mxbluesky.py @@ -166,12 +166,12 @@ def run_process_and_print_output(proc_to_run): print(f"Setting up environment in {mx_repo.deploy_location}") if mx_repo.name == "mx_bluesky": - run_process_and_print_output("./dls_dev_setup.sh") + run_process_and_print_output("./utility_scripts/dls_dev_setup.sh") # If on beamline I24 also deploy the screens to run ssx collections if beamline == "i24": print("Setting up edm screens for serial collections on I24.") - run_process_and_print_output("./deploy/deploy_edm_for_ssx.sh") + run_process_and_print_output("./utility_scripts/deploy/deploy_edm_for_ssx.sh") move_symlink = input( """Move symlink (y/n)? WARNING: this will affect the running version!