udp socket binding istead of process binding to allow internet over network for maps etc

This commit is contained in:
Constantin Leue
2026-07-31 20:49:08 +02:00
parent 3b3451b8a2
commit 5e9ef43f2f
5 changed files with 365 additions and 167 deletions
+49 -64
View File
@@ -1,5 +1,5 @@
import 'dart:async';
import 'dart:io';
import 'dart:io' show InternetAddress;
import 'package:flutter/services.dart';
@@ -17,9 +17,11 @@ import 'mlrs_network_controller.dart';
/// `unavailable` warten (Doku Abschnitt 4).
/// 2. Zieladresse vorbelegen: konfigurierter Host, sonst die vom Netz
/// gemeldete Gateway-Adresse, sonst ungelernt (Doku Abschnitt 3).
/// 3. Kurzes Bindungsfenster: Prozess an das Netz binden, UDP-Socket
/// erzeugen, Prozessbindung sofort wieder loesen (Doku Abschnitt 4) - der
/// Socket selbst bleibt danach am WLAN gebunden.
/// 3. Nativen UDP-Socket oeffnen, der **socket-genau** (nicht prozessweit)
/// per `Network.bindSocket()` an das mLRS-Netz gebunden ist (Doku
/// Abschnitt 4, Stufe 3) - siehe [MlrsNetworkController]-Doku fuer die
/// Begruendung, warum Prozessbindung hier in der Praxis Kartenkacheln/
/// Wetter-Requests beschaedigt hat.
/// 4. Weiterhin auf das Netz hoeren: geht es verloren (`lost`), ist der
/// Socket ungueltig (Doku Abschnitt 8) und die Verbindung wird mit
/// [LinkErrorReason.networkLost] beendet. Automatisches Wiederverbinden
@@ -45,12 +47,12 @@ class UdpTransport implements LinkTransport {
final _incomingController = StreamController<Uint8List>.broadcast();
final _stateController = StreamController<LinkState>.broadcast();
RawDatagramSocket? _socket;
StreamSubscription<RawSocketEvent>? _socketSub;
StreamSubscription<MlrsSocketPacket>? _packetSub;
StreamSubscription<MlrsNetworkEvent>? _networkLossSub;
InternetAddress? _peerAddress;
int? _peerPort;
int? _boundPort;
bool _socketOpen = false;
bool? _staConcurrencySupported;
LinkState _state = LinkState.disconnected;
@@ -109,8 +111,8 @@ class UdpTransport implements LinkTransport {
}
final available = event;
// Ab hier auf Netzverlust reagieren - auch waehrend des kurzen
// Bindungsfensters unten, nicht erst danach (Doku Abschnitt 8).
// Ab hier auf Netzverlust reagieren - auch waehrend des Socket-Aufbaus
// unten, nicht erst danach (Doku Abschnitt 8).
_networkLossSub = _networkController.events.listen(_onNetworkEvent);
final configuredHost = await _getHost();
@@ -126,45 +128,23 @@ class UdpTransport implements LinkTransport {
}
}
await _networkController.bindProcessToNetwork();
RawDatagramSocket socket;
try {
// reuseAddress: false, damit ein tatsaechlich belegter Port hier
// erkennbar mit SocketException scheitert - Darts Standard
// (reuseAddress: true) wuerde den Konflikt sonst still zulassen.
socket = await RawDatagramSocket.bind(
InternetAddress.anyIPv4,
configuredPort,
reuseAddress: false,
);
} on SocketException {
// Port belegt (Doku 3/9: "ist 14550 belegt, ausweichen, aber
// protokollieren") - Ausweichen ist kein Grund, abzubrechen.
socket = await RawDatagramSocket.bind(InternetAddress.anyIPv4, 0);
_lastErrorMessage =
'Port $configuredPort in use, fell back to ${socket.port}.';
} finally {
await _networkController.unbindProcessFromNetwork();
// Nativer, socket-genau gebundener UDP-Socket statt Prozessbindung
// (siehe Klassendoku) - der eigentliche RawDatagramSocket lebt in
// MlrsNetworkPlugin.kt, hier kommen nur noch Bytes ueber den
// EventChannel an.
final bindResult = await _networkController.openSocket(configuredPort);
_socketOpen = true;
_boundPort = bindResult.boundPort;
if (bindResult.fallbackFromPort != null) {
_lastErrorMessage = 'Port ${bindResult.fallbackFromPort} in use, '
'fell back to ${bindResult.boundPort}.';
}
_socket = socket;
_boundPort = socket.port;
_socketSub = socket.listen(
_onSocketEvent,
onError: (Object e) =>
_setError(LinkErrorReason.connectionFailed, e.toString()),
);
_packetSub = _networkController.incomingPackets.listen(_onPacket);
_staConcurrencySupported =
await _networkController.isStaConcurrencySupported();
_setState(LinkState.connected);
} on SocketException catch (e) {
await requestSub.cancel();
await _networkLossSub?.cancel();
_networkLossSub = null;
await _networkController.releaseNetwork();
_fail(LinkErrorReason.connectionFailed, e.message);
} on MissingPluginException {
await requestSub.cancel();
_fail(
@@ -173,9 +153,12 @@ class UdpTransport implements LinkTransport {
);
} on PlatformException catch (e) {
await requestSub.cancel();
await _networkLossSub?.cancel();
_networkLossSub = null;
await _networkController.releaseNetwork();
_fail(
LinkErrorReason.unknown,
e.message ?? 'Platform error requesting the mLRS network.',
LinkErrorReason.connectionFailed,
e.message ?? 'Platform error while opening the mLRS socket.',
);
}
}
@@ -184,59 +167,61 @@ class UdpTransport implements LinkTransport {
if (event is! MlrsNetworkLost) return;
_networkLossSub?.cancel();
_networkLossSub = null;
_socketSub?.cancel();
_socketSub = null;
_socket?.close();
_socket = null;
_packetSub?.cancel();
_packetSub = null;
_boundPort = null;
if (_socketOpen) {
_socketOpen = false;
unawaited(_networkController.closeSocket());
}
_setError(
LinkErrorReason.networkLost,
'The mLRS WiFi network was lost.',
);
}
void _onSocketEvent(RawSocketEvent event) {
if (event != RawSocketEvent.read) return;
final datagram = _socket?.receive();
if (datagram == null) return;
_peerAddress = datagram.address;
_peerPort = datagram.port;
_incomingController.add(datagram.data);
void _onPacket(MlrsSocketPacket packet) {
_peerAddress = InternetAddress.tryParse(packet.address) ?? _peerAddress;
_peerPort = packet.port;
_incomingController.add(packet.data);
}
@override
Future<void> disconnect() async {
await _networkLossSub?.cancel();
_networkLossSub = null;
await _socketSub?.cancel();
_socketSub = null;
_socket?.close();
_socket = null;
await _packetSub?.cancel();
_packetSub = null;
_boundPort = null;
if (_socketOpen) {
_socketOpen = false;
await _networkController.closeSocket();
}
await _networkController.releaseNetwork();
_setState(LinkState.disconnected);
}
@override
Future<void> send(Uint8List data) async {
final socket = _socket;
final peer = _peerAddress;
final port = _peerPort;
if (socket == null || peer == null || port == null) {
if (!_socketOpen || peer == null || port == null) {
throw const LinkConnectException(
LinkErrorReason.connectionFailed,
'send() ohne aktive Verbindung oder bekannte Gegenstelle aufgerufen.',
);
}
socket.send(data, peer, port);
await _networkController.send(data, peer.address, port);
}
@override
void dispose() {
_networkLossSub?.cancel();
_socketSub?.cancel();
_socket?.close();
_socket = null;
_packetSub?.cancel();
if (_socketOpen) {
_socketOpen = false;
unawaited(_networkController.closeSocket());
}
unawaited(_networkController.releaseNetwork());
_networkController.dispose();
_incomingController.close();