Часы и погода от Оpenweathermap, ESP32 + дисплей ILI9341

Текущая погода.

  1. Необходимо получить API ключ Оpenweathermap.
    Members
    Производится запрос текущей погоды каждый час.
  2. Настройка библиотеки TFT_eSPI (version=2.5.30, оригинал - изменений нет).
    GitHub - Bodmer/TFT_eSPI: Arduino and PlatformIO IDE compatible TFT library optimised for the Raspberry Pi Pico (RP2040), STM32, ESP8266 and ESP32 that supports different driver chips · GitHub
    По умолчанию эта библиотека будет работать для ESP8266 с использованием драйвера ILI9341.
    Но в нашем случае используем ESP32, поэтому некоторые настройки нужно изменить.
    Заменяем файл “User_Setup.h” в корне библиотеки файлом “User_Setup.h” из архива.
  3. Подключение к WiFi через WiFiManager, IP - 192.168.4.1, сеть = “ESP32-Clock”.
    Настройки в “settings.h”. Есть OTA обновление прошивки.
    Скриншоты, подключение дисплея к ESP32 и фото дисплея в архиве.
    Clock_OpenWeather.zip — Яндекс Диск
    ================================
    Подключение дисплея ILI9341 к ESP32 (рис. ili9341_esp32.png)
    Display SDO/MISO - подключать не нужно!
    Display LED to ESP32 pin 5
    Display SCK to ESP32 pin 18
    Display SDI/MOSI to ESP32 pin 23
    Display DC to ESP32 pin 2
    Display RESET to ESP32 pin 4
    Display CS to ESP32 pin 15
    Display GND to ESP32 pin GND
    Display VCC to ESP32 3.3V
    ===================================
    Clock_OpenWeather

Clock_OpenWeather.ino


#include <ArduinoJson.h>          //version - 6.21.2
#include <SPI.h>
#include <TimeLib.h>              //version=1.6.1
#include <ArduinoOTA.h>
#include <WiFiUdp.h>
#include <TFT_eSPI.h>             // version=2.5.30  
#include <WiFiClient.h>
#include <DNSServer.h>
#include <HTTPClient.h>
#include <WiFi.h>
#include <Wire.h>
#include <WiFiManager.h>
#include "fonts.h"
#include "ImgWea60.h"
#include "settings.h"
#define LED_BUILTIN  5        // управление яркостью дисплея 
#define LED_BRIGHTNESS 30     // яркость дисплея при старте
#define ENABLE_OTA_UPDATE true        // OTA обновление
#define RU10 &FreeSansBold10pt8b
#define RU8 &FreeMonoBold8pt8b 
#define RUSW &TinyFont5
#define DIG20 &DIG_Bold_20
#define TFT_W 240 //установить ширину экрана tft
#define TIME_OFFSET timeZone * SECS_PER_HOUR // UTC + timeZone час 
//------------------------------------------------
#define Serial_Print    // отладка для работы  - закоментить
//------------------------------------------------
 WiFiUDP Udp;
 WiFiManager wifiManager;
 TFT_eSPI tft = TFT_eSPI();       
 TFT_eSprite ClockSpr = TFT_eSprite(&tft);
 TFT_eSprite ScrollWeatherSpr = TFT_eSprite(&tft);
 HTTPClient http;
 WiFiClient client;
/*----- Weather Json -----*/
 typedef struct {
   float lon; // 37.71
   float lat; // 51.84
   String description; // "небольшая облачность"
   String icon; // "03n"
   float temp; // 12.56
   float feels_like; // 11.96
   int pressure; // 1022     // приведённое к уровню моря давление.
   int humidity; // 80
   int grnd_level; // 1001   // давление на местности 
   int visibility; // 10000
   float speed; // 2.1
   int deg; // 354
   float gust; // 2.15
   long dt; // 1686079705
   String country; // "RU"
   long sunrise; // 1686013976
   long sunset; // 1686073370
   long id; // 540121
   String name; // "Кшенский"
} weather_t;
 weather_t weather;

 void draw_Clocktime();
 void MSG_Weather_Print(String& MSG_W);
 void draw_Scroll();
 void TFT_weather_cur();
 void draw_Sectime();
 bool decode_json(Stream& jsonStr);
 void Get_Weather_http(String& MSG_http);
 void getWeather();
 void restart();
 void display_brightness();
 void initWiFi();

