/*

   Kleine Wetterstation mit Internetanbindung.

   Ausgabe von Zeit, Sonnenstands- und Wetterdaten auf eine Website (http://mesp8266m.ddns.net)

   Hardware: Mikrocontroller ESP8266, BME280-Wetterstation-Modul und 20x4-I2C-LCD-Display.

   Point your browser to http://mesp8266m.ddns.net, you should see a response.

   M. Schulte, 08.01.2020

*/

 

 

#include <Adafruit_BME280.h>

#include <Adafruit_Sensor.h>

#include <ESP8266WebServer.h>

#include <ESP8266mDNS.h>

#include <Wire.h>

#include <LiquidCrystal_I2C.h>

 

LiquidCrystal_I2C lcd(0x27, 20, 4);

 

#define RTC_I2C_ADDRESS 0x68

#define SEALEVELPRESSURE_HPA (1013.25)

 

Adafruit_BME280 bme;

ESP8266WebServer server(80);

 

float pi = 3.14159265;

float kwert = pi / 180;

float latitude = 51.222241;

float longitude = 7.953978;

float Elevation;

float elevation;

float Aufgang;

float Untergang;

float Deklination;

float Azimut;

float Zeitgleichung;

float Zeitdifferenz;

float AufgangOrtszeit;

float UntergangOrtszeit;

float Tageslaenge;

float Refraktion;

float MEZ;                                                                              // Mitteleuropäische Zeit

float MOZ;                                                                              // Mittlere Ortszeit

float WOZ;                                                                              // Wahre Ortszeit

int MEZdez;

float MEZmin;

int MEZsec;

 

float Stunde;

float Minute;

float Sekunde;

float ZeitSeitMittag;

float B = latitude * kwert;                                                             // geogr. Breite in Radians

float R = 0.00;                                                                         // Refraktion Anfangswert

float h = -(50.0 / 60.0) * kwert;                                                       // Höhe des Sonnenmittelpunkts bei Aufgang: Radius + Refraktion

int Grenzwert;

int Zeitzone = 1;                                                                       // Zeitzone Greenwich + 1 (z.B. Berlin)

float P = 1013.25;                                                                      // Luftdruck der Standard-Atmosphäre in hPa

float T = 15.0;                                                                         // Temperatur der Standard-Atmosphäre in °C

 

float Temp;

float E;

int Hoehe = 270;

float a = 0.0065;                                                                       // Temperatur Höhengradient [Grad C/m](Näherungswert bei Normalatmosphäre)

int Feuchte;

float Dichte = 1.29;                                                                    // Dichte von Luft bei Normalbedingungen in kg/m^3

float g = 9.81;                                                                         // Erdbeschleunigung in m/s^2

float p0 = 101325;                                                                      // Mittlerer Luftdruck auf Meereshöhe in Pa

float Druck;                                                                            // Absoluter Luftdruck auf Meereshöhe in Pa

float relDruck;                                                                         // Relativer Luftdruck bezogen auf Meereshöhe in Pa

int jahre, monate, tage, yearday;

int stunden, minuten, sekunden;

char linebuf[30];

const char* ssid =     "??????";                                                         //Enter Router SSID here

const char* password = "??????";                                                         //Enter Router Password here

 

 

void setup() {

 

  Serial.begin(115200);

  delay(100);

  pinMode(LED_BUILTIN, OUTPUT);

 

  lcd.init();

  lcd.backlight();

  lcd.clear();

 

  bme.begin(0x76);                                                                     // Start Wettermodul Bosch BME280

 

  Serial.println();

  Serial.print("Connecting to ");

  Serial.print(ssid);

 

  WiFi.begin(ssid, password);                                                          // Start WiFi

 

  while (WiFi.status() != WL_CONNECTED) {

    delay(1000);

    Serial.print(".");

  }

  Serial.println("");

  Serial.println("WiFi connected ...!");

  Serial.print("Got IP: ");

  Serial.println(WiFi.localIP());

 

  server.on("/", handle_OnConnect);

  server.onNotFound(handle_NotFound);

 

  if (!MDNS.begin("esp8266")) {

    Serial.println("Error setting up MDNS responder!");

    while (1) {

      delay(1000);

    }

  }

  Serial.println("mDNS responder started");

  server.begin();                                                                     // Start TCP (HTTP) server

  Serial.println("TCP server started");

  MDNS.addService("http", "tcp", 80);                                                 // Add service to MDNS-SD

}

 

 

