249 lines
8.4 KiB
Dart
249 lines
8.4 KiB
Dart
// 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);
|
|
}
|