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
- Zuverlässige Zustellung: ACK-basiertes System mit automatischen Wiederholungen
- Nachrichtenverfolgung: Jede Nachricht hat eine eindeutige ID
- Ablaufverwaltung: Nachrichten können ablaufen und werden automatisch bereinigt
- Bessere Fehlerbehandlung: Detaillierte Fehlerinformationen
- 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:
- Prüfen Sie die Logs auf beiden Seiten (Client und Server)
- Stellen Sie sicher, dass der Port 42099 erreichbar ist
- Überprüfen Sie die Authentifizierungsdaten
- Testen Sie die Verbindung mit einem MQTT-Client-Tool (z.B. MQTT Explorer)
Weitere Dokumentation
MESSAGING_LAYER.md- Detaillierte Architektur des Messaging-SystemsMQTT_README.md- MQTT-API-DokumentationCLAUDE.md- Allgemeine Systemarchitektur