From 9e5953ced9c02f883203c8e49b2c7a78333ef34b Mon Sep 17 00:00:00 2001
From: Matthias Nott <mnott@mnsoft.org>
Date: Wed, 25 Mar 2026 09:44:01 +0100
Subject: [PATCH] feat: subnet scan fallback when Bonjour fails (handles iOS Personal Hotspot)
---
lib/services/mqtt_service.dart | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 56 insertions(+), 1 deletions(-)
diff --git a/lib/services/mqtt_service.dart b/lib/services/mqtt_service.dart
index 200ba3c..3fddb0f 100644
--- a/lib/services/mqtt_service.dart
+++ b/lib/services/mqtt_service.dart
@@ -156,8 +156,10 @@
}
/// Discover AIBroker on local network via Bonjour/mDNS.
+ /// Falls back to subnet scan if Bonjour fails (iOS blocks mDNS on Personal Hotspot).
/// Returns the IP address or null if not found within timeout.
Future<String?> _discoverViaMdns({Duration timeout = const Duration(seconds: 3)}) async {
+ // Try Bonjour first
try {
final discovery = BonsoirDiscovery(type: '_mqtt._tcp');
await discovery.initialize();
@@ -187,9 +189,62 @@
await sub?.cancel();
await discovery.stop();
- return ip;
+ if (ip != null) return ip;
} catch (e) {
_mqttLog('MQTT: Bonjour discovery error: $e');
+ }
+
+ // Fallback: scan local subnet for MQTT port (handles Personal Hotspot)
+ _mqttLog('MQTT: Bonjour failed, trying subnet scan...');
+ return _scanSubnetForMqtt();
+ }
+
+ /// Scan the local subnet for an MQTT broker by probing the configured port.
+ /// Useful when iOS Personal Hotspot blocks mDNS.
+ Future<String?> _scanSubnetForMqtt() async {
+ try {
+ // Get device's own IP to determine the subnet
+ final interfaces = await NetworkInterface.list(type: InternetAddressType.IPv4);
+ for (final iface in interfaces) {
+ for (final addr in iface.addresses) {
+ final parts = addr.address.split('.');
+ if (parts.length != 4) continue;
+ // Skip loopback
+ if (parts[0] == '127') continue;
+ // Only scan small subnets (hotspot = /28, max 14 hosts)
+ final subnet = '${parts[0]}.${parts[1]}.${parts[2]}';
+ _mqttLog('MQTT: scanning $subnet.0/24 on ${iface.name}');
+
+ // Probe hosts 1-14 in parallel (covers /28 hotspot subnet)
+ final futures = <Future<String?>>[];
+ for (int i = 1; i <= 14; i++) {
+ final probe = '$subnet.$i';
+ if (probe == addr.address) continue; // skip self
+ futures.add(_probeHost(probe, config.port));
+ }
+
+ final results = await Future.wait(futures);
+ final found = results.firstWhere((r) => r != null, orElse: () => null);
+ if (found != null) {
+ _mqttLog('MQTT: subnet scan found broker at $found');
+ return found;
+ }
+ }
+ }
+ } catch (e) {
+ _mqttLog('MQTT: subnet scan error: $e');
+ }
+ return null;
+ }
+
+ /// Probe a single host:port with a TCP connection attempt (1s timeout).
+ Future<String?> _probeHost(String host, int port) async {
+ try {
+ final socket = await Socket.connect(host, port,
+ timeout: const Duration(seconds: 1));
+ await socket.close();
+ return host;
+ } catch (_) {
return null;
}
}
--
Gitblit v1.3.1