/*
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 += " °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;
}