Matthias Nott
2026-03-24 96c8bb5db1a2e0ced999a366e3cf28f9895ec39f
lib/services/mqtt_service.dart
....@@ -2,6 +2,7 @@
22 import 'dart:convert';
33 import 'dart:io';
44
5
+import 'package:bonsoir/bonsoir.dart';
56 import 'package:flutter/widgets.dart';
67 import 'package:path_provider/path_provider.dart' as pp;
78 import 'package:mqtt_client/mqtt_client.dart';
....@@ -102,29 +103,50 @@
102103 }
103104
104105 final clientId = await _getClientId();
105
- final hosts = _getHosts();
106
- _mqttLog('MQTT: hosts=${hosts.join(", ")} port=${config.port}');
107106
108
- for (final host in hosts) {
107
+ // Connection order: local → Bonjour → VPN → remote
108
+ final attempts = <MapEntry<String, int>>[]; // host → timeout ms
109
+ if (config.localHost != null && config.localHost!.isNotEmpty) {
110
+ attempts.add(MapEntry(config.localHost!, 2500));
111
+ }
112
+ // Bonjour placeholder — inserted dynamically below
113
+ if (config.vpnHost != null && config.vpnHost!.isNotEmpty) {
114
+ attempts.add(MapEntry(config.vpnHost!, 3000));
115
+ }
116
+ if (config.host.isNotEmpty) {
117
+ attempts.add(MapEntry(config.host, 5000));
118
+ }
119
+ _mqttLog('MQTT: attempts=${attempts.map((e) => e.key).join(", ")} port=${config.port}');
120
+
121
+ // Try configured local host first
122
+ for (final attempt in attempts) {
109123 if (_intentionalClose) return;
110
-
111
- _mqttLog('MQTT: trying $host:${config.port}');
124
+ _mqttLog('MQTT: trying ${attempt.key}:${config.port}');
112125 try {
113
- final connected = await _tryConnect(
114
- host,
115
- clientId,
116
- timeout: host == hosts.first && hosts.length > 1 ? 2500 : 5000,
117
- );
118
- _mqttLog('MQTT: $host result=$connected');
119
- if (connected) return;
126
+ if (await _tryConnect(attempt.key, clientId, timeout: attempt.value)) return;
120127 } catch (e) {
121
- _mqttLog('MQTT: $host error=$e');
122
- continue;
128
+ _mqttLog('MQTT: ${attempt.key} error=$e');
129
+ }
130
+
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');
144
+ }
123145 }
124146 }
125147
126148 // All hosts failed — retry after delay
127
- _mqttLog('MQTT: all hosts failed, retrying in 5s');
149
+ _mqttLog('MQTT: all attempts failed, retrying in 5s');
128150 _setStatus(ConnectionStatus.reconnecting);
129151 Future.delayed(const Duration(seconds: 5), () {
130152 if (!_intentionalClose && _status != ConnectionStatus.connected) {
....@@ -133,14 +155,43 @@
133155 });
134156 }
135157
136
- /// Returns [localHost, remoteHost] for dual-connect attempts.
137
- List<String> _getHosts() {
138
- if (config.localHost != null &&
139
- config.localHost!.isNotEmpty &&
140
- config.localHost != config.host) {
141
- return [config.localHost!, config.host];
158
+ /// Discover AIBroker on local network via Bonjour/mDNS.
159
+ /// Returns the IP address or null if not found within timeout.
160
+ Future<String?> _discoverViaMdns({Duration timeout = const Duration(seconds: 3)}) async {
161
+ try {
162
+ final discovery = BonsoirDiscovery(type: '_mqtt._tcp');
163
+ await discovery.initialize();
164
+
165
+ final completer = Completer<String?>();
166
+ StreamSubscription? sub;
167
+
168
+ sub = discovery.eventStream?.listen((event) {
169
+ switch (event) {
170
+ case BonsoirDiscoveryServiceResolvedEvent():
171
+ final ip = event.service.host;
172
+ _mqttLog('MQTT: Bonjour resolved: ${event.service.name} at $ip:${event.service.port}');
173
+ if (ip != null && ip.isNotEmpty && !completer.isCompleted) {
174
+ completer.complete(ip);
175
+ }
176
+ case BonsoirDiscoveryServiceFoundEvent():
177
+ _mqttLog('MQTT: Bonjour found: ${event.service.name}');
178
+ default:
179
+ break;
180
+ }
181
+ });
182
+
183
+ await discovery.start();
184
+
185
+ final ip = await completer.future.timeout(timeout, onTimeout: () => null);
186
+
187
+ await sub?.cancel();
188
+ await discovery.stop();
189
+
190
+ return ip;
191
+ } catch (e) {
192
+ _mqttLog('MQTT: Bonjour discovery error: $e');
193
+ return null;
142194 }
143
- return [config.host];
144195 }
145196
146197 Future<bool> _tryConnect(String host, String clientId, {int timeout = 5000}) async {