|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +MeshMapper Flutter App is a cross-platform wardriving application for MeshCore mesh network devices. It's a Flutter port of the [MeshMapper WebClient](https://github.com/MeshMapper/MeshMapper_WebClient), supporting Android, iOS, and Web (Chrome/Edge only). |
| 8 | + |
| 9 | +**Purpose**: Connect to MeshCore devices via Bluetooth Low Energy, send GPS-tagged pings to the `#wardriving` channel, track repeater echoes, and post coverage data to the MeshMapper API for community mesh mapping. |
| 10 | + |
| 11 | +**Tech Stack**: Flutter 3.2.0+, Dart 3.2.0+, Hive for local storage, Provider for state management |
| 12 | + |
| 13 | +## Common Commands |
| 14 | + |
| 15 | +### Development |
| 16 | +```bash |
| 17 | +# Install dependencies |
| 18 | +flutter pub get |
| 19 | + |
| 20 | +# Run code generation (for Hive models) |
| 21 | +flutter pub run build_runner build --delete-conflicting-outputs |
| 22 | + |
| 23 | +# Run the app |
| 24 | +flutter run # Android/iOS (defaults to connected device) |
| 25 | +flutter run -d chrome # Web (Chrome required for Web Bluetooth) |
| 26 | +flutter run -d chrome --web-browser-flag="--disable-web-security" # For local testing with CORS |
| 27 | + |
| 28 | +# Analyze code |
| 29 | +flutter analyze |
| 30 | + |
| 31 | +# Run tests |
| 32 | +flutter test |
| 33 | + |
| 34 | +# Run a single test file |
| 35 | +flutter test test/services/gps_service_test.dart |
| 36 | +``` |
| 37 | + |
| 38 | +### Building for Release |
| 39 | +```bash |
| 40 | +# Android APK |
| 41 | +flutter build apk --release |
| 42 | + |
| 43 | +# iOS |
| 44 | +flutter build ios --release |
| 45 | + |
| 46 | +# Web |
| 47 | +flutter build web --release |
| 48 | +``` |
| 49 | + |
| 50 | +### Debug Logging |
| 51 | +- Web: Add `?debug=1` to URL to enable debug logging in browser console |
| 52 | +- Mobile: Debug logging always enabled via `debugPrint()` |
| 53 | + |
| 54 | +## Architecture |
| 55 | + |
| 56 | +### Service-Oriented Architecture |
| 57 | + |
| 58 | +The app uses a layered service architecture with clear separation of concerns: |
| 59 | + |
| 60 | +**Bluetooth Abstraction Layer** (`lib/services/bluetooth/`): |
| 61 | +- `BluetoothService`: Abstract interface for BLE operations |
| 62 | +- `MobileBluetoothService`: Android/iOS implementation using `flutter_blue_plus` |
| 63 | +- `WebBluetoothService`: Web implementation using `flutter_web_bluetooth` |
| 64 | +- Platform selection happens at runtime in `main.dart` using `kIsWeb` |
| 65 | + |
| 66 | +**MeshCore Protocol Layer** (`lib/services/meshcore/`): |
| 67 | +- `MeshCoreConnection`: Implements the 10-step connection workflow and MeshCore companion protocol |
| 68 | +- `PacketParser`: Binary packet parsing with BufferReader/Writer utilities |
| 69 | +- `UnifiedRxHandler`: Routes ALL incoming BLE packets to TX tracking or RX logging |
| 70 | +- `TxTracker`: Detects repeater echoes during 7-second window after sending ping |
| 71 | +- `RxLogger`: Logs passive mesh observations, buffers by repeater ID |
| 72 | +- `ChannelService`: Channel hash computation and management |
| 73 | +- `CryptoService`: SHA-256 channel key derivation, AES-ECB message decryption |
| 74 | + |
| 75 | +**Application Services** (`lib/services/`): |
| 76 | +- `GpsService`: GPS tracking with 150km geofence from Ottawa (45.4215, -75.6972) |
| 77 | +- `PingService`: TX/RX ping orchestration, coordinates with TxTracker/RxLogger |
| 78 | +- `ApiQueueService`: Hive-based persistent upload queue with batch POST and retry logic |
| 79 | +- `ApiService`: HTTP client for MeshMapper API endpoints |
| 80 | +- `DeviceModelService`: Loads `assets/device-models.json` and auto-configures TX power |
| 81 | + |
| 82 | +**State Management** (`lib/providers/`): |
| 83 | +- `AppStateProvider`: Single ChangeNotifier for all app state using Provider pattern |
| 84 | +- All UI updates happen via `notifyListeners()` after state mutations |
| 85 | + |
| 86 | +### 10-Step Connection Workflow |
| 87 | + |
| 88 | +Critical safety: The connection sequence MUST complete in order. See `docs/CONNECTION_WORKFLOW.md` for details. |
| 89 | + |
| 90 | +1. **BLE GATT Connect**: Platform-specific BLE connection |
| 91 | +2. **Protocol Handshake**: `deviceQuery()` with protocol version |
| 92 | +3. **Device Info**: `getDeviceName()`, `getPublicKey()`, `getDeviceSettings()` |
| 93 | +4. **Auto-Power Configuration**: Parse manufacturer string, match against `device-models.json`, set power level |
| 94 | +5. **Time Sync**: `sendTime()` syncs device clock |
| 95 | +6. **API Slot Acquisition**: POST to `/capacitycheck.php` to reserve API slot |
| 96 | +7. **Channel Setup**: Create or use existing `#wardriving` channel |
| 97 | +8. **GPS Init**: Acquire GPS lock |
| 98 | +9. **Start Unified RX Handler**: Begin processing ALL incoming packets |
| 99 | +10. **Connected State**: Ready for wardriving |
| 100 | + |
| 101 | +**Critical Power Safety**: PA amplifier devices (30dBm, 33dBm) require specific input power levels. Incorrect values can damage hardware. Auto-power selection from device-models.json is MANDATORY for these devices. |
| 102 | + |
| 103 | +### Unified RX Handler Architecture |
| 104 | + |
| 105 | +**Key Principle**: Accept ALL incoming BLE packets, parse metadata ONCE at entry point, then route to specialized handlers. Never filter by header at entry. |
| 106 | + |
| 107 | +**Flow**: |
| 108 | +``` |
| 109 | +BLE LogRxData Event |
| 110 | + ↓ |
| 111 | +UnifiedRxHandler._handleLogRxData() |
| 112 | + ↓ |
| 113 | +Parse PacketMetadata (ONCE) |
| 114 | + ↓ |
| 115 | + ┌────┴────┐ |
| 116 | + ↓ ↓ |
| 117 | +TX Track RX Log |
| 118 | +(echoes) (passive) |
| 119 | + ↓ ↓ |
| 120 | +7s window Buffer by repeater |
| 121 | + ↓ ↓ |
| 122 | +Update UI Flush to API queue |
| 123 | +``` |
| 124 | + |
| 125 | +**TX Tracking** (during 7-second window after ping): |
| 126 | +- Validates: GROUP_TEXT header, RSSI < -30dBm, channel hash match, decrypted message match, path length > 0 |
| 127 | +- Deduplicates by first hop (repeater ID), keeps best SNR |
| 128 | +- Updates UI with repeater counts |
| 129 | + |
| 130 | +**RX Logging** (continuous passive monitoring): |
| 131 | +- Validates: path length > 0, valid GPS, channel hash in allowed list, decrypts successfully, 90% printable chars, RSSI < -30dBm |
| 132 | +- Buffers per repeater with GPS coordinates |
| 133 | +- Flushes to API queue on 25m movement OR 30s timeout |
| 134 | +- Maintains in-memory log (max 100 entries) for UI |
| 135 | + |
| 136 | +### GPS & Geofencing |
| 137 | + |
| 138 | +- Uses `geolocator` package with high accuracy and continuous tracking |
| 139 | +- **Ottawa Geofence**: 150km radius from Parliament Hill (45.4215, -75.6972) - hard boundary enforced client-side AND server-side |
| 140 | +- **Min Distance Filter**: 25m between pings prevents spam |
| 141 | +- **GPS Freshness**: Manual pings tolerate 60s old GPS, auto pings require fresh acquisition |
| 142 | + |
| 143 | +### API Queue System |
| 144 | + |
| 145 | +Two independent data flows (TX pings, RX observations) merge into unified API batch queue: |
| 146 | + |
| 147 | +- **Storage**: Hive-based persistent queue survives app restarts |
| 148 | +- **Batch Size**: Max 50 messages, auto-flush at 10 items or 30 seconds |
| 149 | +- **Payload Format**: `[{type:"TX"|"RX", lat, lon, who, power, heard, session_id, iatacode}]` |
| 150 | +- **Authentication**: API key in JSON body (NOT query string) |
| 151 | +- **Retry Logic**: Exponential backoff on failures |
| 152 | + |
| 153 | +## Critical Protocol Details |
| 154 | + |
| 155 | +### BLE Service UUIDs (MeshCore Companion Protocol) |
| 156 | +- Service: `6E400001-B5A3-F393-E0A9-E50E24DCCA9E` |
| 157 | +- RX Characteristic: `6E400002-B5A3-F393-E0A9-E50E24DCCA9E` (write to device) |
| 158 | +- TX Characteristic: `6E400003-B5A3-F393-E0A9-E50E24DCCA9E` (notifications from device) |
| 159 | + |
| 160 | +### Channel Key Derivation |
| 161 | +- **Hashtag channels** (`#wardriving`, `#testing`, `#ottawa`, `#wartest`): SHA-256 hash of channel name |
| 162 | +- **Public channel** (`Public`): Fixed key `8b3387e9c5cdea6ac9e5edbaa115cd72` |
| 163 | +- Channel hash (PSK identifier) computed at startup for `#wardriving` |
| 164 | +- Used for repeater echo detection and message decryption (AES-ECB via pointycastle) |
| 165 | + |
| 166 | +### Packet Structure |
| 167 | +- Custom binary protocol with header byte (0x11 = GROUP_TEXT, 0x21 = ADVERT) |
| 168 | +- Path encoding: hop count + repeater IDs (4 bytes each) |
| 169 | +- SNR/RSSI metadata in BLE event payload |
| 170 | +- Encrypted message payload (AES-ECB with channel key) |
| 171 | + |
| 172 | +## Platform-Specific Notes |
| 173 | + |
| 174 | +### Web (Chrome/Edge only) |
| 175 | +- Safari NOT supported (no Web Bluetooth API) |
| 176 | +- Uses `flutter_web_bluetooth` package |
| 177 | +- Debug logging enabled via URL parameter `?debug=1` |
| 178 | +- CORS issues during local development - use `--web-browser-flag="--disable-web-security"` |
| 179 | + |
| 180 | +### Android |
| 181 | +- Requires permissions: Bluetooth, Location (for BLE scanning) |
| 182 | +- minSdkVersion: 21 (Android 5.0+) |
| 183 | +- Background location permission for continuous tracking |
| 184 | +- Uses `flutter_blue_plus` package |
| 185 | + |
| 186 | +### iOS |
| 187 | +- Requires Info.plist entries: NSBluetoothAlwaysUsageDescription, NSLocationWhenInUseUsageDescription |
| 188 | +- Deployment target: 12.0+ |
| 189 | +- Background modes: bluetooth-central, location |
| 190 | +- Uses `flutter_blue_plus` package |
| 191 | + |
| 192 | +## Development Workflow Requirements |
| 193 | + |
| 194 | +### Debug Logging Convention (MANDATORY) |
| 195 | +All debug log messages MUST include a tag in square brackets: |
| 196 | + |
| 197 | +```dart |
| 198 | +debugLog('[BLE] Connection established'); |
| 199 | +debugLog('[GPS] Fresh position acquired: lat=45.12345'); |
| 200 | +debugLog('[PING] Sending ping to channel 2'); |
| 201 | +debugLog('[RX] Buffering observation for repeater 0xABCD1234'); |
| 202 | +``` |
| 203 | + |
| 204 | +**Required Tags**: `[BLE]`, `[CONN]`, `[GPS]`, `[PING]`, `[API QUEUE]`, `[RX BATCH]`, `[RX]`, `[TX]`, `[DECRYPT]`, `[CRYPTO]`, `[UI]`, `[CHANNEL]`, `[TIMER]`, `[WAKE LOCK]`, `[GEOFENCE]`, `[CAPACITY]`, `[AUTO]`, `[INIT]`, `[MODEL]`, `[MAP]` |
| 205 | + |
| 206 | +Never log without a tag. See `docs/DEVELOPMENT_REQUIREMENTS.md` for complete list. |
| 207 | + |
| 208 | +### Documentation Update Requirements |
| 209 | + |
| 210 | +When modifying code, you MUST also update relevant documentation: |
| 211 | + |
| 212 | +1. **Connection workflow changes** → Update `docs/CONNECTION_WORKFLOW.md` (steps, states, error handling) |
| 213 | +2. **Ping/auto-ping changes** → Update `docs/PING_WORKFLOW.md` (validation, lifecycle, UI impacts) |
| 214 | +3. **New status messages** → Add to `docs/STATUS_MESSAGES.md` (exact text, trigger, color) |
| 215 | +4. **Architectural changes** → Update this CLAUDE.md file |
| 216 | + |
| 217 | +### Code Style |
| 218 | +- Use Dart documentation comments (`///`) for public classes and methods |
| 219 | +- Prefer `async`/`await` over `.then()` chains |
| 220 | +- Always wrap async operations in `try`/`catch` blocks |
| 221 | +- Use `debugError()` for logging errors before handling |
| 222 | +- State mutations via `AppStateProvider` with `notifyListeners()` |
| 223 | + |
| 224 | +## Device Model Database |
| 225 | + |
| 226 | +**File**: `assets/device-models.json` |
| 227 | + |
| 228 | +Contains 30+ MeshCore device variants with manufacturer strings, TX power levels, and platform info: |
| 229 | +- **Ikoka**: Stick, Nano, Handheld (22dBm, 30dBm, 33dBm variants) |
| 230 | +- **Heltec**: V2, V3, V4, Wireless Tracker, MeshPocket |
| 231 | +- **RAK**: 4631, 3x72 |
| 232 | +- **LilyGo**: T-Echo, T-Deck, T-Beam, T-LoRa |
| 233 | +- **Seeed**: Wio E5, T1000, Xiao variants |
| 234 | + |
| 235 | +**Detection Flow**: |
| 236 | +1. `deviceQuery()` returns manufacturer string (e.g., "Ikoka Stick-E22-30dBm (Xiao_nrf52)nightly-e31c46f") |
| 237 | +2. `parseDeviceModel()` strips build suffix ("nightly-COMMIT") |
| 238 | +3. `findDeviceConfig()` searches database for exact/partial match |
| 239 | +4. `autoSetPowerLevel()` configures radio power automatically |
| 240 | + |
| 241 | +**Critical Safety**: PA amplifier models MUST use specific power values: |
| 242 | +- 33dBm models: txPower=9, power=2.0 |
| 243 | +- 30dBm models: txPower=20, power=1.0 |
| 244 | +- Standard (22dBm): txPower=22, power=0.3 |
| 245 | + |
| 246 | +## MeshMapper API Endpoints |
| 247 | + |
| 248 | +**Base URL**: `https://yow.meshmapper.net/` |
| 249 | + |
| 250 | +- **POST /capacitycheck.php**: Acquire API slot before connecting |
| 251 | + - Payload: `{iatacode: "YOW", apikey: "...", apiver: "1.6.0"}` |
| 252 | + - Response: `{valid: true|false, reason: null|"outofdate"}` |
| 253 | + |
| 254 | +- **POST /wardriving-api.php**: Batch upload TX/RX coverage data |
| 255 | + - Payload: `[{type:"TX"|"RX", lat, lon, who, power, heard, session_id, iatacode}]` |
| 256 | + - Auth: API key in JSON body (NOT query string) |
| 257 | + |
| 258 | +## Common Pitfalls |
| 259 | + |
| 260 | +1. **Unified RX Handler accepts ALL packets** - No header filtering at entry point. Session log tracking filters headers internally. |
| 261 | + |
| 262 | +2. **GPS freshness varies by context** - Manual pings tolerate 60s old GPS data, auto pings force fresh acquisition. |
| 263 | + |
| 264 | +3. **Control locking during ping lifecycle** - `sendPing()` disables all controls until API post completes. Must call unlock in ALL code paths (success/error). |
| 265 | + |
| 266 | +4. **Disconnect cleanup order matters** - Flush API queue → Release capacity → Delete channel → Close BLE → Clear timers/GPS/wake locks → Reset state. Out-of-order causes errors. |
| 267 | + |
| 268 | +5. **Platform-specific Bluetooth imports** - Use conditional exports (bluetooth_service.dart exports platform-specific implementation). Never import platform-specific files directly. |
| 269 | + |
| 270 | +6. **Hive model generation required** - After modifying `@HiveType` classes, run `flutter pub run build_runner build --delete-conflicting-outputs`. |
| 271 | + |
| 272 | +7. **Web Bluetooth requires HTTPS** - Development uses `flutter run -d chrome` which works, but production deployment needs HTTPS. |
| 273 | + |
| 274 | +## Key File Reference |
| 275 | + |
| 276 | +- `lib/main.dart` - App entry point, platform detection, theme |
| 277 | +- `lib/providers/app_state_provider.dart` - Global state management |
| 278 | +- `lib/services/meshcore/connection.dart` - 10-step connection workflow, MeshCore protocol |
| 279 | +- `lib/services/meshcore/unified_rx_handler.dart` - Packet routing (TX vs RX) |
| 280 | +- `lib/services/meshcore/tx_tracker.dart` - Repeater echo detection (7s window) |
| 281 | +- `lib/services/meshcore/rx_logger.dart` - Passive observation logging |
| 282 | +- `lib/services/ping_service.dart` - TX/RX ping orchestration |
| 283 | +- `lib/services/gps_service.dart` - GPS tracking and geofencing |
| 284 | +- `lib/services/api_queue_service.dart` - Persistent upload queue |
| 285 | +- `lib/services/device_model_service.dart` - Auto-power configuration |
| 286 | +- `assets/device-models.json` - Device database (30+ models) |
| 287 | +- `docs/CONNECTION_WORKFLOW.md` - Connection sequence documentation |
| 288 | +- `docs/PING_WORKFLOW.md` - Ping lifecycle documentation |
| 289 | +- `docs/UNIFIED_RX_HANDLER_PLAN.md` - RX handler architecture |
| 290 | +- `docs/DEVELOPMENT_REQUIREMENTS.md` - Coding standards |
| 291 | + |
| 292 | +## Original WebClient Reference |
| 293 | + |
| 294 | +This Flutter app is a port of the JavaScript-based WebClient. When implementing features: |
| 295 | + |
| 296 | +1. Reference original implementation in `PORTED_APP/content/wardrive.js` (5200+ lines) |
| 297 | +2. Follow same architectural patterns (connection workflow, API queue, channel crypto) |
| 298 | +3. Maintain feature parity where possible |
| 299 | +4. Key differences: |
| 300 | + - Flutter uses Provider for state (not global state object) |
| 301 | + - Hive for persistent storage (not IndexedDB) |
| 302 | + - Platform-specific BLE implementations (not just Web Bluetooth) |
| 303 | + - Dart type safety (not dynamic JavaScript) |
| 304 | + |
| 305 | +The `PORTED_APP/.github/copilot-instructions.md` file contains detailed documentation of the original WebClient architecture and is a valuable reference for understanding intended behavior. |
0 commit comments