void loop() {

 

  MDNS.update();

  server.handleClient();

  machsAlle5Sekunden();

  behandleSerielleBefehle();

 

  yearday = (int)GetDayOfYear(jahre, monate, tage);                                        // Aufruf der Funktion: GetDayOfYear

  Zeitgleichung = BerechneZeitgleichung (yearday);                                         // Aufruf der Funktion: BerechneZeitgleichung

  Deklination = BerechneDeklination (yearday);                                             // Aufruf der Funktion: BerechneDeklination

  Zeitdifferenz = BerechneZeitdifferenz (h, latitude, kwert, Deklination, pi);             // Aufruf der Funktion: BerechneZeitdifferenz

  ZeitSeitMittag = MEZ + longitude / 15.0 - Zeitzone - 12 + Zeitgleichung;                 // Aufruf der Funktion: ZeitSeitMittag

  Azimut = SunAngles(Deklination, B, ZeitSeitMittag, Elevation);                           // Aufruf der Funktion: SunAngles

  Refraktion = BerechneRwert(Elevation, kwert, P, T);                                      // Aufruf der Funktion: BerechneRwert

  Elevation = (Elevation + Refraktion) / kwert;                                            // Höhe mit Refraktionskorrektur in Grad

  Tageslaenge = BerechneTageslaenge (Zeitdifferenz, Zeitgleichung, longitude, Zeitzone, Aufgang, Untergang);   // Aufruf der Funktion: BerechneTageslaenge

 

  Temp = bme.readTemperature();                                                                         // Auslesen Luft-Temperatur in Grad Celsius

  Feuchte = bme.readHumidity();                                                                         // Auslesen Luftfeuchte in %

  Druck = bme.readPressure() / 100.0F;                                                                  // Auslesen Luftduck in hPa

 

  E = 6.112 * exp((17.62 * Temp) / (243.12 + Temp));                                                    // Barometrische Höhenformel (temperaturkompensiert)

  relDruck = Druck * exp(9.80665 * Hoehe / (287.058 * ((273.15 + Temp) + 0.12 * E + a * Hoehe / 2)));   // zur Berechnung des Relativen Luftdrucks

 

  Serial.print("Linebuffer: ");

  Serial.println(linebuf);

  Serial.print("Jahrestag: ");

  Serial.println(yearday);

  Serial.print("Deklination: ");

  Serial.println(Deklination / kwert);

  Serial.print("Azimut: ");

  Serial.println(Azimut);

  Serial.print("Elevation: ");

  Serial.println(Elevation);

  Serial.print("Refraktion: ");

  Serial.println(Refraktion / kwert);

  Serial.print("Sonnenaufgang: ");

  Serial.println(Aufgang);

  Serial.print("Sonnenuntergang: ");

  Serial.println(Untergang);

  Serial.print("Tageslaenge: ");

  Serial.println(Tageslaenge);

 

  Serial.print("Temperatur: ");

  Serial.println(Temp, 1);

  Serial.print("Feuchte: ");

  Serial.println(Feuchte);

  Serial.print("Rel. Luftdruck: ");

  Serial.println(relDruck, 1);

 

  Serial.println( );

  delay(1000);

 

  lcd.setCursor(0, 0);

  lcd.print(linebuf);

  lcd.print(" hPa");

 

  lcd.setCursor(0, 1);

  lcd.print("Temp. :  ");

  lcd.print(Temp, 1);

  lcd.print("   ");

  lcd.write(0xDF);

  lcd.print("C");

 

  lcd.setCursor(0, 2);

  lcd.print("Hum.  :  ");

  lcd.print(Feuchte);

  lcd.print("     %");

 

  lcd.setCursor(0, 3);

  lcd.print("rPress:  ");

  lcd.print(relDruck, 1);

  lcd.print(" hPa");

}

 

 

// Funktion handle_OnConnect

 

