Files
meshcore-sar_android/CLAUDE.md
Janez T a4dc85d130 Add WMS support and measurement functionality
- Implemented WMS layer handling for Slovenian aerial imagery and overlays for cadastral parcels and forest roads.
- Added distance measurement feature allowing users to measure distances on the map with long press interactions.
- Updated localization files for Croatian, Italian, and Slovenian languages to include new measurement and WMS-related strings.
- Enhanced the map layer model to support WMS-specific properties.
- Introduced a new tile provider for debugging WMS requests.
- Updated the drawing toolbar to include measurement mode and clear measurement functionality.
- Integrated SharedPreferences to persist overlay visibility states across app sessions.
- Added utility functions for handling the Slovenian coordinate reference system (EPSG:3794).
2025-10-27 21:49:58 +01:00

844 lines
27 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)
**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.
#### 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
**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
**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)