Files
MicroPythonOS/tests/test_calibration_check_bug.py
Thomas Farstrike 30b3764710 Harmonize frameworks
All frameworks now follow the same singleton class pattern with class methods:

AudioFlinger (already had this pattern)
DownloadManager (refactored)
ConnectivityManager (refactored)
CameraManager (refactored)
SensorManager (refactored)
Pattern Structure:

class FrameworkName:
    _initialized = False
    _instance_data = {}

    @classmethod
    def init(cls, *args, **kwargs):
        """Initialize the framework"""
        cls._initialized = True
        # initialization logic

    @classmethod
    def is_available(cls):
        """Check if framework is available"""
        return cls._initialized

    @classmethod
    def method_name(cls, *args):
        """Framework methods as class methods"""
        # implementation

2. Standardized Imports in __init__.py
All frameworks are now imported consistently as classes:

from .content.package_manager import PackageManager
from .config import SharedPreferences
from .net.connectivity_manager import ConnectivityManager
from .net.wifi_service import WifiService
from .audio.audioflinger import AudioFlinger
from .net.download_manager import DownloadManager
from .task_manager import TaskManager
from .camera_manager import CameraManager
from .sensor_manager import SensorManager
3. Updated Board Initialization Files
Fixed imports in all board files to use the new class-based pattern:

linux.py
fri3d_2024.py
fri3d_2026.py
waveshare_esp32_s3_touch_lcd_2.py
4. Updated UI Components
Fixed topmenu.py to import SensorManager as a class instead of a module.

5. Benefits of This Harmonization
 Consistency: All frameworks follow the same pattern - no more mixing of module imports and class imports  Simplicity: Single, clear way to use frameworks - always as classes with class methods  Functionality: All frameworks work identically - init(), is_available(), and other methods are consistent  Maintainability: New developers see one pattern to follow across all frameworks  No Breaking Changes: Apps continue to work without modification (Quasi apps, Lightning Piggy, etc.)

6. Testing
All tests pass successfully, confirming:

Framework initialization works correctly
Board hardware detection functions properly
UI components render without errors
No regressions in existing functionality
The harmonization is complete and production-ready. All frameworks now provide a unified, predictable interface that's easy to understand and extend.
2026-01-23 15:31:47 +01:00

163 lines
5.5 KiB
Python

