<file_sep>/*
   Hier werden die persistenten Daten
   der Rest-of-Work-Uhr im EEPROM
   behandelt.
*/
void InitEE (void) {
  EEPROM.get(0, Config);
  if (Config.check != 0x55aa) {
    Config.check = 0x55aa;
    for (int i = 0; i < 6; i++) {
      Config.PumpeDa[i] = false;
      strcpy(Config.Pflanzen[i], "Noch nix");
      Config.PumpeZeit[i] = 10;
    }
    Config.PumpeDa[6] = true;
    Config.PumpeZeit[6] = 0;
    strcpy(Config.Pflanzen[6], "Überlauf");
    Config.PumpeDa[7] = true;
    Config.PumpeZeit[7] = 0;
    strcpy(Config.Pflanzen[7], "Wassereimer");
    EEPROM.put(0, Config);
    EEPROM.commit();
    Serial.println("EEPROM initialisiert");
  }
  else {
    Serial.println("EEPROM ok");
  }
}

void GetEE (void) {
  EEPROM.get(0, Config);  
}

void PutEE (void) {
  EEPROM.put(0, Config);
  EEPROM.commit();
}
<file_sep>String MakeDateString(time_t DateTime) {
  String DateString = "";
  DateString = day(DateTime);
  DateString += ".";
  DateString += month(DateTime);
  DateString += ".";
  DateString += year(DateTime);
  return DateString;
}

String MakeTimeString(time_t DateTime) {
  String TimeString = "";
  TimeString += hour(DateTime);
  TimeString += ":";
  if (minute(DateTime) < 10)
    TimeString += "0";
  TimeString += minute(DateTime);
  return TimeString;
}

int MakeDateDisplay (void) {
  int Day, Month;
  Day = day(now());
  Month = month(now());
  return (Day * 100 + Month);
}

time_t SyncTimeToNTP1(void) {
  uint32_t CurrentTime, NewTime;
  static bool NTPSync = false;
  CurrentTime = now();
  while (!NTPSync) {
    Serial.println("******Time-Sync******");
    while (!timeClient.update()) {
      Serial.print(".");
      delay(100);
    }
    NewTime = timeClient.getEpochTime();
    if (abs(NewTime - CurrentTime) < 10000)
      NTPSync = true;
  }
  Serial.println("");
  NTPSync = false;
  return (LTZ.toLocal(timeClient.getEpochTime(), &tcr));
}

time_t SyncTimeToNTP(void) {
  Serial.println("******Time-Sync******");
  while(!timeClient.update())
    Serial.print(".");
  Serial.println("");
  return (LTZ.toLocal(timeClient.getEpochTime(), &tcr));
}


time_t TimeCalc (int Td, int Tm, int Ty) {
  tmElements_t Tin;
  Tin.Second = 0;
  Tin.Minute = 0;
  Tin.Hour = 0;
  Tin.Day = Td;
  Tin.Month = Tm;
  Tin.Year = Ty;
  return (makeTime(Tin));
}

void I2CScanner (void) {
  static int nDevices;
  static int ScanCount = 0;
  static byte address;
  byte error;
  Serial.print("I2C-Scan #");
  Serial.println(ScanCount);

  nDevices = 0;
  for (address = 1; address < 127; address++ )
  {
    // The i2c_scanner uses the return value of
    // the Write.endTransmisstion to see if
    // a device did acknowledge to the address.
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
    if (error == 0)
    {
      Serial.print("I2C Baustein gefunden unter 0x");
      if (address < 16)
        Serial.print("0");
      Serial.print(address, HEX);
      Serial.println("  !");
      nDevices++;
    }
    else if (error == 4)
    {
      Serial.print("Unbekannter Fehler bei Sdresse 0x");
      if (address < 16)
        Serial.print("0");
      Serial.println(address, HEX);
    }
  }
  if (nDevices == 0)
    Serial.println("Keine I2C Bausteine gefunden\n");
  else
    Serial.println("fertig\n");
  LastScan = millis();           // wait 1 second for next scan
  ScanCount++;
}

void ReadAnalog(void) {
  static int MessCount = 0;
  static unsigned long TimeReadAnalog = 0;
  if (millis() > TimeReadAnalog) {
    Vcc = (float)ESP.getVcc() / 1000;
    sensor.requestTemperatures();
    Temperatur = sensor.getTempCByIndex(0);
    for (int i = 0; i < 4; i++) {
      int AnaVal0 = ads0.readADC_SingleEnded(i);
      int AnaVal1 = ads1.readADC_SingleEnded(i);

      // ADC 0
      AnalogRaw[i] = AnaVal0;
      AnalogRaw[i + 4] = AnaVal1;
      if (AnaVal0 < 11600)
        Analog[i] = 100;
      else if (AnaVal0 > 21500)
        Analog[i] = 0;
      else
        Analog[i] = map(AnaVal0, 11600, 21500, 100, 0);

      // ADC 1
      if (AnaVal1 < 11600)
        Analog[i + 4] = 100;
      else if (AnaVal1 > 21500)
        Analog[i + 4] = 0;
      else
        Analog[i + 4] = map(AnaVal1, 11600, 21500, 100, 0);

    }
    for (int i = 0; i < 8; i++) {
      Volt[i] = (int)(AnalogRaw[i] * 0.125);
    }
    TimeReadAnalog = millis() + 1000;
  }
}

void DoPumpenTest (void) {
  static bool Running = false;
  if (DoTest) {
    if (millis() < TestRunTime[TestPumpe]) {
      PumpeStatus[TestPumpe] = 99;
      mcp.digitalWrite(TestPumpe, HIGH);
      if (!SayTestOnce) {
        Serial.println("Geht los");
        SayTestOnce = true;
      }
    }
    else {
      mcp.digitalWrite(TestPumpe, LOW);
      PumpeStatus[TestPumpe] = 0;
      DoTest = false;
      SayTestOnce = false;
      Serial.println("Fertg");
    }
  }
}

void RunPumpe(void) {
  for (int i = 0; i < 6; i++) {
    if (TestRunTime[i] > millis()) {
      mcp.digitalWrite(i, HIGH);
      PumpeStatus[i] = true;
    }
    else {
      mcp.digitalWrite(i, LOW);
      PumpeStatus[i] = false;
    }
  }
}
<file_sep>/*
   Elke's Wasserwerk für die Überwinterung
*/
#include <Wire.h>
#include "Adafruit_MCP23017.h"
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <ESP8266HTTPUpdateServer.h> //für http-Update
#include <ESP8266Ping.h>
#include <DNSServer.h>
#include <WiFiManager.h>         //https://github.com/tzapu/WiFiManager
#include <ESP8266Ping.h>
#include <FS.h>   //Include File System Headers
//#include <LittleFS.h>
#include <EEPROM.h>
#include <NTPClient.h>
#include <WiFiUDP.h>
#include <TimeLib.h>
#include <Timezone.h>     // from https://github.com/JChristensen/Timezone
#define ARDUINOJSON_USE_LONG_LONG 1
#include <ArduinoJson.h>
#include <PubSubClient.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <Adafruit_ADS1015.h>

ADC_MODE(ADC_VCC);

#define sdaPIN D2
#define sclPIN D1
Adafruit_MCP23017 mcp;

#define ONE_WIRE_BUS D4
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensor(&oneWire);
DeviceAddress insideThermometer;

// ADC ADS1115
Adafruit_ADS1115 ads0(0x49); // ADS1115, single ended
Adafruit_ADS1115 ads1(0x48); // ADS1115, single ended
float Temperatur, Vcc;

//MQTT
#define INTERVAL 60 //Interval Sekunden zwischen den Messungen
#define RECOVER 10  //Interval, wenn Publish nicht erfolgreich
#define TOPIC_1 "19a/wasserwerk/cpusupply"
#define TOPIC_2 "19a/wasserwerk/timestamp"
#define TOPIC_3 "19a/wasserwerk/temperatur"
#define TOPIC_6 "19a/wasserwerk/ip-adresse"
#define TOPIC_7 "19a/wasserwerk/daten"
char FloatString[30];

WiFiClient espClient;
PubSubClient MQTT_Client(espClient);
const char* mqtt_server = "192.168.2.13";
const char ClientName[] = "19a-wasserwerk";

