Source code for ophyd_async.epics.admerlin

"""Support for the ADMerlin areaDetector driver.

https://github.com/areaDetector/ADMerlin.
"""

from collections.abc import Sequence
from typing import Annotated as A

from ophyd_async.core import (
    DetectorTriggerLogic,
    PathProvider,
    SignalDict,
    SignalR,
    SignalRW,
    StrictEnum,
)
from ophyd_async.epics.adcore import (
    ADArmLogic,
    ADBaseIO,
    ADWriterType,
    AreaDetector,
    NDPluginBaseIO,
    prepare_exposures,
)
from ophyd_async.epics.core import PvSuffix

__all__ = [
    "MerlinDetector",
    "MerlinDriverIO",
    "MerlinTriggerLogic",
    "MerlinTriggerMode",
]

_MIN_DEAD_TIME = 0.002


[docs] class MerlinTriggerMode(StrictEnum): """Trigger modes for ADMerlin detector.""" INTERNAL = "Internal" TRIGGER_ENABLE = "Trigger Enable" TRIGGER_START_RISING = "Trigger start rising" TRIGGER_START_FALLING = "Trigger start falling" TRIGGER_BOTH_RISING = "Trigger both rising" LVDS_TRIG_ENABLE = "LVDS Trig Enable" LVDS_TRIG_START_RISING = "LVDS Trig start rising" LVDS_TRIG_START_FALLING = "LVDS Trig start falling" LVDS_TRIG_BOTH_RISING = "LVDS Trig both rising" SOFTWARE = "Software"
[docs] class MerlinDriverIO(ADBaseIO): """Driver for merlin model:DU897_BV as deployed on p99. This mirrors the interface provided by ADMerlin/db/merlin.template. https://github.com/areaDetector/ADMerlin/blob/master/merlinApp/Db/merlin.template """ trigger_mode: A[SignalRW[MerlinTriggerMode], PvSuffix.rbv("TriggerMode")]
# The deadtime of an Merlin controller varies depending on the exact model of camera. # Ideally we would maximize performance by dynamically retrieving the deadtime at # runtime. See https://github.com/bluesky/ophyd-async/issues/308
[docs] class MerlinTriggerLogic(DetectorTriggerLogic): """Trigger logic for MerlinDriverIO.""" def __init__(self, driver: MerlinDriverIO): self.driver = driver
[docs] def get_deadtime(self, config_values: SignalDict) -> float: return _MIN_DEAD_TIME
[docs] async def prepare_internal(self, num: int, livetime: float, deadtime: float): await self.driver.trigger_mode.set(MerlinTriggerMode.INTERNAL) await prepare_exposures(self.driver, num, livetime, deadtime)
[docs] async def prepare_edge(self, num: int, livetime: float): # Is this the right trigger mode? await self.driver.trigger_mode.set(MerlinTriggerMode.TRIGGER_START_RISING) await prepare_exposures(self.driver, num, livetime)
[docs] class MerlinDetector(AreaDetector[MerlinDriverIO]): """Create an ADMerlin AreaDetector instance. :param prefix: EPICS PV prefix for the detector :param path_provider: Provider for file paths during acquisition :param driver_suffix: Suffix for the driver PV, defaults to "cam1:" :param writer_type: Type of file writer (HDF or TIFF) :param writer_suffix: Suffix for the writer PV :param plugins: Additional areaDetector plugins to include :param config_sigs: Additional signals to include in configuration :param name: Name for the detector device """ def __init__( self, prefix: str, path_provider: PathProvider | None = None, driver_suffix="cam1:", writer_type: ADWriterType | None = ADWriterType.HDF, writer_suffix: str | None = None, plugins: dict[str, NDPluginBaseIO] | None = None, config_sigs: Sequence[SignalR] = (), name: str = "", ) -> None: driver = MerlinDriverIO(prefix + driver_suffix) super().__init__( prefix=prefix, driver=driver, arm_logic=ADArmLogic(driver), trigger_logic=MerlinTriggerLogic(driver), path_provider=path_provider, writer_type=writer_type, writer_suffix=writer_suffix, plugins=plugins, config_sigs=config_sigs, name=name, )