Files
meshcore-sar_android/CLAUDE.md

877 lines
28 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 + 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
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
### 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 definition
- `lib/services/tile_cache_service.dart` - getTileProviderForWms() method
**Example**:
```dart
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**:
1. **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)
2. **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`):
```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**:
1. Query WMTS GetCapabilities:
```bash
curl "https://prostor.zgs.gov.si/geowebcache/service/wmts?REQUEST=GetCapabilities&SERVICE=WMTS"
```
2. Find `<TileMatrixSet>` for EPSG:3794
3. Extract `<TopLeftCorner>` (origin)
4. Extract `<ScaleDenominator>` for each `<TileMatrix>` (convert to resolutions)
5. Query WMS GetCapabilities for bounds:
```bash
curl "https://prostor.zgs.gov.si/geowebcache/service/wms?REQUEST=GetCapabilities&SERVICE=WMS"
```
#### Caching Strategy
**Implementation** (`lib/services/tile_cache_service.dart`):
```dart
FMTCTileProvider getTileProviderForWms(MapLayer layer) {
return _store.getTileProvider(
loadingStrategy: BrowseLoadingStrategy.cacheFirst,
cachedValidDuration: const Duration(days: 30),
);
}
```
**Behavior**:
1. **Cache First**: Check local cache before network request
2. **30-Day Validity**: Tiles expire after 30 days (suitable for aerial imagery that updates infrequently)
3. **Automatic Caching**: All viewed tiles automatically saved to ObjectBox database
4. **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'`:
1. **Slovenian Aerial 2024 layer** in the base layer selector
2. **Cadastral Parcels overlay toggle** in map options
3. **Forest Roads overlay toggle** in map options
4. **WMS Overlays section divider** and header
```dart
// 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`):
```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**:
```dart
MapOptions(
// CRITICAL: Use layer's CRS, not default EPSG:3857
crs: _currentLayer.crs ?? const Epsg3857(),
// ... other options
)
```
#### Troubleshooting
**RangeError: Invalid value: Not in inclusive range 0..15: 16**:
- **Cause**: Map zoom level exceeds the number of resolutions defined in Slovenian CRS
- **Fix**: Zoom level is automatically clamped when switching to WMS layers
- **Implementation**:
- Layer switching clamps zoom to `layer.maxZoom` if current zoom exceeds it
- Layer loading from settings clamps `_savedMapZoom` to layer's maximum
- Prevents crash when switching from high-zoom layer (19+) to WMS layer (15 max)
**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, not `NetworkTileProvider()` or custom providers
- **Verify**: Check `tile_cache_service.dart:75` is being called
**Layer Not Appearing**:
- **Cause 1**: Layer name mismatch (e.g., `DOF_2024` vs `pregledovalnik: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.crs` in 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
1. **Find Layer in GetCapabilities**:
```bash
curl "https://prostor.zgs.gov.si/geowebcache/service/wms?REQUEST=GetCapabilities" | grep "<Name>"
```
2. **Add to MapLayer** (`lib/models/map_layer.dart`):
```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,
);
}
```
3. **Verify CRS Support**: Ensure layer supports EPSG:3794 in GetCapabilities
4. **Test**: Check for 400 errors, verify tiles load correctly
#### Technical Notes
**Why Not Use WMTS Instead of WMS?**
- `flutter_map` has excellent WMS support via `WMSTileLayerOptions`
- 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: slovenianCrs` is 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](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)
- [EPSG:3794 Reference](https://epsg.io/3794) - Slovenian CRS definition
- [Proj4dart Package](https://pub.dev/packages/proj4dart) - Coordinate transformation library
- [OGC WMS Specification](https://www.ogc.org/standards/wms) - 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_DATA` exposes all radio traffic (diagnostic feature)