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()