This file provides guidance to WARP (warp.dev) when working with code in this repository.
MeshCore Wardrive is a Flutter Android application for mapping MeshCore mesh network coverage in real-time. It tracks GPS location, sends ping requests via LoRa companion devices (USB/Bluetooth), listens for observer responses via MQTT, and visualizes coverage on an interactive map.
Version: 1.0.17
Minimum Android: API 21 (Android 5.0)
Flutter SDK: 3.10.0+
# Get dependencies
flutter pub get
# Run on connected device (development mode)
flutter run
# Run tests
flutter test
# Analyze code for issues
flutter analyze
# Generate app icons (after changing icon image)
flutter pub run flutter_launcher_icons# Build debug APK
flutter build apk --debug
# Build release APK
flutter build apk --release
# Output: build/app/outputs/flutter-apk/app-release.apk# Install on connected device
flutter installGPS → LocationService → DatabaseService (SQLite)
↓
Manual/Auto ping trigger
↓
LoRaCompanionService (USB/Bluetooth) → Physical LoRa device
↓
LoRa broadcast to mesh
↓
Observers hear ping → Publish to MQTT broker
↓
MQTT listener (in LoRaCompanionService)
↓
Correlate response with GPS sample → Update pingSuccess
↓
AggregationService → Coverage grid calculation
↓
MapScreen (visualization)
LocationService (lib/services/location_service.dart)
- Manages GPS tracking with foreground service
- Coordinates auto-ping based on distance intervals
- Saves samples to database with ping results
- Broadcasts position updates and ping events
LoRaCompanionService (lib/services/lora_companion_service.dart)
- Handles USB/Bluetooth connections to LoRa devices
- Implements MeshCore binary protocol (see
meshcore_protocol.dart) - Sends ping commands to physical LoRa radio
- Listens to MQTT for observer responses
- Correlates ping IDs with responses to determine success/failure
- Tracks discovered repeaters during wardriving
DatabaseService (lib/services/database_service.dart)
- SQLite database for persisting GPS samples
- Schema includes: position, timestamp, geohash, rssi, snr, pingSuccess
- Indexes on geohash and timestamp for query performance
AggregationService (lib/services/aggregation_service.dart)
- Aggregates GPS samples into coverage grid squares (geohash-based)
- Implements time-weighted scoring: newer samples get higher weight
- Handles contradiction detection: recent samples override old ones
- Calculates success rate (received/lost) per coverage area
- Generates color coding based on success rate or age
MeshCoreProtocol (lib/services/meshcore_protocol.dart)
- Binary protocol parser for companion radio communication
- Supports USB mode (framed with
>+ length) and BLE mode (unwrapped) - Handles commands: send ping, get contacts, scan repeaters, etc.
- Parses responses: contact info, advertisements, battery status
Sample (lib/models/models.dart)
- Individual GPS point with optional ping data
- Fields: id, position (LatLng), timestamp, geohash, rssi, snr, pingSuccess
- pingSuccess: true (observer heard), false (dead zone), null (GPS-only)
Coverage (lib/models/models.dart)
- Aggregated coverage area (geohash square)
- Tracks received (weighted successful pings) and lost (weighted failed pings)
- Contains list of repeater IDs that responded in this area
Repeater (lib/models/models.dart)
- Mesh network repeater/node information
- Fields: id (public key prefix), position, elevation, name, rssi, snr
Configurable precision for coverage grid (lib/utils/geohash_utils.dart):
- Precision 4: ~20km × 20km (regional)
- Precision 5: ~5km × 5km (city)
- Precision 6: ~1.2km × 610m (default, neighborhood)
- Precision 7: ~153m × 153m (street-level)
- Precision 8: ~38m × 19m (building-level)
Default coverage precision is 6. Users can adjust in settings.
Default: 805 meters (0.5 miles). Adjustable in app settings or LocationService._pingIntervalMeters.
Default: 20 seconds. Located in LocationService._handleAutoPing() → loraCompanion.ping(timeoutSeconds: 20).
Default broker: mqtt.meshcore.io. Configurable in LoRaCompanionService (supports custom brokers).
Default: 6. Adjustable in app settings, affects map visualization granularity.
MeshCore MQTT Authentication (see MESHCORE_AUTH_SETUP.md):
- Uses Ed25519 signature-based authentication
- Public/private key pair stored securely on device (
flutter_secure_storage) - JWT token generation with signed payloads
- Username format:
v1_{PUBLIC_KEY_HEX}
Encryption: Uses pointycastle package for Ed25519 cryptography.
Minimal test coverage currently exists. When adding tests:
- Use
flutter testto run - Test files in
test/directory - Mock LoRa/MQTT connections for unit tests
- Consider integration tests with simulated MQTT broker
Auto-ping only triggers when:
- GPS tracking is active
- Auto-ping toggle is enabled
- LoRa device is connected (USB or Bluetooth)
- Sufficient distance traveled since last ping
Each ping has a unique 8-character ID. When LoRa device broadcasts, observers respond via MQTT with the ping ID. The app correlates MQTT messages containing the ping ID to mark the sample as successful.
The aggregation system uses time-based weighting:
- Recent samples (< 1 day): full weight (1.0)
- Week-old samples: 0.8 weight
- Month-old samples: 0.5 weight
- Older samples: 0.2 weight
Contradictions: If newer samples show opposite results (e.g., dead zone vs covered), older samples are reduced to 0.1 weight.
Required for background GPS tracking on Android 8+. Displays persistent notification when tracking is active. Uses flutter_foreground_task package.
- USB: Preferred, more stable, less battery drain. Requires OTG cable and data-capable USB cable.
- Bluetooth: More convenient but higher latency. Device must be paired in Android settings first.
'<' (start marker) | 2-byte length (LE) | command code | payload
Unwrapped frames sent directly via UART characteristic (no start marker or length prefix).
CMD_SEND_CONTROL_DATA (55) + subtype DISCOVER_REQ (0x8) + 8-char ping ID
Topic: meshcore/{IATA}/{OBSERVER_KEY}/packets
Payload should contain the ping ID and optionally rssi, snr, observer position.
- Make code changes in
lib/ - Test on device:
flutter run(hot reload enabled) - Verify no issues:
flutter analyze - Run tests:
flutter test - Build release:
flutter build apk --releasewhen ready
- Check location permissions: "Allow all the time" required for background tracking
- Verify GPS is enabled on device
- Check foreground service is running (notification visible)
- USB: Verify cable is data-capable, not charge-only
- Bluetooth: Ensure device is paired in Android settings first
- Check device name matches expected patterns (contains "lora", "meshtastic", etc.)
- Verify MQTT broker connection is active
- Check LoRa device is actually transmitting (view in debug logs)
- Confirm you're in range of active observers
- Validate MQTT topic subscription pattern matches observer publish topics
- Set "Ignore Repeater Prefix" in settings to filter out your own portable repeater
lib/
├── main.dart # App entry, theme configuration
├── models/
│ └── models.dart # Data classes (Sample, Coverage, Repeater, Edge)
├── screens/
│ ├── map_screen.dart # Main UI with map, controls, settings
│ ├── debug_log_screen.dart # Debug terminal for logs
│ └── debug_diagnostics_screen.dart # Advanced diagnostics
├── services/
│ ├── location_service.dart # GPS tracking & auto-ping orchestration
│ ├── lora_companion_service.dart # LoRa device communication & MQTT
│ ├── meshcore_protocol.dart # Binary protocol parser
│ ├── database_service.dart # SQLite persistence
│ ├── aggregation_service.dart # Coverage calculation engine
│ ├── upload_service.dart # Web map upload (future feature)
│ ├── settings_service.dart # User preferences
│ ├── debug_log_service.dart # Debug logging
│ └── persistent_debug_logger.dart # Persistent log storage
└── utils/
└── geohash_utils.dart # Geohash encoding/decoding utilities
- flutter_map: Interactive map display (OpenStreetMap tiles)
- geolocator: GPS positioning
- flutter_foreground_task: Background location service
- usb_serial: USB connection to LoRa devices
- flutter_blue_plus: Bluetooth Low Energy communication
- sqflite: Local SQLite database
- geohash_plus: Geohash encoding/decoding
- pointycastle: Cryptography (Ed25519 signatures)
- http: MQTT over WebSocket (may use mqtt_client in future)
- Follow standard Dart/Flutter conventions
- Use
analysis_options.yaml(includespackage:flutter_lints/flutter.yaml) - Prefix private fields/methods with underscore (
_) - Use broadcast streams for multi-subscriber event propagation
- Prefer async/await over raw Futures
- Use
setState()for UI updates in StatefulWidgets - Store persistent data in SQLite, transient settings in SharedPreferences
- Secure credentials in FlutterSecureStorage
README.md: User-facing setup and featuresQUICKSTART.md: First-time user guideLORA_COMPANION_GUIDE.md: Detailed LoRa device setup and MQTT configurationMESHCORE_AUTH_SETUP.md: Ed25519 authentication for MQTT brokerCHANGELOG.md: Version history