diff --git a/CLAUDE.md b/CLAUDE.md index dd13468..ba662cf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -27,6 +27,8 @@ You are a seasoned embedded Rust engineer with deep expertise in: - Efficient binary protocols (fixed-size packets, checksums) - Avoid over-engineering: solve the problem at hand, not hypothetical future problems - Keep abstractions minimal until they prove necessary +- **Never use `#[allow(dead_code)]`** - dead code must be removed or used, not silenced +- **Never duplicate constants** - use a single `const` and reference it everywhere. Add a unit test to verify defaults use the constant. **Testing Philosophy:** - Write meaningful tests that catch real bugs, not boilerplate @@ -302,10 +304,10 @@ After 5 consecutive stationary detections: | Preset | Accel | Brake | Lateral | Yaw | Min Speed | Use Case | |--------|-------|-------|---------|-----|-----------|----------| -| Track | 0.30g | 0.50g | 0.50g | 0.15 rad/s | 3.0 m/s | Racing, autocross | -| Canyon | 0.20g | 0.35g | 0.30g | 0.10 rad/s | 2.5 m/s | Spirited twisty roads | -| City (default) | 0.10g | 0.18g | 0.12g | 0.05 rad/s | 2.0 m/s | Daily driving | -| Highway | 0.08g | 0.15g | 0.10g | 0.04 rad/s | 4.0 m/s | Cruise, subtle inputs | +| Track | 0.35g | 0.35g | 0.50g | 0.15 rad/s | 4.0 m/s | Racing, autocross | +| Canyon | 0.22g | 0.22g | 0.28g | 0.10 rad/s | 3.0 m/s | Spirited twisty roads | +| City (default) | 0.10g | 0.15g | 0.10g | 0.04 rad/s | 2.0 m/s | Daily driving | +| Highway | 0.12g | 0.15g | 0.12g | 0.04 rad/s | 5.0 m/s | Cruise, subtle inputs | | Custom | User-defined | | | | | Fine-tuning via sliders | Exit thresholds are 50% of entry values for hysteresis (prevents oscillation). @@ -531,29 +533,198 @@ Result: acceleration due to motion only (no gravity component). ### mode.rs - Driving Mode Classifier -**NEW: Independent Component Detection** (replaces exclusive state machine) +**Hybrid Mode Detection** with GPS/IMU blending: +- Longitudinal acceleration (ACCEL/BRAKE) uses **blended GPS + IMU** +- Lateral acceleration (CORNER) uses **filtered IMU** (GPS can't sense lateral) +- GPS-derived acceleration computed from velocity changes at 25Hz +- Blending ratio adapts based on GPS rate and freshness + ``` -Each mode component detected independently with hysteresis: +Independent hysteresis detection: -ACCEL: entry at 0.10g → exit at 0.05g -BRAKE: entry at -0.18g → exit at -0.09g (mutually exclusive with ACCEL) -CORNER: entry at 0.12g lat + 0.05rad/s yaw → exit at 0.06g lat or low yaw +ACCEL: entry at 0.10g → exit at 0.05g (from blended lon accel) +BRAKE: entry at -0.15g → exit at -0.08g (from blended lon accel) +CORNER: entry at 0.10g lat + 0.04rad/s yaw → exit at 0.05g lat -Combined states possible: +Combined states: - ACCEL + CORNER = corner exit (trail throttle) - BRAKE + CORNER = corner entry (trail braking) ``` -**Hysteresis:** Entry threshold > exit threshold prevents oscillation. +**Benefits of Hybrid Detection:** +- Mounting angle independent (GPS measures actual vehicle acceleration) +- No drift (GPS doesn't drift like IMU) +- Graceful fallback when GPS degrades **All modes require:** speed > min_speed (2.0 m/s / 7.2 km/h) -**Speed Display:** -Uses **raw GPS speed** directly (system.rs:295-297) for responsive updates. -- No longer uses EKF-filtered velocity (which was laggy) -- Server sends raw GPS speed (5 Hz updates) -- Dashboard applies light EMA smoothing (alpha=0.4) for car speedometer-like display -- Mode detection still uses EKF velocity for stability +### filter.rs - Biquad Low-Pass Filter + +2nd-order Butterworth IIR filter for vibration removal from IMU longitudinal acceleration. + +**Problem:** +Engine vibration (30-100 Hz) causes 0.08-0.12g noise on longitudinal G readings when +the vehicle is running but stationary. This makes mode detection unreliable. + +**Solution (based on ArduPilot research):** +- ArduPilot uses **20Hz** for INS_ACCEL_FILTER default +- Sharp braking events can have frequency components up to 5-10Hz +- Engine/road vibration is 30-100Hz → separable from driving dynamics (0-10Hz) + +**Configuration:** +- Sample rate: **200 Hz (IMU rate)** - CRITICAL: must match actual process_imu() call rate! +- Cutoff: **15 Hz** (preserves driving dynamics 0-10Hz, removes vibration 30+Hz) +- Q = 0.707 (Butterworth - maximally flat passband, no overshoot) + +**Frequency Response:** +``` +0-10 Hz: ~100% pass (driving dynamics preserved) +15 Hz: -3 dB (cutoff point) +30 Hz: -12 dB (engine vibration attenuated) +60+ Hz: -24+ dB (engine vibration removed) +``` + +**Implementation:** +```rust +// Direct Form I biquad +y[n] = b0*x[n] + b1*x[n-1] + b2*x[n-2] - a1*y[n-1] - a2*y[n-2] +``` + +### fusion.rs - Sensor Fusion Module + +Handles GPS-corrected orientation, GPS/IMU blending, and continuous calibration. +Uses an **ArduPilot-style approach**: GPS velocity provides ground truth to correct AHRS +orientation errors, enabling accurate 200 Hz IMU data for mode detection. + +**The Core Problem:** +The WT901 AHRS cannot distinguish linear acceleration from tilt. During forward +acceleration, it reports false pitch (thinks device is tilting backward). This corrupts +gravity removal and produces wrong earth-frame acceleration. Without correction, +measured correlation with ground truth was only 0.32 (garbage). + +**Solution: GPS-Corrected Orientation** +Compare IMU-predicted acceleration (using AHRS angles) with GPS-derived acceleration +(ground truth). The difference reveals orientation error: +``` +pitch_error ≈ (ax_imu - ax_gps) / G +``` + +**Components:** + +1. **OrientationCorrector** - Learns pitch/roll corrections from GPS velocity (NEW) + - Compares IMU-derived acceleration with GPS ground truth + - Learns pitch correction during acceleration (forward/backward) + - Learns roll correction during cornering (left/right) + - Applies corrections BEFORE gravity removal + - Confidence increases with learning samples (0-100%) + - Max correction capped at ±15° for safety + - Enables accurate 200 Hz IMU instead of being limited to GPS rate + +2. **GpsAcceleration** - Computes longitudinal acceleration from GPS velocity + - `accel = (speed_new - speed_old) / dt` + - At 25Hz GPS: dt = 40ms, clean acceleration signal + - Tracks staleness for fallback to IMU + - Provides ground truth for OrientationCorrector learning + +3. **TiltEstimator** - Learns residual mounting offset when stopped + - After 3 seconds stationary, averages earth-frame acceleration + - Handles minor offsets not captured by OrientationCorrector + - Relearns at every stop (adapts to device repositioning) + +4. **YawRateCalibrator** - Learns gyro bias while driving straight + - When GPS heading is stable (±2° for 3+ seconds), assumes true yaw rate ≈ 0 + - Any measured yaw rate during this time is gyro bias + - Applies correction to prevent lateral drift in centripetal calculation + - Essential for highway driving where ZUPT rarely triggers + +5. **SensorFusion** - Main processor + - Takes raw body-frame IMU + AHRS angles (NEW API) + - Applies orientation correction (learned from GPS comparison) + - Removes gravity using corrected orientation + - Applies residual tilt correction (learned when stopped) + - **10 Hz Butterworth low-pass filter** removes engine vibration + - Blends corrected IMU with GPS based on correction confidence + - Computes centripetal lateral (speed × calibrated_yaw_rate) + +**GPS/IMU Blending Ratios (based on OrientationCorrector confidence):** +``` +Confidence > 80%: 30% GPS / 70% corrected IMU (trust corrected IMU) +Confidence 30-80%: 50% GPS / 50% corrected IMU (moderate confidence, interpolated) +Confidence < 30%: 80% GPS / 20% corrected IMU (not confident yet, interpolated) +GPS stale (>200ms): 0% GPS / 100% corrected IMU (fallback) +``` +Note: GPS blending IS still necessary even after OrientationCorrector learns because: +1. Cold start needs GPS until correction converges (first 1-2 minutes) +2. Provides continuous sanity check / measurement diversity +3. Graceful degradation if correction diverges + +**process_imu() API (NEW):** +```rust +pub fn process_imu( + &mut self, + ax_raw: f32, // Raw body-frame X accel (m/s²) + ay_raw: f32, // Raw body-frame Y accel (m/s²) + az_raw: f32, // Raw body-frame Z accel (gravity on Z) + ahrs_roll_deg: f32, // AHRS roll angle (degrees) + ahrs_pitch_deg: f32, // AHRS pitch angle (degrees) + yaw: f32, // Vehicle yaw (radians, from EKF) + speed: f32, // Speed (m/s) + yaw_rate: f32, // Yaw rate (rad/s) + dt: f32, // Time step (seconds) + is_stationary: bool, // Whether vehicle is stopped +) -> (f32, f32) // Returns (lon_blended, lat_centripetal) +``` + +**Data Flow:** +``` +LONGITUDINAL (GPS-Corrected Orientation): +GPS (25Hz) → GpsAcceleration → lon_accel_gps ──────────────────────────┐ + │ │ + └──► OrientationCorrector learns │ + ↓ │ +IMU raw (200Hz) → OrientationCorrector.correct(pitch, roll) │ + ↓ │ + Corrected orientation │ + ↓ │ + remove_gravity → body_to_earth → Tilt → Biquad(10Hz)│ + ↓ │ + lon_imu_corrected ─────────────► Blend ◄────────────┘ + ↓ (based on confidence) + lon_blended + +LATERAL (centripetal): +GPS heading → YawRateCalibrator → learned_bias + ↓ +Gyro yaw_rate → (yaw_rate - bias) → calibrated_yaw_rate + ↓ +GPS speed × calibrated_yaw_rate → lat_centripetal + +OUTPUT: +(lon_blended, lat_centripetal) → mode.rs (classification) + → Dashboard (G-meter) +``` + +**Why This Works (ArduPilot Insight):** +- During acceleration, pitch error causes ax_earth to be wrong +- GPS velocity gives us true horizontal acceleration (dv/dt = ground truth) +- The innovation (IMU - GPS difference) is directly proportional to pitch error +- Learning is continuous while driving, adapts to any mount angle +- Once learned, enables accurate 200 Hz IMU instead of 25 Hz GPS rate + +**Diagnostics (NEW):** +- `pitch_correction_deg`: Learned pitch correction (degrees) +- `roll_correction_deg`: Learned roll correction (degrees) +- `pitch_confidence`: Correction confidence (0-100%) +- `roll_confidence`: Correction confidence (0-100%) + +**Why Centripetal Lateral?** +- Traditional IMU lateral (accelerometer) suffers from mount angle sensitivity and can "stick" +- Centripetal: `a_lat = v × ω` is physics-based, instant, and mount-independent +- Uses calibrated yaw rate to prevent drift during highway driving +- Pro telemetry systems use this approach + +**Dashboard Display:** The G-meter shows the same blended longitudinal and centripetal lateral +values that mode classification uses. What you see is what the algorithm sees. ### websocket_server.rs - HTTP Dashboard Server @@ -645,6 +816,22 @@ Displayed as updates per minute (rolling average), not cumulative count. Typical - ZUPT: 0-60/min (depends on stops) - Free heap: ~60KB (stable during operation) +**Color-Coded Thresholds:** +The diagnostics page displays metrics with color coding for at-a-glance health status: + +| Metric | Green (OK) | Yellow (Warning) | Red (Error) | +|--------|------------|------------------|-------------| +| Loop Rate | > 500 Hz | > 200 Hz | < 200 Hz | +| Position σ | < 5 m | < 10 m | > 10 m | +| Velocity σ | < 0.5 m/s | < 1.0 m/s | > 1.0 m/s | +| Yaw σ | < 5° | < 10° | > 10° | +| Bias X/Y | < 0.3 m/s² | < 0.5 m/s² | > 0.5 m/s² | +| Heap | > 40 KB | > 20 KB | < 20 KB | +| Yaw Bias | < 10 mrad/s | < 50 mrad/s | > 50 mrad/s | +| Tilt X/Y | < 0.3 m/s² | < 0.5 m/s² | > 0.5 m/s² | +| Pitch/Roll Corr | < 10° | - | > 10° | +| Orient Conf | > 50% | - | < 50% | + ### udp_stream.rs - UDP Client (Station Mode) **Features:** diff --git a/README.md b/README.md index 62d68f2..235dfc2 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,7 @@ [](https://www.rust-lang.org/) [](https://www.espressif.com/) -**Real-time vehicle dynamics tracking using GPS+IMU sensor fusion.** Professional-grade algorithms on $50 hardware. - ---- - -## What Is This? - -A complete ESP32-C3 firmware that fuses GPS and IMU data to track your vehicle's position, velocity, and acceleration in real-time. Built for track day logging, vehicle dynamics research, and DIY automotive projects. +A complete ESP32-C3 firmware that fuses GPS and IMU data to track your vehicle's position, velocity, and acceleration in real-time. Built for track day logging, vehicle dynamics research, and DIY automotive projects. Professional-grade algorithms on $50 hardware. **What it does:** - Tracks position (±1m accuracy between GPS updates) @@ -20,14 +14,7 @@ A complete ESP32-C3 firmware that fuses GPS and IMU data to track your vehicle's - Streams telemetry at 20-30Hz over WiFi - **Built-in mobile dashboard** - view live data on your phone -**How it works:** -- 7-state Extended Kalman Filter fuses GPS (5Hz) and IMU (50Hz) -- Gyro integrates yaw between GPS updates to maintain heading -- Body-frame accelerations transformed to earth frame using current orientation -- Zero-velocity updates eliminate IMU drift when stationary -- Constant Turn Rate and Acceleration (CTRA) model for cornering - -**Why it's useful:** +**Why build this instead of buying?** - Track day data logging without $1000+ commercial systems - Vehicle dynamics research and education - DIY EV/kit car development @@ -36,6 +23,41 @@ A complete ESP32-C3 firmware that fuses GPS and IMU data to track your vehicle's --- +## Table of Contents + +- [Hardware Requirements](#hardware-requirements) +- [Quick Start](#quick-start) + - [Option A: Web Flasher](#option-a-web-flasher-easiest) + - [Option B: Build from Source](#option-b-build-from-source) +- [Building From Source](#building-from-source) + - [Prerequisites](#prerequisites) + - [Build Commands](#build-commands) + - [Common Build Issues](#common-build-issues) +- [System Architecture](#system-architecture) + - [Data Flow](#data-flow) + - [File Structure](#file-structure) + - [Key Algorithms](#key-algorithms) +- [Telemetry Format](#telemetry-format) +- [Configuration](#configuration) + - [WiFi and Network](#wifi-and-network) + - [EKF Tuning](#ekf-tuning) + - [EKF Health Metrics](#ekf-health-metrics) + - [Mode Detection Thresholds](#mode-detection-thresholds) +- [Performance](#performance) +- [LED Status Codes](#led-status-codes) +- [Calibration](#calibration) +- [Mobile Dashboard](#mobile-dashboard) + - [Dashboard Features](#dashboard-features) + - [Diagnostics Page](#diagnostics-page) + - [WiFi Modes](#wifi-modes) + - [Using Python Tools](#using-python-tools-station-mode-only) +- [How It Works](#how-it-works) +- [Future Enhancements](#future-enhancements) +- [Contributing](#contributing) +- [License](#license) + +--- + ## Hardware Requirements ### Core Components ($50-100 total) @@ -307,6 +329,12 @@ cargo fmt # Lint cargo clippy -- -D warnings +# Run unit tests (on host, no device needed) +cargo test -p sensor-fusion -p wt901 -p ublox-gps + +# Run driving simulation (demonstrates GPS-corrected orientation) +cargo run -p sensor-fusion --example drive_sim + # Clean build (if things go wrong) cargo clean ``` @@ -434,28 +462,38 @@ GPS (5-25Hz) IMU (200Hz) ``` blackbox/ +├── framework/ # sensor-fusion library crate (host-testable) +│ └── src/ +│ ├── lib.rs # Crate entry point +│ ├── ekf.rs # Extended Kalman Filter +│ ├── transforms.rs # Body↔Earth↔Vehicle coordinate math +│ ├── mode.rs # Driving mode classifier +│ ├── fusion.rs # GPS/IMU blending, tilt correction +│ ├── filter.rs # Biquad filter (unused, for reference) +│ └── velocity.rs # Velocity source selection +├── drivers/ +│ ├── wt901/ # WT901 IMU driver crate +│ └── ublox-gps/ # u-blox GPS driver crate ├── sensors/ -│ └── blackbox/ +│ └── blackbox/ # ESP32 firmware (embedded binary) │ ├── .cargo/ -│ │ └── config.toml # ESP32-C3 build configuration +│ │ └── config.toml # ESP32-C3 build configuration │ ├── src/ -│ │ ├── main.rs # Main loop and setup -│ │ ├── config.rs # Build-time configuration (GPS model, WiFi mode) -│ │ ├── system.rs # Sensor/estimator/publisher managers -│ │ ├── imu.rs # WT901 UART parser (auto-detect baud) -│ │ ├── gps.rs # NMEA/UBX parser with warmup (NEO-6M/M9N) -│ │ ├── diagnostics.rs # System health monitoring -│ │ ├── transforms.rs # Body↔Earth coordinate math -│ │ ├── mode.rs # Driving mode classifier -│ │ ├── binary_telemetry.rs # 67-byte packet format -│ │ ├── websocket_server.rs # Mobile dashboard & HTTP server -│ │ ├── udp_stream.rs # High-speed UDP client -│ │ ├── mqtt.rs # MQTT client for status -│ │ ├── wifi.rs # WiFi connection manager -│ │ └── rgb_led.rs # WS2812 status LED -│ ├── Cargo.toml # Rust dependencies -│ ├── sdkconfig.defaults # ESP-IDF configuration -│ └── build.rs # Build script +│ │ ├── main.rs # Main loop and setup +│ │ ├── config.rs # Build-time configuration +│ │ ├── system.rs # Sensor/estimator/publisher managers +│ │ ├── imu.rs # WT901 UART parser (auto-detect baud) +│ │ ├── gps.rs # NMEA/UBX parser with warmup +│ │ ├── diagnostics.rs # System health monitoring +│ │ ├── binary_telemetry.rs # 67-byte packet format +│ │ ├── websocket_server.rs # Mobile dashboard & HTTP server +│ │ ├── udp_stream.rs # High-speed UDP client +│ │ ├── mqtt.rs # MQTT client for status +│ │ ├── wifi.rs # WiFi connection manager +│ │ └── rgb_led.rs # WS2812 status LED +│ ├── Cargo.toml # Rust dependencies +│ ├── sdkconfig.defaults # ESP-IDF configuration +│ └── build.rs # Build script ├── tools/ │ └── python/ │ ├── configure_wt901.py # IMU configuration tool (200Hz setup) @@ -883,6 +921,23 @@ python3 probe_wt901.py /dev/ttyUSB0 ``` Useful for verifying IMU is working before flashing, or determining what baud rate it's configured to. +**Analyze Telemetry CSV (works with both modes):** +```bash +# Export CSV from dashboard, then analyze on your computer +python3 analyze_telemetry.py recorded_session.csv +``` +Analyzes a CSV export from the dashboard and provides: +- **Timing analysis**: Sample rate, duration +- **Speed distribution**: Time in speed bands +- **Acceleration analysis**: lon_g range, mean, bias detection +- **Mode distribution**: Time spent in IDLE/ACCEL/BRAKE/CORNER +- **Ground truth comparison**: Compares lon_g with GPS-derived acceleration +- **Mode detection accuracy**: Precision and recall for ACCEL/BRAKE modes +- **Correlation**: How well lon_g matches actual acceleration (want >0.7) +- **Diagnostic summary**: Identifies potential issues like bias or high false positive rates + +This is useful for validating that the orientation correction is working properly after a test drive. + ### Choosing a Mode | Feature | Access Point | Station | @@ -896,6 +951,45 @@ Useful for verifying IMU is working before flashing, or determining what baud ra --- +## How It Works + +The system uses sensor fusion to combine fast but drifty IMU measurements with slow but accurate GPS updates. Here's what happens under the hood: + +**7-State Extended Kalman Filter** +- Tracks: position (x, y), heading (yaw), velocity (vx, vy), and accelerometer biases (bax, bay) +- Predicts state 200 times per second using IMU accelerations +- Corrects drift 5-25 times per second using GPS position and velocity +- Learns accelerometer biases during stops (Zero-Velocity Update / ZUPT) + +**Coordinate Transformations** +- Raw IMU measures in "body frame" (sensor orientation) +- Gravity is removed using roll/pitch angles +- Accelerations rotated to "earth frame" (horizontal plane) +- Then rotated to "vehicle frame" (car forward/left) for mode detection + +**GPS-Corrected Orientation (ArduPilot-style)** +- Problem: AHRS can't distinguish tilt from acceleration (reports false pitch when accelerating) +- Solution: Compare IMU-derived acceleration with GPS velocity ground truth +- Learn and correct pitch/roll errors over time +- Enables accurate 200Hz acceleration despite AHRS limitations + +**Tilt Correction** +- When stopped, the device learns its mounting offset automatically +- Works regardless of mounting angle (within reason) +- Re-learns if you move the device + +**Motion Models** +- CTRA (Constant Turn Rate and Acceleration): Used when speed >2 m/s and turning +- CA (Constant Acceleration): Used when going straight or slow + +**Why this matters for track days:** +- High-rate IMU gives responsive g-force readings (200 Hz) +- GPS prevents drift over time +- Mode detection works reliably regardless of mounting angle +- ZUPT eliminates accumulated errors at every stop + +--- + ## Future Enhancements - [ ] SD card logging for offline operation diff --git a/docs/index.html b/docs/index.html index 9270172..88a484d 100644 --- a/docs/index.html +++ b/docs/index.html @@ -4,7 +4,120 @@
7-state EKF fuses 200Hz IMU with 5Hz GPS for drift-free position estimates.
+7-state Extended Kalman Filter combines fast IMU (200Hz) with accurate GPS. Orientation correction automatically learns mounting angle errors.
Zero-velocity updates prevent drift. Bias continuously estimated when stationary.
+When stopped, the system knows velocity is exactly zero. This prevents position drift that would otherwise accumulate over time.
+Device can be mounted at any angle (within 15°). GPS provides ground truth to automatically learn and correct orientation errors.
Understanding the 7-state Extended Kalman Filter and sensor fusion
++ The Extended Kalman Filter (EKF) tracks 7 values that describe your vehicle's motion. These are continuously estimated and refined as new sensor data arrives: +
+