// lib/services/gnss/ble_gnss_connection.dart import 'dart:async'; import 'dart:io'; import 'dart:typed_data'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:geolocator/geolocator.dart'; import 'gnss_connection.dart'; import 'gnss_device_service.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 = ''; BluetoothCharacteristic? _rxChar; 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); if (Platform.isAndroid) { await _device!.requestMtu(512); } // 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'), ); _rxChar = gnssService.characteristics.firstWhere((c) => c.characteristicUuid.str.toLowerCase() == _nusRxCharUuid.toLowerCase()); // 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(); } @override void sendData(Uint8List data) { if (_rxChar == null) return; _rxChar!.write(data, withoutResponse: true); } @override Stream get positionStream => const Stream.empty(); }