Wie der aufmerksame Leser weiß, haben sich in den letzten Monaten einige Dinge in meinem Leben verändert. Daher komme ich lange nicht mehr so viel zum basteln wie in der Vergangenheit, aber die aktuelle Energiekriese hat mich zu folgendem Projekt inspiriert.
Ich bin mal durch das Haus gegangen und habe Steckernetzteile gezählt, bzw. diverse Microcontroller und Raspberrys und war erschrocken, wie viele kleine Verbraucher sich so angesammelt haben – die Meisten davon habe ich ja hier dokumentiert. 🙂
Wild entschlossen habe ich angefangen diese zu hinterfragen und ggf. auch einfach als “unnötig” zu deklarieren. So sind z.B. jetzt alle Spielekonsolen über eine schaltbare Steckdose deaktiviert und viele Dinge die ohnehin wenig oder garnicht benutzt wurden sind entsorgt oder verkauft.
Auch mein Heizungs-Raspberry, der tatsächlich nichts anderes mehr tut als auf das S0 Signal von einem Stromzähler zu hören und 1-Wire Temperatursensoren auszulesen, kam mir doch etwas oversized vor und mit diesem Projekt löse ich ihn durch einen ESP8266 ab. Natürlich bleibt ein Steckernetzteil erhalten, die Stromaufnahme ist aber trotzdem geringer und ich konnte so auch noch die Funktionalität erweitern.
Die Stromzähler
Denn ich habe in den letzten Monaten die Schlafzimmer und das Wohnzimmer mit Klimageräten ausgestattet – das war nicht nur diesen Sommer sehr angenehm. Um die Kosten bzw. den Verbrauch im Blick zu halten habe ich nicht nur die Heizung/Wärmpumpe mit einem Zwischenzähler ausgestattet, sondern auch die beiden Klimeageräte. Für die Wärmepumpe ist schon seit langem ein DRT428DC (3-phasig) im Einsatz und für die Klimaanlagen haben ich mir zwei Eltako WSZ15D-32A (1-phasig) besorgt.
Die Schaltung auf dem Steckbrett
Und los ging die Bastelei! (endlich!) Die Schaltung war auf dem Breadboard schnell zusammen gesteckt. Tatsächlich bin ich bei der Hardware nur über den S0 gestolpert. Bei den Zählern haben die 3.3V des ESP leider nicht ausgereicht für eine zuverlässige Erfassung. Ein Blick ins Datenblatt und es war klar das es mindestens 5V sein müssen. Da mein Steckernetzteil 5V hat, habe ich also für den S0 direkt diese Spannung genommen und mit einem Spannungsteiler die Spannung für die GPIOs angepasst.
Ich habe mir auch die Mühe gemacht mal einen Schaltplan zu zeichnen:
– Ein LM1117-3.3 macht aus den 5V Eingangsspannung 3.3V
– In der Mitte sitzt der ESP8266
– Oben links sieht man den Spannungsteiler mit Pull-Down Wiederständen
– Unten rechts ist der 1-Wire Bus (parasitär) für die Temperatursensoren.
Was man auch sieht sind zwei RJ45 Buchsen. Ich habe nämlich sowohl für die Anbindung der S0-Anschlüsse als auch für die Temperatursensoren zwei Netzwerkkabel zerschnitten um eine zuverlässige Steckverbindung zu bekommen. Da ich die Schaltung dann auf einer Lochrasterplatine unterbringen wollte, habe ich mir auch noch RJ45 Breakout-Boards besorgt.
Die Platine sieht in einem kleine 3D-Druck-Gehäuse dann so aus. (Natürlich gibts zu dem Gehäuse auch noch einen Deckel…
Der Arduino Quellcode
Die Software besteht grundsätzlich aus zwei Teilen. Einmal dem Arduino Sketch für den ESP8266 und einem PHP-Script als Gegenstück, welches auf meinem NAS läuft und die Werte in die Datenbank schreibt.
Der ESP macht folgendes:
1. Per Interrupt auf steigende Flanken auf den 3 GPIOs hören um dann einen Zähler hochzuzählen
2. Nach 60 Sekunden die aktuellen Temperaturen auszulesen
3. Und das alles als JSON an das PHP-Script zu senden
Da mein NAS irgendwie träge ist und der erste Versuch des Datensendens mit einem Fehler abbricht, wiederhole ich als Workaround das Senden einfach so lange, bis es geklappt hat.
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <Ticker.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#define INT_PIN1 13
#define INT_PIN2 12
#define INT_PIN3 14
#define temperatureBus 4
#define addressTempVorlauf "28CF8D2905000054"
#define addressTempRuecklauf "281A8C29050000B6"
#define addressTempGruen "28E92729050000DE"
#define addressTempBraun "28F78B2905000084"
Ticker timer;
OneWire oneWire(temperatureBus);
DallasTemperature sensors(&oneWire);
float powerEG, powerOG, powerHeizung = 0;
volatile unsigned long intCounter[3];
int numberTempSensors;
float temperatureVorlauf = -1;
float temperatureRuecklauf = -1;
DeviceAddress tempDeviceAddress;
bool dataReadyToSend = false;
String httpRequestData = "";
WiFiClient client;
HTTPClient http;
bool isReady = false;
void ICACHE_RAM_ATTR isr1()
{
intCounter[0]++;
}
void ICACHE_RAM_ATTR isr2()
{
intCounter[1]++;
}
void ICACHE_RAM_ATTR isr3()
{
intCounter[2]++;
}
String getDeviceAddressAsHex() {
String result = "";
char hexVal[2];
for (uint8_t i = 0; i < 8; i++) {
hexVal[0] = 0x0;
hexVal[1] = 0x0;
sprintf(hexVal, "%02X\0" , tempDeviceAddress[i]);
result += hexVal;
}
return result;
}
bool sendDataToServer()
{
if (WiFi.status() == WL_CONNECTED) {
if (http.begin(client, "http://<ip-vom-nas>/scripts/powermeasurement.php")) {
http.addHeader("Content-Type", "application/json");
http.setTimeout(50);
httpRequestData = "{\"powerog\":" + String(powerOG) + ",";
httpRequestData += "\"powereg\":" + String(powerEG) + ",";
httpRequestData += "\"powerheizung\":" + String(powerHeizung) + ",";
httpRequestData += "\"tempvorlauf\":" + String(temperatureVorlauf) + ",";
httpRequestData += "\"tempruecklauf\":" + String(temperatureRuecklauf) + "}";
Serial.println("Sending request " + httpRequestData);
Serial.flush();
int httpResponseCode = http.POST(httpRequestData);
http.end();
if (httpResponseCode != 200)
{
return false;
}
return true;
} else {
// Serial.println("UNABLE TO CONNECT");
return false;
}
} else {
// Serial.println("WiFi Disconnected");
return false;
}
}
void readTemperatures()
{
sensors.requestTemperatures();
delay(1000);
temperatureVorlauf = -1;
temperatureRuecklauf = -1;
for (int i = 0; i < numberTempSensors; i++) {
if (sensors.getAddress(tempDeviceAddress, i)) {
float temp = sensors.getTempC(tempDeviceAddress);
String deviceAddress = getDeviceAddressAsHex();
if (deviceAddress == addressTempVorlauf) {
temperatureVorlauf = temp;
} else if (deviceAddress == addressTempRuecklauf) {
temperatureRuecklauf = temp;
}
}
}
}
void onTimerTick()
{
noInterrupts();
powerOG = intCounter[0]; powerOG = powerOG * 60 / 2000;
powerEG = intCounter[1]; powerEG = powerEG * 60 / 2000;
powerHeizung = intCounter[2]; powerHeizung = powerHeizung * 60 / 1000;
intCounter[0] = 0;
intCounter[1] = 0;
intCounter[2] = 0;
interrupts();
dataReadyToSend = true;
}
void setup() {
Serial.begin(115200);
Serial.println("Init ports");
pinMode(INT_PIN1, INPUT);
pinMode(INT_PIN2, INPUT);
pinMode(INT_PIN3, INPUT);
Serial.println("Starting wifi");
IPAddress ip(192, 168, X, Y);
IPAddress gateway(192, 168, X, Z);
IPAddress subnet(255, 255, 255, 0);
WiFi.config(ip, gateway, subnet);
WiFi.begin("<ssid>", "<password>");
WiFi.hostname("esp-stromzaehler");
Serial.println("Warte auf Verbindung");
while (WiFi.status() != WL_CONNECTED)
{
Serial.print(".");
delay(500);
}
Serial.print("Mit Wlan verbunden. IP Adresse: ");
Serial.println(WiFi.localIP());
Serial.flush();
Serial.println("Starting sensors");
sensors.begin();
numberTempSensors = sensors.getDeviceCount();
Serial.print(numberTempSensors, DEC);
Serial.println(" Devices found");
for (int i = 0; i < numberTempSensors; i++) {
if (sensors.getAddress(tempDeviceAddress, i)) {
Serial.print("Found device ");
Serial.print(i, DEC);
Serial.print(" at Address ");
Serial.print(getDeviceAddressAsHex());
Serial.println();
} else {
Serial.print("Found ghost at ");
Serial.print(i, DEC);
Serial.println();
}
}
Serial.println("Starting timer");
timer.attach(60, onTimerTick);
Serial.println("Setting interrupts");
attachInterrupt(digitalPinToInterrupt(INT_PIN1), isr1, RISING);
attachInterrupt(digitalPinToInterrupt(INT_PIN2), isr2, RISING);
attachInterrupt(digitalPinToInterrupt(INT_PIN3), isr3, RISING);
Serial.println("Setup done...");
isReady = true;
}
void loop() {
if (isReady == true)
{
readTemperatures();
if (dataReadyToSend == true)
{
dataReadyToSend = false;
while (sendDataToServer() == false)
{
delay(250);
}
Serial.println("sending data ok");
}
}
}
Der PHP Quellcode
Und hier noch das bischen PHP (als Quick&Dirty Script), welches die per JSON gesendeten Werte entgegen nimmt, in die MySQL-Datenbank (ebenfalls auf dem NAS) schreibt und dann noch an den Homeserver schickt.
<?php
/*** INIT ***/
date_default_timezone_set ("Europe/Berlin");
$mysql = mysqli_connect("192.168.X.Y", "<benutzer>", "<passwort>");
if (!$mysql) {
die('Verbindung schlug fehl: ' . mysqli_error());
}
mysqli_select_db ($mysql, "heizung");
/*** Read Data ***/
$postData = file_get_contents('php://input');
if (strlen($postData) < 10) {
return;
}
$data = json_decode($postData);
if (!is_object($data)) {
return;
}
$powerOG = $data->powerog;
$powerEG = $data->powereg;
$powerHeizung = $data->powerheizung;
$tempVorlauf = $data->tempvorlauf;
$tempRuecklauf = $data->tempruecklauf;
/*** Temperaturen ***/
// Daten in Datenbank schreiben
$query = "INSERT INTO temperaturen SET datum = NOW(), `fbh-vorlauf` = '" . (floatval($tempVorlauf)) . "', `fbh-ruecklauf` = '" . (floatval($tempRuecklauf)) . "' ";
mysqli_query($mysql, $query);
# Alte Datensaetze loeschen
$query = "DELETE FROM temperaturen WHERE DATE_SUB(now(), INTERVAL 365 DAY) >= datum";
mysqli_query($mysql, $query);
/*** Energie ***/
// neue daten einfügen
$query = "INSERT INTO stromverbrauch SET datum = NOW(), verbrauch = '" . (floatval($powerHeizung)) . "', verbrauch_eg = '" . (floatval($powerEG)) . "', verbrauch_og = '" . (floatval($powerOG)) . "' ";
mysqli_query($mysql, $query);
// alte daten loeschen
$query = "DELETE FROM stromverbrauch WHERE DATE_SUB(now(), INTERVAL 365 DAY) >= datum";
mysqli_query($mysql, $query);
/*** HOMESERVER DATEN ***/
$data = "";
$data .= "WP-Stromverbrauch=\"".(floatval($powerHeizung))."\"\r\n";
$data .= "WP-HK-Vorlauf=\"".(floatval($tempVorlauf))."\"\r\n";
$data .= "WP-HK-Ruecklauf=\"".(floatval($tempRuecklauf))."\"\r\n";
if (($socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === false) {
echo "socket_create() fehlgeschlagen: Grund: " . socket_strerror(socket_last_error()) . "\n";
}
$result = socket_connect($socket, "192.168.X.Z", 11108);
if ($result === false) {
echo "socket_connect() fehlgeschlagen.\nGrund: ($result) " . socket_strerror(socket_last_error($socket)) . "\n";
}
socket_write($socket, $data, strlen($data));
socket_close($socket);
echo "1";
?>
Damit werden jetzt die gemessenen Werte vom ESP in die Datenbank geschrieben und gleichzeitig auch nochmal an den Homeserver geschickt. Dieser nutzt die Infos “ist die Heizung an?” oder “ist die Klimaanlage an?” an verschiedenen Stellen in der programmierten Logik im sinnvolle Entscheidungen zu treffen – und der alte Raspberry ist jetzt ausgemustert.