Kleine Wetterstation mit JSON und Mondphase

Schon länger ärgert es mich, dass ich aktuelle Wetterdaten und weitere nützliche Informationen für den Tag mir immer mühsam von verschiedenen Seiten oder Geräten holen muss. Dazu zählen das Wetter, Sonnenaufgang und -untergang, Temperatur und die Mondphase. Natürlich dürfen auch das aktuelle Datum und die Uhrzeit nicht fehlen.

Die oben genannten Informationen generiere ich mir von aus drei verschiedenen Quellen, einem NTP-Server, einem Wetterdienst, der die Informationen im JSON-Format liefert und durch simple Mathematik. Letzteres dient für die Berechnung der Mondphase für den aktuellen Tag. Die Hardware soll dabei schlank aber gleichzeitig leistungsstark für weitere Modifikationen sein.

Damit das komplette Projekt funktioniert, bedarf es eines freien Accounts bei openweathermap.org und den API-Key für diesen Account. Wie Sie an die API-Key kommen, wird in der Hilfe von openweathermap.org ausführlich erklärt. Damit keiner mit dem IoT-Gerät Unsinn treibt, wird gleichzeitig auch das sichere https-Protokoll verwendet.

Was das JSON-Protokoll ist und wie die Mondphase berechnet wird, erkläre ich vor der benötigten Hard- und Software.

Was das JSON-Protokoll ist und wie die Mondphase berechnet wird, erkläre ich vor der benötigten Hard- und Software.

 Die Berechnung der Mondphase

Wie in der Einleitung erwähnt, wird der ESP32 die Mondphase nicht über eine API abfragen, sondern diese selbst berechnen. Dazu braucht es simple Mathematik. Damit die Berechnung funktioniert, brauchen wir das genaue Datum einer vergangenen Vollmondnacht. Diese kann man sich unter www.mondverlauf.de für einen genauen Standort ausgeben lassen.

Danach errechnet man das Ergebnis zwischen der vergangenen Zeit bis heute und teilt dieses durch die Zeit, die zwischen zwei Neumondphasen vergeht. Diese Zeitspanne nennt sich auch synodische Periode.

Von diesem Ergebnis verwenden wir nur die Nachkommastellen, um die aktuelle Mondphase zu ermitteln. Dabei sagen die Nachkommastellen folgende Mondphasen aus:

  • Vollmond                             bei 0,0
  • Abnehmender Halbmond    bei 0,25
  • Neumond                            bei 0,5
  • Zunehmender Halbmond    bei 0,75


Ein kleines Beispiel dazu:

  • Aktuell ist der 04.05.2020, 22:00 Uhr
  • Der letzte Vollmond war am 08.04.2020 um 4:34 Uhr
  • Dazwischen liegen knapp 26,72 Tage
  • Diese Tage werden durch 29,53 geteilt (synodische Periode)
  • Als Ergebnis erhalten wir 0,90 (zweite Stelle hinter dem Komma gerundet)
  • Die Zahl vor dem Komma gibt an, wie oft schon eine Mondperiode durchlaufen wurde, in diesem Fall kein Mal
  • Die Nachkommastelle gibt Aussage darüber, dass wir einen zunehmenden Mond haben und es bald Vollmond sein wird

Damit die Berechnung im Sketch funktioniert, definieren wir eine Variable vom Typ time und beschreiben diese in der Funktion setup() mit einer Funktion tmConvert, siehe Code 1.

time_t tmConvert(int iYear, byte byMonth, byte byDay, byte byHour, byte byMinute, byte bySecond)
{
  tmElements_t tmSet;
  tmSet.Year = iYear - 1970;
  tmSet.Month = byMonth;
  tmSet.Day = byDay;
  tmSet.Hour = byHour;
  tmSet.Minute = byMinute;
  tmSet.Second = bySecond;
  return makeTime(tmSet);
}

Code 1: Funktion, um UTC-Zeit letzte Mondphase zu errechnen

Die Berechnung und die Ausgabe erfolgen über zwei separate Funktionen. Die Funktion MoonUpdate übernimmt dabei die Berechnung der Mondphase wie oben beschrieben, siehe Code 2. Der Rückgabewert der Funktion ist eine Variable vom Typ double.

double MoonUpdate()
{
    double dDaySinceLastFullmoon = (now() / 86400 - tmLastFullMoon / 86400) / 29.53;
    int iToHundret = (dDaySinceLastFullmoon - int(dDaySinceLastFullmoon)) * 100;
    return (double) iToHundret / 100; //Needed to get only 2 decimal places
}
Code 2: Funktion, um aktuelle Mondphase zu errechnen


Die Funktion GetMoonPhase übernimmt die Interpretation des double-Werts von Moon-Update und wandelt diesen in einen für den Menschen verständlichen Wert, in diesem Fall als lesbaren Text. Zu Beginn ruft GetMoonPhase die Funktion MoonUpdate auf, um den aktuellen double-Wert zu erhalten, siehe 
Code 3.

