Bevor ich größere Projekte angehe, in meinem Fall eine vollautomatische Aquariumsteuerung mit einigen Features, versuche ich Teilprojekte daraus zu machen und später das große Ganze daraus zu programmieren. Das hat viele Vorteile, da man Code schon vorher testen und optimieren kann, bevor man mit dem großen Ganzen beginnt. Dieses Projekt entstand, da ich zunächst die Daten für Sonnenaufgang und -untergang, sowie die Zeitzone abfragen wollte. Mit diesen doch geringen Daten, kam direkt die Idee auf, eine analoge Uhr auf dem Az-Touch Wandmod zu realisieren, welche auch in Teilen dann bei der Aquariumsteuerung eingesetzt wird. Gerade das Az-Touch Wandmod eignet sich mit einigen Modifikationen perfekt für solche Aufgaben.
Einige Zeilen Code habe ich schon in anderen Projekten verwendet und kann hier eine Optimierungsschleife durchlaufen. Damit die Uhr aber nicht das Einzige ist, wird auch die tagesaktuelle Zeit von Sonnenaufgang und -untergang auf dem Display visualisiert. Damit wir aber nicht nur einfache Texte anzeigen, sollen monochrome Bilder auf dem Display genutzt werden. Wie das geht und wie man z.B. Bitmaps für den ESP32 NodeMCU lesbare Daten umwandelt, soll heute das Thema vom Blogbeitrag werden.
Die Hard- und Software für diesen Blog
Damit Sie diesen Blog nachbauen können, brauchen Sie die Komponenten aus Tabelle 1:
Tabelle 1: Bauteile für die analoge Uhr
An Software brauchen Sie:
- Arduino IDE (https://www.arduino.cc/en/Main/Software), hier am besten die aktuellste Version herunterladen
- Die Bibliothek TFT_eSPI, NTPClient, ArduinoJson, WiFi, TimeLib mit allen Abhängigkeiten.
- Das Programm LCD Image Converter (https://sourceforge.net/p/lcd-image-converter/activity/?page=0&limit=100#604634eaa7028b0bbaf4db72) in der Version 20190317
Zusätzlich benötigen Sie einen Zugang zu openweathermap.org, wobei die kostenlose Basisversion für dieses Projekt vollkommen ausreicht.
Das Programm LCD Image Converter
Wenn Programme entwickelt werden, so muss man immer darauf achten, dass die Ausgaben auf der Displayanzeige jeder versteht. Im Falle von Diagnoseausgaben reicht Englisch oder die Muttersprache, aber bei grafischen Benutzeroberflächen für den Endanwender, kurz GUI, muss entweder alles in jede Sprache übersetzt werden, oder man bedient sich eindeutigen Piktogrammen.
Für meine analoge Uhr wollte ich nun nicht alles ausschreiben, gerade auch durch den limitierten Platz, sondern Piktogramme für den Sonnenaufgang und -untergang benutzten, siehe Abbildung 1.
Abbildung 1: Bitmaps für analoge Uhr
Das Problem ist, die Bilder habe ich mit Paint schnell als Bitmap, Endung bmp, erstellt, diese kann das Az-Touch bzw. der ESP32 NodeMCU aber nicht lesen. Da es sich noch um einfache Piktogramme handelt, könnte nun im Quellcode eine Funktion geschrieben werden, die mit Zeichenfunktionen, Kreise, Linien, etc., diese auf das Display zeichnet, aber das ist viel zu viel Mathematik und ein Bild kann schnell in lesbaren Maschinencode umgewandelt werden.
Abbildung 2: Das Programm LCD Image Converter
Meine Wahl fiel auf das Programm LCD Image Converter, siehe Abbildung 2, welches sowohl ein sehr einfacher Zeichen- und Fonteditor ist, aber auch die Möglichkeit bietet, Bilder in Maschinencode zu konvertieren. Letzteres werde ich Ihnen für monochrome Bilder zeigen, kann aber auch für farbige Bilder angewendet werden.
Zunächst muss über File -> Open das zu konvertierende Bild ausgewählt werden, siehe Abbildung 3.
Abbildung 3: Eine Bilddatei laden
Nach dem Laden des Bildes haben Sie gleich zwei interessante Änderungen in der Oberfläche. Zunächst sehen Sie das ausgewählte Bild in der Mitte und unten rechts die Bildgröße, hier 64 x 64 Pixel.
Als nächstes öffnen wir über Options -> Conversion… das Conversion-Interface aus, siehe Abbildung 4.
Abbildung 4: Conversioninterface öffnen
Damit das Bild auf dem Display korrekt angezeigt wird, in unserem Fall ein monochromes Bild, müssen noch die Einstellungen angepasst werden. Zunächst wird das Profil auf Monochrome gestellt und im Reiter „Scanning“ Top to Buttom, siehe Abbildung 5.
Abbildung 5: Scanning-Einstellungen anpassen
Das bewirkt, dass der Maschinencode die korrekte Orientierung hat, von oben nach unten. Als Nächstes wird in den Reiter Preprocessing gewechselt, siehe Abbildung 6.
Abbildung 6: Preprocessing-Einstellungen anpassen
Hier wird die Option Inverse aktiviert, da sonst das Bild später nicht in der gewählten Farbe, sondern der Hintergrund vom Bild auf dem Display eingefärbt wird. Nun noch den Button Show Preview drücken und das Ergebnis wird im nächsten Fenster angezeigt, siehe Abbildung 7.
Abbildung 7: Voransicht des Bildes und Maschinencode
Interessant ist hier der rechte Teil im neuen Fenster Preview, bei der die vielen Hex-Daten angezeigt werden. Hierbei handelt es sich nun um die Bilddaten in Maschinencode, was später auf dem Az-Touch angezeigt werden soll. Kopieren Sie den gesamten Inhalt und erstellen Sie eine neue Headerdatei, z.B. bitmap.h.
Fügen Sie zunächst die folgenden Zeilen am Anfang der Datei ein, siehe Code 1.
Die ersten 3 Zeilen bewirken, dass die Headerdatei richtig im Code hinterlegt wird. Die weiteren Zeilen bewirken, dass die pgmspace.h für ESP-MicroController oder Arduino-MicroController korrekt geladen werden.
Damit nun später im Quellcode das monochrome Bild sunrise genutzt werden kann, muss die entsprechende Variable angelegt werden, siehe Code 2.
const unsigned char sunrise[] PROGMEM = {
};
Den kopierten Inhalt vom LCD Image Converter fügen Sie nun dem Array hinzu, siehe Code 3, was den gekürzten Inhalt der neu angelegten Header-Datei wiedergibt.
//Icon for sunset 64*64 pixel
const unsigned char sunrise[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
....
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
Code 3: Maschinencode für das Array sunrise, stark gekürzt
Das gleiche Vorgehen mit dem Array, wird nun für alle weiteren Grafiken durchgeführt. Am Ende haben Sie, je nach Anzahl von Grafiken, eine Header-Datei mit vielen Zeilen. Für die zwei Grafiken in diesem Projekt, fallen knapp 150 Zeilen Code an.
Damit sind die Vorbereitungen für die analoge Uhr abgeschlossen.
Die analoge Uhr auf dem AZ-Touch
Insgesamt umfasst dieses kleinere Projekt knapp 500 Zeilen Code. Der Code liegt wie gewohnt auf meinem Github-Repository für Sie bereit und ist mit vielen Kommentaren versehen, was auch ein Grund für die vielen Zeilen sind. Jedoch sollen einige Funktionen und Abschnitte näher erläutert werden, damit Sie verstehen, wie der Code funktioniert.
Ganz zu Anfang im Quellcode werden die Defines für die Farben der Uhr festgelegt, siehe Code 4.
/* --- Some color definitions, feel free to change --- */
Code 4: Defines für die Farben der Uhr
Hier können Sie nach Lust und Laune die Farbe tauschen, achten Sie dazu auf die Defines in der Datei TFT_eSPI.h in der gleichnamigen Bibliothek. Das vereinfacht den Austausch der Farben ungemein.
Nach den Includes der benötigten Bibliotheken kommt einer der wichtigsten Parts für das Projekt, das Setzen der WLAN-Parameter und die benötigten API-Daten für openweathermap.org, siehe Code 5.
const char* ssid = ""; //CHANGE SSID from network
const char* password = ""; //CHANGE Password from network
/*--- Needed variables for openweathermap.org ---*/
const String apiKey = ""; //CHANGE API-Key from openweathermap.org
const String location = ""; //CHANGE Location for openweathermap.org
Code 5: WLAN-Zugang und API-Informationen openweathermap.org
An dieser Stelle fügen Sie bitte die benötigen Daten ein, damit sich später der ESP32 NodeMCU mit dem Internet verbinden und auch die Wetterdaten von openweathermap.org runterladen kann. Hier sollten Sie sich schon bei openweathermap.org registriert haben.
Die setup()-Funktion aktiviert das TFT, das WLAN mit den hinterlegten Daten, synchronisiert die Zeit vom ESP32 NodeMCU mit dem NTP-Server und zeichnet die Basisgrafiken auf das Display. Damit Sie sehen, welcher Teil der setup()-Funktion gerade bearbeitet wird, gibt es eine Art Statusausgabe auf dem Display, siehe Abbildung 8.
Abbildung 8: Status vom Start des Programms
Damit sehen Sie dann auch sofort, wo das Programm ggf. nicht weiterkommt und Sie die analoge Uhr nicht sehen können. Das hilft ungemein, wenn der Az-Touch mal nicht am PC angeschlossen ist. Ist alles erfolgreich abgeschlossen, wird nach 5 Sekunden die Uhr mit allen Daten angezeigt, siehe Abbildung 9.
Abbildung 9: Az-Touch mit allen Daten
Wie Sie schnell sehen werden, passiert in der loop()-Funktion vom Umfang her nicht viel, siehe Code 6. Lediglich vier benötigte Funktionen werden aufgerufen und Daten an den seriellen Monitor geschickt.
/*
* =================================================================
* Function: loop
* Returns: void
* Description: Main loop to let program work
* =================================================================
*/
void loop()
{
DrawTime();
UpdateOpenWeatherMapData();
UpdateNTPTime();
UpdateSunriseSunset();
//Serial output
if(pPrevSecond != second() || bFirstrun)
{
Serial.print(daysOfTheWeek[weekday()] + "., ");
Serial.print(GetDigits(hour()) );
Serial.print(":");
Serial.print(GetDigits(minute()) );
Serial.print(":");
Serial.println(GetDigits(second()) );
pPrevSecond = second();
}
if(bFirstrun)
bFirstrun = false;
}
Code 6: Die loop()-Funktion des Projekts
Aber genau hier liegt der Vorteil! Die loop()-Funktion ist nicht überfüllt und man kann schnell Code austauschen. Die Funktionen haben eindeutige Namen, weswegen Sie schnell herausfinden werden, welche Funktion welchen Arbeitsschritt leistet. Jedoch möchte ich an dieser Stelle auf alle Funktionen der loop()-Funktion eingehen.
DrawTime() bewirkt, dass die Zeiger der Uhr bei jedem Stunden- oder Minutenwechsel neu gezeichnet werden. Wenn Sie die Funktion im Quellcode näher ansehen, wird zuerst der alte Zeiger mit der Hintergrundfarbe vom Display überschrieben und danach der neue Zeiger gezeichnet. Hier steckt für die Zeiger die meiste Mathematik, da entsprechend der Uhrzeit die Zeiger im richtigen Winkel angeordnet sein müssen. Da im Quellcode nicht jeder Pixel einzeln von uns gezeichnet, sondern eine Linie mit einem Start- und Endpunkt definiert wird, müssen wir über die Winkelfunktionen Sinus und Cosinus, ausgehend vom Uhrenmittelpunkt, den Endpunkt der Zeiger errechnen. Ist dies geschehen, wird mittels drawLine() der Zeiger in die Uhr gezeichnet.
Scheuen Sie nicht, sich die Funktion DrawTime() und die darin aufgerufenen Funktionen mal genauer anzusehen, auch wenn das Endergebnis funktioniert.
Direkt nach DrawTime() folgt die Funktion UpdateOpenWeatherMapData(), welche, wie der Name schon sagt, die aktuellen Daten von openweathermap.org anfragt. Dies geschieht in drei wesentlichen Schritten:
- Anfrage für den Client vorbereiten und abschicken
- Antwort abwarten und benötigten Daten, hier das JSON-Format, vorbearbeiten
- Daten vom JSON-Format aufbereiten und wichtige Daten speichern
Dieser Teil vom Code ist etwas komplexer, da ein https-Telegramm für openweathermap.org vorbereitet wird, hier über die Funktion RequestData() und die Antwort korrekt formatiert werden muss. Dazu sollten beim Antworttelegramm unnötige Daten eliminiert werden. Im Anschluss konvertiert UpdateOpenWeatherMapData() den empfangenen JSON-String in die richtige Klase um und extrahiert die Daten für Zeitzone, Sonnenaufgang und -untergang. Diese neuen Daten werden mit den vorhandenen verglichen und bei einer Änderung ein Update der GUI oder der Zeitzone angestoßen. Da sich unsere benötigten Daten nicht sekündlich ändern, wird die oben beschriebene Arbeit nur alle 30 Minuten durchgeführt.
Damit unsere analoge Uhr auch immer die richtige Zeit anzeigt, wird mittels UpdateNTPTime()-Funktion alle 15 Minuten die interne Uhr vom ESP32 NodeMCU mit dem NTP-Server synchronisiert, somit sollte unsere Uhr immer sekundengenau laufen.
Zuletzt kommt noch die UpdateSunriseSunset()-Funktion, die im Gegensatz zu den bisherigen Funktionen wahrscheinlich die einfachste ist. Sollte sich durch die UpdateOpenWeatherMapData()-Funktion eine Änderung von Sonnenaufgang und -untergang ergeben haben, so wird die alte Uhrzeit auf dem Display gelöscht und die neue Zeit eingetragen.
So einfach das nun wahrscheinlich alles in der Theorie klingen mag, gerade die ersten drei Funktionen in der loop()-Funktion sind doch recht komplexer Natur. Zum einen, weil hier viel Mathematik genutzt wird, zum anderen weil die Beschaffung der Daten von openweathermap.org nicht trivial ist. Eine große Hilfe bei der Beschaffung der Daten war die gute und ausführliche API-Doku von openweathermap.org und die Tatsache, dass ich eine ähnlichen RequestData()-Funktion schon einmal für ein anderes Projekt erstellt habe. Diese sollte fast universell nutzbar sein, sofern Sie für ein anderes Projekt von einer anderen Seite Daten anfordern wollen. Da ich kein Freund von http-Übertragungen bin, nutzt die RequestData()-Funktion die sicherere https-Variante, welche aber auch für http eingesetzt werden kann.
Den kompletten Quellcode finden Sie hier
Vorbereitung für das Projekt
Bevor Sie nun das Programm kompilieren und danach frustriert sind, dass dieses fehlschlägt. Im Projektverzeichnis finden Sie eine Datei mit dem Namen User_Setup.h. Diese ersetzen Sie bitte im Order C:\User\IHR-Name\Documents\Arduino\libraries\TFT_eSPI, siehe Abbildung 10. Es macht Sinn, zuvor die dort befindliche Datei mit gleichem Namen umzubenennen, z,B. in User_Setup_old.h.
Abbildung 10: User_Setup.h überschreiben
Bei den neuen Az-Touch Mods ist es egal ob Sie die 2.4“- oder 2.8“-Variante benutzten. Da beide die gleiche Auflösung haben, kann der Quellcode ohne weitere Anpassungen direkt für beide Varianten genutzt werden.
Was bleibt nun noch zu tun? Nun wie oben beschrieben müssen Sie die Daten für WLAN und openweathermap.org mit Ihren Daten überschreiben. Des Weiteren können Sie die Farben nach Belieben verändern oder aber nach der Anleitung weiter oben eigene Piktogramme für die Uhr verwenden. Für diejenigen unter Ihnen, die Spaß an Mathematik haben, können Sie den Zeigern andere Formen verpassen, der Kreativität sind keine Grenzen gesetzt. Da es noch ein bisschen Platz auf dem Display gibt, wäre sogar vorstellbar, das Datum und den Wochentag in der Uhr anzuzeigen. Diese Informationen sind im Quellcode schon vorhanden, müssen nur noch an der richtigen Position im Display visualisiert werden.
Ich hoffe Sie werden viel Freude mit der Uhr haben und das Interesse für einfache oder komplexere IoT-Projekte ist geweckt.
Weitere Projekte für Az-Delivery von mir, finden Sie unter https://github.com/M3taKn1ght/Blog-Repo.
15 comments
Andreas Wolter
@Frank: der Code wurde ergänzt. Vielen Dank für den Hinweis.
Grüße,
Andreas Wolter
AZ-Delivery Blog
Frank
Seid gegrüßt,
leider ist der Fehler, den Rudolf gefunden hatte, noch immer nicht im Quellcode (auf Github) behoben worden. Da fehlt nach wie vor die entscheidende Zeile:
WiFiClientSecure client;
client.setInsecure(); <—— um diese Zeile geht es
Nach dem Einfügen funktionierte das Programm bei mir auch gleich wie es sollte und die Verbindung wurde gemacht.
Meine Anzeige habe ich um Wochentag und Datum ergänzt. Die Farben habe ich umgedreht. Der Hintergrund ist Schwarz und die Anzeigen sind hell. Das Lesen bzw. Anschauen finde ich so abends angenehmer für die Augen.
Liebe Grüße
Siegl
Hallo
Vielen Dank für die schnelle Hilfe.Ich werde es versuchen.
Andreas Wolter
@Siegl:
eigene Headerdateien müssen sich im gleichen Ordner befinden, wie die .ino Sketchdatei. Wenn man den Sketch öffnet, wird diese Datei ebenfalls in die IDE geladen. Man sieht dann im oberen Bereich einen Reiter mit dem Namen der Datei. Um den Sketchordner zu öffnen, kann man in der Arduino IDE im Menü “Sketch” den Punkt “Sketch-Ordner anzeigen” wählen, oder die Tastenkombination STRG+K. Die Headerdatei sollte dann noch per #include “headerdatei.h” eingebunden werden (wie es Jörn Weise im Sketchdatei getan hat). Dabei ist darauf zu achten, dass man den Namen dieser Headerdatei in Anführungszeichen setzt, statt in Pfeilklammern. Im Quellcode wurde der Hinweis hinterlassen, wie man die Datei einbindet: //This is a file in the same dir from this program(!!!)
Ich hoffe, ich konnte helfen.
Grüße,
Andreas Wolter
Siegl
Hallo
Ich benötige noch einmal Hilfe.
Wie bekomme ich die “bitmap.h”in das Programm eingebunden ?
Danke
Siegl
Arduino: 1.8.16 (Windows Store 1.8.51.0) (Windows 10), Board: “ESP32 Dev Module, Disabled, Default 4MB with spiffs (1.2MB APP/1.5MB SPIFFS), 240MHz (WiFi/BT), QIO, 80MHz, 4MB (32Mb), 921600, None”
Hallo
Ich bekomme immer diese Fehlermeldung.Was mache ich falsch?
In file included from C:\Users\User\Documents\Arduino\libraries\TFT_eSPI-master/TFT_eSPI.h:39:0,
from C:\Users\User\Documents\Arduino\sketch_oct05aopa\sketch_oct05aopa.ino:18:C:\Users\User\Documents\Arduino\libraries\TFT_eSPI-master/User_Setup_Select.h:22:74: fatal error: User_Setup.h: No such file or directory
compilation terminated.
exit status 1
Fehler beim Kompilieren für das Board ESP32 Dev Module.
Dieser Bericht wäre detaillierter, wenn die Option
“Ausführliche Ausgabe während der Kompilierung”
in Datei → Voreinstellungen aktiviert wäre.
Rudolf
Habe den Fehler gefunden
/*
============= Function: RequestData Returns: String Description: Request to openWeathermap.org to get latest data =============*/
String RequestData()
{
WiFiClientSecure client;
client.setInsecure(); <—————- diese Zeile fehlt im Programm. Ohne kann er keinen client.connect(clientAdress,443 ) aufbauen und es kommt zu “Failed to connect”
Jörn Weise
Hallo flai,
Vielen Dank für die Blumen. Wie bereits im Blog beschrieben, ist das nur ein Teilprojekt die etwas größeres, aber wie immer sollte man größere Projekte immer in kleinere Zwischenschritte unterteilen und manchmal kommt dabei auch etwas neues mit kleinen Änderungen raus.
Gruß
Weise
Willi Wegemann
Hallo,
bekomme Fehler "cannot declare variable ‘ntpUPD’ tobe of abstract type ‘WiFiUPD’ " beim compilieren.
Wer weiss Rat ?
W.Wegemann
p.s. habe #include Time statt TimLib . Konnte TimeLib nicht finden. Server streikt
Rudolf
Hi
Habe das problem das ich keine Daten von http://api.openweathermap.org rein bekomme.
wenn ich die Adresse direkt aufrufe geht es
Wifi connecting…
Wifi connected, IP address: 192.168.0.24
Failed to connect
Received data:
Failed to connect
Received data:
Update time: SUCCESS
Kann wer helfen ?
Danke Rudolf
Wolfgang Specht
Hallo,
ich habe ein Problem bei der Herstellung der WiFi Verbindung.
Es kommt die Meldung Failied to connect.
Die Hardware läuft mit fehlerfrei mit anderen Prorammen.
MfG
Andreas Wolter
Der Link zu den vollständigen Quellcodes wurde aktiviert. Wir entschuldigen uns für die Verspätung.
flai
Eine sehr schöne und inspirative Umsetzung. Vielen Dank für die Bereitstellung des Codes und die ausführlichen Erläuterungen.
Wolfgang Specht
Hallo ,
wo finde ich den kompletten Quellcode. Der Link im Text funktioniert nicht(ist Schwarz hinterlegt).
Bitte um Info
MfG
Werner
Hallo Forum,
bitte bei “Den kompletten Quellcode finden Sie hier” Link einfügen.