wifi connection established by app, process socket binding, primary os connection untouched
This commit is contained in:
@@ -15,6 +15,7 @@ final wifiTransportProvider = Provider<UdpTransport>((ref) {
|
||||
final transport = UdpTransport(
|
||||
getHost: settingsRepo.loadConnectionWifiHost,
|
||||
getPort: settingsRepo.loadConnectionWifiPort,
|
||||
getSsidPrefix: settingsRepo.loadConnectionWifiSsidPrefix,
|
||||
);
|
||||
ref.onDispose(transport.dispose);
|
||||
return transport;
|
||||
@@ -96,6 +97,7 @@ class WifiConnectionLogNotifier
|
||||
StreamSubscription<LinkState>? _stateSub;
|
||||
StreamSubscription<void>? _dataSub;
|
||||
int _packetCount = 0;
|
||||
String? _lastLoggedPeer;
|
||||
|
||||
int get packetCount => _packetCount;
|
||||
|
||||
@@ -105,8 +107,9 @@ class WifiConnectionLogNotifier
|
||||
_stateSub?.cancel();
|
||||
_dataSub?.cancel();
|
||||
_packetCount = 0;
|
||||
_lastLoggedPeer = null;
|
||||
_stateSub = transport.state.listen(_onStateChanged);
|
||||
_dataSub = transport.incoming.listen((_) => _packetCount++);
|
||||
_dataSub = transport.incoming.listen((_) => _onPacket(transport));
|
||||
ref.onDispose(() {
|
||||
_stateSub?.cancel();
|
||||
_dataSub?.cancel();
|
||||
@@ -114,18 +117,38 @@ class WifiConnectionLogNotifier
|
||||
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 => 'Connecting...',
|
||||
LinkState.connected => 'Listening on port ${transport.boundPort}',
|
||||
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];
|
||||
|
||||
@@ -121,6 +121,7 @@ class _WifiPanel extends ConsumerStatefulWidget {
|
||||
}
|
||||
|
||||
class _WifiPanelState extends ConsumerState<_WifiPanel> {
|
||||
late final TextEditingController _ssidPrefixController;
|
||||
late final TextEditingController _hostController;
|
||||
late final TextEditingController _portController;
|
||||
bool _controllersSeeded = false;
|
||||
@@ -128,12 +129,14 @@ class _WifiPanelState extends ConsumerState<_WifiPanel> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_ssidPrefixController = TextEditingController();
|
||||
_hostController = TextEditingController();
|
||||
_portController = TextEditingController();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_ssidPrefixController.dispose();
|
||||
_hostController.dispose();
|
||||
_portController.dispose();
|
||||
super.dispose();
|
||||
@@ -153,6 +156,7 @@ class _WifiPanelState extends ConsumerState<_WifiPanel> {
|
||||
// ueberschrieben.
|
||||
if (!_controllersSeeded && connectionSettings != null) {
|
||||
_controllersSeeded = true;
|
||||
_ssidPrefixController.text = connectionSettings.connectionWifiSsidPrefix;
|
||||
_hostController.text = connectionSettings.connectionWifiHost ?? '';
|
||||
_portController.text = connectionSettings.connectionWifiPort.toString();
|
||||
}
|
||||
@@ -171,9 +175,17 @@ class _WifiPanelState extends ConsumerState<_WifiPanel> {
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const Text(
|
||||
'Opens a local UDP socket and listens for the mLRS bridge '
|
||||
'(port 14550 by default). The peer address is learned from the '
|
||||
'first incoming packet.',
|
||||
'The app connects to the mLRS bridge WiFi itself (a system dialog '
|
||||
'will ask you to confirm) so your normal mobile data connection '
|
||||
'keeps working for maps and weather. The peer address is learned '
|
||||
'from the first incoming packet.',
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const Text(
|
||||
'Do not manually connect to or save the mLRS network in your '
|
||||
'system WiFi settings - that would make it the default route and '
|
||||
'break maps/weather.',
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey),
|
||||
),
|
||||
if (errorReason != null) ...[
|
||||
@@ -187,15 +199,15 @@ class _WifiPanelState extends ConsumerState<_WifiPanel> {
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: TextField(
|
||||
controller: _hostController,
|
||||
controller: _ssidPrefixController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Host (optional)',
|
||||
hintText: 'auto (learned from traffic)',
|
||||
labelText: 'SSID prefix',
|
||||
isDense: true,
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
onSubmitted: (value) => _saveHost(value),
|
||||
onEditingComplete: () => _saveHost(_hostController.text),
|
||||
onSubmitted: (value) => _saveSsidPrefix(value),
|
||||
onEditingComplete: () =>
|
||||
_saveSsidPrefix(_ssidPrefixController.text),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@@ -215,6 +227,18 @@ class _WifiPanelState extends ConsumerState<_WifiPanel> {
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: _hostController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Host (optional)',
|
||||
hintText: 'auto (network gateway, then learned from traffic)',
|
||||
isDense: true,
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
onSubmitted: (value) => _saveHost(value),
|
||||
onEditingComplete: () => _saveHost(_hostController.text),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
@@ -258,6 +282,15 @@ class _WifiPanelState extends ConsumerState<_WifiPanel> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _saveSsidPrefix(String value) async {
|
||||
final trimmed = value.trim();
|
||||
if (trimmed.isEmpty) return;
|
||||
await ref
|
||||
.read(appSettingsRepositoryProvider)
|
||||
.setConnectionWifiSsidPrefix(trimmed);
|
||||
ref.invalidate(connectionSettingsProvider);
|
||||
}
|
||||
|
||||
Future<void> _saveHost(String value) async {
|
||||
final trimmed = value.trim();
|
||||
await ref
|
||||
@@ -293,16 +326,25 @@ class _WifiErrorHint extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final text = switch (reason) {
|
||||
LinkErrorReason.networkUnavailable =>
|
||||
'No matching WiFi network found, or the system connection dialog '
|
||||
'was dismissed. Make sure the mLRS bridge is powered on and not '
|
||||
'saved/connected in system WiFi settings, then tap "Connect" '
|
||||
'again.',
|
||||
LinkErrorReason.networkLost =>
|
||||
'The mLRS WiFi network was lost (out of range or bridge powered '
|
||||
'off). Tap "Connect" to reconnect.',
|
||||
LinkErrorReason.connectionFailed => 'Connection to the bridge failed.',
|
||||
_ => 'Connection error: ${reason.name}.',
|
||||
};
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: DmcColors.warnRed.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
'Connection error: ${reason.name}.',
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
child: Text(text, style: const TextStyle(fontSize: 12)),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -543,7 +585,13 @@ class _ErrorHint extends StatelessWidget {
|
||||
'Connection to the device failed.',
|
||||
null,
|
||||
),
|
||||
LinkErrorReason.unknown => ('Unknown connection error.', null),
|
||||
// networkUnavailable/networkLost betreffen nur den WLAN-Transport
|
||||
// (siehe _WifiErrorHint) - hier nur der Vollstaendigkeit halber, damit
|
||||
// der gemeinsame LinkErrorReason-Enum exhaustiv bleibt.
|
||||
LinkErrorReason.networkUnavailable ||
|
||||
LinkErrorReason.networkLost ||
|
||||
LinkErrorReason.unknown =>
|
||||
('Unknown connection error.', null),
|
||||
};
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
|
||||
Reference in New Issue
Block a user