void handle_OnConnect() {

 

  digitalWrite(LED_BUILTIN, LOW);

  delay(500);

  digitalWrite(LED_BUILTIN, HIGH);

  delay(500);

 

  server.send(200, "text/html", SendHTML(Hoehe, Temp, Feuchte, relDruck, Druck, yearday, Deklination, Azimut, Elevation, Aufgang, Untergang, Tageslaenge));    // Wetterdaten auf Website (IP = 192.168.178.27) senden

}

 

 

// Funktion handle_NotFound

 

void handle_NotFound() {

  server.send(404, "text/plain", "Link wurde nicht gefunden!");

}

 

 

// Funktion machsAlle5Sekunden

 

void machsAlle5Sekunden() {

  static unsigned long prevMillis = -10000;

  if (millis() - prevMillis < 5000) return;                                                     // Zeit noch nicht erreicht, Funktion abbrechen

  prevMillis = millis();

  rtcReadTime(jahre, monate, tage, stunden, minuten, sekunden);                                 // RTC Uhr auslesen

  snprintf(linebuf, sizeof(linebuf), "%02d.%02d.%04d %02d:%02d:%02d", tage, monate, jahre, stunden, minuten, sekunden);    // Zeit für Ausgabe formatieren

}

 

 

// Funktion behandleSerielleBefehle

 

void behandleSerielleBefehle()

{

  char linebuf[30];

  byte counter;

  if (Serial.available())

  {

    delay(100);                                                                                 // Warte auf das Eintreffen aller Zeichen vom seriellen Monitor

    memset(linebuf, 0, sizeof(linebuf));                                                        // Zeilenpuffer löschen

    counter = 0;                                                                                // Zähler auf Null

    while (Serial.available())

    {

      linebuf[counter] = Serial.read();                                                         // Zeichen in den Zeilenpuffer einfügen

      if (counter < sizeof(linebuf) - 1) counter++;                                             // Zeichenzähler erhöhen

    }

 

    if (strstr(linebuf, "set") == linebuf)                                                      // Prüfe auf Befehl "set" zum Setzen der Zeit, Ab hier ist die Zeile eingelesen

    {

      tage = getIntFromString (linebuf, 1);                                                     // Alle übermittelten Zahlen im String auslesen

      monate = getIntFromString (linebuf, 2);

      jahre = getIntFromString (linebuf, 3);

      stunden = getIntFromString (linebuf, 4);

      minuten = getIntFromString (linebuf, 5);

      sekunden = getIntFromString (linebuf, 6);

    }

    else

    {

      Serial.println("Befehl unbekannt.");

      return;

    }

 

    if (jahre < 2000 || monate < 1 || monate > 12 || tage < 1 || tage > 31 || (stunden + minuten) == 0)   // Ausgelesene Werte einer groben Plausibilitätsprüfung unterziehen:

    {

      Serial.println(linebuf);

      Serial.println("\r\nFehlerhafte Zeitangabe im 'set' Befehl");

      Serial.println("\r\nBeispiel:");

      Serial.println("set 28.08.2013 10:54\r\n");

      return;

    }

    rtcWriteTime(jahre, monate, tage, stunden, minuten, sekunden);

    Serial.println("Zeit und Datum wurden auf neue Werte gesetzt.");

  }

}

 

 

// Funktion rtcReadTime

 

void rtcReadTime(int &jahre, int &monate, int &tage, int &stunden, int &minuten, int &sekunden)               // aktuelle Zeit aus RTC auslesen

{

  Wire.beginTransmission(RTC_I2C_ADDRESS);

  Wire.write(0);                                                                                              // Reset register pointer

  Wire.endTransmission();

  Wire.requestFrom(RTC_I2C_ADDRESS, 7);

 

  sekunden    = bcdToDec(Wire.read() & 0x7f);

  minuten     = bcdToDec(Wire.read());

  stunden     = bcdToDec(Wire.read() & 0x3f);                                                                 // Need to change this if 12 hour am/pm

  bcdToDec(Wire.read());                                                                                      // Platzhalter für Wochentag

  tage        = bcdToDec(Wire.read());

  monate      = bcdToDec(Wire.read());

  jahre       = bcdToDec(Wire.read()) + 2000;

 

  Sekunde = (int)sekunden;

  Minute = (int)minuten;

  Stunde = (int)stunden;

 

  MEZ = Stunde + Minute / 60 + Sekunde / 3600;

  MEZdez = (int)MEZ;

  MEZmin = (MEZ - MEZdez) * 60;

  MEZsec = (MEZmin - (int)MEZmin) * 60;

}

 

 

