From 8c2a52e8e7e467998585435f576ccc2940f5c0b6 Mon Sep 17 00:00:00 2001 From: wakonig_k Date: Tue, 7 Jul 2026 14:19:28 +0200 Subject: [PATCH 1/7] Updating to template version 1.4.1 --- .copier-answers.yml | 2 +- .gitea/workflows/create_update_pr.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index 08016da..f46a724 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -2,7 +2,7 @@ # It is needed to track the repo template version, and editing may break things. # This file will be overwritten by copier on template updates. -_commit: v1.4.0 +_commit: v1.4.1 _src_path: https://github.com/bec-project/plugin_copier_template.git make_commit: false project_name: debye_bec diff --git a/.gitea/workflows/create_update_pr.yml b/.gitea/workflows/create_update_pr.yml index 335858b..6b4ceb0 100644 --- a/.gitea/workflows/create_update_pr.yml +++ b/.gitea/workflows/create_update_pr.yml @@ -16,14 +16,14 @@ jobs: - name: Setup Python uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: "3.12" - name: Checkout uses: actions/checkout@v4 - name: Create virtualenv - run: | - python -m virtualenv .venv + run: | + python -m virtualenv .venv - name: Install tools run: | From b795362e5f3c5e0f95be3e42770427d6e1d1992a Mon Sep 17 00:00:00 2001 From: appel_c Date: Mon, 1 Jun 2026 13:13:34 +0200 Subject: [PATCH 2/7] feat(falcon): Minimal integration of the Falcon, forwarding array data. --- debye_bec/devices/falcon/__init__.py | 0 debye_bec/devices/falcon/falcon.py | 124 +++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 debye_bec/devices/falcon/__init__.py create mode 100644 debye_bec/devices/falcon/falcon.py diff --git a/debye_bec/devices/falcon/__init__.py b/debye_bec/devices/falcon/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/debye_bec/devices/falcon/falcon.py b/debye_bec/devices/falcon/falcon.py new file mode 100644 index 0000000..0ff60b0 --- /dev/null +++ b/debye_bec/devices/falcon/falcon.py @@ -0,0 +1,124 @@ +"""FALCON device implementation for SuperXAS""" + +from __future__ import annotations + +import enum +import traceback +from typing import TYPE_CHECKING + +from bec_lib.logger import bec_logger +from ophyd import Component as Cpt +from ophyd_devices import AsyncSignal, CompareStatus, DeviceStatus, StatusBase +from ophyd_devices.devices.areadetector.plugins import ImagePlugin_V35 as ImagePlugin +from ophyd_devices.devices.dxp import EpicsDXPFalcon, EpicsMCARecord, Falcon +from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase + +if TYPE_CHECKING: + from bec_lib.devicemanager import ScanInfo + +logger = bec_logger.logger + + +class FalconAcquiringStatus(int, enum.Enum): + """Status of Falcon""" + + DONE = 0 + ACQUIRING = 1 + + +class FalconControl(Falcon): + """Falcon Control class at SuperXAS. prefix: 'X10DA-SITORO:'""" + + # DXP parameters + dxp1 = Cpt(EpicsDXPFalcon, "dxp1:") + # MCA record with spectrum data + mca1 = Cpt(EpicsMCARecord, "mca1") + # Image record + image = Cpt(ImagePlugin, "image1:") + + +class FalconSuperXAS(PSIDeviceBase, FalconControl): + """Falcon implementierung at SuperXAS. prefix: 'X10DA-SITORO:'""" + + data = Cpt( + AsyncSignal, + name="data", + ndim=1, + max_size=1000, + doc="1D Waveform data from Falcon detector.", + ) + + ######################################## + # Beamline Specific Implementations # + ######################################## + + def on_init(self) -> None: + """ + Called when the device is initialized. + + No signals are connected at this point. If you like to + set default values on signals, please use on_connected instead. + """ + self._pv_timeout = 1 + self._falcon_energy_channels = None + + def on_connected(self) -> None: + """ + Called after the device is connected and its signals are connected. + Default values for signals should be set here. + """ + # Reset array counter on connect + self.cam.array_counter.set(0).wait(timeout=self._pv_timeout) + self.image.unique_id.subscribe(self._on_new_data_received, run=False) + + def on_stage(self) -> CompareStatus: + """ + Called while staging the device. + + Information about the upcoming scan can be accessed from the scan_info (self.scan_info.msg) object. + """ + + def on_unstage(self) -> CompareStatus: + """Called while unstaging the device.""" + + def on_pre_scan(self) -> DeviceStatus | StatusBase | None: + """Called right before the scan starts on all devices automatically.""" + + def on_trigger(self) -> DeviceStatus | StatusBase | None: + """Called when the device is triggered.""" + + def on_complete(self) -> DeviceStatus | StatusBase | None: + """Called to inquire if a device has completed a scans.""" + + def on_kickoff(self) -> DeviceStatus | StatusBase | None: + """Called to kickoff a device for a fly scan. Has to be called explicitly.""" + + def on_stop(self) -> None: + """Called when the device is stopped.""" + + ######################################## + # Custom Methods # + ######################################## + + def _on_new_data_received(self, value: int, old_value: int, **kwargs): + """Callback for image unique ID updates to trigger preview update.""" + if value == old_value: + return # No new image, or counter reset + try: + # Get new image data + array_data = self.image.array_data.get() + if array_data is None: + logger.info(f"No image data available for preview of {self.name}") + return + if self._falcon_energy_channels is None: + # Initialize energy channels based on the first received data + self._falcon_energy_channels = len(array_data) + logger.info(f"Initialized Falcon energy channels to {self._falcon_energy_channels}") + # Geometry correction for the image + self.data.put( + array_data, + async_update={"type": "add", "max_shape": [None, self._falcon_energy_channels]}, + ) + except Exception: # pylint: disable=broad-except + content = traceback.format_exc() + logger.error(f"Error while updating preview for {self.name} on image update: {content}") From 7aac24ce62b6a99642c94228e88e3ea850904098 Mon Sep 17 00:00:00 2001 From: wyzula-jan Date: Wed, 15 Jul 2026 13:52:29 +0200 Subject: [PATCH 3/7] fix(devices): mo1_bragg add failure_value to CompareStatus to not hang forever and report error --- debye_bec/devices/mo1_bragg/mo1_bragg.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/debye_bec/devices/mo1_bragg/mo1_bragg.py b/debye_bec/devices/mo1_bragg/mo1_bragg.py index ac5b85a..dd857c9 100644 --- a/debye_bec/devices/mo1_bragg/mo1_bragg.py +++ b/debye_bec/devices/mo1_bragg/mo1_bragg.py @@ -291,7 +291,11 @@ class Mo1Bragg(PSIDeviceBase, Mo1BraggPositioner): time.sleep(1) logger.info(f"Device {self.name}, done sleeping") # Load the scan parameters to the controller - status = CompareStatus(self.scan_control.scan_msg, ScanControlLoadMessage.SUCCESS) + status = CompareStatus( + self.scan_control.scan_msg, + ScanControlLoadMessage.SUCCESS, + failure_value=[m for m in ScanControlLoadMessage if m.name.startswith("ERR_")], + ) self.cancel_on_stop(status) self.scan_control.scan_load.put(1) # Wait for params to be checked from controller From f3b9e914cdcb562a2a773c9ac0244629cd636afe Mon Sep 17 00:00:00 2001 From: appel_c Date: Thu, 16 Jul 2026 10:01:17 +0200 Subject: [PATCH 4/7] fix(nidaq): fix on_kickoff call not using set --- debye_bec/devices/nidaq/nidaq.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/debye_bec/devices/nidaq/nidaq.py b/debye_bec/devices/nidaq/nidaq.py index b7882a1..3b256a7 100644 --- a/debye_bec/devices/nidaq/nidaq.py +++ b/debye_bec/devices/nidaq/nidaq.py @@ -5,9 +5,9 @@ from typing import TYPE_CHECKING, Literal from bec_lib.logger import bec_logger from bec_server.scan_server.scans.scan_base import ScanInfo as ScanServerScanInfo from ophyd import Component as Cpt -from ophyd import Device, DeviceStatus, EpicsSignal, EpicsSignalRO, Kind, StatusBase +from ophyd import Device, DeviceStatus, EpicsSignal, EpicsSignalRO, Kind from ophyd.status import WaitTimeoutError -from ophyd_devices import CompareStatus, ProgressSignal, TransitionStatus +from ophyd_devices import CompareStatus, ProgressSignal, StatusBase, TransitionStatus from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase from ophyd_devices.sim.sim_signals import SetableSignal @@ -432,13 +432,18 @@ class Nidaq(PSIDeviceBase, NidaqControl): status.wait(timeout=self.timeout_wait_for_signal) if self.scan_parameters.scan_name != "nidaq_continuous_scan": status = self.on_kickoff() - self.cancel_on_stop(status) - status.wait(timeout=self._timeout_wait_for_pv) + status.wait() 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) + status = CompareStatus( + self.state, + NidaqState.KICKOFF.value, + timeout=self._timeout_wait_for_pv, + description="Waiting for NIDAQ to enter KICKOFF state", + ) + self.kickoff_call.put(1) self.cancel_on_stop(status) return status From b39ff70ece9625d77344d2d7f04b733638557c27 Mon Sep 17 00:00:00 2001 From: x01da Date: Thu, 30 Jul 2026 10:51:25 +0200 Subject: [PATCH 5/7] fix(hutch cam): do not override internal ophyd name --- debye_bec/devices/cameras/hutch_cam.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/debye_bec/devices/cameras/hutch_cam.py b/debye_bec/devices/cameras/hutch_cam.py index 376845d..6479ed1 100644 --- a/debye_bec/devices/cameras/hutch_cam.py +++ b/debye_bec/devices/cameras/hutch_cam.py @@ -39,7 +39,7 @@ class HutchCam(PSIDeviceBase): super().__init__(name=name, scan_info=scan_info, **kwargs) self.scan_parameters: ScanServerScanInfo = None self.hostname = prefix - self.name = "" + self.camera_name = "" # pylint: disable=E1101 def on_connected(self) -> None: @@ -49,12 +49,14 @@ class HutchCam(PSIDeviceBase): info_url = f"http://{CAM_USERNAME}:{CAM_PASSWORD}@{self.hostname}.psi.ch/-wvhttp-01-/info.cgi?item=c.1.name" response = requests.get(info_url, timeout=5) response.raise_for_status() - self.name = response.content.decode("utf-8").split("c.1.name.utf8:=")[-1].strip().lower() + self.camera_name = ( + response.content.decode("utf-8").split("c.1.name.utf8:=")[-1].strip().lower() + ) def on_stage(self) -> DeviceStatus: """Called while staging the device.""" self.scan_parameters = fetch_scan_info(self.scan_info) - file_path = get_full_path(self.scan_info.msg, name=self.name).removesuffix("h5") + file_path = get_full_path(self.scan_info.msg, name=self.camera_name).removesuffix("h5") thread = threading.Thread( target=self.acquire_and_save_from_video, args=(file_path,), daemon=True From 9670648bd8effd72d513c826031f593bc62421c8 Mon Sep 17 00:00:00 2001 From: x01da Date: Thu, 30 Jul 2026 11:26:03 +0200 Subject: [PATCH 6/7] fix(widgets): fix init parameters after new bec version --- debye_bec/bec_widgets/widgets/data_viewer/data_viewer.py | 2 +- debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/debye_bec/bec_widgets/widgets/data_viewer/data_viewer.py b/debye_bec/bec_widgets/widgets/data_viewer/data_viewer.py index 184a475..efbc4dc 100644 --- a/debye_bec/bec_widgets/widgets/data_viewer/data_viewer.py +++ b/debye_bec/bec_widgets/widgets/data_viewer/data_viewer.py @@ -35,7 +35,7 @@ class DataViewer(BECWidget, QWidget): ICON_NAME = "find_in_page" def __init__(self, *arg, parent=None, **kwargs): - super().__init__(parent=parent, theme_update=True, *arg, **kwargs) + super().__init__(parent=parent, *arg, **kwargs) self.get_bec_shortcuts() central = QWidget() diff --git a/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py b/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py index 180e9fb..887bc26 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py @@ -75,7 +75,7 @@ class DigitalTwin(BECWidget, QWidget): ICON_NAME = "lightbulb" def __init__(self, *arg, parent=None, **kwargs): - super().__init__(parent=parent, theme_update=True, *arg, **kwargs) + super().__init__(parent=parent, *arg, **kwargs) self.get_bec_shortcuts() self.beamline = get_beamline_id() From 235a5e4535c3271c1292e9a6a5db9fcd74f1c79e Mon Sep 17 00:00:00 2001 From: wakonig_k Date: Thu, 30 Jul 2026 14:38:54 +0200 Subject: [PATCH 7/7] refactor(tests): update v4 scan names --- tests/tests_devices/test_pilatus.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/tests/tests_devices/test_pilatus.py b/tests/tests_devices/test_pilatus.py index 2f1bb01..fc02311 100644 --- a/tests/tests_devices/test_pilatus.py +++ b/tests/tests_devices/test_pilatus.py @@ -6,23 +6,14 @@ from unittest import mock import ophyd import pytest from bec_lib.messages import ScanStatusMessage -from bec_server.scan_server.scan_worker import ScanWorker -from bec_server.scan_server.scans.scan_base import ScanInfo as ScanServerScanInfo from bec_server.scan_server.tests.scan_fixtures import * from bec_server.scan_server.tests.scan_fixtures import _MockDevice -from ophyd_devices import CompareStatus, DeviceStatus +from ophyd_devices import DeviceStatus from ophyd_devices.interfaces.base_classes.psi_device_base import DeviceStoppedError from ophyd_devices.tests.utils import MockPV, patch_dual_pvs from ophyd_devices.utils.psi_device_base_utils import TaskStatus -from debye_bec.devices.pilatus.pilatus import ( - ACQUIREMODE, - COMPRESSIONALGORITHM, - DETECTORSTATE, - FILEWRITEMODE, - TRIGGERMODE, - Pilatus, -) +from debye_bec.devices.pilatus.pilatus import ACQUIREMODE, DETECTORSTATE, Pilatus from debye_bec.devices.utils.utils import fetch_scan_info if TYPE_CHECKING: # pragma no cover @@ -38,8 +29,8 @@ if TYPE_CHECKING: # pragma no cover @pytest.fixture( scope="function", params=[ - (("samx", 0.1, 1, 5, "samy", 0, 1, 5), {"relative": True}, "_v4_hexagonal_scan"), - ((1, 0.2), {}, "_v4_time_scan"), + (("samx", 0.1, 1, 5, "samy", 0, 1, 5), {"relative": True}, "hexagonal_scan"), + ((1, 0.2), {}, "time_scan"), ((9000, 10000, 1, 20, 0.1, 9500), {}, "xas_advanced_scan"), ], )