refactor(timepix): leave detector setup to the IOC and only check it at stage
CI for superxas_bec / test (push) Successful in 1m59s

This commit is contained in:
wyzula_j committed 2026-10-22 15:56:56 +02:00
1 parent 531f8657c4
commit bda6e5f709
2 files changed
+66 -15

No files matched your search

+27 -9
View File
@@ -213,6 +213,14 @@ TIMEPIX_FLY_BACKEND_DEFAULT_REDIS_TOPIC = "public/timepix_fly_backend/raw_data"
TIMEPIX_SCAN_NAME = "timepix_scan"
# detector setup lives in the IOC (autosave); BEC only checks it at stage
EXPECTED_DETECTOR_SETUP = {
"trigger_mode": TRIGGERMODE.INTERNAL,
"trigger_source": TRIGGERSOURCE.HDMI1_1,
"exposure_mode": EXPOSUREMODE.TIMED,
}
EXPECTED_XES_SETUP = {"tdc1_edge": TDCEdge.RISING, "tdc1_output": TDCOuput.ALL_CHANNELS}
# max wait for the images of a lost acquisition before repeating it
LOST_ACQUISITION_IMAGE_TIMEOUT = 2.0
@@ -721,13 +729,25 @@ class Timepix(PSIDeviceBase, TimePixControl):
"""Enable XES specific settings for the TimePix detector."""
enabled_value = 1 if enabled else 0
self.cam.tdc1_enable.set(enabled_value).wait(timeout=self._pv_timeout)
self.cam.tdc2_enable.set(enabled_value).wait(timeout=self._pv_timeout)
# TDC2 is not cabled; TimePixFly would count its edges as periods too
self.cam.tdc2_enable.set(0).wait(timeout=self._pv_timeout)
self.cam.raw_enable.set(enabled_value).wait(timeout=self._pv_timeout)
if enabled:
self.cam.tdc1_edge.set(TDCEdge.RISING).wait(timeout=self._pv_timeout)
self.cam.tdc1_output.set(TDCOuput.ALL_CHANNELS).wait(timeout=self._pv_timeout)
self.cam.tdc2_edge.set(TDCEdge.RISING).wait(timeout=self._pv_timeout)
self.cam.tdc2_output.set(TDCOuput.ALL_CHANNELS).wait(timeout=self._pv_timeout)
def _check_detector_setup(self) -> None:
"""Warn about detector setup PVs, kept in the IOC, that differ from what BEC expects."""
expected = dict(EXPECTED_DETECTOR_SETUP)
if self.enable_xes:
expected.update(EXPECTED_XES_SETUP)
wrong = []
for attr, value in expected.items():
current = getattr(self.cam, attr).get()
if current != value:
wrong.append(f"{attr}={current} (expected {value.name})")
if wrong:
logger.warning(
f"{self.name}: detector setup differs from what BEC expects: {', '.join(wrong)}. "
"These are set in the IOC, not by BEC."
)
def wait_for_connection(
self, all_signals: bool = False, timeout: float | None = None, **kwargs
@@ -745,9 +765,6 @@ class Timepix(PSIDeviceBase, TimePixControl):
logger.info(f"On Connected of TimePix detector {self.name}...")
# Prepare TimePix Detector
self._enable_xes_settings(self.enable_xes)
self.cam.trigger_mode.set(TRIGGERMODE.INTERNAL).wait(timeout=self._pv_timeout)
self.cam.trigger_source.set(TRIGGERSOURCE.HDMI1_1).wait(timeout=self._pv_timeout)
self.cam.exposure_mode.set(EXPOSUREMODE.TIMED).wait(timeout=self._pv_timeout)
# Reset array counter on connect
self.cam.array_counter.set(0).wait(timeout=self._pv_timeout)
# Set image mode to multiple
@@ -830,6 +847,7 @@ class Timepix(PSIDeviceBase, TimePixControl):
self.cam.acquire_period.set(exp_time).wait(timeout=self._pv_timeout)
self.cam.num_images.set(burst_images).wait(timeout=self._pv_timeout)
self.cam.data_source.set(DATASOURCE.IMAGE).wait(timeout=self._pv_timeout)
self._check_detector_setup()
# Setup file writing
self._full_path = get_full_path(scan_msg, name="timepix")
+39 -6
View File
@@ -264,16 +264,12 @@ def pixel_map():
def test_timepix_on_connected_configures_signals_and_registers_callback(timepix):
"""Connected hook should configure camera, file writer, and backend callback."""
timepix.cam.tdc2_enable.put(1)
timepix.on_connected()
assert timepix.cam.tdc1_enable.get() == 1
assert timepix.cam.tdc2_enable.get() == 1
assert timepix.cam.tdc2_enable.get() == 0
assert timepix.cam.raw_enable.get() == 1
assert timepix.cam.tdc1_edge.get() == TDCEdge.RISING
assert timepix.cam.tdc1_output.get() == TDCOuput.ALL_CHANNELS
assert timepix.cam.trigger_mode.get() == TRIGGERMODE.INTERNAL
assert timepix.cam.trigger_source.get() == TRIGGERSOURCE.HDMI1_1
assert timepix.cam.exposure_mode.get() == EXPOSUREMODE.TIMED
assert timepix.cam.array_counter.get() == 0
assert timepix.hdf.enable.get() == "1"
assert timepix.hdf.file_write_mode.get() == FILEWRITEMODE.STREAM.value
@@ -909,3 +905,40 @@ def test_timepix_stop_cancels_repetition_of_lost_acquisition(xes_timepix):
assert timepix.backend.on_trigger_finished.call_count == 1
assert not timepix._xes_acquisitions
assert timepix._images_to_skip == 0
def test_timepix_on_connected_leaves_detector_setup_to_the_ioc(timepix):
"""Trigger and TDC edge/output PVs belong to the IOC setup; connecting must not write them."""
timepix.cam.trigger_mode.put(TRIGGERMODE.EXTERNAL)
timepix.cam.exposure_mode.put(EXPOSUREMODE.TRIGGER_WIDTH)
timepix.cam.tdc1_edge.put(TDCEdge.FALLING)
timepix.cam.tdc1_output.put(TDCOuput.CHANNEL_0)
timepix.on_connected()
assert timepix.cam.trigger_mode.get() == TRIGGERMODE.EXTERNAL
assert timepix.cam.exposure_mode.get() == EXPOSUREMODE.TRIGGER_WIDTH
assert timepix.cam.tdc1_edge.get() == TDCEdge.FALLING
assert timepix.cam.tdc1_output.get() == TDCOuput.CHANNEL_0
def test_timepix_check_detector_setup_warns_about_unexpected_values(timepix):
"""A setup PV that differs from what BEC expects is reported, not overwritten."""
timepix._enable_xes = True
timepix.cam.trigger_mode.put(TRIGGERMODE.EXTERNAL)
timepix.cam.tdc1_edge.put(TDCEdge.FALLING)
with mock.patch("superxas_bec.devices.timepix.timepix.logger") as logger:
timepix._check_detector_setup()
message = logger.warning.call_args.args[0]
assert "trigger_mode" in message and "tdc1_edge" in message
assert timepix.cam.trigger_mode.get() == TRIGGERMODE.EXTERNAL
def test_timepix_check_detector_setup_is_quiet_when_setup_matches(timepix):
"""No warning when the IOC setup matches."""
timepix._enable_xes = True
with mock.patch("superxas_bec.devices.timepix.timepix.logger") as logger:
timepix._check_detector_setup()
logger.warning.assert_not_called()