diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 0378876..817fb17 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -9,6 +9,12 @@
android:name="com.pravera.flutter_foreground_task.service.ForegroundService"
android:foregroundServiceType="location"
android:stopWithTask="false"/>
+
+
+
-
-
-
-
+
+
+
+
-
+
diff --git a/lib/services/gnss/ble_gnss_connection.dart b/lib/services/gnss/ble_gnss_connection.dart
new file mode 100644
index 0000000..d222d0d
--- /dev/null
+++ b/lib/services/gnss/ble_gnss_connection.dart
@@ -0,0 +1,111 @@
+// lib/services/gnss/ble_gnss_connection.dart
+import 'dart:async';
+import 'package:flutter_blue_plus/flutter_blue_plus.dart';
+import 'gnss_connection.dart';
+
+// Nordic UART Service — a legelterjedtebb BLE UART profil
+const _nusServiceUuid = '6e400001-b5b3-f393-e0a9-e50e24dcca9e';
+const _nusTxCharUuid = '6e400003-b5b3-f393-e0a9-e50e24dcca9e'; // notify
+const _nusRxCharUuid = '6e400002-b5b3-f393-e0a9-e50e24dcca9e'; // write
+
+class BleGnssConnection implements GnssConnection {
+ @override
+ GnssConnectionType get type => GnssConnectionType.ble;
+
+ final String serviceUuid;
+ final String txCharUuid; // notify (eszköz → telefon)
+
+ BleGnssConnection({
+ this.serviceUuid = _nusServiceUuid,
+ this.txCharUuid = _nusTxCharUuid,
+ });
+
+ BluetoothDevice? _device;
+ StreamSubscription? _notifySub;
+ String _lineBuffer = '';
+
+ final _nmeaController = StreamController.broadcast();
+ final _stateController = StreamController.broadcast();
+
+ @override
+ Stream get nmeaLines => _nmeaController.stream;
+
+ @override
+ Stream get connectionState => _stateController.stream;
+
+ @override
+ Future connect(String address) async {
+ _stateController.add(GnssConnectionState.connecting);
+
+ try {
+ _device = BluetoothDevice.fromId(address);
+
+ await _device!.connect(
+ timeout: const Duration(seconds: 10),
+ autoConnect: false,
+ license: License.free);
+
+ // Kapcsolat megszakadás figyelése
+ _device!.connectionState.listen((state) {
+ if (state == BluetoothConnectionState.disconnected) {
+ _stateController.add(GnssConnectionState.disconnected);
+ }
+ });
+
+ // Service discovery
+ final services = await _device!.discoverServices();
+ final gnssService = services.firstWhere(
+ (s) => s.serviceUuid.str.toLowerCase() == serviceUuid.toLowerCase(),
+ orElse: () =>
+ throw Exception('GNSS service nem található: $serviceUuid'),
+ );
+
+ // TX karakterisztika (notify) — eszköztől jönnek az NMEA sorok
+ final txChar = gnssService.characteristics.firstWhere(
+ (c) =>
+ c.characteristicUuid.str.toLowerCase() == txCharUuid.toLowerCase(),
+ orElse: () => throw Exception('TX char nem található: $txCharUuid'),
+ );
+
+ // Notify engedélyezése
+ await txChar.setNotifyValue(true);
+
+ _notifySub = txChar.onValueReceived.listen(_onBleData);
+
+ _stateController.add(GnssConnectionState.connected);
+ } catch (e) {
+ _stateController.add(GnssConnectionState.error);
+ rethrow;
+ }
+ }
+
+ void _onBleData(List bytes) {
+ // BLE csomagok kis méretűek (≤20 byte MTU default),
+ // az NMEA mondatokat össze kell fűzni
+ final chunk = String.fromCharCodes(bytes);
+ _lineBuffer += chunk;
+
+ // Teljes sorok kibocsátása
+ while (_lineBuffer.contains('\n')) {
+ final idx = _lineBuffer.indexOf('\n');
+ final line = _lineBuffer.substring(0, idx).trim();
+ _lineBuffer = _lineBuffer.substring(idx + 1);
+ if (line.isNotEmpty) _nmeaController.add(line);
+ }
+ }
+
+ @override
+ Future disconnect() async {
+ await _notifySub?.cancel();
+ await _device?.disconnect();
+ _stateController.add(GnssConnectionState.disconnected);
+ }
+
+ @override
+ void dispose() {
+ _notifySub?.cancel();
+ _device?.disconnect();
+ _nmeaController.close();
+ _stateController.close();
+ }
+}
diff --git a/lib/services/gnss/bt_serial_gnss_connection.dart b/lib/services/gnss/bt_serial_gnss_connection.dart
new file mode 100644
index 0000000..c7c62b0
--- /dev/null
+++ b/lib/services/gnss/bt_serial_gnss_connection.dart
@@ -0,0 +1,94 @@
+// lib/services/gnss/bt_serial_gnss_connection.dart
+import 'dart:async';
+import 'dart:typed_data';
+import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
+import 'gnss_connection.dart';
+
+class BtSerialGnssConnection implements GnssConnection {
+ @override
+ GnssConnectionType get type => GnssConnectionType.btSerial;
+
+ BluetoothConnection? _connection;
+ String _messageBuffer = '';
+
+ final _nmeaController = StreamController.broadcast();
+ final _stateController = StreamController.broadcast();
+
+ @override
+ Stream get nmeaLines => _nmeaController.stream;
+
+ @override
+ Stream get connectionState => _stateController.stream;
+
+ @override
+ Future connect(String address) async {
+ _stateController.add(GnssConnectionState.connecting);
+ try {
+ _connection = await BluetoothConnection.toAddress(address);
+ _stateController.add(GnssConnectionState.connected);
+ _connection!.input!.listen(
+ _onData,
+ onDone: () {
+ _stateController.add(GnssConnectionState.disconnected);
+ },
+ );
+ } catch (e) {
+ _stateController.add(GnssConnectionState.error);
+ rethrow;
+ }
+ }
+
+ @override
+ Future disconnect() async {
+ await _connection?.close();
+ _connection = null;
+ _stateController.add(GnssConnectionState.disconnected);
+ }
+
+ // 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++;
+ }
+
+ Uint8List buffer = Uint8List(data.length - backspacesCounter);
+ int bufferIndex = buffer.length;
+ backspacesCounter = 0;
+
+ 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];
+ }
+ }
+
+ 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);
+ } 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);
+ }
+ }
+
+ @override
+ void dispose() {
+ _connection?.close();
+ _nmeaController.close();
+ _stateController.close();
+ }
+}
diff --git a/lib/services/gnss/gnss_connection.dart b/lib/services/gnss/gnss_connection.dart
new file mode 100644
index 0000000..d0683ad
--- /dev/null
+++ b/lib/services/gnss/gnss_connection.dart
@@ -0,0 +1,20 @@
+// lib/services/gnss/gnss_connection.dart
+
+enum GnssConnectionType { btSerial, ble }
+
+enum GnssConnectionState { disconnected, connecting, connected, error }
+
+abstract class GnssConnection {
+ GnssConnectionType get type;
+
+ /// NMEA sorok stream-je — mindkét implementáció ezt adja
+ Stream get nmeaLines;
+
+ /// Kapcsolat állapota
+ Stream get connectionState;
+
+ Future connect(String address);
+ Future disconnect();
+
+ void dispose();
+}
diff --git a/lib/services/gnss/gnss_service.dart b/lib/services/gnss/gnss_service.dart
new file mode 100644
index 0000000..06c6def
--- /dev/null
+++ b/lib/services/gnss/gnss_service.dart
@@ -0,0 +1,104 @@
+// lib/services/gnss/gnss_service.dart
+import 'dart:async';
+import 'package:get/get.dart';
+import 'package:nmea/nmea.dart';
+import '../../gnss_sentences/gngga.dart';
+import 'gnss_connection.dart';
+import 'bt_serial_gnss_connection.dart';
+import 'ble_gnss_connection.dart';
+
+class GnssService extends GetxService {
+ static GnssService get to => Get.find();
+
+ GnssConnection? _connection;
+
+ // Reaktív állapot — a controllerek Obx-szel figyelhetik
+ final connectionState = GnssConnectionState.disconnected.obs;
+ final activeConnectionType = Rxn();
+
+ // Parsed NMEA adatok
+ final latitude = 0.0.obs;
+ final longitude = 0.0.obs;
+ final altitude = 0.0.obs;
+ final geoidSeparation = 0.0.obs;
+ final gpsQuality = 0.obs;
+ final utcFix = ''.obs;
+ final satelliteCount = 0.obs;
+ final hdop = 0.0.obs;
+
+ final NmeaDecoder _decoder = NmeaDecoder();
+ StreamSubscription? _nmeaSub;
+ StreamSubscription? _stateSub;
+
+ @override
+ void onInit() {
+ super.onInit();
+ _decoder.registerTalkerSentence('GGA', (l) => Gngga(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);
+ }
+
+ 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;
+ }
+
+ // ── NMEA parsing — egy helyen, nem háromban ──────────────────────
+
+ void _parseNmea(String line) {
+ if (!line.startsWith('\$GNGGA') && !line.startsWith('\$GPGGA')) return;
+
+ try {
+ final sentence = _decoder.decode(line);
+ if (sentence == null || !sentence.valid || sentence is! Gngga) return;
+ if (sentence.gpsQualityIndicator == 0) return;
+
+ latitude.value = sentence.latitude;
+ longitude.value = sentence.longitude;
+ altitude.value = sentence.altitudeAboveMeanSeaLevel;
+ geoidSeparation.value = sentence.geoidSeparation;
+ gpsQuality.value = sentence.gpsQualityIndicator;
+ utcFix.value = sentence.utcOfPositionFix;
+ satelliteCount.value = sentence.numberOfSvsInUse;
+ hdop.value = sentence.hdop;
+ } catch (_) {}
+ }
+
+ @override
+ void onClose() {
+ _disconnect();
+ super.onClose();
+ }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 71b9001..c0ec42e 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -70,6 +70,7 @@ dependencies:
share_plus: ^12.0.1
geolocator: ^14.0.2
flutter_foreground_task: ^9.2.2
+ flutter_blue_plus: ^2.3.2
flutter:
sdk: flutter