diff --git a/CHANGELOG.md b/CHANGELOG.md index d9aad53..9067344 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -### Added -- Comprehensive documentation (README, CONTRIBUTING, CODE_OF_CONDUCT, SECURITY) -- GitHub issue and PR templates -- CLAUDE.md architecture documentation +### Changed +- Renamed `neo6m` driver to `ublox-gps` to reflect support for multiple u-blox GPS modules (NEO-6M, NEO-M9N, etc.) +- Updated driver documentation with UBX protocol examples and dynamic platform models + +### Removed +- Obsolete documentation files (RELEASE.md, IMPLEMENTATION_SUMMARY.md, release-notes.md) ## [0.0.2] - 2025-01-10 @@ -45,7 +47,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Initial release of Blackbox - ESP32-C3 firmware for vehicle telemetry - WT901 IMU driver with UART parsing -- NEO-6M GPS driver with NMEA parsing +- u-blox GPS driver with NMEA parsing (supports NEO-6M, NEO-M9N, etc.) - 7-state Extended Kalman Filter for sensor fusion - Position, velocity, yaw, and accelerometer bias estimation - CTRA motion model for turning dynamics diff --git a/CLAUDE.md b/CLAUDE.md index ba94932..dd13468 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -36,7 +36,7 @@ You are a seasoned embedded Rust engineer with deep expertise in: - Avoid: trivial getters, obvious constructors, framework code - Hardware-dependent code is hard to test—isolate pure functions where possible - Example good tests: `remove_gravity()` with known angles, EKF prediction with synthetic data, NMEA parser with malformed input -- Run tests with: `cargo test -p sensor-fusion -p wt901 -p neo6m` +- Run tests with: `cargo test -p sensor-fusion -p wt901 -p ublox-gps` **Code Review:** After implementing a new feature or making significant changes, perform a code review before considering the work complete. Focus on: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 609f66d..5a8ddd4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -143,7 +143,7 @@ We especially welcome contributions in these areas: 1. **Sensor Drivers** (in `drivers/`) - Additional IMU driver crates (MPU6050, BMI088, LSM6DSO) - - GPS improvements to neo6m (GPGGA parsing, RTK, SBAS) + - GPS improvements to ublox-gps (RTK support, SBAS, additional NMEA sentences) - New driver crates (barometer, temperature, pressure sensors) - All drivers should be no-std compatible with zero dependencies @@ -212,7 +212,7 @@ cargo build # Build specific package cargo build -p blackbox cargo build -p wt901 -cargo build -p neo6m +cargo build -p ublox-gps # Build release binary for ESP32 cd sensors/blackbox diff --git a/Cargo.lock b/Cargo.lock index 0a9cc64..e1c3d1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,11 +98,11 @@ dependencies = [ "esp-idf-svc", "esp-idf-sys", "log", - "neo6m", "sensor-fusion", "serde", "serde_json", "smart-leds", + "ublox-gps", "ws2812-esp32-rmt-driver", "wt901", ] @@ -951,14 +951,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" -[[package]] -name = "neo6m" -version = "0.1.0" -dependencies = [ - "libm", - "log", -] - [[package]] name = "nix" version = "0.29.0" @@ -1453,6 +1445,14 @@ dependencies = [ "winnow", ] +[[package]] +name = "ublox-gps" +version = "0.1.0" +dependencies = [ + "libm", + "log", +] + [[package]] name = "uncased" version = "0.9.10" diff --git a/Cargo.toml b/Cargo.toml index 9b2e664..8bedbde 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ members = [ "framework", # motorsport-telemetry crate "drivers/wt901", # WT901 IMU driver - "drivers/neo6m", # NEO-6M GPS driver + "drivers/ublox-gps", # u-blox GPS driver (NEO-6M, NEO-M9N, etc.) "sensors/blackbox", # Active Wing ESP32 telemetry application ] resolver = "2" diff --git a/IMPLEMENTATION_SUMMARY.md b/IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index 5f004ff..0000000 --- a/IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -1,158 +0,0 @@ -# Implementation Summary - Blackbox Fixes & Improvements - -## Completed (Priority 1) - -✅ **1. Fixed Yaw Slider** - Step changed from 0.01 to 0.005 rad/s for finer control -✅ **2. Fixed Exit Ranges** - All exit thresholds now max at 0.50g (matching entry ranges) -✅ **3. Fixed Compilation Warnings** - Added cfg check to build.rs -✅ **4. Updated CLAUDE.md** - New modes, thresholds, speed display behavior -✅ **5. Updated README.md** - Corrected mode byte documentation - -## In Progress (Priority 2 & 3) - -### Unit Tests Needed - -```rust -// File: src/mode.rs - Add to tests module - -#[test] -fn test_hysteresis_prevents_oscillation() { - let mut classifier = ModeClassifier::new(); - - // Vehicle moving at 5 m/s - let vx = 5.0; - let vy = 0.0; - let yaw = 0.0; - let wz = 0.0; - let ay = 0.0; - - // Acceleration just above entry threshold (0.10g) - let ax1 = 0.11 * G; - classifier.update(ax1, ay, yaw, wz, vx, vy); - assert!(classifier.get_mode().has_accel(), "Should enter ACCEL at 0.11g"); - - // Drop to 0.06g (above 0.05g exit threshold) - let ax2 = 0.06 * G; - classifier.update(ax2, ay, yaw, wz, vx, vy); - assert!(classifier.get_mode().has_accel(), "Should stay ACCEL at 0.06g (above exit)"); - - // Drop to 0.04g (below 0.05g exit threshold) - let ax3 = 0.04 * G; - classifier.update(ax3, ay, yaw, wz, vx, vy); - assert!(classifier.get_mode().is_idle(), "Should exit to IDLE at 0.04g (below exit)"); -} - -#[test] -fn test_speed_threshold_prevents_false_modes() { - let mut classifier = ModeClassifier::new(); - - // High acceleration but speed below threshold - let ax = 0.30 * G; // 0.30g - let ay = 0.0; - let yaw = 0.0; - let wz = 0.0; - let vx = 1.5; // Below 2.0 m/s threshold - let vy = 0.0; - - classifier.update(ax, ay, yaw, wz, vx, vy); - assert!(classifier.get_mode().is_idle(), "Should stay IDLE when speed < min_speed"); - - // Same acceleration, speed above threshold - let vx2 = 2.5; // Above threshold - classifier.update(ax, ay, yaw, wz, vx2, vy); - assert!(classifier.get_mode().has_accel(), "Should detect ACCEL when speed > min_speed"); -} - -#[test] -fn test_corner_independent_persistence() { - let mut classifier = ModeClassifier::new(); - - // Start cornering - let ax = 0.0; - let ay = 0.15 * G; - let yaw = 0.0; - let wz = 0.08; - let vx = 8.0; - let vy = 0.0; - - classifier.update(ax, ay, yaw, wz, vx, vy); - assert!(classifier.get_mode().has_corner(), "Should detect corner"); - - // Add acceleration while still cornering - let ax2 = 0.15 * G; - classifier.update(ax2, ay, yaw, wz, vx, vy); - assert!(classifier.get_mode().has_accel(), "Should detect accel"); - assert!(classifier.get_mode().has_corner(), "Should still detect corner"); - assert_eq!(classifier.get_mode().as_u8(), 5, "Should be ACCEL+CORNER"); - - // Stop accelerating but keep cornering - let ax3 = 0.02 * G; - classifier.update(ax3, ay, yaw, wz, vx, vy); - assert!(!classifier.get_mode().has_accel(), "Should exit accel"); - assert!(classifier.get_mode().has_corner(), "Should still corner"); -} -``` - -### Slider Validation JavaScript - -```javascript -// Add to saveCfg() function in websocket_server.rs - -function saveCfg(){ - var acc = parseFloat($('s-acc').value); - var accexit = parseFloat($('s-accexit').value); - var brake = parseFloat($('s-brake').value); - var brakeexit = parseFloat($('s-brakeexit').value); - var lat = parseFloat($('s-lat').value); - var latexit = parseFloat($('s-latexit').value); - var yaw = parseFloat($('s-yaw').value); - var minspd = parseFloat($('s-minspd').value); - - // Validation - if (accexit >= acc) { - alert('⚠️ Accel Exit must be less than Accel Entry!'); - return; - } - if (brakeexit <= brake) { // Both negative, so exit should be > (less negative) - alert('⚠️ Brake Exit must be greater than Brake Entry!\n(Exit: ' + brakeexit + ' > Entry: ' + brake + ')'); - return; - } - if (latexit >= lat) { - alert('⚠️ Lateral Exit must be less than Lateral Entry!'); - return; - } - - // Negate brake values for transmission - brake = -Math.abs(brake); - brakeexit = -Math.abs(brakeexit); - - // Send via WebSocket - if(ws && ws.readyState === 1) { - ws.send('SET:' + acc + ',' + accexit + ',' + brake + ',' + brakeexit + ',' + lat + ',' + latexit + ',' + yaw + ',' + minspd); - - // Visual feedback - const saveBtn = $('cfg-save'); - saveBtn.textContent = '✓ Saved!'; - saveBtn.style.background = '#10b981'; - setTimeout(() => { - saveBtn.textContent = 'Save'; - saveBtn.style.background = ''; - }, 2000); - } else { - alert('❌ Not connected to device'); - } -} -``` - -### Priority 3 Features - Detailed Implementation - -All remaining features (presets, G-meter scale, tooltips, mobile improvements) are ready to implement. -Would you like me to continue with these now, or should we test the current changes first? - -## Next Steps - -1. Add unit tests to src/mode.rs -2. Add validation to saveCfg() -3. Test firmware on hardware -4. Implement Priority 3 features if desired - diff --git a/RELEASE.md b/RELEASE.md deleted file mode 100644 index fa7a082..0000000 --- a/RELEASE.md +++ /dev/null @@ -1,167 +0,0 @@ -# Release Process - -Quick guide for creating Blackbox releases. - -## Prerequisites - -Install required tools: -```bash -# Install espflash if not already installed -cargo install espflash - -# Install GitHub CLI (optional, for command-line releases) -# Ubuntu/Debian: -sudo apt install gh -# macOS: -brew install gh - -# Authenticate with GitHub -gh auth login -``` - -## Release Steps - -### 1. Build the Release Binary - -```bash -# Run the release script -./scripts/release.sh v0.0.1 -``` - -This will: -- Build optimized firmware -- Generate flashable `.bin` file with bootloader -- Show binary size -- Print next steps - -### 2. Test the Binary (Recommended) - -```bash -# Flash to your ESP32-C3 for testing -espflash write-bin 0x0 sensors/blackbox/blackbox-v0.0.1.bin - -# Or monitor while flashing -espflash write-bin 0x0 sensors/blackbox/blackbox-v0.0.1.bin --monitor -``` - -Verify: -- ✅ Device boots and shows LED sequence -- ✅ WiFi AP "Blackbox" appears -- ✅ Dashboard loads at 192.168.71.1 -- ✅ Telemetry updates at ~20Hz -- ✅ Settings save correctly - -### 3. Commit and Push - -```bash -# Add the release script and notes -git add scripts/release.sh release-notes.md RELEASE.md - -# Commit -git commit -m "Add release tooling for v0.0.1" - -# Push to PR branch -git push origin ap_enabled -``` - -### 4. Merge PR on GitHub - -Go to your PR and click "Merge pull request" - -### 5. Create Git Tag - -```bash -# Switch to main and pull -git checkout main -git pull origin main - -# Create annotated tag -git tag -a v0.0.1 -m "Release v0.0.1 - Access Point Mode + Mobile Dashboard" - -# Push tag -git push origin v0.0.1 -``` - -### 6. Create GitHub Release - -**Option A: Using GitHub CLI (Recommended)** -```bash -gh release create v0.0.1 \ - --title "v0.0.1 - Access Point Mode + Mobile Dashboard" \ - --notes-file release-notes.md \ - sensors/blackbox/blackbox-v0.0.1.bin -``` - -**Option B: Using GitHub Web UI** -1. Go to https://github.com/jctoledo/blackbox/releases/new -2. Choose tag: `v0.0.1` -3. Title: `v0.0.1 - Access Point Mode + Mobile Dashboard` -4. Description: Copy from `release-notes.md` -5. Attach file: `sensors/blackbox/blackbox-v0.0.1.bin` -6. Click "Publish release" - -### 7. Verify Release - -Check that: -- ✅ Release appears at https://github.com/jctoledo/blackbox/releases -- ✅ Binary is downloadable -- ✅ Release notes are formatted correctly -- ✅ Tag is visible in repository - -## Release Checklist - -Before releasing: -- [ ] All tests pass (`cargo test`) -- [ ] No clippy warnings (`cargo clippy -- -D warnings`) -- [ ] Code is formatted (`cargo fmt`) -- [ ] CI checks pass on GitHub -- [ ] Binary tested on actual hardware -- [ ] Dashboard loads and works -- [ ] Settings persist correctly -- [ ] Documentation is up to date -- [ ] CHANGELOG.md updated (if exists) - -## Version Numbering - -We use [Semantic Versioning](https://semver.org/): -- `v0.0.x` - Pre-release, experimental -- `v0.x.0` - Minor feature additions -- `v1.0.0` - First stable release -- `vX.Y.Z` - Major.Minor.Patch - -Examples: -- `v0.0.1` - First pre-release -- `v0.0.2` - Bug fixes -- `v0.1.0` - New features added -- `v1.0.0` - Production-ready - -## Troubleshooting - -**Binary too large:** -```bash -# Check size -ls -lh sensors/blackbox/blackbox-v*.bin - -# ESP32-C3 has 4MB flash, binary should be < 2MB -``` - -**espflash not found:** -```bash -cargo install espflash --force -``` - -**gh command not found:** -Install GitHub CLI or use the web UI instead. - -**Permission denied on release.sh:** -```bash -chmod +x scripts/release.sh -``` - -## Next Release - -For the next release, just increment the version: -```bash -./scripts/release.sh v0.0.2 -# ... follow the same steps -``` diff --git a/docs/FAQ.md b/docs/FAQ.md index d936a6f..e183419 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -77,7 +77,7 @@ Any ESP32-C3 development board works. Common options: The current code is written for the WT901. To use a different IMU: - Modify `src/imu.rs` with your IMU's protocol - Common alternatives: MPU6050, BMI088, LSM6DSO -- Contributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) +- Contributions welcome! See [CONTRIBUTING.md](../CONTRIBUTING.md) ### Can I use a different GPS? @@ -138,7 +138,7 @@ To use it as-is: **No** To modify it: **Some Rust helps** - But the code is well-commented - Start with simple changes (thresholds, rates) -- Check [CLAUDE.md](CLAUDE.md) for architecture +- Check [CLAUDE.md](../CLAUDE.md) for architecture ### Can I use this without WiFi? @@ -203,7 +203,7 @@ Without sensor fusion, you're either: - Stuck with 5 Hz GPS (too slow for dynamics) - Using raw IMU (accumulates huge errors) -The math is in `src/ekf.rs`, explained in [CLAUDE.md](CLAUDE.md). +The math is in `src/ekf.rs`, explained in [CLAUDE.md](../CLAUDE.md). ## Troubleshooting @@ -335,7 +335,7 @@ Basically nothing. A 1 GB hotspot could log for 200+ hours. Yes! Active development as of January 2025. -Check the [CHANGELOG.md](CHANGELOG.md) for recent updates. +Check the [CHANGELOG.md](../CHANGELOG.md) for recent updates. ### Can I use this commercially? @@ -351,7 +351,7 @@ Yes! It's MIT licensed. You can: ### How can I contribute? -See [CONTRIBUTING.md](CONTRIBUTING.md) for full details. +See [CONTRIBUTING.md](../CONTRIBUTING.md) for full details. Quick version: 1. Fork the repo @@ -366,7 +366,7 @@ Or help by: ### Where can I get help? -1. **Documentation** - Start with [README.md](README.md) and [SUPPORT.md](SUPPORT.md) +1. **Documentation** - Start with [README.md](../README.md) and this FAQ 2. **Search issues** - Someone might have asked already 3. **Open an issue** - Use templates, provide details 4. **Community** - Help each other in issue discussions diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 6f36e37..0000000 --- a/docs/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# Documentation - -Additional documentation and resources for Blackbox. - -## Available Documentation - -### Main Project Docs (in root) -- **[README.md](../README.md)** - Project overview and quick start -- **[CLAUDE.md](../CLAUDE.md)** - Architecture and implementation details -- **[CONTRIBUTING.md](../CONTRIBUTING.md)** - How to contribute + Code of Conduct -- **[CHANGELOG.md](../CHANGELOG.md)** - Version history - -### In This Directory (docs/) -- **[FAQ.md](FAQ.md)** - Frequently asked questions and troubleshooting -- **[SECURITY.md](SECURITY.md)** - Security policy and vulnerability reporting - -## Future Documentation - -This directory is reserved for additional documentation: - -### Planned -- [ ] **hardware-guide.md** - Detailed hardware assembly guide with photos -- [ ] **mounting-guide.md** - Best practices for in-vehicle mounting -- [ ] **tuning-guide.md** - How to tune the Kalman filter for your setup -- [ ] **dashboard-tutorial.md** - Building custom dashboards -- [ ] **data-analysis.md** - Analyzing telemetry data -- [ ] **coordinate-frames.md** - Deep dive into coordinate transformations - -### Images -Place images and diagrams in `docs/images/`: -- Wiring diagrams -- PCB layouts -- Mounting examples -- Dashboard screenshots - -### Examples -See `../examples/` for: -- Example telemetry data -- Reference implementations -- Integration examples - -## Contributing Documentation - -Documentation improvements are highly valued! See [CONTRIBUTING.md](../CONTRIBUTING.md). - -Good documentation: -- Is clear and concise -- Includes examples -- Has diagrams where helpful -- Is tested (actually works) -- Considers the reader's skill level diff --git a/docs/SECURITY.md b/docs/SECURITY.md index 3ec646c..419b9d7 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -76,7 +76,7 @@ Security issues include: ### How to Report **DO:** -1. Email security reports to: [INSERT SECURITY EMAIL] +1. Use GitHub's private vulnerability reporting: go to the Security tab → "Report a vulnerability" 2. Include detailed description of the vulnerability 3. Provide steps to reproduce if possible 4. Give us reasonable time to fix before public disclosure (90 days) diff --git a/docs/SENSOR_TOOLKIT_GUIDE.md b/docs/SENSOR_TOOLKIT_GUIDE.md deleted file mode 100644 index 8cafd61..0000000 --- a/docs/SENSOR_TOOLKIT_GUIDE.md +++ /dev/null @@ -1,573 +0,0 @@ -## Sensor Toolkit Guide - -# Adding New Sensors to Blackbox - -Blackbox is designed as a **modular sensor fusion platform**. Adding a new sensor is as simple as implementing a trait and registering it. This guide shows you how. - ---- - -## Architecture Overview - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ Distributed Sensor Network │ -└─────────────────────────────────────────────────────────────────┘ - -┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ -│ ESP32 #1 │ │ ESP32 #2 │ │ ESP32 #3 │ │ ESP32 #4 │ -│ │ │ │ │ │ │ │ -│ WT901 IMU │ │ NEO-6M GPS │ │ Wheel Speed │ │ CAN Bus │ -│ │ │ │ │ │ │ │ -└──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ - │ │ │ │ - │ MQTT │ MQTT │ MQTT │ MQTT - │ topic: │ topic: │ topic: │ topic: - │ imu/wt901 │ gps/neo6m │ wheels/abs │ can/obd2 - │ │ │ │ - └─────────────────┴─────────────────┴─────────────────┘ - │ - ▼ - ┌─────────────────────────┐ - │ Fusion Coordinator │ - │ (subscribes to all) │ - │ │ - │ Multi-Sensor EKF │ - └────────────┬────────────┘ - │ - ▼ - ┌───────────────┐ - │ Fused State │ - │ Published to: │ - │ car/state │ - └───────────────┘ -``` - ---- - -## Quick Start: Add a New Sensor in 5 Minutes - -### Step 1: Define Your Sensor Struct - -```rust -// src/sensor_plugins.rs - -use crate::sensor_framework::*; - -pub struct MySensor { - // Your sensor-specific fields - last_reading_us: u64, - some_calibration_value: f32, -} - -impl MySensor { - pub fn new() -> Self { - Self { - last_reading_us: 0, - some_calibration_value: 0.0, - } - } -} -``` - -### Step 2: Implement the `Sensor` Trait - -```rust -impl Sensor for MySensor { - fn capabilities(&self) -> SensorCapabilities { - SensorCapabilities { - id: SensorId(42), // Pick a unique ID - name: "My Cool Sensor", - measurement_types: &[MeasurementType::Custom("my_measurement")], - update_rate_hz: 100.0, - latency_ms: 10.0, - accuracy: 0.5, - mqtt_topic: "car/sensors/mysensor", - } - } - - fn poll(&mut self) -> Option { - // Read from your sensor hardware - // Return None if no new data available - - let now_us = unsafe { esp_idf_svc::sys::esp_timer_get_time() as u64 }; - - // Example: Return a reading - Some(SensorReading { - sensor_id: SensorId(42), - timestamp_us: now_us, - data: SensorData::Custom(CustomReading { - data: vec![1, 2, 3, 4], // Your sensor data - }), - }) - } - - fn init(&mut self) -> Result<(), SensorError> { - // Initialize your sensor hardware - // Setup GPIO, I2C, SPI, UART, etc. - - log::info!("MySensor initialized!"); - Ok(()) - } - - fn calibrate(&mut self) -> Result<(), SensorError> { - // Optional: Calibration routine - // Can return Ok(()) if no calibration needed - - log::info!("MySensor calibrated!"); - Ok(()) - } - - fn is_healthy(&self) -> bool { - // Optional: Health check - // Return false if sensor is malfunctioning - true - } -} -``` - -### Step 3: Register Your Sensor - -```rust -// In main.rs - -let mut registry = SensorRegistry::new(); - -// Register your sensor -let my_sensor = Box::new(MySensor::new()); -registry.register(my_sensor).expect("Failed to register sensor"); - -// Poll all sensors -loop { - let readings = registry.poll_all(); - for reading in readings { - publisher.publish(&reading).ok(); - } -} -``` - -**That's it!** Your sensor is now integrated into the system. - ---- - -## Real-World Examples - -### Example 1: Adding Wheel Speed Sensors - -**Hardware:** 4x Hall effect sensors on wheel hubs - -```rust -pub struct WheelSpeedSensor { - fl_count: u32, // Front-left pulse count - fr_count: u32, - rl_count: u32, - rr_count: u32, - last_time_us: u64, - pulses_per_rev: u32, // Depends on your sensor - wheel_circumference: f32, // meters -} - -impl WheelSpeedSensor { - pub fn new() -> Self { - Self { - fl_count: 0, - fr_count: 0, - rl_count: 0, - rr_count: 0, - last_time_us: 0, - pulses_per_rev: 48, // Example: 48 pulses per wheel rotation - wheel_circumference: 1.884, // Example: 60cm diameter wheel - } - } - - // Called from GPIO interrupt handler - pub fn on_fl_pulse(&mut self) { self.fl_count += 1; } - pub fn on_fr_pulse(&mut self) { self.fr_count += 1; } - pub fn on_rl_pulse(&mut self) { self.rl_count += 1; } - pub fn on_rr_pulse(&mut self) { self.rr_count += 1; } -} - -impl Sensor for WheelSpeedSensor { - fn capabilities(&self) -> SensorCapabilities { - SensorCapabilities { - id: SensorId(11), - name: "ABS Wheel Speed", - measurement_types: &[MeasurementType::WheelSpeed, MeasurementType::Velocity3D], - update_rate_hz: 100.0, - latency_ms: 5.0, - accuracy: 0.05, // ±5cm/s - mqtt_topic: "car/sensors/wheels", - } - } - - fn poll(&mut self) -> Option { - let now_us = unsafe { esp_idf_svc::sys::esp_timer_get_time() as u64 }; - let dt = (now_us - self.last_time_us) as f32 * 1e-6; - - if dt < 0.01 { // Poll at 100Hz max - return None; - } - - // Convert pulse counts to speeds - let fl_speed = (self.fl_count as f32 / self.pulses_per_rev as f32) - * self.wheel_circumference / dt; - - // Reset counts - self.fl_count = 0; - // ... same for fr, rl, rr - - self.last_time_us = now_us; - - Some(SensorReading { - sensor_id: SensorId(11), - timestamp_us: now_us, - data: SensorData::WheelSpeed(WheelSpeedReading { - fl: fl_speed, - fr: 0.0, // Calculate similarly - rl: 0.0, - rr: 0.0, - }), - }) - } - - fn init(&mut self) -> Result<(), SensorError> { - // Setup GPIO interrupts for wheel speed sensors - // (ESP-IDF specific code omitted for brevity) - Ok(()) - } -} -``` - -### Example 2: Adding CAN Bus (OBD2) - -**Hardware:** ESP32 with MCP2515 CAN controller - -```rust -use esp_idf_hal::can::*; - -pub struct CanBusSensor { - can: CanDriver, - rpm: u16, - throttle: u8, - speed: u8, -} - -impl Sensor for CanBusSensor { - fn capabilities(&self) -> SensorCapabilities { - SensorCapabilities { - id: SensorId(10), - name: "CAN Bus OBD2", - measurement_types: &[ - MeasurementType::Custom("RPM"), - MeasurementType::Throttle, - MeasurementType::Velocity3D, - ], - update_rate_hz: 50.0, - latency_ms: 20.0, - accuracy: 1.0, - mqtt_topic: "car/sensors/can", - } - } - - fn poll(&mut self) -> Option { - // Read CAN messages - if let Ok(frame) = self.can.receive() { - match frame.id() { - 0x0C => { // RPM (PID 0x0C) - self.rpm = u16::from_be_bytes([frame.data()[0], frame.data()[1]]); - } - 0x11 => { // Throttle (PID 0x11) - self.throttle = frame.data()[0]; - } - 0x0D => { // Vehicle speed (PID 0x0D) - self.speed = frame.data()[0]; - } - _ => {} - } - } - - Some(SensorReading { - sensor_id: SensorId(10), - timestamp_us: unsafe { esp_idf_svc::sys::esp_timer_get_time() as u64 }, - data: SensorData::Can(CanReading { - can_id: 0x123, - data: vec![self.throttle, self.speed], - }), - }) - } - - fn init(&mut self) -> Result<(), SensorError> { - // Initialize CAN bus at 500kbps - // (Implementation details omitted) - Ok(()) - } -} -``` - -### Example 3: Adding Lidar for Lane Detection - -**Hardware:** Cheap 2D Lidar (e.g., RPLidar A1) - -```rust -pub struct LidarSensor { - // Serial port for lidar - last_scan: Vec<[f32; 3]>, -} - -impl Sensor for LidarSensor { - fn capabilities(&self) -> SensorCapabilities { - SensorCapabilities { - id: SensorId(20), - name: "RPLidar A1", - measurement_types: &[MeasurementType::LaneDetection], - update_rate_hz: 10.0, // 10Hz scan rate - latency_ms: 100.0, - accuracy: 0.1, // ±10cm - mqtt_topic: "car/sensors/lidar", - } - } - - fn poll(&mut self) -> Option { - // Read lidar scan - // Process points to detect lane markings - // Return processed data - - Some(SensorReading { - sensor_id: SensorId(20), - timestamp_us: unsafe { esp_idf_svc::sys::esp_timer_get_time() as u64 }, - data: SensorData::Lidar(LidarReading { - points: self.last_scan.clone(), - }), - }) - } - - fn init(&mut self) -> Result<(), SensorError> { - // Start lidar motor - // Begin scanning - Ok(()) - } -} -``` - ---- - -## Sensor Fusion Integration - -Once your sensor is publishing data, integrate it into the fusion coordinator: - -### Option 1: Local Fusion (Single ESP32) - -```rust -// In fusion_coordinator.rs - -impl FusionCoordinator { - pub fn process_reading(&mut self, reading: SensorReading) { - match reading.data { - SensorData::WheelSpeed(wheels) => { - self.process_wheel_speed(wheels, reading.timestamp_us); - } - SensorData::Can(can) => { - self.process_can(can, reading.timestamp_us); - } - // Add your sensor here! - SensorData::Custom(custom) => { - // Process your custom sensor data - // Update EKF or other estimators - } - _ => {} - } - } -} -``` - -### Option 2: Distributed Fusion (Multiple ESP32s) - -Each sensor runs on its own ESP32 and publishes to MQTT: - -```rust -// On Sensor ESP32 -let mut sensor = MySensor::new(); -let mut mqtt = MqttClient::new("mqtt://broker:1883").unwrap(); - -loop { - if let Some(reading) = sensor.poll() { - let json = serde_json::to_string(&reading).unwrap(); - mqtt.publish("car/sensors/mysensor", &json).unwrap(); - } -} -``` - -```rust -// On Fusion ESP32 -let mut coordinator = FusionCoordinator::new(); -let mut mqtt = MqttClient::new("mqtt://broker:1883").unwrap(); - -mqtt.subscribe("car/sensors/#").unwrap(); // Subscribe to all sensors - -mqtt.on_message(|topic, payload| { - let reading: SensorReading = serde_json::from_slice(payload).unwrap(); - coordinator.process_reading(reading); -}); -``` - ---- - -## Best Practices - -### 1. **Sensor IDs** -- Pick unique IDs (1-1000) -- Reserve ranges: IMU (1-9), GPS (10-19), Wheels (20-29), CAN (30-39), etc. - -### 2. **Timestamps** -- Always use microsecond timestamps -- Use system time, not sensor time (for synchronization) - -### 3. **MQTT Topics** -- Format: `car/sensors//` -- Examples: `car/sensors/imu/wt901`, `car/sensors/gps/neo6m` - -### 4. **Error Handling** -- Sensors should gracefully degrade -- Return `None` from `poll()` if no data available -- Return errors from `init()` if hardware fails - -### 5. **Performance** -- Keep `poll()` non-blocking -- Use interrupts for high-frequency sensors -- Buffer data if necessary - ---- - -## Debugging New Sensors - -### 1. Test Sensor in Isolation - -```rust -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_my_sensor() { - let mut sensor = MySensor::new(); - sensor.init().unwrap(); - - // Simulate hardware input - // Check output - let reading = sensor.poll(); - assert!(reading.is_some()); - } -} -``` - -### 2. Enable Logging - -```rust -impl Sensor for MySensor { - fn poll(&mut self) -> Option { - log::debug!("MySensor polled"); - // ... your code - let reading = /* ... */; - log::info!("MySensor reading: {:?}", reading); - Some(reading) - } -} -``` - -### 3. Use the LogPublisher - -```rust -let mut sensor = MySensor::new(); -let mut publisher = LogPublisher; - -sensor.init().unwrap(); - -loop { - if let Some(reading) = sensor.poll() { - publisher.publish(&reading).unwrap(); - } -} -``` - -Output: -``` -[SENSOR 42] Custom(CustomReading { data: [1, 2, 3, 4] }) -``` - ---- - -## FAQ - -**Q: Can I add a sensor that's not on an ESP32?** -A: Yes! Use MQTT. Any device that can publish MQTT messages can be a sensor (Raspberry Pi, Arduino, phone, etc.) - -**Q: How do I handle sensors with different update rates?** -A: The fusion coordinator automatically handles this. Fast sensors (200Hz IMU) and slow sensors (5Hz GPS) work together seamlessly. - -**Q: What if my sensor needs calibration?** -A: Implement the `calibrate()` method. Call it from main before starting the main loop. - -**Q: Can I add vision sensors (cameras)?** -A: Yes, but you'll need a more powerful processor (ESP32-S3 or Raspberry Pi) for image processing. Publish only processed results (lane detection, object detection) not raw images. - -**Q: How do I test sensor fusion without hardware?** -A: Create a `SimulatedSensor` that plays back recorded data or generates synthetic data. - ---- - -## Advanced: Custom Measurement Types - -If the built-in `MeasurementType` enum doesn't cover your sensor: - -```rust -// In sensor_framework.rs - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum MeasurementType { - // ... existing types ... - - // Add your custom type - TirePressure, - SuspensionTravel, - FuelLevel, - - Custom(&'static str), // For truly custom measurements -} -``` - -Then use it in your sensor: - -```rust -impl Sensor for TirePressureSensor { - fn capabilities(&self) -> SensorCapabilities { - SensorCapabilities { - id: SensorId(100), - name: "TPMS", - measurement_types: &[MeasurementType::TirePressure], - // ... - } - } -} -``` - ---- - -## Example: Complete Sensor Plugin - -See `src/sensor_plugins.rs` for complete examples: -- `Wt901ImuSensor` - IMU implementation -- `Neo6mGpsSensor` - GPS implementation -- `WheelSpeedSensor` - Wheel speed template -- `CanBusSensor` - CAN bus template - ---- - -## Next Steps - -1. Implement your sensor using the `Sensor` trait -2. Test it in isolation with `LogPublisher` -3. Register it with `SensorRegistry` -4. Add fusion logic in `FusionCoordinator::process_reading()` -5. Deploy and publish to MQTT -6. Monitor fused output on `car/fusion/state` - -Happy sensing! 🚗💨 diff --git a/drivers/neo6m/README.md b/drivers/neo6m/README.md deleted file mode 100644 index 7bc0c88..0000000 --- a/drivers/neo6m/README.md +++ /dev/null @@ -1,157 +0,0 @@ -# NEO-6M GPS NMEA Parser - -Pure Rust parser for NMEA sentences from NEO-6M (and compatible) GPS receivers. - -## Features - -- ✅ Zero-allocation NMEA parsing -- ✅ Supports GPRMC/GNRMC sentences -- ✅ Reference point averaging (warmup) -- ✅ Local coordinate conversion (lat/lon → meters) -- ✅ Position-based speed calculation -- ✅ `no_std` compatible -- ✅ No external dependencies - -## Hardware - -This driver works with any GPS receiver that outputs NMEA sentences: -- **NEO-6M** - Popular low-cost GPS module -- **NEO-7M, NEO-8M** - Higher precision variants -- Any NMEA 0183 compatible GPS -- **Baud rate:** Typically 9600 (configurable on module) - -## Usage - -Add to your `Cargo.toml`: - -```toml -[dependencies] -neo6m = { path = "path/to/drivers/neo6m" } -``` - -### Basic Example - -```rust -use neo6m::NmeaParser; - -let mut parser = NmeaParser::new(); - -// Read bytes from UART and feed to parser -loop { - let byte = uart.read_byte(); - - if parser.feed_byte(byte) { - // New sentence parsed - let fix = parser.last_fix(); - - if fix.valid { - println!("GPS: {:.6}, {:.6}", fix.lat, fix.lon); - println!("Speed: {:.1} m/s", fix.speed); - println!("Course: {:.1}°", fix.course.to_degrees()); - println!("Time: {:02}:{:02}:{:02} UTC", fix.hour, fix.minute, fix.second); - } - } -} -``` - -### With Local Coordinates - -```rust -use neo6m::NmeaParser; - -let mut parser = NmeaParser::new(); - -// Feed data until warmup complete (averages first 5 fixes) -while !parser.is_warmed_up() { - if parser.feed_byte(uart.read_byte()) { - println!("Warmup: {:.0}%", parser.warmup_progress() * 100.0); - } -} - -println!("Reference point: {:.6}, {:.6}", - parser.reference().lat, parser.reference().lon); - -// Now get local coordinates -loop { - if parser.feed_byte(uart.read_byte()) { - if let Some((east, north)) = parser.to_local_coords() { - println!("Position: ({:.2}m E, {:.2}m N)", east, north); - } - } -} -``` - -### Position-Based Speed - -```rust -use neo6m::NmeaParser; - -let mut parser = NmeaParser::new(); -let mut last_timestamp_ms = 0; - -loop { - if parser.feed_byte(uart.read_byte()) { - let now_ms = get_timestamp_ms(); - - // Update position-based speed calculation - parser.update_position_speed(now_ms, last_timestamp_ms); - - println!("GPS speed: {:.1} m/s", parser.last_fix().speed); - println!("Position-based speed: {:.1} m/s", parser.position_based_speed()); - - last_timestamp_ms = now_ms; - } -} -``` - -## NMEA Sentences - -This parser currently supports: - -### GPRMC/GNRMC - Recommended Minimum -``` -$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A -``` - -Fields: -- Time: 12:35:19 UTC -- Status: A (active/valid) -- Latitude: 48°07.038' N = 48.1173° -- Longitude: 11°31.000' E = 11.5167° -- Speed: 22.4 knots = 11.54 m/s -- Course: 84.4° true -- Date: 23/03/1994 - -## Coordinate System - -The parser provides two coordinate systems: - -### 1. Geographic (WGS84) -- Latitude/Longitude in degrees -- Direct from GPS - -### 2. Local ENU (East-North-Up) -- Origin at reference point (averaged from first N fixes) -- East/North in meters -- Useful for navigation and vehicle dynamics - -Conversion formulas: -``` -north_m = (lat - ref_lat) × 111,320 -east_m = (lon - ref_lon) × 111,320 × cos(ref_lat) -``` - -## No-Std Support - -This driver works in `no_std` environments: - -```toml -[dependencies] -neo6m = { path = "...", default-features = false } -``` - -Note: Requires `alloc` feature for NMEA sentence parsing (Vec usage). - -## License - -MIT OR Apache-2.0 diff --git a/drivers/neo6m/Cargo.toml b/drivers/ublox-gps/Cargo.toml similarity index 67% rename from drivers/neo6m/Cargo.toml rename to drivers/ublox-gps/Cargo.toml index 0d877f5..6a448f4 100644 --- a/drivers/neo6m/Cargo.toml +++ b/drivers/ublox-gps/Cargo.toml @@ -1,15 +1,15 @@ [package] -name = "neo6m" +name = "ublox-gps" version.workspace = true authors.workspace = true edition.workspace = true -description = "NEO-6M GPS NMEA parser for embedded systems" +description = "u-blox GPS driver with NMEA and UBX protocol support (NEO-6M, NEO-M9N, etc.)" license = "MIT OR Apache-2.0" -keywords = ["gps", "nmea", "neo6m", "parser", "embedded"] +keywords = ["gps", "nmea", "ubx", "ublox", "neo-m9n", "parser", "embedded"] categories = ["embedded", "hardware-support", "no-std", "parser-implementations"] [lib] -name = "neo6m" +name = "ublox_gps" [dependencies] # Floating point math for no-std diff --git a/drivers/ublox-gps/README.md b/drivers/ublox-gps/README.md new file mode 100644 index 0000000..537b4f0 --- /dev/null +++ b/drivers/ublox-gps/README.md @@ -0,0 +1,221 @@ +# u-blox GPS Driver + +Pure Rust driver for u-blox GPS receivers with NMEA parsing and UBX protocol support. + +## Supported Hardware + +| Module | Max Rate | Protocol | Features | +|--------|----------|----------|----------| +| NEO-6M | 5 Hz | NMEA only | Budget option, basic accuracy | +| NEO-7M | 10 Hz | NMEA + UBX | Improved accuracy | +| NEO-M8N | 10 Hz | NMEA + UBX | Better sensitivity | +| **NEO-M9N** | **25 Hz** | NMEA + UBX | Automotive mode, best accuracy | + +Any NMEA 0183 compatible GPS receiver will work for basic functionality. + +## Features + +- Zero-allocation NMEA parsing (no_std compatible) +- Supports GPRMC/GNRMC (position/velocity), GGA (satellites), GSA (DOP) +- UBX protocol commands for GPS configuration (NEO-M9N) +- Reference point averaging (warmup) +- Local coordinate conversion (lat/lon to meters) +- Position-based speed calculation +- Dynamic platform models (Automotive, Pedestrian, etc.) + +## Usage + +Add to your `Cargo.toml`: + +```toml +[dependencies] +ublox-gps = { path = "path/to/drivers/ublox-gps" } +``` + +### Basic NMEA Parsing + +```rust +use ublox_gps::NmeaParser; + +let mut parser = NmeaParser::new(); + +// Read bytes from UART and feed to parser +loop { + let byte = uart.read_byte(); + + if parser.feed_byte(byte) { + // New sentence parsed + let fix = parser.last_fix(); + + if fix.valid { + println!("GPS: {:.6}, {:.6}", fix.lat, fix.lon); + println!("Speed: {:.1} m/s", fix.speed); + println!("Satellites: {}", fix.satellites); + println!("HDOP: {:.1}", fix.hdop); + } + } +} +``` + +### With Local Coordinates + +```rust +use ublox_gps::NmeaParser; + +let mut parser = NmeaParser::new(); + +// Feed data until warmup complete (averages first 5 fixes) +while !parser.is_warmed_up() { + if parser.feed_byte(uart.read_byte()) { + println!("Warmup: {:.0}%", parser.warmup_progress() * 100.0); + } +} + +println!("Reference point: {:.6}, {:.6}", + parser.reference().lat, parser.reference().lon); + +// Now get local coordinates +loop { + if parser.feed_byte(uart.read_byte()) { + if let Some((east, north)) = parser.to_local_coords() { + println!("Position: ({:.2}m E, {:.2}m N)", east, north); + } + } +} +``` + +### Configuring NEO-M9N (UBX Protocol) + +```rust +use ublox_gps::ubx::{UbxCommands, DynamicModel, generate_init_sequence}; + +// Generate initialization sequence for 10Hz automotive mode +let mut buffer = [0u8; 128]; +let commands = generate_init_sequence(10, 115200, &mut buffer); + +// Send baud rate command at current GPS baud (factory: 38400) +let (baud_off, baud_len) = commands[0]; +uart.write(&buffer[baud_off..baud_off + baud_len]); +delay_ms(100); + +// Switch UART to new baud rate +uart.set_baudrate(115200); +delay_ms(100); + +// Send rate + dynamic model command +let (config_off, config_len) = commands[1]; +uart.write(&buffer[config_off..config_off + config_len]); +``` + +### Individual UBX Commands + +```rust +use ublox_gps::ubx::{UbxCommands, DynamicModel}; + +let mut buffer = [0u8; 32]; + +// Set measurement rate to 25 Hz +let len = UbxCommands::cfg_valset_rate(25, &mut buffer); +uart.write(&buffer[..len]); + +// Set automotive dynamic model +let len = UbxCommands::cfg_valset_dynmodel(DynamicModel::Automotive, &mut buffer); +uart.write(&buffer[..len]); + +// Set baud rate to 115200 +let len = UbxCommands::cfg_valset_baudrate(115200, &mut buffer); +uart.write(&buffer[..len]); +``` + +## Dynamic Platform Models + +The NEO-M9N supports multiple dynamic models optimized for different use cases: + +| Model | Use Case | Max Altitude | Max Velocity | +|-------|----------|--------------|--------------| +| Portable | General handheld | 12km | 310 m/s | +| Stationary | Fixed installation | 9km | 10 m/s | +| Pedestrian | Walking/running | 9km | 30 m/s | +| **Automotive** | Vehicle telemetry | 9km | 100 m/s | +| Sea | Marine | 9km | 25 m/s | +| Airborne1g | Light aircraft | 50km | 100 m/s | +| Airborne2g | Aerobatic | 50km | 250 m/s | +| Airborne4g | High-g maneuvers | 50km | 500 m/s | + +**Automotive mode** is recommended for vehicle telemetry - it's optimized for road dynamics and provides the best position/velocity accuracy for cars. + +## NMEA Sentences + +### GPRMC/GNRMC - Recommended Minimum +``` +$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A +``` + +Fields: Time, Status (A=valid), Latitude, Longitude, Speed (knots), Course, Date + +### GPGGA/GNGGA - Fix Data +``` +$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*47 +``` + +Fields: Time, Lat, Lon, Fix Quality, Satellites, HDOP, Altitude + +### GPGSA/GNGSA - DOP and Active Satellites +``` +$GPGSA,A,3,04,05,06,07,08,09,10,11,12,13,14,15,1.4,0.9,1.1*3D +``` + +Fields: Mode, Fix Type, Satellite PRNs, PDOP, HDOP, VDOP + +## Coordinate System + +The parser provides two coordinate systems: + +### 1. Geographic (WGS84) +- Latitude/Longitude in degrees +- Direct from GPS + +### 2. Local ENU (East-North-Up) +- Origin at reference point (averaged from first N fixes) +- East/North in meters +- Useful for navigation and vehicle dynamics + +Conversion formulas: +``` +north_m = (lat - ref_lat) * 111,320 +east_m = (lon - ref_lon) * 111,320 * cos(ref_lat) +``` + +## GpsFix Structure + +```rust +pub struct GpsFix { + pub lat: f64, // Latitude (degrees, + = North) + pub lon: f64, // Longitude (degrees, + = East) + pub speed: f32, // Ground speed (m/s) + pub course: f32, // Course over ground (radians) + pub valid: bool, // Fix validity + pub hour: u8, // UTC hour + pub minute: u8, // UTC minute + pub second: u8, // UTC second + pub altitude: f32, // Altitude MSL (meters) + pub satellites: u8, // Satellites in use + pub fix_quality: FixQuality, // Fix type + pub hdop: f32, // Horizontal DOP + pub pdop: f32, // Position DOP + pub vdop: f32, // Vertical DOP +} +``` + +## No-Std Support + +This driver works in `no_std` environments: + +```toml +[dependencies] +ublox-gps = { path = "...", default-features = false } +``` + +## License + +MIT OR Apache-2.0 diff --git a/drivers/neo6m/src/lib.rs b/drivers/ublox-gps/src/lib.rs similarity index 98% rename from drivers/neo6m/src/lib.rs rename to drivers/ublox-gps/src/lib.rs index 0b6e58e..49e8764 100644 --- a/drivers/neo6m/src/lib.rs +++ b/drivers/ublox-gps/src/lib.rs @@ -1,14 +1,15 @@ -//! NEO-6M / NEO-M9N GPS Driver +//! u-blox GPS Driver //! -//! This crate provides a pure Rust parser for NMEA sentences from u-blox GPS -//! receivers (NEO-6M, NEO-M9N, etc.). It supports GPRMC/GNRMC/GGA/GSA sentences -//! and provides position, velocity, satellite info, and time information. +//! This crate provides a pure Rust driver for u-blox GPS receivers with NMEA +//! parsing and UBX protocol support (NEO-6M, NEO-M9N, etc.). It supports +//! GPRMC/GNRMC/GGA/GSA sentences and provides position, velocity, satellite +//! info, and time information. //! //! # Features //! //! - Zero-allocation NMEA parsing //! - Supports GPRMC/GNRMC (position/velocity), GGA (satellites), GSA (DOP) -//! - UBX protocol commands for GPS configuration +//! - UBX protocol commands for GPS configuration (NEO-M9N) //! - Reference point averaging (warmup) //! - Local coordinate conversion (lat/lon → meters) //! - Position-based speed calculation @@ -18,7 +19,7 @@ //! # Example //! //! ```ignore -//! use neo6m::NmeaParser; +//! use ublox_gps::NmeaParser; //! //! let mut parser = NmeaParser::new(); //! diff --git a/drivers/neo6m/src/ubx.rs b/drivers/ublox-gps/src/ubx.rs similarity index 100% rename from drivers/neo6m/src/ubx.rs rename to drivers/ublox-gps/src/ubx.rs diff --git a/framework/README.md b/framework/README.md index a1e25c8..e314e37 100644 --- a/framework/README.md +++ b/framework/README.md @@ -229,7 +229,7 @@ MIT License - Use it, modify it, sell it, race with it. See `LICENSE` for detail - **Blackbox** - ESP32-based telemetry system using this framework - **wt901 driver** - Standalone WT901 IMU driver crate -- **neo6m driver** - Standalone NEO-6M GPS driver crate +- **ublox-gps driver** - u-blox GPS driver crate (NEO-6M, NEO-M9N, etc.) ## Documentation diff --git a/release-notes.md b/release-notes.md deleted file mode 100644 index f42ae7b..0000000 --- a/release-notes.md +++ /dev/null @@ -1,129 +0,0 @@ -# Blackbox v0.0.1 - First Release 🚀 - -ESP32-C3 vehicle telemetry system with built-in mobile dashboard and dual WiFi modes. - -## 🎯 Major Features - -### Built-in Mobile Dashboard -- Mobile-optimized web interface running directly on ESP32 -- Real-time telemetry display at ~20 Hz via HTTP polling -- G-meter with enhanced trail visualization and glow effects -- Live speed display with car speedometer-like smoothing -- Mode detection visualization (6 states including combined modes) -- Session timer and GPS coordinates -- CSV data recording and export -- **Live settings configuration** with validation - -**Usage:** Connect phone to `Blackbox` WiFi → Open browser to `http://192.168.71.1` - -### Dual WiFi Operating Modes - -**Access Point Mode (Default)** -- ESP32 creates its own WiFi network - no router required -- Perfect for track days, autocross, mobile use -- Fixed IP: `192.168.71.1:80` -- SSID: `Blackbox` / Password: `blackbox123` - -**Station Mode** -- ESP32 connects to your existing WiFi network -- UDP telemetry streaming (20 Hz) to laptop -- MQTT status messages -- For data logging and multi-client scenarios - -### Enhanced Mode Detection -- **6 driving states:** IDLE, ACCEL, BRAKE, CORNER, ACCEL+CORNER, BRAKE+CORNER -- Combined states using bitflags (e.g., trail braking = BRAKE+CORNER) -- Independent component detection with hysteresis -- Tuned for street driving (lowered acceleration threshold to 0.10g) -- **177 comprehensive unit tests** - -## 🔧 Hardware Requirements - -- **ESP32-C3** microcontroller -- **WT901** 9-axis IMU (UART, 200Hz) -- **NEO-6M** GPS module (UART, 5Hz) -- **WS2812** RGB LED (status indicator) - -## 📥 Installation - -### Flash the Firmware - -**Method 1: Using espflash (recommended)** -```bash -espflash write-bin 0x0 blackbox-v0.0.1.bin -``` - -**Method 2: Using ESP Web Flasher** -1. Go to https://espressif.github.io/esptool-js/ -2. Connect ESP32-C3 via USB -3. Select `blackbox-v0.0.1.bin` -4. Flash offset: `0x0` -5. Click "Program" - -### First Boot -1. Power on ESP32 - LED shows boot sequence (see README for codes) -2. **AP Mode (default):** Connect phone to WiFi network "Blackbox" (password: `blackbox123`) -3. Open browser to `http://192.168.71.1` -4. View live telemetry! - -### Switch to Station Mode -To use UDP streaming and MQTT (requires rebuild): -```bash -export WIFI_MODE="station" -export WIFI_SSID="YourNetwork" -export WIFI_PASSWORD="YourPassword" -export MQTT_BROKER="mqtt://192.168.1.100:1883" -export UDP_SERVER="192.168.1.100:9000" -cargo build --release -# Then flash the new binary -``` - -## 📊 Dashboard Features - -- **G-meter:** Real-time visualization with gradient trail, tick marks, enhanced glow -- **Max G tracking:** L/R/Accel/Brake peak values -- **Speed display:** EMA-filtered for smoothness (like car speedometer) -- **Mode indicator:** Visual icons for each driving state -- **Settings:** Adjust all 8 mode detection thresholds live -- **Recording:** Capture session data and export to CSV - -## 🎨 Technical Highlights - -- 7-state Extended Kalman Filter for sensor fusion -- Zero-velocity updates (ZUPT) prevent drift when stopped -- Gravity-compensated accelerations -- GPS warmup with local coordinate mapping -- HTTP polling architecture (replaced WebSocket to fix thread blocking) -- Binary telemetry protocol (67 bytes, 20Hz) -- Configurable mode detection thresholds - -## 📝 Binary Protocol - -Mode byte changed to bitflags: -- `0` = IDLE -- `1` = ACCEL -- `2` = BRAKE -- `4` = CORNER -- `5` = ACCEL+CORNER (trail throttle) -- `6` = BRAKE+CORNER (trail braking) - -## 🐛 Known Issues - -None reported yet! This is the first release. - -## 📚 Documentation - -- [README](https://github.com/jctoledo/blackbox/blob/main/README.md) - Full setup guide -- [CLAUDE.md](https://github.com/jctoledo/blackbox/blob/main/CLAUDE.md) - Architecture details -- [Hardware Setup](https://github.com/jctoledo/blackbox/blob/main/sensors/blackbox/README.md) - Pin assignments - -## 🙏 Credits - -Built with: -- Rust ESP-IDF framework -- ESP32-C3 RISC-V chip -- sensor-fusion library - ---- - -**Full Changelog:** https://github.com/jctoledo/blackbox/compare/...v0.0.1 diff --git a/sensors/README.md b/sensors/README.md index b2afec9..0faf8a4 100644 --- a/sensors/README.md +++ b/sensors/README.md @@ -61,7 +61,7 @@ sensor-fusion = { path = "../../framework" } # Sensor drivers (if using existing ones) wt901 = { path = "../../drivers/wt901" } -neo6m = { path = "../../drivers/neo6m" } +ublox-gps = { path = "../../drivers/ublox-gps" } # Your platform HAL # For ESP32: @@ -182,7 +182,7 @@ See the framework documentation for details on: Reusable sensor drivers available: - [wt901](../drivers/wt901/) - WT901 9-axis IMU -- [neo6m](../drivers/neo6m/) - NEO-6M GPS receiver +- [ublox-gps](../drivers/ublox-gps/) - u-blox GPS driver (NEO-6M, NEO-M9N, etc.) Create your own driver crates in `drivers/` for reuse across projects! diff --git a/sensors/blackbox/Cargo.toml b/sensors/blackbox/Cargo.toml index cbf368b..81d922b 100644 --- a/sensors/blackbox/Cargo.toml +++ b/sensors/blackbox/Cargo.toml @@ -10,7 +10,7 @@ sensor-fusion = { path = "../../framework" } # Sensor drivers (workspace members) wt901 = { path = "../../drivers/wt901" } -neo6m = { path = "../../drivers/neo6m" } +ublox-gps = { path = "../../drivers/ublox-gps" } # ESP32 HAL and services esp-idf-hal = "0.45" diff --git a/sensors/blackbox/src/gps.rs b/sensors/blackbox/src/gps.rs index 2dcb282..e1ff0a2 100644 --- a/sensors/blackbox/src/gps.rs +++ b/sensors/blackbox/src/gps.rs @@ -3,5 +3,5 @@ /// This module re-exports the GPS driver types for use in the application. /// Supports both NEO-6M and NEO-M9N modules (same NMEA protocol). // Re-export the driver types -pub use neo6m::ubx::generate_init_sequence; -pub use neo6m::NmeaParser; +pub use ublox_gps::ubx::generate_init_sequence; +pub use ublox_gps::NmeaParser; diff --git a/sensors/blackbox/src/system.rs b/sensors/blackbox/src/system.rs index ab86d68..2fc057b 100644 --- a/sensors/blackbox/src/system.rs +++ b/sensors/blackbox/src/system.rs @@ -59,7 +59,7 @@ impl SensorManager { } /// Get GPS fix (convenience method) - pub fn gps_fix(&self) -> &neo6m::GpsFix { + pub fn gps_fix(&self) -> &ublox_gps::GpsFix { self.gps_parser.last_fix() }