Files
votianlt/MQTT_MIGRATION_GUIDE.md
2025-10-23 12:18:42 +02:00

10 KiB

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):

app.mqtt.enabled=true
app.mqtt.broker-uri=mqtt://mqtt-2.assecutor.de
app.mqtt.client-id=server-${random.uuid}

Neue Konfiguration:

# 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:

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<void> 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:

{
  "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:

class MessageEnvelope {
  final String messageId;
  final DateTime timestamp;
  final String topic;
  final Map<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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:

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<String, dynamic> 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

void setupMessageHandlers() {
  client.updates!.listen((List<MqttReceivedMessage<MqttMessage>> 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

Future<void> sendMessage(
  String messageType,
  Map<String, dynamic> 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):

{
  "taskId": "123",
  "status": "completed"
}

Neues Format (empfohlen):

{
  "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

Future<void> 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