From 67ef20cfc8301660281f36635374de00de1de483 Mon Sep 17 00:00:00 2001 From: appel_c Date: Wed, 11 Feb 2026 11:22:09 +0100 Subject: [PATCH] fix(ddg): fix logic to resolve trigger on ddg1. --- .../epics/delay_generator_csaxs/ddg_1.py | 67 +++++++++---------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/csaxs_bec/devices/epics/delay_generator_csaxs/ddg_1.py b/csaxs_bec/devices/epics/delay_generator_csaxs/ddg_1.py index 07a4d69..4c21169 100644 --- a/csaxs_bec/devices/epics/delay_generator_csaxs/ddg_1.py +++ b/csaxs_bec/devices/epics/delay_generator_csaxs/ddg_1.py @@ -488,12 +488,18 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS): def on_trigger(self) -> DeviceStatus: """ This method is called from BEC as a software trigger. Here the logic is as follows: - We check the signal of the "fast_shutter_control". If it is low, we know that the shutter - open/closes for each trigger cycle. In this case, we can use the signal of the - "fast_shutter_readback" to check if the shutter transitioned from open to close, which - indicates the end of the burst. If the "fast_shutter_control" signal is high, we know - that the shutter is kept open during the scan. In this case, we can only rely on polling - the event status register to check if the burst finished. + + We first check if the trigger_source is set to SINGLE_SHOT. Only then will we received, + otherwise we return a status object directly as the DDG is triggered by an external + source which will have to implement its own logic to wait for trigger signals to + be received. + + I SINGLE_SHOT, the implementation here will send a software trigger. Now there are + two options to wait for the trigger (burst) cycle to be done. One is to rely on the + signal of the "mcs" card if it is present. However, this is only possible if the + scan_type is not "fly" as in fly scans the ef channel is not triggered to send the last + pulse to the card (but the card is finishing its acquisition in complete itself). Then + we rely on the polling of the event status register to check if the burst cycle is done. It follows a specific procedure to ensure that the DDG1 and MCS card are properly handled on a trigger event. The established logic is as follows: @@ -512,8 +518,8 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS): - Return the status object to BEC which will automatically resolve once the status register has the END_OF_BURST bit set. The callback of the status object will also stop the polling loop. """ + overall_start = time.time() self._stop_polling() - self._poll_thread_poll_loop_done.wait(timeout=1) # NOTE If the trigger source is not SINGLE_SHOT, the DDG is triggered by an external source # thus we can not expect that trigger signals are meant to be awaited for. In this case, @@ -525,40 +531,33 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS): # NOTE If the MCS card is present in the current session of BEC, # we prepare the card for the next trigger. The procedure is implemented - # in the '_prepare_mcs_on_trigger' method. - # Prepare the MCS card for the next software trigger + # in the '_prepare_mcs_on_trigger' method. We will also use the mcs card + # to indicate when the burst cycle is done. If no mcs card is available + # the fallback is to use the polling of the DDG mcs = self.device_manager.devices.get("mcs", None) - if mcs is None or mcs.enabled is False: - logger.info("Did not find mcs card with name 'mcs' in current session") - else: - status_mcs = self._prepare_mcs_on_trigger(mcs) - # NOTE Timeout of 3s should be plenty, any longer wait should checked. If this happens to crash - # an acquisition regularly with a WaitTimeoutError, the timeout can be increased but it should - # be investigated why the EPICS interface is slow to respond. - status_mcs.wait(timeout=3) - - # Use fast shutter readback in case it is not kept open - # NOTE: THe update frequency of the EpicsSIgnal is ~10Hz (100ms), needs to be checked if that can - # be adjusted to ~2kHz - if ( - self.fast_shutter_control.get() != 1 - ): # Shutter is not kept open, we can rely on its close signal. - status = TransitionStatus( - self.fast_shutter_readback, [1, 0] - ) # Add timeout error with explicit info update epics update freq - else: - # NOTE This sleep is needed for 20ms to make sure that the HW of the DDG is - # again ready to process new commands. It was transferred from just after the - # _stop_polling() call, as it should only be relevant in case of polling the - # event status register, which may only be if the shutter is kept open. + if mcs is None or mcs.enabled is False or self.scan_info.msg.scan_type == "fly": + self._poll_thread_poll_loop_done.wait(timeout=1) + logger.warning("Did not find mcs card with name 'mcs' in current session") time.sleep(0.02) # Shutter is kept open, we can only rely on the event status register status = self._prepare_trigger_status_event() # Start polling thread again to monitor event status self._start_polling() - # Trigger the DDG1 - self.cancel_on_stop(status) + else: + start_time = time.time() + logger.debug(f"Preparing mcs card ") + status_mcs = self._prepare_mcs_on_trigger(mcs) + # NOTE Timeout of 3s should be plenty, any longer wait should checked. If this happens to crash + # an acquisition regularly with a WaitTimeoutError, the timeout can be increased but it should + # be investigated why the EPICS interface is slow to respond. + status_mcs.wait(timeout=3) + status = TransitionStatus(mcs.acquiring, [ACQUIRING.ACQUIRING, ACQUIRING.DONE]) + logger.debug(f"Finished preparing mcs card {time.time()-start_time}") + + # Send trigger self.trigger_shot.put(1, use_complete=True) + self.cancel_on_stop(status) + logger.info(f"Configured ddg in {time.time()-overall_start}") return status def on_stop(self) -> None: