399 lines
10 KiB
Markdown
399 lines
10 KiB
Markdown
# 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<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:
|
|
|
|
```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<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:
|
|
|
|
```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<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
|
|
|
|
```dart
|
|
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
|
|
|
|
```dart
|
|
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):**
|
|
```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<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
|
|
|