Menu
Back to Blog
1 min read
Automatisierung

Raspberry Pi IoT Projekte mit Node.js

IoT-Projekte mit Raspberry Pi und Node.js. GPIO-Steuerung, Sensor-Integration, MQTT und Web Dashboards.

Raspberry PiNode.jsGPIOIoTSensorsHome Automation
Raspberry Pi IoT Projekte mit Node.js

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

# 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

// 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));
}
// 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

// 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');
}
// 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
  };
}
// 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

// 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

// 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');
});
<!-- 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

# /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
# 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