diff --git a/ophyd_devices/epics/devices/DelayGeneratorDG645.py b/ophyd_devices/epics/devices/DelayGeneratorDG645.py index 2b04981..d0aa10d 100644 --- a/ophyd_devices/epics/devices/DelayGeneratorDG645.py +++ b/ophyd_devices/epics/devices/DelayGeneratorDG645.py @@ -134,6 +134,10 @@ class DelayGeneratorDG645(Device): current device """ + SUB_PROGRESS = "progress" + SUB_VALUE = "value" + _default_sub = SUB_VALUE + USER_ACCESS = [ "set_channels", "_set_trigger", @@ -403,7 +407,7 @@ class DelayGeneratorDG645(Device): LINE = 6 """ value = int(trigger_source) - self.source.set(value) + self.source.put(value) def _ddg_is_okay(self, raise_on_error=False) -> None: status = self.status.read()[self.status.name]["value"] @@ -452,14 +456,14 @@ class DelayGeneratorDG645(Device): ) self._set_trigger(getattr(TriggerSource, self.set_trigger_source.get())) # Set threshold level for ext. pulses - self.level.set(self.thres_trig_level.get()) + self.level.put(self.thres_trig_level.get()) def _check_burst_cycle(self, status) -> None: """Checks burst cycle of delay generator Force readout, return value from end of burst cycle """ while True: - self.trigger_burst_readout.set(1) + self.trigger_burst_readout.put(1) if ( self.burst_cycle_finished.read()[self.burst_cycle_finished.name]["value"] == 1 and self.delay_finished.read()[self.delay_finished.name]["value"] == 1 @@ -571,7 +575,7 @@ class DelayGeneratorDG645(Device): def trigger(self) -> DeviceStatus: # if self.scaninfo.scan_type == "step": if self.source.read()[self.source.name]["value"] == int(TriggerSource.SINGLE_SHOT): - self.trigger_shot.set(1).wait() + self.trigger_shot.put(1) # status = super().trigger(status=) status = DeviceStatus(self) burst_state = threading.Thread(target=self._check_burst_cycle, args=(status,), daemon=True) @@ -590,19 +594,19 @@ class DelayGeneratorDG645(Device): "first", ], "Supported bust configs are 'all' and 'first'" - self.burstMode.set(1).wait() - self.burstCount.set(count).wait() - self.burstDelay.set(delay).wait() - self.burstPeriod.set(period).wait() + self.burstMode.put(1) + self.burstCount.put(count) + self.burstDelay.put(delay) + self.burstPeriod.put(period) if config == "all": - self.burstConfig.set(0).wait() + self.burstConfig.put(0) elif config == "first": - self.burstConfig.set(1).wait() + self.burstConfig.put(1) def burst_disable(self): """Disable the burst mode""" - self.burstMode.set(0).wait() + self.burstMode.put(0) # Automatically connect to test environmenr if directly invoked diff --git a/ophyd_devices/epics/devices/bec_scaninfo_mixin.py b/ophyd_devices/epics/devices/bec_scaninfo_mixin.py index 2a10b3b..5adf6ef 100644 --- a/ophyd_devices/epics/devices/bec_scaninfo_mixin.py +++ b/ophyd_devices/epics/devices/bec_scaninfo_mixin.py @@ -1,6 +1,9 @@ import os from bec_lib.core import DeviceManagerBase, BECMessage, MessageEndpoints +from bec_lib.core import bec_logger + +logger = bec_logger.logger class BecScaninfoMixin: @@ -39,23 +42,28 @@ class BecScaninfoMixin: info=self.bec_info_msg, ) - def _get_username(self) -> str: + def get_username(self) -> str: if not self.sim_mode: return self.device_manager.producer.get(MessageEndpoints.account()).decode() return os.getlogin() def load_scan_metadata(self) -> None: self.scan_msg = scan_msg = self._get_current_scan_msg() - self.metadata = { - "scanID": scan_msg.content["scanID"], - "RID": scan_msg.content["info"]["RID"], - "queueID": scan_msg.content["info"]["queueID"], - } - self.scanID = scan_msg.content["scanID"] - self.scan_number = scan_msg.content["info"]["scan_number"] - self.exp_time = scan_msg.content["info"]["exp_time"] - self.frames_per_trigger = scan_msg.content["info"]["frames_per_trigger"] - self.num_points = scan_msg.content["info"]["num_points"] - self.scan_type = scan_msg.content["info"].get("scan_type", "step") - self.readout_time = scan_msg.content["info"]["readout_time"] - self.username = self._get_username() + logger.info(f"{self.scan_msg}") + try: + self.metadata = { + "scanID": scan_msg.content["scanID"], + "RID": scan_msg.content["info"]["RID"], + "queueID": scan_msg.content["info"]["queueID"], + } + self.scanID = scan_msg.content["scanID"] + self.scan_number = scan_msg.content["info"]["scan_number"] + self.exp_time = scan_msg.content["info"]["exp_time"] + self.frames_per_trigger = scan_msg.content["info"]["frames_per_trigger"] + self.num_points = scan_msg.content["info"]["num_points"] + self.scan_type = scan_msg.content["info"].get("scan_type", "step") + self.readout_time = scan_msg.content["info"]["readout_time"] + except Exception as exc: + logger.error(f"Failed to load scan metadata: {exc}.") + + self.username = self.get_username() diff --git a/ophyd_devices/epics/devices/eiger9m_csaxs.py b/ophyd_devices/epics/devices/eiger9m_csaxs.py index 15f208b..7556a9b 100644 --- a/ophyd_devices/epics/devices/eiger9m_csaxs.py +++ b/ophyd_devices/epics/devices/eiger9m_csaxs.py @@ -4,9 +4,8 @@ from typing import Any, List import numpy as np from ophyd import EpicsSignal, EpicsSignalRO, EpicsSignalWithRBV -from ophyd import CamBase, DetectorBase, Device +from ophyd import DetectorBase, Device from ophyd import ADComponent as ADCpt -from ophyd.areadetector.plugins import FileBase from bec_lib.core import BECMessage, MessageEndpoints from bec_lib.core.file_utils import FileWriterMixin @@ -25,7 +24,7 @@ class EigerError(Exception): pass -class SlsDetectorCam(Device): # CamBase, FileBase): +class SlsDetectorCam(Device): detector_type = ADCpt(EpicsSignalRO, "DetectorType_RBV") setting = ADCpt(EpicsSignalWithRBV, "Setting") delay_time = ADCpt(EpicsSignalWithRBV, "DelayTime") @@ -125,6 +124,7 @@ class Eiger9mCsaxs(DetectorBase): parent=parent, **kwargs, ) + self._stopped = False if device_manager is None and not sim_mode: raise EigerError("Add DeviceManager to initialization or init with sim_mode=True") @@ -234,16 +234,22 @@ class Eiger9mCsaxs(DetectorBase): def _prep_file_writer(self) -> None: self.filepath = self.filewriter.compile_full_filename( - self.scaninfo.scan_number, "eiger.h5", 1000, 5, True + self.scaninfo.scan_number, f"{self.name}.h5", 1000, 5, True ) # self._close_file_writer() logger.info(f" std_daq output filepath {self.filepath}") - self.std_client.start_writer_async( - { - "output_file": self.filepath, - "n_images": int(self.scaninfo.num_points * self.scaninfo.frames_per_trigger), - } - ) + try: + self.std_client.start_writer_async( + { + "output_file": self.filepath, + "n_images": int(self.scaninfo.num_points * self.scaninfo.frames_per_trigger), + } + ) + except Exception as exc: + time.sleep(5) + if self.std_client.get_status()["state"] == "READY": + raise EigerError(f"Timeout of start_writer_async with {exc}") + while True: det_ctrl = self.std_client.get_status()["acquisition"]["state"] if det_ctrl == "WAITING_IMAGES": @@ -277,24 +283,29 @@ class Eiger9mCsaxs(DetectorBase): msg.dumps(), ) self.arm_acquisition() - logger.info("Waiting for detector to be armed") + logger.info("Waiting for Eiger9m to be armed") while True: det_ctrl = self.cam.detector_state.read()[self.cam.detector_state.name]["value"] if det_ctrl == int(DetectorState.RUNNING): break + if self._stopped == True: + break time.sleep(0.005) - logger.info("Detector is armed") - + logger.info("Eiger9m is armed") + self._stopped = False return super().stage() def unstage(self) -> List[object]: """unstage the detector and file writer""" - logger.info("Waiting for eiger9M to return from acquisition") + logger.info("Waiting for Eiger9M to return from acquisition") while True: det_ctrl = self.cam.acquire.read()[self.cam.acquire.name]["value"] if det_ctrl == 0: break + if self._stopped == True: + break time.sleep(0.005) + logger.info("Eiger9M finished") logger.info("Waiting for std daq to receive images") while True: @@ -302,7 +313,10 @@ class Eiger9mCsaxs(DetectorBase): # TODO if no writing was performed before if det_ctrl == "FINISHED": break + if self._stopped == True: + break time.sleep(0.005) + logger.info("Std_daq finished") # Message to BEC state = True @@ -311,7 +325,7 @@ class Eiger9mCsaxs(DetectorBase): MessageEndpoints.public_file(self.scaninfo.scanID, self.name), msg.dumps(), ) - logger.info("Eiger done") + self._stopped = False return super().unstage() def arm_acquisition(self) -> None: @@ -324,12 +338,9 @@ class Eiger9mCsaxs(DetectorBase): """Stop the scan, with camera and file writer""" self.cam.acquire.set(0) self._close_file_writer() - self.unstage() super().stop(success=success) self._stopped = True -# Automatically connect to test environmenr if directly invoked if __name__ == "__main__": eiger = Eiger9mCsaxs(name="eiger", prefix="X12SA-ES-EIGER9M:", sim_mode=True) - eiger.stage() diff --git a/ophyd_devices/epics/devices/epics_motor_ex.py b/ophyd_devices/epics/devices/epics_motor_ex.py index a2b79b9..f4a38c0 100644 --- a/ophyd_devices/epics/devices/epics_motor_ex.py +++ b/ophyd_devices/epics/devices/epics_motor_ex.py @@ -40,5 +40,6 @@ class EpicsMotorEx(EpicsMotor): # set configuration attributes for key, value in attrs.items(): + # print out attributes that are being configured print("setting ", key, "=", value) getattr(self, key).put(value) diff --git a/ophyd_devices/epics/devices/falcon_csaxs.py b/ophyd_devices/epics/devices/falcon_csaxs.py index a172572..319ce13 100644 --- a/ophyd_devices/epics/devices/falcon_csaxs.py +++ b/ophyd_devices/epics/devices/falcon_csaxs.py @@ -1,13 +1,14 @@ +import enum import os import time from typing import List from ophyd import EpicsSignal, EpicsSignalRO, EpicsSignalWithRBV, Component as Cpt, Device -from ophyd.mca import EpicsMCARecord, EpicsDXPMapping, EpicsDXPLowLevel, EpicsDXPMultiElementSystem -from ophyd.areadetector.plugins import HDF5Plugin, HDF5Plugin_V21, FilePlugin_V22 +from ophyd.mca import EpicsMCARecord +from ophyd.areadetector.plugins import HDF5Plugin_V21, FilePlugin_V22 from bec_lib.core.file_utils import FileWriterMixin -from bec_lib.core import MessageEndpoints, BECMessage, RedisConnector +from bec_lib.core import MessageEndpoints, BECMessage from bec_lib.core import bec_logger from ophyd_devices.epics.devices.bec_scaninfo_mixin import BecScaninfoMixin @@ -16,6 +17,15 @@ from ophyd_devices.utils import bec_utils logger = bec_logger.logger +class FalconError(Exception): + pass + + +class DetectorState(int, enum.Enum): + DONE = 0 + ACQUIRING = 1 + + class EpicsDXPFalcon(Device): """All high-level DXP parameters for each channel""" @@ -37,12 +47,21 @@ class EpicsDXPFalcon(Device): current_pixel = Cpt(EpicsSignalRO, "CurrentPixel") -class FalconError(Exception): - pass - - -class FalconHDF5Plugins(HDF5Plugin_V21, FilePlugin_V22): - pass +class FalconHDF5Plugins(Device): # HDF5Plugin_V21, FilePlugin_V22): + capture = Cpt(EpicsSignalWithRBV, "Capture") + enable = Cpt(EpicsSignalWithRBV, "EnableCallbacks", string=True, kind="config") + xml_file_name = Cpt(EpicsSignalWithRBV, "XMLFileName", string=True, kind="config") + lazy_open = Cpt(EpicsSignalWithRBV, "LazyOpen", string=True, doc="0='No' 1='Yes'") + temp_suffix = Cpt(EpicsSignalWithRBV, "TempSuffix", string=True) + # file_path = Cpt( + # EpicsSignalWithRBV, "FilePath", string=True, kind="config", path_semantics="posix" + # ) + file_path = Cpt(EpicsSignalWithRBV, "FilePath", string=True, kind="config") + file_name = Cpt(EpicsSignalWithRBV, "FileName", string=True, kind="config") + file_template = Cpt(EpicsSignalWithRBV, "FileTemplate", string=True, kind="config") + num_capture = Cpt(EpicsSignalWithRBV, "NumCapture", kind="config") + file_write_mode = Cpt(EpicsSignalWithRBV, "FileWriteMode", kind="config") + capture = Cpt(EpicsSignalWithRBV, "Capture") class FalconCsaxs(Device): @@ -77,6 +96,8 @@ class FalconCsaxs(Device): pixels_per_buffer = Cpt(EpicsSignal, "PixelsPerBuffer") pixels_per_run = Cpt(EpicsSignal, "PixelsPerRun") + # HDF5 + def __init__( self, prefix="", @@ -101,153 +122,141 @@ class FalconCsaxs(Device): ) if device_manager is None and not sim_mode: raise FalconError("Add DeviceManager to initialization or init with sim_mode=True") - + self._stopped = False self.name = name self.wait_for_connection() # Make sure to be connected before talking to PVs if not sim_mode: + from bec_lib.core.bec_service import SERVICE_CONFIG + self.device_manager = device_manager self._producer = self.device_manager.producer + self.service_cfg = SERVICE_CONFIG.config["service_config"]["file_writer"] else: self._producer = bec_utils.MockProducer() self.device_manager = bec_utils.MockDeviceManager() + self.scaninfo = BecScaninfoMixin(device_manager, sim_mode) + self.scaninfo.load_scan_metadata() + self.service_cfg = {"base_path": f"/sls/X12SA/data/{self.scaninfo.username}/Data10/"} self.scaninfo = BecScaninfoMixin(device_manager, sim_mode) - # TODO - self.scaninfo.username = "e21206" - self.service_cfg = {"base_path": f"/sls/X12SA/data/{self.scaninfo.username}/Data10/"} + self.scaninfo.load_scan_metadata() self.filewriter = FileWriterMixin(self.service_cfg) self.readout = 0.003 # 3 ms - self._value_pixel_per_buffer = 16 - # TODO create file template from filewriter compile filename - self._file_template = f"%s%s_{self.name}.h5" - - self.num_frames = 0 - + self._value_pixel_per_buffer = 1 # 16 self._clean_up() self._init_hdf5_saving() self._init_mapping_mode() def _clean_up(self) -> None: """Clean up""" - self.hdf5.capture.set(0) - self.stop_all.set(1) - self.erase_all.set(1) + self.hdf5.capture.put(0) + self.stop_all.put(1) + self.erase_all.put(1) def _init_hdf5_saving(self) -> None: """Set up hdf5 save parameters""" - self.hdf5.enable.set(1) # EnableCallbacks - self.hdf5.xml_file_name.set("layout.xml") # Points to hardcopy of HDF5 Layout xml file - self.hdf5.lazy_open.set(1) # Yes -> To be checked how to add FilePlugin_V21+ - self.hdf5.temp_suffix.set("temps") # -> To be checked how to add FilePlugin_V22+ + self.hdf5.enable.put(1) # EnableCallbacks + self.hdf5.xml_file_name.put("layout.xml") # Points to hardcopy of HDF5 Layout xml file + self.hdf5.lazy_open.put(1) # Yes -> To be checked how to add FilePlugin_V21+ + self.hdf5.temp_suffix.put("temps") # -> To be checked how to add FilePlugin_V22+ def _init_mapping_mode(self) -> None: """Set up mapping mode params""" - self.collect_mode.set(1) # 1 MCA Mapping, 0 MCA Spectrum - self.preset_mode.set(1) # 1 Realtime - self.input_logic_polarity.set(0) # 0 Normal, 1 Inverted - self.pixel_advance_mode.set(1) # 0 User, 1 Gate, 2 Sync - self.ignore_gate.set(1) # 1 Yes - self.auto_pixels_per_buffer.set(0) # 0 Manual 1 Auto - self.pixels_per_buffer.set(16) # - - def _get_current_scan_msg(self) -> BECMessage.ScanStatusMessage: - msg = self.device_manager.producer.get(MessageEndpoints.scan_status()) - return BECMessage.ScanStatusMessage.loads(msg) - - def _load_scan_metadata(self) -> None: - scan_msg = self._get_current_scan_msg() - self.metadata = { - "scanID": scan_msg.content["scanID"], - "RID": scan_msg.content["info"]["RID"], - "queueID": scan_msg.content["info"]["queueID"], - } - self.scanID = scan_msg.content["scanID"] - self.scan_number = scan_msg.content["info"]["scan_number"] - self.exp_time = scan_msg.content["info"]["exp_time"] - self.num_frames = scan_msg.content["info"]["num_points"] - self.username = self.device_manager.producer.get(MessageEndpoints.account()).decode() - self.device_manager.devices.mokev.read()["mokev"]["value"] - # self.triggermode = scan_msg.content["info"]["trigger_mode"] - self.filepath = self.filewriter.compile_full_filename( - self.scan_number, "falcon", 1000, 5, True - ) + self.collect_mode.put(1) # 1 MCA Mapping, 0 MCA Spectrum + self.preset_mode.put(1) # 1 Realtime + self.input_logic_polarity.put(0) # 0 Normal, 1 Inverted + self.pixel_advance_mode.put(1) # 0 User, 1 Gate, 2 Sync + self.ignore_gate.put(1) # 1 Yes + self.auto_pixels_per_buffer.put(0) # 0 Manual 1 Auto + self.pixels_per_buffer.put(16) # def _prep_det(self) -> None: """Prepare detector for acquisition""" - self.collect_mode.set(1) - self.preset_real.set(self.exposure_time) - self.pixels_per_run.set(self.num_frames) - self.auto_pixels_per_buffer.set(0) - self.pixels_per_buffer.set(self._value_pixel_per_buffer) + self.collect_mode.put(1) + self.preset_real.put(self.scaninfo.exp_time) + self.pixels_per_run.put(int(self.scaninfo.num_points * self.scaninfo.frames_per_trigger)) + self.auto_pixels_per_buffer.put(0) + self.pixels_per_buffer.put(self._value_pixel_per_buffer) def _prep_file_writer(self) -> None: """Prep HDF5 weriting""" # TODO creta filename and destination path from filepath - self.destination_path = os.path.join(self.service_cfg["base_path"]) - self.filename = f"test_{self.scan_number}" - self.hdf5.file_path.set(self.destination_path) - self.hdf5.file_name.set(self.filename) - self.hdf5.file_template.set(self._file_template) - self.hdf5.num_capture.set(self.num_frames // self._value_pixel_per_buffer + 1) - self.hdf5.file_write_mode.set(2) - self.hdf5.capture.set(1) + self.destination_path = self.filewriter.compile_full_filename( + self.scaninfo.scan_number, f"{self.name}.h5", 1000, 5, True + ) + # self.hdf5.file_path.set(self.destination_path) + file_path, file_name = os.path.split(self.destination_path) + self.hdf5.file_path.put(file_path) + self.hdf5.file_name.put(file_name) + self.hdf5.file_template.put(f"%s%s") + self.hdf5.num_capture.put(self.scaninfo.num_points // self._value_pixel_per_buffer + 1) + self.hdf5.file_write_mode.put(2) + self.hdf5.capture.put(1) def stage(self) -> List[object]: """stage the detector and file writer""" - # TODO remove once running from BEC - # self._load_scan_metadata() - self.scan_number = 10 - self.exp_time = 0.5 - self.num_frames = 3 - self.mokev = 12 + # TODO clean up needed? + # self._clean_up() + self.scaninfo.load_scan_metadata() + self.mokev = self.device_manager.devices.mokev.obj.read()[ + self.device_manager.devices.mokev.name + ]["value"] + logger.info("Waiting for pilatus2 to be armed") self._prep_det() + logger.info("Pilatus2 armed") + logger.info("Waiting for pilatus2 zmq stream to be ready") self._prep_file_writer() + logger.info("Pilatus2 zmq ready") - msg = BECMessage.FileMessage(file_path=self.filepath, done=False) - self.producer.set_and_publish( - MessageEndpoints.public_file(self.metadata["scanID"], self.name), + msg = BECMessage.FileMessage(file_path=self.destination_path, done=False) + self._producer.set_and_publish( + MessageEndpoints.public_file(self.scaninfo.scanID, self.name), msg.dumps(), ) - + self.arm_acquisition() + logger.info("Waiting for Falcon to be armed") + while True: + det_ctrl = self.state.read()[self.state.name]["value"] + if det_ctrl == int(DetectorState.ACQUIRING): + break + if self._stopped == True: + break + time.sleep(0.005) + logger.info("Falcon is armed") + self._stopped = False return super().stage() - def acquire(self) -> None: - self.start_all.set(1) + def arm_acquisition(self) -> None: + self.start_all.put(1) def unstage(self) -> List[object]: + logger.info("Waiting for Falcon to return from acquisition") + while True: + det_ctrl = self.state.read()[self.state.name]["value"] + if det_ctrl == int(DetectorState.DONE): + break + if self._stopped == True: + break + time.sleep(0.005) + logger.info("Falcon done") + # TODO needed? self._clean_up() - # TODO check if acquisition is done and successful! state = True - msg = BECMessage.FileMessage(file_path=self.filepath, done=True, successful=state) - self.producer.set_and_publish( - MessageEndpoints.public_file(self.metadata["scanID"], self.name), + msg = BECMessage.FileMessage(file_path=self.destination_path, done=True, successful=state) + self._producer.set_and_publish( + MessageEndpoints.public_file(self.scaninfo.metadata["scanID"], self.name), msg.dumps(), ) + self._stopped = False return super().unstage() - def _check_falcon_done(self) -> bool: - state = self.state.read()[f"{self.name }_state"]["value"] - if state is [0, 1]: - return not bool(state) - else: - # TODO raise error - logger.warning("Returned in unknown state") - return state - def stop(self, *, success=False) -> None: - """Stop acquisition - Stop or Stop and Erase - """ - self._clean_up.set(1) - # self.erase_all.set(1) - self.unstage() + """Stop the scan, with camera and file writer""" + self._clean_up() super().stop(success=success) self._stopped = True - # Automatically connect to test environmenr if directly invoked - if __name__ == "__main__": falcon = FalconCsaxs(name="falcon", prefix="X12SA-SITORO:", sim_mode=True) - falcon.stage() diff --git a/ophyd_devices/epics/devices/mcs_csaxs.py b/ophyd_devices/epics/devices/mcs_csaxs.py index a041b01..5d61f62 100644 --- a/ophyd_devices/epics/devices/mcs_csaxs.py +++ b/ophyd_devices/epics/devices/mcs_csaxs.py @@ -93,6 +93,9 @@ class SIS38XX(Device): class McsCsaxs(SIS38XX): + SUB_PROGRESS = "progress" + SUB_VALUE = "value" + _default_sub = SUB_VALUE # scaler = Cpt(ScalerCH, "scaler1") # mca2 = Cpt(EpicsMCARecord, "mca2") @@ -127,6 +130,7 @@ class McsCsaxs(SIS38XX): # mca30 = Cpt(EpicsMCARecord, "mca30") # mca31 = Cpt(EpicsMCARecord, "mca31") # mca32 = Cpt(EpicsMCARecord, "mca32") + current_channel = Cpt(EpicsSignalRO, "CurrentChannel", auto_monitor=True) num_lines = Cpt( bec_utils.ConfigSignal, @@ -164,7 +168,6 @@ class McsCsaxs(SIS38XX): parent=parent, **kwargs, ) - if device_manager is None and not sim_mode: raise MCSError("Add DeviceManager to initialization or init with sim_mode=True") @@ -188,6 +191,8 @@ class McsCsaxs(SIS38XX): self._stopped = False self._acquisition_done = False self._lock = threading.RLock() + self.counter = 0 + self.n_points = 0 self._init_mcs() def _init_mcs(self) -> None: @@ -212,7 +217,17 @@ class McsCsaxs(SIS38XX): for mca in self.mca_names: signal = getattr(self, mca) signal.subscribe(self._on_mca_data, run=False) - self._counter = 0 + self.current_channel.subscribe(self._progress_update, run=False) + + def _progress_update(self, value, **kwargs) -> None: + num_lines = self.num_lines.get() + max_value = self.scaninfo.num_points + self._run_subs( + sub_type=self.SUB_PROGRESS, + value=self.counter * int(self.scaninfo.num_points / num_lines) + max(value - 1, 0), + max_value=max_value, + done=bool(max_value == self.counter), + ) @threadlocked def _on_mca_data(self, *args, obj=None, **kwargs) -> None: @@ -229,19 +244,18 @@ class McsCsaxs(SIS38XX): # return self._updated = True - self._counter += 1 - logger.info(f"counter {self._counter}") - if (self.scaninfo.scan_type == "fly" and self._counter == self.num_lines.get()) or ( - self.scaninfo.scan_type == "step" and self._counter == self.scaninfo.num_points + self.counter += 1 + if (self.scaninfo.scan_type == "fly" and self.counter == self.num_lines.get()) or ( + self.scaninfo.scan_type == "step" and self.counter == self.scaninfo.num_points ): self._acquisition_done = True self.stop_all.put(1, use_complete=False) self._send_data_to_bec() - self.erase_all.set(1) + self.erase_all.put(1) # Require wait for # time.sleep(0.01) self.mca_data = defaultdict(lambda: []) - self._counter = 0 + self.counter = 0 return self.erase_start.set(1) self._send_data_to_bec() @@ -257,7 +271,6 @@ class McsCsaxs(SIS38XX): "num_lines": self.num_lines.get(), } ) - logger.info(f"{self.mca_data}") msg = BECMessage.DeviceMessage( signals=dict(self.mca_data), metadata=self.scaninfo.scan_msg.metadata, @@ -276,16 +289,16 @@ class McsCsaxs(SIS38XX): def _set_acquisition_params(self) -> None: if self.scaninfo.scan_type == "step": - n_points = int(self.scaninfo.frames_per_trigger + 1) + self.n_points = int(self.scaninfo.frames_per_trigger + 1) elif self.scaninfo.scan_type == "fly": - n_points = int(self.scaninfo.num_points / int(self.num_lines.get()) + 1) + self.n_points = int(self.scaninfo.num_points / int(self.num_lines.get()) + 1) else: raise MCSError(f"Scantype {self.scaninfo} not implemented for MCS card") - if n_points > 10000: + if self.n_points > 10000: raise MCSError( - f"Requested number of points N={n_points} exceeds hardware limit of mcs card 10000 (N-1)" + f"Requested number of points N={self.n_points} exceeds hardware limit of mcs card 10000 (N-1)" ) - self.num_use_all.set(n_points) + self.num_use_all.set(self.n_points) self.preset_real.set(0) def _set_trigger(self, trigger_source: TriggerSource) -> None: @@ -299,7 +312,7 @@ class McsCsaxs(SIS38XX): Check ReadoutMode class for more information about options """ # self.read_mode.set(ReadoutMode.EVENT) - self.erase_all.set(1) + self.erase_all.put(1) self.read_mode.set(ReadoutMode.EVENT) def _force_readout_mcs_card(self) -> None: @@ -307,7 +320,7 @@ class McsCsaxs(SIS38XX): def stage(self) -> List[object]: """stage the detector and file writer""" - logger.info("Stage Eiger") + logger.info("Stage mcs") self.scaninfo.load_scan_metadata() self._prep_det() self._prep_readout() @@ -347,7 +360,7 @@ class McsCsaxs(SIS38XX): Start: start_all Erase/Start: erase_start """ - self._counter = 0 + self.counter = 0 self.erase_start.set(1) # self.start_all.set(1) @@ -359,7 +372,7 @@ class McsCsaxs(SIS38XX): # self.erase_all.set(1) self._stopped = True self._acquisition_done = True - self._counter = 0 + self.counter = 0 super().stop(success=success) diff --git a/ophyd_devices/epics/devices/pilatus_csaxs.py b/ophyd_devices/epics/devices/pilatus_csaxs.py index db97798..2efee34 100644 --- a/ophyd_devices/epics/devices/pilatus_csaxs.py +++ b/ophyd_devices/epics/devices/pilatus_csaxs.py @@ -6,11 +6,12 @@ from typing import List import requests import numpy as np -from ophyd.areadetector import ADComponent as ADCpt, PilatusDetectorCam, DetectorBase -from ophyd.areadetector.plugins import FileBase +from ophyd import EpicsSignal, EpicsSignalRO, EpicsSignalWithRBV +from ophyd import DetectorBase, Device +from ophyd import ADComponent as ADCpt from ophyd_devices.utils import bec_utils as bec_utils -from bec_lib.core import BECMessage, MessageEndpoints, RedisConnector +from bec_lib.core import BECMessage, MessageEndpoints from bec_lib.core.file_utils import FileWriterMixin from bec_lib.core import bec_logger @@ -24,10 +25,6 @@ class PilatusError(Exception): pass -class PilatusDetectorCamEx(PilatusDetectorCam, FileBase): - pass - - class TriggerSource(int, enum.Enum): INTERNAL = 0 EXT_ENABLE = 1 @@ -36,6 +33,72 @@ class TriggerSource(int, enum.Enum): ALGINMENT = 4 +class SlsDetectorCam(Device): # CamBase, FileBase): + # detector_type = ADCpt(EpicsSignalRO, "DetectorType_RBV") + # setting = ADCpt(EpicsSignalWithRBV, "Setting") + # beam_energy = ADCpt(EpicsSignalWithRBV, "BeamEnergy") + # enable_trimbits = ADCpt(EpicsSignalWithRBV, "Trimbits") + # bit_depth = ADCpt(EpicsSignalWithRBV, "BitDepth") + # trigger_software = ADCpt(EpicsSignal, "TriggerSoftware") + # high_voltage = ADCpt(EpicsSignalWithRBV, "HighVoltage") + # Receiver and data callback + # receiver_mode = ADCpt(EpicsSignalWithRBV, "ReceiverMode") + # receiver_stream = ADCpt(EpicsSignalWithRBV, "ReceiverStream") + # enable_data = ADCpt(EpicsSignalWithRBV, "UseDataCallback") + # missed_packets = ADCpt(EpicsSignalRO, "ReceiverMissedPackets_RBV") + # # Direct settings access + # setup_file = ADCpt(EpicsSignal, "SetupFile") + # load_setup = ADCpt(EpicsSignal, "LoadSetup") + # command = ADCpt(EpicsSignal, "Command") + # Mythen 3 + # counter_mask = ADCpt(EpicsSignalWithRBV, "CounterMask") + # counter1_threshold = ADCpt(EpicsSignalWithRBV, "Counter1Threshold") + # counter2_threshold = ADCpt(EpicsSignalWithRBV, "Counter2Threshold") + # counter3_threshold = ADCpt(EpicsSignalWithRBV, "Counter3Threshold") + # gate1_delay = ADCpt(EpicsSignalWithRBV, "Gate1Delay") + # gate1_width = ADCpt(EpicsSignalWithRBV, "Gate1Width") + # gate2_delay = ADCpt(EpicsSignalWithRBV, "Gate2Delay") + # gate2_width = ADCpt(EpicsSignalWithRBV, "Gate2Width") + # gate3_delay = ADCpt(EpicsSignalWithRBV, "Gate3Delay") + # gate3_width = ADCpt(EpicsSignalWithRBV, "Gate3Width") + # Moench + # json_frame_mode = ADCpt(EpicsSignalWithRBV, "JsonFrameMode") + # json_detector_mode = ADCpt(EpicsSignalWithRBV, "JsonDetectorMode") + + # Eiger9M + # delay_time = ADCpt(EpicsSignalWithRBV, "DelayTime") + # num_frames = ADCpt(EpicsSignalWithRBV, "NumFrames") + # acquire = ADCpt(EpicsSignal, "Acquire") + # acquire_time = ADCpt(EpicsSignal, 'AcquireTime') + # detector_state = ADCpt(EpicsSignalRO, "DetectorState_RBV") + # threshold_energy = ADCpt(EpicsSignalWithRBV, "ThresholdEnergy") + # num_gates = ADCpt(EpicsSignalWithRBV, "NumGates") + # num_cycles = ADCpt(EpicsSignalWithRBV, "NumCycles") + # timing_mode = ADCpt(EpicsSignalWithRBV, "TimingMode") + + # Pilatus_2 300k + num_images = ADCpt(EpicsSignalWithRBV, "NumImages") + num_exposures = ADCpt(EpicsSignalWithRBV, "NumExposures") + delay_time = ADCpt(EpicsSignalWithRBV, "NumExposures") + trigger_mode = ADCpt(EpicsSignalWithRBV, "TriggerMode") + acquire = ADCpt(EpicsSignal, "Acquire") + armed = ADCpt(EpicsSignalRO, "Armed") + + read_file_timeout = ADCpt(EpicsSignal, "ImageFileTmot") + detector_state = ADCpt(EpicsSignalRO, "StatusMessage_RBV") + status_message_camserver = ADCpt(EpicsSignalRO, "StringFromServer_RBV") + acquire_time = ADCpt(EpicsSignal, "AcquireTime") + acquire_period = ADCpt(EpicsSignal, "AcquirePeriod") + threshold_energy = ADCpt(EpicsSignalWithRBV, "ThresholdEnergy") + file_path = ADCpt(EpicsSignalWithRBV, "FilePath") + file_name = ADCpt(EpicsSignalWithRBV, "FileName") + file_number = ADCpt(EpicsSignalWithRBV, "FileNumber") + auto_increment = ADCpt(EpicsSignalWithRBV, "AutoIncrement") + file_template = ADCpt(EpicsSignalWithRBV, "FileTemplate") + file_format = ADCpt(EpicsSignalWithRBV, "FileNumber") + gap_fill = ADCpt(EpicsSignalWithRBV, "GapFill") + + class PilatusCsaxs(DetectorBase): """Pilatus_2 300k detector for CSAXS @@ -43,12 +106,12 @@ class PilatusCsaxs(DetectorBase): Device class: PilatusDetectorCamEx Attributes: - name str: 'eiger' + name str: 'pilatus_2' prefix (str): PV prefix (X12SA-ES-PILATUS300K:) """ - cam = ADCpt(PilatusDetectorCamEx, "cam1:") + cam = ADCpt(SlsDetectorCam, "cam1:") def __init__( self, @@ -91,7 +154,7 @@ class PilatusCsaxs(DetectorBase): self.service_cfg = {"base_path": f"/sls/X12SA/data/{self.scaninfo.username}/Data10/"} self.scaninfo = BecScaninfoMixin(device_manager, sim_mode) - self.filepath = "" + self.filepath_h5 = "" self.filewriter = FileWriterMixin(self.service_cfg) self.readout = 1e-3 # 3 ms @@ -100,28 +163,6 @@ class PilatusCsaxs(DetectorBase): msg = self.device_manager.producer.get(MessageEndpoints.scan_status()) return BECMessage.ScanStatusMessage.loads(msg) - # def _load_scan_metadata(self) -> None: - # scan_msg = self._get_current_scan_msg() - # self.metadata = { - # "scanID": scan_msg.content["scanID"], - # "RID": scan_msg.content["info"]["RID"], - # "queueID": scan_msg.content["info"]["queueID"], - # } - # self.scanID = scan_msg.content["scanID"] - # self.scan_number = scan_msg.content["info"]["scan_number"] - # self.exp_time = scan_msg.content["info"]["exp_time"] - # self.num_frames = scan_msg.content["info"]["num_points"] - # self.username = self.device_manager.producer.get( - # MessageEndpoints.account() - # ).decode() - # self.device_manager.devices.mokev.read()["mokev"]["value"] - # # self.triggermode = scan_msg.content["info"]["trigger_mode"] - # # self.filename = self.filewriter.compile_full_filename( - # # self.scan_number, "pilatus_2", 1000, 5, True - # # ) - # # TODO fix with BEC running - # # self.filename = '/sls/X12SA/Data10/e21206/data/test.h5' - def _prep_det(self) -> None: # TODO slow reaction, seemed to have timeout. self._set_det_threshold() @@ -143,7 +184,7 @@ class PilatusCsaxs(DetectorBase): # self.cam.acquire_period.set(self.exp_time + self.readout) self.cam.num_images.set(int(self.scaninfo.num_points * self.scaninfo.frames_per_trigger)) self.cam.num_exposures.set(1) - self._set_trigger(TriggerSource.INTERNAL) # EXT_TRIGGER) + self._set_trigger(TriggerSource.EXT_ENABLE) # EXT_TRIGGER) def _set_trigger(self, trigger_source: TriggerSource) -> None: """Set trigger source for the detector, either directly to value or TriggerSource.* with @@ -164,12 +205,12 @@ class PilatusCsaxs(DetectorBase): self.filepath_h5 = self.filewriter.compile_full_filename( self.scaninfo.scan_number, "pilatus_2.h5", 1000, 5, True ) - self.cam.file_path.set(f"/dev/shm/zmq/") - self.cam.file_name.set(f"{self.scaninfo.username}_2_{self.scaninfo.scan_number:05d}") - self.cam.auto_increment.set(1) # auto increment - self.cam.file_number.set(0) # first iter - self.cam.file_format.set(0) # 0: TIFF - self.cam.file_template.set("%s%s_%5.5d.cbf") + self.cam.file_path.put(f"/dev/shm/zmq/") + self.cam.file_name.put(f"{self.scaninfo.username}_2_{self.scaninfo.scan_number:05d}") + self.cam.auto_increment.put(1) # auto increment + self.cam.file_number.put(0) # first iter + self.cam.file_format.put(0) # 0: TIFF + self.cam.file_template.put("%s%s_%5.5d.cbf") # compile filename basepath = f"/sls/X12SA/data/{self.scaninfo.username}/Data10/pilatus_2/" @@ -254,17 +295,19 @@ class PilatusCsaxs(DetectorBase): if not res.ok: res.raise_for_status() except Exception as exc: - logger.info("exc") + logger.info(f"Pilatus2 wait threw Exception: {exc}") def _close_file_writer(self) -> None: """Close the file writer for pilatus_2 a zmq service is running on xbl-daq-34 that is waiting for a zmq message to stop the writer for the pilatus_2 x12sa-pd-2 """ - - res = requests.delete(url="http://x12sa-pd-2:8080/stream/pilatus_2") - if not res.ok: - res.raise_for_status() + try: + res = requests.delete(url="http://x12sa-pd-2:8080/stream/pilatus_2") + if not res.ok: + res.raise_for_status() + except Exception as exc: + logger.info(f"Pilatus2 delete threw Exception: {exc}") def _stop_file_writer(self) -> None: res = requests.put( @@ -278,34 +321,48 @@ class PilatusCsaxs(DetectorBase): def stage(self) -> List[object]: """stage the detector and file writer""" + self._close_file_writer() + self._stop_file_writer() self.scaninfo.load_scan_metadata() self.mokev = self.device_manager.devices.mokev.obj.read()[ self.device_manager.devices.mokev.name ]["value"] + logger.info("Waiting for pilatus2 to be armed") self._prep_det() + logger.info("Pilatus2 armed") + logger.info("Waiting for pilatus2 zmq stream to be ready") self._prep_file_writer() - self.acquire() + logger.info("Pilatus2 zmq ready") + msg = BECMessage.FileMessage( + file_path=self.filepath_h5, done=False, metadata={"input_path": self.destination_path} + ) return super().stage() + def pre_scan(self) -> None: + self.acquire() + def unstage(self) -> List[object]: """unstage the detector and file writer""" # Reset to software trigger self.triggermode = 0 + # TODO if images are missing, consider adding delay self._close_file_writer() self._stop_file_writer() # Only sent this out once data is written to disk since cbf to hdf5 converter will be triggered - msg = BECMessage.FileMessage(file_path=self.filepath, done=True) + msg = BECMessage.FileMessage( + file_path=self.filepath_h5, done=True, metadata={"input_path": self.destination_path} + ) self._producer.set_and_publish( MessageEndpoints.public_file(self.scaninfo.scanID, self.name), msg.dumps(), ) - msg = BECMessage.FileMessage(file_path=self.filepath, done=True) self._producer.set_and_publish( MessageEndpoints.file_event(self.name), msg.dumps(), ) + logger.info("Pilatus2 done") return super().unstage() def acquire(self) -> None: diff --git a/ophyd_devices/galil/sgalil_ophyd.py b/ophyd_devices/galil/sgalil_ophyd.py index ae71a3f..3934ba0 100644 --- a/ophyd_devices/galil/sgalil_ophyd.py +++ b/ophyd_devices/galil/sgalil_ophyd.py @@ -155,7 +155,8 @@ class GalilController(Controller): def stop_all_axes(self) -> str: # return self.socket_put_and_receive(f"XQ#STOP,1") # Command stops all threads and motors! - return self.socket_put_and_receive(f"ST") + # self.socket_put_and_receive(f"ST") + return self.socket_put_and_receive(f"AB") def axis_is_referenced(self) -> bool: return bool(float(self.socket_put_and_receive(f"MG allaxref").strip())) @@ -405,7 +406,7 @@ class GalilSetpointSignal(GalilSignalBase): Returns: float: setpoint / target value """ - return self.setpoint + return self.setpoint * self.parent.sign @retry_once @threadlocked