// 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 = 'elgi03'.obs; final password = 'StEfan14'.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 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 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 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 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 _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') ?? 'elgi01'; password.value = prefs.getString('ntrip_password') ?? 'StEfan14'; } 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); }