String GetMoonPhase()
{
    static double dMoonphase = MoonUpdate();     //Method to update moonphase
    Serial.println("Calculated moon result: " +String(dMoonphase,3));
    if(dMoonphase == 0.0) 
      dMoonphase = 1.00;
      
    if(dMoonphase < double(0.25))
    {
      return "Abnehmende Mond";
    }
    else if (dMoonphase == 0.25)
    {
      return "Abnehmender Halbmond";
    }
    else if(0.25 < dMoonphase && dMoonphase < 0.50)
    {
      return "Abnehmende Sichel";
    }
    else if(dMoonphase == 0.50)
    {
      return "Neumond";
    }
    else if(0.50 < dMoonphase && dMoonphase < 0.75)
    {
      return "Zunehmende Sichel";
    }
    else if(dMoonphase == 0.75)
    {
      return "Zunehmender Halbmond";
    }
    else if(0.75 < dMoonphase && dMoonphase < 1.00)
    {
      return "Zunehmender Mond";
    }
    else //Moonphase == 1
    {
      return "Vollmond";
    }
}

Code 3:Berechnung der Mondphase 

Was ist das JSON-Format

JSON ist die Abkürzung von JavaScript Object Notation und ist ein offenes Standard Dateiformat in einer einfachen lesbaren Textform, ähnlich wie XML. Es dient dem simplen Datenaustausch zwischen Anwendungen und findet unter anderem in der Industrie, bei systemübergreifender Kommunikation, Anwendung.

Beim JSON-Format handelt es sich zwar um ein gültiges JavaScript, aber es gibt Parser für alle verbreiteten Programmiersprachen, wie auch dem Arduino. In unserem Falle nutzen wir den Parser ArduinoJson, der uns die Antwort auf unserer Wetteranfrage in gültiges und leicht zu parsendes JSON umwandelt.

Eine Antwort kann wie folgt aussehen und ist dabei schnell und einfach zu lesen.

{
  "Herausgeber": "Xema",
  "Nummer": "1234-5678-9012-3456",
  "Deckung": 2e+6,
  "Waehrung": "EURO",
  "Inhaber":
  {
    "Name": "Mustermann",
    "Vorname": "Max",
    "maennlich": true,
    "Hobbys": ["Reiten", "Golfen", "Lesen"],
    "Alter": 42,
    "Kinder": [],
    "Partner": null
  }
}

Im Falle der Wetterdaten ist die Vorgehensweise wie folgt:

  1. Anfrage an den Wetterdienst openweathermap.org senden
  2. Antwort-String abwarten
  3. Nicht benötigte Daten, wie z.B. den Header, der Antwort eliminieren
  4. Den erhaltenen String ins DynamicJsonDocument-Format konvertieren
  5. Daten auslesen und ggf. schon in den richtigen Variablentyp umwandeln

Die oben beschriebene Arbeitsreihenfolge wird über die Funktion WeatherUpdate ausgeführt, siehe Code 4. Um mit ArduinoJson nun einen genauen Wert, wie z.B. das Alter von Herrn Mustermann aus unserem fiktiven Beispiel, zu bekommen, muss im Unterknoten „Inhaber“ die Variable „Alter“ ausgelesen werden. Diese Abfrage ist relativ simpel.

jsonDoc["Inhaber"]["Alter"]

Durch das Anfügen von „.as<int>()“ kann die Variable direkt noch in das gewünschte Variablenformat, hier Integer, konvertiert werden. Genau dieselbe Methode wird im Sketch genutzt, um die benötigten Werte direkt in die globalen Variablen zu schreiben, zu finden ab dem Kommentar „Save data to global var“.

void WeatherUpdate()
{
  static bool bUpdateDone;
  if((firstrun || (minute() % 5) == 0) && !bUpdateDone)
  {
    jsonDoc.clear();  //Normally not needed, but sometimes new data will not stored
    String strRequestData = RequestWeather(); //Get JSON as RAW string
    Serial.println("Received data: " + strRequestData);
    //Only do an update, if we got valid data
    if(strRequestData != "")  //Only do an update, if we got valid data
    {
      DeserializationError error = deserializeJson(jsonDoc, strRequestData); //Deserialize string to AJSON-doc
      if (error)
      {
        Serial.print(F("deserializeJson() failed: "));
        Serial.println(error.c_str());
        return;
      }
      //Save data to global var
      strMinTemp = RoundTemp(jsonDoc["main"]["temp_min"].as<double>());
      strMaxTemp = RoundTemp(jsonDoc["main"]["temp_max"].as<double>());
      strCurTemp = RoundTemp(jsonDoc["main"]["temp"].as<double>());
      strFeelTemp = RoundTemp(jsonDoc["main"]["feels_like"].as<double>());
      strSunrise  = jsonDoc["sys"]["sunrise"].as<int>();
      strSunset   = jsonDoc["sys"]["sunset"].as<int>();
      static long timeZone = jsonDoc["timezone"];  //Get latest timezone
      //Print to Serial Monitor
      Serial.println("Min Temp: " + strMinTemp);
      Serial.println("Max Temp: " + strMaxTemp);
      Serial.println("Cur Temp: " + strCurTemp);
      Serial.println("Feel Temp: " + strFeelTemp);
      //Check if timezone changed (sommer- / wintertime
      if(timeZone != utcOffsetInSeconds)
      {
        utcOffsetInSeconds = timeZone;
        timeClient.setTimeOffset(utcOffsetInSeconds);
      }
    }
    bUpdateDone = true;
    bUpdateDisplay = true;
    }
  if((minute() % 5) != 0)
   bUpdateDone = false;
}

