20 KiB
CLAUDE.md - MeshCore SAR Technical Reference
This document provides technical details for AI assistants (like Claude) working with this codebase.
⚠️ IMPORTANT: Flutter Development Rules
NEVER run or kill Flutter processes:
- DO NOT execute
flutter runcommand - DO NOT kill Flutter processes (e.g.,
pkill flutter,killall flutter) - The user manages the Flutter development server themselves
- Only make code changes and let the user trigger hot reload manually
Hot reload happens automatically when you save files - the user has their own Flutter process running and will see changes instantly.
Project Overview
Type: Flutter Mobile Application Purpose: Search and Rescue (SAR) operations with MeshCore mesh network devices Architecture: Provider-based state management with BLE communication Target Platforms: iOS 13+, Android 5.0+ (API 21+)
Project Structure
lib/
├── models/ # Data models
│ ├── contact.dart # Contact with telemetry
│ ├── contact_telemetry.dart # GPS, battery, temperature
│ ├── message.dart # Messages and SAR markers
│ ├── sar_marker.dart # SAR tactical markers
│ ├── device_info.dart # BLE device connection state
│ └── map_layer.dart # Map tile layer definitions
├── services/ # Business logic services
│ ├── meshcore_ble_service.dart # BLE communication
│ ├── meshcore_constants.dart # Protocol constants
│ ├── buffer_reader.dart # Binary protocol reader
│ ├── buffer_writer.dart # Binary protocol writer
│ ├── cayenne_lpp_parser.dart # Telemetry decoder
│ └── tile_cache_service.dart # Offline map tiles
├── providers/ # State management
│ ├── connection_provider.dart # BLE connection state
│ ├── contacts_provider.dart # Contact list management
│ ├── messages_provider.dart # Message history + SAR markers
│ ├── map_provider.dart # Map navigation state
│ └── app_provider.dart # Coordinator provider
├── screens/ # UI screens
│ ├── home_screen.dart # Main screen with tabs
│ ├── messages_tab.dart # Message list view
│ ├── contacts_tab.dart # Contact list view
│ └── map_tab.dart # Interactive map view
├── widgets/ # Reusable UI components
│ └── map_markers.dart # Custom map marker widgets
├── utils/ # Utilities
│ └── sar_message_parser.dart # Parse S:<emoji>:lat,lon format
└── main.dart # App entry point
Key Technologies
Core Dependencies
- flutter_blue_plus (^2.0.0): BLE communication
- flutter_map (^8.2.2): Interactive mapping with OpenStreetMap
- flutter_map_tile_caching (^10.1.1): Offline map tile storage
- provider (^6.1.0): State management
- latlong2 (^0.9.0): GPS coordinate handling
- geolocator (^14.0.2): Precise GPS location tracking
- permission_handler (^12.0.1): Runtime permissions
MeshCore Protocol
The app implements the MeshCore BLE protocol based on https://github.com/meshcore-dev/meshcore.js
BLE Service: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E
RX Characteristic (write): 6E400002-B5A3-F393-E0A9-E50E24DCCA9E
TX Characteristic (notify): 6E400003-B5A3-F393-E0A9-E50E24DCCA9E
Command Codes (Client → Device)
4: Get contacts list2: Send text message39: Request telemetry
Response Codes (Device → Client)
3: Contact information7: Message received0x8B(139): Telemetry response (Cayenne LPP format)
Binary Protocol Format
All protocol messages use little-endian byte order.
Get Contacts Request:
[0x04] - Command code
Contact Response:
[0x03] - Response code
[32 bytes] - Public key
[1 byte] - Contact type (0=none, 1=chat, 2=repeater, 3=room)
[64 bytes] - Advertised name (null-terminated string)
[4 bytes] - Latitude (int32, divide by 10000 for degrees)
[4 bytes] - Longitude (int32, divide by 10000 for degrees)
Message Received:
[0x07] - Response code
[1 byte] - Message type (0=contact, 1=channel)
[4 bytes] - Sender public key prefix
[4 bytes] - Recipient public key prefix
[2 bytes] - Text length (uint16)
[N bytes] - UTF-8 text
Send Text Message:
[0x02] - Command code
[32 bytes] - Recipient public key
[2 bytes] - Text length (uint16)
[N bytes] - UTF-8 text
Request Telemetry:
[0x27] (39) - Command code
[32 bytes] - Contact public key
Telemetry Response:
[0x8B] (139) - Response code
[4 bytes] - Contact public key prefix
[N bytes] - Cayenne LPP payload
Cayenne LPP Format
Telemetry data uses Cayenne Low Power Payload format:
[Channel] [Type] [Data...]
Supported Types:
136(0x88): GPS Location- 4 bytes: Latitude (int32, divide by 10000)
- 4 bytes: Longitude (int32, divide by 10000)
- 4 bytes: Altitude (int32, divide by 100)
103(0x67): Temperature Sensor- 2 bytes: Temperature (int16, divide by 10 for °C)
2(0x02): Analog Input (used for battery voltage)- 2 bytes: Value (uint16, divide by 100 for volts)
SAR Message Format
Special tactical markers embedded in messages:
S:<emoji>:<latitude>,<longitude>
Recognized Emojis:
🧑or👤: Found Person🔥: Fire Location🏕️or⛺: Staging Area
Examples:
S:🧑:46.0569,14.5058- Person found at coordinatesS:🔥:46.0570,14.5060- Fire detectedS:🏕️:46.0571,14.5062- Base camp location
Parsing Rules:
- Must start with
S: - Single emoji character after first colon
- Comma-separated lat,lon after second colon
- Coordinates can be negative (e.g.,
-12.3456) - No spaces allowed in format
State Management Architecture
Provider Hierarchy
MultiProvider
├── ConnectionProvider # BLE connection state
├── ContactsProvider # Contact list
├── MessagesProvider # Messages + SAR markers
├── MapProvider # Map navigation
└── AppProvider # Coordinator (uses all above)
Event Flow
BLE Device → MeshCoreBleService → ConnectionProvider → AppProvider
↓
ContactsProvider
MessagesProvider
↓
UI
Example: Receiving a Message
- BLE device sends message via TX characteristic
MeshCoreBleService._onDataReceived()parses binary data- Calls
onMessageReceivedcallback ConnectionProviderreceives messageAppProviderenhances message (check for SAR format)MessagesProvider.addMessage()stores message- UI rebuilds via
Consumer<MessagesProvider>
Contact Types
enum ContactType {
none(0), // Unknown/invalid
chat(1), // Team member (shown on map)
repeater(2), // Network repeater node
room(3), // Communication channel/room
}
Map Display Rules:
- Only
ContactType.chatcontacts with valid GPS are shown on map - Repeaters and rooms are listed in Contacts tab but not mapped
Map Implementation
Tile Layers
Three tile sources are supported via MapLayer enum:
-
OpenStreetMap (default)
- URL:
https://tile.openstreetmap.org/{z}/{x}/{y}.png - Max zoom: 19
- Best for street-level navigation
- URL:
-
OpenTopoMap
- URL:
https://a.tile.opentopomap.org/{z}/{x}/{y}.png - Max zoom: 17
- Shows topographic features, elevation contours
- URL:
-
ESRI World Imagery
- URL:
https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x} - Max zoom: 19
- Satellite imagery
- URL:
Offline Tile Caching
Uses flutter_map_tile_caching with ObjectBox backend:
// Initialize cache
await FMTCObjectBoxBackend().initialise();
final store = FMTCStore('meshcore_sar_tiles');
await store.manage.create();
// Download region
final region = RectangleRegion(bounds);
await store.download.startForeground(region: region);
Cache Behavior:
CacheBehavior.cacheFirst: Use cached tiles if available- 30-day validity period
- Automatic background updates when online
Map Markers
Team Member Markers (Blue):
- CircleAvatar with person icon
- Battery percentage badge at top
- Name label at bottom
- Tap to show details dialog
SAR Event Markers (Color-coded):
- Found Person: Green with 🧑
- Fire: Red with 🔥
- Staging Area: Orange with 🏕️
- Time ago label at top
- Type label at bottom
- Tap to show details dialog
Map Navigation
Navigation from Messages Tab:
- User taps SAR marker message
MapProvider.navigateToLocation()called- Target location and zoom stored in provider
- Tab switches to Map
MapTab._handleMapNavigation()moves mapMapProvider.clearNavigation()resets state
Zoom State Preservation:
- Current zoom stored in
MapProvider - Maintained across tab switches
- Updated on user zoom gestures
User Location Tracking
The app tracks the user's precise GPS location in real-time:
Permission Setup (iOS Info.plist):
<key>NSLocationWhenInUseUsageDescription</key>
<string>MeshCore SAR needs location access to display team members and SAR markers on the map</string>
<key>NSLocationTemporaryPreciseUsageDescription</key>
<string>MeshCore SAR needs precise location for accurate positioning in SAR operations</string>
<key>NSLocationDefaultAccuracyReduced</key>
<false/>
Implementation (lib/screens/map_tab.dart):
Position? _currentPosition;
bool _trackingLocation = false;
// Request permission and start tracking
final position = await Geolocator.getCurrentPosition(
locationSettings: const LocationSettings(
accuracy: LocationAccuracy.best,
distanceFilter: 0,
),
);
// Listen to continuous position updates
Geolocator.getPositionStream(
locationSettings: const LocationSettings(
accuracy: LocationAccuracy.best,
distanceFilter: 10, // Update every 10 meters
),
).listen((Position position) {
setState(() => _currentPosition = position);
if (_trackingLocation) {
// Auto-center map on user location
_mapController.move(
LatLng(position.latitude, position.longitude),
_mapController.camera.zoom
);
}
});
User Location Marker:
- Blue pulsing circle showing current position
- Navigation icon indicating heading
- Tap location button to center map on user
- Tap again to enable tracking mode (map follows user movement)
Map Legend
Collapsible Legend (lib/screens/map_tab.dart):
- Shows counts of team members and SAR markers
- Click to collapse to compact view
- Click again to expand
- Positioned in top-right corner
bool _showLegend = true;
GestureDetector(
onTap: () => setState(() => _showLegend = !_showLegend),
child: _showLegend
? _MapLegend(/* full legend with all counts */)
: Card(
child: Column([
Text('Legend'),
Icon(Icons.expand_more),
]),
),
)
Detailed Compass Dialog
Location Display (lib/screens/map_tab.dart):
- Ultra-compact location format display with tap-to-toggle formats
- Tap location text to switch between DD and DMS formats
- No labels - just the coordinates for maximum space efficiency
- Two formats available:
- DD (Decimal Degrees): 5 decimal places (e.g., "46.05690, 14.50580")
- DMS (Degrees, Minutes, Seconds): Traditional format (e.g., "46°03'24.84"N, 14°30'20.88"E")
- Monospace font for coordinate values
Dialog Controls:
- Tap anywhere outside interactive elements to close dialog
- Tap location coordinates to toggle format
- No close button - cleaner, more compact interface
class _LocationFormatToggle extends StatefulWidget {
bool _showDMS = false;
@override
Widget build(BuildContext context) {
final displayText = _showDMS
? 'DMS format with newline'
: 'DD format single line';
return GestureDetector(
onTap: () => setState(() => _showDMS = !_showDMS),
behavior: HitTestBehavior.opaque,
child: Container(/* compact display */),
);
}
}
Building and Development
Development Commands
# Install dependencies
flutter pub get
# Run in debug mode
flutter run
# Run with specific device
flutter run -d <device-id>
# Hot reload (during debug)
# Press 'r' in terminal
# Hot restart (during debug)
# Press 'R' in terminal
# Analyze code
flutter analyze
# Run tests
flutter test
# Format code
dart format lib/
# Clean build
flutter clean
iOS Build
# Open Xcode workspace
open ios/Runner.xcworkspace
# Build from command line
flutter build ios --release
# Create IPA (requires signing)
flutter build ipa
Key iOS Files:
ios/Runner/Info.plist: Permissions and app configurationios/Podfile: CocoaPods dependenciesios/Runner.xcodeproj: Xcode project
Android Build
# Debug APK
flutter build apk --debug
# Release APK
flutter build apk --release
# App Bundle (for Play Store)
flutter build appbundle --release
# Split APKs by ABI
flutter build apk --split-per-abi
Key Android Files:
android/app/src/main/AndroidManifest.xml: Permissions and app configurationandroid/app/build.gradle: App-level build configurationandroid/build.gradle: Project-level build configuration
Common Development Tasks
Adding a New BLE Command
-
Add command code to
lib/services/meshcore_constants.dart:static const int cmdYourCommand = 42; -
Create command method in
lib/services/meshcore_ble_service.dart:Future<void> yourCommand() async { final writer = BufferWriter(); writer.writeByte(MeshCoreConstants.cmdYourCommand); await _sendCommand(writer.toBytes()); } -
Handle response in
_onDataReceived():case MeshCoreConstants.respYourResponse: // Parse response data onYourCallback?.call(data); break;
Adding a New SAR Marker Type
-
Update enum in
lib/models/sar_marker.dart:enum SarMarkerType { // existing types... yourType('🆕', 'Your Type'); } -
Add to parser in
lib/utils/sar_message_parser.dart:case '🆕': return SarMarkerType.yourType; -
Add color in
lib/widgets/map_markers.dart:case SarMarkerType.yourType: return Colors.purple; -
Update providers in
lib/providers/messages_provider.dart:List<SarMarker> get yourTypeMarkers => sarMarkers.where((m) => m.type == SarMarkerType.yourType).toList();
Adding a New Map Layer
-
Add to model in
lib/models/map_layer.dart:static const yourLayer = MapLayer( type: MapLayerType.yourLayer, name: 'Your Layer', urlTemplate: 'https://your-tile-server/{z}/{x}/{y}.png', attribution: '© Your Attribution', maxZoom: 19, ); -
Add to list:
static const List<MapLayer> allLayers = [ openStreetMap, openTopoMap, esriWorldImagery, yourLayer, // Add here ]; -
Layer automatically appears in layer selector UI
Testing
Unit Tests
# Run all tests
flutter test
# Run specific test file
flutter test test/widget_test.dart
# Run with coverage
flutter test --coverage
Integration Tests
# Run integration tests
flutter drive --target=test_driver/app.dart
Manual Testing Checklist
BLE Connection:
- Scan discovers MeshCore devices
- Connection successful
- Device info displayed in status bar
- Disconnect works properly
Contacts:
- Contacts load after connection
- Contacts grouped by type
- Telemetry request works
- Battery/GPS displayed correctly
Messages:
- Messages received and displayed
- SAR markers highlighted
- Tap SAR marker navigates to map
- Message timestamps correct
Map:
- Map loads and displays tiles
- Team member markers appear
- SAR markers appear with correct colors
- Layer switching works
- Zoom/pan gestures work
- Marker tap shows details
- Offline tiles load
Troubleshooting
BLE Issues
"Bluetooth adapter not available":
- Check device Bluetooth is on
- Verify permissions granted
- iOS: Check Info.plist has usage descriptions
- Android: Check AndroidManifest.xml has permissions
"Connection failed":
- Device must support BLE
- Check service UUID matches
- Verify device is in range (<10m typically)
- Try scanning again
Runtime Issues
MissingPluginException for geolocator or other plugins:
Example error:
MissingPluginException(No implementation found for method isLocationServiceEnabled
on channel flutter.baseflow.com/geolocator_apple)
This occurs when native plugin implementations aren't properly installed. Common after adding new dependencies.
Solution:
# For iOS
cd ios
pod install
cd ..
# Clean and rebuild
flutter clean
flutter pub get
flutter run
If still failing on iOS:
cd ios
rm Podfile.lock
rm -rf Pods/
pod install
cd ..
flutter clean
flutter pub get
Build Issues
iOS Pod Install Fails:
cd ios
rm Podfile.lock
rm -rf Pods/
pod install --repo-update
cd ..
CocoaPods ObjectBox Version Conflict:
This error occurs when flutter_map_tile_caching updates its ObjectBox dependency but the cached Podfile.lock has an older version:
[!] CocoaPods could not find compatible versions for pod "ObjectBox":
In snapshot (Podfile.lock): ObjectBox (= 1.9.2)
In Podfile: objectbox_flutter_libs depends on ObjectBox (= 4.4.1)
Solution:
# Navigate to iOS directory
cd ios
# Remove cached dependency lock file
rm Podfile.lock
# Remove all installed pods
rm -rf Pods/
# Update CocoaPods repository (this may take a few minutes)
pod repo update
# Reinstall all pods with updated versions
pod install
# Return to project root
cd ..
# Clean Flutter build cache
flutter clean
# Reinstall Flutter dependencies
flutter pub get
# Run the app
flutter run
Alternative solution (if the above doesn't work):
cd ios
rm Podfile.lock
rm -rf Pods/
pod deintegrate
pod cache clean --all
pod setup
pod install
cd ..
flutter clean
flutter pub get
Note: The pod repo update command can take 5-10 minutes as it downloads the entire CocoaPods specifications repository. This is normal.
Android Gradle Timeout:
// android/gradle.properties
org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.jvmargs=-Xmx4096m
Flutter Version Conflicts:
flutter channel stable
flutter upgrade
flutter pub upgrade
Performance Optimization
BLE Communication
- Buffer incoming data to handle partial packets
- Throttle telemetry requests (max 1 per second per contact)
- Use
notifyListeners()sparingly in providers
Map Performance
- Limit visible markers (cluster if >100 markers)
- Use
repaint boundaryfor marker widgets - Implement marker virtualization for large datasets
Memory Management
- Dispose controllers in
dispose()methods - Clear message history after 1000 messages
- Implement tile cache size limits
Security Considerations
- BLE: No authentication in current protocol - add encryption for production
- Permissions: Request minimum required permissions
- Data: No sensitive data should be logged
- Network: Use HTTPS for all tile sources
Future Enhancements
Potential features to add:
- Message Sending: UI to compose and send messages
- Route Recording: Track team member paths over time
- Geofencing: Alerts when team members enter/exit areas
- Voice Notes: Attach audio to SAR markers
- Team Chat: Real-time chat between team members
- Mission Plans: Pre-loaded search patterns
- Statistics: Coverage analysis, search time tracking
References
- Flutter Documentation
- flutter_blue_plus API
- flutter_map Documentation
- MeshCore Protocol
- Cayenne LPP Specification
- Provider Package
Contact
For questions or contributions, please refer to the project repository or contact the development team.