Weichwasseranlage mit ESP8266 (mod 12f) abfragen

Einleitung

Ich möchte gerne einen meiner RaspberryPI von ein paar Aufgaben entbinden und suche alternativen um die Aufgaben zu übernehmen. Z.B. ist die Abfrage der Weichwasseranlage die Haupttätigkeit (weil das Script jede Minute läuft) die ich abgeben möchte.

Da ich angefangen habe mit Arduino und ESP zu experimentieren sollte eins dieser kleinen Helfer die Aufgabe übernehmen und die Werte gleichzeitig mittels MQTT für Openhab zur Verfügung stellen.

Ziel ist also die Daten von der Anlage zu holen, zu analysieren und an die Influx-DB sowie per MQTT an Openhab zu schicken.

Ich habe mir dazu folgenden Code zusammengestellt (der aus vielen verschiedenen Quellen stammt und modifiziert wurde). Ich danke allen die mit ihren Fragen und vor allem den Antworten dazu beigetragen haben diesen Chaos-Code zu erstellen. Er ist nicht schön, aber funktioniert wunderbar.

Features

  • Weichwasseranlage abfragen
  • zusätzlich angebundene Sensoren
    • Temperatursensor
    • Luftfeuchtesensor
    • Luftdurcksensor
  • Uhrzeit per NTP (inkl. Sommer- und Winterzeit)
  • Webserver um ein paar Daten direkt anzuzeigen
  • Daten an InfluxDB schicken
  • Daten per MQTT an Openhab schicken

Material

ESP8266-12F * Affiliatelink

Jumper-Kabel * Affiliatelink

BMP180 * Affiliatelink

Code

#include <ESP8266HTTPClient.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <InfluxDbClient.h>

#include <NTPClient.h>
#include <WiFiUdp.h> 

#include <ESP8266WebServer.h>

// #############################################
// WLAN-Zugang
// #############################################
#define wifi_ssid "WLAN-SSID"
#define wifi_password "WLAN-PW"

// #############################################
// MQTT-Server (Openhab)
// #############################################
#define mqtt_server "192.168.0.13"
#define mqtt_user "openhabian"         
#define mqtt_password ""     

// #############################################
// MQTT-Topics für BME280
// ############################################
#define humidity_topic "esp1/humidity"
#define temperature_topic "esp1/temperature"
#define pressure_topic "esp1/pressure"

// #############################################
// InfluxDB-Server und DB-Name
// #############################################
#define INFLUXDB_URL "http://192.168.0.12:8086"
#define INFLUXDB_DB_NAME "weichwasser"

// #############################################
// NTP-Client
// #############################################
#define MY_NTP_SERVER "192.168.2.1"           
#define MY_TZ "CET-1CEST,M3.5.0/02,M10.5.0/03"   

// #############################################
// Datentypen BME280 definieren
// #############################################
float temp = 0.0;
float hum = 0.0;
float pres = 0.0;
float diff = 0.5;

time_t now;                         // this is the epoch
tm tm;                              // the structure tm holds time information in a more convient way
String TimeStamp;

ESP8266WebServer server(80);
String htmltext;

Adafruit_BME280 bme; // I2C
unsigned long delayTime;

WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[50];
int value = 0;

void setup() {
  Serial.begin(115200);
  setup_wifi();
  client.setServer(mqtt_server, 1883);

  while(!Serial);    // time to get serial running
    unsigned status;
    status = bme.begin(0x76);  
    if (!status) {
        Serial.println("Could not find a valid BME280 sensor, check wiring, address, sensor ID!");
        while (1);
    }
    delayTime = 1000;

  configTime(MY_TZ, MY_NTP_SERVER);
  
  server.on("/", handle_OnData);
  server.on("/f.css", handleCss);
  server.onNotFound(handle_NotFound);
  server.begin();
  
  Serial.println("HTTP server started");
  
}

