Bei diesem Projekt soll die Uhrzeit, das Datum und die neuesten Schlagzeilen als Laufschrift auf einer LED Matrix dargestellt werden. Datum und Uhrzeit sollen von einem Zeitserver aktualisiert werden. Die Schlagzeilen erhalten Sie von einem sogenannten RSS-Feed. Das ist ein Dienst, den verschiedene Web Server wie zum Beispiel tagesschau.de anbieten. Dieser Dienst nutzt als Übertragungsprotokoll http bzw. https, die Daten werden aber nicht wie üblich im HTML Format, sondern als XML, also ohne Layout-Informationen, geliefert. Hier ein Beispiel wie solche XML Daten aussehen: <rss version="2.0"> <channel> <title>tagesschau.de - Die Nachrichten der ARD</title> <link>http://www.tagesschau.de</link> <description>tagesschau.de</description> <language>de</language> <docs>http://blogs.law.harvard.edu/tech/rss</docs> <ttl>30</ttl> <item> <title> Spahn hofft auf Corona-Impfstoff für Kinder bis Sommer </title> <link> https://www.tagesschau.de/inland/coronavirus-impfung-kinder-101.html </link> <description>Der Impfplan für Deutschland steht - doch gerade für die Jüngsten fehlt bisher ein Wirkstoff. Bundesgesundheitsminister Spahn setzt darauf, dass bis zum Sommer auch ein Vakzin für Kinder und Jugendliche entwickelt wird. </description> <guid> https://www.tagesschau.de/inland/coronavirus-impfung-kinder-101.html </guid> <category/> </item> <item> <title> Impfstoffe in der EU: Warum die Verhandlungen so lange stockten </title> Der Hauptblock hat den XML-Tag <channel>. Nach einigen allgemeinen Informationen folgen die einzelnen Schlagzeilen, jeweils in einem Block mit XML-Tag <item>. Innerhalb dieses Blocks gibt es einen Block mit dem XML-Tag <title>. Den Inhalt dieses Blocks wollen wir zur Anzeige bringen. Benötigte Hardware Anzahl Bauteil Anmerkung 1 ESP32 D1 Mini NodeMCU 1 MAX7219 8x32 4 in 1 Dot Matrix LED 1 Lochrasterplatte 4x6 cm 1 5-polige abgewinkelte Stiftleiste liegt Matrix bei 2 Federleiste 8-polig für Controller 1 5-poliges Verbindungskabel Buchse zu Buchse liegt Matrix bei 4 Gehäuseteile aus dem 3-D Drucker, Schraube 2.2 mm zur Befestigung des Displays Schaltung Die LED Matrix wird einfach mit dem SPI Bus des ESP32 verbunden. Der Datenausgang des ESP32 MOSI (GPIO23) wird mit DIN der Matrix verbunden. Der Taktausgang des ESP32 CLK (GPIO18) wird mit dem Takteingang der Matrix verbunden. Als Chip-Select wird der GPIO16 des ESP32 verwendet, der wird mit dem Anschluss CS der Matrix verbunden. Die Versorgung der Matrix erfolgt mit 5V, das ist kein Problem für den ESP32, da alle hier verwendeten Anschlüsse als Ausgang verwendet werden und daher keine höheren Spannungen von der Matrix abbekommen können. Programm Zusätzlich zum ESP32 Package, benötigen Sie vier Bibliotheken. Die Bibliothek TinyXML verwenden wir um die gewünschten Informationen aus den empfangenen XML-Daten zu extrahieren. Die Bibliothek hat nur drei Funktionen. void init (uint8_t* buffer, uint16_t maxbuflen, XMLcallback XMLcb); Diese Funktion wird in der Setup Funktion aufgerufen. Der Parameter buffer zeigt auf ein Byte Array zur Aufnahme von temporären Daten, der Parameter maxbuflen gibt die Größe des Buffers an. Der Parameter XMLcallback zeigt auf eine Callbackfunktion void XML_callback(uint8_t statusflags, char* tagName, uint16_t tagNameLen, char* data, uint16_t dataLen) die immer dann aufgerufen wird, wenn ein XML-Element komplett verarbeitet wurde. Der Parameter statusflags gibt an welche Daten zur Verfügung gestellt werden. Da wir uns für den Inhalt eines XML-Tags interessieren, werden wir nur den Status STATUS_TAG_TEXT verwenden. Der Parameter tagName zeigt auf ein Zeichenarray, das den vollständigen XML Pfad zum Element entält. Für den Titel eines Items des Newsfeed ist das dann der Pfad „/rss/channel/item/title“. Der Parameter tagNameLen liefert die Länge des Pfades. Der Parameter data zeigt ebenfalls auf ein Zeichen Array, das die Daten enthält. Der letzte Parameter dataLen liefert die Größe des Daten Arrays. void reset(); Diese Funktion setzt alle internen Zeiger zurück. Sie sollte immer aufgerufen werden, ehe neue XML-Daten eingelesen werden. void processChar(uint8_t ch); Diese Funktion sendet ein Zeichen der XML-Daten, die verarbeitet werden sollen, an den XML-Parser. Diese Funktion muss nacheinander für jedes empfangene Zeichen aufgerufen werden. Führt das übertragene Zeichen zum Abschluss eines XML-Blocks, so wird die Callbackfunktion mit den entsprechenden Daten aufgerufen. Die Bibliothek LG_Matrix_Print dient zur Anzeige von Strings auf der LED-Matrix. Die folgenden Funktionen werden im Sketch verwendet: void setEnabled(bool enabled); Der Zugriff auf die LED-Matrix wird freigegeben oder gesperrt. void setIntensity(uint8_t level); Diese Funktion verändert die Helligkeit der Anzeige. Werte zwischen 0 und 15 sind erlaubt. Normalerweise ist 1 vollkommen ausreichend. void display(); Der Inhalt des Anzeigespeichers wird an die Matrix übertragen und damit sichtbar. void clear() Der Anzeigespeicher wird gelöscht. int printText(int start, String text, boolean isUTF8 = true); Diese Funktion wandelt den im Parameter text angegebenen String, beginnend mit dem n-ten Zeichen, mit Hilfe des internen Zeichensatzes in das entsprechende Bitmuster im Anzeigespeicher um. Bei welchem Zeichen mit der Ausgabe begonnen werden soll, wird im Parameter start angegeben. Der optionale Parameter isUTF8 schaltet den internen Codewandler ein oder aus. void ticker(String message, uint16_t wait); Die Funktion ermöglicht es, sehr einfach eine Laufschrift zu realisieren. Der Parameter message zeigt auf einen String, der den anzuzeigenden Text enthält. Der zweite Parameter wait gibt die Zeit in Millisekunden an, die gewartet werden soll, ehe der Text um ein Pixel weitergeschoben wird. Um alles Weitere kümmert sich die Bibliothek. boolean updateTicker(); Diese Funktion muss in der Loop Funktion aufgerufen werde, damit die Laufschrift aktualisiert wird. Die Bibliothek enthält noch weitere Funktionen, die aber in diesem Sketch nicht verwendet werden. Die Bibliothek WebConfig dient dazu, sowohl die WLAN Zugangsdaten als auch andere Einstellungen über einen Browser konfigurieren zu können. Die Konfiguration wird im Flash Filesystem SPIFFS gespeichert und bleibt auch nach dem Abschalten des Mikrocontrollers erhalten.Von dieser Bibliothek werden nur ein paar wenige Funktionen in diesem Sketch verwendet. Eine detaillierte Beschreibung findet man unter https://github.com/GerLech/WebConfig/blob/master/README.md oder in meinem Smarthome-Buch. Die WebConfig Bibliothek benötigt die ArduinoJSON Bibliothek (nicht Arduino_JSON), diese muss also auch installiert werden, wird aber nicht im Sketch inkludiert. Der Sketch: #include <LG_Matrix_Print.h> //Bibliothek für die Matrixanzeige#include <HTTPClient.h> //Web Client für den Empfang des RSS-Feeds#include <SPIFFS.h> //Filesystem zum Speichern der Konfiguration#include <WebServer.h> //Webserver für die Konfiguration#include <ESPmDNS.h> //Multicast DNS für Namensauflösung#include <WebConfig.h> //Bibliothek zur Konfiguration über eine Webseite #include <TinyXML.h> //XML-Interpreter zum Lesen des RSS-Feed#define LEDMATRIX_CS_PIN 16 //CS Pin der LED Matrix// Anzahl der 8x8 LED Segmente#define LEDMATRIX_SEGMENTS 4// Timeout zum Lesen des RSS-Feed in Sekunden#define READ_TIMEOUT 10//Prameter für das Konfigurations-FormularString params = "[" "{" "'name':'ssid'," "'label':'Name des WLAN'," "'type':"+String(INPUTTEXT)+"," "'default':''" "}," "{" "'name':'pwd'," "'label':'WLAN Passwort'," "'type':"+String(INPUTPASSWORD)+"," "'default':''" "}," "{" "'name':'rssUrl'," "'label':'RSS Feed URL'," "'type':"+String(INPUTTEXT)+"," "'default':''" "}," "{" "'name':'ntp'," "'label':'NTP Server'," "'type':"+String(INPUTTEXT)+"," "'default':'de.pool.ntp.org'" "}," "{" "'name':'maxNews'," "'label':'Schlagzeilen'," "'type':"+String(INPUTNUMBER)+"," "'min':1,'max':10," "'default':'1'" "}," "{" "'name':'intens'," "'label':'Helligkeit'," "'type':"+String(INPUTNUMBER)+"," "'min':1,'max':15," "'default':'1'" "}," "{" "'name':'disptime'," "'label':'Anzeigedauer (s)'," "'type':"+String(INPUTNUMBER)+"," "'min':1,'max':30," "'default':'5'" "}" "]";//Web Server InstanzWebServer server;//Web Konfigurations InstanzWebConfig conf;//LED Matrix InstanzLG_Matrix_Print lmd(LEDMATRIX_SEGMENTS, LEDMATRIX_CS_PIN);//XML Interpreter InstanzTinyXML xml;//Globale Variablenuint32_t last = 0; //Zeit der letzten Aktion in msuint8_t buffer[2000]; //Buffer für XML-InterpreterString news[10]; //Speicher für Nachrichten (Max. 10)uint8_t newsCnt = 0; //Anzahl der aktuellen Nachrichten im Speicheruint8_t curNews = 0; //Index der gerade angezeigten Nachrichtuint8_t dispMode = 0; //Art der Anzeige 0=Zeit, 1=Datum, 2=News;//Diese Funktion wird vom XML-Interpreter aufgerufen, //wenn ein XML-Tag gelesen wurde//tagName enthält den vollständigen XML-Pfad des Tags, //data den Inhalt des Tagsvoid XML_callback(uint8_t statusflags, char* tagName, uint16_t tagNameLen, char* data, uint16_t dataLen) { if (statusflags & STATUS_TAG_TEXT) { //Serial.println(tagName); //wenn wir einen Titel-Tag finden, //und die maximale Anzahl der Meldungen noch //nicht erreicht ist, wird die Meldung gespeichert //und der Zähler erhöht if (strcasecmp(tagName,"/rss/channel/item/title")==0) { data[dataLen] = '\0'; if (newsCnt < conf.getInt("maxNews")) { //Die maximale Anzahl der Nachrichten wird //aus der Konfiguration gelesen news[newsCnt] = data; newsCnt++; } } }}//WLAN Verbindung initialisieren boolean initWiFi() { boolean connected = false; WiFi.mode(WIFI_STA); Serial.print("Verbindung zu "); Serial.print(conf.values[0]); Serial.println(" herstellen"); if (conf.values[0] != "") { //wenn eine SSID bekannt ist, //wird versucht eine Verbindung herzustellen WiFi.begin(conf.values[0].c_str(),conf.values[1].c_str()); uint8_t cnt = 0; while ((WiFi.status() != WL_CONNECTED) && (cnt<20)){ delay(500); Serial.print("."); cnt++; } Serial.println(); if (WiFi.status() == WL_CONNECTED) { Serial.print("IP-Adresse = "); Serial.println(WiFi.localIP()); connected = true; } } //konnte keine Verbindung hergestellt werden, //wird ein Accesspoint gestartet //der Accesspoint hat kein Passwort. Über die IP-Adresse //192.168.4.1 kann die Konfiguration durchgeführt werden if (!connected) { WiFi.mode(WIFI_AP); WiFi.softAP(conf.getApName(),"",1); } return connected;}//Diese Funktion wird aufgerufen, //wenn der Webserver eine Anfrage erhältvoid handleRoot() { //Die Anfrage wird an die Konfigurationsinstanz weitergegeben conf.handleFormRequest(&server);}//Neue Nachrichten vom RSS-Feed lesenvoid getNews() { String error = ""; if(WiFi.status()== WL_CONNECTED){ //HTTP Client HTTPClient http; Serial.print("[HTTP] begin...\n"); //url aus der Konfiguration http.begin(conf.getValue("rssUrl")); Serial.print("[HTTP] GET...\n"); // Anfrage abschicken int httpCode = http.GET(); // httpCode ist im Fall eines Fehlers negativ if(httpCode > 0) { // HTTP Antwort vom Server erhalten Serial.printf("[HTTP] GET... code: %d\n", httpCode); if(httpCode == HTTP_CODE_OK) { String payload = http.getString(); newsCnt = 0; xml.reset(); for (uint16_t i=0; i<payload.length(); i++) xml.processChar(payload[i]); } else{ error = "Server antwortet mit "+String(httpCode); } } else { error = "Server antwortet mit "+http.errorToString(httpCode); } http.end(); if (newsCnt > 0) { //Falls Nachrichten empfangen wurden, //wird die erste Nachricht angezeigt curNews = 0; lmd.ticker(news[0],100); } } else { initWiFi(); error = "Keine Internetverbindung!"; } if (error != "") { news[0] = error; newsCnt = 1; curNews = 0; lmd.ticker(news[0],100); }}//Die aktuelle Uhrzeit wird angezeigt wenn start wahr istvoid showTime(boolean start){ if (start) { Serial.println("Time Start"); last = millis(); char sttime[10]; struct tm timeinfo; dispMode=0; if(getLocalTime(&timeinfo)){ strftime(sttime, sizeof(sttime), "%H:%M ", &timeinfo); lmd.printText(0,String(sttime)); lmd.display(); } else { //liefert die RTC keine Werte so wird ??:?? angezeigt lmd.printText(0,"??:?? "); lmd.display(); } } else { //Wenn das Ende der Anzeigedauer erreicht wurde, //wird auf Datum umgeschaltet if ((millis()-last) > (conf.getInt("disptime")*1000)) { showDate(true); } }}//Das aktuelle Datum wird angezeigt wenn start wahr istvoid showDate(boolean start) { if (start) { Serial.println("Date Start"); last = millis(); char sttime[10]; struct tm timeinfo; dispMode = 1; if(getLocalTime(&timeinfo)){ strftime(sttime, sizeof(sttime), "%d.%b ", &timeinfo); lmd.printText(0,String(sttime)); lmd.display(); } else { //liefert die RTC keine Werte so wird ??.??? angezeigt lmd.printText(0,"??.???"); lmd.display(); } } else { //Wenn das Ende der Anzeigedauer erreicht wurde, //wird auf News umgeschaltet if ((millis()-last) > (conf.getInt("disptime")*1000)) { showNews(true); } }}//Eine Nachricht wird angezeigt wenn start wahr ist //werden Nachrichten vom Server geholt und //die erste Nachricht angezeigt sonst die nächstevoid showNews(boolean start) { if (start) { Serial.println("News Start"); if (curNews == 0) { getNews(); } else { lmd.ticker(news[curNews],100); } dispMode = 2; } else { //der Ticker wird aktualisiert. Wenn das Ende der Nachricht erreicht //wurde wird auf die nächste Nachricht weitergeschaltet. //Die Anzeige wird auf Zeitanzeige umgeschaltet if (!lmd.updateTicker()) { curNews++; if (curNews >= newsCnt) curNews = 0; showTime(true); } } }//Wird einmal beim Start des Programms ausgeführtvoid setup() { //Filesystem initialisieren und //falls noch nicht geschehen, formatieren SPIFFS.begin(true); //Serielle Schnittstelle starten Serial.begin(115200); //XML-Interpreter initialisieren xml.init((uint8_t *)buffer, sizeof(buffer), &XML_callback); //Formular zur Webkonfiguration vorbereiten conf.setDescription(params); //Konfiguration falls vorhanden aus dem Filesystem lesen conf.readConfig(); // Anzeige initialisieren lmd.setEnabled(true); lmd.setIntensity(conf.getInt("intens")); // 0 = low, 10 = high lmd.clear(); lmd.display(); //WLAN Verbindung herstellen initWiFi(); //Multicast DNS starten char dns[30]; sprintf(dns,"%s.local",conf.getApName()); if (MDNS.begin(dns)) { Serial.println("MDNS responder gestartet"); } //Webserver starten server.on("/",handleRoot); server.begin(80); delay(1000); //Wenn eine Internetvebindung besteht, die Echtzeituhr des ESP32 //mit Daten vom Zeitserver starten if (WiFi.status()== WL_CONNECTED) configTzTime("CET-1CEST,M3.5.0/03,M10.5.0/03", conf.getValue("ntp")); showTime(true);}void loop() { if (millis() < last) last=millis(); //falls ein Überlauf auftrat nach etwa 50 Tagen //Anfragen des Webserver behandeln server.handleClient(); //Anzeige aktualisieren switch (dispMode) { case 0: showTime(false); break; case 1: showDate(false); break; case 2: showNews(false); break; }} Der Sketch zum Herunterladen Nach dem Start kann das Programm noch keine Verbindung zum WLAN herstellen. Daher wird ein Access-Point gestartet. Seine SSID ist die MAC-Adresse des ESP32. In der WLAN-Einstellung des Smartphones sollte man die SSID sehen. Man kann nun dieses Netzwerk auswählen und sich damit verbinden. Das Netzwerk benötigt kein Passwort. Eventuell meldet das Smartphone, dass keine Internetverbindung möglich ist und ob man das gewählte Netzwerk beibehalten will. Hier auf Beibehalten tippen. Nun kann man einen Browser starten und die URL 192.168.4.1 aufrufen. Es sollte die Konfigurationsseite der Matrix-Uhr erscheinen. Der Name des Accesspoints ist die MAC-Adresse und kann beliebig geändert werden. Es folgen die Zugangsdaten zum WLAN. Für die URL des RSS-Feed kann man zum Beispiel https://www.tagesschau.de/newsticker.rdf eingeben. Der NTP Server kann so bleiben. Es kann aber auch zum Beispiel die Fritzbox mit fritz.box gesetzt werden. Es folgen die maximale Anzahl der Schlagzeilen, die Helligkeit für das Display und die Zeit in Sekunden, wie lange Uhrzeit und Datum angezeigt werden sollen. Zum Schluss auf SAVE und RESTART tippen. Die Matrixuhr wird neu gestartet und sollte sich jetzt mit dem WLAN verbinden. In der Ausgabe am seriellen Monitor kann man die Anmeldung verfolgen. Die Konfigurationsseite erreicht man dann über die IP-Adresse, die der Matrix-Uhr vom Router zugewiesen wurde. Sie wird im seriellen Monitor der Arduino IDE angezeigt. Einbau in ein Gehäuse Wer einen 3D-Drucker zur Verfügung hat, kann sich ein passendes Gehäuse drucken. Es werden insgesamt viert Teile benötigt. Ein Unterteil Uhr_unten.stl, ein Deckel Uhr_deckel.stl und zwei Halterungen für das Display Uhr_halter.stl. Nun zum Zusammenbau. Als erstes wird die Lochrasterplatten mit den beiden Federleisten und der abgewinkelten Stiftleiste bestückt. Auf der Rückseite wird die folgende Verdrahtung ausgeführt. Jetzt können Sie den Controller auf die Federleisten stecken und die Matrix mit dem Controller über das 5-polige Kabel verbinden. Hier ist auf die richtige Reihenfolge der Pins zu achten. Nach dem Zusammenstecken ist der richtige Zeitpunkt, um nochmal alles zu überprüfen und einen Probelauf durchzuführen, ehe man mit dem Einbau beginnt. Nächster Schritt ist der Einbau der Matrix und der Lochrasterplatte im Gehäuse. Damit die Matrix befestigt werden kann, müssen zuerst die beiden Halterungen an der Matrix angebracht werden. Nun können Sie die Matrix im Deckel und die Lochrasterplatte im Unterteil befestigen. So das wars. Die Matrix Uhr mit News-Feed ist fertig. Viel Spaß beim Basteln. Der Beitrag als PDF Programmerweiterung mit zusätzlicher Wetteranzeige #include <LG_Matrix_Print.h> //Bibliothek für die Matrixanzeige #include <HTTPClient.h> //Web Client für den Empfang des RSS-Feeds #include <SPIFFS.h> //Filesystem zum Speichern der Konfiguration #include <WebServer.h> //Webserver für die Konfiguration #include <ESPmDNS.h> //Multicast DNS für Namensauflösung #include <WebConfig.h> //Bibliothek zur Konfiguration über eine Webseite #include <TinyXML.h> //XML-Interpreter zum Lesen des RSS-Feed #include <ArduinoJson.h> //JSON Bibliothek #define LEDMATRIX_CS_PIN 16 //CS Pin der LED Matrix // Anzahl der 8x8 LED Segmente #define LEDMATRIX_SEGMENTS 4 // Timeout zum Lesen des RSS-Feed in Sekunden #define READ_TIMEOUT 10 //Prameter für das Konfigurations-Formular String params = "[" "{" "'name':'ssid'," "'label':'Name des WLAN'," "'type':" + String(INPUTTEXT) + "," "'default':''" "}," "{" "'name':'pwd'," "'label':'WLAN Passwort'," "'type':" + String(INPUTPASSWORD) + "," "'default':''" "}," "{" "'name':'rssUrl'," "'label':'RSS Feed URL'," "'type':" + String(INPUTTEXT) + "," "'default':''" "}," "{" "'name':'ntp'," "'label':'NTP Server'," "'type':" + String(INPUTTEXT) + "," "'default':'de.pool.ntp.org'" "}," "{" "'name':'maxNews'," "'label':'Schlagzeilen'," "'type':" + String(INPUTNUMBER) + "," "'min':1,'max':10," "'default':'1'" "}," "{" "'name':'intens'," "'label':'Helligkeit'," "'type':" + String(INPUTNUMBER) + "," "'min':1,'max':15," "'default':'1'" "}," "{" "'name':'disptime'," "'label':'Anzeigedauer (s)'," "'type':" + String(INPUTNUMBER) + "," "'min':1,'max':30," "'default':'5'" "}," "{" "'name':'weather'," "'label':'Wetter'," "'type':" + String(INPUTCHECKBOX) + "," "'default':'0'" "}," "{" "'name':'weatherkey'," "'label':'Open Weather Key'," "'type':" + String(INPUTTEXT) + "," "'default':''" "}," "{" "'name':'plz'," "'label':'Postleitzahl'," "'type':" + String(INPUTNUMBER) + "," "'min':1,'max':99999," "'default':''" "}," "{" "'name':'country'," "'label':'Land'," "'type':"+String(INPUTRADIO)+"," "'options':[" "{'v':'de','l':'Deutschland'}," "{'v':'at','l':'Österreich'}," "{'v':'ch','l':'Schweiz'}]," "'default':'de'" "}" "]"; const String windDirection[] = {"N","NNO","NO","ONO","O","OSO","SO","SSO","S","SSW","SW","WSW","W","WNW","NW","NNW"}; //Web Server Instanz WebServer server; //Web Konfigurations Instanz WebConfig conf; //LED Matrix Instanz LG_Matrix_Print lmd(LEDMATRIX_SEGMENTS, LEDMATRIX_CS_PIN); //XML Interpreter Instanz TinyXML xml; //Globale Variablen uint32_t last = 0; //Zeit der letzten Aktion in ms uint8_t buffer[2000]; //Buffer für XML-Interpreter String news[10]; //Speicher für Nachrichten (Max. 10) uint8_t newsCnt = 0; //Anzahl der aktuellen Nachrichten im Speicher uint8_t curNews = 0; //Index der gerade angezeigten Nachricht String weatherData[8]; //Wetter Daten uint8_t weatherCnt = 8; //Anzahl der aktuellen Wetterzeilen im Speicher uint8_t curWeather = 0; //Index der gerade angezeigte Wetterzeile uint8_t dispMode = 0; //Art der Anzeige 0=Zeit, 1=Datum, 2=News, 3=Wetter; boolean waitForConfig = false;//Ist true wenn kein gültiges WLAN existiert; uint32_t lastWeather = 0; //Zeitstempel für Wetter //Diese Funktion wird vom XML-Interpreter aufgerufen, wenn ein XML-Tag gelesen wurde //tagName enthält den vollständigen XML-Pfad des Tags, data den Inhalt des Tags void XML_callback(uint8_t statusflags, char* tagName, uint16_t tagNameLen, char* data, uint16_t dataLen) { if (statusflags & STATUS_TAG_TEXT) { //Serial.println(tagName); //wenn wir einen Titel-Tag finden, und die maximale Anzahl der Meldungen noch //nicht erreicht ist, wird die Meldung gespeichert und der Zähler erhöht if (strcasecmp(tagName, "/rss/channel/item/title") == 0) { data[dataLen] = '\0'; if (newsCnt < conf.getInt("maxNews")) { //Die maximale Anzahl der Nachrichten wird aus der Konfiguration gelesen news[newsCnt] = data; newsCnt++; } } } } //WLAN Verbindung initialisieren boolean initWiFi() { boolean connected = false; if (!waitForConfig) { WiFi.mode(WIFI_STA); Serial.print("Verbindung zu "); Serial.print(conf.values[0]); Serial.println(" herstellen"); if (conf.values[0] != "") { //wenn eine SSID bekannt ist, wird versucht eine Verbindung herzustellen WiFi.begin(conf.values[0].c_str(), conf.values[1].c_str()); uint8_t cnt = 0; while ((WiFi.status() != WL_CONNECTED) && (cnt < 20)) { delay(500); Serial.print("."); cnt++; } Serial.println(); } if (WiFi.status() == WL_CONNECTED) { Serial.print("IP-Adresse = "); Serial.println(WiFi.localIP()); connected = true; } } //konnte keine Verbindung hergestellt werden, wird ein Accesspoint gestartet //der Accesspoint hat kein Passwort. Über die IP-Adresse 192.168.4.1 kann //die Konfiguration durchgeführt werden if (!connected) { WiFi.mode(WIFI_AP); WiFi.softAP(conf.getApName(), "", 1); waitForConfig = true; } return connected; } //Diese Funktion wird aufgerufen, wenn der Webserver eine Anfrage erhält void handleRoot() { //Die Anfrage wird an die Konfigurationsinstanz weitergegeben conf.handleFormRequest(&server); } //Neue Nachrichten vom RSS-Feed lesen void getNews() { String error = ""; if (WiFi.status() == WL_CONNECTED) { //HTTP Client HTTPClient http; Serial.print("[HTTP] begin...\n"); //url aus der Konfiguration http.begin(conf.getValue("rssUrl")); Serial.print("[HTTP] GET...\n"); // Anfrage abschicken int httpCode = http.GET(); // httpCode ist im Fall eines Fehlers negativ if (httpCode > 0) { // HTTP Antwort vom Server erhalten Serial.printf("[HTTP] GET... code: %d\n", httpCode); if (httpCode == HTTP_CODE_OK) { String payload = http.getString(); payload.replace("&quot;","\""); payload.replace("&#034;","\""); payload.replace("&#035;","#"); newsCnt = 0; xml.reset(); for (uint16_t i = 0; i < payload.length(); i++) xml.processChar(payload[i]); } else { error = "Server antwortet mit " + String(httpCode); } } else { error = "Server antwortet mit " + http.errorToString(httpCode); } http.end(); if (newsCnt > 0) { //Falls Nachrichten empfangen wurden, wird die erste Nachricht angezeigt curNews = 0; lmd.ticker(news[0], 100); } } else { if (!waitForConfig) initWiFi(); error = "Keine Internetverbindung!"; } if (error != "") { news[0] = error; newsCnt = 1; curNews = 0; lmd.ticker(news[0], 100); } } //Wetter von open Weather lesen void getWeather() { float temp1,temp2; String error = ""; if (WiFi.status() == WL_CONNECTED) { //HTTP für Openweather String endpoint = String("https://api.openweathermap.org/data/2.5/weather?zip=") +conf.getValue("plz") + String(",")+ conf.getValue("country") + String("&lang=de&units=metric&appid=")+conf.getValue("weatherkey"); Serial.println(endpoint); HTTPClient http; weatherCnt = 0; http.begin(endpoint); //Specify the URL int httpCode = http.GET(); //Make the request // httpCode ist im Fall eines Fehlers negativ if (httpCode > 0) { // HTTP Antwort vom Server erhalten Serial.printf("[HTTP] GET... code: %d\n", httpCode); if (httpCode == HTTP_CODE_OK) { String payload = http.getString(); Serial.println(payload); const size_t capacity = 56 * JSON_ARRAY_SIZE(1) + JSON_ARRAY_SIZE(2) + JSON_ARRAY_SIZE(8) + JSON_ARRAY_SIZE(48) + 14 * JSON_OBJECT_SIZE(1) + 66 * JSON_OBJECT_SIZE(4) + 9 * JSON_OBJECT_SIZE(6) + 35 * JSON_OBJECT_SIZE(10) + 13 * JSON_OBJECT_SIZE(11) + 4 * JSON_OBJECT_SIZE(13) + 4 * JSON_OBJECT_SIZE(14) + JSON_OBJECT_SIZE(16) + 9190; DynamicJsonDocument doc(capacity); DeserializationError jerror = deserializeJson(doc, payload); if (jerror) { Serial.print(F("deserializeJson() hat nicht funktioniert: ")); Serial.println(jerror.c_str()); error = String(jerror.c_str()); return; } weatherData[0] = String("Das Wetter in ")+doc["name"].as<String>()+String(" ist: "); weatherData[1] = doc["weather"][0]["description"].as<String>(); temp1 = doc["main"]["temp"]; weatherData[2] = String("Temperatur ")+String(temp1,1)+String(" °C"); temp1 = doc["main"]["temp_min"]; temp2 = doc["main"]["temp_max"]; weatherData[3] = String("min ") + String(temp1,1) + String(" °C max ") + String(temp2,1) + String(" °C"); temp1 = doc["main"]["pressure"]; weatherData[4] = String("Luftdruck ") + String(temp1,0) + String(" hPa"); temp1 = doc["main"]["humidity"]; weatherData[5] = String("Luftfeuchtigkeit ") + String(temp1,0) + String(" %"); temp1 = doc["wind"]["speed"]; temp2 = doc["wind"]["deg"]; int sector = ((temp2 + 11) / 22.5 - 1); if (sector > 15) sector = 15; weatherData[6] = String("Wind ") + String(temp1,1) + String(" m/s aus ") + windDirection[sector]; if (doc["wind"].containsKey("gust")) { temp1 = doc["wind"]["gust"]; weatherData[6] += String(" Böen mit ")+String(temp1,1) +String(" m/s"); } temp1 = doc["clouds"]["all"]; weatherData[7] = String("Bewölkung ") + String(temp1,0) + String(" %"); weatherCnt = 8; } else { error = "Server antwortet mit " + String(httpCode); } } else { error = "Server antwortet mit " + http.errorToString(httpCode); } http.end(); if (weatherCnt > 0) { //Falls Daten empfangen wurden, wird die erste Nachricht angezeigt curWeather = 0; lmd.ticker(weatherData[0], 100); } } else { if (!waitForConfig) initWiFi(); error = "Keine Internetverbindung!"; } if (error != "") { weatherData[0] = error; weatherCnt = 1; curWeather = 0; lmd.ticker(weatherData[0], 100); } } //Die aktuelle Uhrzeit wird angezeigt wenn start wahr ist void showTime(boolean start) { if (start) { Serial.println("Time Start"); last = millis(); char sttime[10]; struct tm timeinfo; dispMode = 0; if (getLocalTime(&timeinfo)) { strftime(sttime, sizeof(sttime), "%H:%M ", &timeinfo); lmd.printText(0, String(sttime)); lmd.display(); } else { //liefert die RTC keine Werte so wird ??:?? angezeigt lmd.printText(0, "??:?? "); lmd.display(); } } else { //Wenn das Ende der Anzeigedauer erreicht wurde, wird auf Datum umgeschaltet if ((millis() - last) > (conf.getInt("disptime") * 1000)) { showDate(true); } } } //Das aktuelle Datum wird angezeigt wenn start wahr ist void showDate(boolean start) { if (start) { Serial.println("Date Start"); last = millis(); char sttime[10]; struct tm timeinfo; dispMode = 1; if (getLocalTime(&timeinfo)) { strftime(sttime, sizeof(sttime), "%d.%b ", &timeinfo); lmd.printText(0, String(sttime)); lmd.display(); } else { //liefert die RTC keine Werte so wird ??.??? angezeigt lmd.printText(0, "??.???"); lmd.display(); } } else { //Wenn das Ende der Anzeigedauer erreicht wurde, wird auf News umgeschaltet if ((millis() - last) > (conf.getInt("disptime") * 1000)) { if (conf.getBool("weather")) { showWeather(true); } else { showNews(true); } } } } //Eine Nachricht wird angezeigt wenn start wahr ist werden Nachrichten //vm Server geholt und die erste Nachricht angezeigt sonst die nächste void showNews(boolean start) { if (start) { Serial.println("News Start"); if (curNews == 0) { getNews(); } else { lmd.ticker(news[curNews], 100); } dispMode = 2; } else { //der Ticker wird aktualisiert. Wenn das Ende der Nachricht erreicht //wurde wird auf die nächste Nachricht weitergeschaltet. Die Anzeige //wird auf Zeitanzeige umgeschaltet if (!lmd.updateTicker()) { curNews++; if (curNews >= newsCnt) { curNews = 0; showTime(true); } else { lmd.ticker(news[curNews],100); } } } } //Eine Nachricht wird angezeigt wenn start wahr ist werden Nachrichten //vm Server geholt und die erste Nachricht angezeigt sonst die nächste void showWeather(boolean start) { if (start) { Serial.println("Weather Start"); if (curWeather == 0) { uint32_t now = millis(); //Wetter nur alle 15 min aktualisieren if ((lastWeather == 0) || (lastWeather > now) || ((now - lastWeather) > 900000)) { getWeather(); lastWeather = now; } else { lmd.ticker(weatherData[curWeather],100); } } else { lmd.ticker(weatherData[curWeather], 100); } dispMode = 3; } else { //der Ticker wird aktualisiert. Wenn das Ende der Nachricht erreicht //wurde wird auf die nächste Nachricht weitergeschaltet. Die Anzeige //wird auf Zeitanzeige umgeschaltet if (!lmd.updateTicker()) { curWeather++; if (curWeather >= weatherCnt) { curWeather = 0; showNews(true); } else { lmd.ticker(weatherData[curWeather],100); } } } } //Wird einmal beim Start des Programms ausgeführt void setup() { //Filesystem initialisieren und falls noch nicht geschehen, formatieren SPIFFS.begin(true); //Serielle Schnittstelle starten Serial.begin(115200); //XML-Interpreter initialisieren xml.init((uint8_t *)buffer, sizeof(buffer), &XML_callback); //Formular zur Webkonfiguration vorbereiten conf.setDescription(params); //Konfiguration falls vorhanden aus dem Filesystem lesen conf.readConfig(); // Anzeige initialisieren lmd.setEnabled(true); lmd.setIntensity(conf.getInt("intens")); // 0 = low, 10 = high lmd.clear(); lmd.display(); //WLAN Verbindung herstellen initWiFi(); //Multicast DNS starten char dns[30]; sprintf(dns, "%s.local", conf.getApName()); if (MDNS.begin(dns)) { Serial.println("MDNS responder gestartet"); } //Webserver starten server.on("/", handleRoot); server.begin(80); delay(1000); //Wenn eine Internetvebindung besteht, die Echtzeituhr des ESP32 //mit Daten vom Zeitserver starten if (WiFi.status() == WL_CONNECTED) configTzTime("CET-1CEST,M3.5.0/03,M10.5.0/03", conf.getValue("ntp")); showTime(true); } void loop() { if (WiFi.status() != WL_CONNECTED) initWiFi(); if (millis() < last) last = millis(); //falls ein Überlauf auftrat nach etwa 50 Tagen //Anfragen des Webserver behandeln server.handleClient(); //Anzeige aktualisieren switch (dispMode) { case 0: showTime(false); break; case 1: showDate(false); break; case 2: showNews(false); break; case 3: showWeather(false); break; } } Der Sketch zum Herunterladen Zusätzlich kann jetzt auch das aktuelle Wetter von https://openweathermap.org/ angezeigt werden. Alle Einstellungen können über die Konfigurationsseite eingestellt werden. Mit der Checkbox kann die Anzeige des Wetters (zwischen Datum und Nachrichten) ein und ausgeschaltet werden. Für den Empfang der Wetterdaten ist ein Key erforderlich, den man mit einer kostenlosen Registrierung bei openweathermap.org erhalten kann. Postleitzahl und Land erklären sich von selbst. Nachtrag da die Nutzerdaten im Flash des ESP32 gespeichert werden, bleiben sie dort auch erhalten. Ein neues Hochladen des Sketches ändert daran nichts. Für den ESP8266 kann man für den Upload in den Boardeinstellungen "erase flash" einstellen, um den Speicher wieder komplett zu löschen. Das geht für den ESP32 nicht. Als Lösung können Sie das esptool.py verwenden. Für die Ausführung muss Python installiert sein. Laden Sie die Datei herunter und geben Sie folgende Zeile ein, während der ESP per USB verbunden ist: python esptool.py erase_flash Das Tool kann auch über pip installiert werden. Eventuell müssen Sie den ESP32 manuell in den Flashmode versetzen (BOOT Taste). Alternativ können Sie unter Windows das ESP Flash Download Tool verwenden. Eine Anleitung dazu finden Sie hier. Danach sind alle Daten gelöscht und Sie können den Sketch neu hochladen, sollten Sie einmal Probleme haben. Update: Aufgrund einer Leseranfrage aus den Kommentaren wurde der Sketch durch einige Funktionen ergänzt, um zu Zeigen, wie man das Webinterface oder die Anzeige verändern bzw. erweitern kann: Zeit/Schritt in ms wird über die Web-Konfiguration eingestellt Zeit und Datum werden zentriert wenn genug Platz, ist wird beim Datum auch das Jahr angezeigt Newsreader Wetter Sketch V2 Download #include <LG_Matrix_Print.h> //Bibliothek für die Matrixanzeige #include <HTTPClient.h> //Web Client für den Empfang des RSS-Feeds #include <SPIFFS.h> //Filesystem zum Speichern der Konfiguration #include <WebServer.h> //Webserver für die Konfiguration #include <ESPmDNS.h> //Multicast DNS für Namensauflösung #include <WebConfig.h> //Bibliothek zur Konfiguration über eine Webseite #include <TinyXML.h> //XML-Interpreter zum Lesen des RSS-Feed #include <ArduinoJson.h> //JSON Bibliothek #define LEDMATRIX_CS_PIN 16 //CS Pin der LED Matrix // Anzahl der 8x8 LED Segmente #define LEDMATRIX_SEGMENTS 4 // Timeout zum Lesen des RSS-Feed in Sekunden #define READ_TIMEOUT 10 //Prameter für das Konfigurations-Formular String params = "[" "{" "'name':'ssid'," "'label':'Name des WLAN'," "'type':" + String(INPUTTEXT) + "," "'default':''" "}," "{" "'name':'pwd'," "'label':'WLAN Passwort'," "'type':" + String(INPUTPASSWORD) + "," "'default':''" "}," "{" "'name':'rssUrl'," "'label':'RSS Feed URL'," "'type':" + String(INPUTTEXT) + "," "'default':''" "}," "{" "'name':'ntp'," "'label':'NTP Server'," "'type':" + String(INPUTTEXT) + "," "'default':'de.pool.ntp.org'" "}," "{" "'name':'maxNews'," "'label':'Schlagzeilen'," "'type':" + String(INPUTNUMBER) + "," "'min':1,'max':10," "'default':'1'" "}," "{" "'name':'intens'," "'label':'Helligkeit'," "'type':" + String(INPUTNUMBER) + "," "'min':1,'max':15," "'default':'1'" "}," "{" "'name':'disptime'," "'label':'Anzeigedauer (s)'," "'type':" + String(INPUTNUMBER) + "," "'min':1,'max':30," "'default':'5'" "}," "{'name':'stepduration'," "'label':'Schrittdauer (ms)'," "'type':" + String(INPUTNUMBER) + "," "'min':10,'max':1000," "'default':'100'" "}," "{" "'name':'weather'," "'label':'Wetter'," "'type':" + String(INPUTCHECKBOX) + "," "'default':'0'" "}," "{" "'name':'weatherkey'," "'label':'Open Weather Key'," "'type':" + String(INPUTTEXT) + "," "'default':''" "}," "{" "'name':'plz'," "'label':'Postleitzahl'," "'type':" + String(INPUTNUMBER) + "," "'min':1,'max':99999," "'default':''" "}," "{" "'name':'country'," "'label':'Land'," "'type':"+String(INPUTRADIO)+"," "'options':[" "{'v':'de','l':'Deutschland'}," "{'v':'at','l':'Österreich'}," "{'v':'ch','l':'Schweiz'}]," "'default':'de'" "}" "]"; const String windDirection[] = {"N","NNO","NO","ONO","O","OSO","SO","SSO","S","SSW","SW","WSW","W","WNW","NW","NNW"}; //Web Server Instanz WebServer server; //Web Konfigurations Instanz WebConfig conf; //LED Matrix Instanz LG_Matrix_Print lmd(LEDMATRIX_SEGMENTS, LEDMATRIX_CS_PIN); //XML Interpreter Instanz TinyXML xml; //Globale Variablen uint32_t last = 0; //Zeit der letzten Aktion in ms uint8_t buffer[2000]; //Buffer für XML-Interpreter String news[10]; //Speicher für Nachrichten (Max. 10) uint8_t newsCnt = 0; //Anzahl der aktuellen Nachrichten im Speicher uint8_t curNews = 0; //Index der gerade angezeigten Nachricht String weatherData[8]; //Wetter Daten uint8_t weatherCnt = 8; //Anzahl der aktuellen Wetterzeilen im Speicher uint8_t curWeather = 0; //Index der gerade angezeigte Wetterzeile uint8_t dispMode = 0; //Art der Anzeige 0=Zeit, 1=Datum, 2=News, 3=Wetter; boolean waitForConfig = false;//Ist true wenn kein gültiges WLAN existiert; uint32_t lastWeather = 0; //Zeitstempel für Wetter //Diese Funktion wird vom XML-Interpreter aufgerufen, wenn ein XML-Tag gelesen wurde //tagName enthält den vollständigen XML-Pfad des Tags, data den Inhalt des Tags void XML_callback(uint8_t statusflags, char* tagName, uint16_t tagNameLen, char* data, uint16_t dataLen) { if (statusflags & STATUS_TAG_TEXT) { //Serial.println(tagName); //wenn wir einen Titel-Tag finden, und die maximale Anzahl der Meldungen noch //nicht erreicht ist, wird die Meldung gespeichert und der Zähler erhöht if (strcasecmp(tagName, "/rss/channel/item/title") == 0) { data[dataLen] = '\0'; if (newsCnt < conf.getInt("maxNews")) { //Die maximale Anzahl der Nachrichten wird aus der Konfiguration gelesen news[newsCnt] = data; newsCnt++; } } } } //WLAN Verbindung initialisieren boolean initWiFi() { boolean connected = false; if (! waitForConfig) { WiFi.mode(WIFI_STA); Serial.print("Verbindung zu "); Serial.print(conf.values[0]); Serial.println(" herstellen"); if (conf.values[0] != "") { //wenn eine SSID bekannt ist, wird versucht eine Verbindung herzustellen WiFi.begin(conf.values[0].c_str(), conf.values[1].c_str()); uint8_t cnt = 0; while ((WiFi.status() != WL_CONNECTED) && (cnt < 20)) { delay(500); Serial.print("."); cnt++; } Serial.println(); if (WiFi.status() == WL_CONNECTED) { Serial.print("IP-Adresse = "); Serial.println(WiFi.localIP()); connected = true; } } } //konnte keine Verbindung hergestellt werden, wird ein Accesspoint gestartet //der Accesspoint hat kein Passwort. Über die IP-Adresse 192.168.4.1 kann //die Konfiguration durchgeführt werden if (!connected) { WiFi.mode(WIFI_AP); WiFi.softAP(conf.getApName(), "", 1); waitForConfig = true; } return connected; } //Diese Funktion wird aufgerufen, wenn der Webserver eine Anfrage erhält void handleRoot() { //Die Anfrage wird an die Konfigurationsinstanz weitergegeben conf.handleFormRequest(&server); } //Neue Nachrichten vom RSS-Feed lesen void getNews() { String error = ""; if (WiFi.status() == WL_CONNECTED) { //HTTP Client HTTPClient http; Serial.print("[HTTP] begin...\n"); //url aus der Konfiguration http.begin(conf.getValue("rssUrl")); Serial.print("[HTTP] GET...\n"); // Anfrage abschicken int httpCode = http.GET(); // httpCode ist im Fall eines Fehlers negativ if (httpCode > 0) { // HTTP Antwort vom Server erhalten Serial.printf("[HTTP] GET... code: %d\n", httpCode); if (httpCode == HTTP_CODE_OK) { String payload = http.getString(); payload.replace("&quot;","\""); payload.replace("&#034;","\""); payload.replace("&#035;","#"); newsCnt = 0; xml.reset(); for (uint16_t i = 0; i < payload.length(); i++) xml.processChar(payload[i]); } else { error = "Server antwortet mit " + String(httpCode); } } else { error = "Server antwortet mit " + http.errorToString(httpCode); } http.end(); if (newsCnt > 0) { //Falls Nachrichten empfangen wurden, wird die erste Nachricht angezeigt curNews = 0; lmd.ticker(news[0], conf.getInt("stepduration")); } } else { if (!waitForConfig) initWiFi(); error = "Keine Internetverbindung!"; } if (error != "") { news[0] = error; newsCnt = 1; curNews = 0; lmd.ticker(news[0], conf.getInt("stepduration")); } } //Wetter von open Weather lesen void getWeather() { float temp1,temp2; String error = ""; if (WiFi.status() == WL_CONNECTED) { //HTTP für Openweather String endpoint = String("https://api.openweathermap.org/data/2.5/weather?zip=") +conf.getValue("plz") + String(",")+ conf.getValue("country") + String("&lang=de&units=metric&appid=")+conf.getValue("weatherkey"); Serial.println(endpoint); HTTPClient http; weatherCnt = 0; http.begin(endpoint); //Specify the URL int httpCode = http.GET(); //Make the request // httpCode ist im Fall eines Fehlers negativ if (httpCode > 0) { // HTTP Antwort vom Server erhalten Serial.printf("[HTTP] GET... code: %d\n", httpCode); if (httpCode == HTTP_CODE_OK) { String payload = http.getString(); Serial.println(payload); const size_t capacity = 56 * JSON_ARRAY_SIZE(1) + JSON_ARRAY_SIZE(2) + JSON_ARRAY_SIZE(8) + JSON_ARRAY_SIZE(48) + 14 * JSON_OBJECT_SIZE(1) + 66 * JSON_OBJECT_SIZE(4) + 9 * JSON_OBJECT_SIZE(6) + 35 * JSON_OBJECT_SIZE(10) + 13 * JSON_OBJECT_SIZE(11) + 4 * JSON_OBJECT_SIZE(13) + 4 * JSON_OBJECT_SIZE(14) + JSON_OBJECT_SIZE(16) + 9190; DynamicJsonDocument doc(capacity); DeserializationError jerror = deserializeJson(doc, payload); if (jerror) { Serial.print(F("deserializeJson() hat nicht funktioniert: ")); Serial.println(jerror.c_str()); error = String(jerror.c_str()); return; } weatherData[0] = String("Das Wetter in ")+doc["name"].as<String>()+String(" ist: "); weatherData[1] = doc["weather"][0]["description"].as<String>(); temp1 = doc["main"]["temp"]; weatherData[2] = String("Temperatur ")+String(temp1,1)+String(" °C"); temp1 = doc["main"]["temp_min"]; temp2 = doc["main"]["temp_max"]; weatherData[3] = String("min ") + String(temp1,1) + String(" °C max ") + String(temp2,1) + String(" °C"); temp1 = doc["main"]["pressure"]; weatherData[4] = String("Luftdruck ") + String(temp1,0) + String(" hPa"); temp1 = doc["main"]["humidity"]; weatherData[5] = String("Luftfeuchtigkeit ") + String(temp1,0) + String(" %"); temp1 = doc["wind"]["speed"]; temp2 = doc["wind"]["deg"]; int sector = ((temp2 + 11) / 22.5 - 1); if (sector > 15) sector = 15; weatherData[6] = String("Wind ") + String(temp1,1) + String(" m/s aus ") + windDirection[sector]; if (doc["wind"].containsKey("gust")) { temp1 = doc["wind"]["gust"]; weatherData[6] += String(" Böen mit ")+String(temp1,1) +String(" m/s"); } temp1 = doc["clouds"]["all"]; weatherData[7] = String("Bewölkung ") + String(temp1,0) + String(" %"); weatherCnt = 8; } else { error = "Server antwortet mit " + String(httpCode); } } else { error = "Server antwortet mit " + http.errorToString(httpCode); } http.end(); if (weatherCnt > 0) { //Falls Daten empfangen wurden, wird die erste Nachricht angezeigt curWeather = 0; lmd.ticker(weatherData[0], conf.getInt("stepduration")); } } else { if (!waitForConfig) initWiFi(); error = "Keine Internetverbindung!"; } if (error != "") { weatherData[0] = error; weatherCnt = 1; curWeather = 0; lmd.ticker(weatherData[0], conf.getInt("stepduration")); } } //Die aktuelle Uhrzeit wird angezeigt wenn start wahr ist void showTime(boolean start) { if (start) { Serial.println("Time Start"); last = millis(); char sttime[10]; struct tm timeinfo; dispMode = 0; String msg; uint16_t pos = 0; uint16_t len = 0; uint16_t pixels = LEDMATRIX_SEGMENTS * 8; lmd.clear(); if (getLocalTime(&timeinfo)) { strftime(sttime, sizeof(sttime), "%H:%M", &timeinfo); msg=String(sttime); len=lmd.pixelLength(msg); if (len < pixels) pos = (pixels - len) /2; lmd.printText(pos,msg); lmd.display(); } else { //liefert die RTC keine Werte so wird ??:?? angezeigt lmd.printText(0, "??:??"); lmd.display(); } } else { //Wenn das Ende der Anzeigedauer erreicht wurde, wird auf Datum umgeschaltet if ((millis() - last) > (conf.getInt("disptime") * 1000)) { showDate(true); } } } //Das aktuelle Datum wird angezeigt wenn start wahr ist void showDate(boolean start) { if (start) { Serial.println("Date Start"); last = millis(); char sttime[20]; String msg; uint16_t pos = 0; uint16_t len = 0; struct tm timeinfo; uint16_t pixels = LEDMATRIX_SEGMENTS * 8; dispMode = 1; lmd.clear(); if (getLocalTime(&timeinfo)) { strftime(sttime, sizeof(sttime), "%d.%b %Y", &timeinfo); msg = String(sttime); len = lmd.pixelLength(msg); if (len > pixels) { strftime(sttime, sizeof(sttime), "%d.%b ", &timeinfo); lmd.printText(0, String(sttime)); } else { pos = (pixels-len) /2; lmd.printText(pos,msg); } lmd.display(); } else { //liefert die RTC keine Werte so wird ??.??? angezeigt lmd.printText(0, "??.???"); lmd.display(); } } else { //Wenn das Ende der Anzeigedauer erreicht wurde, wird auf News umgeschaltet if ((millis() - last) > (conf.getInt("disptime") * 1000)) { if (conf.getBool("weather")) { showWeather(true); } else { showNews(true); } } } } //Eine Nachricht wird angezeigt wenn start wahr ist werden Nachrichten //vm Server geholt und die erste Nachricht angezeigt sonst die nächste void showNews(boolean start) { if (start) { Serial.println("News Start"); if (curNews == 0) { getNews(); } else { lmd.ticker(news[curNews], conf.getInt("stepduration")); } dispMode = 2; } else { //der Ticker wird aktualisiert. Wenn das Ende der Nachricht erreicht //wurde wird auf die nächste Nachricht weitergeschaltet. Die Anzeige //wird auf Zeitanzeige umgeschaltet if (!lmd.updateTicker()) { curNews++; if (curNews >= newsCnt) { curNews = 0; showTime(true); } else { lmd.ticker(news[curNews],conf.getInt("stepduration")); } } } } //Eine Nachricht wird angezeigt wenn start wahr ist werden Nachrichten //vm Server geholt und die erste Nachricht angezeigt sonst die nächste void showWeather(boolean start) { if (start) { Serial.println("Weather Start"); if (curWeather == 0) { uint32_t now = millis(); //Wetter nur alle 15 min aktualisieren if ((lastWeather == 0) || (lastWeather > now) || ((now - lastWeather) > 900000)) { getWeather(); lastWeather = now; } else { lmd.ticker(weatherData[curWeather],conf.getInt("stepduration")); } } else { lmd.ticker(weatherData[curWeather], conf.getInt("stepduration")); } dispMode = 3; } else { //der Ticker wird aktualisiert. Wenn das Ende der Nachricht erreicht //wurde wird auf die nächste Nachricht weitergeschaltet. Die Anzeige //wird auf Zeitanzeige umgeschaltet if (!lmd.updateTicker()) { curWeather++; if (curWeather >= weatherCnt) { curWeather = 0; showNews(true); } else { lmd.ticker(weatherData[curWeather],conf.getInt("stepduration")); } } } } //Wird einmal beim Start des Programms ausgeführt void setup() { //Filesystem initialisieren und falls noch nicht geschehen, formatieren SPIFFS.begin(true); //Serielle Schnittstelle starten Serial.begin(115200); //XML-Interpreter initialisieren xml.init((uint8_t *)buffer, sizeof(buffer), &XML_callback); //Formular zur Webkonfiguration vorbereiten conf.setDescription(params); //Konfiguration falls vorhanden aus dem Filesystem lesen conf.readConfig(); // Anzeige initialisieren lmd.setEnabled(true); lmd.setIntensity(conf.getInt("intens")); // 0 = low, 10 = high lmd.clear(); lmd.display(); //WLAN Verbindung herstellen initWiFi(); //Multicast DNS starten char dns[30]; sprintf(dns, "%s.local", conf.getApName()); if (MDNS.begin(dns)) { Serial.println("MDNS responder gestartet"); } //Webserver starten server.on("/", handleRoot); server.begin(80); delay(1000); //Wenn eine Internetvebindung besteht, die Echtzeituhr des ESP32 //mit Daten vom Zeitserver starten if (WiFi.status() == WL_CONNECTED) configTzTime("CET-1CEST,M3.5.0/03,M10.5.0/03", conf.getValue("ntp")); showTime(true); } void loop() { if (WiFi.status() != WL_CONNECTED) initWiFi(); if (millis() < last) last = millis(); //falls ein Überlauf auftrat nach etwa 50 Tagen //Anfragen des Webserver behandeln server.handleClient(); //Anzeige aktualisieren switch (dispMode) { case 0: showTime(false); break; case 1: showDate(false); break; case 2: showNews(false); break; case 3: showWeather(false); break; } }