void print_Img(int x, int y, String WeaIcon) {
  int w, h; w=60; h=60; 
  if      (WeaIcon == "01d") tft.pushImage(x,y,w,h,img_01d);
  else if (WeaIcon == "01n") tft.pushImage(x,y,w,h,img_01n);  
  else if (WeaIcon == "02d") tft.pushImage(x,y,w,h,img_02d);
  else if (WeaIcon == "02n") tft.pushImage(x,y,w,h,img_02n);  
  else if (WeaIcon == "03d" || WeaIcon == "03n") tft.pushImage(x,y,w,h,img_03dn);
  else if (WeaIcon == "04d" || WeaIcon == "04n") tft.pushImage(x,y,w,h,img_04dn);
  else if (WeaIcon == "09d" || WeaIcon == "09n") tft.pushImage(x,y,w,h,img_09dn);
  else if (WeaIcon == "10d" || WeaIcon == "10n") tft.pushImage(x,y,w,h,img_10dn);  
  else if (WeaIcon == "11d" || WeaIcon == "11n") tft.pushImage(x,y,w,h,img_11dn);
  else if (WeaIcon == "13d" || WeaIcon == "13n") tft.pushImage(x,y,w,h,img_13dn);
  else if (WeaIcon == "50d" || WeaIcon == "50n") tft.pushImage(x,y,w,h,img_50dn);
  Serial.println("Icon name: " + WeaIcon);
}// print_Img
/*------------ TFT Clocktime -----------------------------*/
void draw_Clocktime() {
  uint8_t hh = hour(), mm = minute(), ss = second(); 
  ClockSpr.setTextColor(TFT_WHITE, TFT_BLACK);
  ClockSpr.setFreeFont(RU10);
  ClockSpr.drawString(utf8rus( days[ weekday() ] ), 120, ypos + 20);
  ClockSpr.setTextColor(TFT_YELLOW, TFT_BLACK);
  ClockSpr.setFreeFont(DIG20);  
  if (weather.temp > 0)
    ClockSpr.drawString(("+" + String(weather.temp,1) + "@C"), 120,ypos + 50); 
   else                                       //@ - перерисован на знак градуса °C, текущая температура
    ClockSpr.drawString((String(weather.temp,1) + "@C"), 120,ypos + 50);  
  ClockSpr.setFreeFont(RU10);  
  ClockSpr.setTextColor(TFT_GREENYELLOW, TFT_BLACK);
  ClockSpr.drawString( utf8rus( String( day() ) + " " + months[ month() ] + " " + year()), 120, 10 );
  ClockSpr.setTextColor(TFT_WHITE, TFT_BLACK);
  byte omm = 99, oss = 99;
  byte xcolon = 0;
  int x_pos = 51;   // // d:\arduino-1.8.19\...\libraries\TFT_eSPI\examples\320 x 240\TFT_Clock_Digital
  ClockSpr.setTextDatum(TL_DATUM );
  if (omm != mm) {                                         // Обновлять часы и минуты каждую минуту
      omm = mm;                                              // рисуем часы и минуты  
      ClockSpr.setTextColor(TFT_WHITE, TFT_BLACK);
      if (hh < 10) x_pos += ClockSpr.drawChar('0', x_pos, ypos - 40, 7); // Добавить 0 в часы
      x_pos += ClockSpr.drawNumber(hh, x_pos, ypos - 40, 7);             // рисуем часы
      xcolon = x_pos;                                         // Сохранение координат двоеточия, чтобы мигать позже
      x_pos += ClockSpr.drawChar(':', x_pos, ypos - 40, 7);
      if (mm < 10) x_pos += ClockSpr.drawChar('0', x_pos, ypos - 40, 7); // Добавить 0 в минуты 
      x_pos += ClockSpr.drawNumber(mm, x_pos, ypos - 40, 7);             // рисуем минуты
    }
  if (oss != ss) {                                         // Перерисовывать секунды каждую секунду
      oss = ss;
      if (ss % 2) {                                          // Включить/выключить двоеточие
        ClockSpr.setTextColor(TFT_BLACK, TFT_BLACK);        // затемнить двоеточие
        ClockSpr.drawChar(':', xcolon, ypos - 40, 7);                  // Час:минута двоеточие
        ClockSpr.setTextColor(TFT_WHITE, TFT_BLACK);        // Вернуть цвет 
      }
      else {
        ClockSpr.drawChar(':', xcolon, ypos - 40, 7);                  // Час:минута двоеточие
      } 
    ClockSpr.setFreeFont(DIG20);
      x_pos = 103;                                
      if (ss < 10) x_pos += ClockSpr.drawNumber(0, x_pos, ypos - 65); // Добавить 0
      ClockSpr.drawNumber(ss, x_pos, ypos - 65);                     // рисуем секунды
    } 
  ClockSpr.setTextDatum(MC_DATUM);
} // end draw_Clocktime
/*------------ TFT draw_Sectime -----------------------------*/
void draw_Scroll() {
  String MSG_Weather;
  MSG_Weather.reserve(350);
  MSG_Weather += F(" Погода ");
  MSG_Weather += weather.name;
  MSG_Weather += F(",");
  MSG_Weather += weather.description;
  MSG_Weather += F(",Температура ");
  MSG_Weather += String(weather.temp,1);
  MSG_Weather += F("@C,Ощущается как ");
  MSG_Weather += String(weather.feels_like,1);
  MSG_Weather += F("@C,Давление ");
  MSG_Weather += String(weather.grnd_level * 0.750062, 0); // давление на местности
  MSG_Weather += F(" мм,Влажность ");
  MSG_Weather += String(weather.humidity);
  MSG_Weather += F("%,Ветер ");
  MSG_Weather += String(WindDeg_Direction(weather.deg));
  MSG_Weather += F(",");
  MSG_Weather += String(weather.speed, 1);
  MSG_Weather += F(" м/c,Порывы ");
  MSG_Weather += String(weather.gust, 1);
  MSG_Weather += F(" м/c");
  int16_t MSG_Weather_Width;//Ширина  прокручиваемого  всего сообщения в пикселях.
  int Space_Repeats = 50; //Пробел между повторами в пикселях.
  MSG_Weather_Width = tft.textWidth(utf8rus(MSG_Weather))+ Space_Repeats; // -60
  ScrollWeatherSpr.createSprite(MSG_Weather_Width + TFT_W, 12 );
  Step_Count--; 
  if (Step_Count <= 0) { 
     Step_Count = (MSG_Weather_Width / abs(Step_Scroll));
	 MSG_Weather_Print(MSG_Weather);
    }
  ScrollWeatherSpr.scroll(Step_Scroll);    // Сдвиньте спрайт на пиксели Step_Scroll.
  ScrollWeatherSpr.pushSprite(0, 247); // Верхний левый угол спрайта
} // end draw_Scroll