"""Test for calibration check bug after calibrating.
Reproduces issue where check_calibration_quality() returns None after calibration.
"""
import unittest
import sys
# Mock hardware before importing SensorManager
class MockI2C:
def __init__(self, bus_id, sda=None, scl=None):
self.bus_id = bus_id
self.sda = sda
self.scl = scl
self.memory = {}
def readfrom_mem(self, addr, reg, nbytes):
if addr not in self.memory:
raise OSError("I2C device not found")
if reg not in self.memory[addr]:
return bytes([0] * nbytes)
return bytes(self.memory[addr][reg])
def writeto_mem(self, addr, reg, data):
if addr not in self.memory:
self.memory[addr] = {}
self.memory[addr][reg] = list(data)
class MockQMI8658:
def __init__(self, i2c_bus, address=0x6B, accel_scale=0b10, gyro_scale=0b100):
self.i2c = i2c_bus
self.address = address
self.accel_scale = accel_scale
self.gyro_scale = gyro_scale
@property
def temperature(self):
return 25.5
@property
def acceleration(self):
return (0.0, 0.0, 1.0) # At rest, Z-axis = 1G
@property
def gyro(self):
return (0.0, 0.0, 0.0) # Stationary
# Mock constants
_QMI8685_PARTID = 0x05
_REG_PARTID = 0x00
_ACCELSCALE_RANGE_8G = 0b10
_GYROSCALE_RANGE_256DPS = 0b100
# Create mock modules
mock_machine = type('module', (), {
'I2C': MockI2C,
'Pin': type('Pin', (), {})
})()
mock_qmi8658 = type('module', (), {
'QMI8658': MockQMI8658,
'_QMI8685_PARTID': _QMI8685_PARTID,
'_REG_PARTID': _REG_PARTID,
'_ACCELSCALE_RANGE_8G': _ACCELSCALE_RANGE_8G,
'_GYROSCALE_RANGE_256DPS': _GYROSCALE_RANGE_256DPS
})()
def _mock_mcu_temperature(*args, **kwargs):
return 42.0
mock_esp32 = type('module', (), {
'mcu_temperature': _mock_mcu_temperature
})()
# Inject mocks
sys.modules['machine'] = mock_machine
sys.modules['mpos.hardware.drivers.qmi8658'] = mock_qmi8658
sys.modules['esp32'] = mock_esp32
try:
import _thread
except ImportError:
mock_thread = type('module', (), {
'allocate_lock': lambda: type('lock', (), {
'acquire': lambda self: None,
'release': lambda self: None
})()
})()
sys.modules['_thread'] = mock_thread
# Now import the module to test
from mpos import SensorManager
class TestCalibrationCheckBug(unittest.TestCase):
"""Test case for calibration check bug."""
def setUp(self):
"""Set up test fixtures before each test."""
# Reset SensorManager state
SensorManager._initialized = False
SensorManager._imu_driver = None
SensorManager._sensor_list = []
SensorManager._has_mcu_temperature = False
# Create mock I2C bus with QMI8658
self.i2c_bus = MockI2C(0, sda=48, scl=47)
self.i2c_bus.memory[0x6B] = {_REG_PARTID: [_QMI8685_PARTID]}
def test_check_quality_after_calibration(self):
"""Test that check_calibration_quality() works after calibration.
This reproduces the bug where check_calibration_quality() returns
None or shows "--" after performing calibration.
"""
# Initialize
SensorManager.init(self.i2c_bus, address=0x6B)
# Step 1: Check calibration quality BEFORE calibration (should work)
print("\n=== Step 1: Check quality BEFORE calibration ===")
quality_before = SensorManager.check_calibration_quality(samples=10)
self.assertIsNotNone(quality_before, "Quality check BEFORE calibration should return data")
self.assertIn('quality_score', quality_before)
print(f"Quality before: {quality_before['quality_rating']} ({quality_before['quality_score']:.2f})")
# Step 2: Calibrate sensors
print("\n=== Step 2: Calibrate sensors ===")
accel = SensorManager.get_default_sensor(SensorManager.TYPE_ACCELEROMETER)
gyro = SensorManager.get_default_sensor(SensorManager.TYPE_GYROSCOPE)
self.assertIsNotNone(accel, "Accelerometer should be available")
self.assertIsNotNone(gyro, "Gyroscope should be available")
accel_offsets = SensorManager.calibrate_sensor(accel, samples=10)
print(f"Accel offsets: {accel_offsets}")
self.assertIsNotNone(accel_offsets, "Accelerometer calibration should succeed")
gyro_offsets = SensorManager.calibrate_sensor(gyro, samples=10)
print(f"Gyro offsets: {gyro_offsets}")
self.assertIsNotNone(gyro_offsets, "Gyroscope calibration should succeed")
# Step 3: Check calibration quality AFTER calibration (BUG: returns None)
print("\n=== Step 3: Check quality AFTER calibration ===")
quality_after = SensorManager.check_calibration_quality(samples=10)
self.assertIsNotNone(quality_after, "Quality check AFTER calibration should return data (BUG: returns None)")
self.assertIn('quality_score', quality_after)
print(f"Quality after: {quality_after['quality_rating']} ({quality_after['quality_score']:.2f})")
# Verify sensor reads still work
print("\n=== Step 4: Verify sensor reads still work ===")
accel_data = SensorManager.read_sensor(accel)
self.assertIsNotNone(accel_data, "Accelerometer should still be readable")
print(f"Accel data: {accel_data}")
gyro_data = SensorManager.read_sensor(gyro)
self.assertIsNotNone(gyro_data, "Gyroscope should still be readable")
print(f"Gyro data: {gyro_data}")
if __name__ == '__main__':
unittest.main()