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 <klaus.wakonig@psi.ch>

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KLnmUurqcNd1FiDY5M2uZr
This commit is contained in:
2026-09-01 22:20:39 +02:00
committed by x12sa
co-authored by Claude Opus 5
parent 98bfeb6586
commit 10be2b5585
2 changed files with 55 additions and 2 deletions
+25 -2
View File
@@ -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"""
+30
View File
@@ -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()