Files
TG3/tg3_local_teleop/test_gesture_toggle.py

354 lines
12 KiB
Python

#!/usr/bin/env python3
from __future__ import annotations
import unittest
from gesture_toggle import (
GuardedMomentaryGesture,
GestureToggle,
normalized_pose_to_positions,
right_a_pressed,
right_b_pressed,
select_hand_target,
select_right_hand_target,
)
POINT_POSE = [0.2, 0.688, 0.0, 0.98, 0.98, 0.98]
class GestureToggleTest(unittest.TestCase):
def setUp(self) -> None:
self.gesture = GestureToggle(hold_seconds=1.0, release_seconds=0.5)
self.gesture.new_session()
def stable_release(self, started_at: float) -> float:
self.assertFalse(
self.gesture.update(
started_at, armed=True, input_healthy=True, pressed=False
)
)
finished_at = started_at + 0.5
self.assertFalse(
self.gesture.update(
finished_at, armed=True, input_healthy=True, pressed=False
)
)
self.assertFalse(self.gesture.require_release)
return finished_at
def test_new_session_held_button_cannot_activate(self) -> None:
self.assertFalse(
self.gesture.update(0.0, armed=True, input_healthy=True, pressed=True)
)
self.assertFalse(
self.gesture.update(10.0, armed=True, input_healthy=True, pressed=True)
)
self.assertFalse(self.gesture.active)
self.assertTrue(self.gesture.freeze_right_hand)
def test_continuous_one_second_holds_toggle_once_each(self) -> None:
released_at = self.stable_release(0.0)
self.assertFalse(
self.gesture.update(
released_at + 0.01,
armed=True,
input_healthy=True,
pressed=True,
)
)
self.assertFalse(
self.gesture.update(
released_at + 1.0,
armed=True,
input_healthy=True,
pressed=True,
)
)
self.assertTrue(
self.gesture.update(
released_at + 1.01,
armed=True,
input_healthy=True,
pressed=True,
)
)
self.assertTrue(self.gesture.active)
self.assertEqual(self.gesture.toggle_count, 1)
# Keeping the same physical press held cannot turn the gesture off.
self.assertFalse(
self.gesture.update(
released_at + 10.0,
armed=True,
input_healthy=True,
pressed=True,
)
)
self.assertTrue(self.gesture.active)
second_release = self.stable_release(released_at + 10.01)
self.gesture.update(
second_release + 0.01,
armed=True,
input_healthy=True,
pressed=True,
)
self.assertTrue(
self.gesture.update(
second_release + 1.02,
armed=True,
input_healthy=True,
pressed=True,
)
)
self.assertFalse(self.gesture.active)
self.assertEqual(self.gesture.toggle_count, 2)
self.assertFalse(self.gesture.freeze_right_hand)
def test_short_press_freezes_and_requires_stable_release(self) -> None:
released_at = self.stable_release(0.0)
self.gesture.update(
released_at + 0.01,
armed=True,
input_healthy=True,
pressed=True,
)
self.assertTrue(self.gesture.freeze_right_hand)
self.assertEqual(self.gesture.state, "holding_start")
self.gesture.update(
released_at + 0.8,
armed=True,
input_healthy=True,
pressed=False,
)
self.assertFalse(self.gesture.active)
self.assertTrue(self.gesture.require_release)
self.assertTrue(self.gesture.freeze_right_hand)
self.gesture.update(
released_at + 1.29,
armed=True,
input_healthy=True,
pressed=False,
)
self.assertTrue(self.gesture.require_release)
self.gesture.update(
released_at + 1.3,
armed=True,
input_healthy=True,
pressed=False,
)
self.assertFalse(self.gesture.require_release)
self.assertFalse(self.gesture.freeze_right_hand)
def test_invalid_button_or_feedback_gap_cancels_pending_hold(self) -> None:
released_at = self.stable_release(0.0)
self.gesture.update(
released_at + 0.01,
armed=True,
input_healthy=True,
pressed=True,
)
self.gesture.update(
released_at + 0.99,
armed=True,
input_healthy=True,
pressed=None,
)
self.assertTrue(self.gesture.require_release)
self.assertIsNone(self.gesture.hold_started_at)
released_at = self.stable_release(released_at + 1.0)
self.gesture.update(
released_at + 0.01,
armed=True,
input_healthy=True,
pressed=True,
)
self.gesture.update(
released_at + 0.99,
armed=True,
input_healthy=False,
pressed=True,
)
self.assertFalse(self.gesture.active)
self.assertTrue(self.gesture.require_release)
self.assertIsNone(self.gesture.hold_started_at)
def test_feedback_gap_preserves_already_active_gesture(self) -> None:
released_at = self.stable_release(0.0)
self.gesture.update(
released_at + 0.01,
armed=True,
input_healthy=True,
pressed=True,
)
self.assertTrue(
self.gesture.update(
released_at + 1.02,
armed=True,
input_healthy=True,
pressed=True,
)
)
self.gesture.update(
released_at + 1.03,
armed=True,
input_healthy=False,
pressed=True,
)
self.assertTrue(self.gesture.active)
self.assertTrue(self.gesture.require_release)
def test_disarm_clears_active_without_an_output_action(self) -> None:
released_at = self.stable_release(0.0)
self.gesture.update(
released_at + 0.01,
armed=True,
input_healthy=True,
pressed=True,
)
self.gesture.update(
released_at + 1.02,
armed=True,
input_healthy=True,
pressed=True,
)
self.assertTrue(self.gesture.active)
self.assertFalse(
self.gesture.update(
released_at + 1.03,
armed=False,
input_healthy=True,
pressed=True,
)
)
self.assertFalse(self.gesture.active)
self.assertEqual(self.gesture.state, "disarmed")
self.assertTrue(self.gesture.require_release)
class GestureHelpersTest(unittest.TestCase):
def test_right_a_is_index_zero_and_malformed_values_fail_closed(self) -> None:
self.assertTrue(right_a_pressed({"button": {"right": [1, 0, 0]}}))
self.assertFalse(right_a_pressed({"button": {"right": [0, 1, 1]}}))
self.assertIsNone(right_a_pressed({"button": {"right": []}}))
self.assertIsNone(right_a_pressed({"button": {"right": [-1]}}))
self.assertIsNone(right_a_pressed({"button": {"right": [0.5]}}))
def test_right_b_is_index_one_and_malformed_values_fail_closed(self) -> None:
self.assertTrue(right_b_pressed({"button": {"right": [0, 1, 0]}}))
self.assertFalse(right_b_pressed({"button": {"right": [1, 0, 1]}}))
self.assertIsNone(right_b_pressed({"button": {"right": [0]}}))
self.assertIsNone(right_b_pressed({"button": {"right": [0, -1]}}))
self.assertIsNone(right_b_pressed({"button": {"right": [0, 0.5]}}))
def test_point_pose_converts_to_expected_brainco_positions(self) -> None:
self.assertEqual(
normalized_pose_to_positions(POINT_POSE, 1, 1000),
[201, 688, 1, 980, 980, 980],
)
def test_right_only_override_and_short_press_freeze(self) -> None:
source = [401, 401, 51, 51, 51, 51]
previous = [450, 430, 80, 90, 100, 110]
point = [201, 688, 1, 980, 980, 980]
self.assertEqual(
select_right_hand_target(
source, previous, point, active=True, freeze=False
),
point,
)
self.assertEqual(
select_right_hand_target(
source, previous, point, active=False, freeze=True
),
previous,
)
self.assertEqual(
select_right_hand_target(
source, previous, point, active=False, freeze=False
),
source,
)
def test_same_point_target_can_be_applied_to_both_hands(self) -> None:
point = [201, 688, 1, 980, 980, 980]
sources = {
"left": [401, 401, 51, 51, 51, 51],
"right": [402, 402, 52, 52, 52, 52],
}
previous = {
"left": [410, 420, 60, 70, 80, 90],
"right": [430, 440, 100, 110, 120, 130],
}
selected = {
side: select_hand_target(
sources[side],
previous[side],
point,
active=True,
freeze=False,
)
for side in ("left", "right")
}
self.assertEqual(selected, {"left": point, "right": point})
class GuardedMomentaryGestureTest(unittest.TestCase):
def setUp(self) -> None:
self.gesture = GuardedMomentaryGesture(release_seconds=0.5)
self.gesture.new_session()
def test_held_at_session_start_cannot_activate(self) -> None:
self.assertFalse(
self.gesture.update(
0.0, armed=True, input_healthy=True, pressed=True
)
)
self.assertFalse(
self.gesture.update(
10.0, armed=True, input_healthy=True, pressed=True
)
)
self.assertFalse(self.gesture.active)
self.assertTrue(self.gesture.freeze_right_hand)
def test_press_is_immediate_after_stable_release_and_release_restores(self) -> None:
self.gesture.update(0.0, armed=True, input_healthy=True, pressed=False)
self.gesture.update(0.5, armed=True, input_healthy=True, pressed=False)
self.assertFalse(self.gesture.require_release)
self.assertTrue(
self.gesture.update(
0.51, armed=True, input_healthy=True, pressed=True
)
)
self.assertTrue(self.gesture.active)
self.assertEqual(self.gesture.activation_count, 1)
self.assertTrue(
self.gesture.update(
0.52, armed=True, input_healthy=True, pressed=False
)
)
self.assertFalse(self.gesture.active)
self.assertFalse(self.gesture.freeze_right_hand)
def test_malformed_input_deactivates_and_requires_release(self) -> None:
self.gesture.update(0.0, armed=True, input_healthy=True, pressed=False)
self.gesture.update(0.5, armed=True, input_healthy=True, pressed=False)
self.gesture.update(0.6, armed=True, input_healthy=True, pressed=True)
self.assertTrue(self.gesture.active)
self.assertTrue(
self.gesture.update(
0.7, armed=True, input_healthy=True, pressed=None
)
)
self.assertFalse(self.gesture.active)
self.assertTrue(self.gesture.require_release)
self.assertTrue(self.gesture.freeze_right_hand)
if __name__ == "__main__":
unittest.main()