fix(aerotech): move settles only after holding the tolerance for 0.1 s
After a hard stop (5 mm/s, 500 mm/s^2) X rings at ~10 Hz with ~20 um. The single position error check caught a zero crossing (-0.19 um), the move was declared settled 3.5 um off the target and failed (premove of a scan aborted). Now position error and distance to the target must stay within the tolerance for in_position_hold (0.1 s) without interruption. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M8zoDyJxbBLmjZCEPUo5FM
This commit is contained in:
1 parent
201e6406d2
commit
4d95cf755f
2 files changed
+38
-5
No files matched your search
@@ -524,6 +524,9 @@ class A3200Motor(Device, PositionerBase):
|
||||
position error to settle within tolerance before the position is compared with
|
||||
the target. The controller's In Position status is not used: its
|
||||
InPositionDistance can be tighter than the axis settles (X: 10 nm).
|
||||
in_position_hold: position error and distance to the target must stay within
|
||||
tolerance this long (s) before the move counts as settled. Default 0.1 s: after
|
||||
a hard stop X rings at ~10 Hz, a single reading can be a zero crossing.
|
||||
acceleration: ramp rate for moves in user units/s^2; None keeps the controller's
|
||||
ramp settings (DefaultRampRate).
|
||||
max_scan_velocity: highest velocity a continuous scan may set on this axis, in user
|
||||
@@ -580,6 +583,7 @@ class A3200Motor(Device, PositionerBase):
|
||||
egu="mm",
|
||||
tolerance=0.001,
|
||||
in_position_timeout=2.0,
|
||||
in_position_hold=0.1,
|
||||
require_homed=True,
|
||||
acceleration=None,
|
||||
max_scan_velocity=None,
|
||||
@@ -598,6 +602,7 @@ class A3200Motor(Device, PositionerBase):
|
||||
self.sign = sign
|
||||
self.tolerance = tolerance
|
||||
self.in_position_timeout = in_position_timeout
|
||||
self.in_position_hold = in_position_hold
|
||||
self.require_homed = require_homed
|
||||
self.pso_input = pso_input
|
||||
self.pso_counts_divider = pso_counts_divider
|
||||
@@ -696,13 +701,29 @@ class A3200Motor(Device, PositionerBase):
|
||||
logger.warning(f"{self.name} stopped with faults {faults}.")
|
||||
self._done_moving(success=not faults)
|
||||
return
|
||||
# the profile is done, the axis may still be settling
|
||||
# the profile is done, the axis may still be settling: after a hard stop X rings
|
||||
# at ~10 Hz with ~20 um, so a single small position error can be a zero crossing.
|
||||
# Settled = position error and distance to the target within the tolerance for
|
||||
# in_position_hold without interruption.
|
||||
deadline = time.time() + self.in_position_timeout
|
||||
while abs(self.controller.position_error(self.axis_Id)) > self.tolerance:
|
||||
if time.time() > deadline:
|
||||
settled_since = None
|
||||
while True:
|
||||
now = time.time()
|
||||
in_tolerance = (
|
||||
abs(self.controller.position_error(self.axis_Id)) <= self.tolerance
|
||||
and abs(self.readback.get() - position) <= self.tolerance
|
||||
)
|
||||
if not in_tolerance:
|
||||
settled_since = None
|
||||
elif settled_since is None:
|
||||
settled_since = now
|
||||
if settled_since is not None and now - settled_since >= self.in_position_hold:
|
||||
break
|
||||
if now > deadline:
|
||||
logger.warning(
|
||||
f"{self.name}: position error not within {self.tolerance}"
|
||||
f" {self.egu} {self.in_position_timeout} s after the end of the move."
|
||||
f"{self.name}: not settled within {self.tolerance} {self.egu} for"
|
||||
f" {self.in_position_hold} s, {self.in_position_timeout} s after the end"
|
||||
" of the move."
|
||||
)
|
||||
break
|
||||
time.sleep(0.02)
|
||||
|
||||
@@ -377,6 +377,18 @@ def test_move_waits_for_position_error_within_tolerance():
|
||||
assert motor.controller.position_error("X") == 0.0
|
||||
|
||||
|
||||
def test_move_does_not_settle_on_a_zero_crossing():
|
||||
# ringing after the stop: one reading within tolerance, then out again, then settled
|
||||
motor = _sim_motor(velocity=100.0, tolerance=0.001, in_position_hold=0.1)
|
||||
errors = iter([0.0] + [0.01] * 5)
|
||||
motor.controller.position_error = lambda axis: next(errors, 0.0)
|
||||
start = time.time()
|
||||
status = motor.move(1.0, wait=True)
|
||||
# 5 readings out of tolerance (~0.1 s), then held for 0.1 s
|
||||
assert time.time() - start >= 0.2
|
||||
assert status.success
|
||||
|
||||
|
||||
def test_move_ignores_in_position_tighter_than_tolerance():
|
||||
# settled within tolerance, but outside the controller's InPositionDistance
|
||||
motor = _sim_motor(velocity=100.0, tolerance=0.001)
|
||||
|
||||
Reference in new issue
Block a user