@@ -449,6 +449,8 @@ Current stable version: 0.3.3 (as of latest CHANGELOG entry)
449449- Config/preferences: ` internal_filesystem/lib/mpos/config.py `
450450- Top menu/drawer: ` internal_filesystem/lib/mpos/ui/topmenu.py `
451451- Activity navigation: ` internal_filesystem/lib/mpos/activity_navigator.py `
452+ - Sensor management: ` internal_filesystem/lib/mpos/sensor_manager.py `
453+ - IMU drivers: ` internal_filesystem/lib/mpos/hardware/drivers/qmi8658.py ` and ` wsen_isds.py `
452454
453455## Common Utilities and Helpers
454456
@@ -642,6 +644,7 @@ def defocus_handler(self, obj):
642644- ` mpos.sdcard.SDCardManager ` : SD card mounting and management
643645- ` mpos.clipboard ` : System clipboard access
644646- ` mpos.battery_voltage ` : Battery level reading (ESP32 only)
647+ - ` mpos.sensor_manager ` : Unified sensor access (accelerometer, gyroscope, temperature)
645648
646649## Audio System (AudioFlinger)
647650
@@ -849,6 +852,173 @@ class LEDAnimationActivity(Activity):
849852- ** Thread-safe** : No locking (single-threaded usage recommended)
850853- ** Desktop** : Functions return ` False ` (no-op) on desktop builds
851854
855+ ## Sensor System (SensorManager)
856+
857+ MicroPythonOS provides a unified sensor framework called ** SensorManager** (Android-inspired) that provides easy access to motion sensors (accelerometer, gyroscope) and temperature sensors across different hardware platforms.
858+
859+ ### Supported Sensors
860+
861+ ** IMU Sensors:**
862+ - ** QMI8658** (Waveshare ESP32-S3): Accelerometer, Gyroscope, Temperature
863+ - ** WSEN_ISDS** (Fri3d Camp 2024 Badge): Accelerometer, Gyroscope
864+
865+ ** Temperature Sensors:**
866+ - ** ESP32 MCU Temperature** : Internal SoC temperature sensor
867+ - ** IMU Chip Temperature** : QMI8658 chip temperature
868+
869+ ### Basic Usage
870+
871+ ** Check availability and read sensors** :
872+ ``` python
873+ import mpos.sensor_manager as SensorManager
874+
875+ # Check if sensors are available
876+ if SensorManager.is_available():
877+ # Get sensors
878+ accel = SensorManager.get_default_sensor(SensorManager.TYPE_ACCELEROMETER )
879+ gyro = SensorManager.get_default_sensor(SensorManager.TYPE_GYROSCOPE )
880+ temp = SensorManager.get_default_sensor(SensorManager.TYPE_SOC_TEMPERATURE )
881+
882+ # Read data (returns standard SI units)
883+ accel_data = SensorManager.read_sensor(accel) # Returns (x, y, z) in m/s²
884+ gyro_data = SensorManager.read_sensor(gyro) # Returns (x, y, z) in deg/s
885+ temperature = SensorManager.read_sensor(temp) # Returns °C
886+
887+ if accel_data:
888+ ax, ay, az = accel_data
889+ print (f " Acceleration: { ax:.2f } , { ay:.2f } , { az:.2f } m/s² " )
890+ ```
891+
892+ ### Sensor Types
893+
894+ ``` python
895+ # Motion sensors
896+ SensorManager.TYPE_ACCELEROMETER # m/s² (meters per second squared)
897+ SensorManager.TYPE_GYROSCOPE # deg/s (degrees per second)
898+
899+ # Temperature sensors
900+ SensorManager.TYPE_SOC_TEMPERATURE # °C (MCU internal temperature)
901+ SensorManager.TYPE_IMU_TEMPERATURE # °C (IMU chip temperature)
902+ ```
903+
904+ ### Tilt-Controlled Game Example
905+
906+ ``` python
907+ from mpos.app.activity import Activity
908+ import mpos.sensor_manager as SensorManager
909+ import mpos.ui
910+ import time
911+
912+ class TiltBallActivity (Activity ):
913+ def onCreate (self ):
914+ self .screen = lv.obj()
915+
916+ # Get accelerometer
917+ self .accel = SensorManager.get_default_sensor(SensorManager.TYPE_ACCELEROMETER )
918+
919+ # Create ball UI
920+ self .ball = lv.obj(self .screen)
921+ self .ball.set_size(20 , 20 )
922+ self .ball.set_style_radius(10 , 0 )
923+
924+ # Physics state
925+ self .ball_x = 160.0
926+ self .ball_y = 120.0
927+ self .ball_vx = 0.0
928+ self .ball_vy = 0.0
929+ self .last_time = time.ticks_ms()
930+
931+ self .setContentView(self .screen)
932+
933+ def onResume (self , screen ):
934+ self .last_time = time.ticks_ms()
935+ mpos.ui.task_handler.add_event_cb(self .update_physics, 1 )
936+
937+ def onPause (self , screen ):
938+ mpos.ui.task_handler.remove_event_cb(self .update_physics)
939+
940+ def update_physics (self , a , b ):
941+ current_time = time.ticks_ms()
942+ delta_time = time.ticks_diff(current_time, self .last_time) / 1000.0
943+ self .last_time = current_time
944+
945+ # Read accelerometer
946+ accel = SensorManager.read_sensor(self .accel)
947+ if accel:
948+ ax, ay, az = accel
949+
950+ # Apply acceleration to velocity
951+ self .ball_vx += (ax * 5.0 ) * delta_time
952+ self .ball_vy -= (ay * 5.0 ) * delta_time # Flip Y
953+
954+ # Update position
955+ self .ball_x += self .ball_vx
956+ self .ball_y += self .ball_vy
957+
958+ # Update ball position
959+ self .ball.set_pos(int (self .ball_x), int (self .ball_y))
960+ ```
961+
962+ ### Calibration
963+
964+ Calibration removes sensor drift and improves accuracy. The device must be ** stationary** during calibration.
965+
966+ ``` python
967+ # Calibrate accelerometer and gyroscope
968+ accel = SensorManager.get_default_sensor(SensorManager.TYPE_ACCELEROMETER )
969+ gyro = SensorManager.get_default_sensor(SensorManager.TYPE_GYROSCOPE )
970+
971+ # Calibrate (100 samples, device must be flat and still)
972+ accel_offsets = SensorManager.calibrate_sensor(accel, samples = 100 )
973+ gyro_offsets = SensorManager.calibrate_sensor(gyro, samples = 100 )
974+
975+ # Calibration is automatically saved to SharedPreferences
976+ # and loaded on next boot
977+ ```
978+
979+ ### Performance Recommendations
980+
981+ ** Polling rate recommendations:**
982+ - ** Games** : 20-30 Hz (responsive but not excessive)
983+ - ** UI feedback** : 10-15 Hz (smooth for tilt UI)
984+ - ** Background monitoring** : 1-5 Hz (screen rotation, pedometer)
985+
986+ ``` python
987+ # ❌ BAD: Poll every frame (60 Hz)
988+ def update_frame (self , a , b ):
989+ accel = SensorManager.read_sensor(self .accel) # Too frequent!
990+
991+ # ✅ GOOD: Poll every other frame (30 Hz)
992+ def update_frame (self , a , b ):
993+ self .frame_count += 1
994+ if self .frame_count % 2 == 0 :
995+ accel = SensorManager.read_sensor(self .accel)
996+ ```
997+
998+ ### Hardware Support Matrix
999+
1000+ | Platform | Accelerometer | Gyroscope | IMU Temp | MCU Temp |
1001+ | ----------| ---------------| -----------| ----------| ----------|
1002+ | Waveshare ESP32-S3 | ✅ QMI8658 | ✅ QMI8658 | ✅ QMI8658 | ✅ ESP32 |
1003+ | Fri3d 2024 Badge | ✅ WSEN_ISDS | ✅ WSEN_ISDS | ❌ | ✅ ESP32 |
1004+ | Desktop/Linux | ❌ | ❌ | ❌ | ❌ |
1005+
1006+ ### Implementation Details
1007+
1008+ - ** Location** : ` lib/mpos/sensor_manager.py `
1009+ - ** Pattern** : Module-level singleton (similar to ` battery_voltage.py ` )
1010+ - ** Units** : Standard SI (m/s² for acceleration, deg/s for gyroscope, °C for temperature)
1011+ - ** Calibration** : Persistent via SharedPreferences (` data/com.micropythonos.sensors/config.json ` )
1012+ - ** Thread-safe** : Uses locks for concurrent access
1013+ - ** Auto-detection** : Identifies IMU type via chip ID registers
1014+ - ** Desktop** : Functions return ` None ` (graceful fallback) on desktop builds
1015+
1016+ ### Driver Locations
1017+
1018+ - ** QMI8658** : ` lib/mpos/hardware/drivers/qmi8658.py `
1019+ - ** WSEN_ISDS** : ` lib/mpos/hardware/drivers/wsen_isds.py `
1020+ - ** Board init** : ` lib/mpos/board/waveshare_esp32_s3_touch_lcd_2.py ` and ` lib/mpos/board/fri3d_2024.py `
1021+
8521022## Animations and Game Loops
8531023
8541024MicroPythonOS supports frame-based animations and game loops using the TaskHandler event system. This pattern is used for games, particle effects, and smooth animations.
0 commit comments