Files

138 lines
5.0 KiB
Python
Raw Permalink Normal View History

"""Safety and protocol checks for the Turtle2 action bridge."""
import importlib.util
from pathlib import Path
import numpy as np
import pytest
_BRIDGE_PATH = Path(__file__).parents[1] / "scripts" / "tcp_ws_bridge.py"
_SPEC = importlib.util.spec_from_file_location("tcp_ws_bridge", _BRIDGE_PATH)
bridge = importlib.util.module_from_spec(_SPEC)
assert _SPEC.loader is not None
_SPEC.loader.exec_module(bridge)
def test_packet_limit_scales_chunk_without_cumulative_ramp():
current = np.zeros(7)
traj = np.array(
[
[0.10, 0.04, -0.02, 0.20, -0.10, 0.05, 0.0],
[0.20, 0.08, -0.04, 0.40, -0.20, 0.10, 0.1],
[0.40, 0.16, -0.08, 0.80, -0.40, 0.20, 0.2],
]
)
limited, scales = bridge._limit_action_packet(traj, current, 0.005, 0.0125)
np.testing.assert_allclose(limited[:, :3], traj[:, :3] * 0.005 / 0.40)
np.testing.assert_allclose(limited[:, 3:6], traj[:, 3:6] * 0.0125 / 0.80)
assert np.max(np.abs(limited[:, :3] - current[:3])) <= 0.005 + 1e-12
assert np.max(np.abs(limited[:, 3:6] - current[3:6])) <= 0.0125 + 1e-12
assert np.max(np.abs(np.diff(limited[:, :3], axis=0))) <= 0.005 + 1e-12
assert np.max(np.abs(np.diff(limited[:, 3:6], axis=0))) <= 0.0125 + 1e-12
assert scales["position"][1] < 1.0
assert scales["rotation"][1] < 1.0
def test_prepare_robot_actions_keeps_packet_endpoint_small():
traj = np.array(
[
[0.10, 0.00, 0.00, 0.20, 0.00, 0.00, 0.0],
[0.20, 0.00, 0.00, 0.40, 0.00, 0.00, 0.0],
[0.30, 0.00, 0.00, 0.60, 0.00, 0.00, 0.0],
[0.40, 0.00, 0.00, 0.80, 0.00, 0.00, 0.0],
]
)
state = np.zeros(7)
actions = bridge.prepare_robot_actions(
{"follow1_pos": traj.tolist(), "follow2_pos": traj.tolist()},
state_follow1_pos=state,
state_follow2_pos=state,
state_head_pos=[0.0, -1.0],
state_lift=[0.4],
state_car_pose=np.zeros(3),
action_horizon=4,
action_end_ratio=1.0,
action_interpolate_multiplier=1,
max_position_delta=0.005,
max_rotation_delta=0.0125,
clip_action_delta=True,
)
right = np.asarray(actions["follow2_pos"])
assert right[-1, 0] <= 0.005 + 1e-12
assert right[-1, 3] <= 0.0125 + 1e-12
assert right[-1, 0] < 0.01 # old cumulative clipping reached 4 * 0.005
def test_disabled_base_motion_sends_relative_zero():
actions = bridge.prepare_robot_actions(
{"follow1_pos": [[0.0] * 7] * 2, "follow2_pos": [[0.0] * 7] * 2},
state_follow1_pos=[0.0] * 7, state_follow2_pos=[0.0] * 7,
state_head_pos=[0.0, -1.0], state_lift=[0.1], state_car_pose=[1.2, -0.3, 0.7],
action_horizon=2, action_end_ratio=1.0, action_interpolate_multiplier=1,
max_position_delta=0.005, max_rotation_delta=0.0125, clip_action_delta=True,
)
assert actions["car_pose"] == [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]
def test_right_arm_only_mode_holds_serializer_left_arm():
state_left = np.array([1.0, 2.0, 3.0, 0.1, 0.2, 0.3, 0.4])
right = np.array(
[
[0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.7],
[0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.8],
]
)
synthetic_left = np.full((2, 7), 99.0)
actions = bridge.prepare_robot_actions(
{"follow1_pos": synthetic_left.tolist(), "follow2_pos": right.tolist()},
state_follow1_pos=state_left,
state_follow2_pos=np.zeros(7),
state_head_pos=[0.0, -1.0],
state_lift=[0.4],
state_car_pose=np.zeros(3),
action_horizon=2,
action_end_ratio=1.0,
action_interpolate_multiplier=1,
)
np.testing.assert_allclose(actions["follow1_pos"], np.repeat(state_left[None, :], 2, axis=0))
np.testing.assert_allclose(actions["follow2_pos"], right)
def test_right_arm_feedback_reports_missing_motion():
initial = np.zeros(7)
command = np.array([0.003, 0.0, 0.0, 0.0, 0.01, 0.0, 0.5])
missing = bridge.assess_right_arm_feedback(initial, initial, command)
assert missing["command_requests_motion"]
assert missing["missing_feedback"]
observed = np.array([0.001, 0.0, 0.0, 0.0, 0.003, 0.0, 0.1])
moved = bridge.assess_right_arm_feedback(initial, observed, command)
assert moved["observed_motion"]
assert not moved["missing_feedback"]
def test_raw_action_response_is_rejected_before_robot_packet():
"""The unsupported raw path must not reinterpret virtual padding as pose."""
raw = np.zeros((2, 26))
raw[:, :10] = [0.01, 0.02, 0.03, 1, 0, 0, 0, 1, 0, 0.8]
raw[:, 16:26] = [0.20, 0.10, 0.05, 1, 0, 0, 0, 1, 0, 0.2]
with pytest.raises(ValueError, match="--serialize-actions"):
bridge.prepare_robot_actions(
{"predict_action": raw.tolist()},
state_follow1_pos=np.zeros(7),
state_follow2_pos=np.zeros(7),
state_head_pos=[0, -1],
state_lift=[0.1],
state_car_pose=np.zeros(3),
action_horizon=2,
action_end_ratio=1,
action_interpolate_multiplier=1,
)