// NTP
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};     //Central European Summer Time
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};       //Central European Standard Time
Timezone LTZ(CEST, CET);    // this is the Timezone object that will be used to calculate local time
TimeChangeRule *tcr;        //pointer to the time change rule, use to get the TZ abbrev
const long utcOffsetInSeconds = 0;
unsigned long UpdateIntervall = 1800;
WiFiUDP udp;
NTPClient timeClient(udp, "pool.ntp.org", utcOffsetInSeconds, UpdateIntervall);
tmElements_t TP;
const char* Wochentag[8] = {
  "Fehler",
  "Sonntag",
  "Montag",
  "Dienstag",
  "Mittwoch",
  "Donnerstag",
  "Freitag",
  "Samstag"
};
// Ende NTP

ESP8266WebServer server(80); //Server on port 80
ESP8266HTTPUpdateServer serverUpdater;

IPAddress ip, ClientIP;
char HostIP[15];
time_t LastShow = 0, LastSync = 0, LastPing = 0, LastPub = 0;
char MyTime[48];

struct EEData_t {
  int check;
  bool PumpeDa[8];
  int PumpeZeit[8];
  char Pflanzen[8][17];
};

time_t DaysLeft = 0;

EEData_t Config;

bool PumpeStatus[8] = {false, false, false, false, false, false, false, false};
int PublishOK = 0;

// I2C Scan
long LastScan;
// Ende

// ADC
int Analog[8];
int AnalogRaw[8];
int Volt[8];
// Ende

// Pumpentest
int TestPumpe;
bool DoTest = false;
bool SayTestOnce = false;
int TestZeit;
unsigned long TestRunTime[8] = {0, 0, 0, 0, 0, 0, 0, 0};
// Ende

void setup() {
  Serial.begin(115200);
  Serial.println("");
  Serial.printf("Wasserwerk, Compilerzeit: %s %s\n",__DATE__,__TIME__);
  WiFiManager wifiManager;
  SPIFFS.begin();
  EEPROM.begin(512);
  sensor.begin();
  Wire.begin(sdaPIN, sclPIN);

  // hir die I2C-Schnittstellen
  // Parallel Expander
  mcp.begin(); // use default address 0
  for (int i = 0; i < 8; i++) {
    mcp.pinMode(i, OUTPUT);
    mcp.digitalWrite(i, LOW);
  }
  // ADCs
  ads0.setGain(GAIN_ONE);
  ads0.begin();
  ads1.setGain(GAIN_ONE);
  ads1.begin();

  uint8_t macAddr[6];
  char Hostname[15];
  WiFi.macAddress(macAddr);
  sprintf(Hostname, "ottO-%02x-%02x-%02x", macAddr[3], macAddr[4], macAddr[5]);
  WiFi.hostname(Hostname);
  wifiManager.setDebugOutput(false);
  //wifiManager.resetSettings();
  if (!wifiManager.autoConnect("Wasserwerk-AP")) {
    Serial.println("failed to connect, we should reset as see if it connects");
    delay(3000);
    ESP.restart();
    delay(5000);
  }
  ip = WiFi.localIP();
  sprintf(HostIP, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
  Serial.printf("IP-Adresse ist %s\n", HostIP);
  setSyncProvider(SyncTimeToNTP);
  setSyncInterval(1800);
  LastSync = now();
  InitEE();
  GetEE();
  MQTT_Client.setServer(mqtt_server, 1883);
  DefServerFunctions();
  serverUpdater.setup(&server);
  server.begin();
  MDNS.addService("http", "tcp", 80);
}

void loop() {
  server.handleClient();
  MDNS.update();
  //DoPumpenTest();
  RunPumpe();
  ReadAnalog();
  if (now() > LastPub) {
    sprintf(MyTime, "%02d-%02d-%04d %02d:%02d:%02d UTC", day(timeClient.getEpochTime()), month(timeClient.getEpochTime()), year(timeClient.getEpochTime()), \
            hour(timeClient.getEpochTime()), minute(timeClient.getEpochTime()), second(timeClient.getEpochTime()));
    if (reconnectNB()) {
      if (PublishData()) {
        LastPub = now() + INTERVAL;
        PublishOK = 1;
      }
      else {
        LastPub = now() + RECOVER;
        PublishOK = 0;
      }
    }
    else {
      LastPub = now() + RECOVER;
      PublishOK = 2;
    }
  }
}
<file_sep>void DefServerFunctions(void) {
  server.on("/", handleRoot);      //Which routine to handle at root location. This is display page
  server.on("/readJSON", sendJSON);
  server.on("/getJSON", readJSON);
  server.on("/TestPumpe", TestPumpen);
  server.on("/runPumpe", TestPumpen);
  server.on("/hostReboot", RebootHost);
  //Initialize Webserver
  server.on("/", handleRoot);
  server.onNotFound(handleWebRequests); //Set setver all paths are not found so we can handle as per URI
}
<file_sep>void handleRoot() {
  server.sendHeader("Location", "/index.html", true);  //Redirect to our html web page
  server.send(302, "text/plane", "");
}

bool loadFromSPIFFS(String path) {
  Serial.print("path ist ");
  String dataType = "text/plain";
  if (path.endsWith("/"))
    path += "index.html";
  if (path.endsWith(".src"))
    path = path.substring(0, path.lastIndexOf("."));
  else if (path.endsWith(".html")) dataType = "text/html";
  else if (path.endsWith(".htm")) dataType = "text/html";
  else if (path.endsWith(".css")) dataType = "text/css";
  else if (path.endsWith(".js")) dataType = "application/javascript";
  else if (path.endsWith(".png")) dataType = "image/png";
  else if (path.endsWith(".gif")) dataType = "image/gif";
  else if (path.endsWith(".jpg")) dataType = "image/jpeg";
  else if (path.endsWith(".ico")) dataType = "image/x-icon";
  else if (path.endsWith(".xml")) dataType = "text/xml";
  else if (path.endsWith(".pdf")) dataType = "application/pdf";
  else if (path.endsWith(".zip")) dataType = "application/zip";
  File dataFile = SPIFFS.open(path.c_str(), "r");
  if (server.hasArg("download")) dataType = "application/octet-stream";
  if (server.streamFile(dataFile, dataType) != dataFile.size()) {
  }
  dataFile.close();
  Serial.println(path);
  return true;
}

void handleWebRequests() {
  //Serial.println(server.client().remoteIP());
  if (loadFromSPIFFS(server.uri())) return;
  String message = "File Not Detected\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i = 0; i < server.args(); i++) {
    message += " NAME:" + server.argName(i) + "\n VALUE:" + server.arg(i) + "\n";
  }
  server.send(404, "text/plain", message);
  Serial.println(message);
}

