From c934aa8e9a5d30bfed60fd250781f62e41bca986 Mon Sep 17 00:00:00 2001 From: gac-x01da Date: Fri, 9 May 2025 10:00:31 +0200 Subject: [PATCH 01/10] refactor: update configs with optic slit config, machine config --- debye_bec/device_configs/x01da_machine.yaml | 13 + .../device_configs/x01da_optic_slits.yaml | 227 ++++++++++++++++++ .../device_configs/x01da_test_config.yaml | 5 +- 3 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 debye_bec/device_configs/x01da_machine.yaml create mode 100644 debye_bec/device_configs/x01da_optic_slits.yaml diff --git a/debye_bec/device_configs/x01da_machine.yaml b/debye_bec/device_configs/x01da_machine.yaml new file mode 100644 index 0000000..fe1ae5d --- /dev/null +++ b/debye_bec/device_configs/x01da_machine.yaml @@ -0,0 +1,13 @@ +curr: + readoutPriority: baseline + description: SLS ring current + deviceClass: ophyd.EpicsSignalRO + deviceConfig: + auto_monitor: true + read_pv: AGEBD-DBPM3CURR:CURRENT-AVG + deviceTags: + - machine + onFailure: buffer + enabled: true + readOnly: true + softwareTrigger: false \ No newline at end of file diff --git a/debye_bec/device_configs/x01da_optic_slits.yaml b/debye_bec/device_configs/x01da_optic_slits.yaml new file mode 100644 index 0000000..0b364f7 --- /dev/null +++ b/debye_bec/device_configs/x01da_optic_slits.yaml @@ -0,0 +1,227 @@ +## Optics Slits 1 -- Physical positioners + +sl1_trxr: + readoutPriority: baseline + description: Optics slits 1 X-translation Ring-edge + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-SL1:TRXR + onFailure: retry + enabled: true + softwareTrigger: false + deviceTags: + - optics + - slits +sl1_trxw: + readoutPriority: baseline + description: Optics slits 1 X-translation Wall-edge + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-SL1:TRXW + onFailure: retry + enabled: true + softwareTrigger: false + deviceTags: + - optics + - slits +sl1_tryb: + readoutPriority: baseline + description: Optics slits 1 Y-translation Bottom-edge + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-SL1:TRYB + onFailure: retry + enabled: true + softwareTrigger: false + deviceTags: + - optics + - slits +sl1_tryt: + readoutPriority: baseline + description: Optics slits 1 X-translation Top-edge + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-SL1:TRYT + onFailure: retry + enabled: true + softwareTrigger: false + deviceTags: + - optics + - slits +bm1_try: + readoutPriority: baseline + description: Beam Monitor 1 Y-translation + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-BM1:TRY + onFailure: retry + enabled: true + softwareTrigger: false + deviceTags: + - optics + - slits + +## Optics Slits 1 -- Virtual positioners + +sl1_centerx: + readoutPriority: baseline + description: Optics slits 1 X-center + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-SL1:CENTERX + onFailure: retry + enabled: true + softwareTrigger: false + deviceTags: + - optics + - slits +sl1_gapx: + readoutPriority: baseline + description: Optics slits 1 X-gap + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-SL1:GAPX + onFailure: retry + enabled: true + softwareTrigger: false + deviceTags: + - optics + - slits +sl1_centery: + readoutPriority: baseline + description: Optics slits 1 Y-center + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-SL1:CENTERY + onFailure: retry + enabled: true + softwareTrigger: false + deviceTags: + - optics + - slits +sl1_gapy: + readoutPriority: baseline + description: Optics slits 1 Y-gap + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-SL1:GAPY + onFailure: retry + enabled: true + softwareTrigger: false + deviceTags: + - optics + - slits + +## Optics Slits 2 -- Physical positioners + +sl2_trxr: + readoutPriority: baseline + description: Optics slits 2 X-translation Ring-edge + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-SL2:TRXR + onFailure: retry + enabled: true + softwareTrigger: false + deviceTags: + - optics + - slits +sl2_trxw: + readoutPriority: baseline + description: Optics slits 2 X-translation Wall-edge + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-SL2:TRXW + onFailure: retry + enabled: true + softwareTrigger: false + deviceTags: + - optics + - slits +sl2_tryb: + readoutPriority: baseline + description: Optics slits 2 Y-translation Bottom-edge + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-SL2:TRYB + onFailure: retry + enabled: true + softwareTrigger: false + deviceTags: + - optics + - slits +sl2_tryt: + readoutPriority: baseline + description: Optics slits 2 X-translation Top-edge + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-SL2:TRYT + onFailure: retry + enabled: true + softwareTrigger: false + deviceTags: + - optics + - slits +bm2_try: + readoutPriority: baseline + description: Beam Monitor 2 Y-translation + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-BM2:TRY + onFailure: retry + enabled: true + softwareTrigger: false + deviceTags: + - optics + - slits + +## Optics Slits 2 -- Virtual positioners + +sl2_centerx: + readoutPriority: baseline + description: Optics slits 2 X-center + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-SL2:CENTERX + onFailure: retry + enabled: true + softwareTrigger: false + deviceTags: + - optics + - slits +sl2_gapx: + readoutPriority: baseline + description: Optics slits 2 X-gap + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-SL2:GAPX + onFailure: retry + enabled: true + softwareTrigger: false + deviceTags: + - optics + - slits +sl2_centery: + readoutPriority: baseline + description: Optics slits 2 Y-center + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-SL2:CENTERY + onFailure: retry + enabled: true + softwareTrigger: false + deviceTags: + - optics + - slits +sl2_gapy: + readoutPriority: baseline + description: Optics slits 2 Y-gap + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-SL2:GAPY + onFailure: retry + enabled: true + softwareTrigger: false + deviceTags: + - optics + - slits \ No newline at end of file diff --git a/debye_bec/device_configs/x01da_test_config.yaml b/debye_bec/device_configs/x01da_test_config.yaml index 13def75..06df1fe 100644 --- a/debye_bec/device_configs/x01da_test_config.yaml +++ b/debye_bec/device_configs/x01da_test_config.yaml @@ -1,4 +1,7 @@ - +optic_slit_config: + - !include /data/test/x01da-test-bec/production/debye_bec/debye_bec/device_configs/x01da_optic_slits.yaml +machine_config: + - !include /data/test/x01da-test-bec/production/debye_bec/debye_bec/device_configs/x01da_machine.yaml ## Slit Diaphragm -- Physical positioners sldi_trxr: readoutPriority: baseline -- 2.49.1 From 79ead32e79a1702929b0dfed73d8612742cb557a Mon Sep 17 00:00:00 2001 From: gac-x01da Date: Fri, 9 May 2025 10:15:35 +0200 Subject: [PATCH 02/10] feat(nidaq): ensure nidaq is powered on during on_connected --- debye_bec/devices/nidaq/nidaq.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/debye_bec/devices/nidaq/nidaq.py b/debye_bec/devices/nidaq/nidaq.py index a09e7e4..1d3e022 100644 --- a/debye_bec/devices/nidaq/nidaq.py +++ b/debye_bec/devices/nidaq/nidaq.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Literal, cast from bec_lib.logger import bec_logger from ophyd import Component as Cpt from ophyd import Device, DeviceStatus, EpicsSignal, EpicsSignalRO, Kind, StatusBase +from ophyd.status import SubscriptionStatus, WaitTimeoutError from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase from ophyd_devices.sim.sim_signals import SetableSignal @@ -313,6 +314,8 @@ class NidaqControl(Device): readout_range = Cpt(EpicsSignal, suffix="NIDAQ-ReadoutRange", kind=Kind.config) encoder_type = Cpt(EpicsSignal, suffix="NIDAQ-EncoderType", kind=Kind.config) stop_call = Cpt(EpicsSignal, suffix="NIDAQ-Stop", kind=Kind.config) + power = Cpt(EpicsSignal, suffix="NIDAQ-Power", kind=Kind.config) + heartbeat = Cpt(EpicsSignal, suffix="NIDAQ-Heartbeat", kind=Kind.config, auto_monitor=True) ai_chans = Cpt(EpicsSignal, suffix="NIDAQ-AIChans", kind=Kind.config) ci_chans = Cpt(EpicsSignal, suffix="NIDAQ-CIChans6614", kind=Kind.config) @@ -472,6 +475,16 @@ class Nidaq(PSIDeviceBase, NidaqControl): Called after the device is connected and its signals are connected. Default values for signals should be set here. """ + def heartbeat_callback(*, old_value, value, **kwargs): + return ((old_value) == 0 and (value == 1)) or ((old_value) == 1 and (value == 0)) + status = SubscriptionStatus(self.heartbeat, callback=heartbeat_callback) + try: + status.wait(timeout=self.timeout_wait_for_signal) # Raises if timeout is reached + except WaitTimeoutError: + self.power.put(1) + + status.wait(timeout=self.timeout_wait_for_signal) + if not self.wait_for_condition( condition=lambda: self.state.get() == NidaqState.STANDBY, timeout=self.timeout_wait_for_signal, -- 2.49.1 From a1433efbf8fa9fe0b9daa3e0678ea8c60db2567c Mon Sep 17 00:00:00 2001 From: gac-x01da Date: Tue, 13 May 2025 15:56:37 +0200 Subject: [PATCH 03/10] update of test_config to include focusing mirror --- .../device_configs/x01da_test_config.yaml | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/debye_bec/device_configs/x01da_test_config.yaml b/debye_bec/device_configs/x01da_test_config.yaml index 06df1fe..860d9bf 100644 --- a/debye_bec/device_configs/x01da_test_config.yaml +++ b/debye_bec/device_configs/x01da_test_config.yaml @@ -273,6 +273,120 @@ mo_roty: enabled: true softwareTrigger: false + ## Focusing Mirror -- Physical Positioners + +fm_trxu: + readoutPriority: baseline + description: Focusing Mirror X-translation upstream + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-FM:TRXU + onFailure: retry + enabled: true + softwareTrigger: false +fm_trxd: + readoutPriority: baseline + description: Focusing Mirror X-translation downstream + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-FM:TRXD + onFailure: retry + enabled: true + softwareTrigger: false +fm_tryd: + readoutPriority: baseline + description: Focusing Mirror Y-translation downstream + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-FM:TRYD + onFailure: retry + enabled: true + softwareTrigger: false +fm_tryur: + readoutPriority: baseline + description: Focusing Mirror Y-translation upstream ring + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-FM:TRYUR + onFailure: retry + enabled: true + softwareTrigger: false +fm_tryuw: + readoutPriority: baseline + description: Focusing Mirror Y-translation upstream wall + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-FM:TRYUW + onFailure: retry + enabled: true + softwareTrigger: false +fm_bnd: + readoutPriority: baseline + description: Focusing Mirror bender + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-FM:BND + onFailure: retry + enabled: true + softwareTrigger: false + +## Focusing Mirror -- Virtual Positioners + +fm_rotx: + readoutPriority: baseline + description: Focusing Morror Pitch + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-FM:ROTX + onFailure: retry + enabled: true + softwareTrigger: false +fm_roty: + readoutPriority: baseline + description: Focusing Morror Yaw + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-FM:ROTY + onFailure: retry + enabled: true + softwareTrigger: false +fm_rotz: + readoutPriority: baseline + description: Focusing Morror Roll + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-FM:ROTZ + onFailure: retry + enabled: true + softwareTrigger: false +fm_xctp: + readoutPriority: baseline + description: Focusing Morror Center Point X + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-FM:XTCP + onFailure: retry + enabled: true + softwareTrigger: false +fm_ytcp: + readoutPriority: baseline + description: Focusing Morror Center Point Y + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-FM:YTCP + onFailure: retry + enabled: true + softwareTrigger: false +fm_ztcp: + readoutPriority: baseline + description: Focusing Morror Center Point Z + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-OP-FM:ZTCP + onFailure: retry + enabled: true + softwareTrigger: false + # Ionization Chambers ic0: -- 2.49.1 From f038679d7625cd6762e2c39414bfbc21498c0f50 Mon Sep 17 00:00:00 2001 From: gac-x01da Date: Fri, 16 May 2025 14:52:45 +0200 Subject: [PATCH 04/10] refactor(nidaq): add energy pv from nidaq --- debye_bec/devices/nidaq/nidaq.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/debye_bec/devices/nidaq/nidaq.py b/debye_bec/devices/nidaq/nidaq.py index 1d3e022..4de0f84 100644 --- a/debye_bec/devices/nidaq/nidaq.py +++ b/debye_bec/devices/nidaq/nidaq.py @@ -190,6 +190,14 @@ class NidaqControl(Device): auto_monitor=True, ) + energy_epics = Cpt( + EpicsSignalRO, + suffix="NIDAQ-ENERGY", + kind=Kind.normal, + doc="EPICS Energy reading", + auto_monitor=True, + ) + ### Readback for BEC emitter ### ai0_mean = Cpt( @@ -299,6 +307,7 @@ class NidaqControl(Device): di4_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 4, MAX") enc = Cpt(SetableSignal, value=0, kind=Kind.normal) + energy = Cpt(SetableSignal, value=0, kind=Kind.normal) ### Control PVs ### -- 2.49.1 From 718a001a8a935e8db7cd3f9ae516986ff289f8cd Mon Sep 17 00:00:00 2001 From: gac-x01da Date: Tue, 27 May 2025 09:16:03 +0200 Subject: [PATCH 05/10] typo and adding pinhole motors --- .../device_configs/x01da_test_config.yaml | 58 +++++++++++++++---- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/debye_bec/device_configs/x01da_test_config.yaml b/debye_bec/device_configs/x01da_test_config.yaml index 860d9bf..6a5d95c 100644 --- a/debye_bec/device_configs/x01da_test_config.yaml +++ b/debye_bec/device_configs/x01da_test_config.yaml @@ -213,15 +213,6 @@ mo1_bragg: onFailure: retry enabled: true softwareTrigger: false -dummy_pv: - readoutPriority: monitored - description: Heartbeat of Bragg - deviceClass: ophyd.EpicsSignalRO - deviceConfig: - read_pv: "X01DA-OP-MO1:BRAGG:heartbeat_RBV" - onFailure: retry - enabled: true - softwareTrigger: false mo1_bragg_angle: readoutPriority: baseline description: Positioner for the Monochromator @@ -259,7 +250,7 @@ mo_trx: description: Monochromator X Translation deviceClass: ophyd.EpicsMotor deviceConfig: - prefix: X01DA-OP-MO1:TRY + prefix: X01DA-OP-MO1:TRX onFailure: retry enabled: true softwareTrigger: false @@ -597,4 +588,49 @@ es1_alignment_laser: onFailure: retry enabled: true softwareTrigger: false - \ No newline at end of file + +## Pinhole alignment stages -- Physical Positioners + +pin1_trx: + readoutPriority: baseline + description: Pinhole X-translation + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-ES1-PIN1:TRX + onFailure: retry + enabled: true + softwareTrigger: false + tags: Endstation + +pin1_try: + readoutPriority: baseline + description: Pinhole Y-translation + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-ES1-PIN1:TRY + onFailure: retry + enabled: true + softwareTrigger: false + tags: Endstation + +pin1_rotx: + readoutPriority: baseline + description: Pinhole X-rotation + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-ES1-PIN1:ROTX + onFailure: retry + enabled: true + softwareTrigger: false + tags: Endstation + +pin1_roty: + readoutPriority: baseline + description: Pinhole Y-rotation + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X01DA-ES1-PIN1:ROTY + onFailure: retry + enabled: true + softwareTrigger: false + tags: Endstation \ No newline at end of file -- 2.49.1 From 89cc27a8da4ffd6b4e70e701f107052c2336d068 Mon Sep 17 00:00:00 2001 From: gac-x01da Date: Tue, 27 May 2025 10:50:35 +0200 Subject: [PATCH 06/10] feat: add nidaq_continuous_scan scan --- debye_bec/devices/nidaq/nidaq.py | 85 +++++++++++++++++++++++----- debye_bec/scans/__init__.py | 4 ++ debye_bec/scans/nidaq_cont_scan.py | 91 ++++++++++++++++++++++++++++++ 3 files changed, 166 insertions(+), 14 deletions(-) create mode 100644 debye_bec/scans/nidaq_cont_scan.py diff --git a/debye_bec/devices/nidaq/nidaq.py b/debye_bec/devices/nidaq/nidaq.py index 4de0f84..a1eaf3e 100644 --- a/debye_bec/devices/nidaq/nidaq.py +++ b/debye_bec/devices/nidaq/nidaq.py @@ -1,5 +1,6 @@ from __future__ import annotations +import time from typing import TYPE_CHECKING, Literal, cast from bec_lib.logger import bec_logger @@ -325,6 +326,7 @@ class NidaqControl(Device): stop_call = Cpt(EpicsSignal, suffix="NIDAQ-Stop", kind=Kind.config) power = Cpt(EpicsSignal, suffix="NIDAQ-Power", kind=Kind.config) heartbeat = Cpt(EpicsSignal, suffix="NIDAQ-Heartbeat", kind=Kind.config, auto_monitor=True) + time_left = Cpt(EpicsSignalRO, suffix="NIDAQ-TimeLeft", kind=Kind.config, auto_monitor=True) ai_chans = Cpt(EpicsSignal, suffix="NIDAQ-AIChans", kind=Kind.config) ci_chans = Cpt(EpicsSignal, suffix="NIDAQ-CIChans6614", kind=Kind.config) @@ -344,6 +346,7 @@ class Nidaq(PSIDeviceBase, NidaqControl): def __init__(self, prefix: str = "", *, name: str, scan_info: ScanInfo = None, **kwargs): super().__init__(name=name, prefix=prefix, scan_info=scan_info, **kwargs) + self.scan_info : ScanInfo self.timeout_wait_for_signal = 5 # put 5s firsts self._timeout_wait_for_pv = 3 # 3s timeout for pv calls self.valid_scan_names = [ @@ -351,6 +354,7 @@ class Nidaq(PSIDeviceBase, NidaqControl): "xas_simple_scan_with_xrd", "xas_advanced_scan", "xas_advanced_scan_with_xrd", + "nidaq_continuous_scan", ] ######################################## @@ -503,6 +507,7 @@ class Nidaq(PSIDeviceBase, NidaqControl): f"Device {self.name} has not been reached in state STANDBY, current state {NidaqState(self.state.get())}" ) self.scan_duration.set(0).wait(timeout=self._timeout_wait_for_pv) + self.time_left.subscribe(self._progress_update, run=False) def on_stage(self) -> DeviceStatus | StatusBase | None: """ @@ -522,8 +527,16 @@ class Nidaq(PSIDeviceBase, NidaqControl): raise NidaqError( f"Device {self.name} has not been reached in state STANDBY, current state {NidaqState(self.state.get())}" ) - self.scan_type.set(ScanType.TRIGGERED).wait(timeout=self._timeout_wait_for_pv) - self.scan_duration.set(0).wait(timeout=self._timeout_wait_for_pv) + # If scan is not part of the valid_scan_names, + if self.scan_info.msg.scan_name != "nidaq_continuous_scan": + self.scan_type.set(ScanType.TRIGGERED).wait(timeout=self._timeout_wait_for_pv) + self.scan_duration.set(0).wait(timeout=self._timeout_wait_for_pv) + self.enable_compression.set(1).wait(timeout=self._timeout_wait_for_pv) + else: + self.scan_type.set(ScanType.CONTINUOUS).wait(timeout=self._timeout_wait_for_pv) + self.scan_duration.set(self.scan_info.msg.scan_parameters["scan_duration"]).wait(timeout=self._timeout_wait_for_pv) + self.enable_compression.set(self.scan_info.msg.scan_parameters["compression"]).wait(timeout=self._timeout_wait_for_pv) + self.stage_call.set(1).wait(timeout=self._timeout_wait_for_pv) if not self.wait_for_condition( @@ -534,21 +547,30 @@ class Nidaq(PSIDeviceBase, NidaqControl): raise NidaqError( f"Device {self.name} has not been reached in state STAGE, current state {NidaqState(self.state.get())}" ) - self.kickoff_call.set(1).wait(timeout=self._timeout_wait_for_pv) + if self.scan_info.msg.scan_name != "nidaq_continuous_scan": + status = self.on_kickoff() + status.wait(timeout=self._timeout_wait_for_pv) logger.info(f"Device {self.name} was staged: {NidaqState(self.state.get())}") + def on_kickoff(self) -> DeviceStatus | StatusBase: + """ Kickoff the Nidaq""" + status = self.kickoff_call.set(1) + return status + def on_unstage(self) -> DeviceStatus | StatusBase | None: """Called while unstaging the device. Check that the Nidaq goes into Standby""" def _get_state(): return self.state.get() == NidaqState.STANDBY + # TODO We need to wait longer if rle is disabled if not self.wait_for_condition( condition=_get_state, timeout=self.timeout_wait_for_signal, check_stopped=False ): raise NidaqError( f"Device {self.name} has not been reached in state STANDBY, current state {NidaqState(self.state.get())}" ) + self.enable_compression.set(1).wait(self._timeout_wait_for_pv) logger.info(f"Device {self.name} was unstaged: {NidaqState(self.state.get())}") def on_pre_scan(self) -> DeviceStatus | StatusBase | None: @@ -561,6 +583,12 @@ class Nidaq(PSIDeviceBase, NidaqControl): """ if not self._check_if_scan_name_is_valid(): return None + + if self.scan_info.msg.scan_name == "nidaq_continuous_scan": + logger.info( + f"Device {self.name} ready to be kicked off for nidaq_continuous_scan" + ) + return None def _wait_for_state(): return self.state.get() == NidaqState.KICKOFF @@ -587,18 +615,47 @@ class Nidaq(PSIDeviceBase, NidaqControl): """ if not self._check_if_scan_name_is_valid(): return None - self.on_stop() - # TODO check if this wait can be removed. We are waiting in unstage anyways which will always be called afterwards - # Wait for device to be stopped - status = self.wait_for_condition( - condition=lambda: self.state.get() == NidaqState.STANDBY, - check_stopped=True, - timeout=self.timeout_wait_for_signal, - ) - return status - def on_kickoff(self) -> DeviceStatus | StatusBase | None: - """Called to kickoff a device for a fly scan. Has to be called explicitly.""" + def _check_state(self) -> bool: + while True: + if self.stopped is True: + raise NidaqError(f"Device {self.name} was stopped") + if self.state.get() == NidaqState.STANDBY: + return + # if time.time() > timeout_time: + # raise TimeoutError(f"Device {self.name} ran into timeout") + time.sleep(0.1) + + if self.scan_info.msg.scan_name != "nidaq_continuous_scan": + self.on_stop() + timeout = self.timeout_wait_for_signal + status = self.wait_for_condition( + condition=lambda: self.state.get() == NidaqState.STANDBY, + check_stopped=True, + timeout=timeout, + ) + else: + status = self.task_handler.submit_task(task=_check_state, task_args=(self,)) + return status + + def _progress_update(self, value, **kwargs) -> None: + """Callback method to update the scan progress, runs a callback + to SUB_PROGRESS subscribers, i.e. BEC. + + Args: + value (int) : current progress value + """ + scan_duration = self.scan_info.msg.scan_parameters.get("scan_duration", None) + if not isinstance(scan_duration, (int, float)): + return + value = scan_duration - value + max_value = scan_duration + self._run_subs( + sub_type=self.SUB_PROGRESS, + value=value, + max_value=max_value, + done=bool(value == max_value), + ) def on_stop(self) -> None: """Called when the device is stopped.""" diff --git a/debye_bec/scans/__init__.py b/debye_bec/scans/__init__.py index dbb6721..206af37 100644 --- a/debye_bec/scans/__init__.py +++ b/debye_bec/scans/__init__.py @@ -4,3 +4,7 @@ from .mono_bragg_scans import ( XASSimpleScan, XASSimpleScanWithXRD, ) + +from .nidaq_cont_scan import ( + NIDAQContinuousScan, +) diff --git a/debye_bec/scans/nidaq_cont_scan.py b/debye_bec/scans/nidaq_cont_scan.py new file mode 100644 index 0000000..7adf5dc --- /dev/null +++ b/debye_bec/scans/nidaq_cont_scan.py @@ -0,0 +1,91 @@ +"""This module contains the scan class for the nidaq of the Debye beamline for use in continuous mode.""" + +import time +from typing import Literal + +import numpy as np +from bec_lib.device import DeviceBase +from bec_lib.logger import bec_logger +from bec_server.scan_server.scans import AsyncFlyScanBase + +logger = bec_logger.logger + + +class NIDAQContinuousScan(AsyncFlyScanBase): + """Class for the nidaq continuous scan (without mono)""" + + scan_name = "nidaq_continuous_scan" + scan_type = "fly" + scan_report_hint = "device_progress" + required_kwargs = [] + use_scan_progress_report = False + pre_move = False + gui_config = { + "Scan Parameters": ["scan_duration"], + "Data Compression" : ["compression"], + } + + def __init__( + self, + scan_duration: float, + daq: DeviceBase = "nidaq", + compression: bool = False, + **kwargs, + ): + """ The NIDAQ continuous scan is used to measure with the NIDAQ without moving the + monochromator or any other motor. The NIDAQ thus runs in continuous mode, with a + set scan_duration. + + Args: + scan_duration (float): Duration of the scan. + daq (DeviceBase, optional): DAQ device to be used for the scan. + Defaults to "nidaq". + Examples: + >>> scans.nidaq_continuous_scan(scan_duration=10) + """ + super().__init__(**kwargs) + self.scan_duration = scan_duration + self.daq = daq + self.start_time = 0 + self.primary_readout_cycle = 1 + self.scan_parameters["scan_duration"] = scan_duration + self.scan_parameters["compression"] = compression + + def update_readout_priority(self): + """Ensure that NIDAQ is not monitored for any quick EXAFS.""" + super().update_readout_priority() + self.readout_priority["async"].append("nidaq") + + def prepare_positions(self): + """Prepare the positions for the scan.""" + yield None + + def pre_scan(self): + """Pre Scan action.""" + + self.start_time = time.time() + # Ensure parent class pre_scan actions to be called. + yield from super().pre_scan() + + def scan_report_instructions(self): + """ + Return the instructions for the scan report. + """ + yield from self.stubs.scan_report_instruction({"device_progress": [self.daq]}) + + def scan_core(self): + """Run the scan core. + Kickoff the acquisition of the NIDAQ wait for the completion of the scan. + """ + kickoff_status = yield from self.stubs.kickoff(device=self.daq) + kickoff_status.wait(timeout=5) # wait for proper kickoff of device + + complete_status = yield from self.stubs.complete(device=self.daq, wait=False) + + while not complete_status.done: + # Readout monitored devices + yield from self.stubs.read(group="monitored", point_id=self.point_id) + time.sleep(self.primary_readout_cycle) + self.point_id += 1 + + self.num_pos = self.point_id \ No newline at end of file -- 2.49.1 From bc666dc8074e50e8c66c67f24c07a2004d1d00b4 Mon Sep 17 00:00:00 2001 From: gac-x01da Date: Tue, 27 May 2025 10:51:56 +0200 Subject: [PATCH 07/10] fix: double timeout for for wait for ScanControlMessage --- debye_bec/devices/mo1_bragg/mo1_bragg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debye_bec/devices/mo1_bragg/mo1_bragg.py b/debye_bec/devices/mo1_bragg/mo1_bragg.py index 18e41bb..12a43a3 100644 --- a/debye_bec/devices/mo1_bragg/mo1_bragg.py +++ b/debye_bec/devices/mo1_bragg/mo1_bragg.py @@ -204,7 +204,7 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner): self.wait_for_signal( self.scan_control.scan_msg, ScanControlLoadMessage.SUCCESS, - timeout=self.timeout_for_pvwait, + timeout=2*self.timeout_for_pvwait, ) return None -- 2.49.1 From 39adeb72de3d4644b17958f0a269e994e21157c0 Mon Sep 17 00:00:00 2001 From: gac-x01da Date: Tue, 27 May 2025 11:40:49 +0200 Subject: [PATCH 08/10] test: add test for nidaq continous scan --- debye_bec/devices/mo1_bragg/mo1_bragg.py | 2 +- debye_bec/devices/nidaq/nidaq.py | 34 ++-- debye_bec/scans/__init__.py | 5 +- debye_bec/scans/nidaq_cont_scan.py | 17 +- tests/tests_devices/test_nidaq.py | 167 ++++++++++++++++++ .../tests_scans/test_nidaq_continous_scan.py | 127 +++++++++++++ 6 files changed, 320 insertions(+), 32 deletions(-) create mode 100644 tests/tests_devices/test_nidaq.py create mode 100644 tests/tests_scans/test_nidaq_continous_scan.py diff --git a/debye_bec/devices/mo1_bragg/mo1_bragg.py b/debye_bec/devices/mo1_bragg/mo1_bragg.py index 12a43a3..50a659e 100644 --- a/debye_bec/devices/mo1_bragg/mo1_bragg.py +++ b/debye_bec/devices/mo1_bragg/mo1_bragg.py @@ -204,7 +204,7 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner): self.wait_for_signal( self.scan_control.scan_msg, ScanControlLoadMessage.SUCCESS, - timeout=2*self.timeout_for_pvwait, + timeout=2 * self.timeout_for_pvwait, ) return None diff --git a/debye_bec/devices/nidaq/nidaq.py b/debye_bec/devices/nidaq/nidaq.py index a1eaf3e..f560949 100644 --- a/debye_bec/devices/nidaq/nidaq.py +++ b/debye_bec/devices/nidaq/nidaq.py @@ -346,7 +346,7 @@ class Nidaq(PSIDeviceBase, NidaqControl): def __init__(self, prefix: str = "", *, name: str, scan_info: ScanInfo = None, **kwargs): super().__init__(name=name, prefix=prefix, scan_info=scan_info, **kwargs) - self.scan_info : ScanInfo + self.scan_info: ScanInfo self.timeout_wait_for_signal = 5 # put 5s firsts self._timeout_wait_for_pv = 3 # 3s timeout for pv calls self.valid_scan_names = [ @@ -488,14 +488,16 @@ class Nidaq(PSIDeviceBase, NidaqControl): Called after the device is connected and its signals are connected. Default values for signals should be set here. """ + def heartbeat_callback(*, old_value, value, **kwargs): - return ((old_value) == 0 and (value == 1)) or ((old_value) == 1 and (value == 0)) + return ((old_value) == 0 and (value == 1)) or ((old_value) == 1 and (value == 0)) + status = SubscriptionStatus(self.heartbeat, callback=heartbeat_callback) try: - status.wait(timeout=self.timeout_wait_for_signal) # Raises if timeout is reached + status.wait(timeout=self.timeout_wait_for_signal) # Raises if timeout is reached except WaitTimeoutError: self.power.put(1) - + status.wait(timeout=self.timeout_wait_for_signal) if not self.wait_for_condition( @@ -527,15 +529,19 @@ class Nidaq(PSIDeviceBase, NidaqControl): raise NidaqError( f"Device {self.name} has not been reached in state STANDBY, current state {NidaqState(self.state.get())}" ) - # If scan is not part of the valid_scan_names, + # If scan is not part of the valid_scan_names, if self.scan_info.msg.scan_name != "nidaq_continuous_scan": self.scan_type.set(ScanType.TRIGGERED).wait(timeout=self._timeout_wait_for_pv) self.scan_duration.set(0).wait(timeout=self._timeout_wait_for_pv) self.enable_compression.set(1).wait(timeout=self._timeout_wait_for_pv) else: self.scan_type.set(ScanType.CONTINUOUS).wait(timeout=self._timeout_wait_for_pv) - self.scan_duration.set(self.scan_info.msg.scan_parameters["scan_duration"]).wait(timeout=self._timeout_wait_for_pv) - self.enable_compression.set(self.scan_info.msg.scan_parameters["compression"]).wait(timeout=self._timeout_wait_for_pv) + self.scan_duration.set(self.scan_info.msg.scan_parameters["scan_duration"]).wait( + timeout=self._timeout_wait_for_pv + ) + self.enable_compression.set(self.scan_info.msg.scan_parameters["compression"]).wait( + timeout=self._timeout_wait_for_pv + ) self.stage_call.set(1).wait(timeout=self._timeout_wait_for_pv) @@ -553,7 +559,7 @@ class Nidaq(PSIDeviceBase, NidaqControl): logger.info(f"Device {self.name} was staged: {NidaqState(self.state.get())}") def on_kickoff(self) -> DeviceStatus | StatusBase: - """ Kickoff the Nidaq""" + """Kickoff the Nidaq""" status = self.kickoff_call.set(1) return status @@ -583,11 +589,9 @@ class Nidaq(PSIDeviceBase, NidaqControl): """ if not self._check_if_scan_name_is_valid(): return None - + if self.scan_info.msg.scan_name == "nidaq_continuous_scan": - logger.info( - f"Device {self.name} ready to be kicked off for nidaq_continuous_scan" - ) + logger.info(f"Device {self.name} ready to be kicked off for nidaq_continuous_scan") return None def _wait_for_state(): @@ -625,7 +629,7 @@ class Nidaq(PSIDeviceBase, NidaqControl): # if time.time() > timeout_time: # raise TimeoutError(f"Device {self.name} ran into timeout") time.sleep(0.1) - + if self.scan_info.msg.scan_name != "nidaq_continuous_scan": self.on_stop() timeout = self.timeout_wait_for_signal @@ -637,7 +641,7 @@ class Nidaq(PSIDeviceBase, NidaqControl): else: status = self.task_handler.submit_task(task=_check_state, task_args=(self,)) return status - + def _progress_update(self, value, **kwargs) -> None: """Callback method to update the scan progress, runs a callback to SUB_PROGRESS subscribers, i.e. BEC. @@ -645,7 +649,7 @@ class Nidaq(PSIDeviceBase, NidaqControl): Args: value (int) : current progress value """ - scan_duration = self.scan_info.msg.scan_parameters.get("scan_duration", None) + scan_duration = self.scan_info.msg.scan_parameters.get("scan_duration", None) if not isinstance(scan_duration, (int, float)): return value = scan_duration - value diff --git a/debye_bec/scans/__init__.py b/debye_bec/scans/__init__.py index 206af37..9a3e710 100644 --- a/debye_bec/scans/__init__.py +++ b/debye_bec/scans/__init__.py @@ -4,7 +4,4 @@ from .mono_bragg_scans import ( XASSimpleScan, XASSimpleScanWithXRD, ) - -from .nidaq_cont_scan import ( - NIDAQContinuousScan, -) +from .nidaq_cont_scan import NIDAQContinuousScan diff --git a/debye_bec/scans/nidaq_cont_scan.py b/debye_bec/scans/nidaq_cont_scan.py index 7adf5dc..172da13 100644 --- a/debye_bec/scans/nidaq_cont_scan.py +++ b/debye_bec/scans/nidaq_cont_scan.py @@ -20,19 +20,12 @@ class NIDAQContinuousScan(AsyncFlyScanBase): required_kwargs = [] use_scan_progress_report = False pre_move = False - gui_config = { - "Scan Parameters": ["scan_duration"], - "Data Compression" : ["compression"], - } + gui_config = {"Scan Parameters": ["scan_duration"], "Data Compression": ["compression"]} def __init__( - self, - scan_duration: float, - daq: DeviceBase = "nidaq", - compression: bool = False, - **kwargs, + self, scan_duration: float, daq: DeviceBase = "nidaq", compression: bool = False, **kwargs ): - """ The NIDAQ continuous scan is used to measure with the NIDAQ without moving the + """The NIDAQ continuous scan is used to measure with the NIDAQ without moving the monochromator or any other motor. The NIDAQ thus runs in continuous mode, with a set scan_duration. @@ -78,7 +71,7 @@ class NIDAQContinuousScan(AsyncFlyScanBase): Kickoff the acquisition of the NIDAQ wait for the completion of the scan. """ kickoff_status = yield from self.stubs.kickoff(device=self.daq) - kickoff_status.wait(timeout=5) # wait for proper kickoff of device + kickoff_status.wait(timeout=5) # wait for proper kickoff of device complete_status = yield from self.stubs.complete(device=self.daq, wait=False) @@ -88,4 +81,4 @@ class NIDAQContinuousScan(AsyncFlyScanBase): time.sleep(self.primary_readout_cycle) self.point_id += 1 - self.num_pos = self.point_id \ No newline at end of file + self.num_pos = self.point_id diff --git a/tests/tests_devices/test_nidaq.py b/tests/tests_devices/test_nidaq.py new file mode 100644 index 0000000..0bd8c6f --- /dev/null +++ b/tests/tests_devices/test_nidaq.py @@ -0,0 +1,167 @@ +# pylint: skip-file +import threading +from typing import Generator +from unittest import mock + +import ophyd +import pytest +from bec_server.scan_server.scan_worker import ScanWorker +from ophyd.status import WaitTimeoutError +from ophyd_devices.interfaces.base_classes.psi_device_base import DeviceStoppedError +from ophyd_devices.tests.utils import MockPV + +# from bec_server.device_server.tests.utils import DMMock +from debye_bec.devices.nidaq.nidaq import Nidaq, NidaqError + +# TODO move this function to ophyd_devices, it is duplicated in csaxs_bec and needed for other pluging repositories +from debye_bec.devices.test_utils.utils import patch_dual_pvs + + +@pytest.fixture(scope="function") +def scan_worker_mock(scan_server_mock): + """Scan worker fixture, utility to generate scan_info for a given scan name.""" + scan_server_mock.device_manager.connector = mock.MagicMock() + scan_worker = ScanWorker(parent=scan_server_mock) + yield scan_worker + + +@pytest.fixture(scope="function") +def mock_nidaq() -> Generator[Nidaq, None, None]: + """Fixture for the Nidaq device.""" + name = "nidaq" + prefix = "nidaq:prefix_test:" + with mock.patch.object(ophyd, "cl") as mock_cl: + mock_cl.get_pv = MockPV + mock_cl.thread_class = threading.Thread + dev = Nidaq(name=name, prefix=prefix) + patch_dual_pvs(dev) + yield dev + + +def test_init(mock_nidaq): + """Test the initialization of the Nidaq device.""" + dev = mock_nidaq + assert dev.name == "nidaq" + assert dev.prefix == "nidaq:prefix_test:" + assert dev.valid_scan_names == [ + "xas_simple_scan", + "xas_simple_scan_with_xrd", + "xas_advanced_scan", + "xas_advanced_scan_with_xrd", + "nidaq_continuous_scan", + ] + + +def test_check_if_scan_name_is_valid(mock_nidaq): + """Test the check_if_scan_name_is_valid method.""" + dev = mock_nidaq + dev.scan_info.msg.scan_name = "xas_simple_scan" + assert dev._check_if_scan_name_is_valid() + dev.scan_info.msg.scan_name = "invalid_scan_name" + assert not dev._check_if_scan_name_is_valid() + + +def test_set_config(mock_nidaq): + dev = mock_nidaq + # TODO #21 Add test logic for set_config, issue created # + + +def test_on_connected(mock_nidaq): + """Test the on_connected method of the Nidaq device.""" + dev = mock_nidaq + dev.power.put(0) + dev.heartbeat._read_pv.mock_data = 0 + # First scenario, raise timeout error + + # This will raise a WaitTimeoutError error as we currently do not support callbacks in the MockPV + dev.timeout_wait_for_signal = 0.1 + # To check that it raised, we check that dev.power PV is set to 1 + # Set state PV to 0, 1 is expected value + dev.state._read_pv.mock_data = 0 + with pytest.raises(WaitTimeoutError): + dev.on_connected() + assert dev.power.get() == 1 + # TODO, once the MOCKPv supports callbacks, we can test the rest of the logic issue #22 + + +# def test_on_stage(mock_nidaq): +# dev = mock_nidaq +# #TODO Add once MockPV supports callbacks #22 + + +def test_on_kickoff(mock_nidaq): + """Test the on_kickoff method of the Nidaq device.""" + dev = mock_nidaq + dev.kickoff_call.put(0) + dev.kickoff() + assert dev.kickoff_call.get() == 1 + + +def test_on_unstage(mock_nidaq): + """Test the on_unstage method of the Nidaq device.""" + dev = mock_nidaq + dev.state._read_pv.mock_data = 0 # Set state to 0, 1 is Standby + dev._timeout_wait_for_pv = 0.1 # Set a short timeout for testing + dev.enable_compression._read_pv.mock_data = 0 # Compression enabled + with pytest.raises(NidaqError): + dev.on_unstage() + dev.state._read_pv.mock_data = 1 + dev.on_unstage() + assert dev.enable_compression.get() == 1 + + +@pytest.mark.parametrize( + ["scan_name", "raise_error", "nidaq_state"], + [ + ("line_scan", False, 0), + ("xas_simple_scan", False, 3), + ("xas_simple_scan", True, 0), + ("nidaq_continuous_scan", False, 0), + ], +) +def test_on_pre_scan(mock_nidaq, scan_name, raise_error, nidaq_state): + """Test the on_pre_scan method of the Nidaq device.""" + dev = mock_nidaq + dev.state.put(nidaq_state) + dev.scan_info.msg.scan_name = scan_name + dev._timeout_wait_for_pv = 0.1 # Set a short timeout for testing + if not raise_error: + dev.pre_scan() + else: + with pytest.raises(NidaqError): + dev.pre_scan() + + +def test_on_complete(mock_nidaq): + """Test the on_complete method of the Nidaq device.""" + dev = mock_nidaq + # Check for nidaq_continuous_scan + dev.scan_info.msg.scan_name = "nidaq_continuous_scan" + dev.state.put(0) # Set state to DISABLED + status = dev.complete() + assert status.done is False + dev.state.put(1) + # Should resolve now + status.wait(timeout=5) # Wait for the status to complete + assert status.done is True + + # Check for XAS simple scan + dev.scan_info.msg.scan_name = "xas_simple_scan" + dev.state.put(0) # Set state to ACQUIRE + dev.stop_call.put(0) + dev._timeout_wait_for_pv = 5 + status = dev.on_complete() + assert status.done is False + assert dev.stop_call.get() == 1 # Should have called stop + dev.state.put(1) # Set state to STANDBY + # Should resolve now + status.wait(timeout=5) # Wait for the status to complete + assert status.done is True + + # Test that it resolves if device is stopped + dev.state.put(0) # Set state to DISABLED + dev.stopped = True # Reset stopped state + status = dev.on_complete() + with pytest.raises(NidaqError): + status.wait(timeout=5) + assert status.done is True diff --git a/tests/tests_scans/test_nidaq_continous_scan.py b/tests/tests_scans/test_nidaq_continous_scan.py new file mode 100644 index 0000000..f71cbc2 --- /dev/null +++ b/tests/tests_scans/test_nidaq_continous_scan.py @@ -0,0 +1,127 @@ +# pylint: skip-file +from unittest import mock + +from bec_lib.messages import DeviceInstructionMessage +from bec_server.device_server.tests.utils import DMMock + +from debye_bec.scans import NIDAQContinuousScan + + +def get_instructions(request, ScanStubStatusMock): + request.metadata["RID"] = "my_test_request_id" + + def fake_done(): + """ + Fake done function for ScanStubStatusMock. Upon each call, it returns the next value from the generator. + This is used to simulate the completion of the scan. + """ + yield False + yield False + yield True + + def fake_complete(*args, **kwargs): + yield "fake_complete" + return ScanStubStatusMock(done_func=fake_done) + + with ( + mock.patch.object(request.stubs, "complete", side_effect=fake_complete), + mock.patch.object(request.stubs, "_get_result_from_status", return_value=None), + ): + reference_commands = list(request.run()) + + for cmd in reference_commands: + if not cmd or isinstance(cmd, str): + continue + if "RID" in cmd.metadata: + cmd.metadata["RID"] = "my_test_request_id" + if "rpc_id" in cmd.parameter: + cmd.parameter["rpc_id"] = "my_test_rpc_id" + cmd.metadata.pop("device_instr_id", None) + + return reference_commands + + +def test_xas_simple_scan(scan_assembler, ScanStubStatusMock): + + request = scan_assembler(NIDAQContinuousScan, scan_duration=10) + request.device_manager.add_device("nidaq") + reference_commands = get_instructions(request, ScanStubStatusMock) + assert reference_commands == [ + None, + None, + DeviceInstructionMessage( + metadata={"readout_priority": "monitored", "RID": "my_test_request_id"}, + device=None, + action="scan_report_instruction", + parameter={"device_progress": ["nidaq"]}, + ), + DeviceInstructionMessage( + metadata={"readout_priority": "monitored", "RID": "my_test_request_id"}, + device=None, + action="open_scan", + parameter={ + "scan_motors": [], + "readout_priority": { + "monitored": [], + "baseline": [], + "on_request": [], + "async": ["nidaq"], + }, + "num_points": 0, + "positions": [], + "scan_name": "nidaq_continuous_scan", + "scan_type": "fly", + }, + ), + DeviceInstructionMessage(metadata={}, device="nidaq", action="stage", parameter={}), + DeviceInstructionMessage( + metadata={}, + device=["bpm4i", "eiger", "mo1_bragg", "samx"], + action="stage", + parameter={}, + ), + DeviceInstructionMessage( + metadata={"readout_priority": "baseline", "RID": "my_test_request_id"}, + device=["samx"], + action="read", + parameter={}, + ), + DeviceInstructionMessage( + metadata={"readout_priority": "monitored", "RID": "my_test_request_id"}, + device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"], + action="pre_scan", + parameter={}, + ), + DeviceInstructionMessage( + metadata={"readout_priority": "monitored", "RID": "my_test_request_id"}, + device="nidaq", + action="kickoff", + parameter={"configure": {}}, + ), + "fake_complete", + DeviceInstructionMessage( + metadata={"readout_priority": "monitored", "RID": "my_test_request_id", "point_id": 0}, + device=["bpm4i", "eiger", "mo1_bragg"], + action="read", + parameter={"group": "monitored"}, + ), + DeviceInstructionMessage( + metadata={"readout_priority": "monitored", "RID": "my_test_request_id", "point_id": 1}, + device=["bpm4i", "eiger", "mo1_bragg"], + action="read", + parameter={"group": "monitored"}, + ), + "fake_complete", + DeviceInstructionMessage( + metadata={}, + device=["bpm4i", "eiger", "mo1_bragg", "nidaq", "samx"], + action="unstage", + parameter={}, + ), + DeviceInstructionMessage( + metadata={"readout_priority": "monitored", "RID": "my_test_request_id"}, + device=None, + action="close_scan", + parameter={}, + ), + ] -- 2.49.1 From 07d05f9490f7bc7b30c80adfd1aac223b3f84653 Mon Sep 17 00:00:00 2001 From: appel_c Date: Tue, 27 May 2025 16:02:45 +0200 Subject: [PATCH 09/10] fix(nidaq): fix proper handling return of DeviceStatus for complete method --- debye_bec/devices/nidaq/nidaq.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/debye_bec/devices/nidaq/nidaq.py b/debye_bec/devices/nidaq/nidaq.py index f560949..8dc0d2e 100644 --- a/debye_bec/devices/nidaq/nidaq.py +++ b/debye_bec/devices/nidaq/nidaq.py @@ -632,12 +632,7 @@ class Nidaq(PSIDeviceBase, NidaqControl): if self.scan_info.msg.scan_name != "nidaq_continuous_scan": self.on_stop() - timeout = self.timeout_wait_for_signal - status = self.wait_for_condition( - condition=lambda: self.state.get() == NidaqState.STANDBY, - check_stopped=True, - timeout=timeout, - ) + status = self.task_handler.submit_task(task=_check_state, task_args=(self,)) else: status = self.task_handler.submit_task(task=_check_state, task_args=(self,)) return status -- 2.49.1 From 27ff5697afe42eb07119aa2aeaf4b9f40f613bbf Mon Sep 17 00:00:00 2001 From: appel_c Date: Tue, 27 May 2025 17:26:20 +0200 Subject: [PATCH 10/10] fix(device-configs): change to relative path for !include syntax --- debye_bec/device_configs/x01da_test_config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/debye_bec/device_configs/x01da_test_config.yaml b/debye_bec/device_configs/x01da_test_config.yaml index 6a5d95c..25c3fa1 100644 --- a/debye_bec/device_configs/x01da_test_config.yaml +++ b/debye_bec/device_configs/x01da_test_config.yaml @@ -1,7 +1,7 @@ optic_slit_config: - - !include /data/test/x01da-test-bec/production/debye_bec/debye_bec/device_configs/x01da_optic_slits.yaml + - !include ./x01da_optic_slits.yaml machine_config: - - !include /data/test/x01da-test-bec/production/debye_bec/debye_bec/device_configs/x01da_machine.yaml + - !include ./x01da_machine.yaml ## Slit Diaphragm -- Physical positioners sldi_trxr: readoutPriority: baseline -- 2.49.1