| .. | .. |
|---|
| 1 | 1 | import 'dart:async'; |
|---|
| 2 | 2 | import 'dart:convert'; |
|---|
| 3 | +import 'dart:io'; |
|---|
| 3 | 4 | |
|---|
| 4 | 5 | import 'package:flutter/widgets.dart'; |
|---|
| 6 | +import 'package:path_provider/path_provider.dart' as pp; |
|---|
| 5 | 7 | import 'package:mqtt_client/mqtt_client.dart'; |
|---|
| 6 | 8 | import 'package:mqtt_client/mqtt_server_client.dart'; |
|---|
| 7 | 9 | import 'package:shared_preferences/shared_preferences.dart'; |
|---|
| .. | .. |
|---|
| 11 | 13 | import 'websocket_service.dart' show ConnectionStatus; |
|---|
| 12 | 14 | import 'wol_service.dart'; |
|---|
| 13 | 15 | |
|---|
| 14 | | -/// MQTT port — standard unencrypted MQTT. |
|---|
| 15 | | -const int mqttPort = 1883; |
|---|
| 16 | +// Debug log to file (survives release builds) |
|---|
| 17 | +Future<void> _mqttLog(String msg) async { |
|---|
| 18 | + try { |
|---|
| 19 | + final dir = await pp.getApplicationDocumentsDirectory(); |
|---|
| 20 | + final file = File('${dir.path}/mqtt_debug.log'); |
|---|
| 21 | + final ts = DateTime.now().toIso8601String().substring(11, 19); |
|---|
| 22 | + await file.writeAsString('[$ts] $msg\n', mode: FileMode.append); |
|---|
| 23 | + } catch (_) {} |
|---|
| 24 | +} |
|---|
| 16 | 25 | |
|---|
| 17 | 26 | /// MQTT client for PAILot, replacing WebSocketService. |
|---|
| 18 | 27 | /// |
|---|
| .. | .. |
|---|
| 56 | 65 | if (_clientId != null) return _clientId!; |
|---|
| 57 | 66 | final prefs = await SharedPreferences.getInstance(); |
|---|
| 58 | 67 | var id = prefs.getString('mqtt_client_id'); |
|---|
| 59 | | - if (id == null) { |
|---|
| 60 | | - id = 'pailot-${const Uuid().v4()}'; |
|---|
| 68 | + // Regenerate if old format (too long for MQTT 3.1.1) |
|---|
| 69 | + if (id == null || id.length > 23) { |
|---|
| 70 | + // MQTT 3.1.1 client IDs: max 23 chars, alphanumeric |
|---|
| 71 | + id = 'pailot${const Uuid().v4().replaceAll('-', '').substring(0, 16)}'; |
|---|
| 61 | 72 | await prefs.setString('mqtt_client_id', id); |
|---|
| 62 | 73 | } |
|---|
| 63 | 74 | _clientId = id; |
|---|
| .. | .. |
|---|
| 88 | 99 | for (final host in hosts) { |
|---|
| 89 | 100 | if (_intentionalClose) return; |
|---|
| 90 | 101 | |
|---|
| 102 | + _mqttLog('MQTT: trying $host:${config.port}'); |
|---|
| 91 | 103 | try { |
|---|
| 92 | 104 | final connected = await _tryConnect( |
|---|
| 93 | 105 | host, |
|---|
| 94 | 106 | clientId, |
|---|
| 95 | 107 | timeout: host == hosts.first && hosts.length > 1 ? 2500 : 5000, |
|---|
| 96 | 108 | ); |
|---|
| 109 | + _mqttLog('MQTT: $host result=$connected'); |
|---|
| 97 | 110 | if (connected) return; |
|---|
| 98 | | - } catch (_) { |
|---|
| 111 | + } catch (e) { |
|---|
| 112 | + _mqttLog('MQTT: $host error=$e'); |
|---|
| 99 | 113 | continue; |
|---|
| 100 | 114 | } |
|---|
| 101 | 115 | } |
|---|
| 102 | 116 | |
|---|
| 103 | 117 | // All hosts failed |
|---|
| 118 | + debugPrint('MQTT: all hosts failed'); |
|---|
| 104 | 119 | _setStatus(ConnectionStatus.disconnected); |
|---|
| 105 | 120 | onError?.call('Failed to connect to MQTT broker'); |
|---|
| 106 | 121 | } |
|---|
| .. | .. |
|---|
| 117 | 132 | |
|---|
| 118 | 133 | Future<bool> _tryConnect(String host, String clientId, {int timeout = 5000}) async { |
|---|
| 119 | 134 | try { |
|---|
| 120 | | - final client = MqttServerClient.withPort(host, clientId, mqttPort); |
|---|
| 135 | + final client = MqttServerClient.withPort(host, clientId, config.port); |
|---|
| 121 | 136 | client.keepAlivePeriod = 30; |
|---|
| 122 | 137 | client.autoReconnect = true; |
|---|
| 123 | 138 | client.connectTimeoutPeriod = timeout; |
|---|
| 124 | | - client.logging(on: false); |
|---|
| 139 | + client.logging(on: true); |
|---|
| 125 | 140 | |
|---|
| 126 | 141 | client.onConnected = _onConnected; |
|---|
| 127 | 142 | client.onDisconnected = _onDisconnected; |
|---|
| .. | .. |
|---|
| 140 | 155 | |
|---|
| 141 | 156 | client.connectionMessage = connMessage; |
|---|
| 142 | 157 | |
|---|
| 158 | + _mqttLog('MQTT: connecting to $host:${config.port} as $clientId'); |
|---|
| 143 | 159 | final result = await client.connect(); |
|---|
| 160 | + _mqttLog('MQTT: connect result=${result?.state}'); |
|---|
| 144 | 161 | if (result?.state == MqttConnectionState.connected) { |
|---|
| 145 | 162 | _client = client; |
|---|
| 146 | 163 | return true; |
|---|
| .. | .. |
|---|
| 148 | 165 | client.disconnect(); |
|---|
| 149 | 166 | return false; |
|---|
| 150 | 167 | } catch (e) { |
|---|
| 168 | + _mqttLog('MQTT: connect exception=$e'); |
|---|
| 151 | 169 | return false; |
|---|
| 152 | 170 | } |
|---|
| 153 | 171 | } |
|---|