This page in English

ESP8266 / ESP32 en displays



Introductie

Introductie

De ESP8266 en de ESP32 zijn microcontrollers met WiFi aan boord. Ze kunnen geprogrammeerd worden via PC en een USB-kabel. Spotgoedkoop en gemakkelijk.

Daarna kan de microcontroller op zichzelf werken, mits we stroom voorzien via batterij of adaptor met USB-stroomkabel. Deze microcontrollers zijn zo goedkoop en veelzijdig dat ze hun voorganger - de Arduino - grotendeels hebben verdrongen. Intussen zijn ze gemeengoed bij duizenden hobbyisten wereldwijd.

Door de vele verbindingsmogelijkheden (WiFi, bluetooth, IR-afstandsbediening, ...) en de mogelijke aansluitingen (relais, LED, display, sensor voor temperatuur enz.) kunnen we er talloze projecten mee maken die we vanop afstand kunnen bedienen (drones, thermostaat, besturing van een slot of lamp of motor), of projecten die waarschuwingen kunnen sturen naar onze GSM. Ook besturing via een app op smartphone (zelfs van op honderden kilometers afstand) is gemakkelijk.

De voorbeelden hier gaan over klokken met enkele displays. Waarom klokjes? Ze zijn relatief eenvoudig, zowel qua hardware als qua programmeren. En het zijn uitstekende voorbeelden als je met displays wil werken. Een NTP-klok (die de tijd synchroniseert via internet) moet rekening houden met zomer- of winteruur, en automatisch omschakelen. Zoniet is de code onvolledig. Het uur weergeven als "14:7" is slordig. Er zijn talloze methodes en functies om (zeker bij tijdsweergave) een vooroplopende "0" te gebruiken.

Op deze pagina werd de code zo compact mogelijk gehouden.

De sketches zijn in het Engels opgesteld, om een breder publiek te kunnen bereiken.



Wat heb je hiervoor nodig?

Wat heb je hiervoor nodig?

Download de Arduino IDE (gratis)

Dit is de compiler, waarmee je sketches schrijft en de code uploadt naar de microcontroller. In het menu van de Arduino IDE, klik op "Bestand" - "Voorkeuren", en op de lijn "borden beheerder" plak je volgende lijn:
http://arduino.esp8266.com/stable/package_esp8266com_index.json
Best - met een komma gescheiden - ook de volgende lijn er aan toevoegen:
https://dl.espressif.com/dl/package_esp32_index.json

Dan is de IDE ingesteld voor zowel de ESP8266 als de ESP32.

Je kan nu gewoon de ESP8266 of ESP32 met een USB-kabel aansluiten op de PC, en beginnen met programmeren.

Selecteer eerst via het menu de juiste microcontroller:


In het geval van een ESP8266:


In geval van een ESP32:


Bibliotheken die je nodig hebt kan je via het menu toevoegen:

Ofwel haal je een bibliotheek binnen uit de lijst (Bibliotheken Beheren), ofwel download je die eerst via de links in de sketches, en installeer je die als "zip".

Hardware om mee op te starten:

Een goedkoop starter pack voor de ESP8266
Een duurder (vollediger) starter pack voor de ESP8266
Soldeerloze verbindingsdraden
Een ESP32 microcontroller



TM1637-display + ESP8266

TM1637-display + ESP8266

Amazon: TM1637


Verbindingen:

ESP8266	-> TM1637
=================
GND --------> GND
3V3 --------> VCC
D2  --------> DIO
D1  --------> CLK


Sketch voor een eenvoudig klokje:

#include <WiFiManager.h>    // https://github.com/tzapu/WiFiManager/
#include <TM1637Display.h>  // https://github.com/avishorp/TM1637/

struct tm tInfo;  // https://cplusplus.com/reference/ctime/tm/
WiFiManager myWiFi;
TM1637Display ledSeg(5, 4);  // scl (clk) -> GPIO5 (D1) / sda (dio) -> GPIO4 (D2).

void setup() {
  Serial.begin(115200);                                           // watch serial monitor in case of new WiFi-connection
  myWiFi.autoConnect("TM1637_ESP");                               // connect ESP to new wifi via GSM or PC in that case
  ledSeg.setBrightness(2);                                        // 0 .. 7
  configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "be.pool.ntp.org");  // https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
}

void loop() {
  getLocalTime(&tInfo);                                                                           // sntp sync at startup & every hour from then on
  ledSeg.showNumberDecEx(tInfo.tm_hour * 100 + tInfo.tm_min, 0x40 * (millis() % 1000 < 500), 1);  // 0x40 = ":", 1 = leading zeros
}
  • schakelt automatisch over op zomer- of wintertijd
  • synchroniseert 1 keer per uur met SNTP-server
  • kan met elke WiFi verbinden zonder aanpassing van de code (instelbaar met bvb. GSM)
  • geen "hard gecodeerd" paswoord.
  • slechts 18 lijnen code
  • geen delay()-opdrachten

Hoe werkt het?

  • #include <WiFiManager.h> bibliotheek om de ESP met de wifi te verbinden, inclusief webserver als de WiFi-gegevens niet eerder ingebracht waren. De verbinding kan overal. Als de verbinding niet lukt, dan zie je op de seriele monitor:
    • *wm:StartAP with SSID: TM1637_ESP
    • *wm:AP IP address: 192.168.4.1
    • *wm:Starting Web Portal

  • Je moet met je GSM of tablet of PC verbinden met dit access point, een browser starten en als adres 192.168.4.1 ingeven, en je kan de WiFi-gegevens ingeven. Voordeel is dat je de ESP kan verplaatsen naar elders (of een WiFi-toegangspunt wijzigen) zonder dat je opnieuw de code moet compileren. Ook zonder serial monitor zal je merken als het klokje niet werkt: dan moet je ook via het accces point verbinden en de WiFi-gegevens inbrengen. Eenmaal het klokje gestart is kan je de WiFi-verbinding verbreken, het klokje zal gewoon verder lopen (uiteraard zonder synchronisatie dan).

  • De laatste lijnen van de functies "setup" en "loop" zijn krachtig, ze voeren veel uit op 1 lijn.
    • configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "be.pool.ntp.org"); dit is de lijn die er voor zorgt dat de juiste tijdzone gebruikt wordt, het klokje zich automatisch aanpast aan zomer- of wintertijd, en vervolgens aangeeft met welke NTP-servers je wenst te synchroniseren.
    • ledSeg.showNumberDecEx(ti.tm_hour * 100 + ti.tm_min, 0x40 * (millis() % 1000 < 500), 1); de tijd wordt op het display weergegeven met een dubbelpunt dat elke halve seconde verborgen of getoond wordt, en met vooroplopende nullen.

  • getLocalTime(&timeinfo); vraagt de tijd uit de ESP8266 op, aangepast volgens tijdzone en zomertijd / wintertijd. De eerste keer wordt er gesynchroniseerd met de NTP-server, de volgende keren gebeurt de synchronisatie om het uur (werd uitgebreid getest met callbackfuncties). Om het uur betekent niet "telkens wanneer het uur verspringt", wel telkens een uur na de vorige synchronisatie.

  • Er is geen interval ingesteld voor de synchronisatie met SNTP. Standaard - in dit geval dus - staat dat interval op 1 uur (ESP8266), dat is ruimschoots voldoende om de correcte tijd te verkrijgen. Indien je een ander interval zou willen instellen, in het voorbeeld hieronder (SSD1306-display) zie je op de laatste lijnen hoe dat kan. Update wintertijd / zomertijd gebeurt ook zonder synchronisatie). De update via NTP gebeurt nl. steeds in UTC-tijd, nooit in lokale tijd. En het is de lokale tijd die de zomertijd of wintertijd aanstuurt.

Waar zijn de bibliotheken?

Bij de vele voorbeelden van klokjes die je op internet vindt, zie je "NTPClient.h", "Time.h", "TimeLib.h" en "WiFiUDP.h". Waar zijn die naartoe in deze sketch?

Kort antwoord: je hebt die niet nodig. Time.h is ingebouwd in de ESP core. We moeten deze bibliotheken dus niet toevoegen aan de lijst van de Arduino IDE, en we moeten ze niet declareren in deze sketches.





TM1637-display + ESP32

TM1637-display + ESP32

Volgende code voor ons "minimumklokje" is aangepast voor zowel de ESP8266 als de ESP32. Het standaard interval voor de tijdsynchronisatie voor de ESP32 is 3 uur, voor de ESP8266 is dat 1 uur. De enige aanpassingen zijn de GPIO-aansluitingen van het display:

Verbindingen:

ESP32 ---> TM1637
=================
GND --------> GND
3V3 --------> VCC
G21 --------> DIO
G22 --------> CLK


Sketch:

#include <WiFiManager.h>    // https://github.com/tzapu/WiFiManager/
#include <TM1637Display.h>  // https://github.com/avishorp/TM1637/

struct tm tInfo;  // https://cplusplus.com/reference/ctime/tm/
WiFiManager myWiFi;
#if defined(ESP32)  
TM1637Display ledSeg(22, 21);  // scl (clk) -> G22 / sda (dio) -> G21.
#elif defined(ESP8266)
TM1637Display ledSeg(5, 4);  // scl (clk) -> GPIO5 (D1) / sda (dio) -> GPIO4 (D2).
#endif

void setup() {
  Serial.begin(115200);                                           // watch serial monitor in case of new WiFi-connection
  myWiFi.autoConnect("TM1637_ESP");                               // connect ESP to new wifi via GSM or PC in that case
  ledSeg.setBrightness(2);                                        // 0 .. 7
  configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "be.pool.ntp.org");  // https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
}

void loop() {
  getLocalTime(&tInfo);                                                                           // sntp sync at startup & every hour from then on
  ledSeg.showNumberDecEx(tInfo.tm_hour * 100 + tInfo.tm_min, 0x40 * (millis() % 1000 < 500), 1);  // 0x40 = ":", 1 = leading zeros
}

We zouden deze code kunnen uitbreiden, zodat het klokje, als het een WiFi-verbinding aangaat tijdens de opstart, zelf zijn locatie opzoekt via een API. Dan kan de tijdzone automatisch toegekend worden. Zo zou je een NTP-klokje kunnen maken met de ESP, dat je om het even waar ter wereld kan gebruiken. Na het verbinden met een beschikbare WiFi geeft het de correcte locale tijd weer.



SSD1306-display + ESP8266 | ESP32

SSD1306-display + ESP8266 | ESP32

Dit is een klein en goedkoop display (0.96"), eveneens eenvoudige aansluiting via dezelfde pinnen als I2C. Hier kan heel wat meer informatie op in vergelijking met LED-segment displays. Deze code kan zonder wijzigingen zowel op de ESP8266 als op de ESP32 gezet worden. Uiteraard moet SCL en SDA naar resp. D1 (GPIO5) en D2 (GPIO4)[ESP8266] of naar 22 en 21 [ESP32].

In de code hieronder hebben we een interval van 4 uur ingesteld om te synchroniseren met SNTP.

Amazon: SSD1306
Amazon: SSD1306 (blauwe tekst)

   



Verbindingen:

ESP32 ---> SDD1306 / SH1106
===========================
GND  --------> GND
3V3  --------> VCC
G21  --------> SDA
G22  --------> SCL
===========================
ESP8266 -> SDD1306 / SH1106
===========================
GND  --------> GND
3V3  --------> VCC
D2   --------> SDA
D1   --------> SCL


Sketch:

#include <WiFiManager.h>  // https://github.com/tzapu/WiFiManager/
#if defined(ESP32) 
#include <esp_sntp.h>  // ESP32 core
#endif
#include <Adafruit_SSD1306.h>
#include <Fonts/FreeSansBold12pt7b.h>
#include <Fonts/FreeSansBold18pt7b.h>

WiFiManager myWiFi;
Adafruit_SSD1306 display(128, 64, &Wire, -1); // GND -> GND, 3V3 -> VCC, esp32: G21 -> SDA, G22 -> SCL
struct tm tInfo;  // https://cplusplus.com/reference/ctime/tm/          esp8266: D1 -> SCL,  D2 -> SDA

void setup() {
  Serial.begin(115200);  // watch serial monitor in case of new WiFi-connection
  display.begin(2, 0x3C);
  display.setTextColor(1);  // white = 1, black = 0
  display.setFont(&FreeSansBold12pt7b);
  myWiFi.autoConnect("SSD1306_ESP");  // connect ESP to new wifi via GSM or PC if new WiFi-connection
#if defined(ESP32)
  sntp_set_sync_interval(4 * 60 * 60 * 1000UL);
#endif
  configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "be.pool.ntp.org");
}

