Files
meshcore-sar_android/CLAUDE.md

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 run command
  • 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 list
  • 2: Send text message
  • 39: Request telemetry

Response Codes (Device → Client)

  • 3: Contact information
  • 7: Message received
  • 0x8B (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 coordinates
  • S:🔥:46.0570,14.5060 - Fire detected
  • S:🏕️: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

  1. BLE device sends message via TX characteristic
  2. MeshCoreBleService._onDataReceived() parses binary data
  3. Calls onMessageReceived callback
  4. ConnectionProvider receives message
  5. AppProvider enhances message (check for SAR format)
  6. MessagesProvider.addMessage() stores message
  7. 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.chat contacts 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:

  1. OpenStreetMap (default)

    • URL: https://tile.openstreetmap.org/{z}/{x}/{y}.png
    • Max zoom: 19
    • Best for street-level navigation
  2. OpenTopoMap

    • URL: https://a.tile.opentopomap.org/{z}/{x}/{y}.png
    • Max zoom: 17
    • Shows topographic features, elevation contours
  3. ESRI World Imagery

    • URL: https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}
    • Max zoom: 19
    • Satellite imagery

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:

  1. User taps SAR marker message
  2. MapProvider.navigateToLocation() called
  3. Target location and zoom stored in provider
  4. Tab switches to Map
  5. MapTab._handleMapNavigation() moves map
  6. MapProvider.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 configuration
  • ios/Podfile: CocoaPods dependencies
  • ios/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 configuration
  • android/app/build.gradle: App-level build configuration
  • android/build.gradle: Project-level build configuration

Common Development Tasks

Adding a New BLE Command

  1. Add command code to lib/services/meshcore_constants.dart:

    static const int cmdYourCommand = 42;
    
  2. 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());
    }
    
  3. Handle response in _onDataReceived():

    case MeshCoreConstants.respYourResponse:
      // Parse response data
      onYourCallback?.call(data);
      break;
    

Adding a New SAR Marker Type

  1. Update enum in lib/models/sar_marker.dart:

    enum SarMarkerType {
      // existing types...
      yourType('🆕', 'Your Type');
    }
    
  2. Add to parser in lib/utils/sar_message_parser.dart:

    case '🆕':
      return SarMarkerType.yourType;
    
  3. Add color in lib/widgets/map_markers.dart:

    case SarMarkerType.yourType:
      return Colors.purple;
    
  4. 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

  1. 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,
    );
    
  2. Add to list:

    static const List<MapLayer> allLayers = [
      openStreetMap,
      openTopoMap,
      esriWorldImagery,
      yourLayer, // Add here
    ];
    
  3. 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 boundary for 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:

  1. Message Sending: UI to compose and send messages
  2. Route Recording: Track team member paths over time
  3. Geofencing: Alerts when team members enter/exit areas
  4. Voice Notes: Attach audio to SAR markers
  5. Team Chat: Real-time chat between team members
  6. Mission Plans: Pre-loaded search patterns
  7. Statistics: Coverage analysis, search time tracking

References

Contact

For questions or contributions, please refer to the project repository or contact the development team.