void loop() {
  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  server.handleClient();

  long now = millis();
  if (now - lastMsg > 60000) {
    lastMsg = now;
    getValues();
    fetchWeichwasser();

    timeClient.update();
  }
}

void setup_wifi() {
  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(wifi_ssid);

  WiFi.begin(wifi_ssid, wifi_password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

bool checkBound(float newValue, float prevValue, float maxDiff) {
  return !isnan(newValue) &&
  (newValue < prevValue - maxDiff || newValue > prevValue + maxDiff);
}

void getValues() {
    float newPres = bme.readPressure() / 100.0F;
    float newTemp = bme.readTemperature();
    float newHum = bme.readHumidity();

    if (checkBound(newTemp, temp, diff)) {
      temp = newTemp;
      Serial.print("New temperature:");
      Serial.println(String(temp).c_str());
      client.publish(temperature_topic, String(temp).c_str(), true);
    }

    if (checkBound(newHum, hum, diff)) {
      hum = newHum;
      Serial.print("New Huminity:");
      Serial.println(String(hum).c_str());
      client.publish(humidity_topic, String(hum).c_str(), true);
    }

    if (checkBound(newPres, pres, diff)) {
      pres = newPres;
      Serial.print("New Pressure:");
      Serial.println(String(pres).c_str());
      client.publish(pressure_topic, String(pres).c_str(), true);
    }  
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "ESP8266-";
    clientId += String(random(0xffff), HEX);
    // Attempt to connect
    if (client.connect(clientId.c_str(), mqtt_user, mqtt_password)) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      client.publish("outTopic", "ESP01 alive");
      // ... and resubscribe
      client.subscribe("inTopic");
    } else {
        Serial.print("failed, rc=");
        Serial.print(client.state());
        Serial.println(" try again in 5 seconds");
        // Wait 5 seconds before retrying
        delay(5000);
      }
   }
}

