Files
Portfolio/blog-posts/64-raspberry-pi-iot.md
damjan_savicandClaude Opus 4.5 e1bbe5455d 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>
2026-02-01 15:07:20 +01:00

767 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Raspberry Pi IoT Projekte mit Node.js
**Meta-Description:** IoT-Projekte mit Raspberry Pi und Node.js. GPIO-Steuerung, Sensor-Integration, MQTT und Web Dashboards.
**Keywords:** Raspberry Pi, Node.js, GPIO, IoT, Sensors, Home Automation, MQTT, Embedded Linux
---
## Einführung
Der **Raspberry Pi** ist der perfekte Single-Board Computer für IoT-Projekte. Mit **Node.js** kombiniert man die Hardware-Nähe mit dem mächtigen npm-Ökosystem für Web-basierte IoT-Anwendungen.
---
## Raspberry Pi Setup
```
┌─────────────────────────────────────────────────────────────┐
│ RASPBERRY PI 5 SPECIFICATIONS │
├─────────────────────────────────────────────────────────────┤
│ │
│ CPU: Broadcom BCM2712 quad-core Arm Cortex-A76 @ 2.4GHz │
│ RAM: 4GB / 8GB LPDDR4X-4267 │
│ │
│ Connectivity: │
│ ├── Gigabit Ethernet │
│ ├── Dual-band WiFi 802.11ac │
│ ├── Bluetooth 5.0 / BLE │
│ ├── 2× USB 3.0 + 2× USB 2.0 │
│ └── PCIe 2.0 x1 │
│ │
│ GPIO: │
│ ├── 40-pin GPIO Header │
│ ├── 26 GPIO Pins │
│ ├── I2C, SPI, UART │
│ └── PWM (Hardware) │
│ │
│ Storage: microSD / NVMe (via HAT) │
│ Power: 5V/5A USB-C │
│ │
│ GPIO Pinout (Subset): │
│ ┌────┬────┬────────────────────────┐ │
│ │ 1 │ 2 │ 3.3V | 5V │ │
│ │ 3 │ 4 │ GPIO2 | 5V │ │
│ │ 5 │ 6 │ GPIO3 | GND │ │
│ │ 7 │ 8 │ GPIO4 | GPIO14(TX) │ │
│ │ 9 │ 10 │ GND | GPIO15(RX) │ │
│ └────┴────┴────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
---
## Node.js Installation
```bash
# Node.js via NodeSource (empfohlen)
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt-get install -y nodejs
# Alternativ: NVM (Node Version Manager)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash
source ~/.bashrc
nvm install 22
nvm use 22
# Verify
node --version
npm --version
# Build Tools für native Addons
sudo apt-get install -y build-essential python3
# GPIO Berechtigungen (ohne sudo)
sudo usermod -aG gpio $USER
sudo usermod -aG i2c $USER
sudo usermod -aG spi $USER
```
---
## GPIO Control mit Node.js
```typescript
// lib/gpio.ts
import { Gpio } from 'onoff';
// LED Control
const LED_PIN = 17;
const led = new Gpio(LED_PIN, 'out');
// LED einschalten
led.writeSync(1);
// LED ausschalten
led.writeSync(0);
// Asynchrone Steuerung
async function blinkLED(times: number, intervalMs: number) {
for (let i = 0; i < times; i++) {
await led.write(1);
await sleep(intervalMs);
await led.write(0);
await sleep(intervalMs);
}
}
// Button mit Interrupt
const BUTTON_PIN = 18;
const button = new Gpio(BUTTON_PIN, 'in', 'both', { debounceTimeout: 50 });
button.watch((err, value) => {
if (err) {
console.error('Button error:', err);
return;
}
console.log(`Button: ${value === 1 ? 'pressed' : 'released'}`);
});
// Cleanup bei Programmende
process.on('SIGINT', () => {
led.unexport();
button.unexport();
process.exit();
});
// Helper
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
```
```typescript
// Für Raspberry Pi 5: gpiod verwenden
// lib/gpio-pi5.ts
import { Chip, Line } from '@iiot2k/gpiox';
const chip = new Chip(4); // Pi5 verwendet Chip 4
// LED Setup
const ledLine = chip.getLine(17);
ledLine.requestOutput('led');
// LED steuern
ledLine.setValue(1); // On
ledLine.setValue(0); // Off
// Button Setup mit Events
const buttonLine = chip.getLine(18);
buttonLine.requestInput('button');
buttonLine.requestBothEdges();
buttonLine.addEventListener('value', (event) => {
console.log(`Button value: ${event.value}`);
});
```
---
## Sensor-Integration
```typescript
// lib/sensors/dht22.ts
import sensor from 'node-dht-sensor';
const DHT_TYPE = 22; // DHT22
const DHT_PIN = 4;
interface Reading {
temperature: number;
humidity: number;
timestamp: Date;
}
export async function readDHT22(): Promise<Reading> {
return new Promise((resolve, reject) => {
sensor.read(DHT_TYPE, DHT_PIN, (err, temperature, humidity) => {
if (err) {
reject(err);
return;
}
resolve({
temperature: Math.round(temperature * 10) / 10,
humidity: Math.round(humidity * 10) / 10,
timestamp: new Date()
});
});
});
}
// Retry Logic für zuverlässigere Messungen
export async function readDHT22WithRetry(maxRetries = 3): Promise<Reading> {
let lastError: Error | null = null;
for (let i = 0; i < maxRetries; i++) {
try {
const reading = await readDHT22();
// Plausibilitätsprüfung
if (reading.temperature > -40 && reading.temperature < 80 &&
reading.humidity >= 0 && reading.humidity <= 100) {
return reading;
}
} catch (err) {
lastError = err as Error;
await sleep(2000); // DHT braucht Zeit zwischen Messungen
}
}
throw lastError || new Error('Failed to read DHT22');
}
```
```typescript
// lib/sensors/bme280.ts
import BME280 from 'bme280-sensor';
const bme280 = new BME280({ i2cBusNo: 1, i2cAddress: 0x76 });
interface EnvironmentData {
temperature: number;
humidity: number;
pressure: number;
}
export async function initBME280(): Promise<void> {
await bme280.init();
console.log('BME280 initialized');
}
export async function readBME280(): Promise<EnvironmentData> {
const data = await bme280.readSensorData();
return {
temperature: Math.round(data.temperature_C * 10) / 10,
humidity: Math.round(data.humidity * 10) / 10,
pressure: Math.round(data.pressure_hPa * 10) / 10
};
}
```
```typescript
// lib/sensors/ultrasonic.ts
import { Gpio } from 'onoff';
const TRIG_PIN = 23;
const ECHO_PIN = 24;
const trigger = new Gpio(TRIG_PIN, 'out');
const echo = new Gpio(ECHO_PIN, 'in', 'both');
export function measureDistance(): Promise<number> {
return new Promise((resolve, reject) => {
let startTime: bigint;
let endTime: bigint;
let timeout: NodeJS.Timeout;
// Timeout nach 1 Sekunde
timeout = setTimeout(() => {
reject(new Error('Measurement timeout'));
}, 1000);
echo.watch((err, value) => {
if (err) {
clearTimeout(timeout);
reject(err);
return;
}
if (value === 1) {
startTime = process.hrtime.bigint();
} else {
endTime = process.hrtime.bigint();
clearTimeout(timeout);
// Distanz berechnen
const duration = Number(endTime - startTime) / 1e9; // Sekunden
const distance = (duration * 34300) / 2; // cm
resolve(Math.round(distance * 10) / 10);
}
});
// Trigger Puls
trigger.writeSync(1);
setTimeout(() => trigger.writeSync(0), 10); // 10µs Puls
});
}
```
---
## MQTT Sensor Node
```typescript
// apps/sensor-node.ts
import mqtt from 'mqtt';
import { readDHT22WithRetry } from './lib/sensors/dht22';
import { measureDistance } from './lib/sensors/ultrasonic';
import os from 'os';
interface SensorNodeConfig {
brokerUrl: string;
deviceId: string;
readIntervalMs: number;
}
class SensorNode {
private client: mqtt.MqttClient;
private config: SensorNodeConfig;
private baseTopic: string;
private running = false;
constructor(config: SensorNodeConfig) {
this.config = config;
this.baseTopic = `sensors/${config.deviceId}`;
this.client = mqtt.connect(config.brokerUrl, {
clientId: config.deviceId,
will: {
topic: `${this.baseTopic}/status`,
payload: Buffer.from('offline'),
qos: 1,
retain: true
}
});
}
async start(): Promise<void> {
await this.waitForConnection();
// Online Status
this.client.publish(
`${this.baseTopic}/status`,
'online',
{ retain: true }
);
// Device Info
this.client.publish(
`${this.baseTopic}/info`,
JSON.stringify({
device_id: this.config.deviceId,
hostname: os.hostname(),
platform: os.platform(),
arch: os.arch(),
uptime: os.uptime(),
memory: {
total: os.totalmem(),
free: os.freemem()
}
}),
{ retain: true }
);
// Commands abonnieren
this.client.subscribe(`${this.baseTopic}/command`);
this.client.on('message', (topic, payload) => {
this.handleCommand(payload.toString());
});
this.running = true;
this.sensorLoop();
}
private async sensorLoop(): Promise<void> {
while (this.running) {
try {
// Alle Sensoren lesen
const [dht, distance] = await Promise.allSettled([
readDHT22WithRetry(),
measureDistance()
]);
const data: Record<string, any> = {
device_id: this.config.deviceId,
timestamp: new Date().toISOString()
};
if (dht.status === 'fulfilled') {
data.temperature = dht.value.temperature;
data.humidity = dht.value.humidity;
}
if (distance.status === 'fulfilled') {
data.distance = distance.value;
}
// System Metrics
data.system = {
cpu_temp: await this.getCPUTemperature(),
memory_usage: (1 - os.freemem() / os.totalmem()) * 100,
load_average: os.loadavg()[0]
};
// Publish
this.client.publish(
`${this.baseTopic}/data`,
JSON.stringify(data),
{ qos: 1 }
);
console.log('Published:', data);
} catch (error) {
console.error('Sensor read error:', error);
}
await this.sleep(this.config.readIntervalMs);
}
}
private handleCommand(payload: string): void {
try {
const command = JSON.parse(payload);
switch (command.action) {
case 'restart':
console.log('Restart command received');
process.exit(0); // Systemd wird neustarten
break;
case 'setInterval':
this.config.readIntervalMs = command.value;
console.log(`Interval set to ${command.value}ms`);
break;
case 'status':
this.publishStatus();
break;
}
} catch (error) {
console.error('Command parse error:', error);
}
}
private async getCPUTemperature(): Promise<number> {
try {
const { readFile } = await import('fs/promises');
const temp = await readFile('/sys/class/thermal/thermal_zone0/temp', 'utf8');
return parseInt(temp) / 1000;
} catch {
return 0;
}
}
private publishStatus(): void {
this.client.publish(
`${this.baseTopic}/status/detailed`,
JSON.stringify({
running: this.running,
uptime: process.uptime(),
memory: process.memoryUsage(),
interval: this.config.readIntervalMs
})
);
}
private waitForConnection(): Promise<void> {
return new Promise((resolve, reject) => {
this.client.on('connect', () => {
console.log('Connected to MQTT broker');
resolve();
});
this.client.on('error', reject);
setTimeout(() => reject(new Error('Connection timeout')), 30000);
});
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
stop(): void {
this.running = false;
this.client.publish(
`${this.baseTopic}/status`,
'offline',
{ retain: true }
);
this.client.end();
}
}
// Verwendung
const node = new SensorNode({
brokerUrl: process.env.MQTT_URL || 'mqtt://localhost:1883',
deviceId: process.env.DEVICE_ID || `rpi-${os.hostname()}`,
readIntervalMs: 30000
});
node.start();
// Graceful Shutdown
process.on('SIGINT', () => {
console.log('Shutting down...');
node.stop();
process.exit(0);
});
```
---
## Web Dashboard
```typescript
// apps/dashboard/server.ts
import express from 'express';
import { createServer } from 'http';
import { Server as SocketServer } from 'socket.io';
import mqtt from 'mqtt';
const app = express();
const server = createServer(app);
const io = new SocketServer(server);
// MQTT Client
const mqttClient = mqtt.connect(process.env.MQTT_URL || 'mqtt://localhost:1883');
// Sensor Data speichern
const sensorData: Map<string, any[]> = new Map();
const MAX_HISTORY = 100;
mqttClient.on('connect', () => {
console.log('MQTT connected');
mqttClient.subscribe('sensors/+/data');
mqttClient.subscribe('sensors/+/status');
});
mqttClient.on('message', (topic, payload) => {
const parts = topic.split('/');
const deviceId = parts[1];
const type = parts[2];
if (type === 'data') {
const data = JSON.parse(payload.toString());
// History speichern
if (!sensorData.has(deviceId)) {
sensorData.set(deviceId, []);
}
const history = sensorData.get(deviceId)!;
history.push(data);
if (history.length > MAX_HISTORY) {
history.shift();
}
// An WebSocket Clients senden
io.emit('sensorData', { deviceId, data });
}
if (type === 'status') {
io.emit('deviceStatus', {
deviceId,
status: payload.toString()
});
}
});
// Static Files
app.use(express.static('public'));
// API Endpoints
app.get('/api/devices', (req, res) => {
const devices = Array.from(sensorData.keys()).map(id => ({
id,
lastReading: sensorData.get(id)?.slice(-1)[0]
}));
res.json(devices);
});
app.get('/api/devices/:id/history', (req, res) => {
const history = sensorData.get(req.params.id) || [];
res.json(history);
});
// WebSocket Events
io.on('connection', (socket) => {
console.log('Client connected');
// Initial Data senden
sensorData.forEach((history, deviceId) => {
socket.emit('initialData', {
deviceId,
history: history.slice(-20)
});
});
// Command an Gerät senden
socket.on('command', ({ deviceId, action, payload }) => {
mqttClient.publish(
`sensors/${deviceId}/command`,
JSON.stringify({ action, ...payload })
);
});
});
server.listen(3000, () => {
console.log('Dashboard running on http://localhost:3000');
});
```
```html
<!-- public/index.html -->
<!DOCTYPE html>
<html>
<head>
<title>Pi Sensor Dashboard</title>
<script src="https://cdn.socket.io/4.7.4/socket.io.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body { font-family: sans-serif; padding: 20px; background: #1a1a2e; color: #eee; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; }
.card { background: #16213e; border-radius: 12px; padding: 20px; }
.value { font-size: 48px; font-weight: bold; color: #00d9ff; }
.label { color: #888; margin-top: 5px; }
canvas { max-height: 200px; }
</style>
</head>
<body>
<h1>Pi Sensor Dashboard</h1>
<div class="grid">
<div class="card">
<div class="value" id="temperature">--</div>
<div class="label">Temperature (°C)</div>
</div>
<div class="card">
<div class="value" id="humidity">--</div>
<div class="label">Humidity (%)</div>
</div>
<div class="card">
<canvas id="chart"></canvas>
</div>
</div>
<script>
const socket = io();
const ctx = document.getElementById('chart').getContext('2d');
const chart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Temperature',
data: [],
borderColor: '#00d9ff',
tension: 0.4
}, {
label: 'Humidity',
data: [],
borderColor: '#ff6b6b',
tension: 0.4
}]
},
options: {
responsive: true,
scales: { y: { beginAtZero: false } }
}
});
socket.on('sensorData', ({ deviceId, data }) => {
document.getElementById('temperature').textContent = data.temperature?.toFixed(1) || '--';
document.getElementById('humidity').textContent = data.humidity?.toFixed(1) || '--';
// Chart Update
const time = new Date(data.timestamp).toLocaleTimeString();
chart.data.labels.push(time);
chart.data.datasets[0].data.push(data.temperature);
chart.data.datasets[1].data.push(data.humidity);
if (chart.data.labels.length > 20) {
chart.data.labels.shift();
chart.data.datasets.forEach(ds => ds.data.shift());
}
chart.update('none');
});
socket.on('initialData', ({ deviceId, history }) => {
history.forEach(data => {
const time = new Date(data.timestamp).toLocaleTimeString();
chart.data.labels.push(time);
chart.data.datasets[0].data.push(data.temperature);
chart.data.datasets[1].data.push(data.humidity);
});
chart.update();
});
</script>
</body>
</html>
```
---
## Systemd Service
```ini
# /etc/systemd/system/sensor-node.service
[Unit]
Description=Raspberry Pi Sensor Node
After=network.target
[Service]
Type=simple
User=pi
WorkingDirectory=/home/pi/sensor-node
ExecStart=/usr/bin/node dist/apps/sensor-node.js
Restart=always
RestartSec=10
Environment=NODE_ENV=production
Environment=MQTT_URL=mqtt://localhost:1883
[Install]
WantedBy=multi-user.target
```
```bash
# Service aktivieren
sudo systemctl daemon-reload
sudo systemctl enable sensor-node
sudo systemctl start sensor-node
# Status prüfen
sudo systemctl status sensor-node
journalctl -u sensor-node -f
```
---
## Fazit
Raspberry Pi mit Node.js bietet:
1. **Full Linux**: Komplettes OS mit npm-Ökosystem
2. **GPIO Access**: Direkte Hardware-Steuerung
3. **Networking**: WiFi, Ethernet, Bluetooth
4. **Web Stack**: Express, Socket.io, React
Perfekt für IoT-Gateways und Edge Computing.
---
## Bildprompts
1. "Raspberry Pi with connected sensors and wires, IoT prototype"
2. "Web dashboard showing sensor data graphs, real-time monitoring"
3. "Smart home hub with Pi and multiple sensor nodes, IoT network"
---
## Quellen
- [Node.js on Raspberry Pi Guide](https://fleetstack.io/blog/nodejs-raspberry-pi-complete-guide)
- [InfluxDB + Node-RED + Raspberry Pi](https://www.influxdata.com/blog/node-red-influxdb-raspberry-pi-iot-sensor-tutorial/)
- [onoff GPIO Library](https://www.npmjs.com/package/onoff)
- [SitePoint GPIO Tutorial](https://www.sitepoint.com/getting-started-with-the-raspberry-pi-gpio-pins-in-node-js/)