Code 4: Parsen der JSON-Wetterdaten

Interessant ist dabei die Ähnlichkeit zu XML und die Tatsache, dass Variablen durch „[]“ zu einem weiteren Unterknoten werden können, welcher wiederum Variablen oder Unterknoten enthalten kann. Somit kann man schnell und einfach Dateninhalte hinzufügen und auch auslesen, sofern man im Quellcode die genaue Knotenposition einprogrammiert.

Benötigte Hardware

Die Hardware für dieses Projekt kann komplett bei AZ-Delivery bezogen werden.

Anzahl Bauteil Anmerkung
1 ESP32 DevelpomentBoard
1 0,96“ OLED Display mit I2C 128
1 Jumper Wire Female to Male


Sollte ein anderes Display oder ESP-Modul von Ihnen genutzt werden, so muss das Pinout der Hardware beachtet werden.

Benötigte Software 

Einrichtung des ESP32 NodeMCU

Zwar habe ich unter dem Punkt „Software“ schon drauf hingewiesen, dass die richtige Boardbibliothek eingebunden werden soll, dennoch möchte ich Ihnen in kurzen Schritten erklären, was zu tun ist. Die detaillierte Anleitung finden Sie bei AZ-Delivery bei den kostenlosen eBooks.

Starten Sie die Arduino IDE und öffnen Sie die Voreinstellungen und tragen Sie die URL https://dl.espressif.com/dl/package_esp32_index.json unter zusätzliche Boardverwalter-URLs ein, siehe Abbildung 1 und Abbildung 2.

Abbildung 1: Voreinstellungen in der Arduino IDE öffnen
Abbildung 2: Boardverwaltungs-URL für ESP32 NodeMCU eintragen

Nach einem Neustart der Arduino IDE öffnen Sie unter Werkzeuge -> Board die Boardverwaltung, siehe Abbildung 3, und geben Sie bei der Suche ESP32 ein, siehe Abbildung 4.

Abbildung 3: Boardverwaltung in der Arduino IDE öffnen

Abbildung 4: Boardbibliothek ESP32 installieren

Wählen Sie die aktuellste Version der Boardbibliothek es32 von Espressif Systems aus, hier 1.0.4, und installieren Sie diese. Nach einem weiteren Neustart sollten die neuen Boards Im Menü Werkzeuge -> Board erscheinen. Hier suchen Sie nach dem Eintrag ESP32 Dev Module und wählen diesen aus, siehe Abbildung 5. Die Standardeinstellungen für dieses Board können Sie so übernehmen.

Abbildung 5:ESP32 Dev Module auswählen

Der Aufbau

Der Aufbau der Schaltung ist mit vier Anschlüssen relativ einfach gehalten. Wir verbinden dabei das Display via I2C und stellen die Stromversorgung mit dem NodeMCU her, siehe Abbildung 6 und Tabelle 2. Gerade für den Testaufbau ist ein Breadboard für die schnelle Verbindung sinnvoll, aber kein Muss.

Abbildung 6: Schematischer Aufbau

 

NodeMCU – Pin OLED-Pin
3.3 V VCC
GND GND
SCL G22
SDA G21

Die Pinbelegung

Bei dem ESP32 DevelopmentBoard ist der GND-Pin neben dem 5V-Pin kein regulärer Ground-Pin, sondern der CMD-Pin. Dieser ist daher nicht zu benutzen!

Die Software 

Ist alles aufgebaut, muss die SSID und das Passwort vom WLAN sowie der API-Key, die Location für openweathermap.org und die letzte Vollmondphase angepasst werden. An den entsprechenden Stellen ist dies mit dem Kommentar TODO markiert. Anschließend kann der Code auf den NodeMCU hochgeladen werden.

// Weatherstation with JSON and moonphase calculation 
// Autor:   Joern Weise
// License: GNU GPl 3.0
// Created: 14. April 2020
// Update:  14. April 2020
//-----------------------------------------------------

#include <Adafruit_SSD1306.h>
#include <Wire.h>
#include <ArduinoJson.h>  
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <TimeLib.h>
#include <WiFiClientSecure.h>
#include <WiFi.h>

