""" Tests for accelerometer-based gesture detection. """ import pytest import asyncio import json import math from pathlib import Path import sys # Test only the gesture types and math, not the full integration # to avoid dependencies on pyWebLayout class MockOrientationSensor: """Mock BMA400 accelerometer for testing""" def __init__(self): self.ax = 0.0 self.ay = 0.0 self.az = 9.8 # Standard gravity async def get_acceleration(self): """Return mock acceleration data""" return (self.ax, self.ay, self.az) def set_acceleration(self, x, y, z): """Set acceleration for testing""" self.ax = x self.ay = y self.az = z class MockHAL: """Mock HAL for testing""" def __init__(self): self.orientation = MockOrientationSensor() self.width = 800 self.height = 1200 @pytest.fixture def mock_hal(): """Create a mock HAL with accelerometer""" return MockHAL() @pytest.fixture def calibration_file(tmp_path): """Create a temporary calibration file""" config = { "up_vector": { "x": 0.0, "y": 9.8, "z": 0.0 }, "tilt_threshold": 0.3, # ~17 degrees "debounce_time": 0.5 } config_path = tmp_path / "test_accel_config.json" with open(config_path, 'w') as f: json.dump(config, f) return str(config_path) def test_load_calibration_success(mock_hal, calibration_file): """Test loading accelerometer calibration""" # Create a minimal HAL-like object class TestHAL: def __init__(self): self.width = 800 self.height = 1200 test_hal = TestHAL() # Manually call the load function result = load_accel_calibration(test_hal, calibration_file) assert result is True assert hasattr(test_hal, 'accel_up_vector') assert test_hal.accel_up_vector == (0.0, 9.8, 0.0) assert test_hal.accel_tilt_threshold == 0.3 assert test_hal.accel_debounce_time == 0.5 def load_accel_calibration(hal, config_path): """Helper function to load calibration (extracted from HardwareDisplayHAL)""" import logging logger = logging.getLogger(__name__) config_file = Path(config_path) if not config_file.exists(): logger.warning(f"Accelerometer calibration file not found: {config_path}") return False try: with open(config_file, 'r') as f: config = json.load(f) # Load up vector up = config.get("up_vector", {}) hal.accel_up_vector = (up.get("x", 0), up.get("y", 0), up.get("z", 0)) # Load thresholds hal.accel_tilt_threshold = config.get("tilt_threshold", 0.3) hal.accel_debounce_time = config.get("debounce_time", 0.5) # State tracking hal.accel_last_tilt_time = 0 return True except Exception as e: logger.error(f"Error loading accelerometer calibration: {e}") return False def test_tilt_detection_forward(): """Test forward tilt detection""" # Setup: device is upright (y = 9.8), then tilt forward (z increases) # Calibrated up vector: (0, 9.8, 0) # Current gravity: (0, 6, 6) - tilted ~45 degrees forward up_vector = (0.0, 9.8, 0.0) current_gravity = (0.0, 6.0, 6.0) # Normalize vectors ux, uy, uz = up_vector u_mag = math.sqrt(ux**2 + uy**2 + uz**2) ux, uy, uz = ux / u_mag, uy / u_mag, uz / u_mag gx, gy, gz = current_gravity g_mag = math.sqrt(gx**2 + gy**2 + gz**2) gx, gy, gz = gx / g_mag, gy / g_mag, gz / g_mag # Calculate tilt angle dot_up = gx * ux + gy * uy + gz * uz perp_x = gx - dot_up * ux perp_y = gy - dot_up * uy perp_z = gz - dot_up * uz perp_mag = math.sqrt(perp_x**2 + perp_y**2 + perp_z**2) tilt_angle = math.atan2(perp_mag, abs(dot_up)) # Should be approximately 45 degrees (0.785 radians) assert abs(tilt_angle - 0.785) < 0.1 # Direction: forward tilt should have positive perpendicular y component # Actually, when tilting forward, gravity vector rotates toward +z # The perpendicular component should reflect this def test_tilt_detection_backward(): """Test backward tilt detection""" # Setup: device is upright (y = 9.8), then tilt backward (z decreases, negative) # Calibrated up vector: (0, 9.8, 0) # Current gravity: (0, 6, -6) - tilted ~45 degrees backward up_vector = (0.0, 9.8, 0.0) current_gravity = (0.0, 6.0, -6.0) # Normalize vectors ux, uy, uz = up_vector u_mag = math.sqrt(ux**2 + uy**2 + uz**2) ux, uy, uz = ux / u_mag, uy / u_mag, uz / u_mag gx, gy, gz = current_gravity g_mag = math.sqrt(gx**2 + gy**2 + gz**2) gx, gy, gz = gx / g_mag, gy / g_mag, gz / g_mag # Calculate tilt angle dot_up = gx * ux + gy * uy + gz * uz perp_x = gx - dot_up * ux perp_y = gy - dot_up * uy perp_z = gz - dot_up * uz perp_mag = math.sqrt(perp_x**2 + perp_y**2 + perp_z**2) tilt_angle = math.atan2(perp_mag, abs(dot_up)) # Should be approximately 45 degrees (0.785 radians) assert abs(tilt_angle - 0.785) < 0.1 def test_no_tilt_when_upright(): """Test that no tilt is detected when device is upright""" # Setup: device is perfectly upright # Calibrated up vector: (0, 9.8, 0) # Current gravity: (0, 9.8, 0) - same as calibration up_vector = (0.0, 9.8, 0.0) current_gravity = (0.0, 9.8, 0.0) # Normalize vectors ux, uy, uz = up_vector u_mag = math.sqrt(ux**2 + uy**2 + uz**2) ux, uy, uz = ux / u_mag, uy / u_mag, uz / u_mag gx, gy, gz = current_gravity g_mag = math.sqrt(gx**2 + gy**2 + gz**2) gx, gy, gz = gx / g_mag, gy / g_mag, gz / g_mag # Calculate tilt angle dot_up = gx * ux + gy * uy + gz * uz perp_x = gx - dot_up * ux perp_y = gy - dot_up * uy perp_z = gz - dot_up * uz perp_mag = math.sqrt(perp_x**2 + perp_y**2 + perp_z**2) tilt_angle = math.atan2(perp_mag, abs(dot_up)) # Should be approximately 0 degrees assert tilt_angle < 0.01 def test_small_tilt_below_threshold(): """Test that small tilts below threshold are ignored""" # Setup: device is slightly tilted (10 degrees) # Calibrated up vector: (0, 9.8, 0) # Current gravity: small tilt angle_rad = math.radians(10) up_vector = (0.0, 9.8, 0.0) current_gravity = (0.0, 9.8 * math.cos(angle_rad), 9.8 * math.sin(angle_rad)) # Normalize vectors ux, uy, uz = up_vector u_mag = math.sqrt(ux**2 + uy**2 + uz**2) ux, uy, uz = ux / u_mag, uy / u_mag, uz / u_mag gx, gy, gz = current_gravity g_mag = math.sqrt(gx**2 + gy**2 + gz**2) gx, gy, gz = gx / g_mag, gy / g_mag, gz / g_mag # Calculate tilt angle dot_up = gx * ux + gy * uy + gz * uz perp_x = gx - dot_up * ux perp_y = gy - dot_up * uy perp_z = gz - dot_up * uz perp_mag = math.sqrt(perp_x**2 + perp_y**2 + perp_z**2) tilt_angle = math.atan2(perp_mag, abs(dot_up)) # Should be approximately 10 degrees (0.174 radians) assert abs(tilt_angle - 0.174) < 0.01 # Should be below default threshold of 0.3 rad (~17 degrees) assert tilt_angle < 0.3 def test_gesture_types_exist(): """Test that accelerometer gesture types are defined""" # Simple direct test - check that gesture strings are defined gestures = [ "tap", "long_press", "swipe_left", "swipe_right", "swipe_up", "swipe_down", "pinch_in", "pinch_out", "drag_start", "drag_move", "drag_end", "tilt_forward", # Our new gestures "tilt_backward" ] # Verify the new gesture strings are valid assert "tilt_forward" in gestures assert "tilt_backward" in gestures if __name__ == "__main__": # Run tests pytest.main([__file__, "-v"])