GNSS RTK - Production-Ready GNSS Positioning Library
A production-ready, industrial-grade Flutter library for high-precision GNSS/RTK positioning. Designed for professional GIS and surveying applications that require centimeter-level accuracy.
Features
✅ Multi-Provider Architecture - TCP, UDP, Broadcast, File, Mock
✅ Complete NMEA 0183 Support - GGA, RMC, GSA, GSV, VTG, GST, ZDA, GLL
✅ RTK/DGPS Support - RTK Fixed, RTK Float, DGPS fix quality detection
✅ Multi-Constellation - GPS, GLONASS, Galileo, BeiDou, QZSS
✅ Health Monitoring - Real-time diagnostics and issue detection
✅ Stream-Based - Reactive programming with RxDart
✅ Memory Safe - No memory leaks, proper resource management
✅ Background Support - Foreground service for Android
✅ Production Ready - Tested, documented, and optimized
Installation
Add to your pubspec.yaml:
dependencies:
gnss_rtk: ^1.0.0
Quick Start
Basic Usage
import 'package:gnss_rtk/gnss_rtk.dart';
// Create a mock provider for testing
final provider = MockGnssProvider(
const MockGnssProviderConfig(
mockScenario: MockScenario.stationary,
),
);
// Create GNSS service
final gnssService = GnssService(
provider: provider,
enableHealthMonitoring: true,
);
// Listen to position updates
gnssService.positionStream.listen((fix) {
print('Position: ${fix.latitude}, ${fix.longitude}');
print('Accuracy: ${fix.estimatedAccuracy}m');
print('Fix Quality: ${fix.fixQuality.description}');
});
// Start the service
await gnssService.start();
// Wait for RTK fixed quality
final fix = await gnssService.waitForValidFix(
minimumQuality: FixQuality.rtkFixed,
timeout: Duration(seconds: 60),
);
TCP Provider (Most Common for Industrial GNSS)
final provider = TcpGnssProvider(
TcpGnssProviderConfig(
host: '192.168.1.100',
port: 2947,
reconnectAttempts: 5,
),
);
final gnssService = GnssService(provider: provider);
await gnssService.start();
UDP Provider
final provider = UdpGnssProvider(
UdpGnssProviderConfig(
port: 5000,
bindAddress: '0.0.0.0',
),
);
Android Broadcast Provider
final provider = BroadcastGnssProvider(
BroadcastGnssProviderConfig(
intentAction: 'com.gnss.tool.NMEA_DATA',
dataKey: 'nmea_sentence',
),
);
File Tail Provider
final provider = FileTailProvider(
FileTailProviderConfig(
filePath: '/sdcard/gnss_data.log',
pollInterval: Duration(milliseconds: 100),
),
);
Architecture
Clean Architecture Layers
┌─────────────────────────────────────┐
│ GnssService (API) │
├─────────────────────────────────────┤
│ GnssHealthMonitor │
├─────────────────────────────────────┤
│ GnssRepository (Logic) │
├─────────────────────────────────────┤
│ NmeaParser (Parser) │
├─────────────────────────────────────┤
│ GnssProvider (Data Source) │
│ ├─ TcpGnssProvider │
│ ├─ UdpGnssProvider │
│ ├─ BroadcastGnssProvider │
│ ├─ FileTailProvider │
│ └─ MockGnssProvider │
└─────────────────────────────────────┘
Why This Architecture?
- Separation of Concerns: Each layer has a single responsibility
- Testability: Easy to mock and test individual components
- Flexibility: Swap providers without changing business logic
- Maintainability: Clear boundaries make code easy to understand
- Scalability: Add new features without breaking existing code
Advanced Usage
Health Monitoring
// Enable health monitoring
final gnssService = GnssService(
provider: provider,
enableHealthMonitoring: true,
healthCheckInterval: Duration(seconds: 5),
);
// Get diagnostics
final diagnostics = gnssService.getDiagnostics();
print('Overall Health: ${diagnostics.overallHealth}');
print('Signal Strength: ${diagnostics.signalStrength}%');
print('Fix Stability: ${diagnostics.fixStabilityScore}%');
// Check for issues
for (final issue in diagnostics.criticalIssues) {
print('CRITICAL: ${issue.message}');
print('Recommendation: ${issue.recommendation}');
}
Satellite Information
gnssService.satellitesStream.listen((satellites) {
print('Visible satellites: ${satellites.length}');
for (final sat in satellites) {
print('${sat.constellation.name}-${sat.prn}: SNR ${sat.snr} dB');
}
// Get satellites by constellation
final gpsSats = satellites.where(
(s) => s.constellation == ConstellationType.gps
);
});
Performance Metrics
final metrics = gnssService.getMetrics();
print('Data Rate: ${metrics.dataRate.toStringAsFixed(2)} pps');
print('Success Rate: ${(metrics.successRate * 100).toStringAsFixed(1)}%');
print('Uptime: ${metrics.uptimeString}');
print('Total Packets: ${metrics.totalPackets}');
print('Valid Packets: ${metrics.validPackets}');
Android Integration
Permissions
Add to android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
Foreground Service (for Background Tracking)
// Start foreground service via method channel
await MethodChannel('com.gnss.rtk/method').invokeMethod(
'startForegroundService',
{
'title': 'GNSS Tracking',
'message': 'High-precision positioning active',
},
);
Broadcast Receiver Setup
Add to AndroidManifest.xml:
<receiver
android:name="com.gnss.rtk.GnssBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.your.gnss.tool.NMEA_DATA" />
</intent-filter>
</receiver>
NMEA Sentences Supported
| Sentence | Description | Parsed Fields |
|---|---|---|
| GGA | Fix data | Lat, Lon, Alt, Fix Quality, Sats, HDOP, Age of Correction |
| RMC | Recommended minimum | Lat, Lon, Speed, Heading, Date/Time, Magnetic Variation |
| GSA | DOP and active sats | Fix Mode, PDOP, HDOP, VDOP, Satellite PRNs |
| GSV | Satellites in view | Satellite PRN, Elevation, Azimuth, SNR |
| VTG | Track and speed | True/Magnetic heading, Speed (knots/km/h) |
| GST | Position error | Horizontal/Vertical accuracy, Error ellipse |
| ZDA | Date and time | UTC Date/Time with milliseconds |
| GLL | Geographic position | Lat, Lon, UTC Time, Status |
Field Use Cases & Edge Cases
1. RTK Connection Loss
Problem: Base station connection drops, RTK fix degrades to Float/DGPS
Solution: Health monitor detects quality degradation and triggers alerts
gnssService.stateStream.listen((state) {
if (state.currentFix?.fixQuality == FixQuality.rtkFloat) {
showWarning('RTK fix degraded to Float');
}
});
2. Tunnel / Indoor Scenarios
Problem: Complete signal loss in tunnels or buildings
Solution: Library maintains last valid fix and tracks time since last update
if (state.isFixStale) {
print('No update for ${state.timeSinceLastValidFix?.inSeconds}s');
useLastValidPosition(state.lastValidFix);
}
3. Mock Location Detection
Problem: Developer options enable fake GPS, corrupting data
Solution: Built-in mock location detection
if (diagnostics.mockLocationDetected) {
showError('Mock location is enabled - disable in settings');
}
4. GNSS Drift
Problem: Position slowly drifts even when stationary
Solution: Monitor fix stability score
if (diagnostics.fixStabilityScore < 80) {
print('Position unstable - check for multipath interference');
}
5. Battery Optimization Issues
Problem: Android kills background service to save battery
Solution: Use foreground service (automatically handled)
// Foreground service prevents Android from killing the app
// Already implemented in GnssForegroundService.kt
6. High Baseline Distance
Problem: Too far from RTK base station (>50km) prevents RTK fix
Solution: Monitor age of correction and baseline
if (fix.ageOfCorrection != null && fix.ageOfCorrection! > 10) {
print('RTK corrections too old: ${fix.ageOfCorrection}s');
}
7. Multipath Interference
Problem: Signals reflect off buildings, causing position errors
Solution: Check DOP values and satellite geometry
if (fix.hdop != null && fix.hdop! > 5.0) {
showWarning('Poor satellite geometry (HDOP: ${fix.hdop})');
}
8. Industrial Tablet Issues
Common Problems:
- Manufacturer modifications to Android
- Custom GNSS apps with proprietary protocols
- Non-standard broadcast intents
- Serial port access restrictions
Solution: Multiple provider types support various integration methods
// Try TCP first (most reliable)
var provider = TcpGnssProvider(...);
// Fall back to Broadcast if TCP fails
if (!provider.isConnected) {
provider = BroadcastGnssProvider(...);
}
// Last resort: File tail
if (!provider.isConnected) {
provider = FileTailProvider(...);
}
Performance Optimizations
1. Backpressure Management
Prevents memory overflow from high-frequency data:
// Built-in throttling (default 100ms)
final repository = GnssRepository(
provider: provider,
throttleDuration: Duration(milliseconds: 100),
bufferSize: 100,
);
2. Duplicate Filtering
Automatically filters duplicate NMEA sentences:
// Checksum validation
// Duplicate detection
// Invalid packet filtering
// All built-in to NmeaParser
3. Stream Optimization
// Use distinct() to prevent unnecessary rebuilds
positionStream
.distinct((prev, next) =>
prev.latitude == next.latitude &&
prev.longitude == next.longitude
)
.listen((fix) => updateUI(fix));
4. Memory Management
// Always dispose when done
@override
void dispose() {
gnssService?.dispose();
super.dispose();
}
Testing
Run unit tests:
flutter test
Run with coverage:
flutter test --coverage
Example App
See example/lib/main.dart for a complete working example with:
- Real-time position display
- Satellite visualization
- Health monitoring dashboard
- Diagnostics viewer
- Start/stop controls
Run example:
cd example
flutter run
Troubleshooting
No Data Received
- Check provider configuration (IP, port, intent action)
- Verify network connectivity
- Check Android permissions
- Enable verbose logging
import 'package:logging/logging.dart';
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen((record) {
print('${record.level.name}: ${record.time}: ${record.message}');
});
Low Accuracy
- Ensure clear sky view
- Check satellite count (need 8+ for good accuracy)
- Verify RTK corrections are being received
- Check HDOP value (should be <2.0)
Memory Leaks
- Always call
dispose()on services - Cancel stream subscriptions
- Use
StreamSubscription.cancel()
License
MIT License - see LICENSE file
Contributing
Contributions welcome! Please read CONTRIBUTING.md
Support
- 📧 Email: support@gnssrtk.dev
- 🐛 Issues: Project Issues
- 📖 Docs: README Documentation
Credits
Developed for industrial GIS and surveying applications requiring professional-grade GNSS positioning.