Page featuring more displays and sketches

Lilygo T-Display S3 simple weather station



In order not to create just a simple clock all the time, here we create a simple weather station. This time we use the library “WiFiManager.h”. This ensures that our sketch remains concise.

We use the API at https://api.openweathermap.org. You need to request a (free) key there. The free key allows the application to retrieve JSON data up to a maximum of 60 times per minute. You also need to adjust the location data (&lat=50.8&lon=4.8) to your own location. You can also set whether you wish to receive the data in the metric system, or imperial (&units=metric). In this sketch we have not set any units, the temperature in this case is returned in degrees Kelvin. Don't forget to adjust the time zone to your location.

The two buttons on the T-Display-S3 increase or decrease the brightness of the display. Also, the time of the JSON data update will be displayed for 5 seconds after pressing a button.

To use the fonts in this sketch:

You can use any font from your own PC (or a downloaded font) on the ESP32.
We will try the font "HP Simplified Bold" in this example.
If you don't have this on your PC you can download it from https://fonnts.com/hp-simplified/
Save this font somewhere you can find it.

Then click https://rop.nl/truetype2gfx/
Select and upload the font.

Important: You must rename the font file before uploading it. The name is used in a few places in the library itself, so renaming afterwards will result in error messages. In this example, we rename it "HPSimplified.ttf".

Choose your desired size (e.g. 54 pt)
Then click on the button "Get GFX font file".

You can then download the font as e.g. "HPSimplified54pt7b.h". Place it in the same folder as the sketch. If you wish to compile the sketch below, repeat for size 27pt: "HPSimplified27pt7b.h". These names should match those in the sketch.

Next, download the font https://www.dafont.com/wifi.font/. Select and upload it to https://rop.nl/truetype2gfx/.
Choose the size 20pt. Then click on the "Get GFX font file" button.
Place "WIFI20pt7b.h" in the same folder as the sketch.


In the TFT_eSPI library used, a file is provided in the User_Setups folder for this board: Setup206_LilyGo_T_Display_S3.h

Sketch:

// sketch for LilyGo_T_Display_S3 - https://www.lilygo.cc/products/t-display-s3?variant=42585826558133

#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 - https://arduinojson.org/
#include <HTTPClient.h>          // for connection with API (Application Programming Interface)
#include "HPSimplified54pt7b.h"  // see https://espgo.be/freefont.html for
#include "HPSimplified27pt7b.h"  // custom fonts in arduino IDE
#include "WIFI20pt7b.h"          // several WiFi icons (free): https://www.dafont.com/wifi.font

WiFiManager myWiFi;
TFT_eSPI tft = TFT_eSPI();           // User_Setup_Select.h: Setup206_LilyGo_T_Display_S3.h
TFT_eSprite sp = TFT_eSprite(&tft);  // sprite to avoid flickering

struct apiData {            // structure for the weather api
  time_t wtim, sunr, suns;  // epoch time of last weather data update, sunrise & sunset
  float temp;               // temperature
  int humi;                 // humidity
};

enum Buttons {
  BUTTON_FLASH = 0,
  BUTTON_OTHER = 14  // button opposite flash button
};

constexpr uint8_t SCREEN_ORIENTATION = 3, TIME_TOP = 74, TEMP_LEFT = 8, TEMP_TOP = 105, HUMI_LEFT = 210, HUMI_TOP = 97;
uint8_t BACK = 255;  // display backlight initial brightness
bool freshStart = true, showUpdateTime = false;
unsigned long lastAction;
const char* myTimeZone = "CET-1CEST,M3.5.0,M10.5.0/3";  // https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
const char* url = "http://api.openweathermap.org/data/2.5/weather?lat=50.9&lon=4.722&appid=get-your-own-api-key";
struct apiData recData;  // globally declared to avoid the loop()-function resetting the value each time

void setup() {
  pinMode(15, OUTPUT);  // PWD
  digitalWrite(15, HIGH);
  pinMode(BUTTON_FLASH, INPUT_PULLUP);
  pinMode(BUTTON_OTHER, INPUT_PULLUP);
  displayInit();
  myWiFi.setAPCallback(show_Message_No_Connection);  // is called when WiFi connection fails
  myWiFi.autoConnect("TD-S3");                       // connect to known WiFi, if it fails start access point named "TD-S3"
  configTzTime(myTimeZone, "be.pool.ntp.org");       // set time zone & SNTP server
}

void loop() {
  showOnDisplay();
  if (!digitalRead(BUTTON_FLASH)) changeBrightness(true);   // increase display brightness
  if (!digitalRead(BUTTON_OTHER)) changeBrightness(false);  // reduce display brightness
}