// Funktion rtcWriteTime

 

void rtcWriteTime(int jahre, int monate, int tage, int stunden, int minuten, int sekunden)                    // aktuelle Zeit in der RTC speichern

{

  Wire.beginTransmission(RTC_I2C_ADDRESS);

  Wire.write(0);

  Wire.write(decToBcd(sekunden));

  Wire.write(decToBcd(minuten));

  Wire.write(decToBcd(stunden));

  Wire.write(decToBcd(0));

  Wire.write(decToBcd(tage));

  Wire.write(decToBcd(monate));

  Wire.write(decToBcd(jahre - 2000));

  Wire.endTransmission();

}

 

 

// Funktion decToBcd

 

byte decToBcd(byte val)                                                             // Hilfsfunktion zum Lesen/Schreiben der RTC

{

  return ( (val / 10 * 16) + (val % 10) );                                          // Convert decimal number to binary coded decimal

}

 

byte bcdToDec(byte val)                                                             // Hilfsfunktion zum Lesen/Schreiben der RTC

{

  return ( (val / 16 * 10) + (val % 16) );

}

 

 

// Funktion getIntFromString

 

int getIntFromString (char *stringWithInt, byte num)

{

  char *tail;

  while (num > 0)

  {

    num--;

    while ((!isdigit (*stringWithInt)) && (*stringWithInt != 0)) stringWithInt++;

    tail = stringWithInt;

    while ((isdigit(*tail)) && (*tail != 0)) tail++;

    if (num > 0) stringWithInt = tail;                                              // new search string is the string after that number

  }

  return (strtol(stringWithInt, &tail, 0));

}

 

 

// Funktion SendHTML

 

String SendHTML(int Hoehe, float Temp, int Feuchte, float relDruck, float Druck, int yearday, float Deklination, float Azimut, float Elevation, float Aufgang, float Untergang, float Tageslaenge) {

  String ptr = "<!DOCTYPE html> <html>\n";

  ptr += "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";

  ptr += "<title>ESP8266 Mini-Wetterstation</title>\n";

  ptr += "<style>html { font-family: Comic Sans MS; display: inline-block; margin: 0px auto; text-align: left;}\n";

  ptr += "body{margin-top: 50px;} h3 {color: #CC0000;margin: 50px auto 30px;}\n";

  ptr += "p {font-size: 16px;color: #0000FF;margin-bottom: 10px;}\n";

  ptr += "</style>\n";

  ptr += "</head>\n";

  ptr += "<body>\n";

  ptr += "<div id=\"webpage\">\n";

  ptr += "<h3>ESP8266 Mini-Wetterstation</h3>\n";

 

  ptr += "<p>Zeit: ";

  ptr += linebuf;                                    // Einbau von Datum und Uhrzeit in Website

 

  ptr += "<p>Jahrestag: ";

  ptr += yearday;

 

  ptr += "<p>Hoehe ue.NN: ";

  ptr += Hoehe;

  ptr += " m</p>";

 

  ptr += "<p>Temperatur: ";

  ptr += Temp;

  ptr += " &deg;C</p>";

 

  ptr += "<p>Luftfeuchte: ";

  ptr += Feuchte;

  ptr += " %</p>";

 

  ptr += "<p>relativer Luftdruck: ";

  ptr += relDruck;

  ptr += " hPa</p>";

 

  ptr += "<p>absoluter Luftdruck: ";

  ptr += Druck;

  ptr += " hPa</p>";

 

  ptr += "<p>Deklination: ";

  ptr += Deklination / kwert;

  ptr += " Grd</p>";

 

  ptr += "<p>Aufgang: ";

  ptr += Aufgang;

  ptr += " h</p>";

 

  ptr += "<p>Untergang: ";

  ptr += Untergang;

  ptr += " h</p>";

 

  ptr += "<p>Tageslaenge: ";

  ptr += Tageslaenge;

  ptr += " h</p>";

 

  ptr += "<p>Azimut: ";

  ptr += Azimut;

  ptr += " Grd</p>";

 

  ptr += "<p>Elevation: ";

  ptr += Elevation;

  ptr += " Grd</p>";

 

  ptr += "</div>\n";

  ptr += "</body>\n";

  ptr += "</html>\n";

 

  return ptr;

}

 

 

