// lib/services/gnss/gnss_service.dart import 'dart:async'; import 'dart:typed_data'; import 'package:get/get.dart'; import 'package:nmea/nmea.dart'; import 'package:terepi_seged/services/gnss/gnss_device_service.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; String _utcTime = ''; String _utcDate = ''; @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 { 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 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; }); _nmeaSub = _connection!.nmeaLines.listen(_parseNmea); await _connection!.connect(address); } Future _disconnect() async { await _nmeaSub?.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 as BtSerialGnssConnection?)?.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(); } }