//Variables for display
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire);

//Login data for WiFi
const char *ssid     = "WIFI_SSID";   //TODO
const char *password = "WIFI-PASS";   //TODO

//Needed variables for openweathermap.org
const String apiKey = "ADD-YOUR_API";         //TODO
const String location = "ADD-YOUR-LOCATION";  //TODO like "Wiesbaden,de"
const char *clientAdress = "api.openweathermap.org";
int strMinTemp, strMaxTemp, strCurTemp, strFeelTemp, strSunrise, strSunset;
DynamicJsonDocument jsonDoc(2000);

//Variables to get and set time
int utcOffsetInSeconds = 7200;
String daysOfTheWeek[7] = {"So","Mo", "Di", "Mi", "Do", "Fr", "Sa"};
time_t tmLastFullMoon;
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", utcOffsetInSeconds);

bool firstrun = true;
bool bUpdateDisplay = true;

//Setup to init the NodeMCU
void setup() {
  Serial.begin(115200);
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) // Address 0x3C for OLED-Display
  { 
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }
  WiFi.begin(ssid, password);
  while ( WiFi.status() != WL_CONNECTED ) {
    delay ( 500 );
    Serial.print ( "." );
  }
  tmLastFullMoon = tmConvert(YEAR,MONTH,DAY,HOUR,MINUTE,SECOND); //TODO https://www.mondverlauf.de
  Serial.println("");
  Serial.println("Last moon: " + String(tmLastFullMoon));
  Serial.println("Connected to Wifi with IP: " + WiFi.localIP().toString());
  timeClient.begin();
}

void loop() {

  WeatherUpdate();  //Method to update weather
  TimeUpdate();     //Method to update time
  DisplayUpdate();  //Method to update Display
  if(firstrun)
    firstrun = false;
}

//Method to update time and overwrite 
void TimeUpdate()
{
  static bool bUpdateDone;
  if((firstrun || (minute() % 2) == 0) && !bUpdateDone)
  {
    bool bUpdate = timeClient.update();
    setTime(timeClient.getEpochTime());
    Serial.print("Update time: ");
    if(bUpdate){
      Serial.println("Success");
    }
    else{
     Serial.println("Failed");
    }
    bUpdateDone = true;
  }
  if((minute() % 2) != 0)
    bUpdateDone = false;
}

//Method to update weather forecast
void WeatherUpdate()
{
  static bool bUpdateDone;
  if((firstrun || (minute() % 5) == 0) && !bUpdateDone)
  {
    jsonDoc.clear();  //Normally not needed, but sometimes new data will not stored
    String strRequestData = RequestWeather(); //Get JSON as RAW string
    Serial.println("Received data: " + strRequestData);
    //Only do an update, if we got valid data
    if(strRequestData != "")  //Only do an update, if we got valid data
    {
      DeserializationError error = deserializeJson(jsonDoc, strRequestData); //Deserialize string to AJSON-doc
      if (error)
      {
        Serial.print(F("deserializeJson() failed: "));
        Serial.println(error.c_str());
        return;
      }
      //Save data to global var
      strMinTemp = RoundTemp(jsonDoc["main"]["temp_min"].as<double>());
      strMaxTemp = RoundTemp(jsonDoc["main"]["temp_max"].as<double>());
      strCurTemp = RoundTemp(jsonDoc["main"]["temp"].as<double>());
      strFeelTemp = RoundTemp(jsonDoc["main"]["feels_like"].as<double>());
      strSunrise  = jsonDoc["sys"]["sunrise"].as<int>();
      strSunset   = jsonDoc["sys"]["sunset"].as<int>();
      static long timeZone = jsonDoc["timezone"];  //Get latest timezone
      //Print to Serial Monitor
      Serial.println("Min Temp: " + strMinTemp);
      Serial.println("Max Temp: " + strMaxTemp);
      Serial.println("Cur Temp: " + strCurTemp);
      Serial.println("Feel Temp: " + strFeelTemp);
      //Check if timezone changed (sommer- / wintertime
      if(timeZone != utcOffsetInSeconds)
      {
        utcOffsetInSeconds = timeZone;
        timeClient.setTimeOffset(utcOffsetInSeconds);
      }
    }
    bUpdateDone = true;
    bUpdateDisplay = true;
    }
  if((minute() % 5) != 0)
   bUpdateDone = false;
}

