GNSS szervíz refraktorálás, hibajavítás, PhoneGpsConnection osztály

This commit is contained in:
2026-05-17 15:04:48 +02:00
parent ee0f90e247
commit 24a6b7d513
8 changed files with 221 additions and 112 deletions
@@ -1,6 +1,9 @@
// 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';
@@ -24,6 +27,7 @@ class BleGnssConnection implements GnssConnection {
BluetoothDevice? _device;
StreamSubscription? _notifySub;
String _lineBuffer = '';
BluetoothCharacteristic? _rxChar;
final _nmeaController = StreamController<String>.broadcast();
final _stateController = StreamController<GnssConnectionState>.broadcast();
@@ -46,6 +50,10 @@ class BleGnssConnection implements GnssConnection {
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) {
@@ -68,6 +76,10 @@ class BleGnssConnection implements GnssConnection {
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);
@@ -109,4 +121,14 @@ class BleGnssConnection implements GnssConnection {
_nmeaController.close();
_stateController.close();
}
@override
void sendData(Uint8List data) {
if (_rxChar == null) return;
_rxChar!.write(data, withoutResponse: true);
}
@override
Stream<Position> get positionStream => const Stream.empty();
}
@@ -2,8 +2,11 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'gnss_connection.dart';
import 'gnss_device_service.dart';
import 'dart:convert';
class BtSerialGnssConnection implements GnssConnection {
@override
@@ -11,6 +14,7 @@ class BtSerialGnssConnection implements GnssConnection {
BluetoothConnection? _connection;
String _messageBuffer = '';
String _lineBuffer = '';
final _nmeaController = StreamController<String>.broadcast();
final _stateController = StreamController<GnssConnectionState>.broadcast();
@@ -48,41 +52,24 @@ class BtSerialGnssConnection implements GnssConnection {
// 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++;
}
_lineBuffer += utf8.decode(data, allowMalformed: true);
Uint8List buffer = Uint8List(data.length - backspacesCounter);
int bufferIndex = buffer.length;
backspacesCounter = 0;
final lines = const LineSplitter().convert(_lineBuffer);
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];
for (int i = 0; i < lines.length - 1; i++) {
final sentence = lines[i].trim();
if (sentence.isNotEmpty) {
_nmeaController.add(sentence);
}
}
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);
if (_lineBuffer.endsWith('\n')) {
if (lines.isNotEmpty && lines.last.trim().isNotEmpty) {
_nmeaController.add(lines.last.trim());
}
_lineBuffer = '';
} 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);
_lineBuffer = lines.isNotEmpty ? lines.last : _lineBuffer;
}
}
@@ -96,4 +83,7 @@ class BtSerialGnssConnection implements GnssConnection {
void sendData(Uint8List data) {
_connection?.output.add(data);
}
@override
Stream<Position> get positionStream => const Stream.empty();
}
+7
View File
@@ -1,5 +1,9 @@
// lib/services/gnss/gnss_connection.dart
import 'dart:typed_data';
import 'package:geolocator/geolocator.dart';
import 'gnss_device_service.dart';
enum GnssConnectionState { disconnected, connecting, connected, error }
@@ -9,6 +13,7 @@ abstract class GnssConnection {
/// NMEA sorok stream-je — mindkét implementáció ezt adja
Stream<String> get nmeaLines;
Stream<Position> get positionStream;
/// Kapcsolat állapota
Stream<GnssConnectionState> get connectionState;
@@ -17,4 +22,6 @@ abstract class GnssConnection {
Future<void> disconnect();
void dispose();
void sendData(Uint8List data);
}
+2 -1
View File
@@ -4,7 +4,7 @@ import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
enum GnssConnectionType { btSerial, ble, phoneGps }
enum GnssConnectionType { none, btSerial, ble, phoneGps }
class GnssDevice {
final String address;
@@ -23,6 +23,7 @@ class GnssDevice {
GnssConnectionType.btSerial => 'BT Serial',
GnssConnectionType.ble => 'BLE',
GnssConnectionType.phoneGps => 'Telefon GPS',
GnssConnectionType.none => 'Kikapcsolva',
};
Map<String, dynamic> toJson() => {
+80 -3
View File
@@ -2,9 +2,11 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:nmea/nmea.dart';
import 'package:terepi_seged/services/gnss/gnss_device_service.dart';
import 'package:terepi_seged/services/gnss/phone_gps_connection.dart';
import '../../gnss_sentences/gngga.dart';
import '../../gnss_sentences/gngst.dart';
@@ -50,9 +52,13 @@ class GnssService extends GetxService {
final NmeaDecoder _decoder = NmeaDecoder();
StreamSubscription? _nmeaSub;
StreamSubscription? _stateSub;
StreamSubscription<Position>? _positionSub;
String _utcTime = '';
String _utcDate = '';
bool _intentionalDisconnection = false;
@override
void onInit() {
super.onInit();
@@ -86,16 +92,28 @@ class GnssService extends GetxService {
}
/// Eszközváltás — GnssDevicePicker hívja.
Future<void> onDeviceChanged(GnssDevice device) async {
Future<void> onDeviceChanged(GnssDevice? device) async {
if (device == null) {
await _disconnect();
activeConnectionType.value = GnssConnectionType.none;
return;
}
switch (device.type) {
case GnssConnectionType.none:
await _disconnect();
activeConnectionType.value = GnssConnectionType.none;
break;
case GnssConnectionType.btSerial:
await connectBtSerial(device.address);
case GnssConnectionType.ble:
await connectBle(device.address);
case GnssConnectionType.phoneGps:
await _disconnect();
connectionState.value = GnssConnectionState.disconnected;
_connection = PhoneGpsConnection();
//connectionState.value = GnssConnectionState.disconnected;
activeConnectionType.value = GnssConnectionType.phoneGps;
await _doConnect('iternal');
}
}
@@ -109,13 +127,40 @@ class GnssService extends GetxService {
Future<void> _doConnect(String address) async {
_stateSub = _connection!.connectionState.listen((s) {
connectionState.value = s;
if (s == GnssConnectionState.disconnected &&
_intentionalDisconnection == false) {
Future.delayed(const Duration(seconds: 3), () => reconnect());
}
});
_nmeaSub = _connection!.nmeaLines.listen(_parseNmea);
_positionSub = _connection!.positionStream.listen(_parseDirectPosition);
_intentionalDisconnection = false;
await _connection!.connect(address);
}
void _parseDirectPosition(Position pos) {
latitude.value = pos.latitude;
longitude.value = pos.longitude;
altitude.value = pos.altitude;
gpsQuality.value = 1; // 1 = Standard (nem-RTK) minőség
satelliteCount.value = 0; // A Geolocator nem ad műholdszámot direktben
// A Geolocator a pontosságot (accuracy) méterben adja vissza
latitudeError.value = pos.accuracy;
longitudeError.value = pos.accuracy;
altitudeError.value = pos.altitudeAccuracy ?? 0.0;
// Az RMC (idő) adatait is beállítjuk a telefon idejéből
gpsDateTime.value = pos.timestamp;
}
Future<void> _disconnect() async {
_intentionalDisconnection = true;
await _nmeaSub?.cancel();
await _positionSub?.cancel();
await _stateSub?.cancel();
await _connection?.disconnect();
_connection?.dispose();
@@ -129,7 +174,7 @@ class GnssService extends GetxService {
void sendToReceiver(Uint8List data) {
if (_connection == null) return;
if (connectionState.value != GnssConnectionState.connected) return;
(_connection as BtSerialGnssConnection?)?.sendData(data);
_connection?.sendData(data);
}
// ── NMEA parsing ──────────────────────────────────────────────────
@@ -196,4 +241,36 @@ class GnssService extends GetxService {
_disconnect();
super.onClose();
}
Future<void> determineInitialPosition() async {
// 1. Ha már van élő adatunk (pl. a külső vevő már küldött koordinátát),
// akkor nincs szükség extra lekérdezésre.
if (latitude.value != 0 && longitude.value != 0) return;
try {
// 2. Gyors lekérdezés: megkérdezzük a telefont, hol voltunk utoljára.
// Ez szinte azonnal visszatér, nem pörgeti fel a GPS chipet.
final lastPosition = await Geolocator.getLastKnownPosition();
if (lastPosition != null) {
latitude.value = lastPosition.latitude;
longitude.value = lastPosition.longitude;
return;
}
// 3. Ha nincs utolsó ismert pozíció (pl. friss telepítés),
// kérünk egy friss pozíciót, de alacsony pontossággal, hogy gyors legyen.
final currentPosition = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.low,
timeLimit: const Duration(seconds: 3), // Ne akassza meg az appot sokáig
);
latitude.value = currentPosition.latitude;
longitude.value = currentPosition.longitude;
} catch (e) {
// Engedélyhiány vagy kikapcsolt helymeghatározás esetén a térkép
// marad a (0,0)-n vagy egy alapértelmezett (pl. budapesti) koordinátán.
print('Nem sikerült lekérni a kezdőpozíciót: $e');
}
}
}
@@ -0,0 +1,74 @@
// lib/services/gnss/phone_gps_connection.dart
import 'dart:async';
import 'dart:typed_data';
import 'package:geolocator/geolocator.dart';
import 'gnss_connection.dart';
import 'gnss_device_service.dart';
class PhoneGpsConnection implements GnssConnection {
@override
GnssConnectionType get type => GnssConnectionType.phoneGps;
final _stateController = StreamController<GnssConnectionState>.broadcast();
final _positionController = StreamController<Position>.broadcast();
StreamSubscription<Position>? _positionSub;
@override
Stream<String> get nmeaLines => const Stream.empty(); // Nincs NMEA
@override
Stream<Position> get positionStream => _positionController.stream;
@override
Stream<GnssConnectionState> get connectionState => _stateController.stream;
@override
Future<void> connect(String address) async {
_stateController.add(GnssConnectionState.connecting);
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
_stateController.add(GnssConnectionState.error);
return;
}
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
_stateController.add(GnssConnectionState.error);
return;
}
}
_stateController.add(GnssConnectionState.connected);
// Belső GPS folyamatos olvasása
_positionSub = Geolocator.getPositionStream(
locationSettings: const LocationSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 0, // Folyamatos frissítés
),
).listen((Position pos) {
_positionController.add(pos);
});
}
@override
Future<void> disconnect() async {
await _positionSub?.cancel();
_stateController.add(GnssConnectionState.disconnected);
}
@override
void sendData(Uint8List data) {
// A telefon beépített GPS-e nem fogad RTCM adatokat
}
@override
void dispose() {
_positionSub?.cancel();
_positionController.close();
_stateController.close();
}
}