Zuletzt geändert: 2018-07-01

Der Sketch zum Gasuhr auslesen übersichtlich aufgeteilt in Tabs.

Gaszaehler.ino

// ****************************************************************
// Sketch Gaszähler Modular(Tab)
// created: Jens Fleischer, 2018-03-30
// last mod: Jens Fleischer, 2018-07-01
// For more information visit: https://fipsok.de
// ****************************************************************
// Hardware: Nodemcu
// D2 = GPIO4  Anschluss Reedrelais vom GPIO4 auf GND
// 10k Pullup-Widerstand von VCC auf GPIO4
// Versorgung über USB
/******************************************************************
  Copyright (c) 2018 Jens Fleischer. All rights reserved.

  This file is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
  This file is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.
*******************************************************************/

#include <ESP8266WebServer.h>     // http://arduino-esp8266.readthedocs.io/en/latest/esp8266wifi/readme.html#server
#include <ArduinoOTA.h>           // http://arduino-esp8266.readthedocs.io/en/latest/ota_updates/readme.html
#include <FS.h>                   // http://arduino-esp8266.readthedocs.io/en/latest/filesystem.html

const byte inputPIN = D2;         // Reedrelais
const byte HeartbeatLed = D4;     // OnBoardLed Esp 12E
unsigned long value, oldvalue;
bool ledz = HIGH, ledh = HIGH;    // Merker für ZählerLed AN/Aus , Merker für HeartbeatLed AN/Aus
unsigned long fehlerZeit;         // Zeitpunkt des letzten Fehlers

ESP8266WebServer server(80);

void setup() {
  Serial.begin(115200);
  Serial.println("");
  pinMode(HeartbeatLed, OUTPUT);
  digitalWrite(HeartbeatLed, HIGH);
  Connect();    // ruft die Funktion zum Verbinden mit dem Wlan auf
  homepage();   // ruft die Funktion zum bereitstellen einer Webseite auf
  SPIFFS.begin();
  server.begin();
  Serial.println("HTTP Server ist gestartet\n");

  ArduinoOTA.onStart([]() {                         // Zählerstand speichern bevor OTA Update
    speichern();
  });
  ArduinoOTA.begin();

  File f = SPIFFS.open("/value.txt", "r");          // Zählerstand beim Start aus Datei lesen
  String str = f.readStringUntil('\n');
  value = str.toInt();
  oldvalue = value;
  f.close();
}

void loop() {
  static unsigned long letzterAufruf = 0;
  ArduinoOTA.handle();
  server.handleClient();
  zaehlerauslesen();  // Funktionsaufruf
  char buf[9];
  snprintf(buf, sizeof(buf), "%li.%02li", value / 100, value % 100);
  if (!thingspeak(buf)) {
    fehlerZeit = millis() / 1000;
  }
  ledh ? digitalWrite(HeartbeatLed, millis() % 500 >= 250) : digitalWrite(HeartbeatLed, HIGH);    // LED zeigt an das der Sketch läuft
  unsigned long aktuelleMillis = millis();
  if (aktuelleMillis - letzterAufruf >= 60000UL * 240 && value != oldvalue) {       // Zählerstand speichern() aller 4 Stunden, wenn der Wert sich geändert hat
    speichern();
    oldvalue = value;
    letzterAufruf = aktuelleMillis;
  }
}

void zaehlerauslesen() {
  bool aktueller = 1;
  static bool vorheriger = 1;
  static unsigned long letzteMillis = 0;
  unsigned long aktuelleMillis = millis();
  if (aktuelleMillis - letzteMillis >= 1000) {       // Das Reedrelais wird einmal pro Sekunde abgefragt
    aktueller = digitalRead(inputPIN);
    if (!aktueller && aktueller != vorheriger) {
      value++;
    }
    vorheriger = aktueller;
    letzteMillis = aktuelleMillis;
  }
  !ledz ? digitalWrite(LED_BUILTIN, HIGH) : !vorheriger ? digitalWrite(LED_BUILTIN, millis() % 500 >= 250) : digitalWrite(LED_BUILTIN, HIGH);    // LED blinkt wenn Reedrelais geschlossen
}

void speichern() {
  File f = SPIFFS.open("/value.txt", "w");
  f.printf("%li\n", value);
  f.close();
}

Connect.ino

// ****************************************************************
// Sketch Connect Modular(Tab) mit optischer Anzeige
// created: Jens Fleischer, 2018-04-08
// last mod: Jens Fleischer, 2018-07-31
// For more information visit: https://fipsok.de
// ****************************************************************
// Hardware: Esp8266
/******************************************************************
  Copyright (c) 2018 Jens Fleischer. All rights reserved.

  This file is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
  This file is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.
*******************************************************************/

