From 10be2b5585387fb30e4977071409d89b6f59ff5f Mon Sep 17 00:00:00 2001 From: menzel Date: Tue, 1 Sep 2026 21:49:21 +0200 Subject: [PATCH] fix(falcon): name prefix in __init__ so the device server stops dropping it The __init__ added in the previous commit took (*args, **kwargs). The device server builds a device's init kwargs by intersecting the deviceConfig keys with the NAMED parameters of the class signature (bec_server/device_server/devices/devicemanager.py:469-475), so 'prefix' was silently discarded and the Falcon was constructed with an empty prefix. Every signal then pointed at a bare suffix -- HDF1:FilePath_RBV instead of X12SA-SITORO:HDF1:FilePath_RBV -- and instantiation failed with "TimeoutError: Failed to connect to all signals" listing several hundred PVs. That reads like an unreachable IOC, which is how it was diagnosed at the beamline for two hours, while caget from the same host worked perfectly. The signature now names name, prefix, scan_info, device_manager and xml_file_name explicitly, matching DDG1. Two tests guard it: one asserts every deviceConfig key is a named parameter, the other that a configured prefix reaches the signal PV names. Reported-by: Klaus Wakonig Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KLnmUurqcNd1FiDY5M2uZr --- csaxs_bec/devices/epics/falcon_csaxs.py | 27 +++++++++++++++++++-- tests/tests_devices/test_falcon_csaxs.py | 30 ++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/csaxs_bec/devices/epics/falcon_csaxs.py b/csaxs_bec/devices/epics/falcon_csaxs.py index 521aad58..d818cf10 100644 --- a/csaxs_bec/devices/epics/falcon_csaxs.py +++ b/csaxs_bec/devices/epics/falcon_csaxs.py @@ -4,6 +4,7 @@ import enum import os import threading import time +from typing import TYPE_CHECKING from typing import Literal from bec_lib.file_utils import get_full_path @@ -17,6 +18,9 @@ from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase from csaxs_bec.devices.utils.utils import fetch_scan_info +if TYPE_CHECKING: # pragma: no cover + from bec_lib.devicemanager import DeviceManagerBase, ScanInfo + logger = bec_logger.logger # NOTE Layout of the written HDF5 file, resolved by the IOC relative to its own working @@ -82,16 +86,35 @@ class FalconcSAXS(PSIDeviceBase, FalconControl): file_event = Cpt(FileEventSignal, name="file_event") - def __init__(self, *args, xml_file_name: str | None = None, **kwargs): + def __init__( + self, + name: str, + prefix: str = "", + scan_info: "ScanInfo | None" = None, + device_manager: "DeviceManagerBase | None" = None, + xml_file_name: str | None = None, + **kwargs, + ): """ + Every deviceConfig key must appear here by name. The device server builds the + init kwargs by intersecting the config keys with the class signature's named + parameters, so a bare *args/**kwargs signature silently drops 'prefix' and the + device is built with empty PV names. + Args: + name: device name. + prefix: EPICS prefix, e.g. 'X12SA-SITORO:'. + scan_info: scan info object. + device_manager: BEC device manager. xml_file_name: HDF5 layout description, as the IOC resolves it. Defaults to DEFAULT_XML_FILE_NAME; pass "" to use the plugin's built-in layout. """ self._xml_file_name_config = ( DEFAULT_XML_FILE_NAME if xml_file_name is None else xml_file_name ) - super().__init__(*args, **kwargs) + super().__init__( + name=name, prefix=prefix, scan_info=scan_info, device_manager=device_manager, **kwargs + ) def on_init(self) -> None: """Initialize Falcon Sitoro detector""" diff --git a/tests/tests_devices/test_falcon_csaxs.py b/tests/tests_devices/test_falcon_csaxs.py index 97ce39b0..156544f9 100644 --- a/tests/tests_devices/test_falcon_csaxs.py +++ b/tests/tests_devices/test_falcon_csaxs.py @@ -1,4 +1,5 @@ # pylint: skip-file +import inspect import os import threading from typing import Generator @@ -360,3 +361,32 @@ def test_falcon_accepts_a_valid_layout_silently(mock_det: FalconcSAXS): ): assert falcon._check_xml_layout(timeout=0.05) is True mock_logger.error.assert_not_called() + + +def test_falcon_init_names_every_device_config_key(): + """The device server drops config keys that are not named parameters. + + bec_server's devicemanager builds the init kwargs by intersecting the deviceConfig + keys with the class signature's named parameters. A *args/**kwargs signature + therefore silently discards 'prefix', and every PV is built without it -- which + presents as 'failed to connect to all signals' rather than as a config error. + """ + params = inspect.signature(FalconcSAXS).parameters + for key in ("name", "prefix", "scan_info", "device_manager", "xml_file_name"): + assert key in params, f"{key} must be a named parameter or the device server drops it" + + +def test_falcon_keeps_the_configured_prefix(): + """A prefix passed as the device server passes it must reach the signals.""" + with patched_device( + FalconcSAXS, + name="falcon", + prefix="X12SA-SITORO:", + device_manager=DMMock(), + _mock_pv_initial_value=1, + ) as dev: + try: + assert dev.prefix == "X12SA-SITORO:" + assert dev.hdf5.file_path.pvname.startswith("X12SA-SITORO:") + finally: + dev.destroy()