void TFT_weather_cur()  // отрисовка прогноза погоды
{
  String Sun_Time;
  String Sun_Hour;  
  int x_Sun = 80; // координаты отрисовки 
  tft.fillRect ( 0, 259, 239, 60, TFT_BLACK);  // Очистить  Восход - Закат
  print_Img(10, 259, weather.icon);
  tft.setFreeFont(RU10);
  tft.setTextSize(1);  
  tft.setTextColor(TFT_WHITE, TFT_BLACK, true);
  tft.drawString(utf8rus(weather.name), 80, 259);  // нас. пункт
  tft.setFreeFont(RU8);
  tft.setTextSize(1);  
  tft.setTextColor(TFT_GREENYELLOW, TFT_BLACK, true);
  time_t hour_t = hour(weather.sunrise + TIME_OFFSET); // Восход
  time_t minute_t = minute(weather.sunrise + TIME_OFFSET);
  if (hour_t < 10)	// добавить 0 к часам Восход
  Sun_Hour = "0" + String(hour_t);
  else Sun_Hour = String(hour_t); 
  if (minute_t < 10) // добавить 0 к минутам Восход
  Sun_Time = Sun_Hour + ":0" + String(minute_t);
  else Sun_Time = Sun_Hour + ":" + String(minute_t);
  x_Sun +=  tft.drawString(utf8rus("Восход:"), 80, 285);  // Восход
  tft.drawString(Sun_Time, x_Sun+5, 285);  // Восход
  hour_t = hour(weather.sunset + TIME_OFFSET); // Закат
  minute_t = minute(weather.sunset + TIME_OFFSET);  
  if (minute_t < 10)  // добавить 0 к минутам Закат
  Sun_Time = String(hour_t) + ":0" + String(minute_t);
  else Sun_Time = String(hour_t) + ":" + String(minute_t);    
  tft.drawString(utf8rus("Закат:"), 80, 303);   // Закат
  tft.drawString(Sun_Time, x_Sun+5, 303);   // Закат
 } // end TFT_weather_cur
