fix(autocal): 校验运动单位并等待命令成功后判断到位

阻止单位不一致的姿态命令,避免提前到达的位置快照误报成功或推进下一轴。
相关检查 164 项通过,2064 组目标及 8256 条运动调用与原基线完全一致。
This commit is contained in:
ykcheungab
2026-10-18 20:24:45 +08:00
parent 9352d0c816
commit cf3e638176
8 changed files with 259 additions and 56 deletions
+92 -1
View File
@@ -5,7 +5,15 @@ import math
import pytest
from orisys_autocal.motion.models import AxisParams, BusStatus, ConnectionSpec
from orisys_autocal.motion.models import (
AxisParams,
AxisState,
AxisStatusBits,
BusStatus,
ConnectionSpec,
MotionResult,
Snapshot,
)
from orisys_autocal.motion.pose_move import PoseMover, plan_pose_move
from orisys_autocal.motion.service import MotionService
from orisys_autocal.motion.transport import DemoTransport
@@ -185,3 +193,86 @@ def test_raw_debug_moves_keep_the_existing_user_unit_contract():
transport.set_units(2, 5.0)
assert instance.move_abs(2, 90.0).ok
assert transport.moves == [(2, 90.0)]
@pytest.mark.parametrize("together", [False, True])
@pytest.mark.parametrize("failure", ["refused", "exception"])
def test_arrival_before_command_failure_never_reports_success(qt_app, together, failure):
instance, transport = service()
actions = []
mover = PoseMover(instance, lambda action: actions.append(action) or len(actions))
finished = []
mover.finished.connect(finished.append)
assert mover.start(plan_pose_move(instance, {"x": 0.0}, together=together)).ok
mover.on_snapshot(Snapshot(axes=(instance._last_axes[0],)))
assert not finished and mover.running
if failure == "refused":
transport.set_units(0, 5.0)
result = actions[0](instance)
assert not result.ok and "UNITS" in result.message
mover.on_command_finished(1, result)
else:
mover.on_command_failed(1, "read failed")
assert mover.state.failure and not mover.state.finished
assert not finished and not mover.running
assert transport.moves == []
@pytest.mark.parametrize("together", [False, True])
def test_only_matching_success_and_a_later_snapshot_allow_arrival(qt_app, together):
instance, _transport = service()
actions = []
mover = PoseMover(instance, lambda action: actions.append(action) or len(actions))
assert mover.start(plan_pose_move(instance, {"x": 0.0}, together=together)).ok
snapshot = Snapshot(axes=(instance._last_axes[0],))
mover.on_command_finished(99, MotionResult.success())
mover.on_snapshot(snapshot)
assert mover.running and not mover.state.finished
result = actions[0](instance)
assert result.ok
mover.on_command_finished(1, result)
assert mover.running, "a pre-acknowledgement snapshot was reused"
mover.on_snapshot(Snapshot(axes=(instance._last_axes[0],)))
assert mover.state.finished and not mover.running
def test_each_sequential_axis_waits_for_its_own_command_success(qt_app):
instance, _transport = service()
actions = []
mover = PoseMover(instance, lambda action: actions.append(action) or len(actions))
assert mover.start(plan_pose_move(instance, {"x": 0.0, "y": 0.0})).ok
snapshot = Snapshot(axes=tuple(instance._last_axes[axis] for axis in (0, 1)))
mover.on_snapshot(snapshot)
assert len(actions) == 1 and mover.state.index == 0
result = actions[0](instance)
assert result.ok
mover.on_command_finished(1, result)
mover.on_snapshot(snapshot)
assert len(actions) == 2 and mover.state.index == 1
mover.on_command_finished(1, MotionResult.success())
mover.on_snapshot(snapshot)
assert mover.running and not mover.state.finished
result = actions[1](instance)
assert result.ok
mover.on_command_finished(2, result)
mover.on_snapshot(snapshot)
assert mover.state.finished
@pytest.mark.parametrize("together", [False, True])
@pytest.mark.parametrize("fault", ["alarm", "read"])
def test_axis_faults_remain_visible_while_command_is_pending(qt_app, together, fault):
instance, _transport = service()
mover = PoseMover(instance, lambda _action: 1)
targets = {"x": 0.0, "y": 0.0} if together else {"y": 0.0}
assert mover.start(plan_pose_move(instance, targets, together=together)).ok
bad_axis = AxisState(
axis=1,
units=10.0,
status=AxisStatusBits(raw=512 if fault == "alarm" else 0),
read_ok=fault != "read",
)
mover.on_snapshot(Snapshot(axes=(AxisState(axis=0, units=10.0), bad_axis)))
assert not mover.running and not mover.state.finished
assert ("alarm" if fault == "alarm" else "read failed") in mover.state.failure