| .. | .. |
|---|
| 44 | 44 | ConnectionStatus _status = ConnectionStatus.disconnected; |
|---|
| 45 | 45 | bool _intentionalClose = false; |
|---|
| 46 | 46 | String? _clientId; |
|---|
| 47 | + String? _lastDiscoveredHost; |
|---|
| 47 | 48 | StreamSubscription? _updatesSub; |
|---|
| 48 | 49 | |
|---|
| 49 | 50 | // Message deduplication |
|---|
| .. | .. |
|---|
| 104 | 105 | |
|---|
| 105 | 106 | final clientId = await _getClientId(); |
|---|
| 106 | 107 | |
|---|
| 107 | | - // Connection order: local → Bonjour → VPN → remote |
|---|
| 108 | + // Connection order: local → cached discovery → Bonjour/scan → VPN → remote |
|---|
| 108 | 109 | final attempts = <MapEntry<String, int>>[]; // host → timeout ms |
|---|
| 109 | 110 | if (config.localHost != null && config.localHost!.isNotEmpty) { |
|---|
| 110 | 111 | attempts.add(MapEntry(config.localHost!, 2500)); |
|---|
| 111 | 112 | } |
|---|
| 112 | | - // Bonjour placeholder — inserted dynamically below |
|---|
| 113 | + // Try cached discovered host before scanning again |
|---|
| 114 | + if (_lastDiscoveredHost != null) { |
|---|
| 115 | + attempts.add(MapEntry(_lastDiscoveredHost!, 3000)); |
|---|
| 116 | + } |
|---|
| 113 | 117 | if (config.vpnHost != null && config.vpnHost!.isNotEmpty) { |
|---|
| 114 | 118 | attempts.add(MapEntry(config.vpnHost!, 3000)); |
|---|
| 115 | 119 | } |
|---|
| .. | .. |
|---|
| 118 | 122 | } |
|---|
| 119 | 123 | _mqttLog('MQTT: attempts=${attempts.map((e) => e.key).join(", ")} port=${config.port}'); |
|---|
| 120 | 124 | |
|---|
| 121 | | - // Try configured local host first |
|---|
| 122 | 125 | for (final attempt in attempts) { |
|---|
| 123 | 126 | if (_intentionalClose) return; |
|---|
| 124 | 127 | _mqttLog('MQTT: trying ${attempt.key}:${config.port}'); |
|---|
| .. | .. |
|---|
| 127 | 130 | } catch (e) { |
|---|
| 128 | 131 | _mqttLog('MQTT: ${attempt.key} error=$e'); |
|---|
| 129 | 132 | } |
|---|
| 133 | + } |
|---|
| 130 | 134 | |
|---|
| 131 | | - // After local host fails, try Bonjour discovery before VPN/remote |
|---|
| 132 | | - if (attempt.key == config.localHost && !_intentionalClose) { |
|---|
| 133 | | - _mqttLog('MQTT: trying Bonjour discovery...'); |
|---|
| 134 | | - final bonjourHost = await _discoverViaMdns(); |
|---|
| 135 | | - if (bonjourHost != null && !_intentionalClose) { |
|---|
| 136 | | - _mqttLog('MQTT: Bonjour found $bonjourHost'); |
|---|
| 137 | | - try { |
|---|
| 138 | | - if (await _tryConnect(bonjourHost, clientId, timeout: 3000)) return; |
|---|
| 139 | | - } catch (e) { |
|---|
| 140 | | - _mqttLog('MQTT: Bonjour host $bonjourHost error=$e'); |
|---|
| 141 | | - } |
|---|
| 142 | | - } else { |
|---|
| 143 | | - _mqttLog('MQTT: Bonjour discovery returned nothing'); |
|---|
| 135 | + // All configured hosts failed — try Bonjour/subnet scan (only once, not on retry) |
|---|
| 136 | + if (_lastDiscoveredHost == null && !_intentionalClose) { |
|---|
| 137 | + _mqttLog('MQTT: trying Bonjour/subnet discovery...'); |
|---|
| 138 | + final discovered = await _discoverViaMdns(); |
|---|
| 139 | + if (discovered != null && !_intentionalClose) { |
|---|
| 140 | + _lastDiscoveredHost = discovered; |
|---|
| 141 | + _mqttLog('MQTT: discovered $discovered, connecting...'); |
|---|
| 142 | + try { |
|---|
| 143 | + if (await _tryConnect(discovered, clientId, timeout: 3000)) return; |
|---|
| 144 | + } catch (e) { |
|---|
| 145 | + _mqttLog('MQTT: discovered host $discovered error=$e'); |
|---|
| 144 | 146 | } |
|---|
| 147 | + } else { |
|---|
| 148 | + _mqttLog('MQTT: discovery returned nothing'); |
|---|
| 145 | 149 | } |
|---|
| 146 | 150 | } |
|---|
| 147 | 151 | |
|---|