void loop() {
  getLocalTime(&tInfo);  // sntp sync at startup & every 4 hours from then on
  display.clearDisplay();
  display.drawRect(0, 0, 128, 40, 1);
  display.setFont(&FreeSansBold18pt7b);
  display.setCursor(4, 31);
  display.printf("%02d:%02d", tInfo.tm_hour, tInfo.tm_min);
  display.setFont(&FreeSansBold12pt7b);
  display.printf(":%02d", tInfo.tm_sec);
  display.setCursor(4, 63);
  display.printf("%02d-%02d-%04d", tInfo.tm_mday, 1 + tInfo.tm_mon, 1900 + tInfo.tm_year);
  display.display();
}
#if defined(ESP8266)
uint32_t sntp_update_delay_MS_rfc_not_less_than_15000() {  // declare only, unnecessary to call this function.
  return 4 * 60 * 60 * 1000UL;                             // NTP sync every 4 hr
}
#endif



SH1106-display + ESP8266 | ESP32

SH1106-display + ESP8266 | ESP32

Dit is een iets groter display (1.3"), eveneens eenvoudige aansluiting via dezelfde pinnen als I2C. In vergelijking met de vorige code hebben we hier de gebruikte bibliotheek gewijzigd, en het interval voor tijdsynchronisatie weggelaten. Ook deze code kan zonder wijzigingen zowel op de ESP8266 als op de ESP32 gezet worden. Uiteraard moet SCL en SDA naar resp. D1 (GPIO5) en D2 (GPIO4)[ESP8266] of naar 22 en 21 [ESP32].

Aliexpress: SH1106 (blauwe tekst)



Verbindingen:

ESP32 ---> SDD1306 / SH1106
===========================
GND  --------> GND
3V3  --------> VCC
G21  --------> SDA
G22  --------> SCL
===========================
ESP8266 -> SDD1306 / SH1106
===========================
GND  --------> GND
3V3  --------> VCC
D2   --------> SDA
D1   --------> SCL


Sketch:

#include <WiFiManager.h>      // https://github.com/tzapu/WiFiManager/
#include <Adafruit_SH110X.h>  // https://github.com/adafruit/Adafruit_SH110X
#include <Fonts/FreeSansBold12pt7b.h>
#include <Fonts/FreeSansBold18pt7b.h>

WiFiManager myWiFi;  
Adafruit_SH1106G display = Adafruit_SH1106G(128, 64, &Wire, -1);  // GND -> GND, 3V3 -> VCC, esp32: G21 -> SDA, G22 -> SCL
struct tm tInfo;  // https://cplusplus.com/reference/ctime/tm/          esp8266: D1 -> SCL,  D2 -> SDA

void setup() {
  Serial.begin(115200);       // watch serial monitor in case of new WiFi-connection
  display.begin(0x3C, true);  // Address 0x3C default
  display.setTextColor(1);    // white = 1, black = 0
  display.setFont(&FreeSansBold12pt7b);
  myWiFi.autoConnect("SSD1306_ESP");  // connect ESP to new wifi via GSM or PC if new WiFi-connection
  configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "be.pool.ntp.org");
}

void loop() {
  getLocalTime(&tInfo);  // sntp sync at startup & every hour from then on (esp8266) - every 3 hours (esp32)
  display.clearDisplay();
  display.drawRect(0, 0, 128, 40, 1);
  display.setFont(&FreeSansBold18pt7b);
  display.setCursor(4, 31);
  display.printf("%02d:%02d", tInfo.tm_hour, tInfo.tm_min);
  display.setFont(&FreeSansBold12pt7b);
  display.printf(":%02d", tInfo.tm_sec);
  display.setCursor(4, 63);
  display.printf("%02d-%02d-%04d", tInfo.tm_mday, 1 + tInfo.tm_mon, 1900 + tInfo.tm_year);
  display.display();
}



ESP8266 + geïntegreerd display SSD1306

ESP8266 + geïntegreerd display SSD1306

AliExpress: ESP8266 V3 (Blauw-geel - aangesloten: SDA->D6, SCL->D5)
AliExpress: ESP8266 V2 - zonder pinnen (Wit - aangesloten: SDA->D1, SCL->D2)

Deze ESP8266 is zeer handig. Je hebt geen bord of draden nodig om het display te verbinden, het display is "aan boord". Gewoon op de USB van je PC aansluiten en je kan een WiFi-netwerkscanner of een klokje programmeren.

De SCL (Serial clock) van het display is reeds verbonden met D5 (GPIO14) van de ESP8266, en de SDA (Serial data) met D6 (GPIO12).

We gebruiken hier een andere bibliotheek voor het display, "SSD1306.h". In de getoonde sketch wordt deze bibliotheek impliciet opgestart door de tweede lijn #include <SSD1306Wire.h>.
Deze bibliotheek heeft enkele handige mogelijkheden, zoals automatisch centreren. Er zijn 3 fonts geïntegreerd in "SSD1306.h".

Sketch:

#include <WiFiManager.h>  // https://github.com/tzapu/WiFiManager/
#include <SSD1306Wire.h>  // https://github.com/ThingPulse/esp8266-oled-ssd1306

WiFiManager myWiFi;
SSD1306Wire display(0x3c, D6, D5);  // ADDRESS, SDA, SCL
struct tm tInfo;                    // https://cplusplus.com/reference/ctime/tm/
char days[7][10] = { "zondag", "maandag", "dinsdag", "woensdag", "donderdag", "vrijdag", "zaterdag" };

void setup() {
  Serial.begin(115200);               // watch serial monitor in case of new WiFi-connection
  myWiFi.autoConnect("SSD1306_ESP");  // connect ESP to new wifi via GSM or PC if new WiFi-connection
  configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "pool.ntp.org");
  display.init();
  display.setContrast(255);
  display.setTextAlignment(TEXT_ALIGN_CENTER);
}

void loop() {
  char hourMin[6], second[3], theDate[11];
  getLocalTime(&tInfo);                              // sntp sync at startup & every hour from then on
  strftime(hourMin, sizeof(hourMin), "%R", &tInfo);  // https://cplusplus.com/reference/ctime/strftime
  strftime(second, sizeof(second), "%S", &tInfo);
  strftime(theDate, sizeof(theDate), "%d-%m-%G", &tInfo);
  display.clear();
  display.setFont(ArialMT_Plain_24);  // display hour:minute in a larger font
  display.drawString(54, 0, hourMin);
  display.setFont(ArialMT_Plain_16);
  display.drawString(95, 7, second);
  display.drawString(64, 26, theDate);
  display.drawString(64, 45, days[tInfo.tm_wday]);
  display.display();
}

Een tweede versie, ditmaal voor de zwart-witte versie van AliExpress


SDA van dit display is aangesloten op D1, SCL op D2.

We hebben een optie toegevoegd aan de code: als de knop "flash" (naast de USB-aansluiting) drie seconden ingedrukt wordt, moeten de WiFi-gegevens gewist worden. Uiteraard gebruiken we geen "delay()". De seriële monitor werd weggelaten, en er werd een functie bijgezet om aan de gebruiker te tonen als er geen WiFi verbinding ingesteld is.

Sketch:

#include <WiFiManager.h>  // https://github.com/tzapu/WiFiManager/
#include <SSD1306Wire.h>  // https://github.com/ThingPulse/esp8266-oled-ssd1306

WiFiManager myWiFi;
SSD1306Wire display(0x3c, D1, D2);  // ADDRESS, SDA, SCL (D1, D2)

struct tm tInfo;  // https://cplusplus.com/reference/ctime/tm/
char days[7][10] = { "zondag", "maandag", "dinsdag", "woensdag", "donderdag", "vrijdag", "zaterdag" };
unsigned long startC;
bool pressed = false;

void setup() {
  display.init();
  display.setContrast(255);
  display.setTextAlignment(TEXT_ALIGN_CENTER);
  myWiFi.setAPCallback(messageNoConnection);  // is called when no wifi was set up yet
  myWiFi.autoConnect("DUINO");                // connect ESP to new wifi via GSM or PC if new WiFi-connection
  configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "pool.ntp.org");
}

void loop() {
  char hourMin[6], second[3], theDate[11];
  getLocalTime(&tInfo);                              // sntp sync at startup & every hour from then on
  strftime(hourMin, sizeof(hourMin), "%R", &tInfo);  // https://cplusplus.com/reference/ctime/strftime
  strftime(second, sizeof(second), "%S", &tInfo);
  strftime(theDate, sizeof(theDate), "%d-%m-%G", &tInfo);
  display.clear();
  display.setFont(ArialMT_Plain_24);  // display hour:minute in a larger font
  display.drawString(54, 0, hourMin);
  display.setFont(ArialMT_Plain_16);
  display.drawString(95, 7, second);
  display.drawString(64, 26, theDate);
  display.drawString(64, 45, days[tInfo.tm_wday]);
  display.display();
  buttonFlashPressed_3_Sec();
}

void buttonFlashPressed_3_Sec() {  // flash button pressed during 3 seconds: erase WiFi credentials
  if (!digitalRead(0) && (pressed) && (millis() - startC > 3000)) myWiFi.resetSettings(), ESP.restart();
  if (!digitalRead(0)) {
    if (!pressed) startC = millis();  // first time the loop encounters the pressed button: start counter
    pressed = true;                   // Indicate that the button was previously pressed
  } else pressed = false;             // button released before the 3 seconds expired.
}

void messageNoConnection(WiFiManager* myWiFi) {
  display.setFont(ArialMT_Plain_10);
  display.clear();
  display.drawString(64, 0, "WiFi: no connection.");
  display.drawString(64, 10, "Connect to hotspot DUINO");
  display.drawString(64, 20, "and open a browser at");
  display.drawString(64, 30, "address 192.168.4.1");
  display.drawString(64, 40, "to enter network name");
  display.drawString(64, 50, "and password");
  display.display();
}



SPI-display ST7735 (160*124px) + ESP8266

SPI-display ST7735 (160*124px) + ESP8266

AliExpress: ST7735

Met de bibliotheek van Adafruit veroorzaakt deze code flikkering op het scherm. Daarom is hier gekozen voor de (veel snellere) bibliotheek TFT_eSPI. Je moet een bestand "User_Setup.h".in de map van de bibliotheek "TFT_eSPI" plaatsen (Opgelet: zie tip hieronder).

Verbindingen:

ESP8266   ======   128*160 display
==================================
  3V3   --------------- VCC
  GND   --------------- GND
  D8    --------------- CS
  D4    --------------- RESET
  D3    --------------- A0
  D7    --------------- SDA
  D5    --------------- SCK
  3V3   --------------- LED


Sketch:

#include <WiFiManager.h>  // https://github.com/tzapu/WiFiManager/
#include <TFT_eSPI.h>     // https://github.com/Bodmer/TFT_eSPI

WiFiManager myWiFi;
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite sprite = TFT_eSprite(&tft);
struct tm tInfo;  // https://cplusplus.com/reference/ctime/tm/
char weekDay[7][10] = { "zondag", "maandag", "dinsdag", "woensdag", "donderdag", "vrijdag", "zaterdag" };

void setup() {
  tft.init(), tft.setRotation(1);
  myWiFi.setAPCallback(showMessageNoConnection);  // is called when no wifi was found
  myWiFi.autoConnect("ST7735_ESP");
  sprite.createSprite(160, 128);  // sprite for faster rendering
  configTzTime(PSTR("CET-1CEST,M3.5.0,M10.5.0/3"), "be.pool.ntp.org");
  sprite.setTextSize(1);  // time zones: https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
}

void loop() {
  getLocalTime(&tInfo);  // sntp sync at startup & every hour from then on (esp8266)
  sprite.fillScreen(TFT_BLACK);
  sprite.setTextColor(TFT_CYAN, TFT_BLACK);
  sprite.setTextFont(7), sprite.setCursor(0, 0, 7);
  sprite.printf("%02d:%02d", tInfo.tm_hour, tInfo.tm_min);
  sprite.setCursor(142, 34, 2);
  sprite.printf("%02d", tInfo.tm_sec);
  sprite.setTextColor(TFT_YELLOW, TFT_BLACK), sprite.setFreeFont(&FreeSansBold12pt7b);  // custom font
  sprite.drawCentreString(weekDay[tInfo.tm_wday], 80, 66, 1);
  sprite.setTextColor(TFT_GREEN, TFT_BLACK), sprite.setCursor(20, 120, 4);
  sprite.printf("%02d-%02d-%04d", tInfo.tm_mday, 1 + tInfo.tm_mon, 1900 + tInfo.tm_year);  // dd-mm-yyyy
  sprite.drawRect(-1, 56, 162, 40, TFT_WHITE);
  sprite.pushSprite(0, 0);
}

