# Hello SW Activity Recognition Example Software-based activity recognition demo using LSM6DSOX accelerometer (and optionally gyroscope) data with feature extraction and threshold-based decision tree running on the ESP32 CPU. **Purpose**: Direct comparison with the MLC hardware version (`hello_mlc_activity`) for recognition sensitivity and system resource utilisation analysis. ## USE_GYRO Toggle A compile-time `#define USE_GYRO` at the top of `main.cpp` controls the sensor mode: | Mode | `USE_GYRO` | Sensors | Features | UI Title | Notes | |------|-----------|---------|----------|----------|-------| | Accel only | `0` | Accel 26Hz ±4g | 12 (accel) | "SW Accel" | Fair MLC comparison (MLC uses accel only) | | Accel + Gyro | `1` | Accel + Gyro 26Hz | 15 (accel + gyro) | "SW Accel+Gyro" | Better Biking/Driving separation | Set `USE_GYRO=0` for an apples-to-apples comparison with MLC. Set `USE_GYRO=1` for improved Biking vs Driving distinction at the cost of ~0.55 mA extra gyro power. ## Activities Detected | Code | Activity | Color | Icon | |------|-----------|-------------|------| | 0 | Stationary | Blue-grey | ⏹ | | 1 | Walking | Green | ▶ | | 4 | Jogging | Orange | ⚡ | | 8 | Biking | Blue | ↻ | | 12 | Driving | Purple | 📍 | ## UI Features (dark theme, adapts to the screen size) The layout is computed from the display size at start-up (the log shows `Screen WxH, canvas WxH`): | Screen | Chart canvas | |--------|--------------| | 240×240 | 220×60 | | 320×320 | 300×140 | The canvas spans the full width (10 px margins) and all the height between the duration timer and the legend; the history bars and the accelerometer waveform stretch with it, and the legend columns are spread over the screen width. Same elements as the MLC version: - Large activity icon + label (center) - Stability timer (duration of current activity) - Scrolling activity history timeline - Raw accelerometer waveform mini-graph - Legend row with color-coded activity labels - **Resource metrics** in status line: classification latency (ms), free heap (KB), accel magnitude ## Sensor Configuration (matches MLC UCF) | Parameter | Value | USE_GYRO=0 | USE_GYRO=1 | |---------------|---------|------------|------------| | Accel ODR | 26 Hz | ✓ | ✓ | | Accel FS | ±4g | ✓ | ✓ | | Gyro ODR | 26 Hz | — | ✓ | | Gyro FS | ±500 dps| — | ✓ | | Window size | 75 | ✓ | ✓ | | Window stride | 37 | ✓ | ✓ | | Sample struct | 12 bytes| ✓ (3 floats) | — (6 floats) | | Sample struct | 24 bytes| — | ✓ (6 floats) | ## Algorithm ### Feature Extraction **Accelerometer per-axis (×3 axes = 12 features)** — matches MLC features: - **Mean**: arithmetic mean of samples - **Variance**: sample variance around the mean - **Peak-to-peak**: max − min dynamic range - **Zero-crossings**: count of mean-crossings per second **Gyroscope (3 features, USE_GYRO=1 only)** — additional to MLC: - **Magnitude mean**: mean of |ω| = √(ωx² + ωy² + ωz²) - **Magnitude variance**: variance of |ω| - **Energy**: mean of (ωx² + ωy² + ωz²) ### Decision Tree Threshold-based classification using extracted features: 1. **Stationary**: avg accel variance < STATIONARY_VAR 2. **Jogging**: avg ZCR > JOG_ZCR AND avg P2P > JOG_P2P 3. **Walking**: avg variance > MOTION_VAR_MIN AND avg ZCR > WALK_ZCR AND avg P2P > WALK_P2P 4. **Driving**: - `USE_GYRO=1`: gyro energy < DRIVE_GYRO_ENERGY AND avg variance < DRIVE_VAR_MAX - `USE_GYRO=0`: avg variance < DRIVE_VAR_MAX AND avg ZCR < WALK_ZCR AND avg P2P < WALK_P2P 5. **Biking**: remaining sustained motion above MOTION_VAR_MIN 6. **Stationary** (fallback): low-level motion not meeting any criteria **Temporal smoothing**: majority vote over 3 consecutive windows (~4 seconds) before updating displayed activity. ### Tuning Thresholds All thresholds are `constexpr` at the top of `main.cpp`: ```cpp THRESH_STATIONARY_VAR = 0.01f // g² THRESH_JOG_ZCR = 4.0f // crossings/sec THRESH_WALK_ZCR = 1.2f // crossings/sec THRESH_WALK_P2P = 0.2f // g THRESH_JOG_P2P = 0.8f // g THRESH_MOTION_VAR_MIN = 0.02f // g² THRESH_DRIVE_GYRO_ENERGY = 50.0f // (dps)² — USE_GYRO=1 only THRESH_DRIVE_VAR_MAX = 0.08f // g² ``` Adjust these based on your mounting orientation and use case. ## Resource Metrics Displayed on the UI status line and console for comparison with MLC: | Metric | SW Version | MLC Version | |--------------------|---------------------|-------------| | CPU overhead | Sampling task + classify | ~0% (on-sensor) | | Classification latency | Measured per window | ~0 (hardware) | | Heap usage | Sample buffers + task stack | UCF loading only | ## Prerequisites - Enable **IMU** in IMU Configuration - Enable **LVGL** display - Enable fonts: `LV_FONT_MONTSERRAT_10`, `14`, `18`, `24` - MLC is **not required** (key difference from `hello_mlc_activity`) ## Build & Flash ```bash source ~/esp/v5.5.1/esp-idf/export.sh 2>/dev/null idf.py menuconfig # Navigate: CUBE32 Application → Hello SW Activity Recognition Example idf.py build && idf.py flash monitor ``` ## Comparison Procedure 1. Flash `hello_mlc_activity`, record console output for 2+ minutes with known activities 2. Flash `hello_activity` (this example), repeat same activity sequence 3. Compare: - **Sensitivity**: which detects transitions faster/more accurately? - **CPU**: SW version shows latency; MLC is zero CPU - **Heap**: compare `esp_get_free_heap_size()` between versions - **Responsiveness**: SW classifies every ~1.4s (50% overlap), MLC every ~2.88s ## Console Output Format ``` Activity: Walking |mag|=1.12g Duration: 8s Latency: 11842us Heap: 184320 Activity: Jogging |mag|=1.85g Duration: 3s Latency: 12105us Heap: 184320 ```
To create a project from this example, run:
idf.py create-project-from-example "cube32esp/cube32_bsp=0.0.17:hello_activity"