Initial commit: Portfolio Website
Vollständige Next.js 15 Portfolio-Website mit: - Blog-System mit 100+ Artikeln - Supabase-Integration - Responsive Design mit Tailwind CSS - TypeScript-Konfiguration - Testing-Setup mit Vitest und Playwright Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,815 @@
|
||||
# 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<string, Set<MessageHandler>> = new Map();
|
||||
private connected = false;
|
||||
private reconnectAttempts = 0;
|
||||
private maxReconnectAttempts = 10;
|
||||
|
||||
constructor(private config: MQTTConfig) {}
|
||||
|
||||
async connect(): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
// 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<string, string> = {
|
||||
'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<void> {
|
||||
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<void> {
|
||||
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<number | null>(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 (
|
||||
<div>
|
||||
{connected ? (
|
||||
<p>Temperature: {temperature ?? 'Loading...'}°C</p>
|
||||
) : (
|
||||
<p>Connecting...</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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/)
|
||||
Reference in New Issue
Block a user