diff --git a/phoenix_bec/device_configs/phoenix_devices.yaml b/phoenix_bec/device_configs/phoenix_devices.yaml index 31fa2c9..e2a52dd 100644 --- a/phoenix_bec/device_configs/phoenix_devices.yaml +++ b/phoenix_bec/device_configs/phoenix_devices.yaml @@ -1,3 +1,20 @@ +falcon_nohdf5: + description: Falcon detector x-ray fluoresence II + deviceClass: phoenix_bec.devices.falcon_phoenix_no_hdf5.FalconPhoenix + deviceConfig: + prefix: 'X07MB-SITORO:' + deviceTags: + - phoenix + - falcon + - no hdf5 + - phoenix_devices.yaml + onFailure: buffer + enabled: true + readoutPriority: async + softwareTrigger: false + + + ################################################### # # phoenix standard devices (motors) @@ -16,7 +33,7 @@ PH_TTL: onFailure: buffer enabled: true readoutPriority: monitored - softwareTrigger: false + softwareTrigger: true PH_Dummy: diff --git a/phoenix_bec/devices/falcon_phoenix_no_hdf5.py b/phoenix_bec/devices/falcon_phoenix_no_hdf5.py index a1dcff2..f67cf0d 100644 --- a/phoenix_bec/devices/falcon_phoenix_no_hdf5.py +++ b/phoenix_bec/devices/falcon_phoenix_no_hdf5.py @@ -97,6 +97,7 @@ class FalconHDF5Plugins(Device): array_counter = Cpt(EpicsSignalWithRBV, "ArrayCounter", kind="config") """ + class FalconSetup(CustomDetectorMixin): """ Falcon setup class for cSAXS @@ -113,7 +114,7 @@ class FalconSetup(CustomDetectorMixin): """Initialize Falcon detector""" self.initialize_default_parameter() self.initialize_detector() - self.initialize_detector_backend() + # self.initialize_detector_backend() def initialize_default_parameter(self) -> None: """ @@ -139,12 +140,12 @@ class FalconSetup(CustomDetectorMixin): def initialize_detector(self) -> None: """Initialize Falcon detector""" self.stop_detector() - self.stop_detector_backend() + # self.stop_detector_backend() self.set_trigger( - mapping_mode=MappingSource.MAPPING, trigger_source=TriggerSource.GATE, ignore_gate=0 + mapping_mode=MappingSource.SPECTRUM, trigger_source=TriggerSource.GATE, ignore_gate=0 ) # 1 Realtime - self.parent.preset_mode.put(1) + self.parent.preset_mode.put(0) # 0 Normal, 1 Inverted self.parent.input_logic_polarity.put(0) # 0 Manual 1 Auto @@ -154,30 +155,34 @@ class FalconSetup(CustomDetectorMixin): def initialize_detector_backend(self) -> None: """Initialize the detector backend for Falcon.""" - w=0 - #---------------------------------------------------------------------- - #self.parent.hdf5.enable.put(1) + w = 0 + # ---------------------------------------------------------------------- + # self.parent.hdf5.enable.put(1) # file location of h5 layout for cSAXS - #self.parent.hdf5.xml_file_name.put("layout.xml") + # self.parent.hdf5.xml_file_name.put("layout.xml") # TODO Check if lazy open is needed and wanted! - #self.parent.hdf5.lazy_open.put(1) - #self.parent.hdf5.temp_suffix.put("") + # self.parent.hdf5.lazy_open.put(1) + # self.parent.hdf5.temp_suffix.put("") # size of queue for number of spectra allowed in the buffer, if too small at high throughput, data is lost - #self.parent.hdf5.queue_size.put(2000) + # self.parent.hdf5.queue_size.put(2000) # Segmentation into Spectra within EPICS, 1 is activate, 0 is deactivate - #self.parent.nd_array_mode.put(1) + # self.parent.nd_array_mode.put(1) def on_stage(self) -> None: """Prepare detector and backend for acquisition""" self.prepare_detector() - self.prepare_data_backend() - self.publish_file_location(done=False, successful=False) + # self.prepare_data_backend() + # self.publish_file_location(done=False, successful=False) + # self.arm_acquisition() + + def on_trigger(self) -> None: + """Actions on pre_scan. This is performed AFTER stage, just before scan_core""" self.arm_acquisition() def prepare_detector(self) -> None: """Prepare detector for acquisition""" self.set_trigger( - mapping_mode=MappingSource.MAPPING, trigger_source=TriggerSource.GATE, ignore_gate=0 + mapping_mode=MappingSource.SPECTRUM, trigger_source=TriggerSource.GATE, ignore_gate=0 ) self.parent.preset_real.put(self.parent.scaninfo.exp_time) self.parent.pixels_per_run.put( @@ -186,7 +191,7 @@ class FalconSetup(CustomDetectorMixin): def prepare_data_backend(self) -> None: """Prepare data backend for acquisition""" - w=9 + w = 9 """ -------------------------------------------------------------- self.parent.filepath.set( self.parent.filewriter.compile_full_filename(f"{self.parent.name}.h5") @@ -230,30 +235,31 @@ class FalconSetup(CustomDetectorMixin): def on_complete(self) -> None: """Complete detector and backend""" - #------------------------------------------------------------------ - #self.finished(timeout=self.parent.TIMEOUT_FOR_SIGNALS) - #self.publish_file_location(done=True, successful=True) - w=9 + # ------------------------------------------------------------------ + # self.finished(timeout=self.parent.TIMEOUT_FOR_SIGNALS) + # self.publish_file_location(done=True, successful=True) + w = 9 + def on_stop(self) -> None: """Stop detector and backend""" self.stop_detector() - #self.stop_detector_backend() + # self.stop_detector_backend() def stop_detector(self) -> None: """Stops detector""" self.parent.stop_all.put(1) self.parent.erase_all.put(1) - #------------------------------------------------------------------- - #signal_conditions = [ + # ------------------------------------------------------------------- + # signal_conditions = [ # (lambda: self.parent.state.read()[self.parent.state.name]["value"], DetectorState.DONE) - #] + # ] - #if not self.wait_for_signals( + # if not self.wait_for_signals( # signal_conditions=signal_conditions, # timeout=self.parent.TIMEOUT_FOR_SIGNALS - self.parent.TIMEOUT_FOR_SIGNALS // 2, # all_signals=False, - #): + # ): # # Retry stop detector and wait for remaining time # raise FalconTimeoutError( # f"Failed to stop detector, timeout with state {signal_conditions[0][0]}" @@ -261,8 +267,8 @@ class FalconSetup(CustomDetectorMixin): def stop_detector_backend(self) -> None: """Stop the detector backend""" - #self.parent.hdf5.capture.put(0) - w=0 + # self.parent.hdf5.capture.put(0) + w = 0 def finished(self, timeout: int = 5) -> None: """Check if scan finished succesfully""" @@ -272,7 +278,7 @@ class FalconSetup(CustomDetectorMixin): ) signal_conditions = [ (self.parent.dxp.current_pixel.get, total_frames), - # (self.parent.hdf5.array_counter.get, total_frames), --------------------- + # (self.parent.hdf5.array_counter.get, total_frames), --------------------- ] if not self.wait_for_signals( signal_conditions=signal_conditions, @@ -288,10 +294,6 @@ class FalconSetup(CustomDetectorMixin): self.stop_detector() self.stop_detector_backend() - - - - def set_trigger( self, mapping_mode: MappingSource, trigger_source: TriggerSource, ignore_gate: int = 0 ) -> None: diff --git a/phoenix_bec/devices/phoenix_trigger.py b/phoenix_bec/devices/phoenix_trigger.py index 232a709..06783cd 100644 --- a/phoenix_bec/devices/phoenix_trigger.py +++ b/phoenix_bec/devices/phoenix_trigger.py @@ -1,25 +1,33 @@ import time -from ophyd import ( - ADComponent as ADCpt, - Device, - DeviceStatus, -) +import enum +import numpy as np from ophyd import Component as Cpt -from ophyd import FormattedComponent as FCpt -from ophyd import Device, EpicsSignal, EpicsSignalRO +from ophyd import DeviceStatus, EpicsSignal, EpicsSignalRO, Kind -from ophyd_devices.interfaces.base_classes.psi_detector_base import PSIDetectorBase, CustomDetectorMixin +from ophyd_devices.interfaces.base_classes.psi_detector_base import ( + PSIDetectorBase, + CustomDetectorMixin, +) -from bec_lib import bec_logger, messages -from bec_lib.endpoints import MessageEndpoints +from bec_lib import bec_logger logger = bec_logger.logger DETECTOR_TIMEOUT = 5 -#class PhoenixTriggerError(Exce start_csmpl=Cpt(EPicsSignal,'START-CSMPL') # cont on / off + +class PhoenixTriggerError(Exception): + """PhoenixTrigger specific error""" + + +class SAMPLINGDONE(int, enum.Enum): + """Sampling Done PV""" + + RUNNING = 0 + DONE = 1 + class PhoenixTriggerSetup(CustomDetectorMixin): """ @@ -28,237 +36,102 @@ class PhoenixTriggerSetup(CustomDetectorMixin): """ - #self.acquire = self.parent.smpl.put(1) - #self.continuous_sampling_on = parent.start_cmpl.put(1) - #self.continuous_sampling_off = self.parent.start_cmpl.put(0) + def on_stage(self) -> None: + """Actions to take place on stage""" + if self.parent.scaninfo.scan_type == "step": + self.parent.start_csmpl.set(0) + self.parent.total_cycles.set(1) + self.parent.smpl.put(1) + time.sleep(0.5) + self.parent.total_cycles.set(np.ceil(self.parent.scaninfo.exp_time * 5)) + logger.info(f"Device {self.parent.name} was staged for step scan") - def __init__(self, *args, parent:Device = None, **kwargs): - super().__init__(*args, parent=parent, **kwargs) - self._counter = 0 + def on_unstage(self) -> None: + """Actions to take place on unstage""" + self.on_stop() + def on_trigger(self) -> DeviceStatus: + """Actions to be performed upon receiving a software trigger""" + # Check first that falcon is set to acquiring + falcon = self.parent.device_manager.devices.get("falcon_nohdf5", None) + if falcon is not None: + if self.wait_for_signals([(falcon.state.get, 1)], timeout=1): + raise PhoenixTriggerError( + f"Falcon not ready to take trigger after 1s timeout in trigger" + ) + falcon.state.get() == 1 # Acquiring + if self.parent.scaninfo.scan_type == "step": + time.sleep(0.2) + self.parent.smpl.put(1) + # Minimum of 1 cycle has to be waited. Cycle == 0.2s + time.sleep(0.2) + # Trigger function from ophyd.Device returns a DeviceStatus. This function + # starts a process that creates a DeviceStatus, and waits for the signal_conditions + # self.parent.smpl_done.get to change to the value SAMPLINGDONE.DONE + # Once this takes place, the DeviceStatus.done flag will be set to True. + # When BEC calls trigger() on the devices, this method will be called assuming that + # the devices config softwareTrigger=True is set. + # In ScanBase, the _at_each_point function calls + # self.stubs.wait(wait_type="trigger", group="trigger", wait_time=self.exp_time) + # which ensures that the DeviceStatus object resolves before continuing, i.e. DeviceStatus.done = True + status = self.wait_with_status( + signal_conditions=[(self.parent.smpl_done.get, SAMPLINGDONE.DONE)], + timeout=5 * self.parent.scaninfo.exp_time, # Check if timeout is appropriate + check_stopped=True, + ) + return status - def on_acquire(self): - self.parent.smpl.put(1) - print('on_aquire') - - - def on_cont_sample_on(self): - self.parent.start_csmpl.put(1) - print('on_cont_sample_on') - - def on_cont_sample_off(self): - self.parent.start_csmpl.put(0) - print('on_cont_sample_off') - - - def on_done(self): - done_out = self.parent.smpl_done.get() - if done_out =='1': - done=True - if done_out == '0': - done=False - return done - - def on_dwell(self,t): - " calculate cycles from time in sec " - cycles=self.parent.total_cycles.put(0)*5 - - def on_stage(self): - # is this called on each point in scan or just before scan ??? - print('on stage') - self.parent.start_csmpl.put(0) - time.sleep(0.05) - cycles=self.parent.total_cycles.get() - time.sleep(0.05) - self.parent.total_cycles.put(0) - time.sleep(0.05) + def on_stop(self) -> None: + """Actions to stop the Device""" + # Put the Device in cont mode + self.parent.total_cycles.set(5) + self.parent.start_csmpl.set(1) self.parent.smpl.put(1) time.sleep(0.5) - print(cycles) - cycles=self.parent.total_cycles.put(cycles) - logger.success('PhoenixTrigger on stage') - - - def on_unstage(self): - # is this called on each point in scan or just before scan ??? - print('on unstage') - #while self.parent.smpl_done.get() - self.parent.total_cycles.put(5) - time.sleep(0.3) - self.parent.start_csmpl.put(1) - time.sleep(0.3) self.parent.smpl.put(1) - time.sleep(2) - + time.sleep(0.2) + if self.parent.smpl_done.get() == SAMPLINGDONE.RUNNING: + return self.parent.smpl.put(1) - time.sleep(.5) - logger.success('PhoenixTrigger.on_unstage') - - - - #def on_trigger(self): - # print('on_trigger') - # self.parent.start_smpl.put(1) - # logger.success('PhoenixTrigger on_trigger') - # - # return self.wait_with_status( - # [(self.parent.smpl_done.get, 1)]) - - - - -# logger.success(' PhoenixTrigger on_trigger complete ') - -# if success: -# status.set_finished() -# else: -# status.set_exception(TimeoutError()) -# return status - - - - #def on_complete(self): - # print('on_complete') - # timeout =10 - - - # logger.success('XXXX complete %d XXXX' % success) - - # success = self.wait_for_signals( - # [ - # (self.parent.smpl_done.get, 0) - # ], - # timeout, - # check_stopped=True, - # all_signals=True - # ) - - - - # if success: - # status.set_finished() - # else: - # status.set_exception(TimeoutError()) - # return status - - - - - # hoenixTrigger on_unstage ') - # self.parent.csmpl.put(1) - # self.parent.smpl.put(1) - # logger.success(' PhoenixTrigger on_unstage finished ') - - #def on_trigger(): - # print('on_trigger') class PhoenixTrigger(PSIDetectorBase): - """ Docstring: - Class for PHOENIX TTL hardware trigger - - Parent class: PSIDetectorBase - - class attributes: - custom_prepare_cls (PhoenixTriggerSetup) : Custom setup for TTL trigger at PHOENIX - inherits from CustomDetectorMixin - in __init__ of PSIDetecor bases - class is initialized - self.custom_prepare = self.custom_prepare_cls(parent=self, **kwargs) - The class PhoenixTrigger is the class to be called via yaml configuration file - the input arguments are defined by PSIDetectorBase, - and need to be given in the yaml configuration file. - To adress chanels such as 'X07MB-OP2:SMPL-DONE': - - use prefix 'X07MB-OP2:' in the device definition in the yaml configuration file. - - - - PSIDetectorBase( - prefix='', - *,Q - name, - kind=None, - parent=None, - device_manager=None, - **kwargs, - ) - Args: - prefix (str): EPICS PV prefix for component (optional) - name (str): name of the device, as will be reported via read() - kind (str): member of class 'ophydobj.Kind', defaults to Kind.normal - omitted -> readout ignored for read 'ophydobj.read()' - normal -> readout for read - config -> config parameter for 'ophydobj.read_configuration()' - hinted -> which attribute is readout for read - parent (object): instance of the parent device - device_manager (object): bec device manager - **kwargs: keyword arguments - File: /data/test/x07mb-test-bec/bec_deployment/ophyd_devices/ophyd_devices/interfaces/base_classes/psi_detector_base.py - Type: type - Subclasses: EpicsSignal + Class for PHOENIX TTL hardware trigger (X07MB-OP2:) """ - ################################################################## - # Specify which functions are revealed to the user in BEC client - # only a set of predefined functions will be visible in dev.TTL - # The Variable USER_ACCESS contains an ascii list of functions which will be - # visible in dev.TTL as well - # Alternatively one couls also create 2nd instance of PhoenixTrigger, - # which is probably not ideal - - USER_ACCESS = ["a_acquire" - ,"a_cont_sample_on" - ,"a_cont_sample_off" - ,"prefix" - ,"a_done" - ,"a_done_cpt" - ,"SMPL"] - - ##################################################################### - # specify Setup class into variable custom_prepare_cls - # in __init__ of PSIDetectorBase will the initialzed by - # self.custom_prepare = self.custom_prepare_cls(parent=self, **kwargs) - # making the instance of PSIDetectorBase availble in functions - custom_prepare_cls = PhoenixTriggerSetup - #############################################################3 - # Now use component to provide channel access - # when PhoenixTrigger is initialized, the parameters of the base class are - # inherided, most notable prefix, which is here X07MB-OP2: - # The input of Component=Cpt is Cpt(deviceClass,suffix) - # if Cpt is used in a class, which has interited Device, here via: - # (Here PhoenixTrigger <-- PSIDetectorBase <- Device - # the Cpt will construct - magically- the Epics channel name - # EpicsPV = prefix+suffix, - # for example - # 'X07MB-OP2:' + 'START-CSMPL' -> 'X07MB-OP2:' + 'START-CSMPL' - # - start_csmpl = Cpt(EpicsSignal, 'START-CSMPL') # cont on / off - intr_count = Cpt(EpicsSignal,'INTR-COUNT') # conter run up - total_cycles = Cpt(EpicsSignal,'TOTAL-CYCLES') # cycles set - smpl = Cpt(EpicsSignal,'SMPL') # start sampling --> aquire - # Signal is of type string - smpl_done = Cpt(EpicsSignal,'SMPL-DONE',string=True) # show trigger is done + start_csmpl = Cpt( + EpicsSignal, "START-CSMPL", kind=Kind.config, put_complete=True + ) # cont on / off + intr_count = Cpt( + EpicsSignal, "INTR-COUNT", kind=Kind.config, put_complete=True + ) # conter run up + total_cycles = Cpt( + EpicsSignal, "TOTAL-CYCLES", kind=Kind.config, put_complete=True + ) # cycles set + smpl = Cpt( + EpicsSignal, "SMPL", kind=Kind.config, put_complete=True + ) # start sampling --> aquire + smpl_done = Cpt( + EpicsSignalRO, "SMPL-DONE", kind=Kind.config + ) # show trigger is done, consider using string=True - def a_acquire(self): - self.custom_prepare.on_acquire() +if __name__ == "__main__": + trigger = PhoenixTrigger(name="trigger", prefix="X07MB-OP2:") + trigger.wait_for_connection(all_signals=True) + trigger.read() + trigger.read_configuration() - def a_cont_sample_on(self): - self.custom_prepare.on_cont_sample_on() + trigger.stage() + status = trigger.trigger() + while status.done is False: + print(f" Waiting for status, flag is {status.done}") + time.sleep(0.2) - def a_cont_sample_off(self): - self.custom_prepare.on_cont_sample_off() - - def a_done(self): - done=self.custom_prepare.on_done() - return done - - def a_dwell(self): - self.custom_prepare.on_dwell() \ No newline at end of file + trigger.unstage()