/*------------ TFT draw_Sectime -----------------------------*/
void draw_Sectime() {
  float value, circle = 100;
  double rad = 0.01745;
  String sec_t[12] = {"45", "40", "35", "30", "25", "20", "15", "10", "05", "0", "55", "50"};
  int start[12], startP[60], angle = 0, r = 105, lastAngle = 0, rAngle = 359, b = 0, b2 = 0;
  bool dir = 0;
  for (int i = 0; i < 360; i++) {
    x[i] = (r * cos(rad * i)) + xpos;
    y[i] = (r * sin(rad * i)) + ypos;
    px[i] = ((r - 16) * cos(rad * i)) + xpos;
    py[i] = ((r - 16) * sin(rad * i)) + ypos;
    lx[i] = ((r - 26) * cos(rad * i)) + xpos;
    ly[i] = ((r - 26) * sin(rad * i)) + ypos;
    if (i % 30 == 0) {
      start[b] = i;
      b++;
    }
    if (i % 6 == 0) {
      startP[b2] = i;
      b2++;
    }
  }
  rAngle = rAngle - 2;
  angle = second() * 6;
  if (angle >= 360) angle = 0;
  if (rAngle <= 0) rAngle = 359;
  if (dir == 0) circle = circle + 0.5;
  else  circle = circle - 0.5;
  if (circle > 140) dir = !dir;
  if (circle < 100) dir = !dir;
  if (angle > -1) {
    lastAngle = angle;
    value = ((angle - 270) / 3.60) * -1;
    if (value < 0) value = value + 100;
    ClockSpr.fillSprite(TFT_BLACK);
    ClockSpr.fillCircle(xpos, ypos, 124, TFT_BLACK);
    for (int i = 0; i < 12; i++)
      if (start[i] + angle < 360) {
        ClockSpr.drawString(sec_t[i], x[start[i] + angle], y[start[i] + angle], 2);
        ClockSpr.drawLine(px[start[i] + angle], py[start[i] + angle], lx[start[i] + angle], ly[start[i] + angle], TFT_WHITE);
      }
      else  {
        ClockSpr.drawString(sec_t[i], x[(start[i] + angle) - 360], y[(start[i] + angle) - 360], 2);
        ClockSpr.drawLine(px[(start[i] + angle) - 360], py[(start[i] + angle) - 360], lx[(start[i] + angle) - 360], ly[(start[i] + angle) - 360], TFT_WHITE);
      }
    for (int i = 0; i < 60; i++)
      if (startP[i] + angle < 360)
        ClockSpr.fillCircle(px[startP[i] + angle], py[startP[i] + angle], 1, TFT_YELLOW);
      else
        ClockSpr.fillCircle(px[(startP[i] + angle) - 360], py[(startP[i] + angle) - 360], 1, TFT_YELLOW);
    ClockSpr.fillTriangle(xpos, ypos - 80, xpos - 3, ypos - 70, xpos + 3, ypos - 70, TFT_RED);
   }
}// end draw_Sectime

