# MQTT Protocol für IoT-Kommunikation **Meta-Description:** MQTT Protokoll für IoT. Publish/Subscribe Pattern, QoS Levels, Broker Setup und Node.js Integration mit MQTT.js. **Keywords:** MQTT, IoT, Publish Subscribe, MQTT.js, Mosquitto, Message Broker, QoS, Real-time --- ## Einführung **MQTT** (Message Queuing Telemetry Transport) ist das Standard-Protokoll für IoT-Kommunikation. Leichtgewichtig, effizient und für instabile Netzwerke optimiert – perfekt für Sensor-Netzwerke und Smart Home Anwendungen. --- ## MQTT Architecture ``` ┌─────────────────────────────────────────────────────────────┐ │ MQTT ARCHITECTURE │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Publishers Broker Subscribers │ │ ┌─────────┐ ┌──────────┐ ┌─────────┐ │ │ │ Sensor │────────→│ │────────→│Dashboard│ │ │ └─────────┘ │ │ └─────────┘ │ │ │ Mosquitto│ │ │ ┌─────────┐ │ / EMQX │ ┌─────────┐ │ │ │ Device │────────→│ │────────→│ Backend │ │ │ └─────────┘ │ │ └─────────┘ │ │ │ │ │ │ ┌─────────┐ │ │ ┌─────────┐ │ │ │ Gateway │←───────→│ │←───────→│ Mobile │ │ │ └─────────┘ └──────────┘ └─────────┘ │ │ │ │ Topic Structure: │ │ home/living-room/temperature → 21.5 │ │ home/living-room/humidity → 45 │ │ home/bedroom/light/state → on │ │ home/bedroom/light/brightness → 80 │ │ │ │ QoS Levels: │ │ 0: At most once (Fire & Forget) │ │ 1: At least once (Acknowledged) │ │ 2: Exactly once (Assured) │ │ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## Mosquitto Broker Setup ```bash # Docker Installation docker run -d \ --name mosquitto \ -p 1883:1883 \ -p 9001:9001 \ -v $(pwd)/mosquitto/config:/mosquitto/config \ -v $(pwd)/mosquitto/data:/mosquitto/data \ -v $(pwd)/mosquitto/log:/mosquitto/log \ eclipse-mosquitto # mosquitto/config/mosquitto.conf listener 1883 listener 9001 protocol websockets allow_anonymous false password_file /mosquitto/config/password.txt persistence true persistence_location /mosquitto/data/ log_dest file /mosquitto/log/mosquitto.log log_type all # TLS Konfiguration listener 8883 cafile /mosquitto/certs/ca.crt certfile /mosquitto/certs/server.crt keyfile /mosquitto/certs/server.key require_certificate false ``` ```yaml # docker-compose.yml version: '3.8' services: mosquitto: image: eclipse-mosquitto:2 container_name: mosquitto ports: - "1883:1883" # MQTT - "8883:8883" # MQTT/TLS - "9001:9001" # WebSocket volumes: - ./mosquitto/config:/mosquitto/config - ./mosquitto/data:/mosquitto/data - ./mosquitto/log:/mosquitto/log restart: unless-stopped ``` --- ## MQTT.js Client ```typescript // lib/mqtt-client.ts import mqtt, { MqttClient, IClientOptions, QoS } from 'mqtt'; interface MQTTConfig { brokerUrl: string; options?: IClientOptions; } interface PublishOptions { qos?: QoS; retain?: boolean; } type MessageHandler = (topic: string, message: string, packet: any) => void; class IoTMQTTClient { private client: MqttClient | null = null; private subscriptions: Map> = new Map(); private connected = false; private reconnectAttempts = 0; private maxReconnectAttempts = 10; constructor(private config: MQTTConfig) {} async connect(): Promise { return new Promise((resolve, reject) => { const options: IClientOptions = { clientId: `mqtt_${Math.random().toString(16).slice(2, 10)}`, clean: true, connectTimeout: 30000, reconnectPeriod: 5000, keepalive: 60, ...this.config.options }; this.client = mqtt.connect(this.config.brokerUrl, options); this.client.on('connect', () => { console.log('Connected to MQTT broker'); this.connected = true; this.reconnectAttempts = 0; // Re-subscribe nach Reconnect this.resubscribe(); resolve(); }); this.client.on('message', (topic, payload, packet) => { const message = payload.toString(); this.handleMessage(topic, message, packet); }); this.client.on('error', (error) => { console.error('MQTT Error:', error); if (!this.connected) { reject(error); } }); this.client.on('close', () => { console.log('MQTT connection closed'); this.connected = false; }); this.client.on('reconnect', () => { this.reconnectAttempts++; console.log(`Reconnecting... Attempt ${this.reconnectAttempts}`); if (this.reconnectAttempts >= this.maxReconnectAttempts) { this.client?.end(); console.error('Max reconnection attempts reached'); } }); this.client.on('offline', () => { console.log('MQTT client offline'); }); }); } private handleMessage(topic: string, message: string, packet: any) { // Exakte Matches const handlers = this.subscriptions.get(topic); if (handlers) { handlers.forEach(handler => handler(topic, message, packet)); } // Wildcard Matches for (const [pattern, patternHandlers] of this.subscriptions) { if (this.matchTopic(pattern, topic)) { patternHandlers.forEach(handler => handler(topic, message, packet)); } } } private matchTopic(pattern: string, topic: string): boolean { if (pattern === topic) return false; // Already handled above const patternParts = pattern.split('/'); const topicParts = topic.split('/'); for (let i = 0; i < patternParts.length; i++) { if (patternParts[i] === '#') { return true; // Multi-level wildcard matches rest } if (patternParts[i] === '+') { continue; // Single-level wildcard matches any single level } if (patternParts[i] !== topicParts[i]) { return false; } } return patternParts.length === topicParts.length; } private resubscribe() { for (const [topic] of this.subscriptions) { this.client?.subscribe(topic, { qos: 1 }); } } // Publish async publish( topic: string, message: string | object, options: PublishOptions = {} ): Promise { return new Promise((resolve, reject) => { if (!this.client || !this.connected) { reject(new Error('Not connected to broker')); return; } const payload = typeof message === 'object' ? JSON.stringify(message) : message; this.client.publish( topic, payload, { qos: options.qos || 1, retain: options.retain || false }, (error) => { if (error) reject(error); else resolve(); } ); }); } // Subscribe subscribe( topic: string, handler: MessageHandler, qos: QoS = 1 ): () => void { if (!this.subscriptions.has(topic)) { this.subscriptions.set(topic, new Set()); this.client?.subscribe(topic, { qos }); } this.subscriptions.get(topic)!.add(handler); // Unsubscribe function return () => { const handlers = this.subscriptions.get(topic); if (handlers) { handlers.delete(handler); if (handlers.size === 0) { this.subscriptions.delete(topic); this.client?.unsubscribe(topic); } } }; } // Disconnect disconnect(): Promise { return new Promise((resolve) => { this.client?.end(false, {}, () => { this.connected = false; resolve(); }); }); } isConnected(): boolean { return this.connected; } } export { IoTMQTTClient }; ``` --- ## Sensor Data Publishing ```typescript // devices/temperature-sensor.ts import { IoTMQTTClient } from '../lib/mqtt-client'; interface SensorReading { device_id: string; timestamp: string; temperature: number; humidity: number; battery: number; } class TemperatureSensor { private client: IoTMQTTClient; private deviceId: string; private baseTopic: string; private intervalId?: NodeJS.Timeout; constructor( brokerUrl: string, deviceId: string, options?: { username?: string; password?: string } ) { this.deviceId = deviceId; this.baseTopic = `sensors/${deviceId}`; this.client = new IoTMQTTClient({ brokerUrl, options: { username: options?.username, password: options?.password, will: { topic: `${this.baseTopic}/status`, payload: Buffer.from('offline'), qos: 1, retain: true } } }); } async start(intervalMs: number = 60000): Promise { await this.client.connect(); // Online Status await this.client.publish( `${this.baseTopic}/status`, 'online', { retain: true } ); // Device Info await this.client.publish( `${this.baseTopic}/info`, { device_id: this.deviceId, type: 'temperature_humidity', firmware: '1.0.0', started_at: new Date().toISOString() }, { retain: true } ); // Periodisch Daten senden this.intervalId = setInterval(() => this.publishReading(), intervalMs); // Initial Reading await this.publishReading(); console.log(`Sensor ${this.deviceId} started`); } private async publishReading(): Promise { const reading: SensorReading = { device_id: this.deviceId, timestamp: new Date().toISOString(), temperature: this.readTemperature(), humidity: this.readHumidity(), battery: this.readBattery() }; // JSON Format await this.client.publish( `${this.baseTopic}/data`, reading, { qos: 1 } ); // Home Assistant Discovery Format await this.client.publish( `homeassistant/sensor/${this.deviceId}/temperature/state`, reading.temperature.toString() ); await this.client.publish( `homeassistant/sensor/${this.deviceId}/humidity/state`, reading.humidity.toString() ); console.log(`Published: ${JSON.stringify(reading)}`); } // Simulierte Sensor-Werte (in Produktion: echte Hardware-Reads) private readTemperature(): number { return Math.round((20 + Math.random() * 5) * 10) / 10; } private readHumidity(): number { return Math.round((40 + Math.random() * 20) * 10) / 10; } private readBattery(): number { return Math.round((80 + Math.random() * 20) * 10) / 10; } async stop(): Promise { if (this.intervalId) { clearInterval(this.intervalId); } await this.client.publish( `${this.baseTopic}/status`, 'offline', { retain: true } ); await this.client.disconnect(); console.log(`Sensor ${this.deviceId} stopped`); } } // Verwendung const sensor = new TemperatureSensor( 'mqtt://localhost:1883', 'living-room-01', { username: process.env.MQTT_USER, password: process.env.MQTT_PASS } ); await sensor.start(30000); // Alle 30 Sekunden // Graceful Shutdown process.on('SIGTERM', async () => { await sensor.stop(); process.exit(0); }); ``` --- ## Data Subscriber Service ```typescript // services/mqtt-data-collector.ts import { IoTMQTTClient } from '../lib/mqtt-client'; import { InfluxDBClient } from '../lib/influxdb'; interface SensorData { device_id: string; timestamp: string; [key: string]: any; } class MQTTDataCollector { private mqtt: IoTMQTTClient; private influx: InfluxDBClient; private subscriptions: Array<() => void> = []; constructor( mqttUrl: string, influxConfig: { url: string; token: string; org: string; bucket: string } ) { this.mqtt = new IoTMQTTClient({ brokerUrl: mqttUrl, options: { clientId: 'data-collector', username: process.env.MQTT_USER, password: process.env.MQTT_PASS } }); this.influx = new InfluxDBClient(influxConfig); } async start(): Promise { await this.mqtt.connect(); // Subscribe to all sensor data const unsubSensors = this.mqtt.subscribe( 'sensors/+/data', async (topic, message) => { try { const data = JSON.parse(message) as SensorData; await this.processSensorData(topic, data); } catch (error) { console.error('Failed to process sensor data:', error); } } ); this.subscriptions.push(unsubSensors); // Subscribe to device status const unsubStatus = this.mqtt.subscribe( 'sensors/+/status', (topic, message) => { const deviceId = topic.split('/')[1]; console.log(`Device ${deviceId} is ${message}`); this.updateDeviceStatus(deviceId, message); } ); this.subscriptions.push(unsubStatus); // Subscribe to commands const unsubCommands = this.mqtt.subscribe( 'commands/#', this.handleCommand.bind(this) ); this.subscriptions.push(unsubCommands); console.log('Data collector started'); } private async processSensorData(topic: string, data: SensorData): Promise { const deviceId = topic.split('/')[1]; // In InfluxDB speichern await this.influx.writePoint({ measurement: 'sensor_readings', tags: { device_id: deviceId, location: this.getDeviceLocation(deviceId) }, fields: { temperature: data.temperature, humidity: data.humidity, battery: data.battery }, timestamp: new Date(data.timestamp) }); // Alerts prüfen await this.checkAlerts(deviceId, data); } private async checkAlerts(deviceId: string, data: SensorData): Promise { // Temperatur-Alert if (data.temperature > 30) { await this.mqtt.publish('alerts/temperature/high', { device_id: deviceId, temperature: data.temperature, timestamp: data.timestamp, message: `High temperature alert: ${data.temperature}°C` }); } // Batterie-Alert if (data.battery < 20) { await this.mqtt.publish('alerts/battery/low', { device_id: deviceId, battery: data.battery, timestamp: data.timestamp, message: `Low battery alert: ${data.battery}%` }); } } private handleCommand(topic: string, message: string): void { const parts = topic.split('/'); const target = parts[1]; const action = parts[2]; console.log(`Command: ${action} for ${target}`); // Command weiterleiten an Gerät this.mqtt.publish(`devices/${target}/command`, { action, payload: JSON.parse(message), timestamp: new Date().toISOString() }); } private getDeviceLocation(deviceId: string): string { const locationMap: Record = { 'living-room-01': 'living_room', 'bedroom-01': 'bedroom', 'kitchen-01': 'kitchen' }; return locationMap[deviceId] || 'unknown'; } private updateDeviceStatus(deviceId: string, status: string): void { // Status in DB speichern console.log(`Device ${deviceId} status: ${status}`); } async stop(): Promise { this.subscriptions.forEach(unsub => unsub()); await this.mqtt.disconnect(); } } ``` --- ## WebSocket Bridge (Browser) ```typescript // lib/mqtt-websocket.ts import mqtt, { MqttClient } from 'mqtt'; class MQTTWebSocketClient { private client: MqttClient | null = null; async connect(wsUrl: string, options?: { username?: string; password?: string; }): Promise { return new Promise((resolve, reject) => { this.client = mqtt.connect(wsUrl, { protocol: 'wss', clientId: `web_${Math.random().toString(16).slice(2, 8)}`, ...options }); this.client.on('connect', () => { console.log('WebSocket MQTT connected'); resolve(); }); this.client.on('error', reject); }); } subscribe(topic: string, callback: (topic: string, message: string) => void): () => void { this.client?.subscribe(topic); const handler = (t: string, payload: Buffer) => { if (this.topicMatches(topic, t)) { callback(t, payload.toString()); } }; this.client?.on('message', handler); return () => { this.client?.unsubscribe(topic); this.client?.removeListener('message', handler); }; } publish(topic: string, message: any): void { const payload = typeof message === 'object' ? JSON.stringify(message) : String(message); this.client?.publish(topic, payload); } private topicMatches(pattern: string, topic: string): boolean { const patternParts = pattern.split('/'); const topicParts = topic.split('/'); for (let i = 0; i < patternParts.length; i++) { if (patternParts[i] === '#') return true; if (patternParts[i] === '+') continue; if (patternParts[i] !== topicParts[i]) return false; } return patternParts.length === topicParts.length; } } // React Hook import { useState, useEffect, useCallback } from 'react'; export function useMQTT(wsUrl: string) { const [client] = useState(() => new MQTTWebSocketClient()); const [connected, setConnected] = useState(false); useEffect(() => { client.connect(wsUrl).then(() => setConnected(true)); }, [wsUrl]); const subscribe = useCallback(( topic: string, callback: (topic: string, message: string) => void ) => { return client.subscribe(topic, callback); }, [client]); const publish = useCallback((topic: string, message: any) => { client.publish(topic, message); }, [client]); return { connected, subscribe, publish }; } // Verwendung in React Component function SensorDashboard() { const { connected, subscribe } = useMQTT('wss://mqtt.example.com:9001'); const [temperature, setTemperature] = useState(null); useEffect(() => { if (!connected) return; const unsub = subscribe('sensors/+/data', (topic, message) => { const data = JSON.parse(message); setTemperature(data.temperature); }); return unsub; }, [connected, subscribe]); return (
{connected ? (

Temperature: {temperature ?? 'Loading...'}°C

) : (

Connecting...

)}
); } ``` --- ## Secure MQTT with TLS ```typescript // lib/secure-mqtt.ts import mqtt from 'mqtt'; import fs from 'fs'; import path from 'path'; const secureClient = mqtt.connect('mqtts://mqtt.example.com:8883', { clientId: `secure_${Date.now()}`, username: process.env.MQTT_USER, password: process.env.MQTT_PASS, // TLS Optionen ca: fs.readFileSync(path.join(__dirname, 'certs/ca.crt')), cert: fs.readFileSync(path.join(__dirname, 'certs/client.crt')), key: fs.readFileSync(path.join(__dirname, 'certs/client.key')), // Bei selbstsignierten Zertifikaten rejectUnauthorized: process.env.NODE_ENV === 'production', // Protokoll-Version protocolVersion: 5, // MQTT 5.0 // Clean Start (MQTT 5.0) clean: true, // Session Expiry (MQTT 5.0) properties: { sessionExpiryInterval: 3600 } }); secureClient.on('connect', (connack) => { console.log('Secure connection established'); console.log('Session present:', connack.sessionPresent); }); ``` --- ## QoS Level Comparison | QoS | Name | Garantie | Use Case | |-----|------|----------|----------| | 0 | At most once | Keine | Telemetrie, unwichtige Daten | | 1 | At least once | Zustellung garantiert | Sensor-Daten, Commands | | 2 | Exactly once | Keine Duplikate | Zahlungen, kritische Daten | --- ## Fazit MQTT für IoT bietet: 1. **Leichtgewichtig**: Minimaler Overhead 2. **Publish/Subscribe**: Entkoppelte Kommunikation 3. **QoS Levels**: Flexible Zustellgarantien 4. **Wildcards**: Flexible Topic-Struktur Standard für IoT-Kommunikation. --- ## Bildprompts 1. "IoT devices connected through message broker, publish subscribe pattern" 2. "Sensor data flowing to central hub, MQTT network visualization" 3. "Quality of Service levels diagram, message delivery guarantee" --- ## Quellen - [MQTT.js GitHub](https://github.com/mqttjs/MQTT.js/) - [HiveMQ MQTT Guide](https://www.hivemq.com/blog/ultimate-guide-on-how-to-use-mqtt-with-node-js/) - [EMQX MQTT Tutorial](https://www.emqx.com/en/blog/how-to-use-mqtt-in-nodejs) - [Mosquitto Documentation](https://mosquitto.org/documentation/)