Workshop: Industrielles RS-485 Modbus Energie-Gateway
In Industrieanlagen, Rechenzentren und Photovoltaik-Parks ist eine kontinuierliche Erfassung elektrischer Leistungswerte unerlässlich für Lastmanagement, Kostenstellenabrechnung und Anomalie-Erkennung. Einfache Smart-Plugs aus dem Heimbereich scheiden in Schaltschränken aus: Sie beherrschen keine 3-phasigen 400-V-Netze und besitzen keine galvanische Trennung.
In diesem praxisorientierten Workshop verwandeln wir einen Raspberry Pi (Raspberry Pi OS 64-Bit Bookworm) in ein industrielles IoT-Energie-Gateway. Wir binden einen geeichten Hutschienen-Stromzähler (Eastron SDM630 / SDM120) über einen differentiellen RS-485-Bus mit Modbus RTU an, protokollieren elektrische Kennzahlen hochfrequent in SQLite (WAL-Modus) und publizieren strukturierte Messwerte per MQTT für Dashboards in Grafana.
1. Materialliste & Elektrische Sicherheit
%%{init: {'flowchart': {'wrappingWidth': 350}}}%%
flowchart TB
subgraph Cabinet["Hauptverteilung (400V / 230V AC)"]
direction TB
GRID["3-Phasen-Netz<br/>(L1, L2, L3, N)"]
METER["Hutschienen-Zaehler<br/>(Eastron SDM630)"]
LOAD["Elektrische Last"]
GRID --> METER
LOAD --- METER
end
subgraph Bus["Industrieller Bus (RS-485)"]
direction TB
TRANS["SP3485 3,3V Transceiver"]
TERM["120 Ω Bus-Abschluss"]
TRANS --- TERM
end
subgraph Gateway["IoT-Gateway (Raspberry Pi)"]
direction TB
RPI["Raspberry Pi Host<br/>(Debian Bookworm)"]
DB["SQLite WAL-Datenbank<br/>(/data/energie.db)"]
MQTT["MQTT-Broker / Grafana"]
RPI --> DB
RPI --> MQTT
end
Cabinet -->|Adernpaar A/B| Bus
Bus -->|UART GPIO 14/15| Gateway
Benötigte Hardware-Komponenten
- Host-Computer: Raspberry Pi 4 (ab 2 GB) oder Raspberry Pi 5.
- Hutschienen-Stromzähler: Eastron SDM630 Modbus-V2 (3-phasig Drehstrom) oder Eastron SDM120 Modbus (1-phasig 230 V).
- Differentieller Transceiver: SP3485 (nativer 3,3-V-Transceiver) oder ein galvanisch getrenntes RS-485 HAT (z. B. Waveshare 2-CH RS485 HAT).
- Verkabelung: 24 AWG geschirmtes verdrilltes Adernpaar (Belden 9841 oder Cat6-Adernpärchen) sowie zwei 120-Ω-Abschlusswiderstände (0,25 W Metallschicht).
- Netzteil: 5 V / 3 A USB-C-Netzteil oder 27 W Raspberry Pi Netzteil.
Lebensgefahr durch Netzspannung (400 V / 230 V AC)
Netzspannungen sind lebensgefährlich! Die Installation des Hutschienen-Messgeräts in der Unterverteilung darf ausnahmslos durch eine zertifizierte Elektrofachkraft bei freigeschaltetem Hauptschalter erfolgen. Die RS-485-Datenklemmen (A, B, G) arbeiten mit Schutzkleinspannung (< 5 V) und sind galvanisch vom Stromnetz getrennt.
2. Hardware-Verdrahtung & Transceiver-Anschluss
Den SP3485-Transceiver mit der 40-Pin-Leiste des Raspberry Pi verbinden:
| SP3485-Pin | Raspberry Pi Pin | Funktion |
|---|---|---|
| VCC | Pin 1 (3,3 V) | Logik-Spannungsversorgung |
| GND | Pin 6 (GND) | Masse |
| TXD (DI) | Pin 8 (BCM GPIO 14 / TXD) | Serielle Sendedaten zum Transceiver |
| RXD (RO) | Pin 10 (BCM GPIO 15 / RXD) | Serielle Empfangsdaten vom Transceiver |
| DE & RE | Pin 12 (BCM GPIO 18) | Richtungsumschaltung (HIGH=Senden, LOW=Empfangen) |
| Klemme A | Stromzähler Klemme A (+) | Differenzielle Datenleitung + |
| Klemme B | Stromzähler Klemme B (-) | Differenzielle Datenleitung - |
| Schirm / G | Schutzerde (nur einseitig auflegen) | Kabelschirm gegen Störungen |
Bus-Terminierung
Schalte jeweils einen 120-Ω-Widerstand parallel zwischen Klemme A und B direkt am Stromzähler sowie am Transceiver des Raspberry Pi.
3. Host-Konfiguration: Kernel-UART & Pakete
Schritt 1: PL011-Hardware-UART freischalten
Bluetooth deaktivieren und den vollwertigen PL011-UART auf die Pins 14/15 legen:
In /boot/firmware/config.txt ergänzen:
In /boot/firmware/cmdline.txt den Parameter console=serial0,115200 entfernen.
System neu starten:
Schritt 2: Python-Bibliotheken installieren
Erforderliche Bibliotheken für Modbus, SQLite und MQTT installieren:
sudo apt update
sudo apt install -y python3-serial python3-pip sqlite3 mosquitto-clients
pip3 install --break-system-packages minimalmodbus paho-mqtt
4. Modbus-RTU Registerbelegung (Eastron SDM-Serie)
Die Eastron SDM-Zähler kommunizieren standardmäßig mit: Baudrate 9600, 8 Datenbits, keine Parität, 1 Stoppbit (8N1) und Slave-ID 1.
Messwerte werden als 32-Bit IEEE 754 Gleitkommazahlen über zwei aufeinanderfolgende 16-Bit-Input-Register übertragen (Funktionscode 0x04):
| Messgröße | Register-Adresse (Hex) | Register-Anzahl | Datentyp | Einheit |
|---|---|---|---|---|
| Phase 1 Spannung | 0x0000 |
2 | 32-Bit Float | Volt (V) |
| Phase 2 Spannung | 0x0002 |
2 | 32-Bit Float | Volt (V) |
| Phase 3 Spannung | 0x0004 |
2 | 32-Bit Float | Volt (V) |
| Phase 1 Stromstärke | 0x0006 |
2 | 32-Bit Float | Ampere (A) |
| Gesamte Wirkleistung | 0x0034 |
2 | 32-Bit Float | Watt (W) |
| Netzfrequenz | 0x0046 |
2 | 32-Bit Float | Hertz (Hz) |
| Gesamtenergie (Import) | 0x0048 |
2 | 32-Bit Float | Kilowattstunden (kWh) |
5. Gateway-Implementierung: Modbus zu SQLite & MQTT
Skript /usr/local/bin/energy_telemetry_gateway.py anlegen:
#!/usr/bin/env python3
"""
Industrielles Modbus RTU Energie-Gateway
Liest Messwerte vom Eastron SDM630 ueber RS-485 ein,
speichert sie in SQLite (WAL-Modus) und publiziert JSON per MQTT.
"""
import time
import json
import sqlite3
import logging
import minimalmodbus
import serial
import paho.mqtt.client as mqtt
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
SERIAL_PORT = "/dev/serial0"
SLAVE_ADDRESS = 1
BAUD_RATE = 9600
DB_FILE = "/data/energie_telemetrie.db"
MQTT_BROKER = "192.168.10.10"
MQTT_PORT = 1883
MQTT_TOPIC = "industrie/strom/verteiler_01"
class EnergyGateway:
def __init__(self):
# Modbus-Schnittstelle konfigurieren
self.meter = minimalmodbus.Instrument(SERIAL_PORT, SLAVE_ADDRESS)
self.meter.serial.baudrate = BAUD_RATE
self.meter.serial.bytesize = 8
self.meter.serial.parity = serial.PARITY_NONE
self.meter.serial.stopbits = 1
self.meter.serial.timeout = 0.5
self.meter.mode = minimalmodbus.MODE_RTU
self.meter.clear_buffers_before_each_transaction = True
# SQLite-Datenbank initialisieren
self._init_database()
# MQTT-Client vorbereiten
self.mqtt_client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, "rpi-energie-gateway")
try:
self.mqtt_client.connect(MQTT_BROKER, MQTT_PORT, keepalive=60)
self.mqtt_client.loop_start()
logging.info(f"Verbunden mit MQTT-Broker unter {MQTT_BROKER}:{MQTT_PORT}")
except Exception as err:
logging.warning(f"MQTT-Verbindung nicht moeglich ({err}). Laeuft im lokalen Modus.")
def _init_database(self) -> None:
conn = sqlite3.connect(DB_FILE)
cursor = conn.cursor()
cursor.execute("PRAGMA journal_mode=WAL;")
cursor.execute("""
CREATE TABLE IF NOT EXISTS messungen (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
spannung_l1 REAL,
spannung_l2 REAL,
spannung_l3 REAL,
strom_l1 REAL,
wirkleistung_w REAL,
frequenz_hz REAL,
arbeit_kwh REAL
);
""")
conn.commit()
conn.close()
logging.info(f"SQLite-Datenbank unter {DB_FILE} (WAL-Modus) initialisiert.")
def read_metrics(self) -> dict:
"""Liest 32-Bit Gleitkomma-Register per Funktionscode 0x04."""
v_l1 = self.meter.read_float(0x0000, functioncode=4, number_of_registers=2)
v_l2 = self.meter.read_float(0x0002, functioncode=4, number_of_registers=2)
v_l3 = self.meter.read_float(0x0004, functioncode=4, number_of_registers=2)
i_l1 = self.meter.read_float(0x0006, functioncode=4, number_of_registers=2)
power = self.meter.read_float(0x0034, functioncode=4, number_of_registers=2)
freq = self.meter.read_float(0x0046, functioncode=4, number_of_registers=2)
kwh = self.meter.read_float(0x0048, functioncode=4, number_of_registers=2)
return {
"timestamp": int(time.time()),
"spannung_l1": round(v_l1, 1),
"spannung_l2": round(v_l2, 1),
"spannung_l3": round(v_l3, 1),
"strom_l1": round(i_l1, 2),
"wirkleistung_w": round(power, 1),
"frequenz_hz": round(freq, 2),
"arbeit_kwh": round(kwh, 2)
}
def persist_and_publish(self, data: dict) -> None:
# In SQLite sichern
conn = sqlite3.connect(DB_FILE)
cursor = conn.cursor()
cursor.execute("""
INSERT INTO messungen (spannung_l1, spannung_l2, spannung_l3, strom_l1, wirkleistung_w, frequenz_hz, arbeit_kwh)
VALUES (?, ?, ?, ?, ?, ?, ?);
""", (data["spannung_l1"], data["spannung_l2"], data["spannung_l3"], data["strom_l1"], data["wirkleistung_w"], data["frequenz_hz"], data["arbeit_kwh"]))
conn.commit()
conn.close()
# Per MQTT senden
payload = json.dumps(data)
self.mqtt_client.publish(MQTT_TOPIC, payload, qos=1)
def run(self) -> None:
logging.info("Starte Messwerterfassung (Zyklus: 1,0s)...")
while True:
try:
metrics = self.read_metrics()
self.persist_and_publish(metrics)
logging.info(f"Netz: {metrics['spannung_l1']}V | Last: {metrics['wirkleistung_w']}W | Frequenz: {metrics['frequenz_hz']}Hz | Zaehlerstand: {metrics['arbeit_kwh']} kWh")
except (minimalmodbus.NoResponseError, minimalmodbus.InvalidResponseError) as err:
logging.warning(f"Modbus-Uebertragungsfehler: {err}")
except Exception as err:
logging.error(f"Unerwarteter Fehler: {err}")
time.sleep(1.0)
if __name__ == "__main__":
gateway = EnergyGateway()
gateway.run()
6. Systemd-Dienst für 24/7-Dauerbetrieb
Dienstdatei /etc/systemd/system/energy-gateway.service erstellen:
[Unit]
Description=Industrielles RS-485 Modbus Energie-Gateway
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=root
ExecStart=/usr/bin/python3 /usr/local/bin/energy_telemetry_gateway.py
Restart=always
RestartSec=5s
KillMode=process
# System-Haertung
ProtectSystem=full
ProtectHome=true
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
Dienst aktivieren und starten:
Logs im Live-Stream verfolgen:
7. Fehlerbehebung in der Praxis
| Fehlermeldung | Häufige Ursache | Behebung |
|---|---|---|
NoResponseError |
Vertauschte A/B-Leitungen | Leitungen an Klemme A und B am Transceiver tauschen. |
InvalidResponseError |
Fehlende Abschlusswiderstände | 120-Ω-Widerstände an beiden Leitungsenden prüfen. |
Permission Denied /dev/serial0 |
Benutzer nicht in dialout-Gruppe | sudo usermod -aG dialout $USER ausführen. |
| Negative Leistungswerte | Stromwandler (CT) falsch herum | Pfeil auf dem Messwandler muss in Richtung Verbraucher zeigen. |
Nächste Schritte
Mit dem industriellen Feldbus-Gateway im Betrieb kannst du deine Edge-Umgebung vervollständigen:
- Hochverfügbarer K3s Edge-Cluster: Baue einen selbstheilenden Kubernetes-Cluster zur Visualisierung deiner Messdaten mit Grafana und Prometheus.
- Mini-Wetterstation Workshop: Erfasse Umwelt- und Klimadaten über I2C-MEMS-Sensoren.