# MQTT Migration Guide - Client Implementation ## Übersicht Dieses Dokument beschreibt die Änderungen am MQTT-Messaging-System und wie mobile Clients (Flutter/Dart) auf den neuen Messaging-Layer umgestellt werden müssen. ## Änderungen am Server ### 1. Neue MQTT-Broker-Konfiguration **Wichtig: Der MQTT-Broker-Port hat sich geändert!** ``` Alter Port: 1883 Neuer Port: 42099 Broker: mqtt-2.assecutor.de:42099 ``` ### 2. Server-Konfiguration Die Server-Konfiguration wurde von der alten `app.mqtt.*` Konfiguration auf das neue Plugin-System umgestellt: **Alte Konfiguration (nicht mehr verwendet):** ```properties app.mqtt.enabled=true app.mqtt.broker-uri=mqtt://mqtt-2.assecutor.de app.mqtt.client-id=server-${random.uuid} ``` **Neue Konfiguration:** ```properties # Messaging Plugin Configuration app.messaging.plugin.type=mqtt app.messaging.plugin.mqtt.broker.host=mqtt-2.assecutor.de app.messaging.plugin.mqtt.broker.port=42099 app.messaging.plugin.mqtt.username=app app.messaging.plugin.mqtt.password=apppwd app.messaging.plugin.mqtt.client.id=votianlt-server ``` ### 3. Verbesserte Fehlerbehandlung Der Server hat jetzt: - Erhöhten Connection-Timeout (60 Sekunden statt 30) - Detaillierte Fehlerdiagnose für Verbindungsprobleme - Automatische Wiederverbindung mit exponentieller Backoff-Strategie ## Client-Anpassungen erforderlich ### 1. MQTT-Broker-Verbindung aktualisieren **Flutter/Dart Beispiel:** ```dart import 'package:mqtt_client/mqtt_client.dart'; import 'package:mqtt_client/mqtt_server_client.dart'; class MqttService { // WICHTIG: Neuer Port! static const String BROKER_HOST = 'mqtt-2.assecutor.de'; static const int BROKER_PORT = 42099; // Geändert von 1883 static const String USERNAME = 'app'; static const String PASSWORD = 'apppwd'; late MqttServerClient client; Future connect(String clientId) async { client = MqttServerClient.withPort( BROKER_HOST, clientId, BROKER_PORT, // Neuer Port ); client.logging(on: true); client.keepAlivePeriod = 60; client.connectTimeoutPeriod = 60000; // 60 Sekunden client.autoReconnect = true; // Authentifizierung client.setProtocolV311(); final connMessage = MqttConnectMessage() .withClientIdentifier(clientId) .authenticateAs(USERNAME, PASSWORD) .withWillQos(MqttQos.atLeastOnce) .startClean() .keepAliveFor(60); client.connectionMessage = connMessage; try { await client.connect(); print('MQTT Connected successfully'); } catch (e) { print('MQTT Connection failed: $e'); client.disconnect(); rethrow; } } } ``` ### 2. Topic-Struktur (unverändert) Die Topic-Struktur bleibt gleich: **Client → Server:** ``` /server/{clientId}/{messageType} ``` **Server → Client:** ``` /client/{clientId}/{messageType} ``` **Acknowledgments:** ``` Client → Server: /ack/server/{messageId} Server → Client: /ack/client/{clientId}/{messageId} ``` ### 3. Message Envelope Format Der Server verwendet jetzt ein Message-Envelope-Format für alle Nachrichten: ```json { "messageId": "uuid-v4", "timestamp": "2025-10-22T10:30:00Z", "topic": "/client/app-user-123/jobs/assigned", "payload": { // Ihre eigentliche Nachricht }, "requiresAck": true, "retryCount": 0, "expiresAt": "2025-10-23T10:30:00Z" } ``` **Client-Implementierung:** ```dart class MessageEnvelope { final String messageId; final DateTime timestamp; final String topic; final Map payload; final bool requiresAck; final int retryCount; final DateTime? expiresAt; MessageEnvelope({ required this.messageId, required this.timestamp, required this.topic, required this.payload, this.requiresAck = true, this.retryCount = 0, this.expiresAt, }); factory MessageEnvelope.fromJson(Map json) { return MessageEnvelope( messageId: json['messageId'], timestamp: DateTime.parse(json['timestamp']), topic: json['topic'], payload: json['payload'], requiresAck: json['requiresAck'] ?? true, retryCount: json['retryCount'] ?? 0, expiresAt: json['expiresAt'] != null ? DateTime.parse(json['expiresAt']) : null, ); } Map toJson() { return { 'messageId': messageId, 'timestamp': timestamp.toIso8601String(), 'topic': topic, 'payload': payload, 'requiresAck': requiresAck, 'retryCount': retryCount, 'expiresAt': expiresAt?.toIso8601String(), }; } } ``` ### 4. Acknowledgment-Handling Wenn eine Nachricht mit `requiresAck: true` empfangen wird, muss der Client eine Bestätigung senden: ```dart class AcknowledgmentMessage { final String messageId; final DateTime timestamp; final String status; // "SUCCESS" oder "FAILED" final String? errorMessage; AcknowledgmentMessage({ required this.messageId, required this.timestamp, required this.status, this.errorMessage, }); Map toJson() { return { 'messageId': messageId, 'timestamp': timestamp.toIso8601String(), 'status': status, if (errorMessage != null) 'errorMessage': errorMessage, }; } } // Verwendung: void sendAcknowledgment(String messageId, bool success, [String? error]) { final ack = AcknowledgmentMessage( messageId: messageId, timestamp: DateTime.now(), status: success ? 'SUCCESS' : 'FAILED', errorMessage: error, ); final topic = '/ack/server/$messageId'; final payload = jsonEncode(ack.toJson()); final builder = MqttClientPayloadBuilder(); builder.addString(payload); client.publishMessage( topic, MqttQos.exactlyOnce, builder.payload!, ); } ``` ### 5. Nachrichten empfangen und verarbeiten ```dart void setupMessageHandlers() { client.updates!.listen((List> messages) { for (var message in messages) { final topic = message.topic; final payload = MqttPublishPayload.bytesToStringAsString( (message.payload as MqttPublishMessage).payload.message, ); try { final json = jsonDecode(payload); // Prüfen, ob es ein Envelope ist if (json.containsKey('messageId') && json.containsKey('payload')) { final envelope = MessageEnvelope.fromJson(json); // Nachricht verarbeiten handleMessage(envelope); // ACK senden, wenn erforderlich if (envelope.requiresAck) { sendAcknowledgment(envelope.messageId, true); } } else { // Legacy-Nachricht ohne Envelope handleLegacyMessage(topic, json); } } catch (e) { print('Error processing message: $e'); // Bei Envelope-Nachrichten: Fehler-ACK senden if (json.containsKey('messageId')) { sendAcknowledgment(json['messageId'], false, e.toString()); } } } }); } ``` ### 6. Nachrichten senden ```dart Future sendMessage( String messageType, Map payload, {bool requiresAck = true} ) async { final envelope = MessageEnvelope( messageId: Uuid().v4(), timestamp: DateTime.now(), topic: '/server/$clientId/$messageType', payload: payload, requiresAck: requiresAck, ); final topic = envelope.topic; final message = jsonEncode(envelope.toJson()); final builder = MqttClientPayloadBuilder(); builder.addString(message); client.publishMessage( topic, MqttQos.exactlyOnce, builder.payload!, ); print('Message sent: $messageType'); } ``` ## Migration Checklist für Clients - [ ] **MQTT-Broker-Port auf 42099 ändern** - [ ] **Connection-Timeout auf mindestens 60 Sekunden erhöhen** - [ ] **Keep-Alive auf 60 Sekunden setzen** - [ ] **Authentifizierung hinzufügen** (username: `app`, password: `apppwd`) - [ ] **MessageEnvelope-Klasse implementieren** - [ ] **AcknowledgmentMessage-Klasse implementieren** - [ ] **Envelope-basierte Nachrichtenverarbeitung implementieren** - [ ] **ACK-Handling für eingehende Nachrichten implementieren** - [ ] **Ausgehende Nachrichten in Envelopes verpacken** - [ ] **Fehlerbehandlung für abgelaufene Nachrichten implementieren** - [ ] **Retry-Logik für fehlgeschlagene Nachrichten implementieren** ## Abwärtskompatibilität Der Server unterstützt derzeit noch Legacy-Nachrichten ohne Envelope-Format, aber es wird empfohlen, so schnell wie möglich auf das neue Format umzustellen. **Legacy-Format (wird noch unterstützt):** ```json { "taskId": "123", "status": "completed" } ``` **Neues Format (empfohlen):** ```json { "messageId": "uuid", "timestamp": "2025-10-22T10:30:00Z", "topic": "/server/client-123/task_completed", "payload": { "taskId": "123", "status": "completed" }, "requiresAck": true } ``` ## Vorteile des neuen Systems 1. **Zuverlässige Zustellung**: ACK-basiertes System mit automatischen Wiederholungen 2. **Nachrichtenverfolgung**: Jede Nachricht hat eine eindeutige ID 3. **Ablaufverwaltung**: Nachrichten können ablaufen und werden automatisch bereinigt 4. **Bessere Fehlerbehandlung**: Detaillierte Fehlerinformationen 5. **Monitoring**: Vollständige Nachrichtenverfolgung im System ## Testen der Verbindung ```dart Future testConnection() async { try { final mqttService = MqttService(); await mqttService.connect('test-client-${Uuid().v4()}'); // Test-Nachricht senden await mqttService.sendMessage( 'test', {'message': 'Hello from client'}, ); print('Connection test successful!'); } catch (e) { print('Connection test failed: $e'); } } ``` ## Support und Fragen Bei Fragen oder Problemen während der Migration: 1. Prüfen Sie die Logs auf beiden Seiten (Client und Server) 2. Stellen Sie sicher, dass der Port 42099 erreichbar ist 3. Überprüfen Sie die Authentifizierungsdaten 4. Testen Sie die Verbindung mit einem MQTT-Client-Tool (z.B. MQTT Explorer) ## Weitere Dokumentation - `MESSAGING_LAYER.md` - Detaillierte Architektur des Messaging-Systems - `MQTT_README.md` - MQTT-API-Dokumentation - `CLAUDE.md` - Allgemeine Systemarchitektur