Dumme Stromzähler smart machen

Dumme Stromzähler smart machen

Auch einen alten Stromzähler kann man mit ein bisschen Bastelarbeit und Bauteilen für 10-15 EUR zumindest smart genug machen, dass er über WLAN den aktuellen Stromverbrauch meldet, aus dem man dann z.B. bunte Grafiken malen kann.

Der Bedarf

Energie wird immer teurer, dabei war es eigentlich bisher schon dringend fällig, unseren Strombedarf zu optimieren. In meinem Kopf spukte auch immer die Idee, den aktuellen Stromverbrauch oben in der Wohnung so anzuzeigen, so dass auch die Kinder verstehen, wann wir viel und wann wir wenig Strom verbrauchen.

Eine Weile hatte ich ja noch gehofft, endlich einen smarten Stromzähler zu bekommen, auf dessen Daten ich dann Zugriff hätte. Vor ein paar Wochen war dann der Ableser der SWM da und meinte auf meine Frage nach einem smarten Zähler nur, er hätte noch sechs Jahre bis zur Rente und er würde die wohl nicht mehr sehen.

Bleibt also nur, selbst den aktuellen Stromverbrauch herauszufinden. Manche Stromzähler haben ja immerhin eine LED, mit der sie die Messwerte digital signalisieren, so etwas kann ja nicht all zu schwer zu dekodieren sein. Unser Zähler leider nicht, der ist dumm wie Brot, hat einen Gesamtstand und ein weisses Rad mit einem roten Strich, das sich je nach aktuellem Stromverbrauch unterschiedlich schnell dreht. Wikipedia hat mir erklärt, dass man diese Art der Konstruktion Ferrariszähler nennt, sehr spannend nachzulesen, wie das funktioniert.

Ferraris-Zähler im Keller. Noch dumm.

Auf dem Zähler steht, dass 75 Umdrehungen für eine Kilowattstunde erfolgen, d.h. eine Umdrehung ist 1/75 kWh “wert”. Wenn ich dafür die Zeit messe, kann ich also bei jeder Umdrehung den durchschnittlichen Stromverbrauch über die letzte Umdrehung ermitteln. Ob mir das hilft, hängt jetzt also vor allem von der zeitlichen Auflösung ab, also der Frage, wie oft bei unserem typischen Stromverbrauch so ein Messwert ermittelt wird.

Die Formelsammlung sagt, die Leistung ist die Arbeit pro Zeit, also $P = \frac{W}{t}$. Die Zeit $t$ wollen wir messen. Laut Aufdruck auf dem Zähler ist die Arbeit pro Umdrehung $W = \frac{1kWh}{75}$, wir wollen aber als Einheit lieber Watt statt Kilowatt und Sekunden statt Stunden benutzen, somit gilt

$$P = \frac{1kWh}{t\cdot 75} = \frac{60 \cdot 60 s \cdot 1000 W}{t\cdot 75} = \frac{48\,000 Ws}{t}$$

Nachdem ich mit der Stoppuhr ein paar Stichproben gemessen habe – immer knapp unter zwei Minuten pro Umdrehung – kann ich also ausrechnen, dass zwei gemessene Minuten dann 400 Watt gemessener Leistung entsprechen, das scheint mir eine plausible Größenordnung zu sein.

$$ P = \frac{48\,000 Ws}{120s} = 400W$$

Allerdings heißt das eben auch, dass ich bei einem Verbrauch von 400 W nur alle 2 Minuten einen Messwert bekomme, bei 800 W minütlich, bei 100W aber nur noch alle 8 Minuten. Das ist nicht hochakkurat, letztlich aber immer noch eine hilfreiche Information.

Die Hardware

Programmierbare Microcontroller mit geringem Leistungsbedarf und WLAN gibt es selbst für Privatgebrauch heute schon für 3-4 EUR, beispielsweise den ESP8266. Praktischerweise hatte ich tatsächlich noch ein paar NodeMCU Amica Module herumliegen, die einen ESP8266 mit einem USB-Anschluss für Stromversorgung und Programmierung und einigen herausgeführten Anschluß-Pins ergänzen. Aber auch z.B. eine kleinere D1 Mini NodeMCU wäre völlig ausreichend.

