111 lines
4.2 KiB
Dart
111 lines
4.2 KiB
Dart
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);
|