void showMessageNoConnection(WiFiManager* myWiFi) {
  tft.fillScreen(TFT_NAVY);
  tft.setTextColor(TFT_YELLOW), tft.setCursor(0, 0, 2);
  tft.print(F("WiFi: no connection\nConnect to hotspot\nST7735_ESP and open\n"));
  tft.print(F("a browser at address\n192.168.4.1\nto enter network name\nand password"));
}

Inhoud van het bestand "User_Setup.h" in het geval van een ST7735 display vind je op

https://github.com/Bodmer/TFT_eSPI/blob/master/User_Setups/Setup2_ST7735.h

TFT_eSPI - Opgelet:

Om hier een gemakkelijk overzicht weer te geven passen we de inhoud van het bestand "User_Setup.h" aan. Dat is een snelle oplossing als je 1 scherm aan de praat wil krijgen en vlug resultaat wil zien.

Als je meerdere displays gebruikt en / of geen gegevens wil verliezen als je een nieuwe kopij van deze bibliotheek installeert, kan je beter de tip volgen van de ontwerper van de bibliotheek:

https://github.com/Bodmer/TFT_eSPI/#tips

Je kan de volledige inhoud van "User_Setup.h" uitcommenten als je zo werkt.




2.13 inch e-paper-display + ESP32

2.13 inch e-paper-display + ESP32

Amazon: 2.13inch e-paper

De code van de sketch hieronder kan je ongewijzigd gebruiken voor de LILYGO TTGO T5: AliExpress

Voordeel van een e-paper display is dat het geen stroom vereist om info op het scherm te tonen. Enkel een klein beetje om gegevens op het display te wijzigen. Wat je er op zet, blijft er permanent op staan, ook zonder enige stroom. Nadeel is een tragere update dan de tft-spi displays (daarom werden de seconden weggelaten), en je moet opletten hoe je het scherm "ververst" om geen hevige "flicker" te krijgen. De gebruikte bibliotheek heeft geen functie "drawCentreString", we moeten zelf een functie voorzien voor een string die we gecentreerd willen weergeven.

Verbindingen:

ESP32 ---> 2.13 inch e-paper
============================
G4   --------> BUSY
G16  --------> RST
G17  --------> DC
G5   --------> CS
G18  --------> CLK
G23  --------> DIN
GND  --------> GND
3V3  --------> VCC


Sketch:

#include <GxEPD.h>
#include <GxDEPG0213BN/GxDEPG0213BN.h>  // 2.13" black/white 128x250 px
#include <WiFiManager.h>                // https://github.com/tzapu/WiFiManager/
#include <esp_sntp.h>                   // ESP32 core
#include <Fonts/FreeSansBold24pt7b.h>
#include <Fonts/FreeSansBold18pt7b.h>
#include <Fonts/FreeSansBold9pt7b.h>
#include <GxIO/GxIO_SPI/GxIO_SPI.h>
#include <GxIO/GxIO.h>

char days[7][10] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
int previousMinute;

GxIO_Class io(SPI, /*CS=5*/ SS, /*DC=*/17, /*RST=*/16);  // 17 & 16 = arbitrary choice
GxEPD_Class display(io, /*RST=*/16, /*BUSY=*/4);         // 16 & 4 = arbitrary choice
struct tm tInfo;                                         // https://cplusplus.com/reference/ctime/tm/
WiFiManager myWiFi;  

void setup() {
  display.init();
  display.setRotation(1);                          // landscape
  display.setTextColor(GxEPD_BLACK, GxEPD_WHITE);  // GxEPD_BLACK = 0x0000, GxEPD_WHITE = 0xFFFF
  myWiFi.setAPCallback(noConnection);              // on screen instructions for wifi connection
  myWiFi.autoConnect("E-PAPER-ESP32");             // will also reconnect after connection was lost
  sntp_set_sync_interval(4 * 60 * 60 * 1000UL);    // sntp sync every 4 hr
  configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "be.pool.ntp.org");
  display.fillScreen(GxEPD_WHITE);
  display.update();  // fully updating the display causes flicker for 2 seconds
}

void loop() {
  char hourMin[6], theDate[11];
  getLocalTime(&tInfo);
  if (previousMinute != tInfo.tm_min) {                      // update display only when minute changes
    strftime(hourMin, sizeof(hourMin), "%R", &tInfo);        // https://cplusplus.com/reference/ctime/strftime
    strftime(theDate, sizeof(theDate), "%d-%m-%G", &tInfo);  // this display is too slow to show the seconds
    display.fillScreen(GxEPD_WHITE);
    showCentered(39, hourMin);
    showCentered(84, days[tInfo.tm_wday]);
    showCentered(126, theDate);
    previousMinute = tInfo.tm_min;
    display.updateWindow(66, 0, 116, 41, true);  // partial update time (= no flicker)
    display.updateWindow(0, 50, 250, 78, true);  // partial update date
    display.powerDown();
  }
}

void showCentered(byte myHeight, const char* myText) {
  display.setTextColor(GxEPD_WHITE);
  display.setFont(strlen(myText) > 9 ? &FreeSansBold18pt7b : &FreeSansBold24pt7b);  // date = smaller font
  display.setCursor(0, myHeight);
  display.print(myText);
  display.setTextColor(GxEPD_BLACK);
  display.setCursor((display.width() - display.getCursorX()) / 2, myHeight);
  display.print(myText);
}

void noConnection(WiFiManager* myWiFi) {
  display.fillScreen(GxEPD_WHITE);
  display.setFont(&FreeSansBold9pt7b);
  display.setTextSize(1);
  showNoConnection(18, F("WiFi: no connection."));
  showNoConnection(35, F("Connect to hotspot:"));
  showNoConnection(52, F("E-PAPER-ESP32 and open"));
  showNoConnection(69, F("a browser at address"));
  showNoConnection(86, F("192.168.4.1"));
  showNoConnection(103, F("to enter network name"));
  showNoConnection(120, F("and password"));
  display.update();
}

void showNoConnection(byte top, const __FlashStringHelper* tekst) {
  display.setCursor(0, top);  // allows for 7 lines on a 128 px height display
  display.print(tekst);       // println without setCursor = interline of text is too large
}



SPI-display ST7789 (320*240px) + ESP8266

SPI-display ST7789 (320*240px) + ESP8266

Amazon: 2 inch display

Dit display heeft een resolutie van 320 * 240 pixels, en geeft een zeer scherp beeld.
Het scherm heeft een hoge refresh rate, je zou - indien gewenst - honderdsten van een seconde kunnen weergeven. De snelste en meest veelzijdige bibliotheek voor dergelijke displays is TFT-eSPI (Bodmer). Er zijn talloze voorbeelden bij, analoge klokjes, fonts, sprites, verschillende metertjes, het spel "pong", grafieken en fractals.

  • weergave tot 100 frames per seconde
  • we kunnen met kleuren werken
  • met deze bibliotheek kunnen we centreren met een functie "drawCentreString"
  • het display heeft de ST7789-driver (ondersteund door meerdere bibliotheken)
  • op dit klokje geven we ook de WiFi-status weer (groen of rood = resp. verbonden of niet)

De code bevat een 10-tal lijnen meer dan die van de e-paper hierboven, omdat we voor het synchronisatie-interval en de callback van de synchronisatie functies voorzien.
Omdat de bibliotheek TFT_eSPI meerdere displays ondersteunt, moet je een bestandje "User_Setup.h" instellen (of een voorgedefinieerde setup kiezen uit de folder "User_Setups" van de bibliotheek TFT_eSPI). Onder de code ga ik daar verder op in. Ook de verbindingen (ESP8266 - display) vind je hieronder.

Verbindingen:

ESP8266	|   2 inch Waveshare (ST7739V driver) | 2.4 inch Waveshare (ILI9341 driver)
===============================
GND	------- GND
3V3	------- VCC
D3	------- DC
RST	------- RST
D5	------- CLK
D7	------- DIN
D8	------- CS
        ------- BL (not connected - niet verbonden)

Sketch:

#include <WiFiManager.h>  // https://github.com/tzapu/WiFiManager/
#include <TFT_eSPI.h>     // https://github.com/Bodmer/TFT_eSPI
#include <coredecls.h>    // ESP8266 core

struct tm tInfo;  // https://cplusplus.com/reference/ctime/tm/
char days[7][10] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
byte previousHour = 24, previousMinute = 61, previousSecond;
String previousWeekDay;

TFT_eSPI tft = TFT_eSPI();
WiFiManager myWiFi;

void setup() {
  Serial.begin(115200);
  tft.init();
  tft.setRotation(1);
  tft.fillScreen(TFT_BLACK);  // clear screen from any previous session
  myWiFi.setAPCallback(showMessageNoConnection);
  myWiFi.autoConnect("ST7789_ESP");
  settimeofday_cb(esp8266NtpCallback);  // we need "#include <coredecls.h>" for this
  tft.fillScreen(TFT_BLACK);            // clear screen from "showMessageNoConnection" messages
  configTzTime(PSTR("CET-1CEST,M3.5.0,M10.5.0/3"), "be.pool.ntp.org");
  tft.setTextColor(TFT_CYAN);
  tft.setTextSize(2);
  tft.setCursor(117, 14, 7);
  tft.print(":");
}

void loop() {
  showChangesOnDisplay();
  WiFi.isConnected() ? showLogoWiFi(TFT_GREEN) : showLogoWiFi(TFT_RED);
}

void showTime(char timeChar[2], int leftS, int top, bool large) {
  tft.setCursor(leftS, top, 7);  // 7 is a built in font
  tft.setTextColor(TFT_CYAN, TFT_BLACK);
  tft.setTextSize(large + 1);
  tft.print(timeChar);
  tft.setTextSize(1);
}

void showChangesOnDisplay() {                             // this function divides the screen changes
  char hourChar[3], minChar[3], secChar[3], theDate[11];  // into chunks to prevent flicker
  getLocalTime(&tInfo);
  if (tInfo.tm_sec != previousSecond) {
    sprintf(secChar, "%02d", tInfo.tm_sec);  // format 2 digits decimal
    showTime(secChar, 256, 61, false);
    previousSecond = tInfo.tm_sec;
  }
  if (tInfo.tm_min != previousMinute) {
    sprintf(minChar, "%02d", tInfo.tm_min);
    showTime(minChar, 131, 14, true);
    previousMinute = tInfo.tm_min;
  }
  if (previousHour != tInfo.tm_hour) {
    sprintf(hourChar, "%02d", tInfo.tm_hour);
    showTime(hourChar, -2, 14, true);
    previousHour = tInfo.tm_hour;
     if (previousWeekDay != days[tInfo.tm_wday]) {
      tft.fillRect(0, 134, 320, 106, TFT_BLACK);
      tft.setTextColor(TFT_YELLOW);
      tft.setFreeFont(&FreeSansBold24pt7b);  // custom font. &FreeMonoBold24pt7b is possible too
      tft.drawCentreString(days[tInfo.tm_wday], 160, 136, 1);
      tft.drawRoundRect(0, 124, 320, 70, 3, TFT_WHITE);
      tft.setTextColor(TFT_GREEN);
      strftime(theDate, sizeof(theDate), "%d-%m-%G", &tInfo);
      tft.drawCentreString(theDate, 160, 202, 1);
      tft.setTextFont(7);
      previousWeekDay = days[tInfo.tm_wday];
    }
  }
}

void showLogoWiFi(int myColor) {
  tft.fillCircle(294, 30, 6, myColor);
  for (byte tel = 0; tel < 3; tel++) {
    tft.drawSmoothArc(294, 32, 30 - tel * 7, 28 - tel * 7, 135, 225, myColor, myColor, true);
  }
}

void showMessageNoConnection(WiFiManager* myWiFi) {
  tft.setCursor(0, 0, 4);
  tft.setTextColor(TFT_GREEN, TFT_BLACK);
  tft.print(F("\nWiFi: no connection\nConnect to hotspot:\n"));
  tft.print(F("ST7789_ESP and open\na browser at address \n192.168.4.1\n"));
  tft.print(F("to enter network name\nand password"));
}