//Method for the API-Request to openweathermap.org
String RequestWeather()
{
  WiFiClientSecure client;
  if(!client.connect(clientAdress,443)){  //Changed to https -> Port 443
    Serial.println("Failed to connect");
    return "";
  }
  /*
   * path as followed:
   * /data/2.5/weather? <- static url-path
   * q="location"       <- given location to get weatherforecast
   * &lang=de           <- german description for weather
   * &units=metric      <- metric value in Celcius and hPa
   * appid="apiKey"     <- API-Key from user-account
   */
  String path = "/data/2.5/weather?q=" + location + "&lang=de&units=metric&appid=" + apiKey;

  //Send request to openweathermap.org
  client.print(
    "GET " + path + " HTTP/1.1\r\n" + 
    "Host: " + clientAdress + "\r\n" + 
    "Connection: close\r\n" + 
    "Pragma: no-cache\r\n" + 
    "Cache-Control: no-cache\r\n" + 
    "User-Agent: ESP32\r\n" + 
    "Accept: text/html,application/json\r\n\r\n");

  //Wait for the answer, max 2 sec.
  uint64_t startMillis = millis();
  while (client.available() == 0) {
    if (millis() - startMillis > 2000) {
      Serial.println("Client timeout");
      client.stop();
      return "";
    }
  }

  //If there is an answer, parse answer from openweathermap.org
  String resHeader = "", resBody = "";
  bool receivingHeader = true;
  while(client.available()) {
    String line = client.readStringUntil('\r');
    if (line.length() == 1 && resBody.length() == 0) {
      receivingHeader = false;
      continue;
    }
    if (receivingHeader) {
      resHeader += line;
    }
    else {
      resBody += line;
    }
  }
  
  client.stop(); //Need to stop, otherwise NodeMCU will crash after a while
  return resBody;
}

int RoundTemp(double dTemp)
{
  return int(dTemp + 0.5);
}

//Method to update display content
void DisplayUpdate()
{
  static int iLastMinute;
  if(iLastMinute != minute() || firstrun || bUpdateDisplay){
    display.clearDisplay();
    display.setTextSize(1);               // Normal 1:1 pixel scale
    display.setTextColor(SSD1306_WHITE);  // Draw white text
    display.setCursor(0,0);               // Start at top-left corner
    display.println(String(daysOfTheWeek[weekday()-1]) + " " + GetDigits(day()) +
                    String(".") + GetDigits(month()) + String(".") + year() + 
                    "  " + GetDigits(hour()) + String(":") + GetDigits(minute()));
    display.println(String("Minimum  ") + strMinTemp + String(" C"));
    display.println(String("Maximum  ") + strMaxTemp + String(" C"));
    display.println(String("Aktuell   ") + strCurTemp + String(" C"));  //"Aktuell" german for current
    display.println(String("Gefuehlt  ") + strFeelTemp + String(" C")); //"Gefuehlt" german for feels like
    display.cp437(true);         // Use full 256 char 'Code Page 437' font
    display.write(int16_t(30));
    display.println(" Sonne  " + GetTimeAsString(strSunrise, utcOffsetInSeconds));
    display.write(int16_t(31));
    display.println(" Sonne  " + GetTimeAsString(strSunset, utcOffsetInSeconds));
    display.println(GetMoonPhase());
    display.display();
    //Print all in serial monitor
    Serial.println("---------------------");
    Serial.println("EpochTime: " + String(timeClient.getEpochTime()));
    Serial.println(String(daysOfTheWeek[weekday()-1]) + String(". ") +
                  GetDigits(day()) + String(".") + GetDigits(month()) + 
                  String(".") + year());
    Serial.println(GetDigits(hour()) + String(":") + GetDigits(minute()));
    Serial.println(String("Min: ") + strMinTemp);
    Serial.println(String("Max: ") + strMaxTemp);
    Serial.println(String("Current: ") + strCurTemp);
    Serial.println(String("Feels like: ") + strFeelTemp);
    Serial.println(String("Sunrise: ") + GetTimeAsString(strSunrise, utcOffsetInSeconds));
    Serial.println(String("Sunset:  ") + GetTimeAsString(strSunset, utcOffsetInSeconds));
    Serial.println(String("Moonphase: ") + GetMoonPhase());
    iLastMinute = minute();
    bUpdateDisplay = false;
  }
}

//Method to write given integer to String
//If the value is less than 10, a "0" is placed in front
String GetDigits(int iValue)
{
  String rValue = "";
  if(iValue < 10)
    rValue += "0";
  rValue += iValue;
  return rValue;
}

time_t tmConvert(int iYear, byte byMonth, byte byDay, byte byHour, byte byMinute, byte bySecond)
{
  tmElements_t tmSet;
  tmSet.Year = iYear - 1970;
  tmSet.Month = byMonth;
  tmSet.Day = byDay;
  tmSet.Hour = byHour;
  tmSet.Minute = byMinute;
  tmSet.Second = bySecond;
  return makeTime(tmSet);
}

String GetTimeAsString(int iValue,  int iOffset)
{
  if(iValue > 0){
    iValue += iOffset;
    return GetDigits(hour(iValue))+":"+GetDigits(minute(iValue));
  }
  return "";
}

double MoonUpdate()
{
    double dDaySinceLastFullmoon = (now() / 86400 - tmLastFullMoon / 86400) / 29.53;
    int iToHundret = (dDaySinceLastFullmoon - int(dDaySinceLastFullmoon)) * 100;
    return (double) iToHundret / 100; //Needed to get only 2 decimal places
}