Ich benutze eine Infrarot-LED und einen Lichtsensor für die Erkennung, sobald die dunklere rote Stelle vor den Sensor kommt. Dazu benötige ich eine Kalibrierung, um das Niveau von “weisse Stelle am Rad” und “rote Stelle am Rad” sinnvoll unterscheiden zu können. um Glück gibt es bereits eine fertige Baugruppe TCRT5000, die eine IR-Diode und einen IR-Empfänger beinhaltet. Noch praktischer gibt es das ganze fertig auf einem Breakout-Board auf dem bereits ein Trimmpoti und eine Anzeige-LED sind, mit denen man den Sensor vor Ort mit einem kleinen Schraubenzieher justieren kann.

Die Gelegenheit kann ich gleich nutzen, um noch einen Temperatur- und Feuchtefühler mit einzubauen, dann kann ich diese Messung gleich mit erfassen. Eigentlich wollte ich dafür einen hochpräzisen Bosch BME280 verwenden, den man vor 2 Jahren noch für 3-4 EUR kaufen konnte. Dank der derzeitigen Chipkrise kostet der aber heute saftige 19 EUR, so wichtig war mir die Temperatur und Feuchtigkeit auch nicht. Zum Glück gibt es aber auch noch den etwas ungenaueren, aber günstigen DHT11, der hier vollkommen ausreicht.

Der Testaufbau

Der Testaufbau war erstaunlich einfach. Im Grunde musste ich nur Masse und 3V der beiden Sensoren jeweils mit der NodeMCU verbinden und für jeden Sensor einen Daten-Pin. Im Grund reicht für dieses Projekt also selbst die einfachste ESP-Variante, denn die zwei digitalen Pins hat selbst die kleinste.

Für den Sensor habe ich ein Kabel mit Dupont-Steckverbindern benutzt, die ich direkt an den Sensor stecken kann. So brauche ich nur den Sensor selbst im Sicherungskasten und kann die Steuereinheit außerhalb des Metallkastens platzieren, wo sie eine bessere WLAN-Verbindung hat. Diese Dupont-Stecker für das 4-polige Verbindungskabel zu krimpen, ist eine Sisyphusarbeit, und ich verfluche jedes Mal meine zwei linken Hände.

Testaufbau am Breadboard. Das Flachbandkabel (doppel-linkshändig selbstgekrimpt) geht zum Sensor.

Die Controller-Software

Jetzt muss die Software für den Controller geschrieben werden. Zum Glück ist man für den ESP8266 nicht mehr auf die spartanische Arduino-IDE oder ähnliches angewiesen, sondern kann mit dem PlattformIO-Plugin komfortabel in VisualStudio Code mit C seinen Code erstellen und verwalten und dann direkt mit dem GNU-Compiler übersetzen.

Die Umsetzung war verblüffend simpel: Ich habe mir den ganzen Aufwand für ein captive WLAN gespart und meine WLAN-Zugangsdaten direkt in den Code kompiliert (nicht so schön, aber spart viel Aufwand). Dafür habe ich mir andererseits das System für Over-The-Air-Updates gegönnt, damit ich für Updates nicht jedes Mal mit Laptop und USB-Kabel in den Keller muss. Sobald der ESP eine WLAN-Verbindung hat, verbinde er sich mit dem eingetragenen MQTT-Server.

Jetzt wird nur noch der Sensor regelmäßig gescannt. Sobald der Sensor den Beginn des roten Bereichs meldet, wird anhand der Zeit seit dem letzten Mal (in Millisekunden) der derzeitige Stromverbrauch errechnet. Vom DHT11 lesen wir noch Temperatur und Luftfeuchtigkeit aus und schicken dann alles zusammen als JSON-Objekt an den MQTT-Server.

Da der DHT11 es gar nicht mag, wenn er zu häufig abgefragt wird, cachen wir beim Auslesen das Ergebnis noch für eine eine Weile, denn beide Werte interessieren mich ohnehin nicht mit sonderlich hoher zeitlicher Auflösung.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <ArduinoOTA.h>