void setup(void) {
  pinMode(LED_BUILTIN, OUTPUT);
  analogWrite(LED_BUILTIN, LED_BRIGHTNESS); // первоначальная яркость дисплея
  Serial.begin(115200);
  tft.begin();
  tft.setSwapBytes(true); //*/
  tft.setRotation(0);
  tft.setTextSize(2);
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_WHITE, TFT_BLACK, true);
  tft.setCursor(0, 26);
  tft.println("Connecting..");
  Serial.println("");
  Serial.println("NTP Clock");
  Serial.print("Connecting to ");
  Serial.println(WiFi.SSID());
  initWiFi();
  tft.println("");
  tft.println("Clock_Esp32");
  tft.println(WiFi.SSID());
  tft.print("IP: ");
  tft.println(WiFi.localIP());
  tft.println("Starting UDP");
  Udp.begin(localPort);
  tft.println("Time sync");
  setSyncProvider(getNtpTime);
  setSyncInterval(3600);
  display_brightness();
  delay(2500); // читаем что вывели на дисплей
  if ( ENABLE_OTA_UPDATE )
  {
    tft.println("Begin OTA handler");
    tft.println("Esp32-Clock");
    initOTA(); // обновление по WiFi
  }
  tft.fillScreen(TFT_BLACK);
  ClockSpr.setColorDepth(8);
  ClockSpr.setSwapBytes(true);
  ClockSpr.createSprite(240, 245);
  ClockSpr.setTextDatum(MC_DATUM);
  ScrollWeatherSpr.setTextDatum(TL_DATUM );
  ScrollWeatherSpr.setColorDepth(1); // Глубина цвета сильно влияет на размер спрайта, который вы можете использовать, и на скорость его перемещения.
  ScrollWeatherSpr.setSwapBytes(true);
  ScrollWeatherSpr.setTextSize(2); 
  ScrollWeatherSpr.setFreeFont(RUSW);   
  getWeather();
  TFT_weather_cur();	  
}// end setup

void loop() {
  if ( ENABLE_OTA_UPDATE )
  {
    ArduinoOTA.handle();
  }
  if ((millis() > 60*1000) and (year() == 1970) and (WiFi.status() == WL_CONNECTED))  //при старте время не было задано 
    {
      restart();
     }	
  display_brightness();
  draw_Sectime();
  draw_Clocktime();
  ClockSpr.pushSprite(0, 0);
//  draw_Scroll();
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
     draw_Scroll();
     previousMillis = currentMillis; }  
  if ((millis() - lastTime) > timerDelay*60*1000) {  
     getWeather();
     TFT_weather_cur();	 
     lastTime = millis(); } 
} // end loop

bool decode_json(Stream& jsonStr)
{
  DynamicJsonDocument doc(1024);
  DeserializationError error = deserializeJson(doc, jsonStr);
  if (error) {
    Serial.print(F("deserializeJson() failed: "));
    Serial.println(error.f_str());
    return false;
    } else
      {
      Serial.println("deserializeJson() без ошибок.");
      JsonObject doc_OPW = doc.as<JsonObject>();
      weather.lon = doc_OPW["coord"]["lon"].as<float>();
      weather.lat = doc_OPW["coord"]["lat"].as<float>();	
      weather.description = doc_OPW["weather"][0]["description"].as<const char *>();	
      weather.icon = doc_OPW["weather"][0]["icon"].as<const char *>();	
      weather.temp = doc_OPW["main"]["temp"].as<float>();	  
      weather.feels_like = doc_OPW["main"]["feels_like"].as<float>();	  
      weather.pressure = doc_OPW["main"]["pressure"].as<int>();	  
      weather.humidity = doc_OPW["main"]["humidity"].as<int>();	  
      weather.grnd_level = doc_OPW["main"]["grnd_level"].as<int>();	  
      weather.visibility = doc_OPW["visibility"].as<int>();	  
      weather.speed = doc_OPW["wind"]["speed"].as<float>();	  
      weather.deg = doc_OPW["wind"]["deg"].as<int>();	  
      weather.gust = doc_OPW["wind"]["gust"].as<float>();	  
      weather.dt = doc_OPW["dt"].as<long>();	  
      weather.country = doc_OPW["sys"]["country"].as<const char *>();	
      weather.sunrise = doc_OPW["sys"]["sunrise"].as<long>();	  
      weather.sunset = doc_OPW["sys"]["sunset"].as<long>();	  
      weather.id = doc_OPW["id"].as<long>();	  
      weather.name = doc_OPW["name"].as<const char *>();	
     return true;
    }//end else
}//end decode_json

