gnss_rtk 1.0.0
gnss_rtk: ^1.0.0 copied to clipboard
Production-ready GNSS RTK positioning library for industrial applications with NMEA parsing and multi-provider support
example/lib/main.dart
// example/lib/main.dart
// Example application demonstrating GNSS RTK library usage
import 'package:flutter/material.dart';
import 'package:gnss_rtk/gnss_rtk.dart';
import 'dart:async';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'GNSS RTK Example',
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
home: const GnssRtkDemo(),
);
}
}
class GnssRtkDemo extends StatefulWidget {
const GnssRtkDemo({Key? key}) : super(key: key);
@override
State<GnssRtkDemo> createState() => _GnssRtkDemoState();
}
class _GnssRtkDemoState extends State<GnssRtkDemo> {
GnssService? _gnssService;
GnssState? _currentState;
GnssFix? _currentFix;
List<SatelliteInfo> _satellites = [];
GnssDiagnostics? _diagnostics;
StreamSubscription? _stateSubscription;
StreamSubscription? _positionSubscription;
StreamSubscription? _satellitesSubscription;
@override
void initState() {
super.initState();
_initializeGnss();
}
Future<void> _initializeGnss() async {
// Create mock provider for demonstration
// In production, use TcpGnssProvider, BroadcastGnssProvider, etc.
final provider = MockGnssProvider(
const MockGnssProviderConfig(
mockScenario: MockScenario.stationary,
updateInterval: Duration(seconds: 1),
),
);
_gnssService = GnssService(
provider: provider,
enableHealthMonitoring: true,
);
// Subscribe to streams
_stateSubscription = _gnssService!.stateStream.listen((state) {
setState(() {
_currentState = state;
});
});
_positionSubscription = _gnssService!.positionStream.listen((fix) {
setState(() {
_currentFix = fix;
});
});
_satellitesSubscription = _gnssService!.satellitesStream.listen((satellites) {
setState(() {
_satellites = satellites;
});
});
// Start periodic diagnostics update
Timer.periodic(const Duration(seconds: 5), (_) {
if (mounted) {
setState(() {
_diagnostics = _gnssService?.getDiagnostics();
});
}
});
}
Future<void> _startService() async {
try {
await _gnssService?.start();
_showSnackBar('GNSS service started', Colors.green);
} catch (e) {
_showSnackBar('Failed to start: $e', Colors.red);
}
}
Future<void> _stopService() async {
try {
await _gnssService?.stop();
_showSnackBar('GNSS service stopped', Colors.orange);
} catch (e) {
_showSnackBar('Failed to stop: $e', Colors.red);
}
}
void _showSnackBar(String message, Color color) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: color,
duration: const Duration(seconds: 2),
),
);
}
@override
void dispose() {
_stateSubscription?.cancel();
_positionSubscription?.cancel();
_satellitesSubscription?.cancel();
_gnssService?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('GNSS RTK Example'),
elevation: 2,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Connection Status Card
_buildStatusCard(),
const SizedBox(height: 16),
// Position Card
_buildPositionCard(),
const SizedBox(height: 16),
// Satellites Card
_buildSatellitesCard(),
const SizedBox(height: 16),
// Diagnostics Card
_buildDiagnosticsCard(),
const SizedBox(height: 16),
// Control Buttons
_buildControlButtons(),
],
),
),
);
}
Widget _buildStatusCard() {
final state = _currentState;
final isConnected = state?.connectionState.isConnected ?? false;
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
isConnected ? Icons.check_circle : Icons.cancel,
color: isConnected ? Colors.green : Colors.red,
),
const SizedBox(width: 8),
Text(
'Connection Status',
style: Theme.of(context).textTheme.titleLarge,
),
],
),
const Divider(),
_buildInfoRow('State', state?.connectionState.name ?? 'Unknown'),
_buildInfoRow('Provider', state?.providerName ?? '-'),
if (state?.errorMessage != null)
_buildInfoRow('Error', state!.errorMessage!, isError: true),
],
),
),
);
}
Widget _buildPositionCard() {
final fix = _currentFix;
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Position Fix',
style: Theme.of(context).textTheme.titleLarge,
),
const Divider(),
if (fix != null) ...[
_buildInfoRow('Latitude', fix.latitude.toStringAsFixed(8)),
_buildInfoRow('Longitude', fix.longitude.toStringAsFixed(8)),
if (fix.altitude != null)
_buildInfoRow('Altitude', '${fix.altitude!.toStringAsFixed(2)} m'),
_buildInfoRow('Fix Quality', fix.fixQuality.description),
_buildInfoRow('Fix Mode', fix.fixMode.description),
if (fix.estimatedAccuracy != null)
_buildInfoRow('Accuracy', '${fix.estimatedAccuracy!.toStringAsFixed(2)} m'),
if (fix.satelliteCount != null)
_buildInfoRow('Satellites', fix.satelliteCount.toString()),
if (fix.hdop != null)
_buildInfoRow('HDOP', fix.hdop!.toStringAsFixed(2)),
] else
const Text('No position fix available'),
],
),
),
);
}
Widget _buildSatellitesCard() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Satellites (${_satellites.length})',
style: Theme.of(context).textTheme.titleLarge,
),
const Divider(),
if (_satellites.isNotEmpty)
SizedBox(
height: 120,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: _satellites.length,
itemBuilder: (context, index) {
final sat = _satellites[index];
return _buildSatelliteCard(sat);
},
),
)
else
const Text('No satellite data'),
],
),
),
);
}
Widget _buildSatelliteCard(SatelliteInfo sat) {
final hasSignal = sat.snr != null && sat.snr! > 0;
return Card(
color: sat.isUsedInFix ? Colors.green.shade50 : null,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'${sat.constellation.name.toUpperCase()}-${sat.prn}',
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 4),
if (sat.snr != null) ...[
Text('SNR: ${sat.snr} dB'),
SizedBox(
width: 40,
child: LinearProgressIndicator(
value: (sat.snr! / 50).clamp(0.0, 1.0),
backgroundColor: Colors.grey.shade300,
valueColor: AlwaysStoppedAnimation<Color>(
hasSignal ? Colors.green : Colors.red,
),
),
),
],
],
),
),
);
}
Widget _buildDiagnosticsCard() {
final diag = _diagnostics;
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'System Health',
style: Theme.of(context).textTheme.titleLarge,
),
const Divider(),
if (diag != null) ...[
_buildInfoRow('Status', diag.overallHealth.name),
_buildInfoRow('Signal', '${diag.signalStrength ?? 0}%'),
_buildInfoRow('Fix Stability', '${diag.fixStabilityScore}%'),
if (diag.issues.isNotEmpty) ...[
const SizedBox(height: 8),
const Text('Issues:', style: TextStyle(fontWeight: FontWeight.bold)),
...diag.issues.map((issue) => Padding(
padding: const EdgeInsets.only(left: 8.0, top: 4.0),
child: Text(
'• ${issue.message}',
style: TextStyle(
color: issue.severity == HealthStatus.critical
? Colors.red
: Colors.orange,
),
),
)),
],
] else
const Text('Diagnostics not available'),
],
),
),
);
}
Widget _buildControlButtons() {
final isRunning = _gnssService?.isRunning ?? false;
return Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: isRunning ? null : _startService,
icon: const Icon(Icons.play_arrow),
label: const Text('Start'),
),
),
const SizedBox(width: 16),
Expanded(
child: ElevatedButton.icon(
onPressed: isRunning ? _stopService : null,
icon: const Icon(Icons.stop),
label: const Text('Stop'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
),
),
),
],
);
}
Widget _buildInfoRow(String label, String value, {bool isError = false}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: const TextStyle(fontWeight: FontWeight.w500)),
Text(
value,
style: TextStyle(
color: isError ? Colors.red : null,
),
),
],
),
);
}
}