refactor: eiger, add trigger function

This commit is contained in:
Christian Appel
2023-10-24 10:55:09 +02:00
parent 78765100ba
commit e6d05c9d02

View File

@ -23,12 +23,14 @@ 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
@ -128,7 +133,7 @@ class Eiger9mCsaxs(DetectorBase):
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,22 +158,21 @@ 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.
@ -177,7 +181,7 @@ class Eiger9mCsaxs(DetectorBase):
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.
@ -190,7 +194,7 @@ class Eiger9mCsaxs(DetectorBase):
# 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,20 +239,20 @@ 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
@ -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(
{ {
@ -282,13 +286,13 @@ class Eiger9mCsaxs(DetectorBase):
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):
@ -353,7 +357,12 @@ class Eiger9mCsaxs(DetectorBase):
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"""