void getWeather() {
  if(WiFi.status()== WL_CONNECTED){   // проверяем соединение WiFi
    String host_uri;
    host_uri.reserve(150);
    host_uri += F("http://api.openweathermap.org/data/2.5/");
    host_uri += F("weather?lat=");
    host_uri += Latitude;
    host_uri += F("&lon=");
    host_uri += Longitude;
    host_uri += F("&lang=ru&appid=");
    host_uri += apikey;
    host_uri += F("&mode=json&units=metric&cnt=1");
    client.stop();
    Get_Weather_http(host_uri);
    int httpCode = http.GET();
    Serial.println(httpCode);
    if (httpCode > 0) { 
      Stream& response = http.getStream(); // ответ  
      decode_json(response); // парсинг данных из JsonObject
#ifdef  Serial_Print  //  отладка      
     Serial.println(" - - weather - - ");
     Serial.print("weather.lon "); Serial.println(weather.lon);
     Serial.print("weather.lat "); Serial.println(weather.lat);
     Serial.print("weather.description "); Serial.println(weather.description);
     Serial.print("weather.icon "); Serial.println(weather.icon);
     Serial.print("weather.temp "); Serial.println(weather.temp);
     Serial.print("weather.feels_like "); Serial.println(weather.feels_like);
     Serial.print("weather.pressure "); Serial.println(weather.pressure);
     Serial.print("weather.humidity "); Serial.println(weather.humidity);
     Serial.print("weather.grnd_level "); Serial.println(weather.grnd_level);
     Serial.print("weather.visibility "); Serial.println(weather.visibility);
     Serial.print("weather.speed "); Serial.println(weather.speed);
     Serial.print("weather.deg "); Serial.println(weather.deg);
     Serial.print("weather.gust "); Serial.println(weather.gust);
     Serial.print("weather.dt "); Serial.println(weather.dt);
     Serial.print("weather.country "); Serial.println(weather.country);
     Serial.print("weather.sunrise "); Serial.println(weather.sunrise);
     Serial.print("weather.sunset "); Serial.println(weather.sunset);
     Serial.print("weather.id "); Serial.println(weather.id);
     Serial.print("weather.name "); Serial.println(weather.name);
#endif
      client.stop();
      http.end();
     }  else
      {
        Serial.println("Connection failed");
        client.stop();
        http.end();
      }
    }
} //end getWeather

String WindDeg_Direction(int Wind_direction) {
  if (Wind_direction >= 338 || Wind_direction < 22)  return Wind_N;  //"Северный";
  if (Wind_direction >=  22 && Wind_direction < 68)  return Wind_NE; //"Северо-Восточный";
  if (Wind_direction >=  68 && Wind_direction < 112) return Wind_E;  //"Восточный";
  if (Wind_direction >= 112 && Wind_direction < 158) return Wind_SE; //"Юго-Восточный";
  if (Wind_direction >= 158 && Wind_direction < 202) return Wind_S;  //"Южный";
  if (Wind_direction >= 202 && Wind_direction < 248) return Wind_SW; //"Юго-Западный";
  if (Wind_direction >= 248 && Wind_direction < 292) return Wind_W;  //"Западный";
  if (Wind_direction >= 292 && Wind_direction < 338) return Wind_NW; //"Северо-Западный";
  return " ?";
}  //end WindDeg_Direction

void restart() // перезапуск ESP32
{
  Serial.println("Restart ESP...");
  ESP.restart();
} // end restart