void sendJSON(void) {
  static int CallCounter = 0;
  const size_t capacity = JSON_OBJECT_SIZE(80);
  DynamicJsonDocument doc(capacity);
  char JSONBuffer[2048];
  char OutputBuffer[20];
  ClientIP = server.client().remoteIP();
  GetEE();
  doc["compile"] = __DATE__ " " __TIME__;
  sprintf(OutputBuffer, "%2.2f", Vcc);
  doc["vcc"] = OutputBuffer;
  sprintf(OutputBuffer, "%3.1f", Temperatur);
  doc["temperatur"] = OutputBuffer;
  doc["wochentag"] = Wochentag[weekday(now())];
  doc["tag"] = MakeDateString(now());
  doc["uhrzeit"] = MakeTimeString(now());
  doc["hostIP"] = HostIP;
  sprintf(OutputBuffer, "%d.%d.%d.%d", ClientIP[0], ClientIP[1], ClientIP[2], ClientIP[3]);
  doc["clientIP"] = OutputBuffer;
  doc["mqtt"] = PublishOK;
  //Serial.println(OutputBuffer);

  JsonArray pumpenConfig = doc.createNestedArray("pumpenConfig");
  JsonArray pumpeStatus = doc.createNestedArray("pumpeStatus");
  JsonArray pflanzenNamen = doc.createNestedArray("pflanzenNamen");
  JsonArray pumpeZeit = doc.createNestedArray("pumpeZeit");
  JsonArray pflanzenFeuchte = doc.createNestedArray("pflanzenFeuchte");
  JsonArray sensorFeuchte = doc.createNestedArray("sensorFeuchte");
  for (int i = 0; i < 8; i++) {
    pumpenConfig.add(Config.PumpeDa[i]);
    pumpeStatus.add(PumpeStatus[i]);
    pflanzenNamen.add(Config.Pflanzen[i]);
    pumpeZeit.add(Config.PumpeZeit[i]);
    pflanzenFeuchte.add(Analog[i]);
    sensorFeuchte.add(Volt[i]);
  }

  serializeJson(doc, JSONBuffer);
  //Serial.println(JSONBuffer);
  server.send(200, "text/plane", JSONBuffer); //Send JSON Data to client ajax request
}

