import 'dart:async'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../transport/link_transport.dart'; import '../../transport/udp/udp_transport.dart'; import 'database_provider.dart'; /// App-lebenslanger UDP-Transport (Doku Kommunikationsschicht Abschnitt 3), /// verdrahtet gegen Host/Port aus den Einstellungen. Analog zu /// [bluetoothTransportProvider] bewusst nicht autoDispose - siehe dortige /// Begruendung. final wifiTransportProvider = Provider((ref) { final settingsRepo = ref.watch(appSettingsRepositoryProvider); final transport = UdpTransport( getHost: settingsRepo.loadConnectionWifiHost, getPort: settingsRepo.loadConnectionWifiPort, getSsidPrefix: settingsRepo.loadConnectionWifiSsidPrefix, ); ref.onDispose(transport.dispose); return transport; }); /// Reaktiver [LinkState] des WLAN-Transports fuer die Oberflaeche. final wifiLinkStateProvider = StreamProvider((ref) { final transport = ref.watch(wifiTransportProvider); return transport.state; }); /// Grund des zuletzt gemeldeten [LinkState.error] des WLAN-Transports. final wifiLinkErrorReasonProvider = Provider((ref) { ref.watch(wifiLinkStateProvider); return ref.watch(wifiTransportProvider).lastErrorReason; }); /// Zeigt an, ob gerade UDP-Pakete ankommen ("receiving"/"not receiving"), /// unabhaengig vom (binaeren) Verbindungszustand - der Socket kann offen /// sein (LinkState.connected), ohne dass die Gegenstelle tatsaechlich /// sendet. Faellt [_receivingTimeout] nach dem letzten Paket automatisch /// wieder auf "not receiving" zurueck, dafuer muss ein Timer laufen statt /// nur auf den incoming-Stream zu reagieren. class WifiStreamActivityNotifier extends Notifier { static const _receivingTimeout = Duration(seconds: 4); StreamSubscription? _dataSub; StreamSubscription? _stateSub; Timer? _timeoutTimer; @override bool build() { final transport = ref.watch(wifiTransportProvider); _dataSub?.cancel(); _stateSub?.cancel(); _timeoutTimer?.cancel(); _dataSub = transport.incoming.listen((_) => _onPacket()); _stateSub = transport.state.listen(_onStateChanged); ref.onDispose(() { _dataSub?.cancel(); _stateSub?.cancel(); _timeoutTimer?.cancel(); }); return false; } void _onPacket() { state = true; _timeoutTimer?.cancel(); _timeoutTimer = Timer(_receivingTimeout, () => state = false); } void _onStateChanged(LinkState linkState) { if (linkState != LinkState.connected) { _timeoutTimer?.cancel(); state = false; } } } final wifiStreamActivityProvider = NotifierProvider( WifiStreamActivityNotifier.new); /// Ein Eintrag im WLAN-Verbindungsprotokoll (Doku 7). class WifiConnectionLogEntry { const WifiConnectionLogEntry(this.timestamp, this.message); final DateTime timestamp; final String message; } /// Verbindungsprotokoll + Paketzaehler fuer den WLAN-Transport, analog zu /// [ConnectionLogNotifier] fuer Bluetooth. class WifiConnectionLogNotifier extends Notifier> { static const _maxEntries = 50; StreamSubscription? _stateSub; StreamSubscription? _dataSub; int _packetCount = 0; String? _lastLoggedPeer; int get packetCount => _packetCount; @override List build() { final transport = ref.watch(wifiTransportProvider); _stateSub?.cancel(); _dataSub?.cancel(); _packetCount = 0; _lastLoggedPeer = null; _stateSub = transport.state.listen(_onStateChanged); _dataSub = transport.incoming.listen((_) => _onPacket(transport)); ref.onDispose(() { _stateSub?.cancel(); _dataSub?.cancel(); }); return const []; } void _onPacket(UdpTransport transport) { _packetCount++; // Gegenstelle wird aus dem ersten eingehenden Paket gelernt (Doku // Kommunikationsschicht v2, Abschnitt 3/9) - nur beim ersten Mal bzw. // bei Aenderung protokollieren, nicht pro Paket. final peer = transport.peerDescription; if (peer != null && peer != _lastLoggedPeer) { _lastLoggedPeer = peer; _append('Peer learned: $peer'); } } void _onStateChanged(LinkState linkState) { final transport = ref.read(wifiTransportProvider); final message = switch (linkState) { LinkState.disconnected => 'Disconnected', LinkState.connecting => 'Requesting mLRS network...', LinkState.connected => 'Network available, listening on port ${transport.boundPort}' ' (STA concurrency: ${_formatStaConcurrency(transport.staConcurrencySupported)})', LinkState.error => 'Error: ' '${transport.lastErrorMessage ?? transport.lastErrorReason?.name ?? 'unknown'}', }; _append(message); } String _formatStaConcurrency(bool? supported) => switch (supported) { true => 'supported', false => 'not supported', null => 'unknown', }; void _append(String message) { final entry = WifiConnectionLogEntry(DateTime.now(), message); final next = [...state, entry]; state = next.length > _maxEntries ? next.sublist(next.length - _maxEntries) : next; } } final wifiConnectionLogProvider = NotifierProvider>( WifiConnectionLogNotifier.new);