From cb470d33d2665fcc6f8448d2736777656cf0cbe7 Mon Sep 17 00:00:00 2001
From: Matthias Nott <mnott@mnsoft.org>
Date: Tue, 24 Mar 2026 00:25:07 +0100
Subject: [PATCH] feat: MQTT migration, offline catch_up, clean session, image support

---
 lib/services/mqtt_service.dart |   23 ++++++++++++++++-------
 1 files changed, 16 insertions(+), 7 deletions(-)

diff --git a/lib/services/mqtt_service.dart b/lib/services/mqtt_service.dart
index 32e6fba..f7a51be 100644
--- a/lib/services/mqtt_service.dart
+++ b/lib/services/mqtt_service.dart
@@ -10,8 +10,15 @@
 import 'package:uuid/uuid.dart';
 
 import '../models/server_config.dart';
-import 'websocket_service.dart' show ConnectionStatus;
 import 'wol_service.dart';
+
+/// Connection status for the MQTT client.
+enum ConnectionStatus {
+  disconnected,
+  connecting,
+  connected,
+  reconnecting,
+}
 
 // Debug log to file (survives release builds)
 Future<void> _mqttLog(String msg) async {
@@ -23,11 +30,11 @@
   } catch (_) {}
 }
 
-/// MQTT client for PAILot, replacing WebSocketService.
+/// MQTT client for PAILot.
 ///
 /// Connects to the AIBroker daemon's embedded aedes broker.
 /// Subscribes to all pailot/ topics and dispatches messages
-/// through the same callback interface as WebSocketService.
+/// through the onMessage callback interface.
 class MqttService with WidgetsBindingObserver {
   MqttService({required this.config});
 
@@ -43,7 +50,7 @@
   final List<String> _seenMsgIdOrder = [];
   static const int _maxSeenIds = 500;
 
-  // Callbacks — same interface as WebSocketService
+  // Callbacks
   void Function(ConnectionStatus status)? onStatusChanged;
   void Function(Map<String, dynamic> message)? onMessage;
   void Function()? onOpen;
@@ -149,9 +156,12 @@
       client.onAutoReconnect = _onAutoReconnect;
       client.onAutoReconnected = _onAutoReconnected;
 
-      // Persistent session: broker queues QoS 1 messages while client is offline
+      // Clean session: we handle offline delivery ourselves via catch_up protocol.
+      // Persistent sessions cause the broker to flood all queued QoS 1 messages
+      // on reconnect, which overwhelms the client with large voice payloads.
       final connMessage = MqttConnectMessage()
           .withClientIdentifier(clientId)
+          .startClean()
           .authenticateAs('pailot', config.mqttToken ?? '');
 
       client.connectionMessage = connMessage;
@@ -268,7 +278,7 @@
 
   /// Route incoming MQTT messages to the onMessage callback.
   /// Translates MQTT topic structure into the flat message format
-  /// that chat_screen expects (same as WebSocket messages).
+  /// that chat_screen expects.
   void _dispatchMessage(String topic, Map<String, dynamic> json) {
     final parts = topic.split('/');
 
@@ -369,7 +379,6 @@
   }
 
   /// Send a message — routes to the appropriate MQTT topic based on content.
-  /// Accepts the same message format as WebSocketService.send().
   void send(Map<String, dynamic> message) {
     final type = message['type'] as String?;
     final sessionId = message['sessionId'] as String?;

--
Gitblit v1.3.1