void display_brightness() // яркость дисплея
{ 
  if ( ( hour() >= day_ ) and ( hour() < night_ ) )
  {
    analogWrite(LED_BUILTIN, slider_day);
  }
  else
  {
    analogWrite(LED_BUILTIN, slider_night);
  }
} // end display_brightness

void initWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin();
  WiFi.persistent(false);
  WiFi.setAutoReconnect(true);
  Serial.println("Connecting to WiFi ..");
  while (WiFi.status() != WL_CONNECTED && millis() < 15 * 1000) {
    delay(500);
    Serial.print(".");
  }
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("");
    Serial.println("WiFi connected");
    Serial.print("IP: ");
    Serial.println(WiFi.localIP());
  }
  else {
    Serial.println("WiFi not connected, starting WiFiManager");
    tft.println("WiFi not connected..");
    tft.println("Starting WiFiManager");
    tft.println("SSID: ESP32-Clock");
    tft.println("IP: 192.168.4.1");
    wifiManager.autoConnect("ESP32-Clock");
    delay(2000);
  }
} // end initWiFi


void MSG_Weather_Print(String& MSG_W) {
  ScrollWeatherSpr.drawString(utf8rus(MSG_W), TFT_W,0);
}

void Get_Weather_http(String& MSG_http) {
    Serial.println(MSG_http);
    http.begin(client, MSG_http);
}

/*-------------- NTP code -------------------------*/ // https://wikihandbk.com/wiki/Arduino:%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D1%80%D1%8B/TimeNTP_ESP8266WiFi
const int NTP_PACKET_SIZE = 48;      // NTP-время – в первых 48 байтах сообщения
byte packetBuffer[NTP_PACKET_SIZE];  // буфер для хранения входящих и исходящих пакетов
// отправляем NTP-запрос серверу времени по указанному адресу:
void sendNTPpacket(IPAddress &address)
{
  // задаем все байты в буфере на «0»:
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // инициализируем значения для создания NTP-запроса
  // (подробнее о пакетах смотрите по ссылке выше)
  packetBuffer[0] = 0b11100011;   // LI (от «leap indicator», т.е. «индикатор перехода»), версия, режим работы
  packetBuffer[1] = 0;     // слой (или тип часов)
  packetBuffer[2] = 6;     // интервал запросов
  packetBuffer[3] = 0xEC;  // точность
  // 8 байтов с нулями, обозначающие базовую задержку и базовую дисперсию:
  packetBuffer[12] = 49;
  packetBuffer[13] = 0x4E;
  packetBuffer[14] = 49;  // После заполнения всех указанных полей
  packetBuffer[15] = 52;  // вы сможете отправлять пакет с запросом о временной метке
  Udp.beginPacket(address, 123); //  NTP-запросы к порту 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
} // end sendNTPpacket

