×

矩阵显示器上的新闻阅读器开源项目

消耗积分:0 | 格式:zip | 大小:0.07 MB | 2023-02-08

哈哈哈

分享资料个

描述

在这个项目中,时间、日期和最新的头条新闻将在 LED 矩阵上显示为自动收报机。日期和时间将从时间服务器更新。标题来自所谓的 RSS 提要。这是由各种网络服务器提供的服务,例如 tagesschau.de。此服务使用 http 或 https 作为传输协议,但数据不是像往常一样以 HTML 格式提供,而是以 XML 格式提供,即没有布局信息。

电路

LED 矩阵仅连接到 ESP32 的 SPI 总线。ESP32 MOSI (GPIO23) 的数据输出连接到矩阵的 DIN。ESP32 CLK (GPIO19) 的时钟输出连接到矩阵的时钟输入。ESP32的GPIO16用作片选,连接到矩阵的CS连接器。矩阵由 5V 供电,这对 ESP32 没有问题,因为这里使用的所有连接器都用作输出,因此无法从矩阵获得更高的电压。

 
poYBAGPi-SSASVXTAAAzp1r1Jd8077.png
 

程序

除了 ESP32 包之外,您还需要四个库。我们使用 TinyXML 库从接收到的 XML 数据中提取所需的信息。图书馆只有三个功能。

void init (uint8_t* buffer, uint16_t maxbuflen, XMLcallback XMLcb); 该函数在设置函数中被调用。参数buffer指向一个存放临时数据的字节数组,参数maxbuflen指定了缓冲区的大小。参数 XMLcallback 指向一个回调函数

void XML_callback(uint8_t statusflags, char* tagName, uint16_t tagNameLen, char* data, uint16_t dataLen)

只要 XML 元素已被完全处理,就会调用它。statusflags 参数指定提供哪些数据。由于我们对 XML 标记的内容感兴趣,因此我们将只使用状态 STATUS_TAG_TEXT。tagName 参数指向包含项目完整 XML 路径的字符数组。对于新闻源项目的标题,这是路径“/rss/channel/item/title”。tagNameLen 参数返回路径的长度。参数数据还指向包含数据的字符数组。最后一个参数 dataLen 返回数据数组的大小。

无效重置();此函数重置所有内部指针。它应该总是在读入新的 XML 数据之前被调用。

void processChar(uint8_t ch); 此函数将要处理的 XML 数据的一个字符发送到 XML 解析器。必须为接收到的每个字符顺序调用此函数。如果传输的字符导致 XML 块的完成,则使用相应的数据调用回调函数。LG_Matrix_Print 库用于在 LED 矩阵上显示字符串。草图中使用了以下函数:void setEnabled(bool enabled);  启用或禁用对 LED 矩阵的访问。

void setIntensity(uint8_t level); 此功能更改显示屏的亮度。允许使用 0 到 15 之间的值。通常1就足够了

无效显示();显示存储器的内容被传输到矩阵并因此可见。

void clear()清除显示内存。

int printText(int start, String text, boolean isUTF8 = true); 此函数使用内部字符集将文本参数中指定的字符串(从第 n 个字符开始)转换为显存中相应的位模式。输出开始的字符在 start 参数中指定。可选参数 isUTF8 打开或关闭内部代码转换器。

void ticker(字符串消息,uint16_t 等待);这个函数可以很容易地实现一个自动收报机。参数 message 指向包含要显示的文本的字符串。第二个参数 wait 指定在文本前进一个像素之前等待的时间(以毫秒为单位)。图书馆负责其他一切。

布尔更新代码();必须在循环函数中调用此函数以更新代码。

该库包含其他功能,但在此草图中未使用它们。WebConfig 库用于通过浏览器配置 WLAN 访问数据以及其他设置。配置存储在闪存文件系统 SPIFFS 中,即使在微控制器关闭后也会保留。详细说明可以在:  https  ://github.com/GerLech/WebConfig/blob/master/README.md或在我的书Smarthome 中找到。

WebConfig 库需要 Arduino_JSON 库,因此也必须安装它,但未包含在草图中。

草图

#include  //Bibliothek für die Matrixanzeige
#include 
//Web Client für den Empfang des RSS-Feeds
#include  //Filesystem zum Speichern der Konfiguration
#include  //Webserver für die Konfiguration
#include  //Multicast DNS für Namensauflösung
#include 
//Bibliothek zur Konfiguration über eine Webseite
#include  //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();
xml.reset();
for (uint16_t i=0; i.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;
}
}

下载草图

启动后,程序还不能与WLAN建立连接。因此,接入点被启动。它的 SSID 是 ESP32 的 MAC 地址。在智能手机的 WLAN 设置中,您应该会看到 SSID。您现在可以选择此网络并连接到它。网络不需要密码。智能手机可能会报告无法连接互联网,以及您是否要保留所选网络。在这种情况下,点击 Keep。< pan>

 
poYBAGPi-SiATwYqAABQsccSrMw494.png
 

现在您可以启动浏览器并调用 URL 192.168.4.1。矩阵时钟的配置页面应该会出现。

 
pYYBAGPi-TSAKmMxAAA1kwrMeOU145.jpg
 

接入点的名称是 MAC 地址,可以根据需要进行更改。随后是 WLAN 的访问数据。例如,对于 RSS 提要的 URL,您可NTP 服务器可以保持原样。但是您也可以设置例如带有 fritz.box 的 Fritzbox。接下来,您必须设置标题的最大数量、显示屏的亮度和以秒为单位的时间,时间和日期应显示多长时间。最后点击保存并重新启动。矩阵时钟将重新启动,现在应该连接到 WLAN。在串行监视器的输出中,您可以跟随登录。然后可以通过路由器分配给矩阵时钟的 IP 地址访问配置页面。

安装在外壳中

那些拥有 3D 打印机的人可以打印出合适的房屋。一共需要四个部分。一个底部部件 Uhr_unterten.stl,一个盖子 Uhr_deckel.stl 和两个显示器支架 Uhr_halter.stl。现在到大会。首先,面包板配有两个母连接器和一个带角度的公连接器。< pan>

 
 
poYBAGPi-TaAO19OAACyb_c0mAg492.jpg
 

下面的接线是在后面板上进行的。

 
pYYBAGPi-TmAYoSfAACretqVF_M729.png
 

现在您可以将控制器插入母连接器,并通过 5 针电缆将矩阵与控制器连接起来。在这里你必须注意引脚的正确顺序。插入后,是时候再次检查所有内容并在开始安装之前进行试运行了。

 
pYYBAGPi-UaALRoPAAB_B01OKP0957.jpg
 

下一步是在外壳中安装矩阵和面包板。为了连接矩阵,必须首先将两个支架连接到矩阵。

 
pYYBAGPi-UqAd7VGAABAfsdHv84018.jpg
 

现在您可以将矩阵固定在盖子上,将面包板固定在底座上。< pan>

 
pYYBAGPi-U2AAbTzAABbWSsrhWc818.jpg
 

就是这样了。带有新闻提要的矩阵时钟已准备就绪

 
pYYBAGPi-VWAHssTAABOSoBfAKI227.jpg
 

玩得开心。

 


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

评论(0)
发评论

下载排行榜

全部0条评论

快来发表一下你的评论吧 !