Matthias Nott
2026-03-25 0682ae7065a7a6b8dc30ea5da049c916b757a87e
lib/services/mqtt_service.dart
....@@ -110,47 +110,43 @@
110110
111111 final clientId = await _getClientId();
112112
113
- // Connection order: local → cached discovery → Bonjour/scan → VPN → remote
114
- final attempts = <MapEntry<String, int>>[]; // host → timeout ms
115
- if (config.localHost != null && config.localHost!.isNotEmpty) {
116
- attempts.add(MapEntry(config.localHost!, 2500));
117
- }
118
- // Try cached discovered host before scanning again
119
- if (_lastDiscoveredHost != null) {
120
- attempts.add(MapEntry(_lastDiscoveredHost!, 3000));
121
- }
122
- if (config.vpnHost != null && config.vpnHost!.isNotEmpty) {
123
- attempts.add(MapEntry(config.vpnHost!, 3000));
124
- }
125
- if (config.host.isNotEmpty) {
126
- attempts.add(MapEntry(config.host, 5000));
127
- }
128
- _mqttLog('MQTT: attempts=${attempts.map((e) => e.key).join(", ")} port=${config.port}');
113
+ // Probe all hosts in parallel to find which one responds, then connect to the winner
114
+ final hosts = <String>{};
115
+ if (config.localHost != null && config.localHost!.isNotEmpty) hosts.add(config.localHost!);
116
+ if (_lastDiscoveredHost != null) hosts.add(_lastDiscoveredHost!);
117
+ if (config.vpnHost != null && config.vpnHost!.isNotEmpty) hosts.add(config.vpnHost!);
118
+ if (config.host.isNotEmpty) hosts.add(config.host);
119
+ _mqttLog('MQTT: probing ${hosts.length} hosts in parallel: ${hosts.join(", ")}');
129120
130
- for (final attempt in attempts) {
131
- if (_intentionalClose) return;
132
- _mqttLog('MQTT: trying ${attempt.key}:${config.port}');
133
- try {
134
- if (await _tryConnect(attempt.key, clientId, timeout: attempt.value)) return;
135
- } catch (e) {
136
- _mqttLog('MQTT: ${attempt.key} error=$e');
121
+ // Probe all configured hosts in parallel — first to respond wins
122
+ String? winner;
123
+ if (hosts.isNotEmpty) {
124
+ final probes = hosts.map((h) => _probeHost(h, config.port)).toList();
125
+ // Also start discovery in parallel
126
+ if (_lastDiscoveredHost == null) {
127
+ probes.add(() async {
128
+ final discovered = await _discoverViaMdns();
129
+ if (discovered != null) {
130
+ _lastDiscoveredHost = discovered;
131
+ return discovered;
132
+ }
133
+ return null;
134
+ }());
137135 }
136
+ final results = await Future.wait(probes);
137
+ winner = results.firstWhere((r) => r != null, orElse: () => null);
138
+ } else if (_lastDiscoveredHost == null) {
139
+ // No configured hosts — try discovery only
140
+ winner = await _discoverViaMdns();
141
+ if (winner != null) _lastDiscoveredHost = winner;
138142 }
139143
140
- // All configured hosts failed — try Bonjour/subnet scan (only once, not on retry)
141
- if (_lastDiscoveredHost == null && !_intentionalClose) {
142
- _mqttLog('MQTT: trying Bonjour/subnet discovery...');
143
- final discovered = await _discoverViaMdns();
144
- if (discovered != null && !_intentionalClose) {
145
- _lastDiscoveredHost = discovered;
146
- _mqttLog('MQTT: discovered $discovered, connecting...');
147
- try {
148
- if (await _tryConnect(discovered, clientId, timeout: 3000)) return;
149
- } catch (e) {
150
- _mqttLog('MQTT: discovered host $discovered error=$e');
151
- }
152
- } else {
153
- _mqttLog('MQTT: discovery returned nothing');
144
+ if (winner != null && !_intentionalClose) {
145
+ _mqttLog('MQTT: probe winner: $winner, connecting...');
146
+ try {
147
+ if (await _tryConnect(winner, clientId, timeout: 5000)) return;
148
+ } catch (e) {
149
+ _mqttLog('MQTT: connect to $winner failed: $e');
154150 }
155151 }
156152