void fetchWeichwasser() {
  String hostURL = "http://192.168.0.14/mux_http";        //URL der Wasserenthaertungsanlage
  String postData = "getXML(\"id=625&show=D_Y_1|D_Y_5|D_Y_10_1|D_A_1_1|D_A_1_2|D_A_1_3|D_A_2_1|D_A_3_1|D_A_3_2~\",1);";
  
  if (WiFi.status() == WL_CONNECTED) {                    //Check WiFi connection status
    HTTPClient http;                                      //Declare object of class HTTPClient
 
    http.begin(espClient, hostURL);                       //Specify request destination
    http.addHeader("Content-Type", "application/xml");    //Specify content-type header
 
    int httpCode = http.POST(postData);                   //Send the request
    String payload = http.getString();                    //Get the response payload
 
    //Serial.println(httpCode);                           //Print HTTP return code
    if (String(httpCode).c_str()!="200") {
      delay(30000);
    }
    //Serial.println(payload);                            //Print request response payload

    payload.replace("<code>ok</code>", "");
    
    //Serial.println(payload);

    /*
    D_Y_1     Wasserverbrauch gestern l
    D_Y_5     Aktueller Regenerationsschritt
    D_Y_10_1  Aktuelle Restkapazität Austauscher  %
    D_A_1_1   Aktueller Durchfluss  m³
    D_A_1_2   Restkapazität m³*°dH
    D_A_1_3   Kapazitätszahl  m³*°dH
    D_A_2_1   Restzeit/-menge Reg.Schritt l oder min
    D_A_3_1   Letzte Regeneration h
    D_A_3_2   Prozentsatz der letzten Regeneration
    */

    String D_Y_1 = xmlTakeParam(payload, "D_Y_1");
    String D_Y_5 = xmlTakeParam(payload, "D_Y_5");
    String D_Y_10_1 = xmlTakeParam(payload, "D_Y_10_1");
    String D_A_1_1 = xmlTakeParam(payload, "D_A_1_1");
    String D_A_1_2 = xmlTakeParam(payload, "D_A_1_2");
    String D_A_1_3 = xmlTakeParam(payload, "D_A_1_3");
    String D_A_2_1 = xmlTakeParam(payload, "D_A_2_1");
    String D_A_3_1 = xmlTakeParam(payload, "D_A_3_1");
    String D_A_3_2 = xmlTakeParam(payload, "D_A_3_2");
    
    D_Y_1.trim();
    D_Y_5.trim();
    D_Y_10_1.trim();
    D_A_1_1.trim();
    D_A_1_2.trim();
    D_A_1_3.trim();
    D_A_2_1.trim();
    D_A_3_1.trim();
    D_A_3_2.trim();
    
    // Single InfluxDB instance
    InfluxDBClient influx_client(INFLUXDB_URL, INFLUXDB_DB_NAME);
    // Define data point with measurement name 'device_status`
    Point pointDevice("softliqSC18");
    // Add data
    pointDevice.addField("D_Y_1", D_Y_1.toFloat());
    pointDevice.addField("D_Y_5", D_Y_5.toFloat());
    pointDevice.addField("D_Y_10_1", D_Y_10_1.toFloat());
    pointDevice.addField("D_A_1_1", D_A_1_1.toFloat());
    pointDevice.addField("D_A_1_2", D_A_1_2.toFloat());
    pointDevice.addField("D_A_1_3", D_A_1_3.toFloat());
    pointDevice.addField("D_A_2_1", D_A_2_1.toFloat());
    pointDevice.addField("D_A_3_1", D_A_3_1.toFloat());
    pointDevice.addField("D_A_3_2", D_A_3_2.toFloat());
    // Write data
    influx_client.writePoint(pointDevice);
    
    if (!client.connected()) {
      reconnect();
    }
    client.publish("ww/wasserverbrauch", String(D_Y_1).c_str(), true);
    client.publish("ww/regenerationsschritt", String(D_Y_5).c_str(), true);
    client.publish("ww/restkapazitaet_prozent", String(D_Y_10_1).c_str(), true);
    client.publish("ww/durchfluss", String(D_A_1_1).c_str(), true);
    client.publish("ww/restkapazitaet_qmdH", String(D_A_1_2).c_str(), true);
    client.publish("ww/kapazitaetszahl_qmdH", String(D_A_1_3).c_str(), true);
    client.publish("ww/restzeitregeneration", String(D_A_2_1).c_str(), true);
    client.publish("ww/letzteregeneration", String(D_A_3_1).c_str(), true);
    client.publish("ww/prozentsatzletzteregeneration", String(D_A_3_2).c_str(), true);    

	time(&now);                       
	localtime_r(&now, &tm);           
	TimeStamp = String(tm.tm_mday) + "." + String(tm.tm_mon + 1) + "." + String(tm.tm_year + 1900) + " " + String(tm.tm_hour) + ":" + String(tm.tm_min) + ":" + String(tm.tm_sec);
    Serial.println(TimeStamp);
    client.publish("ww/time", String(TimeStamp).c_str(), true);  

    htmltext = "";
    htmltext += "<h2>Uhrzeit</h2>"; 
    htmltext += "<p><b>" + String(TimeStamp) + "</b></p>";
    htmltext += "<h2>Klimadaten</h2>"; 
    htmltext += "<p>Temperatur: <b>" + String(temp) + " °C</b></p>";
    htmltext += "<p>Luftfeuchte: <b>" + String(hum) + " %</b></p>";
    htmltext += "<p>Luftdruck: <b>" + String(pres) + " hPa</b></p>";
    htmltext += "<h2>Austauscher</h2>"; 
    htmltext += "<p>Restkapazitaet: <b>" + String(D_Y_10_1) + " %</b></p>";
    htmltext += "<p>Durchfluss: <b>" + String(D_A_1_1) + " m³/h</b></p>";
    htmltext += "<p>Verbrauch gestern: <b>" + String(D_Y_1) + " l</b></p>";
    htmltext += "<h2>Regeneration Austauscher</h2>"; 
    htmltext += "<p>Regeneration vor <b>" + String(D_A_3_1) + " h</b></p>";
    htmltext += "<p>Regeneration über <b>" + String(D_A_3_2) + " %</b></p>";
    htmltext += "<p>Regenerationsschritt: <b>" + String(D_Y_5) + "</b></p>";
    htmltext += "<p>Schrittdauer: <b>" + String(D_A_2_1) + " %/min</b></p>";
    

    http.end();  //Close connection
  } else {
    Serial.println("Error in WiFi connection");
  }
}