String GetMoonPhase()
{
    static double dMoonphase = MoonUpdate();     //Method to update moonphase
    Serial.println("Calculated moon result: " +String(dMoonphase,3));
    if(dMoonphase == 0.0) 
      dMoonphase = 1.00;
      
    if(dMoonphase < double(0.25))
    {
      return "Abnehmender Mond";
    }
    else if (dMoonphase == 0.25)
    {
      return "Abnehmender Halbmond";
    }
    else if(0.25 < dMoonphase && dMoonphase < 0.50)
    {
      return "Abnehmende Sichel";
    }
    else if(dMoonphase == 0.50)
    {
      return "Neumond";
    }
    else if(0.50 < dMoonphase && dMoonphase < 0.75)
    {
      return "Zunehmende Sichel";
    }
    else if(dMoonphase == 0.75)
    {
      return "Zunehmender Halbmond";
    }
    else if(0.75 < dMoonphase && dMoonphase < 1.00)
    {
      return "Zunehmender Mond";
    }
    else //Moonphase == 1
    {
      return "Vollmond";
    }
}

Info zum Code 

Die Funktion loop() führt lediglich die Methoden zum Updaten der Wetterdaten, Zeit und Displayanzeige aus. Beim Update der Displayanzeige wird gleichzeitig auch die aktuelle Mondphase berechnet.

Ist alles hochgeladen und sind die individuellen Änderungen im Code gemacht zeigt das Display die gewünschten Informationen an.

Ich wünsche Ihnen viel Spaß beim Nachbau.

Jörn Weise

Dieses und weitere Projekte finden sich auf GitHub unter: https://github.com/M3taKn1ght/Blog-Repo

21 Kommentare

Jörn Weise

Jörn Weise

An Gerhard: So ganz kann ich die Änderungen nicht nachvollziehen, Sie können mir diese aber gerne an boardriderxp(at) gmx.net schicken. Im Grunde eliminieren Sie mit einer Codezeile die https-Verbindung (Port 443), da Sie wieder den Port 80 nutzen (http).
Ich kann mir aber gerne Ihre Modifikationen noch einmal genauer ansehen und dann mehr dazu sagen.
Gruß
Jörn Weise

Gerhard

Gerhard

Liebe Freunde, mit der Änderung auf ESP8266 vom 26. Juni 2020 und der Anweisung:
Wire.begin(2, 0); // I2C pins (SDA = GPIO2, SCL = GPIO0)
im setup vor dem öffenen der OLED’s läuft das Ganze auch einwandfrei auf eniem ESP-01
Viel Spass Gerhard

Gerhard

Gerhard

Sehr geehrter Herr Joern Weise!
Ich hab es nun selbst geschafft Ihr wirklich gutes Programm
auf ESP8266 zu portieren, es läuft jetzt und ich es auf verschiedenen
ESP8266 12E mit Modulen versehenen Board’s getestet.
Für alle die es auch erprobrn wollen hier die Änderungen:
Zeile 15 entfernen
16 auf #include //ändern
26 const char* host = “api.openweathermap.org”; //und
27 const char *clientAdress = “api.openweathermap.org”; //einfügen
148 – 152 entfernen, dafür vor
164 if (client.connect(host, 80))
{ // einfügen und nach "Accept….

} else { Serial.println(“Failed to connect”); return ""; } /einfügen Ja das sind alle Änderungendie ich gemacht habe. Noch ein Tipp an alle die ein grösseres Display einbauen, man kann durch z.b. mit strPressure = RoundTemp(jsonDoc[“main”][“pressure”].as()); strHumidity = RoundTemp(jsonDoc[“main”][“humidity”].as()); oder auch andere Parameter mittels dem JASON ganz leicht auch mehrere Parameter holen, muß aber vorher die entsprechenden Strings deffinieren. Enstspechende Information was es alles gibt, gibts auf openweathermap.org Viel Spass Gerhard
Fred Krause

Fred Krause

Vielen Dank Herr Weise.
Ihr Projekt funktionierte bei mir auf Anhieb wie erwartet. Gute Arbeit!
Das Wichtigste ist, man kann viel daraus lernen, wenn man versucht es für die eigenen Wünsche oder andere Displays anzupassen. Probleme macht mir gerade noch das 1.3 Zoll I2C OLED Display (auch von AZ-Del.), weil ich keine passende Bibliothek dafür finde (Controller DS1106). Die u8g2 Bibliothek, die im Quick-Start-Guide beschrieben ist, finde ich nicht. Hat jemand eine Lösung?

Gilbert

Gilbert

Hallo,
Funktioniert sehr gut!
Nur manchmal beim Hochladen den Message:
“A fatal error occurred: Failed to connect to ESP32: Timed out waiting for packet header”
Dann einfach “neu starten”: einige Sekunden auf den Taster “BOOT” zu Beginn des Uploads.
(Die fünf “TODO” nicht vergessen !)
Mit größerem Display könnte man Luftdruck, Wind und andere Daten hinzuzufügen.
Hier unten wass man im Serial Display lesen kann.