void esp8266NtpCallback(bool from_sntp) {  // in setup: "settimeofday_cb(esp8266NtpCallback)";
  if (from_sntp) Serial.printf("\n*** NTP Sync ***\n%s \n", getenv("TZ"));
}

uint32_t sntp_update_delay_MS_rfc_not_less_than_15000() {  // declare only, unnecessary to call this function
  return 4 * 60 * 60 * 1000UL;                             // SNTP sync every 4 hr
}

Je vindt bij de voorgedefinieerde "User_Setups" de meest voorkomende configuraties. Bijvoorbeeld de "Setup18_ST7789.h" in deze folder is bruikbaar voor dit project. Moesten de weergegeven kleuren niet kloppen (rood en groen verwisseld: ligt eigenlijk aan de hardwarefabrikanten): je kan de 3de lijn actief zetten en de 4de lijn uitcommenten. Opgelet: zie tip (rode tekst) hieronder.

//            ST7789
#define USER_SETUP_INFO "User_Setup"
#define ST7789_DRIVER 
// #define TFT_RGB_ORDER TFT_RGB  // Colour order Red-Green-Blue
#define TFT_RGB_ORDER TFT_BGR  // Colour order Blue-Green-Red
#define TFT_HEIGHT 320 // ST7789 240 x 320
#define TFT_CS   PIN_D8  // Chip select control pin D8
#define TFT_DC   PIN_D3  // Data Command control pin
#define TFT_RST  -1    // Set TFT_RST to -1 if the display RESET is connected to NodeMCU RST or 3.3V
// fonts die je niet gebruikt best uitcommenten om FLASH te besparen
#define LOAD_GLCD   // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH
#define LOAD_FONT2  // Font 2. Small 16 pixel high font, needs ~3534 bytes in FLASH, 96 characters
#define LOAD_FONT4  // Font 4. Medium 26 pixel high font, needs ~5848 bytes in FLASH, 96 characters
#define LOAD_FONT6  // Font 6. Large 48 pixel font, needs ~2666 bytes in FLASH, only characters 1234567890:-.apm
#define LOAD_FONT7  // Font 7. 7 segment 48 pixel font, needs ~2438 bytes in FLASH, only characters 1234567890:-.
#define LOAD_FONT8  // Font 8. Large 75 pixel font needs ~3256 bytes in FLASH, only characters 1234567890:-.
#define LOAD_FONT8N // Font 8. Alternative to Font 8 above, slightly narrower, so 3 digits fit a 160 pixel TFT
#define LOAD_GFXFF  // FreeFonts. Include access to the 48 Adafruit_GFX free fonts FF1 to FF48 and custom fonts

#define SPI_FREQUENCY  27000000
#define SPI_READ_FREQUENCY  20000000
#define SPI_TOUCH_FREQUENCY  2500000

TFT_eSPI - Opgelet:

Om hier een gemakkelijk overzicht weer te geven passen we de inhoud van het bestand "User_Setup.h" aan. Dat is een snelle oplossing als je 1 scherm aan de praat wil krijgen en vlug resultaat wil zien.

Als je meerdere displays gebruikt en / of geen gegevens wil verliezen als je een nieuwe kopij van deze bibliotheek installeert, kan je beter de tip volgen van de ontwerper van de bibliotheek:

https://github.com/Bodmer/TFT_eSPI/#tips

Je kan de volledige inhoud van "User_Setup.h" uitcommenten als je zo werkt.

De weekdag "vrijdag" wordt op beide displays weergegeven in dezelfde grootte en in exact hetzelfde lettertype. Je kan dat ook gewoon lezen in de code voor beide klokjes. Omdat het TFT-display een veel hogere resolutie heeft - vooral in de hoogte - zie je toch grote verschillen. [250 * 128] px vs. [320 * 240] px.

Op het arduino forum kan je hier zien hoe 3 prominente ontwikkelaars van bibliotheken (waarvan 2 bibliotheken hier gebruikt worden) elkaar verder helpen. Zo moet het! Kennis moet je delen, niet voor jezelf houden.



ESP32-3248S035: geïntegreerd display (480 * 320px)

ESP32-3248S035: geïntegreerd display (480 * 320px)

Aliexpress: 3.5" capacitive

Dit is een handige combinatie van een ESP32 en een relatief groot aanraakscherm. Je vindt weinig voorbeelden van deze combinatie voor de Arduino IDE.

Het touch-gedeelte gebruiken we in dit voorbeeld niet (maar dat werkt wel perfect), je kan dat gewoon laden (en testen) uit de voorbeelden van de bibliotheek "TFT_eSPI".

In deze schets halen we via een API de dynamische elektriciteitsprijzen op (Europa), om ze op het display weer te geven. We kunnen met de flashknop de weergave wijzigen in numerieke gegevens of grafiek.

Uiteraard is er geen verbindingsschema om weer te geven, maar het is wel een hele zoektocht als je dit systeem wil configureren voor de Arduino IDE. We gebruiken weer de bibliotheek TFT-eSPI (Bodmer). Je moet een bestand "User_Setup.h".in de map van de bibliotheek "TFT_eSPI" plaatsen.

Inhoud van het bestand "User_Setup.h" voor de ESP32-3248S035:

#define ST7796_DRIVER
#define TFT_WIDTH  320
#define TFT_HEIGHT 480 // 
#define TFT_BL   27            // LED back-light control pin
#define TFT_BACKLIGHT_ON HIGH  // Level to turn ON back-light (HIGH or LOW)

#define TFT_MISO 12
#define TFT_MOSI 13  // In some display driver board, it might be written as "SDA" and so on.
#define TFT_SCLK 14
#define TFT_CS   15  // Chip select control pin
#define TFT_DC   2   // Data Command control pin
#define TFT_RST  -1  // Reset pin (could connect to Arduino RESET pin)

#define TOUCH_CS 33     // Chip select pin (T_CS) of touch screen

#define LOAD_GLCD   // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH
#define LOAD_FONT2  // Font 2. Small 16 pixel high font, needs ~3534 bytes in FLASH, 96 characters
#define LOAD_FONT4  // Font 4. Medium 26 pixel high font, needs ~5848 bytes in FLASH, 96 characters
#define LOAD_FONT6  // Font 6. Large 48 pixel font, needs ~2666 bytes in FLASH, only characters 1234567890:-.apm
#define LOAD_FONT7  // Font 7. 7 segment 48 pixel font, needs ~2438 bytes in FLASH, only characters 1234567890:-.
#define LOAD_FONT8  // Font 8. Large 75 pixel font needs ~3256 bytes in FLASH, only characters 1234567890:-.
// #define LOAD_FONT8N // Font 8. Alternative to Font 8 above, slightly narrower, so 3 digits fit a 160 pixel TFT
#define LOAD_GFXFF  // FreeFonts. Include access to the 48 Adafruit_GFX free fonts FF1 to FF48 and custom fonts

// Comment out the #define below to stop the SPIFFS filing system and smooth font code being loaded
// this will save ~20kbytes of FLASH
#define SMOOTH_FONT

#define SPI_FREQUENCY  65000000
#define SPI_READ_FREQUENCY  20000000
#define SPI_TOUCH_FREQUENCY  2500000  //2500000

TFT_eSPI - Opgelet:

Om hier een gemakkelijk overzicht weer te geven passen we de inhoud van het bestand "User_Setup.h" aan. Dat is een snelle oplossing als je 1 scherm aan de praat wil krijgen en vlug resultaat wil zien.

Als je meerdere displays gebruikt en / of geen gegevens wil verliezen als je een nieuwe kopij van deze bibliotheek installeert, kan je beter de tip volgen van de ontwerper van de bibliotheek:

https://github.com/Bodmer/TFT_eSPI/#tips

Je kan de volledige inhoud van "User_Setup.h" uitcommenten als je zo werkt.

Sketch:

/*==========================================================================================
This sketch was written for the ESP32-3248S035 with integrated display (480 * 320 pixels). 
If a WiFi connection cannot be made, a web server is started to store the WiFi credentials.
Dynamic electricity prices (Europe) are retrieved via an API and displayed on the screen. 
The flash button switches the display between figures in two columns or graph. 
Prices for the next day are available between 1200 hours and 2100 hours (UTC). 
The script will check (starting at 1200 UTC) every hour if new API data is available. 
The script keeps repeating this every hour until it succeeds. If the first bidding zone  
returns no results any more, the script tries the second zone. If only the second bidding 
zone produces results, every hour an attempt is made to obtain the data for the first zone. 
=============================================================================================
Arduino IDE - board: ESP32 Dev Module or DOIT ESP32 DEVKIT V1
https://nl.aliexpress.com/item/1005006398547061.html
https://nl.aliexpress.com/item/1005005900820162.html
==========================================================================================*/

#include <WiFiManager.h>  // https://github.com/tzapu/WiFiManager/
#include <TFT_eSPI.h>     // https://github.com/Bodmer/TFT_eSPI
#include <ArduinoJson.h>  // https://github.com/bblanchon/ArduinoJson
#include <HTTPClient.h>   // for API connection
#include <Preferences.h>  // no need to install this library

TFT_eSPI tft = TFT_eSPI();  // User_Setup.h settings available on: https://espgo.be/index-en.html#2035
WiFiManager myWiFi;
Preferences flash;

#define SHOW_ITEMS 34  // max number of records you want to process
#define this_Ssid "S035_ESP"

struct tm tInfo;  // https://cplusplus.com/reference/ctime/tm/
bool chart = true, bidLU = false, freshStart = true, cursorUpdated = false;
unsigned long lastAction;
constexpr uint8_t VALUE_RED = 100, FLASH_BUTTON = 0;  // electricity price in red color if higher than VALUE_RED
const char* biddingZone[] = { "BE", "DE-LU" };        // default & backup bidding zone
double prijzen[SHOW_ITEMS];                           // most recent prices
double prices[SHOW_ITEMS * 2];                        // all prices returned by the API
double priceNow;                                      // price of current hour
time_t epo[SHOW_ITEMS];                               // most recent epoch times
time_t epoc[SHOW_ITEMS * 2];                          // all epoch times returned by the API

void setup() {
  const char* time_Zone = "CET-1CEST,M3.5.0,M10.5.0/3";  // https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
  pinMode(FLASH_BUTTON, INPUT_PULLUP);
  for (const uint8_t &pin : {4, 16, 17}) pinMode(pin, OUTPUT), digitalWrite(pin, HIGH); // red, green & blue LEDs off
  tft_Setup();
  getBidZoneFromFlash();
  myWiFi.setAPCallback(messageNoConnection);
  myWiFi.autoConnect(this_Ssid);
  configTzTime(time_Zone, "be.pool.ntp.org");
  showStartupData();
}

void loop() {
  display_Time();
  if (!digitalRead(FLASH_BUTTON)) changeView();  // flash button changes between chart or list of figures
  // if (priceNow < 0) start_Energy-Guzzling_device();  // priceNow = current electricity price
  // else stop_Energy-Guzzling_device();
}

void tft_Setup() {  // no sprites because the data on the screen is rather static
  tft.init();
  tft.setRotation(3);
  tft.setTextWrap(false);
  tft.fillScreen(TFT_BLACK);  // delete output from previous sessions
  tft.drawCentreString("Connecting to WIFi", 240, 160, 4);
}

void getBidZoneFromFlash() {
  flash.begin("bid_zone", true);         // read from flash, true = read only
  bidLU = flash.getBool("bid_zone", 0);  // retrieve the last set bidding zone - default to first in the array [0]
  flash.end();
}

bool timeElapsed(unsigned long myMillis) {  // avoid blocking task "delay(millisec)"
  return millis() - lastAction >= myMillis;
}

void showStartupData() {
  tft.fillScreen(TFT_BLACK);
  tft.drawCentreString("Retrieving SNTP time", 240, 160, 4);
  getLocalTime(&tInfo);
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_ORANGE);
  tft.drawCentreString("Retrieving Json Data", 240, 160, 4);
}

void changeView() {
  if (timeElapsed(800)) {  // UI debouncing (flash button)
    chart = !chart;
    chart ? show_Chart() : show_Data();
    showCurrentPrice();
    lastAction = millis();
  }
}

