From ff8288bcf992cfa14ab325aaa0f98e0496b4eb0d Mon Sep 17 00:00:00 2001 From: ykcheungab Date: Wed, 7 Oct 2026 16:21:25 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8E=BB=E6=8E=89=E4=BB=BF=E7=9C=9F=E8=BF=90?= =?UTF-8?q?=E5=8A=A8=E5=BA=8F=E5=88=97=E7=9A=84=E5=8D=95=E6=AD=A5=E8=B6=85?= =?UTF-8?q?=E6=97=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 偏航转过 90° 时轴仍在运动,20 秒超时会把全部轴停掉。pose move 测试通过。 --- .../src/orisys_autocal/motion/pose_move.py | 17 +++----------- apps/autocal/tests/test_pose_move.py | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/apps/autocal/src/orisys_autocal/motion/pose_move.py b/apps/autocal/src/orisys_autocal/motion/pose_move.py index 619a1b5..88ad6dd 100644 --- a/apps/autocal/src/orisys_autocal/motion/pose_move.py +++ b/apps/autocal/src/orisys_autocal/motion/pose_move.py @@ -27,7 +27,7 @@ from __future__ import annotations import time from collections.abc import Mapping, Sequence -from dataclasses import dataclass, field +from dataclasses import dataclass from PySide6.QtCore import QObject, Signal @@ -43,10 +43,6 @@ from .units import user_units_to_deg SETTLE_MM = 0.01 SETTLE_DEG = 0.01 -#: Per-step patience. A step that is neither arriving nor moving this long is -#: treated as stuck: reported and abandoned, with no further axes commanded. -STEP_TIMEOUT_S = 20.0 - #: How long a step may report itself idle while still short of its target before it #: counts as failed — a drive that has stopped without arriving will never arrive. STALL_GRACE_S = 2.0 @@ -103,7 +99,6 @@ class MoveState: finished: bool = False failure: str = "" cancelled: bool = False - _step_started_at: float = field(default=0.0) _idle_since: float | None = None @property @@ -205,7 +200,7 @@ class PoseMover(QObject): return MotionResult.refused("没有要移动的轴 / nothing to move") stamp = time.monotonic() if now is None else now - self.state = MoveState(plan=plan, started_at=stamp, _step_started_at=stamp) + self.state = MoveState(plan=plan, started_at=stamp) self.started.emit(plan) self._issue_current() return MotionResult.success(plan.describe()) @@ -229,7 +224,6 @@ class PoseMover(QObject): if step is None: self._complete() return - self.state._step_started_at = time.monotonic() self.state._idle_since = None if not step.units == step.units: # NaN: no calibration for this role self._fail(f"{step.role} 无法换算成用户单位 / cannot convert {step.role}") @@ -258,12 +252,6 @@ class PoseMover(QObject): if step is None: self._complete() return - now = time.monotonic() - if now - self.state._step_started_at > STEP_TIMEOUT_S: - self._fail( - f"{step.role} 等待超时 / timed out waiting for {step.role}" - ) - return state = snapshot.axis(step.axis) if state is None: @@ -292,6 +280,7 @@ class PoseMover(QObject): return # Idle, but not there. A drive that has stopped short will not resume, so # this is a failure rather than something to keep waiting for. + now = time.monotonic() if self.state._idle_since is None: self.state._idle_since = now elif now - self.state._idle_since > STALL_GRACE_S: diff --git a/apps/autocal/tests/test_pose_move.py b/apps/autocal/tests/test_pose_move.py index a4ccd7b..ab1ec6a 100644 --- a/apps/autocal/tests/test_pose_move.py +++ b/apps/autocal/tests/test_pose_move.py @@ -245,6 +245,29 @@ def test_the_mover_waits_while_an_axis_is_still_moving(): assert mover.state.index == 0 +def test_a_long_yaw_is_not_cut_off_while_it_is_still_moving(monkeypatch): + """A yaw past 90° can take longer than a minute. Keep it while the axis moves. + + The old step cap abandoned the plan after 20s and the window then stopped + every axis, so a 180° yaw never arrived. + """ + clock = {"t": 1_000.0} + monkeypatch.setattr( + "orisys_autocal.motion.pose_move.time.monotonic", lambda: clock["t"] + ) + service = make_service( + limits={**LIMITS, "max_yaw_deg": 180.0, "max_pitch_deg": 360.0} + ) + poller = FakePoller(service) + mover = PoseMover(service, poller.submit) + assert mover.start(plan_pose_move(service, {"yaw": 180.0})).ok + clock["t"] += 120.0 + mover.on_snapshot(snapshot_of(service, {3: 1_000.0}, idle=False)) + assert mover.running + assert mover.state.failure == "" + assert len(poller.commands) == 1 + + def test_the_mover_fails_when_an_axis_stops_short(): """A drive that has stopped is not going to arrive: say so, do not wait.""" service = make_service()