// lib/services/gnss/gnss_service.dart 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'; import '../../gnss_sentences/gnrmc.dart'; import 'bt_serial_gnss_connection.dart'; import 'ble_gnss_connection.dart'; import 'gnss_connection.dart'; class GnssService extends GetxService { static GnssService get to => Get.find(); GnssConnection? _connection; // ── Kapcsolat állapot ───────────────────────────────────────────── final connectionState = GnssConnectionState.disconnected.obs; final activeConnectionType = Rxn(); // ── GGA adatok ──────────────────────────────────────────────────── final latitude = 0.0.obs; final longitude = 0.0.obs; final altitude = 0.0.obs; // MSL (ortometrikus) final geoidSeparation = 0.0.obs; // N — geoid undulációja final gpsQuality = 0.obs; final utcFix = ''.obs; final satelliteCount = 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(); StreamSubscription? _nmeaSub; StreamSubscription? _stateSub; StreamSubscription? _positionSub; String _utcTime = ''; String _utcDate = ''; bool _intentionalDisconnection = false; @override void onInit() { super.onInit(); _decoder ..registerTalkerSentence('GGA', (l) => Gngga(raw: l)) ..registerTalkerSentence('GST', (l) => Gngst(raw: l)) ..registerTalkerSentence('RMC', (l) => Gnrmc(raw: l)); } // ── Kapcsolódás ─────────────────────────────────────────────────── Future connectBtSerial(String macAddress) async { await _disconnect(); _connection = BtSerialGnssConnection(); activeConnectionType.value = GnssConnectionType.btSerial; await _doConnect(macAddress); } Future connectBle( String deviceId, { String? serviceUuid, String? txCharUuid, }) async { await _disconnect(); _connection = BleGnssConnection( serviceUuid: serviceUuid ?? '6e400001-b5b3-f393-e0a9-e50e24dcca9e', txCharUuid: txCharUuid ?? '6e400003-b5b3-f393-e0a9-e50e24dcca9e', ); activeConnectionType.value = GnssConnectionType.ble; await _doConnect(deviceId); } /// Eszközváltás — GnssDevicePicker hívja. 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(); _connection = PhoneGpsConnection(); //connectionState.value = GnssConnectionState.disconnected; activeConnectionType.value = GnssConnectionType.phoneGps; await _doConnect('iternal'); } } Future reconnect() async { final device = GnssDeviceService.to.selectedDevice.value; if (device == null) return; await _disconnect(); await onDeviceChanged(device); } 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(); _connection = null; connectionState.value = GnssConnectionState.disconnected; } Future 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?.sendData(data); } // ── NMEA parsing ────────────────────────────────────────────────── void _parseNmea(String line) { 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 { final s = _decoder.decode(line); if (s == null || !s.valid || s is! Gngga) return; if (s.gpsQualityIndicator == 0) return; latitude.value = s.latitude; longitude.value = s.longitude; altitude.value = s.altitudeAboveMeanSeaLevel; geoidSeparation.value = s.geoidSeparation; gpsQuality.value = s.gpsQualityIndicator; utcFix.value = s.utcOfPositionFix; satelliteCount.value = s.numberOfSvsInUse; 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 (_) {} } @override void onClose() { _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'); } } }