EpochTime: 1592408160
Me. le 17/06/2020
Heure : 15:36
Min: 17°C
Max: 18°C
Actuel : 18°C
Ressenti : 18°C
Lever Soleil : 05:42
Coucher Soleil : 21:35
Calcul de la phase de la lune : 0.400
Phase de la Lune : Dernier croissant

Gerhard

Gerhard

Sehr geehrter Herr Joern Weise!
Ein Wirklich schönes Projekt, dass auch auf dem ESP32 einwandfrei läuft.
Ich versuche es nun auf ein ESP-12 (NodeMCU) zu portieren.
Leider gelingt mir das nicht, die Verbindung zum Timeserver geht einwandfrei, zur Wetter.org aber leider nicht. Geändert habe ich nur die WiFi.h auf WiFi8266.h.
Vielleicht haben Sie eine Tipp für mich wie ich das zum laufen bekomme.
Besten Dank für Ihre Antwort im Voraus.
Gerhard

Joern Weise

Joern Weise

@Juergen Babenhauserheide: Damit belegen Sie aber nur unnötig Speicher für eine globale Variable und ist so auch nicht gedacht gewesen. In dem Artikel wird ja beschrieben, dass Sie die letzte Vollmondphase über die angegebene Website ermitteln sollen. Initial werden Sie dadurch immer eine falsche Prognose bekommen.

@Randy P: Die Fehlermeldung sieht danach aus, dass Sie vergessen haben AJSON runterzuladen, wie es am Anfang vom Artikel beschrieben ist. Dadurch versucht die IDE einen Kopierkonstruktor für ein Array von char zu nutzen, obwohl er einen anderen Datentyp dafür nutzen sollte. Bitte die fehlende Bibliothek nachladen.

Randy P

Randy P

bekomme folgende Fehlermeldung
no match for ‘operator[]’ (operand types are ‘ArduinoJson670_0_0::DynamicJsonDocument’ and ‘const char 5’)
bitte um Hilfe

Joern Weise

Joern Weise

@Jochen Pils
Das sind auch erst mal nur Platzhalter. Sie müssen, wie in der Anleitung beschrieben, an dieser Stelle ein Datum für die letzte Vollmondphase angeben. Steht aber genauso auch noch einmal als Kommentar im Quellcode. Andernfalls klappt es natürlich nicht, die aktuelle mondphase zu berechnen.

Juergen Babenhauserheide

Juergen Babenhauserheide

Hallo,
mit dem Original-Code erhalte ich auch die Fehlermeldung: “‘YEAR’ was not declared in this scope”.
Mit dem folgenden Zusatz am Anfang von void setup():
int YEAR;
byte MONTH, DAY, HOUR, MINUTE, SECOND;
funktioniert der Programm-Code einwandfrei.

MfG
Juergen B.

Jochen Pils

Jochen Pils

Schöne Projektidee!
Zeil 56 macht mir Probleme, so dass der Sketch nicht läuft, da bekomme ich Fehlermeldungen:

Wetterstation_MOndphase:56:29: error: ‘YEAR’ was not declared in this scope
Wetterstation_MOndphase:56:34: error: ‘MONTH’ was not declared in this scope
Wetterstation_MOndphase:56:40: error: ‘DAY’ was not declared in this scope
Wetterstation_MOndphase:56:44: error: ‘HOUR’ was not declared in this scope
Wetterstation_MOndphase:56:49: error: ‘MINUTE’ was not declared in this scope
Wetterstation_MOndphase:56:56: error: ‘SECOND’ was not declared in this scope

Kann jemand helfen?
Danke, Jochen

Jürgen Reimann

Jürgen Reimann

Hallo, hab es gleich ausprobiert und läuft problemlos. Danke für die tolle Vorlage

Da ich als Zugang zum Internet auf meiner FritzBox das Gastnetz benutze hatte ich Probleme mit dem NTP Connect, das ist auf dem Gastnetz nämlich gesperrt. Das lässt sich aber leicht lösen, denn viele Internet Access Points haben einen eigenen NTP Server, so auch die FritzBox. Man trägt man einfach statt “europe.pool.ntp.org” die Adresse “fritz.box” ein, vorher unter Benutzereinstellungen noch NTP aktivieren.

Joern Weise

Joern Weise

@Thomas Bierschwale
Das wundert mich dann aber sehr, weil ich in meiner Anleitung genau das gleiche Display verwenden. Gerade wenn nur “Schnee” angezeigt wird, deutet es darauf hin, dass die i2c-Verbindung nicht korrekt ist. Daher wäre nun die Frage, welchen MicroController Sie nutzen und ob das korrekte Pinout für den MicoController genutzt wird. “Schnee” ist immer ein Anzeichen dafür, dass das Display zar Strom bekommt, aber eben keine Daten.