void displayInit() {
  const char* conn_Msg[] = { "Connecting", "", "- WiFi", "- Weather Api", "- Time Sync" };
  tft.init();
  tft.setRotation(SCREEN_ORIENTATION);
  tft.setTextColor(TFT_YELLOW);
  tft.fillRectVGradient(0, 0, tft.width(), tft.height(), TFT_BLACK, TFT_NAVY);
  for (uint8_t i = 0; i < 5; i++) tft.drawString(conn_Msg[i], 0, i * 25, 4);
  sp.createSprite(tft.width(), tft.height());  // sprite for faster rendering
  sp.setTextWrap(false);
}

void show_Message_No_Connection(WiFiManager* myWiFi) {
  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." };
  tft.fillScreen(TFT_NAVY);
  tft.setTextColor(TFT_YELLOW);
  for (uint8_t count = 0; count < 6; count++) tft.drawCentreString(noConnec[count], tft.width() / 2, count * 28, 4);
}

bool timeElapsed(unsigned long myMillis) {   // avoid blocking task "delay(millisec)"
  return millis() - lastAction >= myMillis;  // to someone unfamiliar with c++, this may look cryptic
}

apiData* getApiData() {  // retrieve data from api
  static apiData dataToReturn;
  if (timeElapsed(950)) {  // prevent updating several times per cycle
    lastAction = millis();
    HTTPClient http;
    http.begin(url);  // free 60 calls / minute
    int httpResponseCode = http.GET();
    if (httpResponseCode == 200) {
      String payload = http.getString();
      JsonDocument doc;  
      DeserializationError error = deserializeJson(doc, payload);
      if (!error) {
        dataToReturn.wtim = doc["dt"];                         // time_t UTC weather data (epoch time)
        dataToReturn.sunr = doc["sys"]["sunrise"];             // time_t UTC sunrise
        dataToReturn.suns = doc["sys"]["sunset"];              // time_t UTC sunset
        dataToReturn.temp = doc["main"]["temp"];               // float temp (kelvin)
        dataToReturn.humi = doc["main"]["humidity"];           // int humidity %
      } else dataToReturn = { 0, 0, 0, 372.15, 99 };           // json error
    } else dataToReturn = { 11040, 11040, 11040, 273.15, 0 };  // http error
    http.end();
  }
  return &dataToReturn;
}

void showOnDisplay() {                               // display time & weather information
  struct tm tInfo;                                   // https://cplusplus.com/reference/ctime/tm/
  getLocalTime(&tInfo);                              // time sync during startup and every 3 hours thereafter (esp32)
  if ((freshStart) || (millis() % 180000 < 1000)) {  // api update at startup & every 3 minutes thereafter
    freshStart = false;
    apiData* recDataPtr = getApiData();  // retrieve new data
    if (recDataPtr != nullptr) recData = *recDataPtr, showUpdateTime = true;
  }
  sp.fillSprite(TFT_BLACK);
  sp.setFreeFont(&HPSimplified54pt7b);
  sp.setTextColor(TFT_CYAN);
  sp.setCursor(0, TIME_TOP);
  sp.printf("%02d:%02d", tInfo.tm_hour, tInfo.tm_min);
  sp.setFreeFont(&HPSimplified27pt7b);
  sp.setCursor(258, TIME_TOP);
  sp.printf("%02d", tInfo.tm_sec);
  sp.setTextColor(recData.temp < 273.15 ? TFT_CYAN : TFT_YELLOW);         // below zero Celsius: cyan
  sp.drawFloat(recData.temp - 273.15, 1, TEMP_LEFT + 38, TEMP_TOP - 14);  // celsius
  sp.setTextColor(TFT_YELLOW);
  sp.drawString(String(recData.humi), HUMI_LEFT + 24, HUMI_TOP - 6);
  sp.setFreeFont(&FreeSansBold12pt7b);
  sp.drawString("%", HUMI_LEFT + 84, HUMI_TOP + 13);
  draw_icons();  // thermometer, water drop, sun, arrows
  if (showUpdateTime) {
    sp.setTextColor(TFT_MAGENTA);
    sp.drawString("data", 110, 153, 2);
    sp.drawString(hourMinLocalTime(recData.wtim), 140, 150, 4);
    sp.setTextColor(TFT_WHITE);
    if (timeElapsed(5000)) {
      showUpdateTime = false;
      lastAction = millis();
    }
  }
  sp.setTextColor(TFT_WHITE);
  sp.drawString(hourMinLocalTime(recData.sunr), 42, 150, 4);   // sunrise
  sp.drawString(hourMinLocalTime(recData.suns), 255, 150, 4);  // sunset
  sp.drawFastHLine(0, TIME_TOP + 14, tft.width(), TFT_DARKGREY);
  sp.drawFastHLine(0, TEMP_TOP + 35, tft.width(), TFT_DARKGREY);
  sp.pushSprite(0, 0);
}

