#!/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()