# CLAUDE.md - MeshCore SAR Technical Reference AI assistant guide for the MeshCore SAR Flutter application. ## Table of Contents 1. [Critical Development Rules](#critical-development-rules) 2. [Quick Reference](#quick-reference) 3. [Project Structure](#project-structure) 4. [Protocol Reference](#protocol-reference) 5. [Architecture](#architecture) 6. [Common Tasks](#common-tasks) 7. [Build & Troubleshooting](#build--troubleshooting) --- ## Critical Development Rules ### Flutter Process Management **NEVER run or kill Flutter processes:** - ❌ DO NOT execute `flutter run` command - ❌ DO NOT kill Flutter processes (`pkill flutter`, `killall flutter`) - ✅ User manages Flutter development server - only make code changes - ✅ Hot reload happens automatically when files are saved ### Key Architecture Constraints - **Echo Detection**: Uses DJB2-style hash (NO crypto dependency needed) - **Channel Messages**: NO ACKs (fire-and-forget flood routing) - **Direct Messages**: Automatic ACKs via `PUSH_CODE_SEND_CONFIRMED` - **SAR Markers**: MUST be sent to rooms, NOT public channel --- ## Quick Reference **Project**: Flutter Mobile App (iOS 13+, Android API 21+) **Architecture**: Provider pattern + BLE communication **Protocol**: MeshCore BLE Companion Radio (Little Endian) **Repository**: https://github.com/meshcore-dev/meshcore.js ### BLE UUIDs ``` Service: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E RX (write): 6E400002-B5A3-F393-E0A9-E50E24DCCA9E TX (notify): 6E400003-B5A3-F393-E0A9-E50E24DCCA9E ``` ### Key Dependencies ```yaml flutter_blue_plus: ^2.0.0 # BLE communication flutter_map: ^8.2.2 # Mapping provider: ^6.1.0 # State management geolocator: ^14.0.2 # GPS tracking ``` --- ## Project Structure ``` lib/ ├── l10n/ # Internationalization (en, hr, sl) │ ├── app_localizations.dart # Generated (DO NOT EDIT) │ └── app_*.arb # Translation files ├── models/ # Data models │ ├── contact.dart, message.dart, sar_marker.dart │ ├── map_drawing.dart # Drawing shapes │ └── sent_message_tracker.dart # Echo detection ├── services/ # Business logic │ ├── meshcore_ble_service.dart # BLE coordinator │ ├── protocol/ # Frame parsing & building │ ├── ble/ # Connection, commands, responses │ ├── location_tracking_service.dart # GPS + broadcast │ ├── map_marker_service.dart # Marker generation │ └── validation_service.dart # Form validation ├── providers/ # State management │ ├── connection_provider.dart # BLE state │ ├── contacts_provider.dart # Contact list │ ├── messages_provider.dart # Messages + SAR │ ├── map_provider.dart # Map navigation │ ├── drawing_provider.dart # Map drawings │ └── app_provider.dart # Coordinator ├── screens/ # UI screens │ └── (home, messages, contacts, map, settings, etc.) ├── widgets/ # Reusable components │ ├── map_markers.dart │ └── map/ # Map-specific widgets └── utils/ # Utilities ├── sar_message_parser.dart └── drawing_message_parser.dart ``` --- ## Protocol Reference ### Message Formats #### SAR Marker Format ``` S::,: Emojis: 🧑 or 👤 → Found Person 🔥 → Fire Location 🏕️ or ⛺ → Staging Area Examples: S:🧑:37.7749,-122.4194 S:🔥:40.7128,-74.0060:Large wildfire spreading rapidly ``` #### Map Drawing Format ``` D: Line: D:{"t":0,"c":0,"p":[lat1,lon1,lat2,lon2]} Rectangle: D:{"t":1,"c":1,"b":[topLat,topLon,botLat,botLon]} Fields: t = type (0=line, 1=rectangle) c = color index (0-7: red,blue,green,yellow,orange,purple,pink,cyan) p = points array (flat) b = bounds array (rectangles only) ``` #### Cayenne LPP Format ``` [Channel][Type][Data...] Types: 0x88 (136) → GPS: lat/lon/alt (int32/10000, int32/10000, int32/100) 0x67 (103) → Temperature (int16/10 for °C) 0x02 (2) → Analog Input (uint16/100 for volts/battery) ``` ### Command Quick Reference | Code | Command | Response | Description | |------|---------|----------|-------------| | 1 | CMD_APP_START | RESP_CODE_SELF_INFO (5) | First command after connection | | 2 | CMD_SEND_TXT_MSG | RESP_CODE_SENT (6) | Send DM to contact (with ACK) | | 3 | CMD_SEND_CHANNEL_TXT_MSG | RESP_CODE_SENT (6) | Broadcast to channel (NO ACK) | | 4 | CMD_GET_CONTACTS | RESP_CODE_CONTACTS_START (2) | Sync contact list | | 10 | CMD_SYNC_NEXT_MESSAGE | RESP_CODE_CONTACT_MSG_RECV (7) | Pull next message from queue | | 22 | CMD_DEVICE_QUERY | RESP_CODE_DEVICE_INFO (13) | Get device firmware/hardware info | | 26 | CMD_SEND_LOGIN | PUSH_CODE_LOGIN_SUCCESS (0x85) | Login to room server | ### Push Notifications (Async) | Code | Name | Purpose | |------|------|---------| | 0x80 | PUSH_CODE_ADVERT | New advertisement received | | 0x82 | PUSH_CODE_SEND_CONFIRMED | Message ACK received (DMs only) | | 0x83 | PUSH_CODE_MSG_WAITING | New message in queue → sync it | | 0x88 | PUSH_CODE_LOG_RX_DATA | **Raw packet capture (always-on diagnostic)** | | 0x85 | PUSH_CODE_LOGIN_SUCCESS | Room login successful | | 0x86 | PUSH_CODE_LOGIN_FAIL | Room login failed | ### Constants #### Contact Types (ADV_TYPE) ``` 0 = ADV_TYPE_NONE # Unknown/invalid 1 = ADV_TYPE_CHAT # Team member (shown on map) 2 = ADV_TYPE_REPEATER # Network repeater 3 = ADV_TYPE_ROOM # Communication room/server ``` #### Message Types (TXT_TYPE) ``` 0 = TXT_TYPE_PLAIN # Plain text 1 = TXT_TYPE_CLI_DATA # CLI command 2 = TXT_TYPE_SIGNED_PLAIN # Text + 4-byte pubkey signature ``` #### Error Codes (ERR_CODE) ``` 1 = ERR_CODE_UNSUPPORTED_CMD 2 = ERR_CODE_NOT_FOUND 3 = ERR_CODE_TABLE_FULL 4 = ERR_CODE_BAD_STATE 5 = ERR_CODE_FILE_IO_ERROR 6 = ERR_CODE_ILLEGAL_ARG ``` --- ## Architecture ### Provider Hierarchy ``` MultiProvider ├── ConnectionProvider # BLE connection state ├── ContactsProvider # Contact list management ├── MessagesProvider # Messages + SAR markers ├── MapProvider # Map navigation state ├── DrawingProvider # Map drawing state └── AppProvider # Coordinator (wires everything) ``` ### Event Flow ``` BLE Device → MeshCoreBleService → ConnectionProvider → AppProvider ↓ ContactsProvider MessagesProvider DrawingProvider ↓ UI ``` ### Contact Path Status **outPathLen** indicates routing mode: - **-1 (0xFF)**: Path unknown → **Flood mode** (broadcast to all neighbors) - **0**: Direct connection → **Direct mode** (zero hops, best quality) - **1-64**: Multi-hop path → **Direct mode** (uses learned routing) **Map Display**: Only `ContactType.chat` contacts with valid GPS coordinates shown. ### Channels vs. Rooms **Channels** (numeric identifiers): - Channel 0 = "Public Channel" (default broadcast) - **Ephemeral** - messages NOT persisted - Uses `CMD_SEND_CHANNEL_TXT_MSG` (3) - **NO ACKs** - fire-and-forget flood routing - Pre-configured with secret: `8b3387e9c5cdea6ac9e5edbaa115cd72` (hex) **Rooms** (ADV_TYPE_ROOM contacts): - Named contacts with public keys - **Persistent storage** on room server - Uses `CMD_SEND_TXT_MSG` (2) with room's public key - **Automatic ACKs** when messages stored - Login via `CMD_SEND_LOGIN` (26) to receive stored messages **SAR Routing**: SAR markers MUST go to rooms for reliable delivery. ### Room Login Protocol ``` 1. Send CMD_SEND_LOGIN (26) ↓ 2. Radio generates sender_timestamp & sync_since ↓ 3. Room validates password, stores sync_since ↓ 4. Receive PUSH_CODE_LOGIN_SUCCESS (0x85) or PUSH_CODE_LOGIN_FAIL (0x86) ↓ 5. Room auto-pushes messages (1200ms intervals) ↓ 6. Receive PUSH_CODE_MSG_WAITING (0x83) → call CMD_SYNC_NEXT_MESSAGE (10) ``` **CRITICAL**: Do NOT call `syncAllMessages()` after login. Wait for push notifications. --- ## Echo Detection Feature ### Overview **Status**: ✅ Fully implemented and production-ready **Purpose**: Detect when broadcast messages are rebroadcast by mesh nodes ### How It Works 1. **Packet Identification** (via `PUSH_CODE_LOG_RX_DATA` 0x88): ``` Packet Structure (PAYLOAD_TYPE_GRP_TXT 0x05): [Byte 0] = Header (route + payload type + version) [Byte 1] = Path length [Byte 2] = Sender's node hash (first byte of public key) ✅ [Byte 3+] = Rest of path + encrypted payload ``` 2. **On Connection**: - Receive `RESP_CODE_SELF_INFO` with our public key - Extract **our node hash** (byte 0 of public key) - Store for packet matching 3. **Sending a Message**: - Call `trackSentMessage(messageId)` when user sends channel message - Status = "pending" (waiting for packet capture) 4. **Packet Capture** (50-200ms later): - Radio sends `PUSH_CODE_LOG_RX_DATA` with raw packet - Extract sender hash from packet[2] - If sender hash == our node hash → **This is our packet!** - Calculate DJB2-style hash of entire packet (no crypto dependency) - Store tracker by packet hash for echo detection 5. **Echo Detection**: - Future `PUSH_CODE_LOG_RX_DATA` packets arrive - Calculate packet hash and lookup in tracker map (O(1)) - If match found → **Echo detected!** Increment counter - Track SNR/RSSI signature for path diversity - UI auto-updates to show "Rebroadcast by X nodes" ### Implementation Details **Files**: - `lib/models/sent_message_tracker.dart` - Tracker model - `lib/models/message.dart` - echoCount, firstEchoAt fields - `lib/services/ble/ble_response_handler.dart` - Detection engine - `lib/services/meshcore_ble_service.dart` - Callback wiring - `lib/providers/connection_provider.dart` - Provider callback - `lib/providers/messages_provider.dart` - handleMessageEcho() **Performance**: - Packet ID: O(1) - byte comparison at offset 2 - Hash calc: O(n) where n = packet length (~38-200 bytes) - Echo lookup: O(1) via HashMap - Memory: ~150 bytes/message, max 100 messages = ~15KB - TTL: 5-minute expiry, auto-cleanup **Limitations**: - Echo count ≠ exact receiver count (one node can echo multiple times) - Only detects echoes while app connected - Network topology dependent (dense networks → more echoes) --- ## Common Tasks ### Adding a New BLE Command 1. Add code to `lib/services/meshcore_constants.dart` 2. Build frame in `lib/services/protocol/frame_builder.dart` 3. Add API method in `lib/services/meshcore_ble_service.dart` 4. Parse response in `lib/services/protocol/frame_parser.dart` 5. Handle in `lib/services/ble/ble_response_handler.dart` 6. Add callback in `lib/services/meshcore_ble_service.dart` ### Adding a New SAR Marker Type 1. Update enum in `lib/models/sar_marker.dart` 2. Add parser logic in `lib/utils/sar_message_parser.dart` 3. Add color mapping in `lib/widgets/map_markers.dart` 4. Update handling in `lib/providers/messages_provider.dart` ### Adding Localized Strings 1. **Add to `lib/l10n/app_en.arb`**: ```json { "myNewString": "My new text", "@myNewString": { "description": "What this string is for" } } ``` 2. **Add translations to `app_hr.arb` and `app_sl.arb`** 3. **Generate**: `flutter gen-l10n` 4. **Use in code**: ```dart import '../l10n/app_localizations.dart'; Text(AppLocalizations.of(context)!.myNewString) ``` **IMPORTANT**: Always use relative import path `'../l10n/app_localizations.dart'` ### Importing/Exporting Map Tiles The app supports importing and exporting cached map tiles using the `flutter_map_tile_caching` library's archive format (`.fmtc` files). **Export Workflow**: 1. Navigate to Map Management screen 2. Tap "Export Tiles to File" 3. Archive is created in temporary directory with gzip compression 4. System share sheet appears (iOS/Android) 5. Choose where to save: Files app, email, cloud storage, etc. 6. File named: `meshcore_tiles_.fmtc` **Import Workflow**: 1. Navigate to Map Management screen 2. Tap "Import Tiles from File" 3. Select `.fmtc` archive file 4. Tiles are merged with existing cache 5. Cache statistics refreshed automatically **API Usage**: ```dart // Export current cache to file final tileCount = await tileCacheService.exportStore( '/path/to/export.fmtc', ); // Import tiles from archive (with merge strategy) final result = await tileCacheService.importStore( '/path/to/import.fmtc', storeNames: null, // null = import all stores strategy: ImportConflictStrategy.merge, // default: merge ); // Preview stores in archive before importing final stores = await tileCacheService.listArchiveStores( '/path/to/archive.fmtc', ); ``` **Use Cases**: - **Backup**: Export tiles before clearing cache or reinstalling app - **Sharing**: Pre-download maps once, distribute to team devices - **Disaster Recovery**: Restore offline maps after device reset - **Bandwidth Saving**: Reduce cellular data usage by sharing cached tiles **Technical Details**: - Archive format: `.fmtc` (FMTC native format) - Compression: gzip (built-in by FMTC) - Import strategy: Merge (tiles combined with existing cache) - Export location: Temporary directory → system share sheet - Import source: User-selected via `file_picker` package - Conflict handling: Automatic merge of tile stores - Cross-platform: Works on Android and iOS using native share mechanisms **File**: `lib/services/tile_cache_service.dart` - Export/import methods **UI**: `lib/screens/map_management_screen.dart` - Import/export card ### Map Drawing Workflow ``` User selects mode → DrawingProvider.setDrawingMode() ↓ User taps map → DrawingLayer captures touches ↓ Preview rendered → DrawingProvider.getPreviewDrawing() ↓ User completes → Saved to DrawingProvider._drawings ↓ User shares → DrawingToolbar._shareDrawingsToChannel/Room() ↓ BLE send → ConnectionProvider.sendChannelMessage/sendTextMessage() ↓ Receiver parses → DrawingMessageParser.parseDrawingMessage() ↓ Display → DrawingProvider.addReceivedDrawing() ``` --- ## Build & Troubleshooting ### Build Commands ```bash # Dependencies flutter pub get flutter gen-l10n # After ARB file changes # Development flutter run # Debug mode (user controls this) # Hot reload: save file | Hot restart: press 'R' in terminal # Code Quality flutter analyze flutter test dart format lib/ flutter clean # Release flutter build ios --release flutter build ipa flutter build apk --release flutter build appbundle --release ``` ### Common Issues **BLE Connection Failed**: - Check Bluetooth enabled and permissions granted - Verify device in range (<10m) - Check service UUID matches: `6E400001-B5A3-F393-E0A9-E50E24DCCA9E` **MissingPluginException** (after adding dependencies): ```bash cd ios && pod install && cd .. flutter clean flutter pub get flutter run ``` **iOS Pod Install Fails**: ```bash cd ios rm Podfile.lock rm -rf Pods/ pod install --repo-update cd .. ``` **Android Gradle Timeout** - Add to `android/gradle.properties`: ``` org.gradle.daemon=true org.gradle.parallel=true org.gradle.jvmargs=-Xmx4096m ``` ### Performance Tips **BLE**: - Buffer partial packets - Throttle telemetry (max 1/sec per contact) - Use `notifyListeners()` sparingly **Map**: - Cluster markers if >100 visible - Use `RepaintBoundary` for marker widgets - Implement marker virtualization for large datasets **Memory**: - Dispose controllers in `dispose()` methods - Limit message history to 1000 messages - Set tile cache size limits --- ## Services Reference ### LocationTrackingService (Singleton) **Purpose**: GPS tracking + intelligent mesh location broadcasting **Callbacks**: `onPositionUpdate`, `onError`, `onBroadcastSent`, `onTrackingStateChanged` **Thresholds**: - Min distance: 5.0m - Max distance: 100.0m - Min time: 30s **Logic**: - First update → broadcast immediately - ≥100m moved → broadcast immediately - ≥5m + ≥30s → broadcast **File**: `lib/services/location_tracking_service.dart` (501 lines) ### MapMarkerService (Singleton) **Purpose**: Map marker generation + geodesic calculations **Features**: - Pure functions (testable) - Contact markers with battery badge, distance - SAR markers (color-coded by type) - Distance/bearing calculations (Haversine) - "Time ago" formatting **File**: `lib/services/map_marker_service.dart` (518 lines) ### ValidationService (Singleton) **Purpose**: Form validation + input parsing **Returns**: Structured `ValidationResult` or `ParseResult` **Validates**: - Coordinates (lat: -90 to +90, lon: -180 to +180) - Radio params (freq: 137-1020 MHz, bw: 7.8-500 kHz, sf: 5-12, cr: 5-8, tx: -9 to +22 dBm) - Text/names, zoom levels (0-19) **File**: `lib/services/validation_service.dart` (511 lines) --- ## Map Implementation ### Tile Layers 1. **OpenStreetMap** (default) - Max zoom 19 2. **OpenTopoMap** - Max zoom 17, topographic 3. **ESRI World Imagery** - Max zoom 19, satellite ### Offline Caching - Backend: `flutter_map_tile_caching` + ObjectBox - Behavior: `CacheBehavior.cacheFirst`, 30-day validity - Downloads: `RectangleRegion(bounds).download.startForeground()` ### Marker Types **Team Member**: Blue circle, battery badge, name, distance, tap for details **SAR Event**: Color-coded (green=person, red=fire, orange=staging), time ago, tap for details ### Navigation Flow ``` Messages tab → tap SAR marker ↓ MapProvider.navigateToLocation() ↓ Switch to Map tab ↓ MapTab._handleMapNavigation() → animate to location ↓ MapProvider.clearNavigation() ``` --- ## References - [Flutter Documentation](https://docs.flutter.dev/) - [flutter_blue_plus API](https://pub.dev/documentation/flutter_blue_plus/) - [flutter_map Documentation](https://docs.fleaflet.dev/) - [MeshCore Protocol](https://github.com/meshcore-dev/meshcore.js) - [MeshCore FAQ](https://github.com/meshcore-dev/MeshCore/blob/main/docs/faq.md) - [Cayenne LPP Specification](https://developers.mydevices.com/cayenne/docs/lora/#lora-cayenne-low-power-payload) - [Provider Package](https://pub.dev/packages/provider) --- ## Security Considerations - **BLE**: No authentication in current protocol - consider encryption for production - **Permissions**: Request minimum required permissions only - **Logging**: No sensitive data in logs - **Network**: HTTPS for all tile sources - **Raw Packets**: `PUSH_CODE_LOG_RX_DATA` exposes all radio traffic (diagnostic feature)