String xmlTakeParam(String inStr,String needParam)
{
  if(inStr.indexOf("<"+needParam+">")>0){
     int CountChar=needParam.length();
     int indexStart=inStr.indexOf("<"+needParam+">");
     int indexStop= inStr.indexOf("</"+needParam+">");  
     return inStr.substring(indexStart+CountChar+2, indexStop);
  }
  return "not found";
}
void handle_OnData() {
  server.send(200, "text/html", SendHTML(htmltext)); 
}
void handle_OnConnect() {
  server.send(200, "text/html", SendHTML("Boot")); 
}

void handle_NotFound(){
  server.send(404, "text/plain", "Not found");
}

String SendHTML(String addtext){
  String ptr = "";
  ptr += "<!DOCTYPE html PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>\n";
  ptr += "<html lang='de'>\n";
  ptr += "<head>\n";
  ptr += "<title>Weichwasser Webserver</title>\n";
  ptr += "<link rel='stylesheet' type='text/css' href='/f.css'>\n";
  ptr += "<meta content='text/html; charset=UTF-8' http-equiv='content-type'>\n";
  ptr += "</head>\n";
  ptr += "<body>\n";
  ptr += "<h1>Weichwasser Webserver</h1>\n";
  ptr += addtext;
  ptr += "</body>\n";
  ptr += "</html>";
  
  return ptr;
}

void handleCss()
{
  String message = "";
  message += "*{font-family:sans-serif}\n";
  message += "body{margin:10px;width:300px}\n";
  message += "h1, h2{color:white;background:#8FCE00;text-align:center}\n";
  message += "h1{font-size:1.2em;margin:1px;padding:5px}\n";
  message += "h2{font-size:1.0em}\n";
  message += "h3{font-size:0.9em}\n";
  message += "a{text-decoration:none;color:dimgray;text-align:center}\n";
  message += ".small{font-size:0.6em}\n";
  message += ".value{font-size:1.8em;text-align:center;line-height:50%}\n";
  message += "footer p, .infodaten p{font-size:0.7em;color:dimgray;background:silver;";
  message += "text-align:center;margin-bottom:5px}\n";
  message += "nav{background-color:silver;margin:1px;padding:5px;font-size:0.8em}\n";
  message += "nav a{color:white;padding:10px;text-decoration:none}\n";
  message += "nav a:hover{text-decoration:underline}\n";
  message += "nav p{margin:0px;padding:0px}\n";
  message += ".btn{background-color:#C0C0C0;color:dimgray;text-decoration:none;";
  message += "border-style:solid;border-color:dimgray}\n";
  message += ".on, .off{margin-top:0;margin-bottom:0.2em;margin-left:3em;";
  message += "font-size:1.4em;background-color:#C0C0C0;";
  message += "border-style:solid;width:5em;height:1.5em;text-decoration:none;text-align:center}\n";
  message += ".on{border-color:green}\n)";
  server.send(200, "text/css", message);
}
Ich entschuldige mich ausdrücklich für den schlechten Codestil, ich nehme gerne Optimierungen entgegen :)

Ich wünsche trotzdem viel Spaß damit. Und so sieht es dann aus:

Webserver

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert