From 24a6b7d51336d8325df20937da852e7f02e475f0 Mon Sep 17 00:00:00 2001 From: "torok.istvan" Date: Sun, 17 May 2026 15:04:48 +0200 Subject: [PATCH] =?UTF-8?q?GNSS=20szerv=C3=ADz=20refraktor=C3=A1l=C3=A1s,?= =?UTF-8?q?=20hibajav=C3=ADt=C3=A1s,=20PhoneGpsConnection=20oszt=C3=A1ly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/map_survey_controller.dart | 77 ----------------- lib/services/gnss/ble_gnss_connection.dart | 22 +++++ .../gnss/bt_serial_gnss_connection.dart | 48 +++++------ lib/services/gnss/gnss_connection.dart | 7 ++ lib/services/gnss/gnss_device_service.dart | 3 +- lib/services/gnss/gnss_service.dart | 83 ++++++++++++++++++- lib/services/gnss/phone_gps_connection.dart | 74 +++++++++++++++++ lib/widgets/gnss_device_picker_dialog.dart | 19 ++++- 8 files changed, 221 insertions(+), 112 deletions(-) create mode 100644 lib/services/gnss/phone_gps_connection.dart diff --git a/lib/pages/map_survey/presentations/controllers/map_survey_controller.dart b/lib/pages/map_survey/presentations/controllers/map_survey_controller.dart index 58f5a43..b8d47d3 100644 --- a/lib/pages/map_survey/presentations/controllers/map_survey_controller.dart +++ b/lib/pages/map_survey/presentations/controllers/map_survey_controller.dart @@ -3,11 +3,9 @@ import 'dart:convert'; import 'dart:io'; import 'dart:math'; -import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; import 'package:flutter_map/flutter_map.dart'; // import 'package:flutter_map_geojson/flutter_map_geojson.dart'; import 'package:flutter_map_polywidget/flutter_map_polywidget.dart'; @@ -16,25 +14,16 @@ import 'package:get/get.dart'; import 'package:intl/intl.dart'; // import 'package:location/location.dart'; import 'package:latlong2/latlong.dart'; -import 'package:nmea/nmea.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:permission_handler/permission_handler.dart' - as permission_handler; import 'package:share_plus/share_plus.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:terepi_seged/controls/geoid_grid.dart'; import 'package:terepi_seged/eov/convert_coordinate.dart'; import 'package:terepi_seged/eov/eov.dart'; -import 'package:terepi_seged/gnss_sentences/gngga.dart'; -import 'package:terepi_seged/gnss_sentences/gngst.dart'; -import 'package:terepi_seged/gnss_sentences/gnrmc.dart'; -import 'package:terepi_seged/models/measured_point.dart'; import 'package:terepi_seged/models/point_to_measure.dart'; import 'package:terepi_seged/models/point_with_description_model.dart'; -import 'package:proj4dart/proj4dart.dart' as proj4; import 'package:shared_preferences/shared_preferences.dart'; import 'package:terepi_seged/pages/map_survey/presentations/views/measured_points_table_dialog.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:terepi_seged/services/coord_converter_service.dart'; import 'package:terepi_seged/services/gnss/gnss_connection.dart'; import 'package:terepi_seged/services/gnss/gnss_device_service.dart'; @@ -195,7 +184,6 @@ class MapSurveyController extends GetxController { void onReady() async { super.onReady(); - await _initPhoneGps(); await _initStorage(); gpsHeightController.text = '1.8'; @@ -255,11 +243,6 @@ class MapSurveyController extends GetxController { currentLongitude.value = lon; _updateCurrentLocationMarker(); - // Telefon GPS leállítása ha külső GPS van - if (_phoneLocationSub != null) { - _stopPhoneGps(); - } - // NTRIP GGA küldés NtripService.to.onGgaReceived( _gnss.lastGgaLine.value, @@ -271,66 +254,6 @@ class MapSurveyController extends GetxController { // Telefon GPS fallback // ───────────────────────────────────────────────────────────────── - Future _initPhoneGps() async { - // Egyszeri kezdő pozíció - try { - final last = await Geolocator.getLastKnownPosition(); - if (last != null) { - currentLatitude.value = last.latitude; - currentLongitude.value = last.longitude; - GnssService.to.latitude.value = last.latitude; - GnssService.to.longitude.value = last.longitude; - GnssService.to.gpsQuality.value = 1; - mapController.move( - LatLng(last.latitude, last.longitude), - currentZoom.value, - ); - _updateCurrentLocationMarker(); - } - } catch (_) {} - - // Folyamatos stream ha nincs külső GPS - if (_gnss.connectionState.value != GnssConnectionState.connected) { - await _startPhoneGps(); - } - } - - Future _startPhoneGps() async { - if (_phoneLocationSub != null) return; - - final permission = await Geolocator.checkPermission(); - if (permission == LocationPermission.denied || - permission == LocationPermission.deniedForever) return; - - final serviceEnabled = await Geolocator.isLocationServiceEnabled(); - if (!serviceEnabled) return; - - _phoneLocationSub = Geolocator.getPositionStream( - locationSettings: const LocationSettings( - accuracy: LocationAccuracy.high, - distanceFilter: 0, - ), - ).listen((pos) { - // Leáll ha közben csatlakozott a külső GPS - if (_gnss.connectionState.value == GnssConnectionState.connected) { - _stopPhoneGps(); - return; - } - currentLatitude.value = pos.latitude; - currentLongitude.value = pos.longitude; - GnssService.to.latitude.value = pos.latitude; - GnssService.to.longitude.value = pos.longitude; - GnssService.to.gpsQuality.value = 1; - _updateCurrentLocationMarker(); - }); - } - - void _stopPhoneGps() { - _phoneLocationSub?.cancel(); - _phoneLocationSub = null; - GnssService.to.gpsQuality.value = 0; - } - // ───────────────────────────────────────────────────────────────── // Térkép vezérlők // ───────────────────────────────────────────────────────────────── diff --git a/lib/services/gnss/ble_gnss_connection.dart b/lib/services/gnss/ble_gnss_connection.dart index 1b95289..61b93f5 100644 --- a/lib/services/gnss/ble_gnss_connection.dart +++ b/lib/services/gnss/ble_gnss_connection.dart @@ -1,6 +1,9 @@ // lib/services/gnss/ble_gnss_connection.dart import 'dart:async'; +import 'dart:io'; +import 'dart:typed_data'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; +import 'package:geolocator/geolocator.dart'; import 'gnss_connection.dart'; import 'gnss_device_service.dart'; @@ -24,6 +27,7 @@ class BleGnssConnection implements GnssConnection { BluetoothDevice? _device; StreamSubscription? _notifySub; String _lineBuffer = ''; + BluetoothCharacteristic? _rxChar; final _nmeaController = StreamController.broadcast(); final _stateController = StreamController.broadcast(); @@ -46,6 +50,10 @@ class BleGnssConnection implements GnssConnection { autoConnect: false, license: License.free); + if (Platform.isAndroid) { + await _device!.requestMtu(512); + } + // Kapcsolat megszakadás figyelése _device!.connectionState.listen((state) { if (state == BluetoothConnectionState.disconnected) { @@ -68,6 +76,10 @@ class BleGnssConnection implements GnssConnection { orElse: () => throw Exception('TX char nem található: $txCharUuid'), ); + _rxChar = gnssService.characteristics.firstWhere((c) => + c.characteristicUuid.str.toLowerCase() == + _nusRxCharUuid.toLowerCase()); + // Notify engedélyezése await txChar.setNotifyValue(true); @@ -109,4 +121,14 @@ class BleGnssConnection implements GnssConnection { _nmeaController.close(); _stateController.close(); } + + @override + void sendData(Uint8List data) { + if (_rxChar == null) return; + + _rxChar!.write(data, withoutResponse: true); + } + + @override + Stream get positionStream => const Stream.empty(); } diff --git a/lib/services/gnss/bt_serial_gnss_connection.dart b/lib/services/gnss/bt_serial_gnss_connection.dart index b569f19..a541510 100644 --- a/lib/services/gnss/bt_serial_gnss_connection.dart +++ b/lib/services/gnss/bt_serial_gnss_connection.dart @@ -2,8 +2,11 @@ import 'dart:async'; import 'dart:typed_data'; import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; +import 'package:geolocator/geolocator.dart'; +import 'package:get/get.dart'; import 'gnss_connection.dart'; import 'gnss_device_service.dart'; +import 'dart:convert'; class BtSerialGnssConnection implements GnssConnection { @override @@ -11,6 +14,7 @@ class BtSerialGnssConnection implements GnssConnection { BluetoothConnection? _connection; String _messageBuffer = ''; + String _lineBuffer = ''; final _nmeaController = StreamController.broadcast(); final _stateController = StreamController.broadcast(); @@ -48,41 +52,24 @@ class BtSerialGnssConnection implements GnssConnection { // A meglévő _onDataReceived logika változatlanul void _onData(Uint8List data) { - int backspacesCounter = 0; - for (var byte in data) { - if (byte == 8 || byte == 127) backspacesCounter++; - } + _lineBuffer += utf8.decode(data, allowMalformed: true); - Uint8List buffer = Uint8List(data.length - backspacesCounter); - int bufferIndex = buffer.length; - backspacesCounter = 0; + final lines = const LineSplitter().convert(_lineBuffer); - for (int i = data.length - 1; i >= 0; i--) { - if (data[i] == 8 || data[i] == 127) { - backspacesCounter++; - } else if (backspacesCounter > 0) { - backspacesCounter--; - } else { - buffer[--bufferIndex] = data[i]; + for (int i = 0; i < lines.length - 1; i++) { + final sentence = lines[i].trim(); + if (sentence.isNotEmpty) { + _nmeaController.add(sentence); } } - final dataString = String.fromCharCodes(buffer); - final index = buffer.indexOf(13); // \r - - String sentence; - if (~index != 0) { - sentence = _messageBuffer + dataString.substring(0, index); - _messageBuffer = dataString.substring(index); + if (_lineBuffer.endsWith('\n')) { + if (lines.isNotEmpty && lines.last.trim().isNotEmpty) { + _nmeaController.add(lines.last.trim()); + } + _lineBuffer = ''; } else { - _messageBuffer += dataString; - return; - } - - // Soronként kibocsátjuk - for (final line in sentence.split('\n')) { - final trimmed = line.trim(); - if (trimmed.isNotEmpty) _nmeaController.add(trimmed); + _lineBuffer = lines.isNotEmpty ? lines.last : _lineBuffer; } } @@ -96,4 +83,7 @@ class BtSerialGnssConnection implements GnssConnection { void sendData(Uint8List data) { _connection?.output.add(data); } + + @override + Stream get positionStream => const Stream.empty(); } diff --git a/lib/services/gnss/gnss_connection.dart b/lib/services/gnss/gnss_connection.dart index 4815dc3..9f415a1 100644 --- a/lib/services/gnss/gnss_connection.dart +++ b/lib/services/gnss/gnss_connection.dart @@ -1,5 +1,9 @@ // lib/services/gnss/gnss_connection.dart +import 'dart:typed_data'; + +import 'package:geolocator/geolocator.dart'; + import 'gnss_device_service.dart'; enum GnssConnectionState { disconnected, connecting, connected, error } @@ -9,6 +13,7 @@ abstract class GnssConnection { /// NMEA sorok stream-je — mindkét implementáció ezt adja Stream get nmeaLines; + Stream get positionStream; /// Kapcsolat állapota Stream get connectionState; @@ -17,4 +22,6 @@ abstract class GnssConnection { Future disconnect(); void dispose(); + + void sendData(Uint8List data); } diff --git a/lib/services/gnss/gnss_device_service.dart b/lib/services/gnss/gnss_device_service.dart index 1f588e2..a606814 100644 --- a/lib/services/gnss/gnss_device_service.dart +++ b/lib/services/gnss/gnss_device_service.dart @@ -4,7 +4,7 @@ import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:get/get.dart'; import 'package:shared_preferences/shared_preferences.dart'; -enum GnssConnectionType { btSerial, ble, phoneGps } +enum GnssConnectionType { none, btSerial, ble, phoneGps } class GnssDevice { final String address; @@ -23,6 +23,7 @@ class GnssDevice { GnssConnectionType.btSerial => 'BT Serial', GnssConnectionType.ble => 'BLE', GnssConnectionType.phoneGps => 'Telefon GPS', + GnssConnectionType.none => 'Kikapcsolva', }; Map toJson() => { diff --git a/lib/services/gnss/gnss_service.dart b/lib/services/gnss/gnss_service.dart index 95006ef..c97c67f 100644 --- a/lib/services/gnss/gnss_service.dart +++ b/lib/services/gnss/gnss_service.dart @@ -2,9 +2,11 @@ import 'dart:async'; import 'dart:typed_data'; +import 'package:geolocator/geolocator.dart'; import 'package:get/get.dart'; import 'package:nmea/nmea.dart'; import 'package:terepi_seged/services/gnss/gnss_device_service.dart'; +import 'package:terepi_seged/services/gnss/phone_gps_connection.dart'; import '../../gnss_sentences/gngga.dart'; import '../../gnss_sentences/gngst.dart'; @@ -50,9 +52,13 @@ class GnssService extends GetxService { final NmeaDecoder _decoder = NmeaDecoder(); StreamSubscription? _nmeaSub; StreamSubscription? _stateSub; + StreamSubscription? _positionSub; + String _utcTime = ''; String _utcDate = ''; + bool _intentionalDisconnection = false; + @override void onInit() { super.onInit(); @@ -86,16 +92,28 @@ class GnssService extends GetxService { } /// Eszközváltás — GnssDevicePicker hívja. - Future onDeviceChanged(GnssDevice device) async { + Future onDeviceChanged(GnssDevice? device) async { + if (device == null) { + await _disconnect(); + activeConnectionType.value = GnssConnectionType.none; + return; + } + switch (device.type) { + case GnssConnectionType.none: + await _disconnect(); + activeConnectionType.value = GnssConnectionType.none; + break; case GnssConnectionType.btSerial: await connectBtSerial(device.address); case GnssConnectionType.ble: await connectBle(device.address); case GnssConnectionType.phoneGps: await _disconnect(); - connectionState.value = GnssConnectionState.disconnected; + _connection = PhoneGpsConnection(); + //connectionState.value = GnssConnectionState.disconnected; activeConnectionType.value = GnssConnectionType.phoneGps; + await _doConnect('iternal'); } } @@ -109,13 +127,40 @@ class GnssService extends GetxService { Future _doConnect(String address) async { _stateSub = _connection!.connectionState.listen((s) { connectionState.value = s; + if (s == GnssConnectionState.disconnected && + _intentionalDisconnection == false) { + Future.delayed(const Duration(seconds: 3), () => reconnect()); + } }); + _nmeaSub = _connection!.nmeaLines.listen(_parseNmea); + _positionSub = _connection!.positionStream.listen(_parseDirectPosition); + + _intentionalDisconnection = false; await _connection!.connect(address); } + void _parseDirectPosition(Position pos) { + latitude.value = pos.latitude; + longitude.value = pos.longitude; + altitude.value = pos.altitude; + + gpsQuality.value = 1; // 1 = Standard (nem-RTK) minőség + satelliteCount.value = 0; // A Geolocator nem ad műholdszámot direktben + + // A Geolocator a pontosságot (accuracy) méterben adja vissza + latitudeError.value = pos.accuracy; + longitudeError.value = pos.accuracy; + altitudeError.value = pos.altitudeAccuracy ?? 0.0; + + // Az RMC (idő) adatait is beállítjuk a telefon idejéből + gpsDateTime.value = pos.timestamp; + } + Future _disconnect() async { + _intentionalDisconnection = true; await _nmeaSub?.cancel(); + await _positionSub?.cancel(); await _stateSub?.cancel(); await _connection?.disconnect(); _connection?.dispose(); @@ -129,7 +174,7 @@ class GnssService extends GetxService { void sendToReceiver(Uint8List data) { if (_connection == null) return; if (connectionState.value != GnssConnectionState.connected) return; - (_connection as BtSerialGnssConnection?)?.sendData(data); + _connection?.sendData(data); } // ── NMEA parsing ────────────────────────────────────────────────── @@ -196,4 +241,36 @@ class GnssService extends GetxService { _disconnect(); super.onClose(); } + + Future determineInitialPosition() async { + // 1. Ha már van élő adatunk (pl. a külső vevő már küldött koordinátát), + // akkor nincs szükség extra lekérdezésre. + if (latitude.value != 0 && longitude.value != 0) return; + + try { + // 2. Gyors lekérdezés: megkérdezzük a telefont, hol voltunk utoljára. + // Ez szinte azonnal visszatér, nem pörgeti fel a GPS chipet. + final lastPosition = await Geolocator.getLastKnownPosition(); + + if (lastPosition != null) { + latitude.value = lastPosition.latitude; + longitude.value = lastPosition.longitude; + return; + } + + // 3. Ha nincs utolsó ismert pozíció (pl. friss telepítés), + // kérünk egy friss pozíciót, de alacsony pontossággal, hogy gyors legyen. + final currentPosition = await Geolocator.getCurrentPosition( + desiredAccuracy: LocationAccuracy.low, + timeLimit: const Duration(seconds: 3), // Ne akassza meg az appot sokáig + ); + + latitude.value = currentPosition.latitude; + longitude.value = currentPosition.longitude; + } catch (e) { + // Engedélyhiány vagy kikapcsolt helymeghatározás esetén a térkép + // marad a (0,0)-n vagy egy alapértelmezett (pl. budapesti) koordinátán. + print('Nem sikerült lekérni a kezdőpozíciót: $e'); + } + } } diff --git a/lib/services/gnss/phone_gps_connection.dart b/lib/services/gnss/phone_gps_connection.dart new file mode 100644 index 0000000..db95ac1 --- /dev/null +++ b/lib/services/gnss/phone_gps_connection.dart @@ -0,0 +1,74 @@ +// lib/services/gnss/phone_gps_connection.dart +import 'dart:async'; +import 'dart:typed_data'; +import 'package:geolocator/geolocator.dart'; +import 'gnss_connection.dart'; +import 'gnss_device_service.dart'; + +class PhoneGpsConnection implements GnssConnection { + @override + GnssConnectionType get type => GnssConnectionType.phoneGps; + + final _stateController = StreamController.broadcast(); + final _positionController = StreamController.broadcast(); + StreamSubscription? _positionSub; + + @override + Stream get nmeaLines => const Stream.empty(); // Nincs NMEA + + @override + Stream get positionStream => _positionController.stream; + + @override + Stream get connectionState => _stateController.stream; + + @override + Future connect(String address) async { + _stateController.add(GnssConnectionState.connecting); + + bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); + if (!serviceEnabled) { + _stateController.add(GnssConnectionState.error); + return; + } + + LocationPermission permission = await Geolocator.checkPermission(); + if (permission == LocationPermission.denied) { + permission = await Geolocator.requestPermission(); + if (permission == LocationPermission.denied) { + _stateController.add(GnssConnectionState.error); + return; + } + } + + _stateController.add(GnssConnectionState.connected); + + // Belső GPS folyamatos olvasása + _positionSub = Geolocator.getPositionStream( + locationSettings: const LocationSettings( + accuracy: LocationAccuracy.high, + distanceFilter: 0, // Folyamatos frissítés + ), + ).listen((Position pos) { + _positionController.add(pos); + }); + } + + @override + Future disconnect() async { + await _positionSub?.cancel(); + _stateController.add(GnssConnectionState.disconnected); + } + + @override + void sendData(Uint8List data) { + // A telefon beépített GPS-e nem fogad RTCM adatokat + } + + @override + void dispose() { + _positionSub?.cancel(); + _positionController.close(); + _stateController.close(); + } +} diff --git a/lib/widgets/gnss_device_picker_dialog.dart b/lib/widgets/gnss_device_picker_dialog.dart index ccb4acf..d1a0a97 100644 --- a/lib/widgets/gnss_device_picker_dialog.dart +++ b/lib/widgets/gnss_device_picker_dialog.dart @@ -55,7 +55,18 @@ class GnssDevicePickerDialog extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Telefon GPS opció + // 1. ÚJ: GPS kikapcsolása opció + _DeviceTile( + device: const GnssDevice( + address: 'none', + name: 'GPS kikapcsolása (Térkép mód)', + type: GnssConnectionType.none, + ), + ), + + const SizedBox(height: 8), + + // 2. Telefon GPS opció _DeviceTile( device: const GnssDevice( address: 'phone', @@ -210,7 +221,9 @@ class _DeviceTile extends StatelessWidget { style: const TextStyle(fontSize: 11, color: Colors.grey), ), ], - if (device.type != GnssConnectionType.phoneGps) + // A "none" és a "phoneGps" esetén ne írjunk ki kamucímeket (mint a "phone" vagy "none") + if (device.type != GnssConnectionType.phoneGps && + device.type != GnssConnectionType.none) Padding( padding: const EdgeInsets.only(left: 6), child: Text( @@ -231,10 +244,12 @@ class _DeviceTile extends StatelessWidget { }); } + // Bővítettük a switch utasítást a none opcióval IconData _iconFor(GnssConnectionType type) => switch (type) { GnssConnectionType.btSerial => Icons.bluetooth, GnssConnectionType.ble => Icons.bluetooth_searching, GnssConnectionType.phoneGps => Icons.phone_android, + GnssConnectionType.none => Icons.gps_off, }; }