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

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.

S0 Stromzähler Steckbrett
S0 Stromzähler Schaltung

Die Platine sieht in einem kleine 3D-Druck-Gehäuse dann so aus. (Natürlich gibts zu dem Gehäuse auch noch einen Deckel…

S0 Stromzähler mit 1-Wire Temperaturmessung auf Lochrasterplatine

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.

Jetzt soll das hier aber kein 3D Drucker test werden. Davon gibt es schon mehr als genug und ich kann diese einfach nur bestätigen: Druck starten -> wiederkommen -> perfekt 😉

Jetzt sollte mein neuer Drucker aber auch ein neues zu Hause bekommen und nicht einfach nur auf der Werkbank stehen. Gerade der Filamentwechsler soll wohl eine Menge Platz brauchen (noch nicht zusammen gebaut). Und wenn ich ABS oder ASA Drucke, muss ich da auch noch eine Kiste drum rum bauen können und die Abluft irgendwie nach draußen bekommen.

Darum habe ich mich für ein Regal auf Rollen entschieden.

Regal auf Rollen mit 3D Drucker und Filamentrollen
Regal auf Rollen mit 3D Drucker und Filamentrollen

Von unten nach oben ist meine Aufteilung jetzt so: Lager und Ersatzteile, Mülleimer und Werkzeug, Drucker und Rollen in Benutzung, Filamentlager.

Filament Guide für Prusa MK3S
Filament Guide für Prusa MK3S

Dann hatte ich noch eine alte PTZ Webcam herum liegen. Diese habe ich mit Kabelbindern links am Regal befestigt, damit ich den Druck beobachten kann ohne im Raum sein zu müssen.

Kamera und 3D Drucker
Kamera und 3D Drucker

Obwohl, oder vielleicht gerade weil, der Prusa MK3S wirklich solide und robust aufgebaut ist, vibriert das ganze Holzbrett beim verfahren der Y-Achse. Darum habe ich eine Teppichfliese untergelegt, welche die Vibrationen deutlich dämpft.

Für die Halterung der Spulen habe ich mir auch etwas ausgedacht. Diese habe ich auf der rechten Seite mit einer Gewindestange montiert. Da ich viel mit den 3KG Spulen arbeite, war mir eine M8 Stange zu dünn. Daber bin ich auf M12 gegangen.

Zunächste habe ich übliche Stuhlwinkel am Regal festgeschraubt. Auf diese Winkel habe ich dann eine kleine Aufnahme für die Gewindestange geschraubt. Hier gibt es die STL-Datei der Aufnahme.

Stuhlwinkeln und Aufnahme der M12 Gewindestange
Stuhlwinkeln und Aufnahme der M12 Gewindestange
Aufnahme M12 Gewindestange
Aufnahme der M12 Gewindestange

Hier gibt es meine STL-Dateien des Universal Rollenhalters für M12 Stangen. Ich habe 3 Varianten gemacht, kurz, mittel und lang. Für die 3KG Rollen nehme ich die großen, für alle anderen den mittleren.

Universal Rollenhalter mittel
Universal Rollenhalter mittel
Universal Rollenhalter lang
Universal Rollenhalter lang
Universal Rollenhalter nuss
Universal Rollenhalter Nuss
Kugellager für M12 Gewindestangen
Kugellager für M12 Gewindestangen
Rollenhalter ohne Rolle auf Achse
Rollenhalter ohne Rolle auf Achse
Filamentrollen im Regal
Filamentrollen im Regal

Wie schon bei Version 1.0 des Grillpavillons angekündigt, möchte ich auch wenn ich nicht grille den Pavillon mit etwas Ambiete-Beleuchtung erhellen, so wie auch den ganzen restlichen Garten.

Natürlich habe ich direkt an RGB LED Streifen gedacht, die ich aber auch über KNX steuern möchte. Doch nun erstmal die Hardware, zur Steuerung kommen wir später.

Ich habe mir also 2x 5m LED Streifen bestellt, mit der typischen infrarot Fernbedienung. Das Ganze ist natürlich Wasserfest (IP68). Und da ich möglichst viel Licht haben möchte, habe ich RGBW Streifen gewählt. Also sitzt dort alle paar cm auch eine weiße LED und der Streifen hat 4 Kanäle die ich separat steuern kann (rot, grün, blau und weiß).

Trotzdem lassen sich die Streifen alle paar cm mit einer normalen Schere zerschneiden. Mit einem Messer kann man an den vorgesehenen Stellen einfach die Schutzschicht abschneiden und dann an den vorhandenen Lötpads Kabel anlöten um z.B. zwei Streifen “um die Ecke” mit einander zu verbinden. Danach wieder Schrumpfschlauch drauf (wegen Wasserfest) und fertig.

Und, damit das Licht etwas mehr gestreut wird, habe ich mir Aluminium Profile mit einem entsprechenden Aufsatz besorgt.

Aluminiumprofil mit diffuser Abdeckung
Aluminiumprofil mit diffuser Abdeckung

Zuerst habe ich mir überlegt wie ich das Aluminiumprofil am Pavillon befestigen soll. Denn das vorhandene Gestänge ist rund, das Profil aber gerade, bzw. eckig. Ich brauche also einen Adapter. Endlich mal wieder ein (sinnvoller) Fall für den 3D-Drucker.

Ich habe also einen Adapter entworfen und unter jeder 2m Schiene Profil 4 Adapter befestigt. Die runde Seite kommt auf das Pavillon Gestänge. Die Kerben sind für Kabelbinder – so muss ich nicht Bohren. Das Profil klebe ich einfach mit üblichen Konstruktionskleber auf meine Adapter – fertig.

Hier gibt es natürlich auch wieder die STL-Datei für den Adapter.

Und so sieht das ganze dann fertig montiert aus. Schon sehr schön, aber eben nur mit der dusseligen Fernbedienung zu steuern.
Das darf so natürlich nicht bleiben und da ich alles an Ambiente-Beleuchtung im Garten ja mit Zigbee lösen möchte, habe ich mir für den LED Streifen einen entsprechenden RGBW-Zigbee-Controller besorgt.

Verkabelung Zigbee RGBW Controller

Ich habe die 5 Adern des Strips mit einem 8-Adrigen Kabel bis in meine kleine Verteilerbox geführt. Die Stecker des mitgelieferten Netzteiles habe ich gekürzt und direkt an die schon gelegte Verkabelung angeschlossen.

Zigbee Controller am RGBW Stripe

Sieht etwas wild aus, aber wenn man in der Kiste dann etwas auräumt, dann ist es ok und der Deckel passt super drauf.

Zigbee Controller im Verteilerkasten

Warum es jetzt gerade Zigbee geworden ist, könnt Ihr hier nachlesen.