ESP32 Weather Dashboard with Satellite Maps and 16-day Weather Forecast
The system displays real-time weather data including radar maps, cloud coverage, rain intensity, and detailed multi-day forecasts with interactive graphs.
Devices & Components
1
CrowPanel 7.0" -HMI ESP32 Display 800x480
Hardware & Tools
1
Screwdrivers
Software & Tools
Arduino IDE
Project description
Code
Code
cpp
...
1// By mircemk, April 2026 2 3#define LGFX_USE_V1 4#include <LovyanGFX.hpp> 5#include <lgfx/v1/platforms/esp32s3/Panel_RGB.hpp> 6#include <lgfx/v1/platforms/esp32s3/Bus_RGB.hpp> 7#include <WiFi.h> 8#include <HTTPClient.h> 9#include <ArduinoJson.h> 10#include <PNGdec.h> 11#include <math.h> 12#include "time.h" 13#include <Wire.h> 14 15const char* ssid = "***********"; 16const char* password = "***********"; 17const char* ntpServer = "pool.ntp.org"; 18 19const char* weatherUrl = 20 "https://api.open-meteo.com/v1/forecast?" 21 "latitude=41.1171&longitude=20.8016" 22 "¤t=temperature_2m,weather_code" 23 "&hourly=temperature_2m,weather_code,pressure_msl,cloud_cover" 24 "&daily=weather_code,temperature_2m_max,temperature_2m_min,precipitation_sum," 25 "relative_humidity_2m_mean,wind_speed_10m_max,uv_index_max,shortwave_radiation_sum" 26 "&timezone=auto&forecast_days=16"; 27 28const char* owmApiKey = "*********************"; 29 30int myZoom = 6; 31double myLat = 41.1171; 32double myLon = 20.8016; 33unsigned long lastUpdate = 0; 34long radarTS = 0; 35int appState = 0; 36int lastMinute = -1; 37int brightnessLevel = 100; 38int mapStyle = 0; // 0 = dark_all, 1 = opentopomap, 2 = openstreetmap 39int layerStyle = 0; // 0 = Radar, 1 = Clouds, 2 = Rain 40 41String radarHost = "https://tilecache.rainviewer.com"; 42String radarPath = ""; 43 44const char* mapUrls[] = { 45 "https://basemaps.cartocdn.com/dark_all/", 46 "https://tile.opentopomap.org/", 47 "https://tile.openstreetmap.org/" 48}; 49 50const char* mapNames[] = {"DARK", "TOPO", "OSM"}; 51const char* layerNames[] = {"RADAR", "CLOUDS", "RAIN"}; 52const char* owmLayerIds[] = {"", "clouds_new", "precipitation_new"}; 53 54class LGFX : public lgfx::LGFX_Device { 55public: 56 lgfx::Bus_RGB _bus; 57 lgfx::Panel_RGB _panel; 58 lgfx::Light_PWM _light; 59 60 LGFX(void) { 61 { 62 auto cfg = _panel.config(); 63 cfg.memory_width = 800; 64 cfg.memory_height = 480; 65 cfg.panel_width = 800; 66 cfg.panel_height = 480; 67 _panel.config(cfg); 68 } 69 { 70 auto cfg = _bus.config(); 71 cfg.panel = &_panel; 72 cfg.pin_d0 = GPIO_NUM_15; cfg.pin_d1 = GPIO_NUM_7; cfg.pin_d2 = GPIO_NUM_6; cfg.pin_d3 = GPIO_NUM_5; 73 cfg.pin_d4 = GPIO_NUM_4; cfg.pin_d5 = GPIO_NUM_9; cfg.pin_d6 = GPIO_NUM_46; cfg.pin_d7 = GPIO_NUM_3; 74 cfg.pin_d8 = GPIO_NUM_8; cfg.pin_d9 = GPIO_NUM_16; cfg.pin_d10 = GPIO_NUM_1; cfg.pin_d11 = GPIO_NUM_14; 75 cfg.pin_d12 = GPIO_NUM_21; cfg.pin_d13 = GPIO_NUM_47; cfg.pin_d14 = GPIO_NUM_48; cfg.pin_d15 = GPIO_NUM_45; 76 cfg.pin_henable = GPIO_NUM_41; cfg.pin_vsync = GPIO_NUM_40; cfg.pin_hsync = GPIO_NUM_39; cfg.pin_pclk = GPIO_NUM_0; 77 cfg.freq_write = 12000000; 78 cfg.hsync_front_porch = 40; 79 cfg.hsync_pulse_width = 48; 80 cfg.hsync_back_porch = 40; 81 cfg.vsync_front_porch = 1; 82 cfg.vsync_pulse_width = 31; 83 cfg.vsync_back_porch = 13; 84 cfg.pclk_active_neg = 1; 85 cfg.de_idle_high = 0; 86 cfg.pclk_idle_high = 0; 87 _bus.config(cfg); 88 _panel.setBus(&_bus); 89 } 90 { 91 auto cfg = _light.config(); 92 cfg.pin_bl = GPIO_NUM_2; 93 cfg.freq = 44100; 94 cfg.pwm_channel = 7; 95 _light.config(cfg); 96 _panel.setLight(&_light); 97 } 98 setPanel(&_panel); 99 } 100}; 101 102LGFX lcd; 103LGFX_Sprite mapCanvas(&lcd); 104 105// Weather variables mapping for ZLATEN section 106float currentTemp, morningTemp, noonTemp, eveningTemp; 107int morningCode, noonCode, eveningCode; 108float dMax[16], dMin[16], dRain[16], dPress[16], dCloud[16]; 109float dHum[16], dWind[16], dUV[16], dSolar[16]; 110int dCode[16]; 111 112// Прототипи на функции 113void setBrightnessFromTouchY(int ty); 114void getWeatherData(); 115void renderRadarMap(); 116void drawBottomDashboard(); 117void drawSideButtons(); 118void drawSignature(); 119void drawTopDate(); 120void drawProgressTimer(); 121void drawGraphPage(int type); 122void drawWeatherIcon(int x, int y, int code); 123void drawMiniWeatherIcon(int x, int y, int code); 124int getDayOfMonthOffset(struct tm baseTime, int offsetDays); 125bool fetchPngToBuffer(const String& url, uint8_t** outBuf, size_t* outLen); 126 127#include "touch.h" 128PNG png; 129int globalX, globalY; 130uint16_t panelColor = 0x0841; 131const char* days[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; 132const char* dayShort2[] = {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"}; 133const char* months[] = {"Jan.", "Feb.", "March", "April", "May", "June", "July", "Aug.", "Sept.", "Oct.", "Nov.", "Dec."}; 134 135// --- ICON ENGINE --- 136void drawSun(int x, int y, uint16_t color) { 137 lcd.fillCircle(x, y, 9, color); 138 for (int i = 0; i < 360; i += 45) { 139 float r = i * DEG_TO_RAD; 140 lcd.drawLine(x + cos(r) * 11, y + sin(r) * 11, x + cos(r) * 17, y + sin(r) * 17, color); 141 } 142} 143 144void drawCloud(int x, int y, uint16_t color, int scale = 1) { 145 int off = (scale == 0) ? -2 : 0; 146 lcd.fillCircle(x - 6 + off, y + 2, 7 + off, color); 147 lcd.fillCircle(x + 2 + off, y - 3, 9 + off, color); 148 lcd.fillCircle(x + 10 + off, y + 2, 7 + off, color); 149 lcd.fillRect(x - 6 + off, y + 2, 16, 7 + off, color); 150} 151 152void drawRainDrops(int x, int y, uint16_t color, bool heavy) { 153 lcd.drawLine(x - 4, y + 6, x - 6, y + 13, color); 154 lcd.drawLine(x + 4, y + 6, x + 2, y + 13, color); 155 if (heavy) lcd.drawLine(x, y + 8, x - 2, y + 15, color); 156} 157 158void drawSnowFlakes(int x, int y, uint16_t color, bool heavy) { 159 // left flake 160 lcd.drawLine(x - 5, y + 7, x - 1, y + 11, color); 161 lcd.drawLine(x - 1, y + 7, x - 5, y + 11, color); 162 lcd.drawLine(x - 3, y + 6, x - 3, y + 12, color); 163 lcd.drawLine(x - 6, y + 9, x, y + 9, color); 164 165 // right flake 166 lcd.drawLine(x + 3, y + 7, x + 7, y + 11, color); 167 lcd.drawLine(x + 7, y + 7, x + 3, y + 11, color); 168 lcd.drawLine(x + 5, y + 6, x + 5, y + 12, color); 169 lcd.drawLine(x + 2, y + 9, x + 8, y + 9, color); 170 171 if (heavy) { 172 // center flake 173 lcd.drawLine(x - 1, y + 10, x + 3, y + 14, color); 174 lcd.drawLine(x + 3, y + 10, x - 1, y + 14, color); 175 lcd.drawLine(x + 1, y + 9, x + 1, y + 15, color); 176 lcd.drawLine(x - 2, y + 12, x + 4, y + 12, color); 177 } 178} 179 180void drawMiniSnow(int x, int y, uint16_t color, bool heavy) { 181 // left flake 182 lcd.drawLine(x - 3, y + 4, x - 1, y + 6, color); 183 lcd.drawLine(x - 1, y + 4, x - 3, y + 6, color); 184 lcd.drawPixel(x - 2, y + 3, color); 185 lcd.drawPixel(x - 2, y + 7, color); 186 lcd.drawPixel(x - 4, y + 5, color); 187 lcd.drawPixel(x, y + 5, color); 188 189 // right flake 190 lcd.drawLine(x + 2, y + 4, x + 4, y + 6, color); 191 lcd.drawLine(x + 4, y + 4, x + 2, y + 6, color); 192 lcd.drawPixel(x + 3, y + 3, color); 193 lcd.drawPixel(x + 3, y + 7, color); 194 lcd.drawPixel(x + 1, y + 5, color); 195 lcd.drawPixel(x + 5, y + 5, color); 196 197 if (heavy) { 198 // small center flake 199 lcd.drawPixel(x, y + 6, color); 200 lcd.drawPixel(x, y + 5, color); 201 lcd.drawPixel(x, y + 7, color); 202 lcd.drawPixel(x - 1, y + 6, color); 203 lcd.drawPixel(x + 1, y + 6, color); 204 } 205} 206 207void drawMiniSun(int x, int y, uint16_t color) { 208 lcd.fillCircle(x, y, 4, color); 209 for (int i = 0; i < 360; i += 45) { 210 float r = i * DEG_TO_RAD; 211 lcd.drawLine(x + cos(r) * 6, y + sin(r) * 6, 212 x + cos(r) * 8, y + sin(r) * 8, color); 213 } 214} 215 216void drawMiniCloud(int x, int y, uint16_t color) { 217 lcd.fillCircle(x - 4, y + 1, 4, color); 218 lcd.fillCircle(x + 1, y - 2, 5, color); 219 lcd.fillCircle(x + 6, y + 1, 4, color); 220 lcd.fillRect(x - 4, y + 1, 11, 4, color); 221} 222 223void drawMiniRain(int x, int y, uint16_t color, bool heavy) { 224 lcd.drawLine(x - 3, y + 4, x - 4, y + 8, color); 225 lcd.drawLine(x + 2, y + 4, x + 1, y + 8, color); 226 if (heavy) lcd.drawLine(x, y + 5, x - 1, y + 9, color); 227} 228 229void drawMiniWeatherIcon(int x, int y, int code) { 230 uint16_t sunCol = TFT_YELLOW; 231 uint16_t cloudCol = 0x9E7F; 232 uint16_t rainCol = TFT_CYAN; 233 uint16_t snowCol = TFT_WHITE; 234 235 if (code == 0) { 236 drawMiniSun(x, y, sunCol); 237 } 238 else if (code == 1) { 239 drawMiniSun(x - 2, y - 1, sunCol); 240 drawMiniCloud(x + 4, y + 2, cloudCol); 241 } 242 else if (code == 2) { 243 drawMiniSun(x + 2, y - 3, sunCol); 244 drawMiniCloud(x, y + 1, cloudCol); 245 } 246 else if (code == 3) { 247 drawMiniCloud(x - 2, y, 0x7BEF); 248 drawMiniCloud(x + 3, y + 2, cloudCol); 249 } 250 // Rain / drizzle / rain showers 251 else if ((code >= 51 && code <= 67) || (code >= 80 && code <= 82)) { 252 drawMiniCloud(x, y - 1, cloudCol); 253 drawMiniRain(x, y + 1, rainCol, (code == 65 || code == 82)); 254 } 255 // Snow / snow grains / snow showers 256 else if ((code >= 71 && code <= 77) || (code >= 85 && code <= 86)) { 257 drawMiniCloud(x, y - 1, cloudCol); 258 drawMiniSnow(x, y + 1, snowCol, (code == 75 || code == 86)); 259 } 260 else { 261 drawMiniCloud(x, y, cloudCol); 262 } 263} 264 265void drawWeatherIcon(int x, int y, int code) { 266 uint16_t sunCol = TFT_YELLOW; 267 uint16_t cloudCol = 0x9E7F; 268 uint16_t rainCol = TFT_CYAN; 269 uint16_t snowCol = TFT_WHITE; 270 271 if (code == 0) { 272 drawSun(x, y, sunCol); 273 } 274 else if (code == 1) { 275 drawSun(x, y, sunCol); 276 drawCloud(x + 10, y + 4, cloudCol, 0); 277 } 278 else if (code == 2) { 279 drawSun(x + 6, y - 5, sunCol); 280 drawCloud(x, y, cloudCol); 281 } 282 else if (code == 3) { 283 drawCloud(x - 4, y - 2, 0x7BEF); 284 drawCloud(x + 4, y + 2, cloudCol); 285 } 286 // Rain / drizzle / rain showers 287 else if ((code >= 51 && code <= 67) || (code >= 80 && code <= 82)) { 288 drawCloud(x, y - 3, cloudCol); 289 drawRainDrops(x, y, rainCol, (code == 65 || code == 82)); 290 } 291 // Snow / snow grains / snow showers 292 else if ((code >= 71 && code <= 77) || (code >= 85 && code <= 86)) { 293 drawCloud(x, y - 3, cloudCol); 294 drawSnowFlakes(x, y, snowCol, (code == 75 || code == 86)); 295 } 296 else { 297 drawCloud(x, y, cloudCol); 298 } 299} 300 301int pngDrawCanvas(PNGDRAW *pDraw) { 302 uint16_t pix[256]; 303 png.getLineAsRGB565(pDraw, pix, PNG_RGB565_BIG_ENDIAN, 0); 304 mapCanvas.pushImage(globalX, globalY + pDraw->y, pDraw->iWidth, 1, pix); 305 return 1; 306} 307 308int pngDrawOverlayCanvas(PNGDRAW *pDraw) { 309 uint16_t pix[256]; 310 png.getLineAsRGB565(pDraw, pix, PNG_RGB565_BIG_ENDIAN, 0); 311 312 for (int x = 0; x < pDraw->iWidth; x++) { 313 uint16_t c = pix[x]; 314 if (c == 0) continue; 315 316 if (layerStyle == 1) { 317 if (((globalX + x + globalY + pDraw->y) & 1) != 0) continue; 318 } 319 320 mapCanvas.drawPixel(globalX + x, globalY + pDraw->y, c); 321 } 322 return 1; 323} 324 325bool fetchPngToBuffer(const String& url, uint8_t** outBuf, size_t* outLen) { 326 *outBuf = nullptr; 327 *outLen = 0; 328 329 HTTPClient http; 330 http.setTimeout(15000); 331 332 if (!http.begin(url)) { 333 Serial.printf("HTTP begin failed: %s\n", url.c_str()); 334 return false; 335 } 336 337 // VAZHNO: bara nekopresiran odgovor 338 http.addHeader("Accept-Encoding", "identity"); 339 http.addHeader("User-Agent", "ESP32-WeatherDisplay/1.0"); 340 http.useHTTP10(true); // pomaga kaj nekoi tile serveri 341 342 int code = http.GET(); 343 if (code != HTTP_CODE_OK) { 344 Serial.printf("HTTP GET failed: %d | %s\n", code, url.c_str()); 345 http.end(); 346 return false; 347 } 348 349 String ctype = http.header("Content-Type"); 350 String cenc = http.header("Content-Encoding"); 351 int len = http.getSize(); 352 353 Serial.printf("HTTP 200 | type=%s | enc=%s | len=%d\n", 354 ctype.c_str(), cenc.c_str(), len); 355 356 WiFiClient* stream = http.getStreamPtr(); 357 358 // Ako nema content-length, citaj stream rachno 359 if (len <= 0) { 360 const size_t maxChunkedSize = 100000; 361 uint8_t* buf = (uint8_t*)ps_malloc(maxChunkedSize); 362 if (!buf) { 363 Serial.println("PSRAM alloc failed (chunked)"); 364 http.end(); 365 return false; 366 } 367 368 size_t total = 0; 369 unsigned long t0 = millis(); 370 371 while (http.connected() && (millis() - t0 < 15000)) { 372 while (stream->available()) { 373 if (total >= maxChunkedSize) { 374 Serial.println("Chunked payload too large"); 375 free(buf); 376 http.end(); 377 return false; 378 } 379 buf[total++] = stream->read(); 380 t0 = millis(); 381 } 382 delay(1); 383 } 384 385 if (total < 16) { 386 Serial.printf("Payload too small: %u bytes\n", (unsigned)total); 387 free(buf); 388 http.end(); 389 return false; 390 } 391 392 *outBuf = buf; 393 *outLen = total; 394 http.end(); 395 return true; 396 } 397 398 uint8_t* buf = (uint8_t*)ps_malloc(len); 399 if (!buf) { 400 Serial.printf("PSRAM alloc failed: %d bytes\n", len); 401 http.end(); 402 return false; 403 } 404 405 int actuallyRead = stream->readBytes(buf, len); 406 http.end(); 407 408 if (actuallyRead != len || len < 16) { 409 Serial.printf("Read failed: expected %d got %d\n", len, actuallyRead); 410 free(buf); 411 return false; 412 } 413 414 *outBuf = buf; 415 *outLen = len; 416 return true; 417} 418 419void drawSignature() { 420 if (appState != 0) return; 421 422 int rssi = WiFi.RSSI(); 423 int rx = 35, ry = 4, rw = 80, rh = 22; 424 lcd.fillRect(rx, ry, rw, rh, panelColor); 425 lcd.drawRect(rx, ry, rw, rh, TFT_WHITE); 426 lcd.setTextColor(TFT_WHITE); 427 lcd.setTextSize(1); 428 lcd.setTextDatum(middle_left); 429 char rStr[15]; 430 sprintf(rStr, "%d dBm", rssi); 431 lcd.drawString(rStr, rx + 5, ry + 11); 432 433 int bars = 1; 434 if (rssi > -50) bars = 4; 435 else if (rssi > -60) bars = 3; 436 else if (rssi > -70) bars = 2; 437 438 for (int i = 0; i < 4; i++) { 439 uint16_t bCol = (i < bars) ? TFT_GREEN : TFT_DARKGREY; 440 lcd.fillRect(rx + 55 + (i * 5), ry + rh - 5 - (i * 3), 3, 3 + (i * 3), bCol); 441 } 442 443 int sx = rx + rw + 5; 444 lcd.fillRect(sx, ry, 100, rh, panelColor); 445 lcd.drawRect(sx, ry, 100, rh, TFT_WHITE); 446 lcd.setTextDatum(middle_center); 447 lcd.drawString("by mircemk", sx + 50, ry + 11); 448 449 lcd.fillRect(35, 388, 80, 22, panelColor); 450 lcd.drawRect(35, 388, 80, 22, TFT_WHITE); 451 lcd.drawString("OHRID", 35 + 40, 388 + 11); 452} 453 454void setBrightnessFromTouchY(int ty) { 455 int bx = 4, by = 4, bh = 409; 456 457 if (ty < by) ty = by; 458 if (ty > by + bh) ty = by + bh; 459 460 // gore = poslabo, dolu = pojako 461 brightnessLevel = map(ty, by, by + bh, 255, 20); 462 463 if (brightnessLevel < 20) brightnessLevel = 20; 464 if (brightnessLevel > 255) brightnessLevel = 255; 465 466 lcd.setBrightness(brightnessLevel); 467} 468 469void drawProgressTimer() { 470 if (appState != 0) return; 471 472 int bx = 4, by = 4, bw = 24, bh = 409; 473 474 lcd.drawRect(bx, by, bw, bh, TFT_WHITE); 475 lcd.drawRect(bx + 2, by + 2, bw - 4, bh - 4, TFT_WHITE); 476 lcd.fillRect(bx + 3, by + 3, bw - 6, bh - 6, TFT_BLACK); 477 478 // Progress do sledezen update 479 float p = (float)(millis() - lastUpdate) / 600000.0; 480 if (p > 1.0) p = 1.0; 481 int cH = (int)(397 * p); 482 483 for (int y = 0; y < cH; y += 4) { 484 lcd.drawFastHLine(bx + 7, (by + bh - 7) - y, bw - 14, TFT_SKYBLUE); 485 } 486 487 // Brightness marker 488 int markerY = map(brightnessLevel, 255, 20, by, by + bh); 489 lcd.drawFastHLine(bx + 4, markerY, bw - 8, TFT_YELLOW); 490 lcd.drawFastHLine(bx + 4, markerY - 1, bw - 8, TFT_YELLOW); 491} 492 493void drawBottomDashboard() { 494 struct tm timeinfo; 495 getLocalTime(&timeinfo); 496 497 int midW = 180; 498 int midH = 95; 499 int midX = (800 - midW) / 2; 500 int midY = 480 - midH; 501 502 lcd.fillRect(midX, midY, midW, midH, panelColor); 503 lcd.drawRect(midX, midY, midW, midH, TFT_WHITE); 504 505 char clockStr[10]; 506 sprintf(clockStr, "%02d:%02d", timeinfo.tm_hour, timeinfo.tm_min); 507 lcd.setTextColor(TFT_WHITE); 508 lcd.setTextSize(5); 509 lcd.setTextDatum(top_center); 510 lcd.drawString(clockStr, midX + (midW / 2), midY + 15); 511 512 lcd.setTextColor(TFT_YELLOW); 513 lcd.setTextSize(2); 514 lcd.setTextDatum(middle_center); 515 char tempStr[15]; 516 sprintf(tempStr, "Temp: %d C", (int)currentTemp); 517 lcd.drawString(tempStr, midX + (midW / 2), midY + 74); 518 519 int sideH = 65; 520 lcd.fillRect(0, 480 - sideH, midX, sideH, panelColor); 521 lcd.drawRect(0, 480 - sideH, midX, sideH, TFT_DARKGREY); 522 int partW = midX / 3; 523 524 const char* lblL[] = {"Morning", "Noon", "Evening"}; 525 float tmpL[] = {morningTemp, noonTemp, eveningTemp}; 526 int codL[] = {morningCode, noonCode, eveningCode}; 527 528 for (int i = 0; i < 3; i++) { 529 int cx = (partW * i) + (partW / 2); 530 if (i > 0) lcd.drawFastVLine(partW * i, 480 - sideH, sideH, TFT_DARKGREY); 531 lcd.setTextSize(1); 532 lcd.setTextColor(TFT_LIGHTGREY); 533 lcd.setTextDatum(top_center); 534 lcd.drawString(lblL[i], cx, 480 - 57); 535 drawWeatherIcon(partW * i + 25, 480 - 28, codL[i]); 536 lcd.setTextColor(TFT_WHITE); 537 lcd.setTextSize(3); 538 lcd.setTextDatum(middle_left); 539 lcd.drawNumber((int)tmpL[i], partW * i + 59, 480 - 28); 540 } 541 542 int rx = midX + midW; 543 int rw = 800 - rx; 544 lcd.fillRect(rx, 480 - sideH, rw, sideH, panelColor); 545 lcd.drawRect(rx, 480 - sideH, rw, sideH, TFT_DARKGREY); 546 int partRW = rw / 3; 547 548 for (int i = 1; i < 4; i++) { 549 int dayIdx = (timeinfo.tm_wday + i) % 7; 550 int curX = rx + (partRW * (i - 1)); 551 int cx = curX + (partRW / 2); 552 if (i > 1) lcd.drawFastVLine(curX, 480 - sideH, sideH, TFT_DARKGREY); 553 lcd.setTextSize(1); 554 lcd.setTextColor(TFT_LIGHTGREY); 555 lcd.setTextDatum(top_center); 556 lcd.drawString(days[dayIdx], cx, 480 - 57); 557 drawWeatherIcon(curX + 25, 480 - 28, dCode[i]); 558 lcd.setTextColor(TFT_WHITE); 559 lcd.setTextSize(2); 560 lcd.setTextDatum(top_left); 561 lcd.drawNumber((int)dMax[i], curX + 59, 480 - 42); 562 lcd.setTextColor(0x7BEF); 563 lcd.drawNumber((int)dMin[i], curX + 59, 480 - 22); 564 } 565} 566 567void getWeatherData() { 568 HTTPClient http; 569 http.begin(weatherUrl); 570 571 if (http.GET() == 200) { 572 DynamicJsonDocument doc(32768); 573 deserializeJson(doc, http.getString()); 574 575 currentTemp = doc["current"]["temperature_2m"]; 576 morningTemp = doc["hourly"]["temperature_2m"][9]; 577 morningCode = doc["hourly"]["weather_code"][9]; 578 noonTemp = doc["hourly"]["temperature_2m"][14]; 579 noonCode = doc["hourly"]["weather_code"][14]; 580 eveningTemp = doc["hourly"]["temperature_2m"][21]; 581 eveningCode = doc["hourly"]["weather_code"][21]; 582 583 for (int i = 0; i < 16; i++) { 584 dMax[i] = doc["daily"]["temperature_2m_max"][i]; 585 dMin[i] = doc["daily"]["temperature_2m_min"][i]; 586 dCode[i] = doc["daily"]["weather_code"][i]; 587 dRain[i] = doc["daily"]["precipitation_sum"][i]; 588 dPress[i] = doc["hourly"]["pressure_msl"][i * 24 + 12]; 589 dCloud[i] = doc["hourly"]["cloud_cover"][i * 24 + 12]; 590 591 dHum[i] = doc["daily"]["relative_humidity_2m_mean"][i]; 592 dWind[i] = doc["daily"]["wind_speed_10m_max"][i]; 593 dUV[i] = doc["daily"]["uv_index_max"][i]; 594 dSolar[i] = doc["daily"]["shortwave_radiation_sum"][i]; 595 } 596 } 597 http.end(); 598 599http.begin("https://api.rainviewer.com/public/weather-maps.json"); 600if (http.GET() == 200) { 601 DynamicJsonDocument rDoc(8192); 602 deserializeJson(rDoc, http.getString()); 603 604 radarHost = rDoc["host"] | "https://tilecache.rainviewer.com"; 605 radarPath = ""; 606 607 JsonArray past = rDoc["radar"]["past"].as<JsonArray>(); 608 609 if (!past.isNull() && past.size() > 0) { 610 radarTS = past[past.size() - 1]["time"] | 0; 611 radarPath = (const char*)past[past.size() - 1]["path"]; 612 Serial.printf("RainViewer frame time=%ld\n", radarTS); 613 Serial.printf("RainViewer path=%s\n", radarPath.c_str()); 614 } else { 615 Serial.println("RainViewer: no past radar frames available"); 616 } 617} else { 618 Serial.println("RainViewer API request failed"); 619} 620http.end(); 621} 622 623int getDayOfMonthOffset(struct tm baseTime, int offsetDays) { 624 time_t raw = mktime(&baseTime); 625 raw += (offsetDays * 86400); 626 struct tm *t2 = localtime(&raw); 627 return t2->tm_mday; 628} 629 630void drawGraphPage(int type) { 631 struct tm ti; 632 getLocalTime(&ti); 633 634 lcd.fillScreen(TFT_BLACK); 635 lcd.drawRect(0, 0, 800, 480, TFT_WHITE); 636 637 lcd.fillRect(10, 415, 110, 55, TFT_DARKGREY); 638 lcd.drawRect(10, 415, 110, 55, TFT_WHITE); 639 lcd.setTextColor(TFT_WHITE); 640 lcd.setTextSize(2); 641 lcd.setTextDatum(middle_center); 642 lcd.drawString("BACK", 65, 442); 643 644 // Units label 645const char* unitTxt = ""; 646 647if (type == 1) unitTxt = "Unit: C"; 648else if (type == 2) unitTxt = "Unit: hPa"; 649else if (type == 3) unitTxt = "Unit: mm"; 650else if (type == 4) unitTxt = "Unit: %"; 651else if (type == 5) unitTxt = "Unit: %"; 652else if (type == 6) unitTxt = "Unit: km/h"; 653else if (type == 7) unitTxt = "Unit: index"; 654else if (type == 8) unitTxt = "Unit: MJ/m2"; 655 656lcd.setTextDatum(middle_center); 657lcd.setTextSize(3); 658lcd.setTextColor(TFT_LIGHTGREY); 659lcd.drawString(unitTxt, 370, 448 ); 660 661 int sX = 75, eX = 770, sY = 380, eY = 85, dW = 43; 662 float minV, maxV; 663 664 // Titles 665 lcd.setTextColor(TFT_WHITE); 666 lcd.setTextSize(2); 667 lcd.setTextDatum(top_center); 668 669 if (type == 1) lcd.drawString("16-Day Temperature Forecast", 400, 8); 670 else if (type == 2) lcd.drawString("16-Day Pressure Forecast", 400, 8); 671 else if (type == 3) lcd.drawString("16-Day Rain Forecast", 400, 8); 672 else if (type == 4) lcd.drawString("16-Day Cloud Cover Forecast", 400, 8); 673 else if (type == 5) lcd.drawString("16-Day Humidity Forecast", 400, 8); 674 else if (type == 6) lcd.drawString("16-Day Wind Speed Forecast", 400, 8); 675 else if (type == 7) lcd.drawString("16-Day UV Index Forecast", 400, 8); 676 else if (type == 8) lcd.drawString("16-Day Shortwave Radiation Forecast", 400, 8); 677 678 // Range setup 679 if (type == 1) { 680 minV = 100; 681 maxV = -100; 682 for (int i = 0; i < 16; i++) { 683 if (dMax[i] > maxV) maxV = dMax[i]; 684 if (dMin[i] < minV) minV = dMin[i]; 685 } 686 minV = floor(minV / 5) * 5; 687 maxV = ceil(maxV / 5) * 5; 688 } 689 else if (type == 2) { 690 minV = 2000; 691 maxV = 0; 692 for (int i = 0; i < 16; i++) { 693 if (dPress[i] > maxV) maxV = dPress[i]; 694 if (dPress[i] < minV) minV = dPress[i]; 695 } 696 minV = floor(minV / 2) * 2 - 2; 697 maxV = ceil(maxV / 2) * 2 + 2; 698 } 699 else if (type == 3) { 700 minV = 0; 701 maxV = 0; 702 for (int i = 0; i < 16; i++) { 703 if (dRain[i] > maxV) maxV = dRain[i]; 704 } 705 if (maxV < 5) maxV = 5; 706 else maxV = ceil(maxV / 5) * 5; 707 } 708 else if (type == 4) { 709 minV = 0; 710 maxV = 100; 711 } 712 else if (type == 5) { 713 minV = 0; 714 maxV = 100; 715 } 716 else if (type == 6) { 717 minV = 0; 718 maxV = 0; 719 for (int i = 0; i < 16; i++) { 720 if (dWind[i] > maxV) maxV = dWind[i]; 721 } 722 maxV = ceil(maxV / 5) * 5; 723 if (maxV < 10) maxV = 10; 724 } 725 else if (type == 7) { 726 minV = 0; 727 maxV = 0; 728 for (int i = 0; i < 16; i++) { 729 if (dUV[i] > maxV) maxV = dUV[i]; 730 } 731 maxV = ceil(maxV); 732 if (maxV < 5) maxV = 5; 733 } 734 else { // type == 8 735 minV = 0; 736 maxV = 0; 737 for (int i = 0; i < 16; i++) { 738 if (dSolar[i] > maxV) maxV = dSolar[i]; 739 } 740 maxV = ceil(maxV / 5) * 5; 741 if (maxV < 5) maxV = 5; 742 } 743 744 // Cloud shaded area only for C 745 if (type == 4) { 746 for (int i = 0; i < 15; i++) { 747 int x1 = sX + (i * dW) + (dW / 2); 748 float v1 = dCloud[i], v2 = dCloud[i + 1]; 749 for (int px = 0; px < dW; px++) { 750 float interVal = v1 + (v2 - v1) * (float)px / dW; 751 int interY = map(interVal, minV, maxV, sY, eY); 752 lcd.drawFastVLine(x1 + px, interY, sY - interY, lcd.color565(35, 35, 35)); 753 } 754 } 755 } 756 757 // Weekend backgrounds 758 for (int i = 0; i < 16; i++) { 759 int wD = (ti.tm_wday + i) % 7; 760 if (wD == 6 || wD == 0) { 761 uint16_t c = (wD == 6) ? lcd.color565(20, 20, 35) : lcd.color565(35, 20, 20); 762 lcd.fillRect(sX + (i * dW), eY, dW, sY - eY, c); 763 } 764 } 765 766 // Left scale 767 lcd.setTextSize(2); 768 lcd.setTextDatum(middle_right); 769 770 float step = 5; 771 if (type == 2) step = 2; 772 else if (type == 3) step = maxV / 5; 773 else if (type == 4) step = 20; 774 else if (type == 5) step = 20; 775 else if (type == 6) step = maxV / 5; 776 else if (type == 7) step = 1; 777 else if (type == 8) step = maxV / 5; 778 779 if (step < 1) step = 1; 780 781 for (float v = minV; v <= maxV; v += step) { 782 int yP = map(v, minV, maxV, sY, eY); 783 lcd.drawFastHLine(sX, yP, eX - sX, lcd.color565(60, 60, 60)); 784 lcd.drawNumber((int)v, sX - 12, yP); 785 } 786 787 // Main plot 788 for (int i = 0; i < 16; i++) { 789 int x1 = sX + (i * dW) + (dW / 2); 790 int wD = (ti.tm_wday + i) % 7; 791 int realDate = getDayOfMonthOffset(ti, i); 792 793 // Day label 794 lcd.setTextDatum(top_center); 795 lcd.setTextSize(2); 796 lcd.setTextColor(TFT_LIGHTGREY); 797 lcd.drawString(dayShort2[wD], x1, eY - 46); 798 799 // Mini icon only on T 800 if (type == 1) { 801 drawMiniWeatherIcon(x1, eY - 17, dCode[i]); 802 } 803 804 // Bottom date 805 lcd.setTextDatum(top_center); 806 lcd.setTextSize(2); 807 lcd.setTextColor(TFT_WHITE); 808 lcd.drawNumber(realDate, x1, sY + 8); 809 810 // BAR charts: Rain + Solar 811 if (type == 3 || type == 8) { 812 float barVal = (type == 3) ? dRain[i] : dSolar[i]; 813 uint16_t barCol = (type == 3) ? TFT_BLUE : TFT_YELLOW; 814 815 int barH = map(barVal, 0, maxV, 0, sY - eY); 816 lcd.fillRect(x1 - 15, sY - barH, 30, barH, barCol); 817 lcd.drawRect(x1 - 15, sY - barH, 30, barH, TFT_WHITE); 818 819 if (barVal > 0) { 820 lcd.setTextSize(2); 821 lcd.setTextDatum(middle_center); 822 lcd.setTextColor(TFT_WHITE); 823 lcd.drawString(String(barVal, 1), x1, sY - barH - 16); 824 } 825 } 826 else { 827 float val1 = 0; 828 uint16_t col = TFT_WHITE; 829 830 if (type == 1) { 831 val1 = dMax[i]; 832 col = TFT_RED; 833 } else if (type == 2) { 834 val1 = dPress[i]; 835 col = TFT_YELLOW; 836 } else if (type == 4) { 837 val1 = dCloud[i]; 838 col = TFT_WHITE; 839 } else if (type == 5) { 840 val1 = dHum[i]; 841 col = TFT_BLUE; 842 } else if (type == 6) { 843 val1 = dWind[i]; 844 col = lcd.color565(255, 140, 0); 845 } else if (type == 7) { 846 val1 = dUV[i]; 847 col = TFT_MAGENTA; 848 } 849 850 int y1 = map(val1, minV, maxV, sY, eY); 851 852 if (i < 15) { 853 float val2 = 0; 854 855 if (type == 1) val2 = dMax[i + 1]; 856 else if (type == 2) val2 = dPress[i + 1]; 857 else if (type == 4) val2 = dCloud[i + 1]; 858 else if (type == 5) val2 = dHum[i + 1]; 859 else if (type == 6) val2 = dWind[i + 1]; 860 else if (type == 7) val2 = dUV[i + 1]; 861 862 int x2 = sX + ((i + 1) * dW) + (dW / 2); 863 int y2 = map(val2, minV, maxV, sY, eY); 864 lcd.drawLine(x1, y1, x2, y2, col); 865 866 if (type == 1) { 867 int yMin1 = map(dMin[i], minV, maxV, sY, eY); 868 int yMin2 = map(dMin[i + 1], minV, maxV, sY, eY); 869 lcd.drawLine(x1, yMin1, x2, yMin2, TFT_BLUE); 870 } 871 } 872 873 lcd.fillCircle(x1, y1, 4, col); 874 lcd.drawCircle(x1, y1, 4, TFT_WHITE); 875 876 if (type == 1) { 877 int yMin = map(dMin[i], minV, maxV, sY, eY); 878 lcd.fillCircle(x1, yMin, 4, TFT_BLUE); 879 lcd.drawCircle(x1, yMin, 4, TFT_WHITE); 880 } 881 } 882 } 883} 884 885void drawLocationMarker() { 886 double n = pow(2.0, myZoom); 887 888 // Global pixel coordinates at current zoom 889 double worldX = ((myLon + 180.0) / 360.0) * n * 256.0; 890 double latRad = myLat * M_PI / 180.0; 891 double worldY = (1.0 - log(tan(latRad) + 1.0 / cos(latRad)) / M_PI) / 2.0 * n * 256.0; 892 893 // Center tile indices (isti kako vo renderRadarMap) 894 int cTX = (int)(floor((myLon + 180.0) / 360.0 * n)); 895 int cTY = (int)(floor((1.0 - log(tan(latRad) + 1.0 / cos(latRad)) / M_PI) / 2.0 * n)); 896 897 // Top-left pixel of our composed 3x2 tile map 898 double topLeftWorldX = (cTX - 1) * 256.0; 899 double topLeftWorldY = (cTY + 0) * 256.0; 900 901 // Convert to local sprite coordinates 902 int px = (int)(worldX - topLeftWorldX) + 32; 903 int py = (int)(worldY - topLeftWorldY) - 16; 904 905 // Safety check 906 if (px < 0 || px >= 800 || py < 0 || py >= 415) return; 907 908 // Marker 909 mapCanvas.fillCircle(px, py, 5, TFT_RED); 910 mapCanvas.drawCircle(px, py, 6, TFT_WHITE); 911 mapCanvas.drawPixel(px, py, TFT_WHITE); 912} 913 914void renderRadarMap() { 915 if (appState == 0) { 916 lcd.setTextColor(TFT_WHITE, TFT_BLACK); 917 lcd.setTextSize(3); 918 lcd.setTextDatum(middle_center); 919 lcd.drawString("Loading map...", 400, 200); 920 } 921 922 mapCanvas.fillSprite(TFT_BLACK); 923 924 int cTX = (int)(floor((myLon + 180.0) / 360.0 * pow(2.0, myZoom))); 925 int cTY = (int)(floor((1.0 - log(tan(myLat * M_PI / 180.0) + 1.0 / cos(myLat * M_PI / 180.0)) / M_PI) / 2.0 * pow(2.0, myZoom))); 926 927 for (int i = -1; i < 2; i++) { 928 for (int j = 0; j < 2; j++) { 929 globalX = 32 + ((i + 1) * 256); 930 globalY = (j * 256) - 16; 931 932 String mU = String(mapUrls[mapStyle]) + String(myZoom) + "/" + String(cTX + i) + "/" + String(cTY + j) + ".png"; 933 934 uint8_t* baseBuf = nullptr; 935 size_t baseLen = 0; 936 937 if (fetchPngToBuffer(mU, &baseBuf, &baseLen)) { 938 if (png.openRAM(baseBuf, baseLen, pngDrawCanvas) == PNG_SUCCESS) { 939 png.decode(NULL, 0); 940 png.close(); 941 } else { 942 Serial.printf("Base PNG open failed: %s\n", mU.c_str()); 943 } 944 free(baseBuf); 945 } else { 946 Serial.printf("Base fetch failed: %s\n", mU.c_str()); 947 } 948 949 String layerUrl = ""; 950 951if (layerStyle == 0) { 952 if (radarPath.length() > 0) { 953 layerUrl = radarHost + radarPath + "/256/" + 954 String(myZoom) + "/" + 955 String(cTX + i) + "/" + 956 String(cTY + j) + "/1/1_1.png"; 957 } 958} else { 959 layerUrl = "https://tile.openweathermap.org/map/" + 960 String(owmLayerIds[layerStyle]) + "/" + 961 String(myZoom) + "/" + 962 String(cTX + i) + "/" + 963 String(cTY + j) + 964 ".png?appid=" + String(owmApiKey); 965 } 966 967 if (layerUrl.length() > 0) { 968 uint8_t* overlayBuf = nullptr; 969 size_t overlayLen = 0; 970 971 Serial.printf("Layer=%s\n", layerNames[layerStyle]); 972 Serial.printf("URL=%s\n", layerUrl.c_str()); 973 Serial.printf("Heap=%u PSRAM=%u\n", ESP.getFreeHeap(), ESP.getFreePsram()); 974 975 if (fetchPngToBuffer(layerUrl, &overlayBuf, &overlayLen)) { 976 977 // PNG signature check 978 bool isPng = 979 overlayLen >= 8 && 980 overlayBuf[0] == 0x89 && 981 overlayBuf[1] == 0x50 && 982 overlayBuf[2] == 0x4E && 983 overlayBuf[3] == 0x47 && 984 overlayBuf[4] == 0x0D && 985 overlayBuf[5] == 0x0A && 986 overlayBuf[6] == 0x1A && 987 overlayBuf[7] == 0x0A; 988 989 if (!isPng) { 990 Serial.printf("Overlay is NOT PNG! len=%u\n", (unsigned)overlayLen); 991 Serial.print("First 32 bytes HEX: "); 992 for (size_t k = 0; k < 32 && k < overlayLen; k++) { 993 Serial.printf("%02X ", overlayBuf[k]); 994 } 995 Serial.println(); 996 997 Serial.print("First 120 chars TXT: "); 998 for (size_t k = 0; k < 120 && k < overlayLen; k++) { 999 char c = (char)overlayBuf[k]; 1000 if (c >= 32 && c <= 126) Serial.print(c); 1001 else Serial.print('.'); 1002 } 1003 Serial.println(); 1004 1005 free(overlayBuf); 1006 } else { 1007 int rc = png.openRAM(overlayBuf, overlayLen, pngDrawOverlayCanvas); 1008 if (rc == PNG_SUCCESS) { 1009 int decRc = png.decode(NULL, 0); 1010 if (decRc != PNG_SUCCESS) { 1011 Serial.printf("PNG decode failed rc=%d | %s\n", decRc, layerUrl.c_str()); 1012 } 1013 png.close(); 1014 } else { 1015 Serial.printf("Overlay PNG open failed rc=%d | %s\n", rc, layerUrl.c_str()); 1016 } 1017 free(overlayBuf); 1018 } 1019 1020 } else { 1021 Serial.printf("Overlay fetch failed: %s\n", layerUrl.c_str()); 1022 } 1023} 1024 1025 delay(10); 1026 } 1027 } 1028 1029 drawLocationMarker(); 1030 mapCanvas.pushSprite(0, 0); 1031 1032 int mapX = 740; 1033 int mapY = 387; 1034 int mapW = 60; 1035 int mapH = 22; 1036 1037 lcd.fillRect(mapX, mapY, mapW, mapH, panelColor); 1038 lcd.drawRect(mapX, mapY, mapW, mapH, TFT_WHITE); 1039 lcd.setTextColor(TFT_WHITE); 1040 lcd.setTextSize(1); 1041 lcd.setTextDatum(middle_center); 1042 lcd.drawString(mapNames[mapStyle], mapX + (mapW / 2), mapY + (mapH / 2)); 1043 1044 int layerX = 360; 1045 int layerY = 10; 1046 int layerW = 70; 1047 int layerH = 22; 1048 1049 lcd.fillRect(layerX, layerY, layerW, layerH, panelColor); 1050 lcd.drawRect(layerX, layerY, layerW, layerH, TFT_WHITE); 1051 lcd.setTextColor(TFT_WHITE); 1052 lcd.setTextSize(1); 1053 lcd.setTextDatum(middle_center); 1054 lcd.drawString(layerNames[layerStyle], layerX + (layerW / 2), layerY + (layerH / 2)); 1055} 1056 1057void drawTopDate() { 1058 if (appState != 0) return; 1059 struct tm ti; 1060 getLocalTime(&ti); 1061 1062 char buf[40]; 1063 sprintf(buf, "%s, %d. %s", days[ti.tm_wday], ti.tm_mday, months[ti.tm_mon]); 1064 1065 lcd.fillRect(520, 0, 280, 35, panelColor); 1066 lcd.drawRect(520, 0, 280, 35, TFT_WHITE); 1067 lcd.setTextColor(TFT_WHITE); 1068 lcd.setTextSize(2); 1069 lcd.setTextDatum(middle_center); 1070 lcd.drawString(buf, 660, 17); 1071} 1072 1073void drawSideButtons() { 1074 if (appState != 0) return; 1075 1076 int bw = 34, bh = 75; 1077 1078 // RIGHT SIDE: T P R C 1079 int bxR = 765; 1080 const char* lblR[] = {"T", "P", "R", "C"}; 1081 uint16_t colsR[] = {TFT_RED, TFT_GREEN, TFT_CYAN, TFT_WHITE}; 1082 1083 for (int i = 0; i < 4; i++) { 1084 lcd.fillRect(bxR, 50 + (i * 85), bw, bh, panelColor); 1085 lcd.drawRect(bxR, 50 + (i * 85), bw, bh, TFT_WHITE); 1086 lcd.setTextColor(colsR[i]); 1087 lcd.setTextSize(3); 1088 lcd.setTextDatum(middle_center); 1089 lcd.drawString(lblR[i], bxR + 17, 50 + (i * 85) + 37); 1090 } 1091 1092 // LEFT SIDE: H W U S 1093 int bxL = 32; 1094 const char* lblL[] = {"H", "W", "U", "S"}; 1095 uint16_t colsL[] = { 1096 TFT_BLUE, 1097 lcd.color565(255, 140, 0), 1098 TFT_MAGENTA, 1099 TFT_YELLOW 1100 }; 1101 1102 for (int i = 0; i < 4; i++) { 1103 lcd.fillRect(bxL, 50 + (i * 85), bw, bh, panelColor); 1104 lcd.drawRect(bxL, 50 + (i * 85), bw, bh, TFT_WHITE); 1105 lcd.setTextColor(colsL[i]); 1106 lcd.setTextSize(3); 1107 lcd.setTextDatum(middle_center); 1108 lcd.drawString(lblL[i], bxL + 17, 50 + (i * 85) + 37); 1109 } 1110} 1111 1112void setup() { 1113 Serial.begin(115200); 1114 1115 Wire.begin(19, 20); 1116 Wire.beginTransmission(0x18); 1117 Wire.write(0x01); 1118 Wire.write(0x3B); 1119 Wire.endTransmission(); 1120 1121 touch_init(); 1122 lcd.init(); 1123 lcd.setBrightness(brightnessLevel); 1124 1125 lcd.fillScreen(TFT_BLACK); 1126 lcd.setTextColor(TFT_WHITE); 1127 lcd.setTextSize(3); 1128 lcd.setTextDatum(middle_center); 1129 lcd.drawString("Connecting to internet...", 400, 240); 1130 1131 WiFi.begin(ssid, password); 1132 unsigned long sw = millis(); 1133 while (WiFi.status() != WL_CONNECTED && millis() - sw < 15000) delay(500); 1134 1135 lcd.fillScreen(TFT_BLACK); 1136 if (WiFi.status() == WL_CONNECTED) { 1137 lcd.setTextColor(TFT_GREEN); 1138 lcd.drawString("Connected!", 400, 240); 1139 configTime(3600, 0, ntpServer); 1140 } else { 1141 lcd.setTextColor(TFT_RED); 1142 lcd.drawString("No Internet!", 400, 240); 1143 } 1144 1145 delay(1500); 1146 lcd.fillScreen(TFT_BLACK); 1147 1148 mapCanvas.setPsram(true); 1149 if (!mapCanvas.createSprite(800, 415)) Serial.println("PSRAM Sprite failed!"); 1150 1151 lastUpdate = millis(); 1152 getWeatherData(); 1153 renderRadarMap(); 1154 drawTopDate(); 1155 drawBottomDashboard(); 1156 drawSideButtons(); 1157 drawSignature(); 1158} 1159 1160void loop() { 1161 static unsigned long lT = 0, lTouch = 0; 1162 struct tm ti; 1163 1164 if (appState == 0 && getLocalTime(&ti)) { 1165 if (ti.tm_min != lastMinute) { 1166 lastMinute = ti.tm_min; 1167 drawBottomDashboard(); 1168 drawSignature(); 1169 } 1170 } 1171 1172 if (appState == 0 && millis() - lT > 5000) { 1173 drawProgressTimer(); 1174 lT = millis(); 1175 } 1176 1177 if (touch_has_signal() && touch_touched()) { 1178 int tx = touch_last_x, ty = touch_last_y; 1179 1180 // Brightness slider on left bar 1181 if (appState == 0 && tx >= 0 && tx <= 32 && ty >= 4 && ty <= 413) { 1182 setBrightnessFromTouchY(ty); 1183 drawProgressTimer(); 1184 delay(25); 1185 return; 1186 } 1187 1188 if (millis() - lTouch > 600) { 1189 if (appState == 0) { 1190 if (ty > 0 && ty < 40 && tx > 300 && tx < 500) { 1191 layerStyle = (layerStyle + 1) % 3; 1192 1193 lcd.fillRect(250, 200, 300, 80, TFT_BLACK); 1194 lcd.drawRect(250, 200, 300, 80, TFT_WHITE); 1195 lcd.setTextColor(TFT_YELLOW); 1196 lcd.setTextSize(3); 1197 lcd.setTextDatum(middle_center); 1198 lcd.drawString("Layer: " + String(layerNames[layerStyle]), 400, 240); 1199 delay(800); 1200 1201 renderRadarMap(); 1202 drawTopDate(); 1203 drawBottomDashboard(); 1204 drawSideButtons(); 1205 drawSignature(); 1206 drawProgressTimer(); 1207 } 1208 else if (tx > 310 && tx < 490 && ty > 385 && ty < 480) { 1209 mapStyle = (mapStyle + 1) % 3; 1210 1211 lcd.fillRect(250, 200, 300, 80, TFT_BLACK); 1212 lcd.drawRect(250, 200, 300, 80, TFT_WHITE); 1213 lcd.setTextColor(TFT_YELLOW); 1214 lcd.setTextSize(3); 1215 lcd.setTextDatum(middle_center); 1216 lcd.drawString("Map: " + String(mapNames[mapStyle]), 400, 240); 1217 delay(800); 1218 1219 renderRadarMap(); 1220 drawTopDate(); 1221 drawBottomDashboard(); 1222 drawSideButtons(); 1223 drawSignature(); 1224 } 1225else if (tx > 760) { 1226 if (ty > 50 && ty < 125) appState = 1, drawGraphPage(1); 1227 else if (ty > 135 && ty < 210) appState = 2, drawGraphPage(2); 1228 else if (ty > 220 && ty < 295) appState = 3, drawGraphPage(3); 1229 else if (ty > 305 && ty < 380) appState = 4, drawGraphPage(4); 1230} 1231else if (tx > 32 && tx < 66) { 1232 if (ty > 50 && ty < 125) appState = 5, drawGraphPage(5); 1233 else if (ty > 135 && ty < 210) appState = 6, drawGraphPage(6); 1234 else if (ty > 220 && ty < 295) appState = 7, drawGraphPage(7); 1235 else if (ty > 305 && ty < 380) appState = 8, drawGraphPage(8); 1236} 1237 else if (tx > 250 && tx < 550 && ty > 100 && ty < 380) { 1238 myZoom++; 1239 if (myZoom > 7) myZoom = 5; 1240 1241 lcd.setTextColor(TFT_YELLOW, TFT_BLACK); 1242 lcd.setTextSize(6); 1243 lcd.setTextDatum(middle_center); 1244 lcd.drawString("ZOOM: " + String(myZoom), 400, 240); 1245 delay(800); 1246 1247 renderRadarMap(); 1248 drawTopDate(); 1249 drawBottomDashboard(); 1250 drawSideButtons(); 1251 drawSignature(); 1252 } 1253 } else if (tx < 150 && ty > 400) { 1254 appState = 0; 1255 lcd.fillScreen(TFT_BLACK); 1256 1257 if (mapCanvas.getBuffer() != nullptr) { 1258 mapCanvas.pushSprite(0, 0); 1259 } else { 1260 renderRadarMap(); 1261 } 1262 1263 drawBottomDashboard(); 1264 drawSideButtons(); 1265 drawSignature(); 1266 drawTopDate(); 1267 1268 int mapX = 740; 1269 int mapY = 387; 1270 int mapW = 60; 1271 int mapH = 22; 1272 1273 lcd.fillRect(mapX, mapY, mapW, mapH, panelColor); 1274 lcd.drawRect(mapX, mapY, mapW, mapH, TFT_WHITE); 1275 lcd.setTextColor(TFT_WHITE); 1276 lcd.setTextSize(1); 1277 lcd.setTextDatum(middle_center); 1278 lcd.drawString(mapNames[mapStyle], mapX + (mapW / 2), mapY + (mapH / 2)); 1279 1280 int layerX = 360; 1281 int layerY = 10; 1282 int layerW = 70; 1283 int layerH = 22; 1284 1285 lcd.fillRect(layerX, layerY, layerW, layerH, panelColor); 1286 lcd.drawRect(layerX, layerY, layerW, layerH, TFT_WHITE); 1287 lcd.setTextColor(TFT_WHITE); 1288 lcd.setTextSize(1); 1289 lcd.setTextDatum(middle_center); 1290 lcd.drawString(layerNames[layerStyle], layerX + (layerW / 2), layerY + (layerH / 2)); 1291 } 1292 1293 lTouch = millis(); 1294 } 1295 } 1296 1297 if (appState == 0 && millis() - lastUpdate > 600000) { 1298 lastUpdate = millis(); 1299 getWeatherData(); 1300 renderRadarMap(); 1301 drawTopDate(); 1302 drawBottomDashboard(); 1303 drawSideButtons(); 1304 drawSignature(); 1305 } 1306}
Downloadable files
Code_FINAL
...
Code_FINAL.zip
Comments
Only logged in users can leave comments