GNSS eszközhöz való kapcsolódás és bluetooth kommunikáció refraktorálása külön osztályokba
This commit is contained in:
parent
7192fa5322
commit
f1a3753f36
@ -9,6 +9,12 @@
|
|||||||
android:name="com.pravera.flutter_foreground_task.service.ForegroundService"
|
android:name="com.pravera.flutter_foreground_task.service.ForegroundService"
|
||||||
android:foregroundServiceType="location"
|
android:foregroundServiceType="location"
|
||||||
android:stopWithTask="false"/>
|
android:stopWithTask="false"/>
|
||||||
|
<service
|
||||||
|
android:name="com.baseflow.geolocator.GeolocatorLocationService"
|
||||||
|
android:foregroundServiceType="location"
|
||||||
|
android:stopWithTask="false"
|
||||||
|
android:exported="false"/>
|
||||||
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
@ -42,16 +48,18 @@
|
|||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
|
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH"/>
|
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
|
||||||
<!-- <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30"/>
|
<!-- <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30"/>
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30"/> -->
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30"/> -->
|
||||||
<!-- <uses-permission android:name="android.permission.BLUETOOTH" tools:remove="android:maxSdkVersion"/>
|
<!-- <uses-permission android:name="android.permission.BLUETOOTH" tools:remove="android:maxSdkVersion"/>
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" tools:remove="android:maxSdkVersion"/> -->
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" tools:remove="android:maxSdkVersion"/> -->
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
|
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
|
||||||
|
android:usesPermissionFlags="neverForLocation" tools:targetApi="s"/>
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
|
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||||
|
|
||||||
|
|||||||
111
lib/services/gnss/ble_gnss_connection.dart
Normal file
111
lib/services/gnss/ble_gnss_connection.dart
Normal file
@ -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<String>.broadcast();
|
||||||
|
final _stateController = StreamController<GnssConnectionState>.broadcast();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<String> get nmeaLines => _nmeaController.stream;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<GnssConnectionState> get connectionState => _stateController.stream;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> 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<int> 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<void> disconnect() async {
|
||||||
|
await _notifySub?.cancel();
|
||||||
|
await _device?.disconnect();
|
||||||
|
_stateController.add(GnssConnectionState.disconnected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_notifySub?.cancel();
|
||||||
|
_device?.disconnect();
|
||||||
|
_nmeaController.close();
|
||||||
|
_stateController.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
94
lib/services/gnss/bt_serial_gnss_connection.dart
Normal file
94
lib/services/gnss/bt_serial_gnss_connection.dart
Normal file
@ -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<String>.broadcast();
|
||||||
|
final _stateController = StreamController<GnssConnectionState>.broadcast();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<String> get nmeaLines => _nmeaController.stream;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<GnssConnectionState> get connectionState => _stateController.stream;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> 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<void> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
20
lib/services/gnss/gnss_connection.dart
Normal file
20
lib/services/gnss/gnss_connection.dart
Normal file
@ -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<String> get nmeaLines;
|
||||||
|
|
||||||
|
/// Kapcsolat állapota
|
||||||
|
Stream<GnssConnectionState> get connectionState;
|
||||||
|
|
||||||
|
Future<void> connect(String address);
|
||||||
|
Future<void> disconnect();
|
||||||
|
|
||||||
|
void dispose();
|
||||||
|
}
|
||||||
104
lib/services/gnss/gnss_service.dart
Normal file
104
lib/services/gnss/gnss_service.dart
Normal file
@ -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<GnssConnectionType>();
|
||||||
|
|
||||||
|
// 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<void> connectBtSerial(String macAddress) async {
|
||||||
|
await _disconnect();
|
||||||
|
_connection = BtSerialGnssConnection();
|
||||||
|
activeConnectionType.value = GnssConnectionType.btSerial;
|
||||||
|
await _doConnect(macAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> 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<void> _doConnect(String address) async {
|
||||||
|
_stateSub = _connection!.connectionState.listen((s) {
|
||||||
|
connectionState.value = s;
|
||||||
|
});
|
||||||
|
_nmeaSub = _connection!.nmeaLines.listen(_parseNmea);
|
||||||
|
await _connection!.connect(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -70,6 +70,7 @@ dependencies:
|
|||||||
share_plus: ^12.0.1
|
share_plus: ^12.0.1
|
||||||
geolocator: ^14.0.2
|
geolocator: ^14.0.2
|
||||||
flutter_foreground_task: ^9.2.2
|
flutter_foreground_task: ^9.2.2
|
||||||
|
flutter_blue_plus: ^2.3.2
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user