| .. | .. |
|---|
| 110 | 110 | |
|---|
| 111 | 111 | final clientId = await _getClientId(); |
|---|
| 112 | 112 | |
|---|
| 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(", ")}'); |
|---|
| 129 | 120 | |
|---|
| 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 | + }()); |
|---|
| 137 | 135 | } |
|---|
| 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; |
|---|
| 138 | 142 | } |
|---|
| 139 | 143 | |
|---|
| 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'); |
|---|
| 154 | 150 | } |
|---|
| 155 | 151 | } |
|---|
| 156 | 152 | |
|---|