void display_Time() {
  char theDate[11], theTime[9];
  getLocalTime(&tInfo);  // SNTP sync at startup and every 3 hours thereafter (ESP32)
  time_t now;
  time(&now);  // assign current epoch time (UTC) to "now"
  tft.drawRect(-1, 26, 482, 2, WiFi.isConnected() ? TFT_GREEN : TFT_RED);
  strftime(theDate, sizeof(theDate), "%d-%m-%G", &tInfo);  // https://cplusplus.com/reference/ctime/strftime/
  strftime(theTime, sizeof(theTime), "%T", &tInfo);
  tft.setTextColor(TFT_GREEN, TFT_BLACK), tft.drawString(theDate, 2, 0, 4);
  tft.setTextColor(TFT_WHITE, TFT_BLACK), tft.drawString(theTime, 139, 0, 4);
  tft.drawRightString(bidLU ? "Lux" : "Bel", 480, 8, 2);
  if ((freshStart) || ((((epo[SHOW_ITEMS - 1] - now) / 3600) < 9) && (tInfo.tm_min == 2) && (tInfo.tm_sec == 1))) {
    if (WiFi.isConnected()) get_JsonData();  // ^^from 1200 hrs UTC: try to retrieve new data every hour until it succeeds
    chart ? show_Chart() : show_Data(), showCurrentPrice();
    freshStart = false;
  }
  if (tInfo.tm_min == 0) {
    if (tInfo.tm_sec == 0) tryDefaultZone();       // if not default bidding zone, try default zone every new hour
    if (tInfo.tm_sec == 1) cursorUpdated = false;  // reset update flag
  }
}

void tryDefaultZone() {  // if current bidding zone isn't the default bidding zone, try default zone
  if (!cursorUpdated) {  // prevent updating several times per cycle
    cursorUpdated = true;
    if (bidLU) {                         // if the current bidding zone isn't the default zone
      flash.begin("bid_zone", false);    // flash memory (false = also write)
      flash.putBool("bid_zone", false);  // store default bidding zone to flash memory
      flash.end();
      ESP.restart();  // restart because want to retrieve the data of the default bidding zone.
    }
    chart ? show_Chart() : show_Data();  // refresh local data every hour
    showCurrentPrice();
  }
}

void show_Chart() {
  tft.fillRect(0, 28, 480, 292, TFT_BLACK);       // erase any previous graphic data
  tft.setTextColor(tft.color565(160, 160, 160));  // set color once and adjust only when needed
  for (uint8_t i = 0; i < 6; i++) {               // draw horizontal grid lines and value labels
    tft.drawFastHLine(0, 40 + i * 50, 480, tft.color565(48, 48, 48));
    for (uint8_t j = 0; j < 4; j++) {
      tft.drawRightString(String(i * 100 - 100), j * 150 + 23, 286 - i * 50, 1);
    }
  }
  for (uint8_t i = 0; i < SHOW_ITEMS; i++) {
    if (epo[i]) {                           // only process if there is valid data
      struct tm* cor = localtime(&epo[i]);  // convert epoch to local time
      bool isCurrentHour = (cor->tm_hour == tInfo.tm_hour) && (cor->tm_mday == tInfo.tm_mday);
      if (isCurrentHour) {
        priceNow = prijzen[i];
        tft.drawFastVLine(6 + i * 14, 26, 292, TFT_MAGENTA);
        tft.fillTriangle((i * 14) - 4, 300, (i * 14) - 4, 310, 6 + i * 14, 305, TFT_MAGENTA);
        tft.fillTriangle((i * 14) - 4, 55, (i * 14) - 4, 65, 6 + i * 14, 60, TFT_MAGENTA);
      }
      uint16_t myColor;  // determine the color based on the value of prijzen[i]
      if (prijzen[i] < 0) myColor = TFT_YELLOW;
      else if (prijzen[i] < 50) myColor = TFT_GREEN;
      else if (prijzen[i] < VALUE_RED) myColor = TFT_CYAN;
      else myColor = TFT_RED;
      int price_scaled = prijzen[i] / 2;           // scale prices for display
      int height_offset = (480 - prijzen[i]) / 2;  // calculate offset for positive values
      if (prijzen[i] < 0) {                        // draw the price bar
        tft.fillRect(i * 14, 240, 13, 0 - price_scaled, myColor);
        tft.drawFastVLine(i * 14 + 13, 240, (0 - prijzen[i]) / 2, TFT_BLACK);
      } else {
        tft.fillRect(i * 14, height_offset, 13, price_scaled, myColor);
        tft.drawFastVLine(i * 14 + 13, height_offset, price_scaled, TFT_BLACK);
      }
      tft.setCursor(1 + i * 14, 230, 1);  // draw the hour label at the bottom of the chart
      tft.printf("%02d", cor->tm_hour);
      tft.setTextColor(TFT_RED);  // draw the hour label again (red)
      tft.setCursor(1 + i * 14, 243, 1);
      tft.printf("%02d", cor->tm_hour);
      tft.setTextColor(prijzen[i] > 0 ? TFT_BLACK : TFT_YELLOW);  // Reset text color to default
    }
  }
}

void show_Data() {  // display Json data in 2 columns (time / price)
  tft.fillRect(0, 28, 480, 292, TFT_BLACK);
  tft.drawRect(240, 26, 2, 292, WiFi.isConnected() ? TFT_GREEN : TFT_RED);
  struct tm* cor;
  uint16_t xPos, yPos;
  for (uint8_t i = 0; i < SHOW_ITEMS; i++) {
    if (epo[i]) {                 // check for valid data
      cor = localtime(&epo[i]);   // convert epoch to local time
      xPos = (i < 18) ? 0 : 260;  // calculate position for cursor
      yPos = 33 + (i % 18) * 16;
      tft.setTextColor(TFT_LIGHTGREY, TFT_BLACK);  // display index number
      tft.setCursor(xPos, yPos, 2);
      tft.printf(" %02d. ", i + 1);
      if ((cor->tm_hour == tInfo.tm_hour) && (cor->tm_mday == tInfo.tm_mday)) {  // check if current hour/day matches
        priceNow = prijzen[i];
        tft.setTextColor(TFT_MAGENTA, TFT_BLACK);
      } else tft.setTextColor(TFT_WHITE, TFT_BLACK);
      tft.printf("%02d-%02d-%02d %02d:%02d", cor->tm_mday, cor->tm_mon + 1, cor->tm_year + 1900, cor->tm_hour, cor->tm_min);
      if (prijzen[i] < 0) tft.setTextColor(TFT_YELLOW, TFT_BLACK);  // set color based on price value
      else if (prijzen[i] < 50) tft.setTextColor(TFT_GREEN, TFT_BLACK);
      else if (prijzen[i] < VALUE_RED) tft.setTextColor(TFT_CYAN, TFT_BLACK);
      else tft.setTextColor(TFT_RED, TFT_BLACK);
      tft.drawRightString(String(prijzen[i]), (i < 18 ? 220 : 480), yPos, 2);  // display the price on the right
    }
  }
}

void showCurrentPrice() {  // price (upper right corner of display)
  tft.setCursor(264, 0, 4);
  tft.setTextColor(TFT_SILVER, TFT_BLACK);
  tft.print("/MWh:");
  tft.fillCircle(255, 10, 9, TFT_SILVER);  // euro symbol
  tft.fillCircle(255, 10, 7, TFT_BLACK), tft.fillRect(258, 5, 8, 12, TFT_BLACK);
  tft.drawRect(247, 8, 9, 2, TFT_SILVER), tft.drawRect(249, 11, 6, 2, TFT_SILVER);
  tft.setTextColor(priceNow < VALUE_RED ? TFT_CYAN : TFT_RED);
  if (priceNow < 50) tft.setTextColor(TFT_GREEN);
  if (priceNow < 0) tft.setTextColor(TFT_YELLOW);
  tft.fillRect(344, 0, 100, 22, TFT_BLACK);
  tft.setCursor(344, 0, 4);
  tft.printf("%3.2F", priceNow);
}

void get_JsonData() {
  String my_link;
  uint8_t API_ITEMS;
  memset(prijzen, 0, sizeof(prijzen));
  memset(prices, 0, sizeof(prices));
  memset(epo, 0, sizeof(epo));
  memset(epoc, 0, sizeof(epoc));
  time_t now = mktime(&tInfo) - (mktime(&tInfo) % 3600);  // convert "struct tm" to time_t and round down to the whole hour
  my_link = String("https://api.energy-charts.info/price?bzn=") + biddingZone[bidLU] + "&start="
            + String(now - (24 * 3600)) + "&end="
            + String(now + (SHOW_ITEMS * 3600));
  HTTPClient http;
  http.begin(my_link);
  if (timeElapsed(3000)) {  // allows the http connection to be complete
    lastAction = millis();
    if (http.GET() >= 200 && http.GET() < 300) {
      JsonDocument doc;
      String json = http.getString();
      auto error = deserializeJson(doc, json.c_str());
      if (error) {
        tft.drawCentreString("deserializeJson() failed", 240, 200, 4);
        tft.drawCentreString(error.c_str(), 240, 250, 4);
      }
      for (uint8_t i = 0; i < SHOW_ITEMS * 2; i++) {  // actually more data than we can show on display
        prices[i] = doc["price"][i];
        epoc[i] = doc["unix_seconds"][i];
        API_ITEMS = i;  // count number of returned items
      }
    } else {  // http.get has failed
      tft.fillScreen(TFT_BLACK);
      tft.drawCentreString("Error in API response", 240, 160, 4);
      tft.drawCentreString("Restarting device", 240, 190, 4);
      flash.begin("bid_zone", false);     // true = read only
      flash.putBool("bid_zone", !bidLU);  // try other bidding zone
      flash.end();
      delay(1000);
      ESP.restart();
    }
  }
  http.end();
  uint8_t j = SHOW_ITEMS + 1;
  for (uint8_t i = API_ITEMS; i >= 0; i--) {  // select the most recent Json data
    if (epoc[i] != 0) {                       // ignore empty data
      j--;                                    // counter for results
      prijzen[j - 1] = prices[i];             // transferring valid prices from API results
      epo[j - 1] = epoc[i];                   // also hours to "local" array
      if (j == 0) break;                      // stop when we have the [SHOW_ITEMS] most recent items
    }
  }
}

void messageNoConnection(WiFiManager* myWiFi) {
  const char* connect[] = { "WiFi: no connection", "Connect to hotspot", this_Ssid, "Start a browser - address",
                      "192.168.4.1", "to save network", "and password" };
  tft.fillScreen(TFT_NAVY), tft.setTextColor(TFT_YELLOW);
  for (uint8_t i = 0; i < 7; i++) tft.drawCentreString(connect[i], 240, 40 + i * 30, 4);
}





Lilygo TTGO T-Display (ESP32)


Lilygo TTGO T-Display (ESP32)

LILYGO TTGO T-Display (Aliexpress)
LILYGO TTGO T-Display (Lilygo)

Dit is een ESP32 met een SPI-display aan boord (240*135px). Aan de voorzijde zijn er twee knoppen die we kunnen bedienen terwijl de software loopt, en een resetknop aan de zijkant. Omdat het display relatief klein is, werkt het snel. Dat merk je aan bvb. de sprites. We kunnen deze combinatie bestellen met een bijbehorend plastic doosje. Aleen spijtig dat er geen magneet in de achterzijde van het doosje verwerkt is (in de twee volgende combinaties is dat wel het geval). Als je het plastic kastje gebruikt, moet je redelijk hard op de knoppen duwen. Voor de bediening van een spel is het niet echt geschikt. Dat is veel beter ontworpen bij het volgende product van Lilygo dat we hier bespreken.

Voor de bediening van een klokje (deep sleep, wijzigen tijdzones) hinderen de knoppen niet. Zoals je merkt op het filmpje hieronder, we slaan de gekozen tijdzone op, zodat de ingestelde tijdzone bij een herstart of na deep sleep modus behouden blijft.

We hebben ook eigen fonts en meerdere animaties gebruikt op dit bord. Klik hier voor de voorbeeldschets van het beeld hieronder (pagina in het Engels).





In de gebruikte bibliotheek TFT_eSPI is in de folder User_Setups een bestand voorzien voor dit bord: Setup25_TTGO_T_Display.h

Sketch:

#include <WiFi.h>  // Arduino IDE - board: ESP32 DEV Module
#include <WebServer.h>
#include <TFT_eSPI.h>     // https://github.com/Bodmer/TFT_eSPI
#include <Preferences.h>  // no need to install this library

