//https://espgo.be/freefont.html
#include <WiFi.h> // Arduino IDE - board: Lilygo T_Display-S3
#include <WebServer.h>
#include <TFT_eSPI.h>
#include <Preferences.h>
#include "HPSimplified54pt7b.h"
#include "HPSimplified27pt7b.h"
#include "HPSimplified20pt7b.h"
#include "WIFI20pt7b.h"
Preferences flash;
WebServer server(80);
TFT_eSPI tft = TFT_eSPI(); // User_Setup_Select.h: Setup206_LilyGo_T_Display_S3.h
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[] = {
"Warsaw", "Rome", "Brussels", "Kolkata", "Shanghai", "Auckland", "Los Angeles", "New York"
};
// # of items must match # of locations (line above)
const char* timeZone[] = {
"CET-1CEST,M3.5.0/2,M10.5.0/3", // Warsaw
"CET-1CEST,M3.5.0/2,M10.5.0/3", // Rome
"CET-1CEST,M3.5.0/2,M10.5.0/3", // Brussels (również CET/CEST)
"IST-5:30",
"CST-8",
"NZST-12NZDT,M9.5.0,M4.1.0/3",
"PST8PDT,M3.2.0,M11.1.0",
"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." };
String webText, buttons, ssid, pasw;
bool freshStart = true;
uint8_t count;
void setup() {
pinMode(14, INPUT_PULLUP); // button for changing time zone
pinMode(12, INPUT_PULLUP); // flash button
initDisplay();
tft.setRotation(1);
showStartUpLogo();
connect_to_WiFi();
showConnected();
setTimeZoneFromFlash();
}
void loop() {
display_Time(TFT_YELLOW, TFT_GREEN); // colors of weekday & date
if (!digitalRead(14))
switchTimeZone();
if (!digitalRead(0))
screen_Saver();
}
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]);
}
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(uint16_t WD_COL, uint16_t DT_COL) { // show time information on the screen
struct tm tInfo; // https://cplusplus.com/reference/ctime/tm/
uint8_t TIME_TOP = 82; // distance between top edge of screen and bottom of font
getLocalTime(&tInfo); // time sync during startup and every 3 hours thereafter
if (freshStart) splitScreen(true);
sprite.fillSprite(TFT_BLACK), sprite.setTextColor(WiFi.isConnected() ? TFT_GREEN : TFT_RED);
sprite.setFreeFont(&WIFI20pt7b), sprite.setCursor(267, 38), sprite.print("b"); // https://www.dafont.com/wifi.font = WiFi logo
sprite.setFreeFont(&HPSimplified54pt7b), sprite.setTextColor(TFT_CYAN);
sprite.setCursor(0, TIME_TOP), sprite.printf("%02d:%02d", tInfo.tm_hour, tInfo.tm_min);
sprite.setFreeFont(&HPSimplified27pt7b);
sprite.setCursor(258, TIME_TOP), sprite.printf("%02d", tInfo.tm_sec);
sprite.setTextColor(WD_COL), sprite.setFreeFont(&HPSimplified20pt7b);
sprite.drawCentreString(weekDays[tInfo.tm_wday], 160, 94, 1);
char theDate[11];
sprintf(theDate, "%02d-%02d-%04d", tInfo.tm_mday, 1 + tInfo.tm_mon, 1900 + tInfo.tm_year);
sprite.setTextColor(DT_COL), sprite.drawCentreString(theDate, 160, 137, 1);
if (freshStart)
// before "pushSprite" because the background must be black
splitScreen(false), freshStart = false;
sprite.pushSprite(0, 0);
}
// split (true) or merge (false) sprite horizontally
void splitScreen(bool split) {
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, 75), leftSp.setFreeFont(&HPSimplified27pt7b);
leftSp.fillSprite(TFT_BLACK), leftSp.setTextColor(TFT_RED);
leftSp.drawCentreString(location[count], 160, 3, 1); // old location
for (uint16_t tel = 0; tel < 320; tel += 2) leftSp.pushSprite(tel, 95); // animation (slow) = UI debouncing
count = (count + 1) % (sizeof(timeZone) / sizeof(timeZone[0])); // increase modulo (number of elements in char array)
configTzTime(timeZone[count], "pool.ntp.org"); // set time zone (and DST data) for new location
display_Time(TFT_BLACK, TFT_BLACK), leftSp.fillSprite(TFT_BLACK); // weekday & date not visible this time
leftSp.drawCentreString(location[count], 160, 3, 1); // new location
for (int16_t tel = -320; tel < 1; tel += 2) leftSp.pushSprite(tel, 95);
for (uint8_t tel = 0; tel < 64; tel++) leftSp.pushSprite(0, 95); // Keep text on the screen for about 500 mSec.
leftSp.deleteSprite();
}
void screen_Saver() { // flash button pressed: toggle display backlight 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);
}
// connect to WiFi, if not successful: start web server
void connect_to_WiFi() {
WiFi.mode(WIFI_MODE_STA);
flash.begin("login_data", true); // true = read only
ssid = flash.getString("ssid", "Wokwi-GUEST");
pasw = flash.getString("pasw", "");
flash.end();
WiFi.begin(ssid.c_str(), pasw.c_str());
// we try for about 8 seconds to connect
for (uint8_t i = 0; i < 50; ++i) {
if (WiFi.isConnected()) {
WiFi.setAutoReconnect(true);
WiFi.persistent(true);
// jumps out of this function when WiFi connection succeeds
return;
}
delay(160);
}
// script lands on this line only when the connection fails
show_Message_No_Connection();
int n = WiFi.scanNetworks();
// html to put found networks on buttons on web page
for (int i = 0; i < n; ++i) {
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();
// infinite loop until the WiFi credentials are inserted
for (;;) server.handleClient();
}
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: 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>";
// we want to store this in flash memory
String myssid = server.arg("ssid");
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();
}
}