time_t getNtpTime()
{
  IPAddress ntpServerIP; // IP-адрес NTP-сервера
  while (Udp.parsePacket() > 0) ; // отбраковываем все пакеты, полученные ранее
  Serial.println("Transmit NTP Request"); //  "Передача NTP-запроса"
  WiFi.hostByName(ntpServerName, ntpServerIP);  //  подключаемся к случайному серверу из списка
  Serial.print(ntpServerName);
  Serial.print(": ");
  Serial.println(ntpServerIP);
  sendNTPpacket(ntpServerIP);
  uint32_t beginWait = millis();
  while (millis() - beginWait < 1500) {
    int size = Udp.parsePacket();
    if (size >= NTP_PACKET_SIZE) {
      Serial.println("Receive NTP Response"); // "Получение NTP-ответа"
      Udp.read(packetBuffer, NTP_PACKET_SIZE);  // считываем пакет в буфер
      unsigned long secsSince1900;
      secsSince1900 =  (unsigned long)packetBuffer[40] << 24; //конвертируем 4 байта (начиная с позиции 40)
      secsSince1900 |= (unsigned long)packetBuffer[41] << 16; //в длинное целое число
      secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
      secsSince1900 |= (unsigned long)packetBuffer[43];
      return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
    }
  }
  Serial.println("No NTP Response :-("); //  "Нет NTP-ответа :("
  return 0; // если время получить не удалось, возвращаем «0»
} // end getNtpTime
/*-------- ArduinoOTA code ----------*/
void initOTA()
{
  ArduinoOTA.setHostname("Clock_Esp32");
  Serial.println("Begin OTA handler Clock_Esp32");
  // ArduinoOTA.setPassword("admin");
  ArduinoOTA.onStart([]() {
    String type;
    if (ArduinoOTA.getCommand() == U_FLASH)
      type = "sketch";
    else // U_SPIFFS
      type = "filesystem";
    Serial.println("Start updating " + type);
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\nEnd");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR)         Serial.println("Auth Failed");
    else if (error == OTA_BEGIN_ERROR)   Serial.println("Begin Failed");
    else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
    else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
    else if (error == OTA_END_ERROR)     Serial.println("End Failed");
  });
  ArduinoOTA.begin();
}
/*------- RUS fonts from UTF-8 to Windows-1251 -----*/
String utf8rus(String source)
{
  int i, k;
  String target;
  unsigned char n;
  char m[2] = { '0', '\0' };

  k = source.length(); i = 0;

  while (i < k) {
    n = source[i]; i++;

    if (n >= 127) {
      switch (n) {
        case 208: {
            n = source[i]; i++;
            if (n == 129) {
              n = 192;  // перекодируем букву Ё
              break;
            }
            break;
          }
        case 209: {
            n = source[i]; i++;
            if (n == 145) {
              n = 193;  // перекодируем букву ё
              break;
            }
            break;
          }
      }
    }

    m[0] = n; target = target + String(m);
  }
  return target;
}

//


settings.h


#ifndef SETTINGS_H
#define SETTINGS_H
 unsigned long timerDelay = 60;// минуты -таймер обновления погоды 
// Данные openweathermap.org
 String apikey     = F("ZxZxZxZxZxZxZxZxZxzxzx");  // API key            
 String Latitude   = F("51.8408");                         
 String Longitude  = F("37.7136");                        
// настройки яркости дисплея
 byte slider_day   = 80;              //Яркость день максимум 100
 byte slider_night = 10;            //Яркость ночь
 byte night_       = 22;                  //ночь - время уменьшения яркости дисплея
 byte day_         = 7;                     //день - время увеличения яркости дисплея
// время
 static const char ntpServerName[] = "us.pool.ntp.org";
 const int timeZone = 3;            // часовой пояс: UTC+3 MSK
 unsigned int localPort = 8888;     // локальный порт для прослушивания UDP-пакетов 
 String months[13] = {"", "января", "февраля", "марта", "апреля", "мая", "июня", "июля", "августа", "сентября", "октября", "ноября", "декабря"};
 String days[8] = {"", "Воскресенье", "Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота"}; 
//Ветер
const String Wind_N   = F("Северный");
const String Wind_NE  = F("Северо-Восточный");
const String Wind_E   = F("Восточный");
const String Wind_SE  = F("Юго-Восточный");
const String Wind_S   = F("Южный");
const String Wind_SW  = F("Юго-Западный");
const String Wind_W   = F("Западный");
const String Wind_NW  = F("Северо-Западный");
// Глобальные переменные
 int Step_Count       = 0;        // Счетчик шагов прокрутки
 const long interval  = 320; // интервал прокрутки мсек
 int Step_Scroll      = -25;     // шаг прокрутки
 float x[360], y[360], px[360], py[360], lx[360], ly[360];
 int xpos = 120, ypos = 135;
 unsigned long previousMillis = 0;  
 unsigned long lastTime = 0;                          
#endif

Интересное решение

  • спасибо

чисто эмоционально у меня создаёт впечатление подвисания системы каждую секунду…

В момент прорисовки значений, бегущая строка останавливается

а что часики противосонь, это ничего? )))

Дисплейчик маленький.

Или так.

Что это за шрифт, похожий на семисегментный?

да, с дизайном тут явно не айс

Все файлы тут: https://cloud.mail.ru/public/onpP/Xdm19nV9Q