feat: 添加手柄主键回初始位姿

This commit is contained in:
2026-07-31 15:31:35 +08:00
parent 5267da14c2
commit 4e068ce637
2 changed files with 95 additions and 0 deletions
+70
View File
@@ -30,6 +30,76 @@ class FakeTime:
return SimpleNamespace(nanoseconds=0) return SimpleNamespace(nanoseconds=0)
def _primary_button_teleop(*, move_error=None):
events = []
errors = []
snapshot = JointStateSnapshot([0.2] * 7, time.monotonic())
class Adapter:
def move_to_initial_pose(self):
events.append("move")
if move_error is not None:
raise move_error
def read_joint_state(self):
events.append("read")
return snapshot
teleop = object.__new__(SingleArmVelocityTeleop)
teleop._arm_name = "right_rm75"
teleop._adapter = Adapter()
teleop._last_primary_pressed = None
teleop._grip_rearm_required = False
teleop._safe_stop = lambda reset_active: events.append(
("stop", reset_active)
)
teleop._reset_joint_state = lambda value: events.append(("sync", value))
teleop._handle_trigger_gripper = lambda msg: None
teleop.get_clock = lambda: SimpleNamespace(now=lambda: FakeTime())
teleop.get_logger = lambda: SimpleNamespace(
info=lambda message: None,
error=lambda message: errors.append(message),
)
return teleop, events, errors, snapshot
def test_primary_button_rising_edge_moves_once_and_resyncs() -> None:
teleop, events, _, snapshot = _primary_button_teleop()
released = SimpleNamespace(primary=False)
pressed = SimpleNamespace(primary=True)
teleop._on_controller(released)
teleop._on_controller(pressed)
teleop._on_controller(pressed)
teleop._on_controller(released)
teleop._on_controller(pressed)
expected_once = [
("stop", True),
"move",
"read",
("sync", snapshot),
]
assert events == expected_once * 2
assert teleop._grip_rearm_required
def test_primary_button_move_failure_logs_and_stays_stopped() -> None:
failure = RuntimeError("rm_movej failed")
teleop, events, errors, _ = _primary_button_teleop(
move_error=failure
)
teleop._on_controller(SimpleNamespace(primary=False))
teleop._on_controller(SimpleNamespace(primary=True))
assert events == [("stop", True), "move"]
assert teleop._grip_rearm_required
assert errors == [
"right_rm75 回初始位姿失败:rm_movej failed"
]
def test_startup_joint_query_initializes_qp_and_command_history() -> None: def test_startup_joint_query_initializes_qp_and_command_history() -> None:
positions = [0.1] * 7 positions = [0.1] * 7
pose = np.eye(4) pose = np.eye(4)
@@ -295,6 +295,7 @@ class SingleArmVelocityTeleop(Node):
self._control_fault_latched = False self._control_fault_latched = False
self._stop_sent = True self._stop_sent = True
self._trigger_tool_open = True self._trigger_tool_open = True
self._last_primary_pressed: bool | None = None
self._last_trigger_pressed: bool | None = None self._last_trigger_pressed: bool | None = None
self._tool_command_queue: queue.Queue[tuple[bool, str] | None] | None = None self._tool_command_queue: queue.Queue[tuple[bool, str] | None] | None = None
self._tool_worker_stop = threading.Event() self._tool_worker_stop = threading.Event()
@@ -514,8 +515,32 @@ class SingleArmVelocityTeleop(Node):
def _on_controller(self, msg: XrController) -> None: def _on_controller(self, msg: XrController) -> None:
self._last_msg = msg self._last_msg = msg
self._last_msg_time = self.get_clock().now() self._last_msg_time = self.get_clock().now()
self._handle_initial_pose_button(msg)
self._handle_trigger_gripper(msg) self._handle_trigger_gripper(msg)
def _handle_initial_pose_button(self, msg: XrController) -> None:
if self._last_primary_pressed is None:
self._last_primary_pressed = msg.primary
return
rising_edge = msg.primary and not self._last_primary_pressed
self._last_primary_pressed = msg.primary
if not rising_edge:
return
self._grip_rearm_required = True
self._safe_stop(reset_active=True)
try:
self._adapter.move_to_initial_pose()
self._reset_joint_state(self._adapter.read_joint_state())
except Exception as exc:
self.get_logger().error(
f"{self._arm_name} 回初始位姿失败:{exc}"
)
return
self.get_logger().info(f"{self._arm_name} 已回到初始位姿。")
def _handle_trigger_gripper(self, msg: XrController) -> None: def _handle_trigger_gripper(self, msg: XrController) -> None:
if not self._enable_tool_control or not self._enable_trigger_gripper_control: if not self._enable_tool_control or not self._enable_trigger_gripper_control:
return return