Gilbert Lerch

Gilbert Lerch

Vielen dank für die Antwort. Ich werde den ESP 32 nehmen.

Thomas Bierschwale

Thomas Bierschwale

Ich habe das Projekt nachgebaut. Es läuft soweit, im Monitore werden richtige Werte angezeigt. Leider zeigt das Display nur “Schnee” an. Das Display ist genau dieses hier: https://az-delivery.de/products/0-96zolldisplay. Normalerweise benutze ich immer die Lib , wie auch im Ebook dazu angegeben.
Irgendwo auf diesen Seiten hatte ich mal was zur Abhilfe gesehen, finde es aber nicht mehr. Was kann ich tun?

Joern Weise

Joern Weise

@Anyone: Das freut mich. Die Bauteile sind recht übersichtlich gehalten, sollte daher schnell und unkompliziert aufgebaut sein.

@Ulrich Klaas: Für jedes Board gibt es in der Kategorie “Kostenlose eBooks” eine Anleitung, wie man das ESP zum Leben erweckt. Mittlerweile gibt es einen Haufen an ESP32-Module, mit den verschiedensten Unterschieden. Ich habe mich bewusst für dieses Model entschieden, da es einen 5V-Ausgang hat, der gerade bei den LCD-Displays gebraucht wird, da 3,3V einfach zu wenig ist. Ich bin kein Freund eine Spannungsquelle so zu splitten, dass ich diverse Geräte damit mit Strom versorgen kann, wobei es auch hier eine gute lösung von AZ-Delivery gibt https://az-delivery.de/collections/basis-produkte/products/mb102-breadboard. Meist ist es bei den ESP’s so, dass man mehr Pinouts hat oder mehr Schnittstellen oder sie sehr kompakt sind. Ähnlich wie beim Arduino ist der Grundaufbau “quelloffen” es kann sich theoretisch also jeder selbst etwas basteln. Zudem unterscheidet man noch darin, ob sie eine Programmierschnittstelle haben oder eben nicht. Ich glaube das Thema hier zur Gänze zu klären schaffe ich nicht, dafür gibt es diverse (englischsprachige) Foren, wo es Diskussionen dazu gibt.

@michael: Natürlich können sie auch andere Displays nutzen, wichtig dabei ist aber, dass Sie die richtige Schnittstelle und Libary für das jeweilige Display einsetzen. Da es auf dem Markt eine große Anzahl an Displays gibt, diese unterschiedlich angesteuert werden, kann es durchaus sein, dass Sie den Code stark modifizieren müssen, um ein ähnliches Ergebnis zu bekommen. Ggf. wird es auf meiner GitHub-Seite eine Update mit dem dem 1,8" Display von AZ-Delivery geben (https://az-delivery.de/collections/displays/products/1-8-zoll-spi-tft-display). Dazu muss ich aber dann die Zeit finden.

@Sven Hesse: Danke für dieses schnelle und positive Feedback. Das freut mich als Blogger und auch das Team von AZ-Delivery, wenn solche Projekte positiven anklang finden.

@Gilbert: Geht theoretisch auch mit einem ESP 12, Sie brauchen aber unbedingt die i2c-Schnittstelle, damit das Display auch geht. Zusätzlich ist die verwendete JSON-Libary sehr groß, das heißt auch der Platzbedarf vom Coder sollte nicht unterschätzt werde.

Gilbert

Gilbert

Sehr interessant!
Frage: Geht es auch mit einem ESP8266NodeMCU ESP 12E?
Danke.

Sven Hesse

Sven Hesse

Hi,

vielen Dank für das Tutorial.
Direkt getestet und läuft.

Kleiner Hinweis an @michael: Man kann natürlich auch größere Displays verwenden, dazu findet man auf Github auch reichlich Informationen.

michael

michael

Hallo, tolles Teil. Frage kannn man cu einen anderen “Monitor” anschließen?
zB.: 1,77 Zoll SPI TFT-Display und 128×160 Pixeln für Arduino
Danke und schönes WE

Ulrich Klaas

Ulrich Klaas

Hallo, gibt es eigentlich irgendwo eine vernünftige Übersicht was man für welches Board im Boardverwalter einstellen muss ? Bisher wurde für alle ESP32-Nodemcu Projekte hier das Board ESP32_Firebeetle angegeben. Hier jetzt für das selbe Board ESP32-DEV. Wo sind die Unterschieden in den Einstellungen ? Bei den einfachen Arduinotypen einschließlich Tinies und auch Mighty-Core (also 644 und 1284) blicke ich ja durch, Aber bei den ganzen EPSs ist das ja inflationär.

Anyone

Anyone

Ein sehr schönes Projekt. Das wird umgehend nachgebastelt.

Einen Kommentar hinterlassen

Alle Kommentare werden vor der Veröffentlichung moderiert