From aa49276d8d059bd527373c4f4bf4ba8b5661abaa Mon Sep 17 00:00:00 2001 From: x01dc Date: Thu, 29 Oct 2026 12:59:53 +0100 Subject: [PATCH] fix(aerotech): calibrate returns string keys so the scan does not hang calibrate() returned {channel: offset} with integer keys. The scan server cannot decode an RPC reply with integer map keys (msgpack strict_map_key), so the calibrate status never completed and aerotech_cont_grid waited forever in pre_scan. Now {"enc": offset}; new test round-trips the RPC return values of the capture device through the BEC serializer. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01M8zoDzDfsCTY2o5bT3J7Wv --- csaxs_bec/devices/aerotech/a3200.py | 8 ++++++-- tests/tests_devices/test_a3200.py | 18 ++++++++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/csaxs_bec/devices/aerotech/a3200.py b/csaxs_bec/devices/aerotech/a3200.py index b3eb69b8..5360403b 100644 --- a/csaxs_bec/devices/aerotech/a3200.py +++ b/csaxs_bec/devices/aerotech/a3200.py @@ -890,12 +890,16 @@ class A3200PositionCapture(PSIDeviceBase): frames_per_trigger = getattr(frames, "frames_per_trigger", 1) if frames else 1 self._correlation_group = "monitored" if frames_per_trigger == 1 else "burst_group" - def calibrate(self) -> dict[int, float]: + def calibrate(self) -> dict[str, float]: """ Determine the offset between encoder counts and program position of each channel: one capture triggered by a software PSO fire with the axes at rest. Fires the PSO output once: the line trigger receiver (DDG) must not be armed. + + Returns the offsets by signal name (enc). String keys: the return value + of an RPC call is sent with msgpack, which rejects integer map keys, and the scan + would wait forever for the call to finish. """ channels = sorted(self.channels) self.controller.dataacq_arm( @@ -910,7 +914,7 @@ class A3200PositionCapture(PSIDeviceBase): axis = self.channels[ch] position = self.controller.get_position(axis) self._offsets[ch] = position - counts / self.controller.counts_per_unit(axis) - return dict(self._offsets) + return {f"enc{ch}": offset for ch, offset in self._offsets.items()} def arm(self, n_triggers: int) -> None: """Arm the capture of the next n_triggers trigger edges (start of the array).""" diff --git a/tests/tests_devices/test_a3200.py b/tests/tests_devices/test_a3200.py index e69a744f..a62e405d 100644 --- a/tests/tests_devices/test_a3200.py +++ b/tests/tests_devices/test_a3200.py @@ -305,8 +305,22 @@ def test_line_trigger_needs_pso_input(): def test_capture_calibration_removes_counter_offset(): _, _, capture = _capture_setup() offsets = capture.calibrate() - assert offsets[6] == pytest.approx(-777 / 20000) - assert offsets[2] == pytest.approx(55 / 10000) + assert offsets["enc6"] == pytest.approx(-777 / 20000) + assert offsets["enc2"] == pytest.approx(55 / 10000) + + +def test_capture_rpc_returns_survive_msgpack(): + # an RPC return value that msgpack cannot decode leaves the scan waiting forever + from bec_lib.serialization import MsgpackSerialization + + x, _, capture = _capture_setup() + returns = [capture.calibrate()] + x.move(-1.0, wait=True) + capture.arm(5) + x.move(1.0, wait=True) + returns.append(capture.read_line()) + for value in returns: + assert MsgpackSerialization.loads(MsgpackSerialization.dumps(value)) == value def test_capture_line_publishes_positions():