void Connect() {      // Funktionsaufruf "Connect();" muss im Setup eingebunden werden
  const char* ssid = "Netzwerkname";             // << kann bis zu 32 Zeichen haben
  const char* password = "PasswortvomNetzwerk";  // << mindestens 8 Zeichen jedoch nicht länger als 64 Zeichen

  pinMode(LED_BUILTIN, OUTPUT);     // OnBoardLed Nodemcu
  byte i = 0;
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    digitalWrite(LED_BUILTIN, 0);
    delay(500);
    digitalWrite(LED_BUILTIN, 1);
    delay(500);
    i++;
    Serial.printf(" %i sek\n", i);
    if (i > 9) {
      Serial.print("\nVerbindung zum AP fehlgeschlagen !\n\n");
      ESP.restart();
    }
  }
  Serial.println("\nVerbunden mit: " + WiFi.SSID());
  Serial.println("Esp8266 IP: " + WiFi.localIP().toString());
}

Html.ino

// ****************************************************************
// Sketch Gaszähler Webinterface Modular(Tab)
// created: Jens Fleischer, 2018-04-08
// last mod: Jens Fleischer, 2018-04-16
// For more information visit: https://fipsok.de
// ****************************************************************
// Hardware: Esp8266
/******************************************************************
  Copyright (c) 2018 Jens Fleischer. All rights reserved.

  This file is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
  This file is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.
*******************************************************************/

void homepage() {     // Funktionsaufruf "homepage();" muss im Setup eingebunden werden
  server.on("/", handleRoot);
  server.on("/detail", handledetail);
  server.on("/ledh", []() {
    ledh = !ledh;
    handledetail();
  });
  server.on("/ledz", []() {
    ledz = !ledz;
    handledetail();
  });
}

void handledetail() {
  if (server.arg(0).length() > 0 && server.arg(0).length() <= 7) {
    value = server.arg(0).toInt();
    speichern();                                  // Benutzer Eingabe in der Datei speichern
    Serial.printf("Zählerstand vom Client: %d\n", value);
  }
  char buf[39];
  snprintf(buf, sizeof(buf), "[\"%05ld,%02ld9  m³\",\"%d\",\"%d\",\"%s\"]", value / 100, value % 100, ledh, ledz, Fehlerzeit().c_str());
  server.send(200, "application/json", buf);     // Zählerstand und Led Status an Html Seite senden
}

String Fehlerzeit() {   // ermittelt die vergangene Zeit seit dem letztem Verbindungsfehler zu Thingspeak
  if (fehlerZeit > 0) {
    String Time = "";
    uint8_t mm = (millis() / 1000 - fehlerZeit) / 60 % 60;
    uint16_t hh = ((millis() / 1000 - fehlerZeit) / 60) / 60;
    if (hh > 240) fehlerZeit = 0;      // nach 10 Tagen ohne Fehler, wird die Fehleranzeige zurückgesetzt
    if (hh < 10)Time += "0";
    Time += (String)hh + ":";
    if (mm < 10)Time += "0";
    Time += (String)mm;
    return Time;
  }
  return "0";
}

