285 lines
7.7 KiB
Python
285 lines
7.7 KiB
Python
"""
|
|
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"])
|