fix(positioner): validate deadband against tolerance

This commit is contained in:
wakonig_k committed 2026-09-28 18:17:29 +02:00
1 parent 42370b4c3c
commit 003717ff46
2 files changed
+35 -5

No files matched your search

@@ -142,7 +142,7 @@ class PSISimplePositionerBase(ABC, PSIDeviceBase, PositionerBase):
Args:
name (str): (required) the name of the device
limits (list | tuple | None): If given, a length-2 sequence within the range of which movement is allowed.
deadband (float | None): If given, set a soft deadband of this absolute value, within which positioner moves will return immediately. If the positioner has no motor_done_move signal, you must provide this.
deadband (float | None): If given, set a soft deadband of this absolute value, within which positioner moves will return immediately. If the positioner has no motor_done_move signal, you must provide this. It must not exceed the configured tolerance when moving.
use_put_completion (bool | None): If given, use put completion on the setpoint signal to resolve the move status.
override_suffixes (dict[str, str]): a dictionary of signal_name: pv_suffix which will replace the values in the signal classvar.
"""
@@ -294,13 +294,17 @@ class PSISimplePositionerBase(ABC, PSIDeviceBase, PositionerBase):
TimeoutError
When motion takes longer than `timeout`
ValueError
On invalid positions
On invalid positions or if the deadband exceeds the configured tolerance
RuntimeError
If motion fails other than timing out
"""
if self._deadband is not None and abs(position - self._position) < self._deadband:
return MoveStatus(self, position, done=True, success=True)
if self._deadband is not None:
tolerance = self.tolerance.get()
if self._deadband > tolerance:
raise ValueError(f"Deadband {self._deadband} must not exceed tolerance {tolerance}")
if abs(position - self._position) < self._deadband:
return MoveStatus(self, position, done=True, success=True)
if timeout is None:
timeout = self._timeout
+27 -1
View File
@@ -178,7 +178,7 @@ def mock_done_signal_positioner_with_tolerance():
with patch.object(ophyd, "cl") as mock_cl:
mock_cl.get_pv = MockPV
mock_cl.thread_class = threading.Thread
dev = DoneSignalPositionerWithTolerance(name=name, prefix=prefix, deadband=0.01)
dev = DoneSignalPositionerWithTolerance(name=name, prefix=prefix, deadband=0.001)
patch_dual_pvs(dev)
dev.wait_for_connection()
dev._set_position(0)
@@ -287,3 +287,29 @@ def test_tolerance_checks_requested_target_even_if_setpoint_changes(
assert st.target == 5
with pytest.raises(RuntimeError, match="outside of tolerance"):
st.wait(timeout=1)
def test_move_rejects_tolerance_tighter_than_deadband_before_setpoint_write(
mock_done_signal_positioner_with_tolerance,
):
dev = mock_done_signal_positioner_with_tolerance
dev.tolerance.put(0.0005)
with patch.object(dev.user_setpoint, "put", wraps=dev.user_setpoint.put) as put:
with pytest.raises(ValueError, match="tolerance"):
dev.move(5, wait=False)
put.assert_not_called()
def test_equal_deadband_and_tolerance_allow_instant_success(
mock_done_signal_positioner_with_tolerance,
):
dev = mock_done_signal_positioner_with_tolerance
assert dev.tolerance.get() == dev._deadband
with patch.object(dev.user_setpoint, "put", wraps=dev.user_setpoint.put) as put:
st = dev.move(0.0005, wait=False)
put.assert_not_called()
assert st.done
assert st.success