String hourMinLocalTime(time_t utcTime) {  // convert epoch time (UTC) to string "hh:mm" in local time
  struct tm* cor = localtime(&utcTime);
  char buff[6];
  strftime(buff, sizeof(buff), "%R", cor);  // https://cplusplus.com/reference/ctime/strftime
  return buff;
}

void changeBrightness(bool increase) {  // button pressed: increase or reduce brightness
  showUpdateTime = true;                // pressing the button will also display the data update time for 5 seconds
  if (timeElapsed(300)) {               // UI debouncing 0.3 sec
    increase ? BACK += 16 % 255 : BACK -= 16 % 255;
    analogWrite(TFT_BL, BACK);
    lastAction = millis();
  }
}

void draw_icons() {
  draw_wifi_icon();
  draw_thermometer_icon();
  draw_celsius_icon();
  draw_water_drop_icon();
  draw_sun_icon(0);
  draw_sun_icon(213);
  sp.fillTriangle(27, 167, 37, 167, 32, 154, TFT_YELLOW);     // upward pointing triangle "sunrise"
  sp.fillTriangle(240, 154, 250, 154, 245, 167, TFT_YELLOW);  // downward pointing triangle "sunset"
  sp.drawLine(245, 167, 245, 169, TFT_YELLOW);                // correction on downward triangle rounding the point
}

void draw_wifi_icon() {
  sp.setTextColor(WiFi.isConnected() ? TFT_GREEN : TFT_RED);  // red color when the connection is lost
  sp.setFreeFont(&WIFI20pt7b);                                // https://www.dafont.com/wifi.font = WiFi logo
  sp.drawString("b", 267, 2);
}

void draw_celsius_icon() {
  sp.setTextColor(TFT_YELLOW);
  sp.setFreeFont(&FreeSansBold12pt7b);
  sp.drawString("c", TEMP_LEFT + 15, TEMP_TOP - 5);
  sp.fillCircle(TEMP_LEFT + 14, TEMP_TOP - 3, 3, TFT_YELLOW);
  sp.fillCircle(TEMP_LEFT + 14, TEMP_TOP - 3, 1, TFT_BLACK);
}

void draw_thermometer_icon() {
  sp.drawRoundRect(TEMP_LEFT + 3, TEMP_TOP - 6, 5, 22, 3, TFT_CYAN);
  sp.fillRect(TEMP_LEFT + 4, TEMP_TOP + 5, 3, 7, TFT_RED);
  sp.drawCircle(TEMP_LEFT + 5, TEMP_TOP + 17, 5, TFT_CYAN);
  sp.fillCircle(TEMP_LEFT + 5, TEMP_TOP + 17, 4, TFT_RED);
  sp.fillCircle(TEMP_LEFT + 5, TEMP_TOP + 16, 2, TFT_WHITE);
}

void draw_water_drop_icon() {
  sp.fillCircle(HUMI_LEFT, HUMI_TOP + 21, 8, TFT_CYAN);
  sp.fillTriangle(HUMI_LEFT, HUMI_TOP + 5, HUMI_LEFT - 8, HUMI_TOP + 21, HUMI_LEFT + 8, HUMI_TOP + 21, TFT_CYAN);
  sp.fillTriangle(HUMI_LEFT, HUMI_TOP + 9, HUMI_LEFT - 4, HUMI_TOP + 19, HUMI_LEFT + 4, HUMI_TOP + 19, 0x0595);
  sp.drawSmoothArc(HUMI_LEFT, HUMI_TOP + 20, 6, 5, 0, 110, TFT_WHITE, TFT_WHITE, true);
  sp.fillCircle(HUMI_LEFT, HUMI_TOP + 21, 5, 0x0595);
  sp.fillCircle(HUMI_LEFT - 3, HUMI_TOP + 18, 2, TFT_WHITE);
}

void draw_sun_icon(uint8_t left) {
  sp.drawLine(2 + left, 151, 18 + left, 167, TFT_YELLOW);
  sp.drawLine(2 + left, 167, 18 + left, 151, TFT_YELLOW);
  sp.drawLine(10 + left, 148, 10 + left, 170, TFT_YELLOW);
  sp.drawLine(0 + left, 159, 20 + left, 159, TFT_YELLOW);
  sp.fillCircle(10 + left, 159, 7, TFT_YELLOW);
}


Page featuring more displays and sketches