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;
}
}
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.
Die URL für den ARD Dienst hat sich geändert. Es muss jetzt
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.
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(""","\""); payload.replace(""","\""); payload.replace("#","#"); 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; } }
Zusätzlich kann jetzt auch das aktuelle Wetter von https://openweathermap.
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(""","\""); payload.replace(""","\""); payload.replace("#","#"); 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.
46 Reacties
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
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
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
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
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
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
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
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
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.
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
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
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
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
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
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(“#”,“#”);
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 # im Text.
Grüße R. Gerlinger
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.
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
Hallo Herr Lechner,
ich bins schon wieder, aber diesesmal meine letzte Anfrage.
In der Laufschrift erscheint immer wieder #: im Text.
Ich werde wohl das Projekt auch aufgeben…
Mit freundlichen Gruß
R. 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
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
In der Beschreibung wird drauf hingewiesen das die Lib “Arduino_JSON” zu includieren ist.
<input type=‘%s’ value=‘%s’ name=‘%s’ \>\n";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: ‘\>’
"
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]
uint8t * lastdata8 = &lastdata;
^
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
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
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
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
Hallo zusammen,
wenn in der angezeigten Nachricht ein Anführungszeichen vorkommt, wird anstelle dessen "e; 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
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
Meine Frage ist geklärt. Das von mir vorgesehene Board ist nicht geeignet. Order jetzt ein passendes Board, dann sollte es funktionieren.
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
Wichtiger Hinweis!!
String payload = http.getString(); newsCnt = 0; xml.reset();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.
Die entsprechende Stelle im Sketch wurde gelb markiert. Der Sketch zum Herunterladen wurde aktualisiert.
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
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
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
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
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
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
Tolle Idee mein ESP will nicht ins netz gehen; im Display erscheinen Fragezeichen und keine Internet verbindung bin ratlos
Jörg
Mal wieder ein sehr cooles Projekt. Fehlt eigentlich nur, dass auch noch das Wetter angezeigt wird. ;-)
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
Hallo Baengelchen,
Diese Fehlermeldung erscheint wenn die Zeile
#include “HTTPClient.h”
(Zweite Zeile im Programm) fehlt.
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
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 == HTTPCODEOK) {
^
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
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
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
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
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
Sieht gut aus!
funktioniert das ganze auch ohne Wlan u. Handy?
z.b mit einem TFT , 4X20 Led Display?