From 2fb9dd876e1a7fd25cae9d8d2085ed2b82bcdf64 Mon Sep 17 00:00:00 2001 From: Will Barnett Date: Fri, 27 Sep 2024 14:37:44 +0100 Subject: [PATCH] Revised pressure jump cell adc to use ophyd-async. --- src/dodal/beamlines/p38.py | 17 ++-- src/dodal/devices/areadetector/__init__.py | 5 ++ .../devices/areadetector/pressurejumpcell.py | 48 +++++++++++ .../pressurejumpcell_controller.py | 82 +++++++++++++++++++ .../areadetector/pressurejumpcell_io.py | 29 +++++++ src/dodal/devices/pressure_jump_cell_adc.py | 63 -------------- 6 files changed, 173 insertions(+), 71 deletions(-) create mode 100644 src/dodal/devices/areadetector/__init__.py create mode 100644 src/dodal/devices/areadetector/pressurejumpcell.py create mode 100644 src/dodal/devices/areadetector/pressurejumpcell_controller.py create mode 100644 src/dodal/devices/areadetector/pressurejumpcell_io.py delete mode 100644 src/dodal/devices/pressure_jump_cell_adc.py diff --git a/src/dodal/beamlines/p38.py b/src/dodal/beamlines/p38.py index b14fffb17b..9cfbf45551 100644 --- a/src/dodal/beamlines/p38.py +++ b/src/dodal/beamlines/p38.py @@ -20,7 +20,7 @@ from dodal.devices.i22.fswitch import FSwitch from dodal.devices.linkam3 import Linkam3 from dodal.devices.pressure_jump_cell import PressureJumpCell -from dodal.devices.pressure_jump_cell_adc import PressureJumpCellADC, PressureJumpAdcConfigParams +from dodal.devices.areadetector import PressureJumpCellDetector from dodal.devices.slits import Slits from dodal.devices.tetramm import TetrammDetector from dodal.devices.undulator import Undulator @@ -347,15 +347,16 @@ def high_pressure_xray_cell( ) def high_pressure_xray_cell_adc( - wait_for_connection: bool = True, - fake_with_ophyd_sim: bool = False, - params: PressureJumpAdcConfigParams | None = None, -) -> PressureJumpCellADC: + wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False +) -> PressureJumpCellDetector: return device_instantiation( - PressureJumpCellADC, + PressureJumpCellDetector, "high_pressure_xray_cell_adc", - "", + "-EA-HPXC-01:", wait_for_connection, fake_with_ophyd_sim, - params=params or None, + adc_suffix="TRIG:", + drv_suffix="DET:", + hdf_suffix="FILE:", + path_provider=get_path_provider(), ) diff --git a/src/dodal/devices/areadetector/__init__.py b/src/dodal/devices/areadetector/__init__.py new file mode 100644 index 0000000000..3b787b5628 --- /dev/null +++ b/src/dodal/devices/areadetector/__init__.py @@ -0,0 +1,5 @@ +from .pressurejumpcell import PressureJumpCellDetector + +__all__ = [ + "PressureJumpCellDetector", +] diff --git a/src/dodal/devices/areadetector/pressurejumpcell.py b/src/dodal/devices/areadetector/pressurejumpcell.py new file mode 100644 index 0000000000..ae7c3650ac --- /dev/null +++ b/src/dodal/devices/areadetector/pressurejumpcell.py @@ -0,0 +1,48 @@ +from typing import get_args + +from bluesky.protocols import HasHints, Hints + +from ophyd_async.core import PathProvider, StandardDetector +from ophyd_async.epics import adcore + +from .pressurejumpcell_controller import PressureJumpCellController +from .pressurejumpcell_io import PressureJumpCellDriverIO, PressureJumpCellAdcIO + + +class PressureJumpCellDetector(StandardDetector, HasHints): + """ + Ophyd-async implementation of a Pressure Jump Cell ADC Detector for fast pressure jumps. + The detector may be configured for an external trigger on the TTL Trig input. + """ + + _controller: PressureJumpCellController + _writer: adcore.ADHDFWriter + + def __init__( + self, + prefix: str, + path_provider: PathProvider, + drv_suffix="cam1:", + adc_suffix="TRIG", + hdf_suffix="HDF1:", + name="", + ): + self.drv = PressureJumpCellDriverIO(prefix + drv_suffix) + self.adc = PressureJumpCellAdcIO(prefix + adc_suffix) + self.hdf = adcore.NDFileHDFIO(prefix + hdf_suffix) + + super().__init__( + PressureJumpCellController(self.drv, self.adc), + adcore.ADHDFWriter( + self.hdf, + path_provider, + lambda: self.name, + adcore.ADBaseDatasetDescriber(self.drv), + ), + config_sigs=(self.drv.acquire_time,), + name=name, + ) + + @property + def hints(self) -> Hints: + return self._writer.hints diff --git a/src/dodal/devices/areadetector/pressurejumpcell_controller.py b/src/dodal/devices/areadetector/pressurejumpcell_controller.py new file mode 100644 index 0000000000..986f0d361c --- /dev/null +++ b/src/dodal/devices/areadetector/pressurejumpcell_controller.py @@ -0,0 +1,82 @@ +import asyncio +from typing import Optional, Tuple + +from ophyd_async.core import ( + AsyncStatus, + DetectorControl, + DetectorTrigger, + set_and_wait_for_value, +) +from ophyd_async.epics import adcore + +from .pressurejumpcell_io import ( + PressureJumpCellDriverIO, + PressureJumpCellAdcIO, + PressureJumpCellTriggerMode, + PressureJumpCellAdcTriggerMode, +) + + +#TODO Find out what the deadtime should be and if it can be retrieved from the device +_HIGHEST_POSSIBLE_DEADTIME = 1e-3 + + +class PressureJumpCellController(DetectorControl): + + def __init__(self, driver: PressureJumpCellDriverIO, adc: PressureJumpCellAdcIO) -> None: + self._drv = driver + self._adc = adc + + def get_deadtime(self, exposure: float | None) -> float: + return _HIGHEST_POSSIBLE_DEADTIME + + async def arm( + self, + num: int = 0, + trigger: DetectorTrigger = DetectorTrigger.internal, + exposure: Optional[float] = None, + ) -> AsyncStatus: + if num == 0: + image_mode = adcore.ImageMode.continuous + else: + image_mode = adcore.ImageMode.multiple + if exposure is not None: + await self._drv.acquire_time.set(exposure) + + trigger_mode, adc_trigger_mode = self._get_trigger_info(trigger) + + # trigger mode must be set first and on it's own! + await self._drv.trigger_mode.set(trigger_mode) + await self._adc.adc_trigger_mode.set(adc_trigger_mode) + + await asyncio.gather( + self._drv.num_images.set(num), + self._drv.image_mode.set(image_mode), + ) + + status = await set_and_wait_for_value(self._drv.acquire, True) + return status + + def _get_trigger_info( + self, trigger: DetectorTrigger + ) -> Tuple[PressureJumpCellTriggerMode, PressureJumpCellAdcTriggerMode]: + supported_trigger_types = ( + DetectorTrigger.edge_trigger, + DetectorTrigger.internal, + ) + if trigger not in supported_trigger_types: + raise ValueError( + f"{self.__class__.__name__} only supports the following trigger " + f"types: {supported_trigger_types} but was asked to " + f"use {trigger}" + ) + if trigger == DetectorTrigger.internal: + return (PressureJumpCellTriggerMode.internal, PressureJumpCellAdcTriggerMode.continuous) + else: + return (PressureJumpCellTriggerMode.external, PressureJumpCellAdcTriggerMode.single) + + async def disarm(self): + await asyncio.gather( + adcore.stop_busy_record(self._drv.acquire, False, timeout=1), + adcore.stop_busy_record(self._adc.acquire, False, timeout=1) + ) diff --git a/src/dodal/devices/areadetector/pressurejumpcell_io.py b/src/dodal/devices/areadetector/pressurejumpcell_io.py new file mode 100644 index 0000000000..d0104916b6 --- /dev/null +++ b/src/dodal/devices/areadetector/pressurejumpcell_io.py @@ -0,0 +1,29 @@ +from enum import Enum + +from ophyd_async.core import SubsetEnum +from ophyd_async.epics import adcore +from ophyd_async.epics.signal import epics_signal_rw_rbv + + +class PressureJumpCellTriggerMode(str, Enum): + internal = "Internal" + external = "Exernal" + +class PressureJumpCellAdcTriggerMode(str, Enum): + single = "Single" + multiple = "Multiple" + continuous = "Continuous" + +class PressureJumpCellDriverIO(adcore.ADBaseIO): + def __init__(self, prefix: str, name: str = "") -> None: + self.trigger_mode = epics_signal_rw_rbv( + PressureJumpCellTriggerMode, prefix + "TriggerMode" + ) + super().__init__(prefix, name=name) + +class PressureJumpCellAdcIO(adcore.ADBaseIO): + def __init__(self, prefix: str, name: str = "") -> None: + self.adc_trigger_mode = epics_signal_rw_rbv( + PressureJumpCellAdcTriggerMode, prefix + "TriggerMode" + ) + super().__init__(prefix, name=name) diff --git a/src/dodal/devices/pressure_jump_cell_adc.py b/src/dodal/devices/pressure_jump_cell_adc.py deleted file mode 100644 index 63665401af..0000000000 --- a/src/dodal/devices/pressure_jump_cell_adc.py +++ /dev/null @@ -1,63 +0,0 @@ -from enum import Enum -from functools import partial - -from ophyd_async.core import ( - ConfigSignal, - DeviceVector, - SignalR, - StandardReadable, -) -from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw - -from ophyd import ADComponent as ADC -from ophyd import ( - AreaDetector, - CamBase, - Component, - Device, - EpicsSignal, - HDF5Plugin, - OverlayPlugin, - ProcessPlugin, - ROIPlugin, - StatsPlugin, - Signal, - StatusBase, -) - -class PressureJumpAdcConfigParams: - pass - -class PressureJumpCellADC(AreaDetector): - """ - High pressure X-ray cell fast ADC, used to capture pressure jumps. - """ - cam = ADC(CamBase, "-EA-HPXC-01:CAM:") - scale = ADC(ROIPlugin, "-EA-HPXC-01:SCALE:") - # trig = # TODO support module adUtil NDPluginReframe? - # arr = # TODO support module ADCore NDPluginStdArrays? required? - hdf5 = ADC(HDF5Plugin, "-DI-OAV-01:HDF5:") - - p1Roi = ADC(ROIPlugin, "-EA-HPXC-01:ROIP1:") - p1Proc = ADC(ProcessPlugin, "-EA-HPXC-01:PROCP1:") - p1Stats = ADC(StatsPlugin, "-EA-HPXC-01:STATP1:") - - p2Roi = ADC(ROIPlugin, "-EA-HPXC-01:ROIP2:") - p2Proc = ADC(ProcessPlugin, "-EA-HPXC-01:PROCP2:") - p2Stats = ADC(StatsPlugin, "-EA-HPXC-01:STATP2:") - - p3Roi = ADC(ROIPlugin, "-EA-HPXC-01:ROIP3:") - p3Proc = ADC(ProcessPlugin, "-EA-HPXC-01:PROCP3:") - p3Stats = ADC(StatsPlugin, "-EA-HPXC-01:STATP3:") - - def __init__(self, *args, params: PressureJumpAdcConfigParams, **kwargs): - super().__init__(*args, **kwargs) - self.parameters = params - self.snapshot.oav_params = params - self.subscription_id = None - self._snapshot_trigger_subscription_id = None - - def wait_for_connection(self, all_signals=False, timeout=2): - connected = super().wait_for_connection(all_signals, timeout) - return connected -