HW integratation
This commit is contained in:
@@ -0,0 +1,284 @@
|
||||
"""
|
||||
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"])
|
||||
Reference in New Issue
Block a user