#include "config.h"

#ifndef FERRARIS_CONFIG
#define FERRARIS_CONFIG
// CONFIGURATION HERE vvvvvv
#define CLIENT_ID "Ferraris-Tracker" // MQTT Client-ID
#define LED_BUILTIN_NODE 16          // LED for signalling
#define FERRARIS_PIN 13              // IO pin for
#define FERRARIS_DIVIDER 75          // Number of rotations for a full kWh
#define DHT11_PIN 12                 // IO pin for temp./hum. sensor
#define DHT_WAIT 60000               // Minimum delay between temp queries
#define MQTT_PORT 1883               // MQTT port number

const char *SSID = "wifi network name";              // WLAN name
const char *PSK = "wifi network password";           // WLAN password
const char *MQTT_BROKER = "ip address";              // MQTT broker address
const char *MQTT_TOPIC = "/home/sensors/power/main"; // MQTT topic to post to
// CONFIGURATION HERE ^^^^^^^
#endif

#define SEND_ON HIGH    // Send on HIGH oder LOW
#define MSG_BUF_LEN 100 // JSON preparation
// <millis per hour> * <W instead of kW> / <rounds per kW>:
#define ROTATION_WORTH_Wms ((float)60 * 60 * 1000 * 1000 / FERRARIS_DIVIDER)

WiFiClient espClient;
PubSubClient pubSub(espClient);
DHT dht(DHT11_PIN, DHT11);

int last_val = -1;
long last_millis = -1;

void setup_wifi() {
  delay(10);
  Serial.print("\nConnecting to ");
  Serial.println(SSID);

  WiFi.begin(SSID, PSK);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  digitalWrite(LED_BUILTIN, LOW); // Signal WiFi on 1st builtin LED
  Serial.println("  WiFi connected");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  ArduinoOTA.begin();
}

