Newsreader auf Matrixdisplay - AZ-Delivery

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-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'"
  "}"
  "]";

//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
uint8_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 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;
  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ä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();
        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 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)) {
      showNews(true);
    }
  }
}

//Eine Nachricht wird angezeigt wenn start wahr ist
//werden Nachrichten vom 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);
    }
  }
}

//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 (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.
ACHTUNG:
Die URL für den ARD Dienst hat sich geändert. Es muss jetzt
lauten.
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.


wetter konfiguration

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;
  }
}


ACHTUNG!

In allen Software Varianten ist ein Fehler. Am Ende der Funktion initWifi() muss die Bedingung

if (!connected && !waitForConfig ) {

statt

if (!connected) {

lauten. Sonst wird im Falle, dass keine gültige SSID vorliegt, dauernd der Accesspoint neu initialisiert und eine Verbindung ist nicht möglich.

DisplaysEsp-32Esp-8266

46 commenti

Jos Wich

Jos Wich

ich habe eine gute und eine schlechte Nachricht.
Ja, meine vorher genannte Lösung funktioniert, aber nur auf einem frischen Rechner. Ich habe einen weiteren Test gemacht mit einem WEMOS D1 MINI ESP32, nachdem die DOIT DEVKIT Version einwandfrei lief. Auf meinem Desktop Rechner habe ich beide Versionen Arduino IDE wohl zerschossen. Weder die 1.8.19 noch die 2.3.2 konnte ich benutzen. Ich habe sogar beide deinstalliert, den Rechner neu gebootet und dann zuerst die 2.3.2 neu installiert. Irgendwo in den tiefsten Tiefen der Registry vermute ich noch Reste der alten Installationen.
Aber das D1 MINI Board funktioniert jetzt genauso wie der große Bruder DOIT DEVKIT. Also: mit Thonny Micropython flashen, das zeigt dann ob das Board funktioniert. Danach die einfache Version (ohne Wetter) installieren. Ja es kommen Warnungen das ArduinoJSON veraltet ist. Aber das Programm läuft hervorragend mit dem ESP32 Board Treiber 2.0.17. Bitte nicht die Version 3.0.1 wählen, dann klappt es nicht. Danach konnte ich auch meine Wetter Version “rüberbügeln” und bin jetzt zufrieden.

Viel Erfolg !

Jos

Jos Wich

Jos Wich

Hallo zusammen,
für Alle die, wie ich, dieses hervorragende Projekt plötzlich nicht mehr neu installieren können, folgt hier mein Work-Around:
In der Arduino IDE wurden die ESP32 Treiber von Espressif Systems “erneuert” auf 3.0.1
Dumm nur, dass danach Einiges nicht mehr kompatibel mit den älteren Programmen ist …
Ich habe ein neues WIN10 Home aufgesetzt. Die Arduino IDE 2.3.2 installiert. Allerdings habe ich nicht die neuesten ESP32 Treiber genommen, sondern die 2.0.17 installiert. Die weitere Reihenfolge hat bei mir funktioniert:
das bereits benutzte ESP32 Board mit MicroPython von Thonny geflashed (scheint tiefer im Speicher einzusteigen). Das ist der Beweis dass das Board funktioniert oder nicht. Danach die erste, einfache Version des Newsreaders geladen, bevor ich anschließend die Wetter Version laden konnte. Es mag nicht besonders professionell klingen, aber diese Methode hat bei mir zum Erfolg geführt.
Ich hoffe ich erspare unsere Community weitere schlaflose Nächte und verlorene Stunden …

Jos

Gerald Lechner

Gerald Lechner

Ich habe festgestellt, dass sich die XML Struktur geändert hat. In der Funktion XML_callback sollte die Zeile zur Tag-Auswahl daher statt
if (strcasecmp(tagName, “/rss/channel/item/title”) == 0) ) {
neu:
if ((strcasecmp(tagName, “/rss/channel/item/title”) == 0) ||
(strcasecmp(tagName, “/item/title”) == 0)) {
lauten
@Klaus Baumann: die URL muss
https://www.tagesschau.de/infoservices/alle-meldungen-100%7Erdf.xml
lauten. Das Sonderzeichen “~” muss für HTML mit %7E codiert werden.
@Jan: Direkt ist das leider derzeit nicht möglich. Ich müsste dazu die Bibliothek LG_Matrix_Print mit einer entsprechenden Funktion erweitern. Ich kann aber nicht versprechen diese Erweiterung vorzunehmen.

Klaus Baumann

Klaus Baumann

Hallo nochmal…ich habe jetzt mehrere rrs .xml-Feeds getestet…manche gehen, andere nicht…schade…der Tagesschau-Feed funktioniert leider nicht. Focus Online z.B. funktioniert. Offenbar hat die Tagesschau .xml irgend einen Unterschied in der Syntax, der unserem Reader nicht schmeckt… :(

Klaus Baumann

Klaus Baumann

Guten Abend, ich habe das Projekt grade zusammengebastelt und es funktioniert auch soweit (Uhrzeit/Datum/Wetter). Allerdings bekomme ich statt dem Newsfeed ständig die Meldung “Server antwortet mit 404”. Ich habe den aktualisierten Link “https://www.tagesschau.de/infoservices/alle-meldungen-100~rss2.xml” für den Newsfeed eingegeben…leider auch hier dieselbe Fehlermeldung.
Hat irgendwer nen Tip für mich?

Jan

Jan

Hallo Herr Lechner,
tolles Projekt, habe es nachgebaut und es funktioniert einwandfrei. In das Gehäuse, welches ich mir ausgesucht habe (thingiverse) passt die LED-Matrix nur in eine Richtung rein, sodass die Laufschrift nun auf dem Kopf steht. Ist es möglich das Ganze innerhalb des Codes um 180° zu drehen?
Grüße Jan

Gerald Lechner

Gerald Lechner

In allen Software Varianten ist ein Fehler. Am Ende der Funktion initWifi() muss die Bedingung
if (!connected && !waitForConfig ) {
statt
if (!connected) {
lauten. Sonst wird im Falle, dass keine gültige SSID vorliegt, dauernd der Accesspoint neu initialisiert und eine Verbindung ist nicht möglich.
Außerdem hat sich die URL für den ARD Dienst geändert. Es muss jetzt
https://www.tagesschau.de/infoservices/alle-meldungen-100%7Erdf.xml
lauten.

LutzB

LutzB

Hallo, mir geht’ s genauso , wie Dieter. Alles hat perfekt funktioniert inkusive Wetter.
Jetzt will sich das Teil nicht mit dem Wlan verbinden. 192.168.4.1 klappt auch nicht.
es erscheinen nur :
Bin ich zu blöd kann es sein, dass der neue Router dran schuld ist?
Hat jemand eine Idee ?

Dieter

Dieter

Hallo Herr Lechner + Community,
nach einiger Zeit mit Fremdgehen des Matrix-Tickers als Luftmessgerät, wollte ich gern den Ticker reaktivieren. Leider funktioniert er nicht mehr, es kommt nur die Meldung: “Server antwortet mit connection refused”. Was ist passiert? Am Sketch habe ich nichts verändert. Wer schön wenn mir jemand helfen könnte?!
MfG.

Volker W.

Volker W.

Hallo Gerald,
hoffe du liest das noch, vielen dank für diesen tollen Beitrag.
Habe alles zusammen gebaut und funktionierte nach ein paar Versuchen auch wunderbar!
3x 4-1 Max7219, wegen des besseren lesens.
Habe auch schon verschiedene RSS Feeds ausprobiert, klappt!
Würde aber gerne noch die Geschwindigkeit beim RSS Feed per Konfigurationsfenster ändern können,
bei der Zeit und Datum wäre eine Mittelstellung der Anzeige nett (sind ja 3×4 Module)
Kannst du mir da weiterhelfen
Vielen Dank im Voraus
Volker W.

Jos Wich

Jos Wich

Hallo Community !
anfangs total begeistert von dieser Lösung, stehe ich jetzt vor einem größeren Problem. Ich habe 2x 4fach MAX7219 mit einander verbunden. Als diese Variante stabil lief, habe ich nicht nur die Nachrichten aus der Tagesschau gelesen, nein auch n-tv.de/rss lieferte brav Daten, sogar die Limburger Zeitung aus den Niederlanden…..

Frage: stehe ich mittlerweile auf einer Blacklist wenn alle Anzeigen eine " … connection refused …. " zurück geben ? Wenn ja, wie komme ich da wieder runter ?

R. Gerlinger

R. Gerlinger

Hallo,
habe 2x MAX7219 8×32 4 in 1 Dot Matrix genommen, da ist die Laufschrift besser zu lesen
und habe die Uhrzeit und das Datum in die mitte gesetzt.

HerGro HerrMann

HerGro HerrMann

Hallo Gerald
Heute per Mail Dein Hinweis auf die Erweiterung des Matrix Readers Weather bekommen.
Hab mich gleich bei OpenWeatherMap.org regristriert und key bezogen,
Dein *.ino file installiert und meine Hochachtung: lief auf Anhieb, sehr beeindruckend!!
(Trotz vieler “Warnings” die man nur beim “Überprüfen” angezeigt bekommt,
beim Download allerdings nicht !!??)
Danke für Deine Arbeit
HerrMann

Gerald Lechner

Gerald Lechner

Die Änderung für # bitte nicht aus meinem Kommentar kopieren sondern direkt über Tastatur eingeben. Die Zeile im Kommentar enthält Sonderzeichen, die die Arduino IDE nicht verarbeiten kann.

Rudolf Gerlinger

Rudolf Gerlinger

Hallo Her Lechner,

es ist mir furchtbar Peinlich, ich habe gedacht ich bin fertig mit der Matrix Uhr.
Nach ihre Antwort die Zeile: payload.replace(“&#035”,“#”);
nach der Zeile: String payload = http.getString(); einzufügen
bringt folgende Fehlermeldung: exit status 1 stray ’\342` in program

Verwendetes Modul AZ-Delivery ESP32-WROOM-32
Ich weis nicht mehr weiter, die Laufschrift funktioniert
bis auf die Anzeigen &#035 im Text.

Grüße R. Gerlinger

Gerald Lechner

Gerald Lechner

Mit folgender kleinen Änderung in der Funktion getNews() wird das Anführungszeichen-Problem gelöst.
Nach der Zeile mit dem Code String payload = http.getString(); folgende Zeile payload.replace(“&#035”,“#"); einfügen. Das ist alles.

Gerald Lechner

Gerald Lechner

Es können auch mehrere Matrix-Anzeigen hintereinander geschaltet werden. Im Sketch muss die Anzahl der Segmente entsprechend geändert werden. #define LEDMATRIX_SEGMENTS 8 für zwei Matrix-Anzeigen.

Rudolf Gerlinger

Rudolf Gerlinger

Hallo Herr Lechner,
ich bins schon wieder, aber diesesmal meine letzte Anfrage.
In der Laufschrift erscheint immer wieder &#035: im Text.
Ich werde wohl das Projekt auch aufgeben…

Mit freundlichen Gruß
R. Gerlinger

Rudolf Gerlinger

Rudolf Gerlinger

Hallo Herr Lechner,
da jetzt die Newsreader auf Matrixdisplay funktioniert,
hat meine Frau gefragt ob die Anzeige nicht etwas länger sein kann…
Also zweimal MAX7219 8×32 4 in 1 Dot Matrix LED…
Frauen!?!…

Grüße R. Gerlinger

hergro HerrMann

hergro HerrMann

Hatte eben vor ca. ’ner Stunde zu die Lib geposted und auf warnings hingewiesen.
Hab mich getraut, den sketch trotzdem zu laden: >> Überraschung:
Lief auf Anhieb!
Super! Danke!
Jetzt fängt das Forschen an : – ))

HerGro HerrMann

HerGro HerrMann

In der Beschreibung wird drauf hingewiesen das die Lib “Arduino_JSON” zu includieren ist.
ergibt Fehler ArduinoJson.h nicht gefunden! Bei der erstgenannten heißt die Header Datei Arduino_JSON.h , trotz Umbenennung immer noch fehlermeldung. Dann entdeckte ich das es auch eine Lib gibt “ArduinoJson” nach installation dieser Lib wurde Sketch übersetzt allerdings mit vielen Warnings!
z.B.:/home/herrmann/Arduino/libraries/WebConfig/src/WebConfig.cpp:76:1: warning: unknown escape sequence: ‘\>’
"

<input type=‘%s’ value=‘%s’ name=‘%s’ \>\n";
oder: /home/herrmann/Arduino/libraries/WebConfig/src/WebConfig.cpp:294:104: warning: ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings]
case INPUTCOLOR: createSimple(buf,description[i].name,description[i].label,“color”,values[i]);
oder: /home/herrmann/.arduino15/packages/esp32/hardware/esp32/1.0.6/cores/esp32/esp32-hal-spi.c:923:40: warning: initialization from incompatible pointer type [-Wincompatible-pointer-types]
uint8
t * last
data8 = &last
data;
^
Der Sketch verwendet 999134 Bytes (76%) des Programmspeicherplatzes. Das Maximum sind 1310720 Bytes.
Globale Variablen verwenden 54736 Bytes (16%) des dynamischen Speichers, 272944 Bytes für lokale Variablen verbleiben. Das Maximum sind 327680 Bytes.
Ist das normal? Oder sieht jemand auf einen Blick, was fehlt????
HerrMann
B. Borys

B. Borys

Herr Lechner,
noch mal zur Anführungszeichen-Ersetzung:
Im Blog werden die Anführungszeichen und der HTML-Code dafür nicht richtig dargestellt. Vielleicht wird es so deutlich:
Der Befehl muss lauten payload.replace ( … , … )
und der erste Parameter ist (Anführungszeichen)(Kaufmanns-und)quot;(Anführungszeichen)
der zweite ist (Anführungszeichen)(Backslash)(Anführungszeichen)(Anführungszeichen)

Gerald Lechner

Gerald Lechner

Mit folgender kleinen Änderung in der Funktion getNews() wird das Anführungszeichen-Problem gelöst.
Nach der Zeile mit dem Code String payload = http.getString(); folgende Zeile payload.replace(“"”,“\”"); einfügen. Das ist alles.

B. Borys

B. Borys

Schönes Projekt!
Ein Vorschlag: in XML_callback(), so etwa bei Codeuzeile 113 die Zeile
news[newsCnt].replace(“"”, “\”");
einfügen. Dann erscheinen im angezeigten Lauftext Anführungszeichen statt HTML-Code dafür

Mucki

Mucki

Hallo zusammen,

wenn in der angezeigten Nachricht ein Anführungszeichen vorkommt, wird anstelle dessen &quote; angezeigt. Z.B. wird anstelle von [“Hinweis”] folgendes angezeigt: ["Hinweis"].

Im Quellcode habe ich die Stelle noch nicht gefunden, an der das zu ändern wäre. Hat jemand einen Tipp für mich?

Gerald Lechner

Gerald Lechner

Der hier vorgestellte Sketch ist nur für den ESP32 geeignet, es gibt aber einen ähnlichen Beitrag “Das zwölfte Türchen” aus der Adventreihe 2020, der mit einem ESP8266 arbeitet. Dieser Beitrag nutzt ein anderes Display, aber die Teile zum Holen der Daten aus dem Netz (HTTP Client und Funktion getNews() können auch für den Sketch mit dem Matrixdisplay verwendet werden.

Jürgen Meinunger

Jürgen Meinunger

Meine Frage ist geklärt. Das von mir vorgesehene Board ist nicht geeignet. Order jetzt ein passendes Board, dann sollte es funktionieren.

Juergen

Juergen

Hallo,

ich muss auch mal um Hilfe bitten. Da noch vorhanden möchte ich gerne ein Lolin Node MCU 1.0 verwenden. Ich habe versucht den Sketch mit der Arduino IDE zu compilieren.
Ich erhalte folgende Fehlermeldung:

Arduino: 1.8.13 (Windows 10), Board: “NodeMCU 1.0 (ESP-12E Module), 80 MHz, Flash, Legacy (new can return nullptr), All SSL ciphers (most compatible), 4MB (FS:none OTA:~1019KB), 2, v2 Lower Memory, Disabled, None, Only Sketch, 115200”

matrix_uhr:5:58: fatal error: WebServer.h: No such file or directory

#include //Webserver für die Konfiguration ^

compilation terminated.

exit status 1

WebServer.h: No such file or directory

Irgendwie scheint da noch eine Bibliothek zu fehlen.

Gerald Lechner

Gerald Lechner

Wichtiger Hinweis!!
In der Funktion getNews() fehlte das Zurücksetzen des Zählers, daher wurden nach dem erseten Mal keine neuen Nachrichten gelesen. In der Funktion getNews() muss nach dem Laden der Daten und vor dem Analysieren mit TinyXML der Zähler auf 0 gesetzt werden.

String payload = http.getString(); newsCnt = 0; xml.reset();

Die entsprechende Stelle im Sketch wurde gelb markiert. Der Sketch zum Herunterladen wurde aktualisiert.

Gerald

Gerald

Wichtiger Hinweis!!!
Im Text des Beitrags wurde für den Taktanschluss fälschlicher Weise GPIO19 statt GPIO18 angegeben. Alle Schalt- und Verdrahtungspläne waren aber richtig.

Dieter

Dieter

Hi, welche TinyXML muss denn verwendet werden? Es gibt eine 2. Version, die bei mir nicht in der IDE funktionert. Die erste aber auch nicht, die IDE bleibt immer bei “TinyXML xml;” hängen.
Gruss Dieter

Grauer Wolf

Grauer Wolf

Hallo, ich nochmal.
Habe gerade einen Fehler gefunden. Oben in der Beschreibung steht für CLK GPIO19, es muss aber GPIO18 sein!!!
Bis auf die Compilierungsfehler läuft es jetzt!! Würde mich aber trotzdem über eine Antwort freuen.
Gruß
Grauer Wolf

Grauer Wolf

Grauer Wolf

Hallo, sehr cooles Projekt, es hat mich glleich begeistert. Leider funktioniert überhaupt nichts. Ich verwende ein NodeMCU-ESP32 von jou-it. Eigentlich sind die Pins alle vorhanden und auch richtig angeschlossen.
Das Hochladen klappt, allerdings bekomme ich viele Fehler in der Art:
In member function ‘void WebConfig::handleFormRequest(WebServer*, const char*)’:
warning: ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings
createSimple(buf,“apName”,“Name des Accesspoints”,“text”,apName);

warning: ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings]
case INPUTTEXT: createSimple(buf,_description[i].name,description[i].label,“text”,values[i]);

Es geht schon los, dass ich keine WLAN-Verbindung bekomme. Die Konfiguration funktioniert, aber verbindet sich nicht. Wenn ich die WLAN-SSID und das Password direkt als String eintrage, klappt die Verbindung, die IP wird angezeigt.
Aber auf der Punktmatrix wird nichts angezeigt, bleibt alles dunkel. Ich habe auch mal in der IDE verschiedene Boards ausgewählt, es ist exakt das Gleiche.
Ich würde mich ja über eine Hilfe freuen.

Mit freundlichen Grüßen
Grauer Wolf

Peter

Peter

Hallo Gerald, leider habe ich auch das Problem mit der Matrix-Anzeige. Ich bekomme schon beim Kompilieren den Fehler: WARNUNG: Kategorie ‘’" in der Bibliothek LG_Matrix_Print ist ungültig und wird auf ’Uncategorized’ festgelegt. Hast Du einen Tipp für mich. Ich finde die Projektidee richtig cool!

Viele Grüße Peter

Gerald

Gerald

Hallo Herr Gottfried,
haben Sie, wie gleich nach dem Sketch beschrieben, über den Browser die Zugangsdaten für Ihr WLAN richtig eingegeben?
Immer wenn der ESP32 keine Internetverbindung aufbauen kann, startet er einen Accesspoint. Über die IP-Adresse 192.168.4.1 kann dann wie im Beitrag beschrieben die Konfiguration erfolgen.

Peter Gottfried

Peter Gottfried

Tolle Idee mein ESP will nicht ins netz gehen; im Display erscheinen Fragezeichen und keine Internet verbindung bin ratlos

Jörg

Jörg

Mal wieder ein sehr cooles Projekt. Fehlt eigentlich nur, dass auch noch das Wetter angezeigt wird. ;-)

Baengelchen

Baengelchen

Hallo Gerald,
top, läuft. Zeile war drin, allerdings wie im heruntergeladenen Sketch klein geschrieben:
#include “HttpClient.h”
Hab es von Dir übernommen (Großbuchstaben) und läuft. Danke

Gerald

Gerald

Hallo Baengelchen,
Diese Fehlermeldung erscheint wenn die Zeile
#include “HTTPClient.h”
(Zweite Zeile im Programm) fehlt.

Gerald

Gerald

Hallo Sven,
ich nehme an, dass es Verbindungsprobleme zwischen MCU und Matrix sind. Ich habe das mal durchprobiert. Wenn ich die DIN Leitung (GPIO23) unterbrochen habe, habe ich das von Dir beschriebene Verhalten beobachtet. Also Verdrahtung noch einmal genau checken.
Gruß Gerald

Baengelchen

Baengelchen

Hallo,
hab alles da gehabt und sofort los gelegt. Bin nicht der Programmierer und beim Kompilieren erscheint folgender Fehler:
D:\Cloud\Matrix Uhr und Newsreader\matrix_uhr\matrix_uhr.ino: In function ‘void getNews()’:
matrix_uhr:148:5: error: ‘HTTPClient’ was not declared in this scope
HTTPClient http;
^
matrix_uhr:151:5: error: ‘http’ was not declared in this scope
http.begin(conf.getValue(“rssUrl”));
^
matrix_uhr:161:23: error: ‘HTTP_CODE_OK’ was not declared in this scope
if (httpCode == HTTP
CODE
OK) {
^
Mehrere Bibliotheken wurden für “WiFi.h” gefunden
Benutzt: C:\arduino-1.8.13\portable\packages\esp32\hardware\esp32\1.0.6\libraries\WiFi
Nicht benutzt: C:\arduino-1.8.13\libraries\WiFi
exit status 1
‘HTTPClient’ was not declared in this scope

Woran liegt’s? Was mache ich falsch?

Sven Hesse

Sven Hesse

Hallo Hans W.

so ganz ohne WLAN/LAN – also Internet kann es nicht funktionieren, woher soll denn dann Uhrzeit/Newstext kommen?
Für 4×20 LED Displays gibt es reichlich Tutorials im Netz, mit und ohne WLAN.

Grüße

Sven Hesse

Sven Hesse

Hi Gerald,

wieder einmal mehr Danke für den Beitrag.
Natürlich direkt zusammen gebaut (wenn auch nicht mit einem ESP32 Mini) aber doch mit einem ESP Wroom 32 unter Beachtung der GPIO-Belegung.
Leider ist die LED Matrix nicht dazu zu bewegen, Zeichen darzustellen. Entweder leuchten alle LED durchweg oder es leuchtet überhaupt nix.
AccessPoint wird erstellt, ich kann die Konfigurationsseite aufrufen, im seriellen Monitor werden alle Schritte angezeigt – aber die Matrix mag eben nicht.
Woran kann das denn liegen?

VG
Sven

Mucki

Mucki

Na klar geht auch das Entwicklerbrett ESP32 D1 R32.
Die Pinbelegung steht doch oben im Text: „ Der Datenausgang des ESP32 MOSI (GPIO23) wird mit DIN der Matrix verbunden. Der Taktausgang des ESP32 CLK (GPIO19) wird mit dem Takteingang der Matrix verbunden. Als Chip-Select wird der GPIO16 des ESP32 verwendet,“ wenn diese Pins verwendet werden, muss der Code nicht verändert werden.
Welche Pins das sind findet man im Datenblatt: https://cdn.shopify.com/s/files/1/1509/1638/files/D1_R32_Board_Pinout.pdf?v=1606738342
Viel Erfolg

Wolfgang Butenhoff

Wolfgang Butenhoff

Kann ich für die Schaltung auch mit dem ESP32 D1 R32 development board with CH340G and WiFi + Bluetooth (Arduino compatible Internet Development Board) einsetzen? Wenn ja bitte die entsprechende Pinbelegung mitteilen und wenn erforderlich Code-Änderungen.
Danke für den sehr interessanten Beitrag.

Hans W

Hans W

Sieht gut aus!
funktioniert das ganze auch ohne Wlan u. Handy?
z.b mit einem TFT , 4X20 Led Display?

Lascia un commento

Tutti i commenti vengono moderati prima della pubblicazione