WebServer server(80);
Preferences flash;
TFT_eSPI tft = TFT_eSPI();  // User_Setup: Setup25_TTGO_T_Display.h
TFT_eSprite sprite = TFT_eSprite(&tft);
TFT_eSprite leftSp = TFT_eSprite(&tft);
TFT_eSprite rightS = TFT_eSprite(&tft);

const char* weekdays[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
const char* location[] = { "Brussels", "Shanghai", "Auckland", "New York" };  // # of items must match # of time zones (line below)
const char* timeZone[] = { "CET-1CEST,M3.5.0,M10.5.0/3", "CST-8", "NZST-12NZDT,M9.5.0,M4.1.0/3", "EST5EDT,M3.2.0,M11.1.0" };
const char* startTxt[] = { "connecting", "to WiFi", "WiFi  OK", "Time sync" };
const char* noConnec[] = { "WiFi: no connection.", "Connect to hotspot", "'Lily', open browser", "address 192.168.4.1",
                           "for login + password." };
bool freshStart = true;
String webText, buttons, ssid, pasw;
uint8_t count;

void setup() {
  flash.begin("my-clock", true);       // read from flash, true = read only
  count = flash.getInt("counter", 0);  // retrieve the last set time zone - default to first in the array [0]
  flash.end();
  count = count % (sizeof(timeZone) / sizeof(timeZone[0]));  // modulo (# of elements in array) = prevent errors
  pinMode(35, INPUT_PULLUP);                                 // button "switch time zones"
  pinMode(0, INPUT_PULLUP);
  tft.init(), tft.setRotation(3);
  tft.fillScreen(TFT_BLACK);
  sprite.createSprite(240, 135);  // sprite for faster rendering
  show_Logo_WiFi();               // this is not really necessary but it's nicer.
  connect_to_WiFi();
  showConnected();
  configTzTime(timeZone[count], "pool.ntp.org");  // set the time zone
}

void loop() {
  displayTime();
  if (!digitalRead(0)) displayOnOff();     // flash button
  if (!digitalRead(35)) switchTimeZone();  // button opposite flash button
}

void show_Logo_WiFi() {
  sprite.fillSprite(sprite.color565(100, 100, 100));
  for (int i = 0; i < 8192; i++) {  // create surface with fine texture
    byte j = random(100) + 50;
    sprite.drawPixel(random(240), random(135), sprite.color565(j, j, j));  // random grayscale
  }
  sprite.setTextColor(TFT_YELLOW);
  sprite.drawCentreString(startTxt[0], 120, 76, 4), sprite.drawCentreString(startTxt[1], 120, 100, 4);
  sprite.fillCircle(120, 56, 6, TFT_BLUE);
  for (byte i = 0; i < 3; i++) sprite.drawSmoothArc(120, 54, 40 - i * 11, 35 - i * 11, 128, 232, TFT_BLUE, TFT_BLUE, 1);
  sprite.pushSprite(0, 0);
}

void showConnected() {
  for (byte i = 0; i < 3; i++) sprite.drawSmoothArc(120, 54, 40 - i * 11, 35 - i * 11, 128, 232, TFT_GREEN, TFT_GREEN, 1);
  sprite.fillCircle(120, 56, 6, TFT_GREEN);
  sprite.pushSprite(0, 0);
  leftSp.createSprite(128, 66);  // sprite for new text to be scrolled up
  leftSp.fillSprite(sprite.color565(100, 100, 100));
  for (int i = 0; i < 3072; i++) {
    byte j = random(100) + 50;
    leftSp.drawPixel(random(128), random(66), sprite.color565(j, j, j));  // random grayscale
  }
  leftSp.setTextColor(TFT_GREEN);
  leftSp.drawCentreString(startTxt[2], 64, 5, 4);
  leftSp.setTextColor(TFT_YELLOW);
  leftSp.drawCentreString(startTxt[3], 64, 30, 4);
  for (int i = 1024; i > 560; i--) leftSp.pushSprite(56, i / 8);  // high values to slow down the animation
  leftSp.pushToSprite(&sprite, 56, 70);
  leftSp.deleteSprite();
}

void show_Message_No_Connection() {  // message on display when there is no WiFi connection
  tft.fillScreen(TFT_NAVY);          // also blue color for the leftmost 9 pixels - sprite.pushSprite(9, 0) below
  tft.setTextColor(TFT_YELLOW);
  for (uint8_t i = 0; i < 5; i++) tft.drawCentreString(noConnec[i], 120, i * 27, 4);
}

void displayTime() {
  struct tm tInfo;       // https://cplusplus.com/reference/ctime/tm/
  getLocalTime(&tInfo);  // SNTP update every 3 hours (default ESP32) since we did not set an interval
  if (freshStart) splitScreen(true);
  sprite.setTextColor(TFT_CYAN, TFT_BLACK);
  sprite.fillSprite(TFT_BLACK);
  sprite.setFreeFont(&FreeSansBold18pt7b);
  sprite.setTextSize(2);
  sprite.setCursor(13, 56);
  sprite.printf("%02d:%02d", tInfo.tm_hour, tInfo.tm_min);
  sprite.setTextSize(1);
  sprite.drawFastHLine(0, 72, 240, WiFi.isConnected() ? TFT_GREEN : TFT_RED);
  sprite.setCursor(187, 58);
  sprite.setFreeFont(&FreeSansBold18pt7b);
  sprite.printf("%02d", tInfo.tm_sec);
  sprite.setTextColor(TFT_YELLOW);
  sprite.drawCentreString(weekdays[tInfo.tm_wday], 120, 80, 1);  // weekday
  sprite.setTextColor(TFT_RED);
  sprite.setFreeFont(&FreeSans12pt7b);
  sprite.setCursor(62, 134);
  sprite.printf("%02d-%02d-%04d", tInfo.tm_mday, 1 + tInfo.tm_mon, 1900 + tInfo.tm_year);  // date
  if (freshStart) splitScreen(false);
  sprite.pushSprite(0, 0);
}

void splitScreen(bool split) {  // split (true) or merge (false) sprite horizontally
  leftSp.createSprite(120, 135), rightS.createSprite(120, 135);
  if (!split) freshStart = false;
  for (byte ver = 0; ver < 135; ver++) {  // divide the sprite into 2 pieces & write data to two smaller sprites
    for (byte hor = 0; hor < 120; hor++) leftSp.drawPixel(hor, ver, sprite.readPixel(hor, ver));
    for (byte hor = 120; hor < 240; hor++) rightS.drawPixel(hor - 120, ver, sprite.readPixel(hor, ver));
  }
  if (split) leftSp.drawFastVLine(119, 0, 135, TFT_BLACK), rightS.drawFastVLine(0, 0, 135, TFT_BLACK);
  for (byte hor = 0; hor < 120; hor++) {  // move both sprites to the left & right outer edges
    if (split) leftSp.pushSprite(0 - hor, 0), rightS.pushSprite(hor + 120, 0);
    else leftSp.pushSprite(hor - 120, 0), rightS.pushSprite(240 - hor, 0);
  }
  leftSp.deleteSprite(), rightS.deleteSprite();
}

void switchTimeZone() {
  leftSp.createSprite(240, 28), leftSp.setFreeFont(&FreeSans12pt7b);
  leftSp.fillSprite(TFT_BLACK);
  leftSp.setTextColor(TFT_RED), leftSp.drawCentreString(location[count], 120, 0, 1);
  for (int tel = 0; tel < 240; tel++) leftSp.pushSprite(tel, 110);  // we need an animation that uses up time = UI debouncing
  count = (count + 1) % (sizeof(timeZone) / sizeof(timeZone[0]));   // increase counter modulo (number of elements in string array)
  configTzTime(timeZone[count], "pool.ntp.org");
  leftSp.fillSprite(TFT_BLACK);
  leftSp.drawCentreString(location[count], 120, 0, 1);
  for (int tel = -960; tel < 1; tel++) leftSp.pushSprite(tel / 4, 114);
  leftSp.deleteSprite();
}

void displayOnOff() {
  byte savedZone;
  flash.begin("my-clock");                                 // read from flash memory
  savedZone = flash.getInt("counter", 0);                  // retrieve the last set time zone - default to first in the array [0]
  if (savedZone != count) flash.putInt("counter", count);  // only write the time zone to flash memory when it was changed
  flash.end();                                             // to prevent chip wear from excessive writing
  digitalWrite(TFT_BL, !digitalRead(TFT_BL)), delay(300);
}

void connect_to_WiFi() {  // connect to WiFi, if not successful: start web server
  WiFi.mode(WIFI_MODE_STA);
  flash.begin("login_data", true);  // true = read only
  ssid = flash.getString("ssid", "");
  pasw = flash.getString("pasw", "");
  flash.end();
  WiFi.begin(ssid.c_str(), pasw.c_str());
  for (uint8_t i = 0; i < 50; ++i) {  // we try for about 8 seconds to connect
    if (WiFi.isConnected()) {
      WiFi.setAutoReconnect(true);
      WiFi.persistent(true);
      return;  // jumps out of this function when WiFi connection succeeds
    }
    delay(160);
  }
  show_Message_No_Connection();  // script lands on this line only when the connection fails
  int n = WiFi.scanNetworks();
  for (int i = 0; i < n; ++i) {  // html to put found networks on buttons on web page
    buttons += "\n<button onclick='scrollNaar(this.id)' id='" + WiFi.SSID(i) + "'>" + WiFi.SSID(i) + "</button><br>";
  }
  WiFi.mode(WIFI_MODE_AP);
  WiFi.softAP("Lily", "", 1);
  server.on("/", server_Root);
  server.on("/setting", server_Setting);
  server.begin();
  for (;;) server.handleClient();  // infinite loop until the WiFi credentials are inserted
}

void server_Root() {
  webText = "<!DOCTYPE HTML>\n<html lang='en'>\n<head><title>Setup</title>\n<meta name='viewport' ";
  webText += "content='width=device-width, initial-scale=1.0'>";
  webText += "\n<style>\np {\n  font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif;\n  font-size: 18px;\n  margin: 0;\n  text-align: ";
  webText += "center;\n}\n\nbutton, input[type=submit] {\n  width: 250px;\n  border-radius: 5px;\n  color: White;\n  padding:";
  webText += " 4px 4px;\n  margin-top: 16px;\n  margin: 0 auto;\n  display:block;\n  font-size: 18px;\n  font-weight: 600;";
  webText += "\n  background: DodgerBlue;\n}\n\ninput {\n  width: 250px;\n  font-size: 18px;\n  font-weight: 600;\n}";
  webText += "\n</style>\n</head>\n<body><p style='font-family:arial; ";
  webText += "font-size:240%;'>WiFi setup\n</p><p style='font-family:arial; font-size:160%;'>\n<br>";
  webText += "Networks found:<br> Click on item to select or<br>Enter your network data<br> in the boxes below:</p><br>";
  webText += buttons;
  webText += "\n<form method='get' action='setting'>\n<p><b>\nSSID: <br>\n<input id='ssid' name='ssid'>";
  webText += "<br>PASW: </b><br>\n<input type='password' name='pass'><br><br>\n<input type='submit' value='Save'>";
  webText += "\n</p>\n</form>\n<script>\nfunction scrollNaar(tekst) {\n  document.getElementById('ssid')";
  webText += ".value = tekst;\n  window.scrollTo(0, document.body.scrollHeight);\n}\n</script>\n</body>\n</html>";
  server.send(200, "\ntext/html", webText);
}

void server_Setting() {
  webText = "<!DOCTYPE HTML>\n<html lang='en'>\n<head><title>Setup</title>\n<meta name='viewport' ";
  webText += "content='width=device-width, initial-scale=1.0'>\n<style>\n* {\n  font-family: Arial, Helvetica";
  webText += ", sans-serif;\n  font-size: 45px;\n  font-weight: 600;\n  margin: 0;\n  text-align: center;\n}";
  webText += "\n\n@keyframes op_en_neer {\n  0%   {height:  0px;}\n  50%  {height:  40px;}\n  100% {height:  0px;}\n}";
  webText += "\n\n.opneer {\n  margin: auto;\n  text-align: center;\n  animation: op_en_neer 2s infinite;\n}";
  webText += "\n</style>\n</head>\n<body>\n<div class=\"opneer\"></div>\nESP will reboot<br>Close this window";
  webText += "\n</body>\n</html>";
  String myssid = server.arg("ssid");  // we want to store this in flash memory
  String mypasw = server.arg("pass");
  server.send(200, "\ntext/html", webText);
  delay(500);
  if (myssid.length() > 0 && mypasw.length() > 0) {
    flash.begin("login_data", false);
    flash.putString("ssid", myssid);
    flash.putString("pasw", mypasw);
    flash.end();
    ESP.restart();
  }
}



Lilygo T-Display S3 (ESP32)


Lilygo T-Display S3 (ESP32)

LILYGO T-Display S3

De Lilygo T-Display S3 kan je verkrijgen met een bijbehorend zwart of grijs plastic kastje. Er is een 1.9 inch LCD-kleurenscherm (320 * 170px) aan boord. De module heeft vooraan 2 knoppen (GPIO0 en GPIO14), en zijkant de resetknop.

Omdat we twee knoppen hebben die we kunnen gebruiken - afgezien van de resetknop - werd in deze code de bovenste knop gebruikt voor de deep sleep modus, en de onderste knop om verschillende tijdzones te selecteren. We slaan de ingestelde tijdzone op in het flashgeheugen, zodat die bij een herstart behouden blijft. We gebruiken hiervoor de bibliotheek preferences.h.

We hebben ook wat speeltjes toegevoegd voor een mooiere presentatie. Dit zowel bij het opstarten, het in deep sleep modus gaan, als het wijzigen van tijdzone.

In de gebruikte bibliotheek TFT_eSPI is in de folder User_Setups een bestand voorzien voor dit bord: Setup206_LilyGo_T_Display_S3.h

Je kan ook eigen fonts gebruiken op de ESP. Klik hier voor de voorbeeldschets van het beeld hieronder (pagina in het Engels).



Een eenvoudig weerstation is uiteraard ook mogelijk. Klik hier voor de voorbeeldschets van het beeld hieronder (pagina in het Engels).



Sketch:

#include <WiFi.h>  // Arduino IDE - board: Lilygo T_Display-S3
#include <WebServer.h>
#include <TFT_eSPI.h>     // https://github.com/Bodmer/TFT_eSPI
#include <Preferences.h>  // to save the selected time zone

WebServer server(80);
Preferences flash;
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite sprite = TFT_eSprite(&tft);
TFT_eSprite leftSp = TFT_eSprite(&tft);
TFT_eSprite rightS = TFT_eSprite(&tft);

uint8_t SCREEN_ORIENTATION = 3;  // USB connection: 3 = left side - 1 = right side.
const char* location[] = { "Brussels", "Shanghai", "Auckland", "New York" };
const char* timeZone[] = {  // avoid memory leaks by giving all elements in the array the same length (trailing spaces)
  "CET-1CEST,M3.5.0,M10.5.0/3 ",
  "CST-8                      ",
  "NZST-12NZDT,M9.5.0,M4.1.0/3",
  "EST5EDT,M3.2.0,M11.1.0     "
};
const char* weekDays[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
const char* startTxt[] = { "Connecting to WiFi", "WiFi OK: Time sync" };
const char* noConnec[] = { " WiFi: no connection.", " Connect to hotspot 'TD-S3'", " and open a browser",
                           " at adres 192.168.4.1", " to enter network name", " and password." };

bool noCon = false, freshStart = true;
String webText, buttons, ssid, pasw;
uint8_t count;

void setup() {
  pinMode(14, INPUT_PULLUP);  // button for changing time zone
  pinMode(0, INPUT_PULLUP);   // flash button
  initDisplay();
  showStartUpLogo();
  connect_to_WiFi();  // Will start web server (for setup WiFi data) if connection fails
  showConnected();
  setTimeZoneFromFlash();
}

void loop() {
  display_Time();
  if (!digitalRead(14)) switchTimeZone();
  if (!digitalRead(0)) displayOnOff();  // prevent burn-in
}

void initDisplay() {
  tft.init(), tft.setRotation(SCREEN_ORIENTATION);
  sprite.createSprite(320, 170);  // sprite for faster rendering
}

void showStartUpLogo() {  // use functions of library to draw = smaller than a graphic file
  tft.fillScreen(TFT_BLACK);
  sprite.fillSprite(sprite.color565(100, 100, 100));
  for (uint16_t i = 0; i < 12000; i++) {  // create surface with fine texture
    uint8_t j = random(100) + 50;
    sprite.drawPixel(random(320), random(170), sprite.color565(j, j, j));
  }
  sprite.setTextColor(tft.color565(48, 48, 48));
  for (uint8_t i = 0; i < 8; i++) {  // draw IC legs
    sprite.drawRect(131 + (i * 10), 10, 2, 115, sprite.color565(240, 240, 240));
    sprite.fillRoundRect(129 + (i * 10), 18, 6, 99, 2, sprite.color565(240, 240, 240));
    sprite.drawRect(110, 32 + (i * 10), 115, 2, sprite.color565(240, 240, 240));
    sprite.fillRoundRect(118, 30 + (i * 10), 99, 6, 2, sprite.color565(240, 240, 240));
  }
  sprite.fillRoundRect(122, 22, 91, 91, 3, TFT_BLACK);  // draw IC
  sprite.drawRoundRect(122, 22, 91, 91, 3, TFT_DARKGREY);
  sprite.setTextFont(1);
  sprite.drawCentreString("ESP32", 157, 74, 1);
  sprite.drawCentreString("1732S019", 164, 86, 1);
  sprite.fillCircle(200, 34, 3, sprite.color565(16, 16, 16));
  sprite.setFreeFont(&FreeSans18pt7b);
  sprite.setTextColor(TFT_BLACK);  // embossed text below IC
  sprite.drawCentreString(startTxt[0], 161, 132, 1);
  sprite.setTextColor(TFT_WHITE);
  sprite.drawCentreString(startTxt[0], 159, 130, 1);
  sprite.setTextColor(sprite.color565(100, 100, 100));
  sprite.drawCentreString(startTxt[0], 160, 131, 1);
  sprite.pushSprite(0, 0);
}

void show_Message_No_Connection() {
  tft.fillScreen(TFT_NAVY);
  tft.setTextColor(TFT_YELLOW), tft.setTextFont(4), tft.setCursor(0, 0, 4);
  for (uint8_t count = 0; count < 6; count++) tft.println(noConnec[count]);
  noCon = true;
}

void showConnected() {
  leftSp.createSprite(320, 58);
  leftSp.fillSprite(sprite.color565(100, 100, 100));
  leftSp.setFreeFont(&FreeSans18pt7b);
  for (uint16_t i = 0; i < 4000; i++) {
    uint8_t j = random(100) + 50;
    leftSp.drawPixel(random(320), random(58), sprite.color565(j, j, j));  // random greyscale
  }
  leftSp.setTextColor(TFT_BLACK);  // embossed text "connected"
  leftSp.drawCentreString(startTxt[1], 161, 6, 1);
  leftSp.setTextColor(TFT_WHITE);
  leftSp.drawCentreString(startTxt[1], 159, 4, 1);
  leftSp.setTextColor(sprite.color565(100, 100, 100));
  leftSp.drawCentreString(startTxt[1], 160, 5, 1);
  for (uint16_t i = 850; i > 650; i--) leftSp.pushSprite(0, i / 5);  // slide text upwards
  leftSp.pushToSprite(&sprite, 0, 131);                              // add new text to existing sprite
  leftSp.deleteSprite();
}

void setTimeZoneFromFlash() {
  flash.begin("my-clock", true);       // read from flash (true = read only)
  count = flash.getInt("counter", 0);  // retrieve the last set time zone - default to first in the array [0]
  flash.end();
  count = count % (sizeof(timeZone) / sizeof(timeZone[0]));  // modulo (# of elements in array) = prevent errors
  configTzTime(timeZone[count], "pool.ntp.org");             // clock will automatically adjust to daylight saving time
}

void display_Time() {
  struct tm tInfo;  // https://cplusplus.com/reference/ctime/tm/
  getLocalTime(&tInfo);
  if (freshStart) splitScreen(true);
  sprite.fillSprite(TFT_BLACK);
  uint16_t myColor = (WiFi.status() == WL_CONNECTED) ? TFT_GREEN : TFT_RED;
  sprite.fillCircle(294, 30, 6, myColor);  // logo WiFi
  for (uint8_t tel = 0; tel < 3; tel++) {
    sprite.drawSmoothArc(294, 32, 30 - tel * 7, 28 - tel * 7, 135, 225, myColor, myColor, true);
  }
  sprite.setTextFont(7), sprite.setTextColor(TFT_CYAN, TFT_BLACK), sprite.setTextSize(2);
  sprite.setCursor(-2, 0), sprite.printf("%02d", tInfo.tm_hour);
  sprite.fillRect(126, 28, 5, 10, TFT_CYAN), sprite.fillRect(126, 56, 5, 10, TFT_CYAN);  // colon between hour & minutes
  sprite.setCursor(131, 0), sprite.printf("%02d", tInfo.tm_min);
  sprite.setCursor(256, 45), sprite.setTextSize(1), sprite.printf("%02d", tInfo.tm_sec);
  sprite.setTextColor(TFT_YELLOW), sprite.setFreeFont(&FreeSans12pt7b);
  sprite.setCursor(9, 157), sprite.print(weekDays[tInfo.tm_wday]);
  sprite.drawRoundRect(0, 134, 320, 34, 5, TFT_YELLOW);
  sprite.setCursor(188, 159), sprite.setTextColor(TFT_CYAN);
  sprite.printf("%02d-%02d-%04d", tInfo.tm_mday, 1 + tInfo.tm_mon, 1900 + tInfo.tm_year);
  sprite.setTextColor(TFT_RED), sprite.drawCentreString(location[count], 160, 108, 1);
  if (freshStart) splitScreen(false), freshStart = false;  // before "pushSprite" because the background must be black
  sprite.pushSprite(0, 0);
}

void splitScreen(bool split) {  // split (true) or merge (false) sprite horizontally
  leftSp.createSprite(160, 170), rightS.createSprite(160, 170);
  for (uint8_t ver = 0; ver < 170; ver++) {  // divide the sprite into 2 pieces & write data to two smaller sprites
    for (uint16_t hor = 0; hor < 160; hor++) leftSp.drawPixel(hor, ver, sprite.readPixel(hor, ver));
    for (uint16_t hor = 160; hor < 320; hor++) rightS.drawPixel(hor - 160, ver, sprite.readPixel(hor, ver));
  }
  if (split) {  // avoid sloppy lines: make sure the remaining part of the screen is black
    leftSp.drawFastVLine(159, 0, 170, TFT_BLACK), leftSp.drawFastVLine(158, 0, 170, TFT_BLACK);
    rightS.drawFastVLine(0, 0, 170, TFT_BLACK), rightS.drawFastVLine(1, 0, 170, TFT_BLACK);
  }
  for (uint16_t hor = 0; hor < 160; hor += 2) {
    if (split) leftSp.pushSprite(0 - hor, 0), rightS.pushSprite(hor + 160, 0);  // move both sprites to the outer edge
    else leftSp.pushSprite(hor - 160, 0), rightS.pushSprite(320 - hor, 0);      // merge both sprites
  }
  leftSp.deleteSprite(), rightS.deleteSprite();
}

void switchTimeZone() {
  leftSp.createSprite(320, 28), leftSp.setFreeFont(&FreeSans12pt7b);
  leftSp.fillSprite(TFT_BLACK);
  leftSp.setTextColor(TFT_RED), leftSp.drawCentreString(location[count], 160, 3, 1);
  for (uint16_t tel = 0; tel < 320; tel++) leftSp.pushSprite(tel, 105);  // UI debouncing
  count = (count + 1) % (sizeof(timeZone) / sizeof(timeZone[0]));        // increase modulo (number of elements in char array)
  configTzTime(timeZone[count], "pool.ntp.org");
  leftSp.fillSprite(TFT_BLACK);
  leftSp.drawCentreString(location[count], 160, 3, 1);
  for (int16_t tel = -320; tel < 1; tel++) leftSp.pushSprite(tel, 105);
  leftSp.deleteSprite();
}

void displayOnOff() {  // flash button pressed: display on - off
  uint8_t savedZone;
  flash.begin("my-clock");                                 // flash memory (also write since 2nd param = not set)
  savedZone = flash.getInt("counter", 0);                  // retrieve the last set time zone - default to first in the array [0]
  if (savedZone != count) flash.putInt("counter", count);  // only write the time zone to flash memory when it was changed
  flash.end();                                             // to prevent chip wear from excessive writing
  digitalWrite(TFT_BL, !digitalRead(TFT_BL)), delay(300);
}

void connect_to_WiFi() {  // connect to WiFi, if not successful: start web server
  WiFi.mode(WIFI_MODE_STA);
  flash.begin("login_data", true);  // true = read only
  ssid = flash.getString("ssid", "");
  pasw = flash.getString("pasw", "");
  flash.end();
  WiFi.begin(ssid.c_str(), pasw.c_str());
  for (uint8_t i = 0; i < 50; ++i) {  // we try for about 8 seconds to connect
    if (WiFi.isConnected()) {
      WiFi.setAutoReconnect(true);
      WiFi.persistent(true);
      return;  // jumps out of this function when WiFi connection succeeds
    }
    delay(160);
  }
  show_Message_No_Connection();  // script lands on this line only when the connection fails
  int n = WiFi.scanNetworks();
  for (int i = 0; i < n; ++i) {  // html to put found networks on buttons on web page
    buttons += "\n<button onclick='scrollNaar(this.id)' id='" + WiFi.SSID(i) + "'>" + WiFi.SSID(i) + "</button><br>";
  }
  WiFi.mode(WIFI_MODE_AP);
  WiFi.softAP("TD-S3", "", 1);
  server.on("/", server_Root);
  server.on("/setting", server_Setting);
  server.begin();
  for (;;) server.handleClient();  // infinite loop until the WiFi credentials are inserted
}

void server_Root() {
  webText = "<!DOCTYPE HTML>\n<html lang='en'>\n<head><title>Setup</title>\n<meta name='viewport' ";
  webText += "content='width=device-width, initial-scale=1.0'>";
  webText += "\n<style>\np {\n  font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif;\n  font-size: 18px;\n  margin: 0;\n  text-align: ";
  webText += "center;\n}\n\nbutton, input[type=submit] {\n  width: 250px;\n  border-radius: 5px;\n  color: White;\n  padding:";
  webText += " 4px 4px;\n  margin-top: 16px;\n  margin: 0 auto;\n  display:block;\n  font-size: 18px;\n  font-weight: 600;";
  webText += "\n  background: DodgerBlue;\n}\n\ninput {\n  width: 250px;\n  font-size: 18px;\n  font-weight: 600;\n}";
  webText += "\n</style>\n</head>\n<body><p style='font-family:arial; ";
  webText += "font-size:240%;'>WiFi setup\n</p><p style='font-family:arial; font-size:160%;'>\n<br>";
  webText += "Networks found:<br> Click on item to select or<br>Enter your network data<br> in the boxes below:</p><br>";
  webText += buttons;
  webText += "\n<form method='get' action='setting'>\n<p><b>\nSSID: <br>\n<input id='ssid' name='ssid'>";
  webText += "<br>PASW: </b><br>\n<input type='password' name='pass'><br><br>\n<input type='submit' value='Save'>";
  webText += "\n</p>\n</form>\n<script>\nfunction scrollNaar(tekst) {\n  document.getElementById('ssid')";
  webText += ".value = tekst;\n  window.scrollTo(0, document.body.scrollHeight);\n}\n</script>\n</body>\n</html>";
  server.send(200, "\ntext/html", webText);
}

void server_Setting() {
  webText = "<!DOCTYPE HTML>\n<html lang='en'>\n<head><title>Setup</title>\n<meta name='viewport' ";
  webText += "content='width=device-width, initial-scale=1.0'>\n<style>\n* {\n  font-family: Arial, Helvetica";
  webText += ", sans-serif;\n  font-size: 45px;\n  font-weight: 600;\n  margin: 0;\n  text-align: center;\n}";
  webText += "\n\n@keyframes op_en_neer {\n  0%   {height:  0px;}\n  50%  {height:  40px;}\n  100% {height:  0px;}\n}";
  webText += "\n\n.opneer {\n  margin: auto;\n  text-align: center;\n  animation: op_en_neer 2s infinite;\n}";
  webText += "\n</style>\n</head>\n<body>\n<div class=\"opneer\"></div>\nESP will reboot<br>Close this window";
  webText += "\n</body>\n</html>";
  String myssid = server.arg("ssid");  // we want to store this in flash memory
  String mypasw = server.arg("pass");
  server.send(200, "\ntext/html", webText);
  delay(500);
  if (myssid.length() > 0 && mypasw.length() > 0) {
    flash.begin("login_data", false);
    flash.putString("ssid", myssid);
    flash.putString("pasw", mypasw);
    flash.end();
    ESP.restart();
  }
}





WiFi-verbinding

WiFi-verbinding

De ESP8266 / ESP32 is eigenlijk een programmeerbare schakelaar van GPIO-pinnen, met WiFi en Bluetooth / BLE. WiFi wordt vrijwel altijd gebruikt bij de ESP: de meeste programmeerbare bordjes zonder WiFi zijn veel zuiniger qua energie dan de ESP. We kunnen dus stellen dat we de ESP steeds gebruiken voor WiFi.

Hoe programmeren we best de verbinding dan?

De login-gegevens hard gecodeerd in de sketch? Dat is een slecht idee. Een afzonderlijk bestand "credentials.h" met SSID en paswoord bij de sketch zetten? Een professionele toepassing ontwikkelen op die manier kan evenmin. Een klant zal een sketch niet opnieuw compileren, die wil dat zijn IOT gadget gewoon werkt, na een eenmalige setup.

Eigenlijk moet de ESP bij opstart proberen te verbinden met WiFi, met de login-gegevens vanuit het EEPROM. Indien dat niet lukt, een toegangspunt en een webserver opstarten om de login-gegevens op te slaan, om vervolgens de ESP te herstarten.

Dat is exact wat de bibliotheek WiFiManager.h doet, met slechts 2 lijntjes in je sketch. Je vindt op deze pagina voldoende sketches om te zien hoe dat werkt. Sinds de release van de core 3.0.x (Arduino IDE, bordenbeheer) geeft WiFiManager.h echter fouten bij sommige bordjes.

Als je zonder deze fouten wil werken, en niet graag afhankelijk bent van een bibliotheek, kan je manueel de code toevoegen in je sketch. Hierbij een eenvoudig voorbeeld voor de ESP32, waarbij verbonden wordt met WiFi, en als het niet lukt, een WiFi-toegangspunt en webserver opgestart wordt om de gegevens op te slaan.

#include <WiFi.h>
#include <WebServer.h>
#include <Preferences.h>  // no need to install or download this library
#include <vector>         // C++ Standard Library: dynamic array container

WebServer server(80);
Preferences flash;

#define this_Ssid "ESP32-Conn"
String ssid, pasw;
std::vector<String> availableSSIDs;

void setup() {
  Serial.begin(115200);
  connect_to_WiFi();
  Serial.println(" Connected to " + WiFi.SSID());  // script lands on this line only if the connection succeeded
}

void loop() {}  // no command lines in this design

void connect_to_WiFi() {  // connect to WiFi, if not successful: start web server
  WiFi.mode(WIFI_MODE_STA);
  flash.begin("login_data", true);  // true = read only
  ssid = flash.getString("ssid", "");
  pasw = flash.getString("pasw", "");
  flash.end();
  Serial.print("Connecting");
  WiFi.begin(ssid.c_str(), pasw.c_str());
  for (uint8_t i = 0; i < 50; ++i) {  // we try for about 8 seconds to connect
    if (WiFi.isConnected()) {
      WiFi.setAutoReconnect(true);
      WiFi.persistent(true);
      return;  // jumps out of this function when WiFi connection succeeds
    }
    delay(160);
    Serial.print(".");
  }
  Serial.print("\nConnection failed. Connect to hotspot\n");  // script lands on this line only when the connection fails
  Serial.print(this_Ssid);
  Serial.print("\nand open a browser @ 192.168.4.1 to save the WiFi credentials");
  int numNetworks = WiFi.scanNetworks();
  availableSSIDs.clear();
  for (int i = 0; i < numNetworks; i++) availableSSIDs.push_back(WiFi.SSID(i));  // https://favtutor.com/blogs/push-back-vector-cpp
  WiFi.mode(WIFI_MODE_AP);
  WiFi.softAP(this_Ssid, "", 1);
  server.on("/", serveWiFiSetupPage);
  server.on("/setting", processWiFiSettingsSubmission);
  server.begin();
  for (;;) server.handleClient();  // infinite loop until the WiFi credentials are inserted
}

void serveWiFiSetupPage() {  // html to insert WiFi credentials and P1 meter data
  String webText = F("<!DOCTYPE HTML><html lang='en'><head><title>WiFi Setup</title>");
  webText += F("<meta name='viewport' content='width=device-width, initial-scale=1.0'><style>");
  webText += F("body { font-family: Arial, sans-serif; background: #f4f4f4; margin: 0; padding: 0; text-align: center; }");
  webText += F(".container { max-width: 440px; background: white; padding: 20px; margin: 20px auto; border-radius: 10px;");
  webText += F("box-shadow: 0 0 10px rgba(0,0,0,0.1); } h1 { font-size: 24px; color: #333; margin-bottom: 10px; }");
  webText += F("p { font-size: 16px; color: #555; } label { display: block; text-align: left; margin-top: 12px; color: #333; }");
  webText += F("input, select, button { width: 100%; padding: 10px; margin-top: 4px; border-radius: 5px;");
  webText += F("border: 1px solid #ccc; font-size: 16px; box-sizing: border-box; }");
  webText += F("input[type=submit], button { background: #007BFF; color: white; border: none; cursor: pointer; }");
  webText += F("input[type=submit]:hover, button:hover { background: #0056b3; }");
  webText += F("fieldset { border: 2px solid #007BFF; border-radius: 10px; padding: 16px; margin-top: 20px; background: #f0f8ff;");
  webText += F("box-shadow: inset 0 0 5px rgba(0,123,255,0.1); }legend { font-weight: bold; color: white; background: ");
  webText += F("#006400; padding: 4px 10px; border-radius: 5px; font-size: 16px; }");
  webText += F("#ssid-buttons { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-top: 16px; }");
  webText += F("#ssid-buttons button { width: auto; padding: 8px 14px; font-size: 14px; }</style></head><body>");
  webText += F("<div class='container'><h1>WiFi Setup</h1><p>Select a network or enter details below.</p>");
  webText += F("<div id='ssid-buttons'>");
  for (const String& ssid : availableSSIDs) {
    webText += "<button onclick=\"document.getElementsByName('ssid')[0].value='" + ssid + "'\">" + ssid + "</button>";
  }
  webText += F("</div><form method='get' action='setting'><fieldset><legend>WiFi Settings</legend>");
  webText += F("<label><b>SSID:</b></label><input name='ssid' required><label><b>Password:</b></label><input type='password'");
  webText += F(" id='pass' name='pass' required></fieldset>");
  webText += F("<br><input type='submit' value='Save'></form></div></body></html>");  // Submit
  server.send(200, "text/html", webText);
}

void processWiFiSettingsSubmission() {
  String newSsid = server.arg("ssid");  // collect data from web page
  String newPassword = server.arg("pass");
  uint16_t newP1Interval = server.arg("p1Upd").toInt();
  if (newSsid.length() > 0) {  // save data to FLASH memory
    flash.begin("login_data", false);
    flash.putString("ssid", newSsid);
    flash.putString("pasw", newPassword);
    flash.end();
  }
  String webText = F("<!DOCTYPE HTML>\n<html lang='en'>\n<head><title>Setup</title>\n<meta name='viewport' ");  // user feedback
  webText += F("content='width=device-width, initial-scale=1.0'>\n<style>\n* {\n  font-family: Arial, Helvetica");
  webText += F(", sans-serif;\n  font-size: 45px;\n  font-weight: 600;\n  margin: 0;\n  text-align: center;\n}");
  webText += F("\n\n@keyframes op_en_neer {\n  0%   {height:  0px;}\n  50%  {height:  40px;}\n  100% {height:  0px;}\n}");
  webText += F("\n\n.opneer {\n  margin: auto;\n  text-align: center;\n  animation: op_en_neer 2s infinite;\n}");
  webText += F("\n</style>\n</head>\n<body>\n<div class=\"opneer\"></div>\nESP will reboot<br>Close this window");
  webText += F("\n</body>\n</html>");
  server.send(200, "text/html", webText);
  delay(500);
  ESP.restart();
}



ESP32 + display: digitale elektriciteitsmeter uitlezen
ESP32 + display: digitale elektriciteitsmeter uitlezen