void reconnectMqtt() {
  while (!pubSub.connected()) {
    Serial.print("(Re)connecting MQTT...");
    if (!pubSub.connect(CLIENT_ID)) {
      Serial.print("failed, rc=");
      Serial.print(pubSub.state());
      Serial.println(" retrying in 5 seconds");
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(9600);
  setup_wifi();
  pubSub.setServer(MQTT_BROKER, MQTT_PORT);

  // Set IO modes
  pinMode(FERRARIS_PIN, INPUT);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(LED_BUILTIN_NODE, OUTPUT);

  // DHT11
  pinMode(DHT11_PIN, INPUT);
  dht.begin();
}

void readEnvDht(float *temp, float *hum) {
  static long lastRead = -1;
  static float lastTemp, lastHum;
  long now = millis();

  if (lastRead == -1 || now - lastRead > DHT_WAIT) {
    *temp = dht.readTemperature();
    *hum = dht.readHumidity();
    lastRead = now;
    lastTemp = *temp;
    lastHum = *hum;
  } else {
    *temp = lastTemp;
    *hum = lastHum;
  }
}

void loop() {
  long now = millis();
  int val = digitalRead(FERRARIS_PIN);  // first read the data for precision
  digitalWrite(LED_BUILTIN_NODE, !val); // Feedback on the MCU LED

  // MQTT chores
  if (!pubSub.connected()) {
    reconnectMqtt();
  }
  pubSub.loop();

  if (val == SEND_ON && last_val == !SEND_ON) {
    if (last_millis != -1) {
      char msg[MSG_BUF_LEN];
      float temp, hum;
      long diff_millis = now - last_millis;
      float watts = ROTATION_WORTH_Wms / diff_millis;
      readEnvDht(&temp, &hum);
      snprintf(msg, MSG_BUF_LEN,
               "{\"temp\": %f, \"hum\": %f, \"millis\": %ld, \"W\": %f}", temp,
               hum, diff_millis, watts);
      pubSub.publish(MQTT_TOPIC, msg);
      Serial.println(msg);
    }
    last_val = val;
    last_millis = now;
  } else if (val == !SEND_ON) {
    last_val = val;
  }

  // Check for over the air update request and (if present) flash it
  ArduinoOTA.handle();
  delay(25);
}

Fehlersuche

Zu meinem großen Erstaunen haben tatsächlich die meisten Aufgaben auf Anhieb genau so geklappt, wie sie sollten (das passiert selten genug). Einzig an einem Problem habe ich mir recht lange die Zähne ausgebissen:

Wenn beim Booten kein USB angeschlossen war und eine Konsole geöffnet, dann blockiert der Controller bei der ersten Konsolenausgabe. Sobald man dann USB verbindet und ein Terminal öffnet, geht alles weiter. So etwas kannte ich bisher nur von seriellen Terminals aus den 70er Jahren mit falsch eingestellter Flusskontrolle. In den Dokumentationen und Beispielen ist die Console dagegen überall als nicht-blockierend verwendet (nur so macht das auch bei einer Debug-Ausgabe Sinn). Per Zufall fand ich den Workaround, dass das Umstellen der Flussrate von 115kBps auf 9.6kBps das Problem behoben hat.

Gehäuse und Einbau

Jetzt braucht es noch Gehäuse, um die Controller, Sensoren und Kabel sicher und hübsch zu verstauen. Mit Fusion360 ist das zum Glück recht schnell für den 3D-Drucker erstellt.

Das Gehäuse für den Sensor besteht aus 2 Teilen und lässt sich ohne Schrauben oder Kleber zusammenklicken. Dabei kommt die Steckerleiste genau richtig zu liegen, um sie später von außen anzuschließen. Der Deckel des Sensors ist das einzige Teil für das man im Druck Supports einschalten muss, alle anderen Teile gehen einfach so.

Gehäuse für den TRS5000 Sensor in Autodesk Fusion 360

Das Gehäuse für die Steuereinheit ist ebenfalls ohne Schrauben und Kleber zusammenklickbar. Man könnte einen Tropfen Heißkleber verwenden, um die Pinleiste für das Kabel gegen verschieben zu sichern, aber das hat bei mir alles auch so gepasst. Den DHT11-Sensor kann man einfach auf den Boden klicksen. Das Gehäuse ist sowohl für eine Amica-NodeMCU mit als auch ohne Stiftleiste geeignet.

In meinem Fall habe ich die Verdrahtung mit Litzen direkt an die Pins gelötet und alle nicht benötigten Pins (also die meisten) für bessere Luftzirkulation abgeknipst. Oben ist Platz für den Micro-USB-Stecker, unten für den vierpoligen Dupont-Stecker.

Gehäuse für die NodeMCU und den Temperatursensor in Autodesk Fusion 360

Die Dateien zum selbst Drucken habe ich auf Printables (bevorzugt) und auf Thingiverse hochgeladen.

Montage

[Leider habe ich erst nach der Montage an Fotos gedacht, daher gibt es keine vom Zusammenbau]. Nachdem es sich um eine Mietwohnung handelt, habe ich den Sensor nur mit zwei Stückchen Kreppband am Zähler befestigt. Mit ein bisschen Probieren findet man eine gute Einstellung am Poti, bei der die LED gerade eben noch an ist, wenn der Sensor auf den weißen Teil des Rads schaut und zuverlässig ausgeht, sobald der rote Strich vorbeikommt. Das Kabel habe ich einfach durch den Spalt der Türe durchgeführt und am Controller angesteckt, den ich einfach mit einem Posterstrip außen an den Kasten geklebt habe.

Datenempfang

Bisher hatte ich noch keinen MQTT-Server benötigt. Seit Anno dazumal läuft hier ein Raspberry Pi 3 mit RaspberyMatic, der hier die Automatisierungen koordiniert (HomeMatic, Node Red). Auf diesem Konstrukt kann man zum Glück ganz einfach einen Eclipse Mosquitto als Raspberrymatic Zusatzpaket installieren, der trotz “Eclipse” im Namen erstaunlich leichtgewichtig ist ;-).

Sobald der Server installiert und neugestartet war, liess sich endlich testen, ob wirklich Daten am System ankommen. Für einen schnellen Test braucht es in Node-Red nur einen MQTT-Subscriber-Knoten und einen Debug-Output:

Node Red-Flow zur Anzeige eintreffender MQTT-Daten.

Wenn, einige Sekunden nach dem Deployment der neuen Konfiguration, das Symbol unter dem Knoten grün wird, ist schon einmal die Verbindung zum MQTT-Server hergestellt. Wenn alles richtig konfiguriert ist, dann taucht jetzt in der Debug Pane von Zeit zu Zeit die Datenpakete der Messungen auf:

Node Red-Ausgabe eintreffender MQTT-Daten.

Datenspeicherung

Die ursprüngliche Idee, noch eine Visualisierung der Daten in der Wohnung zu bauen, steht natürlich noch aus, das kommt als nächstes. Zunächst wäre es aber natürlich schade um die schönen Daten, wenn wir diese nicht wenigstens aufheben würden. Was gibt es einfacheres für die Aufzeichnung solcher IoT-Messdaten, als InfluxDB?

Unsere Messdaten kommen praktischerweise bereits als JSON-Object, wir müssen also den MQTT-Knoten noch so konfigurieren, dass er das JSON-Objekt auswertet:

Konfiguration des MQTT-In Knotens in Node Red.

Den Debug-Knoten am Ausgang heben wir uns noch auf für schlechte Zeiten, aber deaktivieren ihn erst einmal mit dem Knopf rechts. Zusätzlich fügen wir einen InfluxDB-Knoten ein, um unser Datentupel in die Datenbank (die hier “Ferraris” heisst) zu schreiben:

Node Red-Flow mit Datenspeicherung in der InfluxDB.

Visualisierung

Wir könnten jetzt die Messwerte direkt in Tabellenform aus der InfluxDB abrufen, aber ein grafischer Überblick ist in diesem Fall viel wichtiger. Für diese Darstellung habe ich mir in Grafana ein neues Dashboard erstellt und dort eine Grafik eingefügt. Die Konfiguration ist straight-forward: Datenbank aus Dropdown wählen, dann den Namen der Messreihe (in meinem Fall “ferraris”), Aggregatfunktion für hohe Zoomlevel, hier habe ich den Mittelwert (“mean”) genommen, fertig.

Konfiguration des Diagramms in Grafana.

Nach einem ersten Blick auf das Panel finde ich, dass die Messungen alle recht plausibel aussehen. Man sieht vor allem recht gut, dass die Messintervalle bei hohen Werten kürzer sind als bei niedrigen.

Erzeugtes Diagramm in Grafana.

Gespannt war ich jetzt vor allem auf die Messung über Nacht, wieviel Verbrauch wir z.B. morgens um 3 Uhr haben, mit Homeserver, SmartHome und diversen Spielereien. Am nächsten morgen war die Enttäuschung groß: Gegen 23 Uhr waren die Messwerte schlagartig vollkommen unplausibel, alle so im Bereich zwischen einem halben und einem Megawatt. Auch wenn mein FreeBSD-Server nachts einige Batch-Aufgaben abarbeitet und Backups ausdünnt und dafür sicher etwas mehr Strom braucht, war das letztlich aber extrem unwahrscheinlich. Des Rätsels Lösung: Der Controller, der temporär nur mit einem Streifen Krepp angeklebt war, hat sich gelöst, beim Absturz kurz am Kabel gezogen und dabei den Sensor verschoben. Ich vermute mal, er hat dann auf die kleinen Rillen am Drehrad reagiert und so diese sagenhaften Messergebnisse generiert.

Fazit

Alles in allem war das ganze Projekt unerwartet wenig Arbeit und ging (ebenfalls unerwartet) ohne Komplikationen von der Hand. Ich werde jetzt erst einmal die Daten ein bisschen beobachten und mir als nächsten Schritt Gedanken über die Anzeige für die Kinder machen.

Kommentare

Sehr schön erläutert. Gute Idee, den Stromverbrauch so zu visualisieren. Man erkennt dann selber, wieviel Strom man verbraucht und (ich zumindest) versucht, die Zahl nach unten zu korrigieren. Hierbei gibt es viele Optionen, den Stromverbrauch einzudämmen. Gutes Projekt, viel Erfolg beim Ausbau! Gruß Gregor
© 2023 Tobias Henöckl