void handleRoot() {   // Html Webseite, der Zählerstand wird aller 5 Sekunden mittels fetch.api aktuallisiert
  String temp = "<!DOCTYPE HTML><html lang='de'><head><meta charset='UTF-8'><meta name= viewport content='width=device-width, initial-scale=1.0,'><title>Gaszähler</title><script>";
  temp += "window.addEventListener('load',function(){document.querySelector('#grey').addEventListener('click',function(){e()});document.querySelector('#ledz').addEventListener";
  temp += "('click',function(){d('ledz')});document.querySelector('#ledh').addEventListener('click',function(){d('ledh')});document.querySelector('input').addEventListener('blur',";
  temp += "function(){b(inObj=document.querySelector('input'))});var c=document.querySelector('#note');function b(f){!f.checkValidity()?(c.innerHTML=f.validationMessage,c.classList.add";
  temp += "('note')):(d('detail',document.querySelector('input').value))}var a;function e(){(a)?document.getElementById('no').style.display='none':document.getElementById('no').style.";
  temp += "display='block'.display='flex';a=!a;c.innerHTML='';c.classList.remove('note');document.querySelector('form').reset()}function d(g,f){if(Number.isInteger(parseInt(f))){e()}";
  temp += "fetch(g,{method:'POST',body:f}).then(function(h){return h.json()}).then(function(j){document.querySelector('#wert').innerHTML=j[0];var h=document.querySelector('#ledh');var ";
  temp += "i=document.querySelector('#ledz');if(j[1]=='0'){h.innerHTML='LED On',h.classList.remove('buttonon')}else{h.innerHTML='LED Off',h.classList.add('buttonon')}if(j[2]=='0')";
  temp += "{i.innerHTML='LED On',i.classList.remove('buttonon')}else{i.innerHTML='LED Off',i.classList.add('buttonon')}if(j[3]!='0')document.querySelector('#led').innerHTML=";
  temp += "'Sendefehler vor '+j[3]+' hh:mm';})}document.addEventListener('DOMContentLoaded',d('detail'));setInterval(d,5000,'detail')});</script><style>body{background-color:tan;";
  temp += "display:flex;flex-flow:column;align-items:center;padding:10px;font-size:18px}#grey,#no{display:flex;flex-flow:column;align-items:center;background-color:#c5c3c3;";
  temp += "width:280px;height:150px;border:.3em solid #aeaeab;box-shadow:5px 10px 5px rgba(0,0,0,0.7);border-radius:.5em}#black{background-color:black;width:14em;";
  temp += "height:2em;border-radius:.2em}#red{border:.27em solid red;position:relative;width:3.7em;height:1.5em;left:6.4em}#wert{color:white;position:relative;top:-2.2em;";
  temp += "left:.1em;letter-spacing:.2em;font-size:1.6em;font-weight:bold}#no{display:none}.note{background-color:#fecdee;padding:.3em;margin-top:1.5em;text-align:center;";
  temp += "max-width:17em;border-radius:.5em;position:relative;top:-13.3em}input,#led{height:30px;margin-top:1em}.button{background-color:#75a27e;height:40px;width:120px;";
  temp += "font-size:16px;box-shadow:5px 5px 5px rgba(0,0,0,0.7)}.buttonon{background-color:#cf848c}</style></head><body class='flexg'>  <div id ='grey'><h1>Zählerstand</h1>";
  temp += "<div id='black'><div id='red'></div></div></div><span id='wert'></span><div id ='no'><form><input placeholder=' Zählerstand eingeben' pattern='[0-9]{1,7}' title='Nur 1 bis";
  temp += " 7 Ziffern sind erlaubt' required></form><div id='led'> Zähler - Led - Heartbeat</div><div><button id='ledz' class='button'>LED On/Off</button> <button id='ledh' ";
  temp += "class='button'>LED On/Off</button></div></div><div id='note'></div></body></html>";
  server.send(200, "text/html", temp);
}

Thingspeak.ino

// ****************************************************************
// Sketch Thingspeak Modular(Tab) (Antwort nicht blockierend)
// created: Jens Fleischer, 2018-04-08
// last mod: Jens Fleischer, 2018-05-05
// For more information visit: https://fipsok.de
// ****************************************************************
// Hardware: Esp8266
/******************************************************************
  Copyright (c) 2018 Jens Fleischer. All rights reserved.

  This file is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
  This file is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.
*******************************************************************/

WiFiClient client;

bool thingspeak(const char* value) {       // Funktionsaufruf "thingspeak();" muss in loop eingebunden sein
  static bool error = false, response = true;
  const uint16_t httpPort = 80;
  const char* host = "184.106.153.149";
  const char* ApiKey = "6B4K5ZH1EEKTU63R";                  // trage deinen ApiKey von https://thingspeak.com
  const unsigned long interval = 1000UL * 300;              // Interval in Sekunden einstellen
  static unsigned long letzteMillis = 0 - interval / 2;     // Sendebeginn einstellen ("- interval" = sofort, "0" = nach Ablauf von Interval)
  const uint16_t timeout = 2000;                            // Zeitraum für Fehlerrückgabe in Millisekunden einstellen
  const String path = (String)"&field1=" + value + "&field2=" + value;     // "field1 - 8" anpassen

  /*************** Thinkspeak senden *********************/

  unsigned long aktuelleMillis = millis();
  if (aktuelleMillis - letzteMillis >= interval || error) {       // senden im Interval und erneut im Fehlerfall
    error = false;
    letzteMillis = aktuelleMillis;
    if (!client.connect(host, httpPort)) {
      Serial.println("Thingspeak Verbindung fehlgeschlagen !");
      error = true;
      return false;
    }
    else {
      client.printf("POST /update?key=%s%s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", ApiKey, path.c_str(), host);
      response = 0;
      return true;
    }
  }

  /************** Thinkspeak auf Anwort prüfen *****************/

  if (!response) {
    if (millis() - letzteMillis > timeout) {
      Serial.println("Thingspeak Client Timeout !");
      error = true;
      return false;
    }
    if (client.available()) {               // einlesen der Antwort
      //Serial.printf("Thingspeak Antwort nach: %4ld ms\n", millis() - letzteMillis); // zeigt die Zeit bis zur Antwort --> passe den Timeout entsprechend an
      String line = client.readStringUntil('\n');
      if (line.startsWith("HTTP/1.1 200 OK")) {    // wenn Antwort mit "HTTP/1.1 200 OK" beginnt war das Senden erfolgreich
        client.stop();
        response = true;
      }
    }
  }
  return true;
}

ESP Startseite