Files
meshcore-sar_android/CLAUDE.md
Janez T 021ce21cbe Refactor SAR marker handling and add template management
- Updated CompassSarList and DetailedCompassDialog to use marker.displayName instead of marker.type.displayName.
- Enhanced DrawingLayer and DrawingMarkersLayer to support simple mode for drawing visibility and interaction.
- Added toggle switches in DrawingToolbar for showing/hiding received drawings and SAR markers.
- Modified MapMarkers to utilize custom emojis and display names for markers.
- Introduced RecipientSelectorSheet for selecting message recipients with search functionality.
- Refactored SarUpdateSheet to use SAR templates instead of marker types, allowing for emoji and name customization.
- Created SarTemplateEditDialog for adding and editing SAR templates with color selection and preview.
2025-10-21 23:44:49 +02:00

617 lines
18 KiB
Markdown

# 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:<emoji>:<latitude>,<longitude>:<optional_message>
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:<json>
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_<timestamp>.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<T>` or `ParseResult<T>`
**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)