// Funktion GetDayOfYear

 

uint16_t GetDayOfYear(uint16_t jahre, uint8_t monate, uint8_t tage) {

  static const uint16_t mdays[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};

  return tage + mdays[monate - 1] + (monate >= 2 && IsLeapYear(jahre));

}

 

 

// Funktion IsLeapYear

 

bool IsLeapYear(uint16_t jahre) {

  return  !(jahre % 4) && ((jahre % 100) || !(jahre % 400));                                               // Schaltjahrberechnung (true = Schaltjahr, false = kein Schaltjahr)

}

 

 

// Funktion: SunAngles für die Berechnung von Azimut und Elevation

 

float SunAngles (float Deklination, float B, float ZeitSeitMittag, float & Elevation) {

  float DK = Deklination;

  float cosdec = cos(DK);

  float sindec = sin(DK);

  float lha = ZeitSeitMittag * (1.0027379 - 1 / 365.25) * 15 * kwert;

  float coslha = cos(lha);

  float sinlha = sin(lha);

  float coslat = cos(B);

  float sinlat = sin(B);

  float N = -cosdec * sinlha;

  float D = sindec * coslat - cosdec * coslha * sinlat;

  Elevation = asin(sindec * sinlat + cosdec * coslha * coslat);                     // Höhe des Sonnenmittelpunkts

  float Azimut = atan2(N, D);

  if (Azimut < 0) {

    Azimut += 2 * pi;

  }

  Azimut = Azimut / kwert;

  return Azimut;

}

 

 

// Funktion: BerechneZeitgleichung

 

float BerechneZeitgleichung (int yearday) {

  float Zeitgleichung = -0.170869921174742 * sin(0.0336997028793971 * yearday + 0.465419984181394) - 0.129890681040717 * sin(0.0178674832556871 * yearday - 0.167936777524864);

  return Zeitgleichung;

}

 

 

// Funktion: BerechneDeklination

 

float BerechneDeklination(int yearday) {

  float Deklination = 0.409526325277017 * sin(0.0169060504029192 * (yearday - 80.0856919827619));

  return Deklination;

}

 

 

// Funktion: BerechneZeitdifferenz

 

float BerechneZeitdifferenz(float h, float latitude, float kwert, float Deklination, float pi) {

  float Zeitdifferenz;

  Zeitdifferenz = 12 * acos((sin(h) - sin(latitude * kwert) * sin(Deklination)) / (cos(latitude * kwert) * cos(Deklination))) / pi;

  return Zeitdifferenz;

}

 

 

// Funktion: BerechneRwert

// Näherungslösung für die Refraktion für ein Objekt bei Höhe hoehe über mathematischem Horizont

// Refraktion beträgt bei Sonnenaufgang 34 Bogenminuten = 0.56667°

// Falls die Höhe der Sonne nicht genauer als auf 0.5° gewünscht ist, kann diese Funktion ignoriert werden

 

double BerechneRwert (double Elevation, double kwert, float P, float T)  {

  double R;

  if (Elevation >= 15 * kwert) {

    R = 0.00452 * kwert * P / tan(Elevation) / (273 + T);                                    // über 15° - einfachere Formel

  }

  else if (Elevation > -1 * kwert) {

    R = kwert * P * (0.1594 + 0.0196 * Elevation + 0.00002 * (Elevation * Elevation)) / ((273 + T) * (1 + 0.505 * Elevation + 0.0845 * (Elevation * Elevation)));

  }

  return R;

}

 

 

// Funktion: BerechneTageslaenge

 

float BerechneTageslaenge (float Zeitdifferenz, float Zeitgleichung, float longitude, int Zeitzone, float & Aufgang, float & Untergang)  {

  float AufgangOrtszeit = 12 - Zeitdifferenz - Zeitgleichung;

  float UntergangOrtszeit = 12 + Zeitdifferenz - Zeitgleichung;

  Aufgang = AufgangOrtszeit - longitude / 15 + Zeitzone;

  Untergang = UntergangOrtszeit - longitude / 15 + Zeitzone;

  float Tageslaenge = Untergang - Aufgang;

  return Tageslaenge;

}