Shelly 1PM + ESP32: Daten speichern & zeitgesteuert senden
Der Shelly 1PM kann nicht nur Verbraucher schalten, sondern auch Verbrauchsdaten in der Cloud speichern und visualisieren. Allerdings ist dafür eine aktive WLAN-Verbindung erforderlich. Ein großer Vorteil des Shelly ist, dass lokale Szenen auch ohne Internetverbindung ausgeführt werden und das Gerät weiterhin über das lokale Netzwerk erreichbar bleibt. Doch sobald die Internetverbindung unterbrochen ist, entstehen Lücken in der Datenaufzeichnung – ein Problem, wenn man eine lückenlose Historie benötigt. In diesem Beitrag zeige ich eine einfache und kostengünstige Lösung, um Messdaten mit einem ESP32 zu puffern und sie zeitgesteuert an ThingSpeak zu senden. https://youtu.be/BwYHEvzcwqs Hinweis: Dieser Beitrag entstand aus einer Idee aus meiner YouTube-Community. Ich freue mich immer über Anregungen und Fragen von euch – so kann ich gezielt Lösungen für echte Probleme entwickeln. Wenn auch du ein technisches Problem hast oder eine Idee für einen Beitrag, dann schreib mir gerne eine Mail oder hinterlasse einen Kommentar! Die hier präsentierte Schaltung und Umsetzung funktioniert mit allen Shelly-Geräten, die eine Leistungsmessung unterstützen. Allerdings kann der JSON-Response je nach Modell variieren, daher solltest du diesen überprüfen und gegebenenfalls an den entsprechenden Stellen anpassen.
Shelly 2PM Gen2 - Vorderseite
Shelly 1PM Gen3
Shelly 1PM Mini Gen3
Shelly Plug S
REST-Schnittstelle am Shelly
Ein großer Vorteil der Shelly-Geräte ist die einfache REST-Schnittstelle, über die sie nicht nur geschaltet, sondern auch ausgelesen werden können. Mit dem Aufruf http:///rpc/Shelly.GetStatus lässt sich der aktuelle Status des Shelly als JSON abrufen. Das JSON-Format ist dabei sowohl für Menschen gut lesbar als auch leicht von einem Mikrocontroller zu verarbeiten. Zudem gibt es keine Begrenzung bei der Abrufhäufigkeit, sodass die Daten theoretisch sogar im Sekundentakt abgefragt werden können.
JSON-Response von einem Shelly Plug S Gen3 Aus dem JSON-Response wird der Block "switch:0" ausgewertet, da er die Leistungsdaten des angeschlossenen Verbrauchers enthält. Zusätzlich können wir den aktuellen Zeitstempel "unixtime" aus dem Block "sys" entnehmen und ebenfalls speichern.
Ablegen der Daten auf dem Mikrocontroller
Die gesammelten Daten werden auf dem Mikrocontroller in einer JSON-Datei gespeichert und bei bestehen einer Internetverbindung an ThingSpeak gesendet. Der ESP32-C3 verfügt über 4 MB Flash-Speicher, was ausreichend ist, um die Daten eines Tages zu speichern. Da dieses Projekt auch in Umgebungen mit instabiler Internetverbindung eingesetzt werden kann, wird vor dem Senden zunächst geprüft, ob eine Verbindung besteht. Ist das der Fall, werden die Daten übertragen. Falls keine Verbindung verfügbar ist, wird das Senden zu einem späteren Zeitpunkt erneut versucht.
Aufbau der Schaltung mit einem Shelly 1PM Mini Gen3 und ESP32-C3 Mini
Für den Aufbau der kleinen Schaltung benötigst du: - einen Shelly 1PM Mini Gen3* oder Shelly Plug S* - einen ESP32-C3 Mini* - ein USB-C Datenkabel Hinweis von mir: Die mit einem Sternchen (*) markierten Links sind Affiliate-Links. Wenn du über diese Links einkaufst, erhalte ich eine kleine Provision, die dazu beiträgt, diesen Blog zu unterstützen. Der Preis für dich bleibt dabei unverändert. Vielen Dank für deine Unterstützung!
Schaltung - Shelly 1PM Mini Gen3 mit ESP32 Der Shelly Plug S bietet den großen Vorteil, dass keine spezielle Verkabelung erforderlich ist. Du kannst einfach einen Verbraucher direkt über die integrierte Schuko-Steckdose anschließen – ideal für Einsteiger, die schnell und unkompliziert loslegen möchten.
Speichern der Daten von einem Shelly 1PM auf dem ESP32-C3 Mini
Um die Daten des Shelly 1PM auf dem ESP32-C3 speichern zu können, muss zunächst eine Verbindung zum Gerät hergestellt und der JSON-Response über die bereits erwähnte REST-Schnittstelle abgerufen und ausgewertet werden. Die Umsetzung ist sowohl mit der Arduino IDE als auch mit MicroPython möglich. Da das Dateihandling in MicroPython deutlich einfacher und übersichtlicher ist, habe ich mich in diesem Projekt für diese Variante entschieden. Flashen des ESP32-C3 Mini für die Programmierung in MicroPython Bevor der kleine Mikrocontroller mit MicroPython programmiert werden kann, muss er zunächst mit der passenden Firmware geflasht werden. Das funktioniert entweder ganz bequem über Thonny, eine leicht bedienbare Entwicklungsumgebung, oder mithilfe des esptool von Espressif Systems über die Kommandozeile. Im folgenden Video zeige ich dir beide Varianten Schritt für Schritt. https://youtu.be/SB7E3tSF2HM Speichern von Daten in einer JSON-Datei auf dem Mikrocontroller ESP32-C3 Der ESP32-C3 verfügt über 4 MB internen Flash-Speicher, der sich vielseitig nutzen lässt – etwa für das Speichern von Bildern, Textdateien oder kleinen Datensätzen. In diesem Projekt verwenden wir den Speicher, um eine JSON-Datei anzulegen und dort die erfassten Messdaten strukturiert abzulegen. Im ersten Schritt erstellen wir die JSON-Datei auf dem Mikrocontroller und definieren eine Grundstruktur, die später fortlaufend mit neuen Werten ergänzt wird. { "10.03.2025": { "1741590664": { "voltage": 230, "ampere": 12, "apower": 23, "current": 1 }, "1741590685": { "voltage": 225, "ampere": 11, "apower": 24, "current": 2 } } } JSON vs. Dictionary in Python MicroPython ist eine speziell für Mikrocontroller optimierte Variante der Programmiersprache Python. Viele bekannte Sprachmuster und Konzepte aus Python lassen sich daher direkt wiederverwenden – das macht die Programmierung besonders einfach und effizient. Da JSON-Datenstrukturen in Python direkt als Dictionary dargestellt werden können, ist der Umgang mit empfangenen oder zu speichernden Daten besonders komfortabel. So lassen sich JSON-Dateien ganz leicht einlesen, bearbeiten und wieder abspeichern – ideal für den Einsatz auf dem ESP32-C3. import json # Datei einlesen und in ein Dictionary umwandeln def read_json_file(filename): try: with open(filename, 'r') as file: data = json.load(file) return data except FileNotFoundError: print(f"Datei '{filename}' wurde nicht gefunden.") except json.JSONDecodeError: print(f"Fehler beim Einlesen der Datei '{filename}': Ungültiges JSON-Format.") return {} # Dictionary schön ausgeben def print_json_dict(data): for key, value in data.items(): print(f"{key}: {value}") # Hauptprogramm filename = "data.json" json_data = read_json_file(filename) if json_data: print("Inhalt der Datei:") print_json_dict(json_data) Dieser Code liest die Datei data.json, die sich auf dem Mikrocontroller befindet, ein und gibt deren Inhalt in der Shell von Thonny aus.
Speichern von Daten in der Datei data.json Das Speichern der Daten ist ebenso unkompliziert wie das Einlesen: Wir befüllen einfach unser Dictionary mit den gewünschten Schlüssel-Wert-Paaren und schreiben es anschließend zurück in die Datei. So lassen sich Messwerte strukturiert und dauerhaft auf dem Mikrocontroller ablegen. import ujson # Beispiel-Daten (könnten z. B. von einem Sensor stammen) data = { "10.03.2025": { "1741590664": { "voltage": 230, "ampere": 12, "apower": 23, "current": 1 } } } # Datei öffnen und Dictionary als JSON speichern def save_json_file(filename, content): try: with open(filename, 'w') as file: ujson.dump(content, file) print("Daten erfolgreich gespeichert.") except OSError: print("Fehler beim Schreiben der Datei.") # Aufruf der Funktion save_json_file("data.json", data) Um einen neuen Datensatz dem Dictionary hinzuzufügen, prüfen wir zunächst, ob bereits ein Eintrag für das entsprechende Datum existiert. Falls ja, fügen wir lediglich den neuen Zeitstempel samt Messwerten zum bestehenden Datum hinzu. Existiert das Datum noch nicht, wird ein neuer Schlüssel mit dem Datum angelegt und der Datensatz dort eingefügt. # Globales Dictionary (muss vorher definiert sein) data = {} def add_entry(date_str, timestamp_str, values_dict): """ Fügt einen neuen Datensatz zum globalen Dictionary 'data' hinzu. :param date_str: Datum als String, z. B. "10.03.2025" :param timestamp_str: Zeitstempel als String, z. B. "1741590664" :param values_dict: Dictionary mit Messwerten """ global data if date_str not in data: data = {} data = values_dict Nachdem die Daten ergänzt wurden, müssen sie nur noch in das Dictionary zurückgeschrieben werden. Damit ist der erste Schritt geschafft: das Speichern und Lesen von JSON-Daten auf dem ESP32-C3 Super Mini mit MicroPython. fertiger Quellcode zum lesen & speichern von JSON Daten auf dem ESP32-C3 # ----------------------------------------------------------------------------- # Autor: Stefan Draeger # Webseite: https://draeger-it.blog # Beitrag: https://draeger-it.blog/shelly-1pm-esp32-daten-speichern-zeitgesteuert-senden/ # Beschreibung: # Dieses Skript liest eine JSON-Datei vom ESP32-C3 ein, fügt neue Messdaten # hinzu und speichert die aktualisierten Daten zurück in die Datei. # ----------------------------------------------------------------------------- import ujson # Globales Dictionary zur Speicherung der Messdaten data = {} # Liest die JSON-Datei ein und wandelt den Inhalt in ein Dictionary um def read_json_file(filename): global data try: with open(filename, 'r') as file: data = ujson.load(file) except OSError: print(f"Datei '{filename}' wurde nicht gefunden.") except ValueError: print(f"Fehler beim Einlesen der Datei '{filename}': Ungültiges JSON-Format.") # Fügt dem Dictionary einen neuen Eintrag mit Zeitstempel und Messwerten hinzu def add_entry(date_str, timestamp_str, values_dict): """ Fügt einen neuen Datensatz zum globalen Dictionary 'data' hinzu. :param date_str: Datum als String, z. B. "10.03.2025" :param timestamp_str: Zeitstempel als String, z. B. "1741590664" :param values_dict: Dictionary mit Messwerten """ global data # Wenn das Datum noch nicht existiert, wird es angelegt if date_str not in data: data = {} # Zeitstempel samt Werten hinzufügen oder überschreiben data = values_dict # Speichert das Dictionary als JSON-Datei auf dem ESP32 def save_data_to_file(): """ Speichert das globale Dictionary 'data' in die Datei 'data.json'. """ global data try: with open("data.json", "w") as file: ujson.dump(data, file) print("Daten erfolgreich gespeichert.") except OSError: print("Fehler beim Schreiben der Datei.") # Gibt das Dictionary zeilenweise in der Shell aus def print_json_dict(): global data for key, value in data.items(): print(f"{key}: {value}") # ---------------------------------------------------------------------- # Hauptprogramm # ---------------------------------------------------------------------- filename = "data.json" # Vorhandene Datei einlesen read_json_file(filename) # Inhalt der Datei anzeigen print("Inhalt der Datei:") print_json_dict() # Neuen Messwert hinzufügen add_entry("10.03.2025", "1741590722", { "voltage": 228, "ampere": 9, "apower": 21, "current": 3 }) # Datei mit neuem Eintrag speichern save_data_to_file() # Datei erneut laden und aktualisierte Daten anzeigen read_json_file(filename) print("Aktualisierter Inhalt der Datei:") print_json_dict() Auslesen des JSON-Response vom Shelly 1PM oder Shelly Plug S Aufbau einer WiFi-Verbindung am ESP32 Bevor wir die REST-Schnittstelle des Shellys ansprechen können, müssen wir eine Verbindung zum lokalen WLAN aufbauen. Dieses ist mit wenigen Zeilen Code gelöst. Quellcode zum aufbauen einer WiFi-Verbindung am ESP32 # ----------------------------------------------------------------------------- # Autor: Stefan Draeger # Webseite: https://draeger-it.blog # Beitrag: https://draeger-it.blog/shelly-1pm-esp32-daten-speichern-zeitgesteuert-senden/ # # Beschreibung: # Dieses Skript stellt mit dem ESP32-C3 Super Mini eine Verbindung zum lokalen # WLAN her und nutzt eine Status-LED an GPIO 2 als einfache Rückmeldung. # # - Während der Verbindungssuche blinkt die LED. # - Nach erfolgreicher Verbindung leuchtet die LED dauerhaft. # - Bei fehlgeschlagener Verbindung bleibt die LED aus. # ----------------------------------------------------------------------------- import network import time from machine import Pin # WLAN-Zugangsdaten SSID = "abc" PASSWORD = "123" # Status-LED an GPIO 2 (z. B. für den ESP32-C3 Super Mini) led = Pin(2, Pin.OUT) # Funktion zum Aufbau der WLAN-Verbindung def connect_wifi(ssid, password): # WLAN-Modul im Station Mode aktivieren wlan = network.WLAN(network.STA_IF) wlan.active(True) # Verbindung starten, falls noch nicht verbunden if not wlan.isconnected(): print("Verbinde mit WLAN...") wlan.connect(ssid, password) # LED blinkt während der Verbindungssuche timeout = 10 # Sekunden while not wlan.isconnected() and timeout > 0: led.value(1 - led.value()) # LED-Zustand wechseln (manuelles Blinken) time.sleep(0.5) timeout -= 0.5 print(".", end="") # Verbindung erfolgreich if wlan.isconnected(): print("nWLAN verbunden.") print("IP-Adresse:", wlan.ifconfig()) led.value(1) # LED dauerhaft an return True else: # Verbindung fehlgeschlagen print("nVerbindung fehlgeschlagen.") led.value(0) # LED bleibt aus return False # WLAN-Verbindung aufbauen connect_wifi(SSID, PASSWORD) Wenn die WiFi-Verbindung aufgebaut wurde, dann wird auf der Shell die zugewiesene IP-Adresse ausgegeben sowie wird eine LED zum leuchten gebracht.
Ausgabe der IP-Adresse des ESP32-C3 auf der Shelly von Thonny Synchronisieren der RTC vom ESP32 Damit wir später einen gültigen Zeitstempel als Schlüssel für unsere Datenspeicherung verwenden können, müssen wir zunächst die RTC (Echtzeituhr) des ESP32 synchronisieren. Sobald die WLAN-Verbindung steht, lässt sich das über einen NTP-Server ganz einfach erledigen. Quellcode zum synchronisieren der RTC am ESP32-C3 # ----------------------------------------------------------------------------- # Autor: Stefan Draeger # Webseite: https://draeger-it.blog # Beitrag: https://draeger-it.blog/shelly-1pm-esp32-daten-speichern-zeitgesteuert-senden/ # # Beschreibung: # Dieses Skript verbindet den ESP32-C3 mit einem WLAN, synchronisiert die # interne Echtzeituhr (RTC) über einen NTP-Server und gibt das aktuelle Datum # formatiert aus. # # - Während der Verbindungssuche blinkt die LED. # - Nach erfolgreicher Verbindung leuchtet die LED dauerhaft. # - Das Datum wird im Format TT.MM.JJJJ ausgegeben. # ----------------------------------------------------------------------------- import time import network import ntptime from machine import Pin # WLAN-Zugangsdaten SSID = "abc" PASSWORD = "123" # Status-LED an GPIO 2 (z. B. für den ESP32-C3 Super Mini) led = Pin(2, Pin.OUT) # Funktion zum Aufbau der WLAN-Verbindung def connect_wifi(ssid, password): """ Baut eine Verbindung zum WLAN auf und gibt den Status zurück. Während der Verbindungssuche blinkt die LED, nach erfolgreicher Verbindung leuchtet sie dauerhaft. :param ssid: Name des WLAN-Netzwerks :param password: WLAN-Passwort :return: True, wenn erfolgreich verbunden, sonst False """ wlan = network.WLAN(network.STA_IF) # WLAN-Modul im Station Mode wlan.active(True) # WLAN aktivieren if not wlan.isconnected(): print("Verbinde mit WLAN...") wlan.connect(ssid, password) # LED blinkt während der Verbindungssuche timeout = 10 # Maximale Wartezeit in Sekunden while not wlan.isconnected() and timeout > 0: led.value(1 - led.value()) # LED-Zustand wechseln (manuelles Blinken) time.sleep(0.5) timeout -= 0.5 print(".", end="") if wlan.isconnected(): print("nWLAN verbunden.") print("IP-Adresse:", wlan.ifconfig()) led.value(1) # LED dauerhaft an = erfolgreich verbunden return True else: print("nVerbindung fehlgeschlagen.") led.value(0) # LED bleibt aus return False # Funktion zur Umwandlung des Unix-Timestamps in ein formatiertes Datum def formatDate(): """ Wandelt den aktuellen Unix-Zeitstempel in ein lesbares Datum um. :return: String im Format "TT.MM.JJJJ" """ timestamp = time.localtime() # Aktuelle Zeit abrufen return "{tag:02d}.{monat:02d}.{jahr:4d}".format(tag=timestamp, monat=timestamp, jahr=timestamp) # WLAN-Verbindung herstellen isWiFiConnected = connect_wifi(SSID, PASSWORD) # Falls WLAN verbunden, Zeit vom NTP-Server holen if isWiFiConnected: try: ntptime.settime() # Zeit vom NTP-Server synchronisieren print("Zeit erfolgreich synchronisiert.") print("Aktuelles Datum:", formatDate()) except: print("Fehler bei der Zeitsynchronisation.") else: print("Keine WLAN-Verbindung – NTP-Sync nicht möglich.") Der Code stellt zunächst eine WLAN-Verbindung her und synchronisiert anschließend die interne RTC des ESP32 über einen NTP-Server. Sobald die Synchronisation abgeschlossen ist, wird der aktuelle Zeitstempel im Format eines Tupels (time.localtime()) ausgelesen. Read the full article















