bluetooth connectivity layer added
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../transport/bluetooth/bluetooth_classic_transport.dart';
|
||||
import '../../transport/bluetooth/bluetooth_device_info.dart';
|
||||
import '../../transport/link_transport.dart';
|
||||
import 'database_provider.dart';
|
||||
|
||||
/// App-lebenslanger Bluetooth-Classic-Transport (Doku Kommunikationsschicht
|
||||
/// Abschnitt 3), verdrahtet gegen die gemerkte Geraete-MAC in den
|
||||
/// Einstellungen. Bewusst nicht autoDispose: die Verbindung soll bestehen
|
||||
/// bleiben, waehrend zwischen Fly- und Plan-Modus gewechselt wird, und erst
|
||||
/// beim Verlassen des Fly-Modus explizit getrennt werden (Doku 3,
|
||||
/// Robustheit: "Verbindung beim Verlassen des Fly-Modus sauber schliessen").
|
||||
final bluetoothTransportProvider = Provider<BluetoothClassicTransport>((ref) {
|
||||
final settingsRepo = ref.watch(appSettingsRepositoryProvider);
|
||||
final transport = BluetoothClassicTransport(
|
||||
getRememberedAddress: settingsRepo.loadConnectionDeviceAddress,
|
||||
rememberAddress: settingsRepo.setConnectionDeviceAddress,
|
||||
);
|
||||
ref.onDispose(transport.dispose);
|
||||
return transport;
|
||||
});
|
||||
|
||||
/// Reaktiver [LinkState] des Bluetooth-Transports fuer die Oberflaeche
|
||||
/// (Verbindungsindikator, Abnahmekriterium: "Zustand jederzeit sichtbar").
|
||||
final linkStateProvider = StreamProvider<LinkState>((ref) {
|
||||
final transport = ref.watch(bluetoothTransportProvider);
|
||||
return transport.state;
|
||||
});
|
||||
|
||||
/// Grund des zuletzt gemeldeten [LinkState.error] (Doku 3, Robustheit: "Grund
|
||||
/// mitfuehren und in der Oberflaeche anzeigen"). Wird bei jedem neuen
|
||||
/// LinkState neu ausgelesen, da der Transport ihn synchron mitfuehrt.
|
||||
final linkErrorReasonProvider = Provider<LinkErrorReason?>((ref) {
|
||||
ref.watch(linkStateProvider);
|
||||
return ref.watch(bluetoothTransportProvider).lastErrorReason;
|
||||
});
|
||||
|
||||
/// Gekoppelte Geraete fuer die manuelle Auswahlliste (Doku 3.2c). Wird bei
|
||||
/// jedem [LinkState]-Wechsel neu geladen, damit eine frisch angeschlossene
|
||||
/// Kopplung (ueber den "Geraet koppeln"-Knopf) ohne expliziten Reload
|
||||
/// auftaucht.
|
||||
final bondedDevicesProvider = FutureProvider<List<BluetoothDeviceInfo>>((ref) {
|
||||
ref.watch(linkStateProvider);
|
||||
return ref.watch(bluetoothTransportProvider).listBondedDevices();
|
||||
});
|
||||
|
||||
/// Ein Eintrag im Verbindungsprotokoll (Doku 7: "einfache Textansicht der
|
||||
/// letzten Ereignisse ... spart im Feld viel Ratearbeit").
|
||||
class ConnectionLogEntry {
|
||||
const ConnectionLogEntry(this.timestamp, this.message);
|
||||
|
||||
final DateTime timestamp;
|
||||
final String message;
|
||||
}
|
||||
|
||||
/// Verbindungsprotokoll + Paketzaehler (Doku 7). Haengt sich an den
|
||||
/// LinkState- und den Rohdaten-Stream des Transports, damit auch ohne
|
||||
/// MSP-Decoder schon sichtbar ist, dass ueberhaupt Bytes ankommen.
|
||||
class ConnectionLogNotifier extends Notifier<List<ConnectionLogEntry>> {
|
||||
static const _maxEntries = 50;
|
||||
|
||||
StreamSubscription<LinkState>? _stateSub;
|
||||
StreamSubscription<void>? _dataSub;
|
||||
int _packetCount = 0;
|
||||
|
||||
int get packetCount => _packetCount;
|
||||
|
||||
@override
|
||||
List<ConnectionLogEntry> build() {
|
||||
final transport = ref.watch(bluetoothTransportProvider);
|
||||
_stateSub?.cancel();
|
||||
_dataSub?.cancel();
|
||||
_packetCount = 0;
|
||||
_stateSub = transport.state.listen(_onStateChanged);
|
||||
_dataSub = transport.incoming.listen((_) => _packetCount++);
|
||||
ref.onDispose(() {
|
||||
_stateSub?.cancel();
|
||||
_dataSub?.cancel();
|
||||
});
|
||||
return const [];
|
||||
}
|
||||
|
||||
void _onStateChanged(LinkState linkState) {
|
||||
final transport = ref.read(bluetoothTransportProvider);
|
||||
final message = switch (linkState) {
|
||||
LinkState.disconnected => 'Getrennt',
|
||||
LinkState.connecting => 'Verbinde ...',
|
||||
LinkState.connected =>
|
||||
'Verbunden${transport.connectedDevice != null ? ' (${transport.connectedDevice!.displayName})' : ''}',
|
||||
LinkState.error =>
|
||||
'Fehler: ${transport.lastErrorMessage ?? transport.lastErrorReason?.name ?? 'unbekannt'}',
|
||||
};
|
||||
_append(message);
|
||||
}
|
||||
|
||||
void _append(String message) {
|
||||
final entry = ConnectionLogEntry(DateTime.now(), message);
|
||||
final next = [...state, entry];
|
||||
state = next.length > _maxEntries
|
||||
? next.sublist(next.length - _maxEntries)
|
||||
: next;
|
||||
}
|
||||
}
|
||||
|
||||
final connectionLogProvider =
|
||||
NotifierProvider<ConnectionLogNotifier, List<ConnectionLogEntry>>(
|
||||
ConnectionLogNotifier.new);
|
||||
Reference in New Issue
Block a user