SharedMapWidget és térkép nézet refraktorálása

This commit is contained in:
torok.istvan 2026-05-16 14:47:34 +02:00
parent f2457817b2
commit d50a324e44
11 changed files with 1167 additions and 1050 deletions

View File

@ -7,6 +7,7 @@ import 'package:terepi_seged/routes/app_pages.dart';
import 'package:terepi_seged/services/coord_converter_service.dart'; import 'package:terepi_seged/services/coord_converter_service.dart';
import 'package:terepi_seged/services/gnss/gnss_device_service.dart'; import 'package:terepi_seged/services/gnss/gnss_device_service.dart';
import 'package:terepi_seged/services/gnss/gnss_service.dart'; import 'package:terepi_seged/services/gnss/gnss_service.dart';
import 'package:terepi_seged/services/ntrip_service.dart';
Future<void> main() async { Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
@ -21,6 +22,7 @@ Future<void> main() async {
() => CoordConverterService().init()); () => CoordConverterService().init());
Get.put(GnssDeviceService()); Get.put(GnssDeviceService());
Get.put(GnssService()); Get.put(GnssService());
Get.put(NtripService());
runApp(const MyApp()); runApp(const MyApp());
} }

View File

@ -84,6 +84,9 @@ class _StakeoutPanel extends GetView<MapSurveyController> {
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
child: Obx(() { child: Obx(() {
final onTarget = controller.isOnTarget; final onTarget = controller.isOnTarget;
final dy = controller.deltaY;
final dx = controller.deltaX;
final dist = controller.distanceToTarget;
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@ -108,17 +111,17 @@ class _StakeoutPanel extends GetView<MapSurveyController> {
children: [ children: [
_DeltaCell( _DeltaCell(
label: 'ΔY', label: 'ΔY',
value: controller.deltaY, value: dy,
unit: 'm', unit: 'm',
), ),
_DeltaCell( _DeltaCell(
label: 'ΔX', label: 'ΔX',
value: controller.deltaX, value: dx,
unit: 'm', unit: 'm',
), ),
_DeltaCell( _DeltaCell(
label: 'Táv', label: 'Táv',
value: controller.distanceToTarget, value: dist,
unit: 'm', unit: 'm',
alwaysPositive: true, alwaysPositive: true,
), ),

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart'; import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart';
import 'package:terepi_seged/services/ntrip_service.dart';
class SettingsDialog extends StatelessWidget { class SettingsDialog extends StatelessWidget {
final controller = Get.find<MapSurveyController>(); final controller = Get.find<MapSurveyController>();
@ -17,19 +18,19 @@ class SettingsDialog extends StatelessWidget {
children: [ children: [
IconButton( IconButton(
onPressed: () { onPressed: () {
if (controller.ntripUsernameController.text.isNotEmpty) { // if (controller.ntripUsernameController.text.isNotEmpty) {
controller.ntripUserName.value = // controller.ntripUserName.value =
controller.ntripUsernameController.text; // controller.ntripUsernameController.text;
controller.saveNtripUserName( // controller.saveNtripUserName(
controller.ntripUsernameController.text); // controller.ntripUsernameController.text);
if (controller // if (controller
.ntripPasswordController.text.isNotEmpty) { // .ntripPasswordController.text.isNotEmpty) {
controller.ntripPassword.value = // controller.ntripPassword.value =
controller.ntripPasswordController.text; // controller.ntripPasswordController.text;
controller.saveNtripPassword( // controller.saveNtripPassword(
controller.ntripPasswordController.text); // controller.ntripPasswordController.text);
} // }
} // }
Get.back(); Get.back();
}, },
icon: const Icon(Icons.close)), icon: const Icon(Icons.close)),
@ -38,19 +39,19 @@ class SettingsDialog extends StatelessWidget {
overlayColor: overlayColor:
MaterialStateProperty.all(Colors.transparent)), MaterialStateProperty.all(Colors.transparent)),
onPressed: () { onPressed: () {
if (controller.ntripUsernameController.text.isNotEmpty) { // if (controller.ntripUsernameController.text.isNotEmpty) {
controller.ntripUserName.value = // controller.ntripUserName.value =
controller.ntripUsernameController.text; // controller.ntripUsernameController.text;
controller.saveNtripUserName( // controller.saveNtripUserName(
controller.ntripUsernameController.text); // controller.ntripUsernameController.text);
if (controller // if (controller
.ntripPasswordController.text.isNotEmpty) { // .ntripPasswordController.text.isNotEmpty) {
controller.ntripPassword.value = // controller.ntripPassword.value =
controller.ntripPasswordController.text; // controller.ntripPasswordController.text;
controller.saveNtripPassword( // controller.saveNtripPassword(
controller.ntripPasswordController.text); // controller.ntripPasswordController.text);
} // }
} // }
Get.back(); Get.back();
}, },
child: const Text( child: const Text(
@ -74,118 +75,118 @@ class SettingsDialog extends StatelessWidget {
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.w500), style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.w500),
), ),
), ),
Obx(() => Column(children: [ // Obx(() => Column(children: [
RadioListTile( // RadioListTile(
title: Text('TiGNSS Rover-BE6A'), // title: Text('TiGNSS Rover-BE6A'),
value: '10:06:1C:97:BE:6A', // value: '10:06:1C:97:BE:6A',
groupValue: controller.gpsAddress.value, // groupValue: controller.gpsAddress.value,
onChanged: (value) { // onChanged: (value) {
controller.gpsAddress.value = value!; // controller.gpsAddress.value = value!;
controller.gpsName.value = 'TiGNSS Rover-BE6A'; // controller.gpsName.value = 'TiGNSS Rover-BE6A';
controller.saveGpsAddress(value); // controller.saveGpsAddress(value);
controller.saveGpsName('TiGNSS Rover-BE6A'); // controller.saveGpsName('TiGNSS Rover-BE6A');
}), // }),
RadioListTile( // RadioListTile(
title: Text('TiGNSS Rover-1DC6'), // title: Text('TiGNSS Rover-1DC6'),
value: 'E8:31:CD:16:1D:C6', // value: 'E8:31:CD:16:1D:C6',
groupValue: controller.gpsAddress.value, // groupValue: controller.gpsAddress.value,
onChanged: (value) { // onChanged: (value) {
controller.gpsAddress.value = value!; // controller.gpsAddress.value = value!;
controller.gpsName.value = 'TiGNSS Rover-1DC6'; // controller.gpsName.value = 'TiGNSS Rover-1DC6';
controller.saveGpsAddress(value); // controller.saveGpsAddress(value);
controller.saveGpsName('TiGNSS Rover-1DC6'); // controller.saveGpsName('TiGNSS Rover-1DC6');
}), // }),
RadioListTile( // RadioListTile(
title: Text('TiGNSS Rover-9C3A'), // title: Text('TiGNSS Rover-9C3A'),
value: '08:3A:8D:14:9C:3A', // value: '08:3A:8D:14:9C:3A',
groupValue: controller.gpsAddress.value, // groupValue: controller.gpsAddress.value,
onChanged: (value) { // onChanged: (value) {
controller.gpsAddress.value = value!; // controller.gpsAddress.value = value!;
controller.gpsName.value = 'TiGNSS Rover-9C3A'; // controller.gpsName.value = 'TiGNSS Rover-9C3A';
controller.saveGpsAddress(value); // controller.saveGpsAddress(value);
controller.saveGpsName('TiGNSS Rover-9C3A'); // controller.saveGpsName('TiGNSS Rover-9C3A');
}), // }),
RadioListTile( // RadioListTile(
title: Text('TiGNSS Rover-72C2'), // title: Text('TiGNSS Rover-72C2'),
value: '10:06:1C:97:72:C2', // value: '10:06:1C:97:72:C2',
groupValue: controller.gpsAddress.value, // groupValue: controller.gpsAddress.value,
onChanged: (value) { // onChanged: (value) {
controller.gpsAddress.value = value!; // controller.gpsAddress.value = value!;
controller.gpsName.value = 'TiGNSS Rover-72C2'; // controller.gpsName.value = 'TiGNSS Rover-72C2';
controller.saveGpsAddress(value); // controller.saveGpsAddress(value);
controller.saveGpsName('TiGNSS Rover-72C2'); // controller.saveGpsName('TiGNSS Rover-72C2');
}), // }),
RadioListTile( // RadioListTile(
title: Text('TiGNSS Rover-FE16'), // title: Text('TiGNSS Rover-FE16'),
value: '10:06:1C:9F:FE:16', // value: '10:06:1C:9F:FE:16',
groupValue: controller.gpsAddress.value, // groupValue: controller.gpsAddress.value,
onChanged: (value) { // onChanged: (value) {
controller.gpsAddress.value = value!; // controller.gpsAddress.value = value!;
controller.gpsName.value = 'TiGNSS Rover-FE16'; // controller.gpsName.value = 'TiGNSS Rover-FE16';
controller.saveGpsAddress(value); // controller.saveGpsAddress(value);
controller.saveGpsName('TiGNSS Rover-FE16'); // controller.saveGpsName('TiGNSS Rover-FE16');
}), // }),
RadioListTile( // RadioListTile(
title: Text('TiGNSS Rover-3B0A'), // title: Text('TiGNSS Rover-3B0A'),
value: '10:C6:1C:9E:3B:0A', // value: '10:C6:1C:9E:3B:0A',
groupValue: controller.gpsAddress.value, // groupValue: controller.gpsAddress.value,
onChanged: (value) { // onChanged: (value) {
controller.gpsAddress.value = value!; // controller.gpsAddress.value = value!;
controller.gpsName.value = 'TiGNSS Rover-3B0A'; // controller.gpsName.value = 'TiGNSS Rover-3B0A';
controller.saveGpsAddress(value); // controller.saveGpsAddress(value);
controller.saveGpsName('TiGNSS Rover-3B0A'); // controller.saveGpsName('TiGNSS Rover-3B0A');
}), // }),
RadioListTile( // RadioListTile(
title: Text('TiGNSS Rover-7FEA'), // title: Text('TiGNSS Rover-7FEA'),
value: '10:06:1C:9C:7F:EA', // value: '10:06:1C:9C:7F:EA',
groupValue: controller.gpsAddress.value, // groupValue: controller.gpsAddress.value,
onChanged: (value) { // onChanged: (value) {
controller.gpsAddress.value = value!; // controller.gpsAddress.value = value!;
controller.gpsName.value = 'TiGNSS Rover-7FEA'; // controller.gpsName.value = 'TiGNSS Rover-7FEA';
controller.saveGpsAddress(value); // controller.saveGpsAddress(value);
controller.saveGpsName('TiGNSS Rover-7FEA'); // controller.saveGpsName('TiGNSS Rover-7FEA');
}), // }),
RadioListTile( // RadioListTile(
title: Text('TiGNSS Rover-A39E'), // title: Text('TiGNSS Rover-A39E'),
value: '10:06:1C:97:A3:9E', // value: '10:06:1C:97:A3:9E',
groupValue: controller.gpsAddress.value, // groupValue: controller.gpsAddress.value,
onChanged: (value) { // onChanged: (value) {
controller.gpsAddress.value = value!; // controller.gpsAddress.value = value!;
controller.gpsName.value = 'TiGNSS Rover-A39E'; // controller.gpsName.value = 'TiGNSS Rover-A39E';
controller.saveGpsAddress(value); // controller.saveGpsAddress(value);
controller.saveGpsName('TiGNSS Rover-A39E'); // controller.saveGpsName('TiGNSS Rover-A39E');
}), // }),
RadioListTile( // RadioListTile(
title: Text('TiGNSS Rover-FF4E'), // title: Text('TiGNSS Rover-FF4E'),
value: '98:CD:AC:62:FF:4E', // value: '98:CD:AC:62:FF:4E',
groupValue: controller.gpsAddress.value, // groupValue: controller.gpsAddress.value,
onChanged: (value) { // onChanged: (value) {
controller.gpsAddress.value = value!; // controller.gpsAddress.value = value!;
controller.gpsName.value = 'TiGNSS Rover-FF4E'; // controller.gpsName.value = 'TiGNSS Rover-FF4E';
controller.saveGpsAddress(value); // controller.saveGpsAddress(value);
controller.saveGpsName('TiGNSS Rover-FF4E'); // controller.saveGpsName('TiGNSS Rover-FF4E');
}), // }),
RadioListTile( // RadioListTile(
title: Text('TiGNSS Rover-8BB2'), // title: Text('TiGNSS Rover-8BB2'),
value: 'E8:31:CD:14:8B:B2', // value: 'E8:31:CD:14:8B:B2',
groupValue: controller.gpsAddress.value, // groupValue: controller.gpsAddress.value,
onChanged: (value) { // onChanged: (value) {
controller.gpsAddress.value = value!; // controller.gpsAddress.value = value!;
controller.gpsName.value = 'TiGNSS Rover-8BB2'; // controller.gpsName.value = 'TiGNSS Rover-8BB2';
controller.saveGpsAddress(value); // controller.saveGpsAddress(value);
controller.saveGpsName('TiGNSS Rover-8BB2'); // controller.saveGpsName('TiGNSS Rover-8BB2');
}), // }),
RadioListTile( // RadioListTile(
title: Text('TiGNSS Rover-FF36'), // title: Text('TiGNSS Rover-FF36'),
value: '98:CD:AC:62:FF:36', // value: '98:CD:AC:62:FF:36',
groupValue: controller.gpsAddress.value, // groupValue: controller.gpsAddress.value,
onChanged: (value) { // onChanged: (value) {
controller.gpsAddress.value = value!; // controller.gpsAddress.value = value!;
controller.gpsName.value = 'TiGNSS Rover-FF36'; // controller.gpsName.value = 'TiGNSS Rover-FF36';
controller.saveGpsAddress(value); // controller.saveGpsAddress(value);
controller.saveGpsName('TiGNSS Rover-FF36'); // controller.saveGpsName('TiGNSS Rover-FF36');
}) // })
])), // ])),
const Padding( const Padding(
padding: EdgeInsets.symmetric(horizontal: 20), padding: EdgeInsets.symmetric(horizontal: 20),
child: Divider( child: Divider(
@ -329,7 +330,7 @@ class SettingsDialog extends StatelessWidget {
SizedBox( SizedBox(
height: 40, height: 40,
child: TextField( child: TextField(
controller: controller.ntripUsernameController, controller: NtripService.to.usernameController,
enableSuggestions: false, enableSuggestions: false,
autocorrect: false, autocorrect: false,
decoration: InputDecoration( decoration: InputDecoration(
@ -351,10 +352,10 @@ class SettingsDialog extends StatelessWidget {
child: TextField( child: TextField(
keyboardType: TextInputType.visiblePassword, keyboardType: TextInputType.visiblePassword,
obscureText: controller.isShowPassword.value, obscureText: controller.isShowPassword.value,
focusNode: controller.passwordFieldFocusNode, //focusNode: controller.passwordFieldFocusNode,
enableSuggestions: false, enableSuggestions: false,
autocorrect: false, autocorrect: false,
controller: controller.ntripPasswordController, controller: NtripService.to.passwordController,
decoration: InputDecoration( decoration: InputDecoration(
floatingLabelBehavior: FloatingLabelBehavior.never, floatingLabelBehavior: FloatingLabelBehavior.never,
isDense: true, isDense: true,
@ -371,7 +372,7 @@ class SettingsDialog extends StatelessWidget {
suffixIcon: Padding( suffixIcon: Padding(
padding: const EdgeInsets.fromLTRB(0, 0, 4, 0), padding: const EdgeInsets.fromLTRB(0, 0, 4, 0),
child: GestureDetector( child: GestureDetector(
onTap: controller.toggleShowPassword, //onTap: controller.toggleShowPassword,
child: Icon( child: Icon(
controller.isShowPassword.value controller.isShowPassword.value
? Icons.visibility_rounded ? Icons.visibility_rounded

View File

@ -11,7 +11,11 @@ class ShellBinding extends Bindings {
// TODO: implement dependencies // TODO: implement dependencies
Get.put(ShellController()); Get.put(ShellController());
Get.put(HomeViewController()); Get.put(HomeViewController());
Get.put(MapViewController()); // Get.put(MapViewController());
Get.put(MapSurveyController()); // Get.put(MapSurveyController());
Get.lazyPut<MapSurveyController>(
() => MapSurveyController(),
fenix: true,
);
} }
} }

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:terepi_seged/pages/home/presentation/views/home_view.dart'; import 'package:terepi_seged/pages/home/presentation/views/home_view.dart';
import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart'; import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart';
import 'package:terepi_seged/services/ntrip_service.dart';
import 'package:terepi_seged/widgets/gnss_status_chip.dart'; import 'package:terepi_seged/widgets/gnss_status_chip.dart';
import '../../../../widgets/app_drawer.dart'; import '../../../../widgets/app_drawer.dart';
@ -30,12 +31,11 @@ class ShellView extends GetView<ShellController> {
const SizedBox(width: 6), const SizedBox(width: 6),
Obx(() => controller.currentIndex.value == 1 Obx(() => controller.currentIndex.value == 1
? NtripStatusChip( ? NtripStatusChip(
isConnected: MapSurveyController.to.ntripIsConnected, isConnected: NtripService.to.isConnected,
onToggle: () { onToggle: () {
final c = MapSurveyController.to; NtripService.to.isConnected.value
c.ntripIsConnected.value ? NtripService.to.disconnect()
? c.disconnectFromNtripServer() : NtripService.to.connect();
: c.connectToNtripServer();
}, },
) )
: const SizedBox.shrink()) : const SizedBox.shrink())

View File

@ -92,4 +92,8 @@ class BtSerialGnssConnection implements GnssConnection {
_nmeaController.close(); _nmeaController.close();
_stateController.close(); _stateController.close();
} }
void sendData(Uint8List data) {
_connection?.output.add(data);
}
} }

View File

@ -1,43 +1,68 @@
// lib/services/gnss/gnss_service.dart // lib/services/gnss/gnss_service.dart
import 'dart:async'; import 'dart:async';
import 'dart:typed_data';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:nmea/nmea.dart'; import 'package:nmea/nmea.dart';
import 'package:terepi_seged/services/gnss/gnss_device_service.dart';
import '../../gnss_sentences/gngga.dart'; import '../../gnss_sentences/gngga.dart';
import 'gnss_connection.dart'; import '../../gnss_sentences/gngst.dart';
import '../../gnss_sentences/gnrmc.dart';
import 'bt_serial_gnss_connection.dart'; import 'bt_serial_gnss_connection.dart';
import 'ble_gnss_connection.dart'; import 'ble_gnss_connection.dart';
import 'gnss_device_service.dart'; import 'gnss_connection.dart';
class GnssService extends GetxService { class GnssService extends GetxService {
static GnssService get to => Get.find(); static GnssService get to => Get.find();
GnssConnection? _connection; GnssConnection? _connection;
// Reaktív állapot a controllerek Obx-szel figyelhetik // Kapcsolat állapot
final connectionState = GnssConnectionState.disconnected.obs; final connectionState = GnssConnectionState.disconnected.obs;
final activeConnectionType = Rxn<GnssConnectionType>(); final activeConnectionType = Rxn<GnssConnectionType>();
// Parsed NMEA adatok // GGA adatok
final latitude = 0.0.obs; final latitude = 0.0.obs;
final longitude = 0.0.obs; final longitude = 0.0.obs;
final altitude = 0.0.obs; final altitude = 0.0.obs; // MSL (ortometrikus)
final geoidSeparation = 0.0.obs; final geoidSeparation = 0.0.obs; // N geoid undulációja
final gpsQuality = 0.obs; final gpsQuality = 0.obs;
final utcFix = ''.obs; final utcFix = ''.obs;
final satelliteCount = 0.obs; final satelliteCount = 0.obs;
final hdop = 0.0.obs; final hdop = 0.0.obs;
// Utolsó nyers GGA sor NtripService küldi vissza a casternek
final lastGgaLine = ''.obs;
// GST adatok (pontossági hibák)
final latitudeError = 0.0.obs;
final longitudeError = 0.0.obs;
final altitudeError = 0.0.obs;
// RMC adatok (dátum/idő)
final gpsDateTime = DateTime(2000).obs;
// Segédmező: van-e érvényes adat
bool get hasValidData => gpsQuality.value > 0;
// Belső
final NmeaDecoder _decoder = NmeaDecoder(); final NmeaDecoder _decoder = NmeaDecoder();
StreamSubscription? _nmeaSub; StreamSubscription? _nmeaSub;
StreamSubscription? _stateSub; StreamSubscription? _stateSub;
String _utcTime = '';
String _utcDate = '';
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
_decoder.registerTalkerSentence('GGA', (l) => Gngga(raw: l)); _decoder
..registerTalkerSentence('GGA', (l) => Gngga(raw: l))
..registerTalkerSentence('GST', (l) => Gngst(raw: l))
..registerTalkerSentence('RMC', (l) => Gnrmc(raw: l));
} }
// Kapcsolódás // Kapcsolódás
Future<void> connectBtSerial(String macAddress) async { Future<void> connectBtSerial(String macAddress) async {
await _disconnect(); await _disconnect();
@ -60,6 +85,27 @@ class GnssService extends GetxService {
await _doConnect(deviceId); await _doConnect(deviceId);
} }
/// Eszközváltás GnssDevicePicker hívja.
Future<void> onDeviceChanged(GnssDevice device) async {
switch (device.type) {
case GnssConnectionType.btSerial:
await connectBtSerial(device.address);
case GnssConnectionType.ble:
await connectBle(device.address);
case GnssConnectionType.phoneGps:
await _disconnect();
connectionState.value = GnssConnectionState.disconnected;
activeConnectionType.value = GnssConnectionType.phoneGps;
}
}
Future<void> reconnect() async {
final device = GnssDeviceService.to.selectedDevice.value;
if (device == null) return;
await _disconnect();
await onDeviceChanged(device);
}
Future<void> _doConnect(String address) async { Future<void> _doConnect(String address) async {
_stateSub = _connection!.connectionState.listen((s) { _stateSub = _connection!.connectionState.listen((s) {
connectionState.value = s; connectionState.value = s;
@ -74,26 +120,74 @@ class GnssService extends GetxService {
await _connection?.disconnect(); await _connection?.disconnect();
_connection?.dispose(); _connection?.dispose();
_connection = null; _connection = null;
connectionState.value = GnssConnectionState.disconnected;
} }
// NMEA parsing egy helyen, nem háromban Future<void> disconnect() => _disconnect();
/// RTCM adat továbbítása a GNSS vevőnek (NtripService hívja).
void sendToReceiver(Uint8List data) {
if (_connection == null) return;
if (connectionState.value != GnssConnectionState.connected) return;
(_connection as BtSerialGnssConnection?)?.sendData(data);
}
// NMEA parsing
void _parseNmea(String line) { void _parseNmea(String line) {
if (!line.startsWith('\$GNGGA') && !line.startsWith('\$GPGGA')) return; if (line.startsWith('\$GNGGA') || line.startsWith('\$GPGGA')) {
_parseGga(line);
} else if (line.startsWith('\$GNGST') && hasValidData) {
_parseGst(line);
} else if (line.startsWith('\$GNRMC') && hasValidData) {
_parseRmc(line);
}
}
void _parseGga(String line) {
try { try {
final sentence = _decoder.decode(line); final s = _decoder.decode(line);
if (sentence == null || !sentence.valid || sentence is! Gngga) return; if (s == null || !s.valid || s is! Gngga) return;
if (sentence.gpsQualityIndicator == 0) return; if (s.gpsQualityIndicator == 0) return;
latitude.value = sentence.latitude; latitude.value = s.latitude;
longitude.value = sentence.longitude; longitude.value = s.longitude;
altitude.value = sentence.altitudeAboveMeanSeaLevel; altitude.value = s.altitudeAboveMeanSeaLevel;
geoidSeparation.value = sentence.geoidSeparation; geoidSeparation.value = s.geoidSeparation;
gpsQuality.value = sentence.gpsQualityIndicator; gpsQuality.value = s.gpsQualityIndicator;
utcFix.value = sentence.utcOfPositionFix; utcFix.value = s.utcOfPositionFix;
satelliteCount.value = sentence.numberOfSvsInUse; satelliteCount.value = s.numberOfSvsInUse;
hdop.value = sentence.hdop; hdop.value = s.hdop;
lastGgaLine.value = line;
_utcTime = s.utcOfPositionFix;
} catch (_) {}
}
void _parseGst(String line) {
try {
final s = _decoder.decode(line);
if (s == null || !s.valid || s is! Gngst) return;
latitudeError.value = s.latitudeError;
longitudeError.value = s.longitudeError;
altitudeError.value = s.heightError;
} catch (_) {}
}
void _parseRmc(String line) {
try {
final s = _decoder.decode(line);
if (s == null || !s.valid || s is! Gnrmc) return;
_utcDate = s.date;
if (_utcDate.length >= 6 && _utcTime.length >= 6) {
gpsDateTime.value = DateTime(
2000 + int.parse('${_utcDate[4]}${_utcDate[5]}'),
int.parse('${_utcDate[2]}${_utcDate[3]}'),
int.parse('${_utcDate[0]}${_utcDate[1]}'),
int.parse('${_utcTime[0]}${_utcTime[1]}'),
int.parse('${_utcTime[2]}${_utcTime[3]}'),
int.parse('${_utcTime[4]}${_utcTime[5]}'),
);
}
} catch (_) {} } catch (_) {}
} }
@ -102,24 +196,4 @@ class GnssService extends GetxService {
_disconnect(); _disconnect();
super.onClose(); super.onClose();
} }
// Eszközváltás a GnssDevicePickerDialog hívja
Future<void> onDeviceChanged(GnssDevice device) async {
switch (device.type) {
case GnssConnectionType.btSerial:
await connectBtSerial(device.address);
case GnssConnectionType.ble:
await connectBle(device.address);
case GnssConnectionType.phoneGps:
// Külső GPS kapcsolat lezárása ha volt
await _disconnect();
connectionState.value = GnssConnectionState.disconnected;
activeConnectionType.value = GnssConnectionType.phoneGps;
// A telefon GPS kezelése a map_controller _startPhoneGps()-ben van
// itt csak jelezzük hogy phone módra váltottunk
}
}
} }

View File

@ -0,0 +1,248 @@
// lib/services/ntrip_service.dart
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// NTRIP kapcsolatot kezelő singleton service.
///
/// Felelőssége:
/// - Socket kapcsolat az NTRIP casterhez
/// - RTCM adatok fogadása és továbbítása a GNSS vevőnek
/// - GGA mondatok küldése a casternek (5 másodpercenként)
/// - Beállítások tárolása SharedPreferences-ben
///
/// Használat:
/// ```dart
/// // Csatlakozás előtt add meg a callback-et:
/// NtripService.to.onRtcmData = (data) => connection.output.add(data);
/// await NtripService.to.connect();
/// ```
class NtripService extends GetxService {
static NtripService get to => Get.find();
// Reaktív állapot
final isConnected = false.obs;
final receivedBytes = 0.obs;
final packetCount = 0.obs;
final ggaSentCount = 0.obs;
final ggaLastSentTime = ''.obs;
// Beállítások
final host = '84.206.45.44'.obs; // gnssnet.hu IP
final port = 2101.obs;
final mountpoint = 'SGO_RTK3.2'.obs;
final username = ''.obs;
final password = ''.obs;
// UI controllerek (beállítás dialóghoz)
final hostController = TextEditingController();
final portController = TextEditingController();
final mountpointController = TextEditingController();
final usernameController = TextEditingController();
final passwordController = TextEditingController();
// Belső állapot
Socket? _socket;
StreamSubscription? _socketSub;
String _lastGgaMessage = '';
DateTime _lastGgaSentTime =
DateTime.now().subtract(const Duration(seconds: 30));
/// Callback: RTCM adat érkezett a controller továbbítja a GNSS vevőnek.
/// Beállítás: `NtripService.to.onRtcmData = (data) => connection.output.add(data);`
Function(Uint8List)? onRtcmData;
// Inicializálás
@override
Future<void> onInit() async {
super.onInit();
await _loadSettings();
_syncControllersFromValues();
}
@override
void onClose() {
disconnect();
hostController.dispose();
portController.dispose();
mountpointController.dispose();
usernameController.dispose();
passwordController.dispose();
super.onClose();
}
// Kapcsolat
Future<void> connect() async {
if (isConnected.value) return;
try {
_socket = await Socket.connect(
InternetAddress(host.value),
port.value,
timeout: const Duration(seconds: 5),
);
_socket!.encoding = ascii;
isConnected.value = true;
receivedBytes.value = 0;
packetCount.value = 0;
// HTTP fejléc összeállítása
final header = _buildNtripHeader();
_socket!.add(_toUint8List(header));
// Adatfogadás
_socketSub = _socket!.listen(
_onData,
onError: _onError,
onDone: _onDone,
);
} catch (e) {
isConnected.value = false;
Get.snackbar(
'NTRIP hiba',
'Nem sikerült csatlakozni: $e',
backgroundColor: const Color(0xFFB71C1C),
colorText: const Color(0xFFFFFFFF),
);
}
}
Future<void> disconnect() async {
if (!isConnected.value && _socket == null) return;
await _socketSub?.cancel();
await _socket?.flush();
_socket?.close();
_socket?.destroy();
_socket = null;
isConnected.value = false;
receivedBytes.value = 0;
}
void reconnect() async {
await disconnect();
await Future.delayed(const Duration(seconds: 1));
await connect();
}
// GGA küldés
/// Az NMEA feldolgozó hívja minden GGA mondatnál.
/// 5 másodpercenként küld egyet az NTRIP casternek.
void onGgaReceived(String ggaLine, String utcTime) {
_lastGgaMessage = ggaLine;
if (!isConnected.value) return;
if (ggaLine.isEmpty) return;
final elapsed = DateTime.now().difference(_lastGgaSentTime).inSeconds;
if (elapsed < 5) return;
_sendGga(ggaLine);
ggaSentCount.value++;
ggaLastSentTime.value = utcTime;
_lastGgaSentTime = DateTime.now();
}
void _sendGga(String ggaMessage) {
if (_socket == null || !isConnected.value) return;
_socket!.add(_toUint8List('$ggaMessage\r\n'));
}
// Belső adatfogadás
void _onData(Uint8List data) {
receivedBytes.value = data.length;
packetCount.value++;
// Csak RTCM adat (>14 byte) kerül a GNSS vevőhöz
if (data.length > 14) {
onRtcmData?.call(data);
}
}
void _onError(dynamic error) {
_socket?.destroy();
isConnected.value = false;
Get.snackbar(
'NTRIP kapcsolat hiba',
error.toString(),
backgroundColor: const Color(0xFFB71C1C),
colorText: const Color(0xFFFFFFFF),
);
}
void _onDone() async {
await _socketSub?.cancel();
await _socket?.flush();
_socket?.destroy();
_socket = null;
isConnected.value = false;
receivedBytes.value = 0;
}
// HTTP fejléc összeállítás
String _buildNtripHeader() {
final auth = _toBase64('${username.value}:${password.value}');
final host = '${this.host.value}:${port.value}';
return 'GET /${mountpoint.value} HTTP/1.1\r\n'
'User-Agent: SharpGps iter.dk\r\n'
'Accept: */*\r\n'
'Connection: close\r\n'
'Authorization: Basic $auth\r\n'
'Host:$host\r\n'
'Ntrip-Version:Ntrip/2.0\r\n'
'\r\n';
}
// Beállítások mentése / betöltése
Future<void> saveSettings() async {
// Szinkronizálás a controllerektől az Rx értékekbe
host.value = hostController.text.trim();
port.value = int.tryParse(portController.text) ?? 2101;
mountpoint.value = mountpointController.text.trim();
username.value = usernameController.text.trim();
password.value = passwordController.text;
final prefs = await SharedPreferences.getInstance();
await prefs.setString('ntrip_host', host.value);
await prefs.setInt('ntrip_port', port.value);
await prefs.setString('ntrip_mountpoint', mountpoint.value);
await prefs.setString('ntrip_username', username.value);
await prefs.setString('ntrip_password', password.value);
}
Future<void> _loadSettings() async {
final prefs = await SharedPreferences.getInstance();
host.value = prefs.getString('ntrip_host') ?? '84.206.45.44';
port.value = prefs.getInt('ntrip_port') ?? 2101;
mountpoint.value = prefs.getString('ntrip_mountpoint') ?? 'SGO_RTK3.2';
username.value = prefs.getString('ntrip_username') ?? '';
password.value = prefs.getString('ntrip_password') ?? '';
}
void _syncControllersFromValues() {
hostController.text = host.value;
portController.text = port.value.toString();
mountpointController.text = mountpoint.value;
usernameController.text = username.value;
// Jelszót nem pre-töltjük biztonsági okokból
}
// Segédfüggvények
String _toBase64(String str) => base64.encode(ascii.encode(str));
Uint8List _toUint8List(String str) => Uint8List.fromList(str.codeUnits);
}

View File

@ -4,6 +4,7 @@ import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:terepi_seged/services/ntrip_service.dart';
import '../services/gnss/gnss_service.dart'; import '../services/gnss/gnss_service.dart';
@ -49,10 +50,10 @@ class CoordinatePanel extends StatelessWidget {
vertError: ctrl.gpsAltitudeError as RxDouble, vertError: ctrl.gpsAltitudeError as RxDouble,
altitudeMsl: ctrl.gpsAltitude as RxDouble, altitudeMsl: ctrl.gpsAltitude as RxDouble,
geoidSeparation: ctrl.gpsGeoidSeparation as RxDouble, geoidSeparation: ctrl.gpsGeoidSeparation as RxDouble,
ntripConnected: ctrl.ntripIsConnected as RxBool, ntripConnected: NtripService.to.isConnected,
ntripBytes: ctrl.ntripReceivedData as RxInt, ntripBytes: NtripService.to.receivedBytes,
ntripPackets: ctrl.ntripDataPacketNumbers as RxInt, ntripPackets: NtripService.to.packetCount,
ggaPackets: ctrl.ggaSenDataPacketNumber as RxInt, ggaPackets: NtripService.to.ggaSentCount,
); );
} }

View File

@ -1,5 +1,6 @@
// lib/widgets/shared_map_widget.dart // lib/widgets/shared_map_widget.dart
import 'dart:math' as math; import 'dart:math' as math;
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/flutter_map.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@ -139,13 +140,17 @@ class _SharedMapWidgetState extends State<SharedMapWidget> {
// 2. Extra rétegek (terepbejárás elemei) // 2. Extra rétegek (terepbejárás elemei)
...widget.extraLayers, ...widget.extraLayers,
// 3. Bemért pontok // 3. Bemért pontok
if (Get.isRegistered<MapSurveyController>()) // if (Get.isRegistered<MapSurveyController>())
Obx(() => MarkerLayer( // Obx(() => MarkerLayer(
markers: _buildMeasuredPointMarkers(), // markers: _buildMeasuredPointMarkers(),
)), // )),
// 4. Kitűzési célpont + vonal // 4. Kitűzési célpont + vonal
if (Get.isRegistered<MapSurveyController>()) if (Get.isRegistered<MapSurveyController>())
Obx(() => _buildStakeoutLayer(lat, lon)), Obx(() {
final lat = GnssService.to.latitude.value;
final lon = GnssService.to.longitude.value;
return _buildStakeoutLayer(lat, lon);
}),
// 5. GPS pozíció // 5. GPS pozíció
Obx(() { Obx(() {
final lat = GnssService.to.latitude.value; final lat = GnssService.to.latitude.value;
@ -187,19 +192,7 @@ class _SharedMapWidgetState extends State<SharedMapWidget> {
List<Marker> _buildMeasuredPointMarkers() { List<Marker> _buildMeasuredPointMarkers() {
if (!Get.isRegistered<MapSurveyController>()) return []; if (!Get.isRegistered<MapSurveyController>()) return [];
return MapSurveyController.to.measuredPoints1 return MapSurveyController.to.pointNotesMarker;
.map((point) => Marker(
point: LatLng(point.latitude, point.longitude),
width: 100,
height: 56,
alignment: Alignment.bottomCenter,
child: _LabeledMarker(
label: point.name,
icon: Icons.location_on,
color: Colors.blue,
),
))
.toList();
} }
Widget _buildStakeoutLayer(double lat, double lon) { Widget _buildStakeoutLayer(double lat, double lon) {
@ -214,6 +207,10 @@ class _SharedMapWidgetState extends State<SharedMapWidget> {
final wgs = CoordConverterService.to final wgs = CoordConverterService.to
.eovToWgsPoint(ctrl.targetEovY.value, ctrl.targetEovX.value); .eovToWgsPoint(ctrl.targetEovY.value, ctrl.targetEovX.value);
final targetLatLng = LatLng(wgs.y, wgs.x); final targetLatLng = LatLng(wgs.y, wgs.x);
final dx = ctrl.eov.value.X - ctrl.targetEovX.value;
final dy = ctrl.eov.value.Y - ctrl.targetEovY.value;
final dist = sqrt(dx * dx + dy * dy);
final onTarget = dist < 0.05;
return Stack(children: [ return Stack(children: [
// Szaggatott vonal // Szaggatott vonal
@ -235,10 +232,8 @@ class _SharedMapWidgetState extends State<SharedMapWidget> {
label: ctrl.targetName.value, label: ctrl.targetName.value,
icon: Icons.flag, icon: Icons.flag,
color: Colors.orange, color: Colors.orange,
activeColor: ctrl.isOnTarget ? Colors.green : null, activeColor: onTarget ? Colors.green : null,
sublabel: ctrl.isOnTarget sublabel: onTarget ? '✓ Célponton' : '${dist.toStringAsFixed(3)} m',
? '✓ Célponton'
: '${ctrl.distanceToTarget.toStringAsFixed(3)} m',
), ),
), ),
]), ]),