void readJSON() {
  DynamicJsonDocument doc(2048);

  if (server.hasArg("plain") == false) { //Check if body received
    server.send(200, "text/plain", "JSON not received");
    Serial.println("Nix empfangen");
    return;
  }
  server.send(200, "text/plain", "JSON received");
  DeserializationError error = deserializeJson(doc, server.arg("plain"));
  if (error) {
    Serial.print(F("deserializeJson() failed: "));
    Serial.println(error.f_str());
    return;
  }
  for (int i = 0; i < 6; i++) {
    Config.PumpeDa[i] = doc["pumpenConfig"][i];
    strcpy(Config.Pflanzen[i], doc["pflanzeName"][i]);
    Config.PumpeZeit[i] = doc["pumpeZeit"][i];
    Serial.printf("Name: %s, Da: %d, Zeit; %d\n", Config.Pflanzen[i], Config.PumpeDa[i], Config.PumpeZeit[i]);
  }
  Serial.println("---+++---");
  PutEE();
  Serial.println("Daten gespeichert");
}

void TestPumpen() {
  if (server.hasArg("pumpe") == false) { //Check if body received
    server.send(200, "text/plain", "kein Parameter empfangen");
    Serial.println("Nix empfangen");
    return;
  }
  /*if (DoTest) {
    server.send(200, "text/plain", "Test laeuft, warten");
    Serial.println("Test laeuft, warten");
    return;
    }*/
  TestPumpe = server.arg("pumpe").toInt();
  DoTest = true;
  TestRunTime[TestPumpe] = millis() + (Config.PumpeZeit[TestPumpe] * 1000);
  server.send(200, "text/plain", "Teste Pumpe");
}

void RebootHost() {
  Serial.println("Reboot ping");
  if (server.hasArg("reboot") == 1) {
    Serial.println("ESP Reboot");
    server.send(200, "text/plain", "reboot ok");
    delay(1000);
    ESP.restart();
    delay(1000);
  }
}
<file_sep>/*
   MQTT reconnect-Routine
*/
bool reconnectNB() {
  if (!MQTT_Client.connected()) {
    MQTT_Client.connect(ClientName, "Batt", "OnOff");
    if (MQTT_Client.connected())
      Serial.println(F("MQTT Connected"));
    else
      Serial.println(F("MQTT NOT connected!"));

    return MQTT_Client.connected();
  }
}

/*
   hier wird auf den MQTT-Broker veröffentlicht.
*/

bool PublishData (void) {
  const size_t capacity = JSON_OBJECT_SIZE(22);
  DynamicJsonDocument doc(capacity);
  char JSONBuffer[512];
  bool PubState = false;
  doc["temperatur"] = Temperatur;
  doc["druck"] = 0.0;
  doc["feuchte"] = 0.0;
  doc["supply"] = ESP.getVcc();
  doc["ip-address"] = HostIP;
  doc["messung_um"] = MyTime;
  doc["timestamp"] = timeClient.getEpochTime();
  serializeJson(doc, JSONBuffer);
  PubState = MQTT_Client.publish(TOPIC_7, JSONBuffer, true);
  Serial.printf("JSON: %s\n\n", JSONBuffer);
  /*delay(500);
    MQTT_Client.disconnect();
    delay(500);*/
  return (PubState);
}
<file_sep># Wasserwerk
Dieses Projekt dient der Bewässerung von max. sechs Pflanzen. In jeder Pflanze wird die Bodenfeuchte in Prozent gemessen. Der Messwert hat wenig mit den tatsächlichen Werten zu tun.
Fest stehen die Eckwerte 0% (staubtrocken) und 100% (blankes Wasser). Die Steuerung erfolgt über ein Webseite, die das Projekt im lokalen Netz anbietet. Diese Webseite sollte NUR im lokalen Netz angeboten werden.

Die Kommunikation zwischen Webclient und Server ist JSON-basiert. Es wird eine Update-Funktion angeboten, die allerdings NICHT Internet-fähig ist, da die Updates vollkommen ungeschützt übertragen werden. Als Nebeneffekt werden auf dem hauseigenen MQTT-Broker einige Daten publiziert.

Alle I/Os gehen über I2C mit Ausnahme des Thermometers. Hier wird OneWire verwendet.
