28 KiB
CLAUDE.md - MeshCore SAR Technical Reference
AI assistant guide for the MeshCore SAR Flutter application.
Table of Contents
- Critical Development Rules
- Quick Reference
- Project Structure
- Protocol Reference
- Architecture
- Common Tasks
- Build & Troubleshooting
Critical Development Rules
Flutter Process Management
NEVER run or kill Flutter processes:
- ❌ DO NOT execute
flutter runcommand - ❌ 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
flutter_blue_plus: ^2.0.0 # BLE communication
flutter_map: ^8.2.2 # Mapping + WMS support
provider: ^6.1.0 # State management
geolocator: ^14.0.2 # GPS tracking
proj4dart: ^2.1.0 # Coordinate transformations (EPSG:3794)
flutter_map_tile_caching: ^10.1.1 # Offline tile caching
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
│ ├── tile_cache_service.dart # Offline tile caching (WMS + standard)
│ └── 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
└── slovenian_crs.dart # EPSG:3794 CRS for WMS
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
-
Packet Identification (via
PUSH_CODE_LOG_RX_DATA0x88):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 -
On Connection:
- Receive
RESP_CODE_SELF_INFOwith our public key - Extract our node hash (byte 0 of public key)
- Store for packet matching
- Receive
-
Sending a Message:
- Call
trackSentMessage(messageId)when user sends channel message - Status = "pending" (waiting for packet capture)
- Call
-
Packet Capture (50-200ms later):
- Radio sends
PUSH_CODE_LOG_RX_DATAwith 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
- Radio sends
-
Echo Detection:
- Future
PUSH_CODE_LOG_RX_DATApackets 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"
- Future
Implementation Details
Files:
lib/models/sent_message_tracker.dart- Tracker modellib/models/message.dart- echoCount, firstEchoAt fieldslib/services/ble/ble_response_handler.dart- Detection enginelib/services/meshcore_ble_service.dart- Callback wiringlib/providers/connection_provider.dart- Provider callbacklib/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
- Add code to
lib/services/meshcore_constants.dart - Build frame in
lib/services/protocol/frame_builder.dart - Add API method in
lib/services/meshcore_ble_service.dart - Parse response in
lib/services/protocol/frame_parser.dart - Handle in
lib/services/ble/ble_response_handler.dart - Add callback in
lib/services/meshcore_ble_service.dart
Adding a New SAR Marker Type
- Update enum in
lib/models/sar_marker.dart - Add parser logic in
lib/utils/sar_message_parser.dart - Add color mapping in
lib/widgets/map_markers.dart - Update handling in
lib/providers/messages_provider.dart
Adding Localized Strings
-
Add to
lib/l10n/app_en.arb:{ "myNewString": "My new text", "@myNewString": { "description": "What this string is for" } } -
Add translations to
app_hr.arbandapp_sl.arb -
Generate:
flutter gen-l10n -
Use in code:
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:
- Navigate to Map Management screen
- Tap "Export Tiles to File"
- Archive is created in temporary directory with gzip compression
- System share sheet appears (iOS/Android)
- Choose where to save: Files app, email, cloud storage, etc.
- File named:
meshcore_tiles_<timestamp>.fmtc
Import Workflow:
- Navigate to Map Management screen
- Tap "Import Tiles from File"
- Select
.fmtcarchive file - Tiles are merged with existing cache
- Cache statistics refreshed automatically
API Usage:
// 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_pickerpackage - 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
# 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):
cd ios && pod install && cd ..
flutter clean
flutter pub get
flutter run
iOS Pod Install Fails:
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
RepaintBoundaryfor 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
- OpenStreetMap (default) - Max zoom 19
- OpenTopoMap - Max zoom 17, topographic
- ESRI World Imagery - Max zoom 19, satellite
WMS Support
Purpose: Integration with Web Map Service (WMS) providers for specialized mapping data
Slovenian CRS (EPSG:3794):
- Projection: Transverse Mercator (Slovenia 1996 / Slovene National Grid)
- Ellipsoid: GRS80
- Usage: Slovenian government WMS/WMTS services (prostor.zgs.gov.si)
- Zoom levels: 0-15 (GeoWebCache tile matrix)
- Bounds: X: 373217.65-695777.65m, Y: 31118.30-246158.30m
- Origin: Top-left (373217.65, 246158.30)
- Resolutions: Calculated from scale denominators (420m/px at zoom 0 to 0.028m/px at zoom 15)
- Language Filter: Only shown when app language is Slovenian (sl) or Croatian (hr)
Tile Caching:
- All WMS layers (base + overlays) use
flutter_map_tile_caching - Cache strategy:
cacheFirst(offline-first with 30-day validity) - Same caching infrastructure as standard tile layers
Files:
lib/utils/slovenian_crs.dart- EPSG:3794 CRS definitionlib/services/tile_cache_service.dart- getTileProviderForWms() method
Example:
import 'package:meshcore_sar_app/utils/slovenian_crs.dart';
// All WMS layers automatically use the cached tile provider
TileLayer(
wmsOptions: WMSTileLayerOptions(
baseUrl: 'https://prostor.zgs.gov.si/geowebcache/service/wms?',
layers: ['pregledovalnik:DOF_2024'],
format: 'image/jpeg',
crs: slovenianCrs,
),
tileProvider: tileCacheService.getTileProviderForWms(layer),
)
WMS Implementation Details
Overview
The app integrates Slovenian government WMS (Web Map Service) layers using a custom EPSG:3794 coordinate reference system. This enables high-resolution aerial imagery and specialized overlays (cadastral parcels, forest roads) for SAR operations in Slovenia.
Language-Based Filtering: WMS layers are only shown to users when the app language is set to Slovenian (sl) or Croatian (hr), since these layers only cover Slovenia geographically and are irrelevant to users in other regions.
Architecture
Layer Types:
-
Base Layer: Slovenian Aerial Imagery 2024 (DOF_2024)
- Source:
https://prostor.zgs.gov.si/geowebcache/service/wms - Format: JPEG (better compression for aerial photos)
- Transparency: False (opaque base layer)
- Max zoom: 15 (GeoWebCache limit)
- Source:
-
Overlay Layers: Cadastral Parcels, Forest Roads
- Format: PNG (supports transparency)
- Transparency: True (overlays on base layer)
- Max zoom: 19
Coordinate System (EPSG:3794):
- Official name: Slovenia 1996 / Slovene National Grid
- Projection: Transverse Mercator
- Parameters:
+proj=tmerc +lat_0=0 +lon_0=15 +k=0.9999 +x_0=500000 +y_0=-5000000 +ellps=GRS80 - Why needed: Slovenian government services use this instead of standard Web Mercator (EPSG:3857)
Tile Grid Alignment: The critical challenge with WMS layers is aligning the client-side tile grid with the server's GeoWebCache configuration. Misalignment causes 400 Bad Request errors.
Correct Configuration (lib/utils/slovenian_crs.dart):
// Origin MUST match WMTS TileMatrixSet TopLeftCorner
final origin = Point<double>(373217.6542445397, 246158.298050262);
// Bounds MUST match WMS capabilities extent
final bounds = Rect.fromLTRB(
373217.65, // min X (west)
31118.30, // min Y (south)
695777.65, // max X (east)
246158.30, // max Y (north)
);
// Resolutions MUST be calculated from scale denominators
// Formula: resolution = scaleDenominator * 0.00028 (OGC standard)
final resolutions = [
420.0, // Zoom 0: 1,500,000 * 0.00028
280.0, // Zoom 1: 1,000,000 * 0.00028
// ... through zoom 15
];
How to Get Correct Values:
- Query WMTS GetCapabilities:
curl "https://prostor.zgs.gov.si/geowebcache/service/wmts?REQUEST=GetCapabilities&SERVICE=WMTS" - Find
<TileMatrixSet>for EPSG:3794 - Extract
<TopLeftCorner>(origin) - Extract
<ScaleDenominator>for each<TileMatrix>(convert to resolutions) - Query WMS GetCapabilities for bounds:
curl "https://prostor.zgs.gov.si/geowebcache/service/wms?REQUEST=GetCapabilities&SERVICE=WMS"
Caching Strategy
Implementation (lib/services/tile_cache_service.dart):
FMTCTileProvider getTileProviderForWms(MapLayer layer) {
return _store.getTileProvider(
loadingStrategy: BrowseLoadingStrategy.cacheFirst,
cachedValidDuration: const Duration(days: 30),
);
}
Behavior:
- Cache First: Check local cache before network request
- 30-Day Validity: Tiles expire after 30 days (suitable for aerial imagery that updates infrequently)
- Automatic Caching: All viewed tiles automatically saved to ObjectBox database
- Offline Support: Cached tiles available when device offline
Storage Location:
- Backend: ObjectBox (embedded database)
- Store name: 'meshcore_tiles' (shared with standard tile layers)
- Format: Binary tile data + metadata (URL, timestamp, headers)
Usage in Map
Language Filtering (lib/screens/map_tab.dart):
The following UI elements are only visible when localeName == 'sl' || localeName == 'hr':
- Slovenian Aerial 2024 layer in the base layer selector
- Cadastral Parcels overlay toggle in map options
- Forest Roads overlay toggle in map options
- WMS Overlays section divider and header
// Implementation pattern
final locale = AppLocalizations.of(context)!.localeName;
if (locale == 'sl' || locale == 'hr') {
// Show WMS layer option
ListTile(
title: Text(_slovenianAerialLayer.name),
// ...
);
}
This ensures users in other regions (English, German, French, Spanish, Italian) don't see geographically irrelevant layers.
Base Layer (lib/screens/map_tab.dart):
if (_currentLayer.isWms && _currentLayer.crs != null) {
TileLayer(
wmsOptions: WMSTileLayerOptions(
baseUrl: _currentLayer.wmsBaseUrl!,
layers: _currentLayer.wmsLayers ?? [],
format: _currentLayer.wmsFormat ?? 'image/jpeg',
crs: _currentLayer.crs!, // EPSG:3794
),
tileProvider: _tileCache.getTileProviderForWms(_currentLayer),
maxZoom: _currentLayer.maxZoom, // 15 for Slovenian layers
);
}
Map Options:
MapOptions(
// CRITICAL: Use layer's CRS, not default EPSG:3857
crs: _currentLayer.crs ?? const Epsg3857(),
// ... other options
)
Troubleshooting
400 Bad Request Errors:
- Cause: Tile grid misalignment (wrong origin, bounds, or resolutions)
- Fix: Verify values match WMTS GetCapabilities exactly
- Debug: Check WMS URL in error logs for out-of-bounds coordinates
Tiles Not Caching:
- Cause: Using wrong tile provider (e.g., NetworkTileProvider instead of FMTC)
- Fix: Ensure
getTileProviderForWms()is used, notNetworkTileProvider()or custom providers - Verify: Check
tile_cache_service.dart:75is being called
Layer Not Appearing:
- Cause 1: Layer name mismatch (e.g.,
DOF_2024vspregledovalnik:DOF_2024) - Cause 2: Wrong CRS in MapOptions (using EPSG:3857 instead of EPSG:3794)
- Fix: Verify layer name in WMS GetCapabilities, ensure
crs: _currentLayer.crsin MapOptions
Performance Issues:
- Issue: Slow tile loading on first view
- Expected: WMS tile generation is slower than pre-rendered tiles (100-500ms per tile)
- Mitigation: Pre-download regions using Map Management screen
Adding New WMS Layers
-
Find Layer in GetCapabilities:
curl "https://prostor.zgs.gov.si/geowebcache/service/wms?REQUEST=GetCapabilities" | grep "<Name>" -
Add to MapLayer (
lib/models/map_layer.dart):static MapLayer getMyNewLayer(Crs slovenianCrs) { return MapLayer( type: MapLayerType.wmsBase, // or create new enum value name: 'My New Layer', urlTemplate: '', // Not used for WMS attribution: '© Data Provider', maxZoom: 15, // Match GeoWebCache capability isWms: true, wmsBaseUrl: 'https://prostor.zgs.gov.si/geowebcache/service/wms?', wmsLayers: const ['workspace:layername'], wmsFormat: 'image/png', // or 'image/jpeg' wmsTransparent: true, // true for overlays, false for base crs: slovenianCrs, ); } -
Verify CRS Support: Ensure layer supports EPSG:3794 in GetCapabilities
-
Test: Check for 400 errors, verify tiles load correctly
Technical Notes
Why Not Use WMTS Instead of WMS?
flutter_maphas excellent WMS support viaWMSTileLayerOptions- WMS and WMTS use same GeoWebCache backend (identical tiles)
- WMS is simpler to configure (no manual tile URL template)
- Caching abstracts the protocol difference
Proj4dart Integration:
- Handles coordinate transformation from EPSG:4326 (GPS) to EPSG:3794 (map)
- Projection registered once at app startup:
proj4.Projection.add('EPSG:3794', ...) - Flutter Map uses it automatically when
crs: slovenianCrsis set
Memory Considerations:
- Each CRS instance stores transformation matrices and bounds
- Use singleton pattern:
final Crs slovenianCrs = getSlovenianCrs(); - Shared across all WMS layers
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
- flutter_blue_plus API
- flutter_map Documentation
- MeshCore Protocol
- MeshCore FAQ
- Cayenne LPP Specification
- Provider Package
- EPSG:3794 Reference - Slovenian CRS definition
- Proj4dart Package - Coordinate transformation library
- OGC WMS Specification - Web Map Service standard
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_DATAexposes all radio traffic (diagnostic feature)