mirror of
https://github.com/bec-project/ophyd_devices.git
synced 2025-06-24 03:38:00 +02:00
refactor: eiger, add trigger function
This commit is contained in:
@ -17,18 +17,20 @@ from bec_lib.core.file_utils import FileWriterMixin
|
|||||||
from bec_lib.core import bec_logger
|
from bec_lib.core import bec_logger
|
||||||
|
|
||||||
from ophyd_devices.epics.devices.bec_scaninfo_mixin import BecScaninfoMixin
|
from ophyd_devices.epics.devices.bec_scaninfo_mixin import BecScaninfoMixin
|
||||||
from ophyd_devices.utils import bec_utils
|
from ophyd_devices.utils import bec_utils
|
||||||
|
|
||||||
logger = bec_logger.logger
|
logger = bec_logger.logger
|
||||||
|
|
||||||
|
|
||||||
class EigerError(Exception):
|
class EigerError(Exception):
|
||||||
'''Base class for exceptions in this module.'''
|
"""Base class for exceptions in this module."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class EigerTimeoutError(Exception):
|
class EigerTimeoutError(Exception):
|
||||||
'''Raised when the Eiger does not respond in time during unstage.'''
|
"""Raised when the Eiger does not respond in time during unstage."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@ -37,6 +39,7 @@ class SlsDetectorCam(Device):
|
|||||||
|
|
||||||
Base class to map EPICS PVs to ophyd signals.
|
Base class to map EPICS PVs to ophyd signals.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
threshold_energy = ADCpt(EpicsSignalWithRBV, "ThresholdEnergy")
|
threshold_energy = ADCpt(EpicsSignalWithRBV, "ThresholdEnergy")
|
||||||
beam_energy = ADCpt(EpicsSignalWithRBV, "BeamEnergy")
|
beam_energy = ADCpt(EpicsSignalWithRBV, "BeamEnergy")
|
||||||
bit_depth = ADCpt(EpicsSignalWithRBV, "BitDepth")
|
bit_depth = ADCpt(EpicsSignalWithRBV, "BitDepth")
|
||||||
@ -50,6 +53,7 @@ class SlsDetectorCam(Device):
|
|||||||
|
|
||||||
class TriggerSource(int, enum.Enum):
|
class TriggerSource(int, enum.Enum):
|
||||||
"""Trigger signals for Eiger9M detector"""
|
"""Trigger signals for Eiger9M detector"""
|
||||||
|
|
||||||
AUTO = 0
|
AUTO = 0
|
||||||
TRIGGER = 1
|
TRIGGER = 1
|
||||||
GATING = 2
|
GATING = 2
|
||||||
@ -57,7 +61,8 @@ class TriggerSource(int, enum.Enum):
|
|||||||
|
|
||||||
|
|
||||||
class DetectorState(int, enum.Enum):
|
class DetectorState(int, enum.Enum):
|
||||||
""" Detector states for Eiger9M detector"""
|
"""Detector states for Eiger9M detector"""
|
||||||
|
|
||||||
IDLE = 0
|
IDLE = 0
|
||||||
ERROR = 1
|
ERROR = 1
|
||||||
WAITING = 2
|
WAITING = 2
|
||||||
@ -108,10 +113,10 @@ class Eiger9mCsaxs(DetectorBase):
|
|||||||
#TODO add here the parameters for kind, read_attrs, configuration_attrs, parent
|
#TODO add here the parameters for kind, read_attrs, configuration_attrs, parent
|
||||||
prefix (str): PV prefix (X12SA-ES-EIGER9M:)
|
prefix (str): PV prefix (X12SA-ES-EIGER9M:)
|
||||||
name (str): 'eiger'
|
name (str): 'eiger'
|
||||||
kind (str):
|
kind (str):
|
||||||
read_attrs (list):
|
read_attrs (list):
|
||||||
configuration_attrs (list):
|
configuration_attrs (list):
|
||||||
parent (object):
|
parent (object):
|
||||||
device_manager (object): BEC device manager
|
device_manager (object): BEC device manager
|
||||||
sim_mode (bool): simulation mode to start the detector without BEC, e.g. from ipython shell
|
sim_mode (bool): simulation mode to start the detector without BEC, e.g. from ipython shell
|
||||||
"""
|
"""
|
||||||
@ -126,9 +131,9 @@ class Eiger9mCsaxs(DetectorBase):
|
|||||||
)
|
)
|
||||||
if device_manager is None and not sim_mode:
|
if device_manager is None and not sim_mode:
|
||||||
raise EigerError("Add DeviceManager to initialization or init with sim_mode=True")
|
raise EigerError("Add DeviceManager to initialization or init with sim_mode=True")
|
||||||
|
|
||||||
# Not sure if this is needed, comment it for now!
|
# Not sure if this is needed, comment it for now!
|
||||||
#self._lock = threading.RLock()
|
# self._lock = threading.RLock()
|
||||||
self._stopped = False
|
self._stopped = False
|
||||||
self.name = name
|
self.name = name
|
||||||
self.wait_for_connection()
|
self.wait_for_connection()
|
||||||
@ -153,31 +158,30 @@ class Eiger9mCsaxs(DetectorBase):
|
|||||||
self.filewriter = FileWriterMixin(self.service_cfg)
|
self.filewriter = FileWriterMixin(self.service_cfg)
|
||||||
self._init()
|
self._init()
|
||||||
|
|
||||||
#TODO function for abstract class?
|
# TODO function for abstract class?
|
||||||
def _init(self) -> None:
|
def _init(self) -> None:
|
||||||
"""Initialize detector, filewriter and set default parameters
|
"""Initialize detector, filewriter and set default parameters"""
|
||||||
"""
|
|
||||||
self._default_parameter()
|
self._default_parameter()
|
||||||
self._init_detector()
|
self._init_detector()
|
||||||
self._init_filewriter()
|
self._init_filewriter()
|
||||||
|
|
||||||
#TODO function for abstract class?
|
# TODO function for abstract class?
|
||||||
def _default_parameter(self) -> None:
|
def _default_parameter(self) -> None:
|
||||||
"""Set default parameters for Eiger 9M
|
"""Set default parameters for Eiger 9M
|
||||||
readout (float) : readout time in seconds
|
readout (float) : readout time in seconds
|
||||||
"""
|
"""
|
||||||
self.reduce_readout = 1e-3
|
self.reduce_readout = 1e-3
|
||||||
|
|
||||||
#TODO function for abstract class?
|
# TODO function for abstract class?
|
||||||
def _init_detector(self) -> None:
|
def _init_detector(self) -> None:
|
||||||
"""Init parameters for Eiger 9m.
|
"""Init parameters for Eiger 9m.
|
||||||
Depends on hardware configuration and delay generators.
|
Depends on hardware configuration and delay generators.
|
||||||
At this point it is set up for gating mode (09/2023).
|
At this point it is set up for gating mode (09/2023).
|
||||||
"""
|
"""
|
||||||
self.stop_acquisition()
|
self.stop_acquisition()
|
||||||
self._set_trigger(TriggerSource.GATING)
|
self._set_trigger(TriggerSource.GATING)
|
||||||
|
|
||||||
#TODO function for abstract class?
|
# TODO function for abstract class?
|
||||||
def _init_filewriter(self) -> None:
|
def _init_filewriter(self) -> None:
|
||||||
"""Init parameters for filewriter.
|
"""Init parameters for filewriter.
|
||||||
For the Eiger9M, the data backend is std_daq client.
|
For the Eiger9M, the data backend is std_daq client.
|
||||||
@ -187,10 +191,10 @@ class Eiger9mCsaxs(DetectorBase):
|
|||||||
self.std_client = StdDaqClient(url_base=self.std_rest_server_url)
|
self.std_client = StdDaqClient(url_base=self.std_rest_server_url)
|
||||||
self.std_client.stop_writer()
|
self.std_client.stop_writer()
|
||||||
timeout = 0
|
timeout = 0
|
||||||
# TODO changing e-account was not possible during beamtimes.
|
# TODO changing e-account was not possible during beamtimes.
|
||||||
# self._update_std_cfg("writer_user_id", int(self.scaninfo.username.strip(" e")))
|
# self._update_std_cfg("writer_user_id", int(self.scaninfo.username.strip(" e")))
|
||||||
# time.sleep(5)
|
# time.sleep(5)
|
||||||
#TODO is this the only state to wait for or should we wait for more from the std_daq client?
|
# TODO is this the only state to wait for or should we wait for more from the std_daq client?
|
||||||
while not self.std_client.get_status()["state"] == "READY":
|
while not self.std_client.get_status()["state"] == "READY":
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
timeout = timeout + 0.1
|
timeout = timeout + 0.1
|
||||||
@ -205,7 +209,7 @@ class Eiger9mCsaxs(DetectorBase):
|
|||||||
|
|
||||||
def _update_std_cfg(self, cfg_key: str, value: Any) -> None:
|
def _update_std_cfg(self, cfg_key: str, value: Any) -> None:
|
||||||
"""Update std_daq config with new e-account for the current beamtime"""
|
"""Update std_daq config with new e-account for the current beamtime"""
|
||||||
#TODO Do we need all the loggers here, should this be properly refactored with a DEBUG mode?
|
# TODO Do we need all the loggers here, should this be properly refactored with a DEBUG mode?
|
||||||
cfg = self.std_client.get_config()
|
cfg = self.std_client.get_config()
|
||||||
old_value = cfg.get(cfg_key)
|
old_value = cfg.get(cfg_key)
|
||||||
logger.info(old_value)
|
logger.info(old_value)
|
||||||
@ -222,7 +226,7 @@ class Eiger9mCsaxs(DetectorBase):
|
|||||||
logger.info(f"Updated std_daq config for key {cfg_key} from {old_value} to {value}")
|
logger.info(f"Updated std_daq config for key {cfg_key} from {old_value} to {value}")
|
||||||
self.std_client.set_config(cfg)
|
self.std_client.set_config(cfg)
|
||||||
|
|
||||||
#TODO function for abstract class?
|
# TODO function for abstract class?
|
||||||
def stage(self) -> List[object]:
|
def stage(self) -> List[object]:
|
||||||
"""Stage command, called from BEC in preparation of a scan.
|
"""Stage command, called from BEC in preparation of a scan.
|
||||||
The device needs to return with a state once it is ready to start the scan!
|
The device needs to return with a state once it is ready to start the scan!
|
||||||
@ -235,23 +239,23 @@ class Eiger9mCsaxs(DetectorBase):
|
|||||||
self.device_manager.devices.mokev.name
|
self.device_manager.devices.mokev.name
|
||||||
]["value"]
|
]["value"]
|
||||||
# Prepare file writer and detector
|
# Prepare file writer and detector
|
||||||
#TODO refactor logger.info to DEBUG mode?
|
# TODO refactor logger.info to DEBUG mode?
|
||||||
#logger.info("Waiting for std daq to be armed")
|
# logger.info("Waiting for std daq to be armed")
|
||||||
self._prep_file_writer()
|
self._prep_file_writer()
|
||||||
#logger.info("std_daq is ready")
|
# logger.info("std_daq is ready")
|
||||||
self._prep_det()
|
self._prep_det()
|
||||||
#logger.info("Eiger9m is ready")
|
# logger.info("Eiger9m is ready")
|
||||||
self._publish_file_location()
|
self._publish_file_location()
|
||||||
self.arm_acquisition()
|
self.arm_acquisition()
|
||||||
#TODO Fix should take place in EPICS or directly on the hardware!
|
# TODO Fix should take place in EPICS or directly on the hardware!
|
||||||
# We observed that the detector missed triggers in the beginning in case BEC was to fast. Adding 50ms delay solved this
|
# We observed that the detector missed triggers in the beginning in case BEC was to fast. Adding 50ms delay solved this
|
||||||
time.sleep(0.05)
|
time.sleep(0.05)
|
||||||
return super().stage()
|
return super().stage()
|
||||||
|
|
||||||
#TODO function for abstract class?
|
# TODO function for abstract class?
|
||||||
def _prep_file_writer(self) -> None:
|
def _prep_file_writer(self) -> None:
|
||||||
"""Prepare file writer for scan
|
"""Prepare file writer for scan
|
||||||
|
|
||||||
self.filewriter is a FileWriterMixin object that hosts logic for compiling the filepath
|
self.filewriter is a FileWriterMixin object that hosts logic for compiling the filepath
|
||||||
"""
|
"""
|
||||||
self.filepath = self.filewriter.compile_full_filename(
|
self.filepath = self.filewriter.compile_full_filename(
|
||||||
@ -263,7 +267,7 @@ class Eiger9mCsaxs(DetectorBase):
|
|||||||
|
|
||||||
self._close_file_writer()
|
self._close_file_writer()
|
||||||
logger.info(f" std_daq output filepath {self.filepath}")
|
logger.info(f" std_daq output filepath {self.filepath}")
|
||||||
#TODO Discuss with Leo if this is needed, or how to start the async writing best
|
# TODO Discuss with Leo if this is needed, or how to start the async writing best
|
||||||
try:
|
try:
|
||||||
self.std_client.start_writer_async(
|
self.std_client.start_writer_async(
|
||||||
{
|
{
|
||||||
@ -281,14 +285,14 @@ class Eiger9mCsaxs(DetectorBase):
|
|||||||
if det_ctrl == "WAITING_IMAGES":
|
if det_ctrl == "WAITING_IMAGES":
|
||||||
break
|
break
|
||||||
time.sleep(0.005)
|
time.sleep(0.005)
|
||||||
|
|
||||||
#TODO function for abstract class?
|
# TODO function for abstract class?
|
||||||
def _close_file_writer(self) -> None:
|
def _close_file_writer(self) -> None:
|
||||||
"""Close file writer"""
|
"""Close file writer"""
|
||||||
self.std_client.stop_writer()
|
self.std_client.stop_writer()
|
||||||
#TODO can I wait for a status message here maybe? To ensure writer returned
|
# TODO can I wait for a status message here maybe? To ensure writer returned
|
||||||
|
|
||||||
#TODO function for abstract class?
|
# TODO function for abstract class?
|
||||||
def _prep_det(self) -> None:
|
def _prep_det(self) -> None:
|
||||||
"""Prepare detector for scan.
|
"""Prepare detector for scan.
|
||||||
Includes checking the detector threshold, setting the acquisition parameters and setting the trigger source
|
Includes checking the detector threshold, setting the acquisition parameters and setting the trigger source
|
||||||
@ -316,6 +320,7 @@ class Eiger9mCsaxs(DetectorBase):
|
|||||||
self.cam.num_images.put(int(self.scaninfo.num_points * self.scaninfo.frames_per_trigger))
|
self.cam.num_images.put(int(self.scaninfo.num_points * self.scaninfo.frames_per_trigger))
|
||||||
self.cam.num_frames.put(1)
|
self.cam.num_frames.put(1)
|
||||||
|
|
||||||
|
# TODO function for abstract class? + call it for each scan??
|
||||||
def _set_trigger(self, trigger_source: TriggerSource) -> None:
|
def _set_trigger(self, trigger_source: TriggerSource) -> None:
|
||||||
"""Set trigger source for the detector.
|
"""Set trigger source for the detector.
|
||||||
Check the TriggerSource enum for possible values
|
Check the TriggerSource enum for possible values
|
||||||
@ -339,11 +344,10 @@ class Eiger9mCsaxs(DetectorBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def arm_acquisition(self) -> None:
|
def arm_acquisition(self) -> None:
|
||||||
"""Arm detector for acquisition
|
"""Arm detector for acquisition"""
|
||||||
"""
|
|
||||||
self.cam.acquire.put(1)
|
self.cam.acquire.put(1)
|
||||||
logger.info("Waiting for Eiger9m to be armed")
|
logger.info("Waiting for Eiger9m to be armed")
|
||||||
#TODO add here timeout?
|
# TODO add here timeout?
|
||||||
while True:
|
while True:
|
||||||
det_ctrl = self.cam.detector_state.read()[self.cam.detector_state.name]["value"]
|
det_ctrl = self.cam.detector_state.read()[self.cam.detector_state.name]["value"]
|
||||||
if det_ctrl == int(DetectorState.RUNNING):
|
if det_ctrl == int(DetectorState.RUNNING):
|
||||||
@ -352,8 +356,13 @@ class Eiger9mCsaxs(DetectorBase):
|
|||||||
break
|
break
|
||||||
time.sleep(0.005)
|
time.sleep(0.005)
|
||||||
logger.info("Eiger9m is armed")
|
logger.info("Eiger9m is armed")
|
||||||
|
|
||||||
#TODO needed? if yes why only for the eiger9m?
|
# TODO is this correct? -> for hardware triggering, nothing should happen upon trigger signal
|
||||||
|
# Comment this otherwise!
|
||||||
|
def trigger(self) -> DeviceStatus:
|
||||||
|
return super().trigger()
|
||||||
|
|
||||||
|
# TODO threadlocked needed? if yes why only for the eiger9m?
|
||||||
@threadlocked
|
@threadlocked
|
||||||
def unstage(self) -> List[object]:
|
def unstage(self) -> List[object]:
|
||||||
"""unstage the detector and file writer"""
|
"""unstage the detector and file writer"""
|
||||||
|
Reference in New Issue
Block a user