Arduino UNO R4 - Smart Weather Station

In this guide, we will learn how to build a smart weather station with the Arduino UNO R4 WiFi and a 1.28 inch round TFT LCD display. The station gets the live weather from the Internet. It does not need any temperature or humidity sensor. In detail, we will learn:

Arduino UNO R4 smart weather station with round TFT LCD display

Hardware Preparation

1×Arduino UNO R4 WiFi
1×Alternatively, DIYables STEM V4 IoT
1×Alternatively, DIYables STEM V4B IoT
1×USB Cable Type-A to Type-C (for USB-A PC)
1×USB Cable Type-C to Type-C (for USB-C PC)
1×1.28 Inch Round Circular TFT LCD Display Module
1×Push Button
1×Button Module Alternatively,
1×Breadboard
1×Jumper Wires

You need only one of the two button parts. See the wiring section for both options.

1×Recommended: Screw Terminal Block Shield for Arduino UNO R4
1×Recommended: Sensors/Servo Expansion Shield for Arduino UNO R4
1×Recommended: Breadboard Shield for Arduino UNO R4
1×Recommended: Enclosure for Arduino UNO R4
1×Recommended: Power Splitter for Arduino UNO R4
1×Recommended: Prototyping Base Plate & Breadboard Kit for Arduino UNO

Or you can buy the following kits:

1×DIYables STEM V4 IoT Starter Kit (Arduino included)
1×DIYables STEM V4B IoT Starter Kit (Arduino included)
1×DIYables Sensor Kit (18 sensors/displays)
Disclosure: Some of the links provided in this section are Amazon affiliate links. We may receive a commission for any purchases made through these links at no additional cost to you.
Additionally, some of these links are for products from our own brand, DIYables .
Arduino Uno R4 WiFi Compatible Boards

※ NOTE THAT:

This project needs the Arduino UNO R4 WiFi. The Arduino UNO R4 Minima has no WiFi, so it cannot reach the weather API.

Overview of the Smart Weather Station

A normal weather station uses a sensor to measure the air around it. This one works in another way. The Arduino UNO R4 asks a weather service on the Internet, and the service sends back the weather for your city. Because of that, the station can also show things that a small sensor cannot measure, such as the rain chance for the next hours and the forecast for the next days.

The station has eight pages. One push button moves between them:

  • HOME - the weather icon, the temperature, the sky description, and a clock.
  • TEMP - the temperature and the "feels like" temperature.
  • HUMIDITY - the humidity as a ring and as a number.
  • WIND - the wind speed and the wind direction as a compass.
  • RAIN - the rain chance with a small rain animation.
  • NEXT HOURS - the temperature and rain chance for the next hours.
  • FORECAST - the highest and lowest temperature for the next 3 days.
  • SYSTEM - the WiFi state, the signal level, and the API state.

The Arduino UNO R4 asks for new weather every 10 minutes. Between two updates, the clock on the home page keeps running.

Overview of the Open-Meteo Weather API

Open-Meteo is a free weather service. It is a good fit for a small board like the Arduino UNO R4:

  • No API key: you do not need to sign up or paste a key into the code.
  • Free for personal use: no cost for a small project like this one.
  • Plain JSON: the answer is a short JSON text that the Arduino UNO R4 can read.
  • Any place: you choose the place with a latitude and a longitude.

The Arduino UNO R4 sends one HTTPS request and gets back one answer of about 1.8 KB. The answer holds the weather now, the next 12 hours, and the next 3 days.

※ NOTE THAT:

The answer also carries the local time of the place you asked for. The code takes the time from there, so the station shows a clock without an NTP client and without an RTC module.

Wiring Diagram

The 1.28 inch round TFT LCD display uses the hardware SPI pins of the Arduino UNO R4. These pins are fixed: D13 is the clock and D11 is the data line.

TFT LCD Pin Arduino UNO R4 Description
VCC 5V Power supply
GND GND Ground
SCL D13 SPI Clock (fixed pin)
SDA D11 SPI MOSI (fixed pin)
DC D9 Data/Command
CS D10 Chip Select
RST D8 Reset

For the button you can pick one of two parts. Both work the same way in the project. Only one line of code is different.

Wiring with a Push Button

A push button has two pins and no resistor. It connects D2 to GND when you press it. The Arduino UNO R4 turns on its own internal pull-up resistor, so you do not need to add any resistor.

The wiring diagram between Arduino UNO R4 smart weather station  with push button

This image is created using Fritzing. Click to enlarge image

Button Pin Arduino UNO R4 Description
Pin 1 D2 Button input with internal pull-up
Pin 2 GND Ground

This is what the code uses by default:

ezButton button(PIN_BUTTON);

Wiring with a Button Module

A button module has three pins and its own resistor on the board. It needs power, and its OUT pin goes to D2. The module gives LOW when you do not press, and HIGH when you press.

The wiring diagram between Arduino UNO R4 smart weather station  with button module

This image is created using Fritzing. Click to enlarge image

See The best way to supply power to the Arduino Uno R4 and other components.

Button Module Pin Arduino UNO R4 Description
VCC 5V Power supply
GND GND Ground
OUT D2 Button signal, LOW when free, HIGH when pressed

The module already has a resistor, so the internal one must not be used. Change one line in the code:

ezButton button(PIN_BUTTON, EXTERNAL_PULLDOWN);

How To Program For the Smart Weather Station

Include the five libraries.

#include <WiFiS3.h> #include <ArduinoHttpClient.h> #include <ArduinoJson.h> #include <DIYables_TFT_Round.h> #include <ezButton.h>

Create the display object with the reset, data/command, and chip select pins.

DIYables_TFT_GC9A01_Round tft(8, 9, 10);

Set your WiFi name, your WiFi password, and your place.

#define WIFI_SSID "YOUR_WIFI_SSID" #define WIFI_PASSWORD "YOUR_WIFI_PASSWORD" #define LATITUDE 37.5665 #define LONGITUDE 126.9780

Start the display and the WiFi.

tft.begin(); tft.setRotation(2); WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

Open an HTTPS connection to the weather server. On the Arduino UNO R4, WiFiSSLClient does the HTTPS part. The root certificates are already inside the WiFi firmware, so you do not need to paste a certificate into the code.

WiFiSSLClient sslClient; HttpClient http(sslClient, "api.open-meteo.com", 443);

Send the request and read the answer.

http.get(path); int statusCode = http.responseStatusCode(); String payload = http.responseBody();

Read the JSON answer. The Arduino UNO R4 has only 32 KB of RAM, so a filter is used. The filter keeps the three blocks that the code needs and drops the rest.

JsonDocument filter; filter["current"] = true; filter["hourly"] = true; filter["daily"] = true; JsonDocument doc; deserializeJson(doc, payload, DeserializationOption::Filter(filter));

Take one value out of the answer.

float temperature = doc["current"]["temperature_2m"] | 0.0;

Draw it on the round display.

tft.setTextSize(3); tft.setTextColor(DIYables_TFT::colorRGB(255, 255, 255)); tft.setCursor(80, 100); tft.print(temperature, 1);

Arduino UNO R4 Code - Smart Weather Station

/* * This Arduino UNO R4 code was developed by newbiely.com * * This Arduino UNO R4 code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/arduino-uno-r4/arduino-uno-r4-smart-weather-station */ /* ============================================================ SMART WEATHER STATION Arduino UNO R4 WiFi + GC9A01 Round TFT + Push Button Location: Seoul, South Korea (change it in USER CONFIGURATION) Weather API: Open-Meteo (free, no API key needed) ------------------------------------------------------------ GC9A01 Round TFT -> Arduino UNO R4 VCC -> 5V GND -> GND SCL -> D13 (hardware SPI clock, fixed) SDA -> D11 (hardware SPI MOSI, fixed) RST -> D8 DC -> D9 CS -> D10 Button D2 -> BUTTON -> GND ------------------------------------------------------------ BUTTON Short press -> Next page Long press -> Refresh weather Very long press -> Back to home page ------------------------------------------------------------ This sketch needs the Arduino UNO R4 WiFi. The UNO R4 Minima has no WiFi, so it cannot read the API. ============================================================ */ #include <WiFiS3.h> #include <ArduinoHttpClient.h> #include <ArduinoJson.h> #include <DIYables_TFT_Round.h> #include <ezButton.h> // ===== USER CONFIGURATION ===== #define WIFI_SSID "YOUR_WIFI_SSID" #define WIFI_PASSWORD "YOUR_WIFI_PASSWORD" #define LATITUDE 37.5665 #define LONGITUDE 126.9780 #define LOCATION_NAME "SEOUL" // Time zone name for the API, URL-encoded. // "/" must be written as "%2F". #define TIMEZONE "Asia%2FSeoul" // ===== API SERVER ===== const char API_HOST[] = "api.open-meteo.com"; const int API_PORT = 443; // ===== PIN CONFIGURATION ===== // SCK (D13) and MOSI (D11) are the hardware SPI pins of the // Arduino UNO R4. They are fixed and are not listed here. #define PIN_BUTTON 2 #define PIN_RST 8 #define PIN_DC 9 #define PIN_CS 10 // ===== TIMING ===== const unsigned long API_UPDATE_INTERVAL = 10UL * 60UL * 1000UL; const unsigned long WIFI_RETRY_INTERVAL = 10UL * 1000UL; const unsigned long WIFI_CONNECT_TIMEOUT = 15UL * 1000UL; const unsigned long BUTTON_DEBOUNCE = 40; const unsigned long LONG_PRESS_TIME = 600; const unsigned long VERY_LONG_PRESS_TIME = 2000; const unsigned long RAIN_ANIMATION_INTERVAL = 90; const unsigned long CLOCK_UPDATE_INTERVAL = 1000; // ===== TFT ===== DIYables_TFT_GC9A01_Round tft(PIN_RST, PIN_DC, PIN_CS); // ===== SCREEN ===== const int SCREEN_W = 240; const int SCREEN_H = 240; const int CENTER_X = 120; const int CENTER_Y = 120; // ===== COLORS ===== #define BLACK DIYables_TFT::colorRGB(0, 0, 0) #define WHITE DIYables_TFT::colorRGB(255, 255, 255) #define CYAN DIYables_TFT::colorRGB(0, 220, 255) #define BLUE DIYables_TFT::colorRGB(40, 120, 255) #define LIGHT_BLUE DIYables_TFT::colorRGB(90, 190, 255) #define GREEN DIYables_TFT::colorRGB(60, 230, 130) #define YELLOW DIYables_TFT::colorRGB(255, 210, 60) #define ORANGE DIYables_TFT::colorRGB(255, 150, 40) #define RED DIYables_TFT::colorRGB(255, 70, 70) #define PURPLE DIYables_TFT::colorRGB(180, 100, 255) #define GRAY DIYables_TFT::colorRGB(125, 125, 135) #define DARK_GRAY DIYables_TFT::colorRGB(42, 42, 52) #define DARK_BLUE DIYables_TFT::colorRGB(12, 18, 48) // ===== PAGE ===== enum Page { PAGE_HOME = 0, PAGE_TEMP, PAGE_HUMIDITY, PAGE_WIND, PAGE_RAIN, PAGE_HOURLY, PAGE_FORECAST, PAGE_SYSTEM, PAGE_COUNT }; Page currentPage = PAGE_HOME; // ===== WEATHER DATA ===== // The Arduino UNO R4 has only 32 KB of RAM, so short text is // kept in fixed char arrays instead of String objects. struct WeatherData { bool valid = false; float temperature = 0; float humidity = 0; float feelsLike = 0; float windSpeed = 0; float windDirection = 0; float pressure = 0; int weatherCode = 0; bool isDay = true; int currentRainChance = 0; float uvIndex = 0; // "HH:MM" char sunrise[6] = "--:--"; char sunset[6] = "--:--"; // NEXT HOURS float hourlyTemp[12]; int hourlyRainChance[12]; int hourlyWeatherCode[12]; // "HH:MM" char hourlyTime[12][6]; // DAILY // "YYYY-MM-DD" char dailyDate[3][11]; float dailyTempMax[3]; float dailyTempMin[3]; int dailyRainChance[3]; int dailyWeatherCode[3]; float dailyUV[3]; }; WeatherData weather; // ===== SOFTWARE CLOCK ===== // The Arduino UNO R4 has no NTP client in this sketch. // Instead, the local time that comes with the weather answer // is stored, then millis() moves the clock forward. // Seconds after midnight at the moment of the last answer. long clockBaseSecond = -1; unsigned long clockBaseMillis = 0; // ===== SYSTEM STATE ===== bool apiOnline = false; bool wifiOnline = false; bool screenDirty = true; bool isFetching = false; bool pendingRefresh = false; unsigned long lastApiUpdate = 0; unsigned long lastWifiRetry = 0; unsigned long wifiConnectStart = 0; unsigned long lastRainAnimation = 0; unsigned long lastClockUpdate = 0; bool wifiConnecting = false; // ===== BUTTON ===== // ezButton handles the debouncing, so the sketch only has to // measure how long the button was held down. // // The line below is for a BARE push button wired to GND. ezButton then // uses the internal pull-up resistor of the board, and the pin reads LOW // while the button is held down. ezButton button(PIN_BUTTON); // If you use a BUTTON MODULE instead, the module carries its own resistor // on the board, so the internal one must not be used. Comment the line // above and use this line instead: // ezButton button(PIN_BUTTON, EXTERNAL_PULLDOWN); unsigned long buttonPressStart = 0; // ===== RAIN ANIMATION ===== const int RAIN_DROP_COUNT = 16; int rainX[RAIN_DROP_COUNT]; int rainY[RAIN_DROP_COUNT]; bool rainInitialized = false; const int RAIN_AREA_X = 30; const int RAIN_AREA_Y = 125; const int RAIN_AREA_W = 180; const int RAIN_AREA_H = 78; // ===== WEATHER DESCRIPTION ===== String weatherDescription(int code) { switch (code) { case 0: return "CLEAR"; case 1: return "MAINLY CLEAR"; case 2: return "PARTLY CLOUDY"; case 3: return "OVERCAST"; case 45: case 48: return "FOG"; case 51: case 53: case 55: return "DRIZZLE"; case 56: case 57: return "FREEZING DRIZZLE"; case 61: case 63: case 65: return "RAIN"; case 66: case 67: return "FREEZING RAIN"; case 71: case 73: case 75: return "SNOW"; case 77: return "SNOW GRAINS"; case 80: case 81: case 82: return "SHOWERS"; case 85: case 86: return "SNOW SHOWERS"; case 95: return "THUNDERSTORM"; case 96: case 99: return "STORM"; default: return "UNKNOWN"; } } // ===== WEATHER COLOR ===== uint16_t weatherColor(int code) { if (code == 0) return YELLOW; if (code == 1 || code == 2) return CYAN; if (code == 3) return GRAY; if (code >= 51 && code <= 67) return BLUE; if (code >= 80 && code <= 82) return LIGHT_BLUE; if (code >= 95) return RED; return WHITE; } // ===== WIND DIRECTION ===== String windDirectionText(float degrees) { const char* directions[] = { "N", "NE", "E", "SE", "S", "SW", "W", "NW" }; int index = (int)((degrees + 22.5) / 45.0); index %= 8; return directions[index]; } // ===== CENTER TEXT ===== void drawCenteredText(String text, int y, int size, uint16_t color) { tft.setTextSize(size); tft.setTextColor(color); int width = text.length() * 6 * size; int x = CENTER_X - width / 2; if (x < 0) x = 0; tft.setCursor(x, y); tft.print(text); } // ===== HEADER ===== void drawHeader(String title, uint16_t color) { // Safe radius from screen center (slightly less than the // physical 120px radius, to keep a small margin from the // round bezel). const float SAFE_RADIUS = 116.0; int size = 2; int width = title.length() * 6 * size; int half = width / 2; int y; if (half >= SAFE_RADIUS) { // Too wide even at the lowest safe position -> use a // smaller font instead of clipping. size = 1; width = title.length() * 6 * size; half = width / 2; y = 14; } else { // Compute the minimum y (top of text) so the top corners // of the text stay inside the round visible area. float insideSpan = sqrt((SAFE_RADIUS * SAFE_RADIUS) - ((float)half * (float)half)); int minY = (int)(120.0 - insideSpan); y = (minY > 14) ? minY : 14; } drawCenteredText(title, y, size, color); int lineY = y + (size == 2 ? 16 : 8) + 6; tft.drawLine(45, lineY, 195, lineY, DARK_GRAY); } // ===== FOOTER ===== void drawFooter() { tft.drawLine(55, 214, 185, 214, DARK_GRAY); drawCenteredText(String(currentPage + 1) + "/" + String(PAGE_COUNT), 220, 1, GRAY); } // ===== SUN ICON ===== void drawSun(int x, int y, int radius) { tft.fillCircle(x, y, radius, YELLOW); for (int i = 0; i < 8; i++) { float angle = i * PI / 4.0; int x1 = x + cos(angle) * (radius + 7); int y1 = y + sin(angle) * (radius + 7); int x2 = x + cos(angle) * (radius + 14); int y2 = y + sin(angle) * (radius + 14); tft.drawLine(x1, y1, x2, y2, YELLOW); } } // ===== CLOUD ICON ===== void drawCloud(int x, int y) { tft.fillCircle(x - 20, y, 15, LIGHT_BLUE); tft.fillCircle(x, y - 8, 20, LIGHT_BLUE); tft.fillCircle(x + 20, y, 15, LIGHT_BLUE); tft.fillRoundRect(x - 35, y, 70, 20, 10, LIGHT_BLUE); } // ===== STATIC RAIN ICON ===== void drawRainIcon(int x, int y) { drawCloud(x, y); for (int i = 0; i < 5; i++) { int dx = -24 + i * 12; int dy = 28; tft.drawLine(x + dx, y + dy, x + dx - 4, y + dy + 10, BLUE); } } // ===== WEATHER ICON ===== void drawWeatherIcon(int code, int x, int y) { if (code == 0) { drawSun(x, y, 18); } else if (code <= 2) { drawSun(x - 12, y - 7, 13); drawCloud(x + 10, y + 10); } else if (code == 3) { drawCloud(x, y); } else if ((code >= 51 && code <= 67) || (code >= 80 && code <= 82)) { drawRainIcon(x, y); } else if (code >= 95) { drawCloud(x, y); tft.drawLine(x - 5, y + 28, x - 12, y + 43, YELLOW); tft.drawLine(x + 10, y + 28, x + 3, y + 43, YELLOW); } else { drawCloud(x, y); } } void drawWifiIcon(int cx, int cy, int level) { const int radii[3] = { 14, 27, 40 }; for (int arc = 0; arc < 3; arc++) { uint16_t color = (arc < level) ? CYAN : DARK_GRAY; for (int deg = -150; deg <= -30; deg += 5) { float angle = deg * PI / 180.0; int x = cx + (int)(cos(angle) * radii[arc]); int y = cy + (int)(sin(angle) * radii[arc]); tft.fillCircle(x, y, 2, color); } } // Base dot (the "device") tft.fillCircle(cx, cy, 5, CYAN); } // ===== PAGE 1 - HOME ===== void drawHome() { uint16_t bg = weather.isDay ? BLACK : DARK_BLUE; tft.fillScreen(bg); if (!weather.valid) { drawCenteredText("NO DATA", 90, 3, RED); drawCenteredText("CHECK CONNECTION", 135, 1, WHITE); drawFooter(); return; } // Weather icon drawWeatherIcon(weather.weatherCode, CENTER_X, 65); // Temperature drawCenteredText(String(weather.temperature, 1) + " C", 112, 3, WHITE); // Location drawCenteredText(LOCATION_NAME, 153, 2, CYAN); // Weather condition drawCenteredText(weatherDescription(weather.weatherCode), 178, 1, weatherColor(weather.weatherCode)); // Clock area drawHomeClock(); drawFooter(); } // ===== HOME CLOCK ===== void drawHomeClock() { uint16_t bg = weather.isDay ? BLACK : DARK_BLUE; // Only clear a small region. // This prevents full-screen flicker. tft.fillRect(82, 196, 76, 12, bg); drawCenteredText(getTimeString(), 197, 1, GRAY); } // ===== PAGE 2 - TEMPERATURE ===== void drawTemperature() { tft.fillScreen(BLACK); drawHeader("TEMPERATURE", ORANGE); int cx = 120; int cy = 125; int radius = 68; int value = constrain((int)weather.temperature, 0, 40); int filled = map(value, 0, 40, 0, 240); // Temperature gauge for (int i = 0; i < 240; i += 4) { float angle = (-135.0 + i) * PI / 180.0; int x = cx + cos(angle) * radius; int y = cy + sin(angle) * radius; uint16_t color = (i < filled) ? ORANGE : DARK_GRAY; tft.fillCircle(x, y, 2, color); } // Main value drawCenteredText(String(weather.temperature, 1), 101, 3, WHITE); drawCenteredText("C", 138, 2, ORANGE); drawCenteredText("FEELS " + String(weather.feelsLike, 1) + " C", 168, 1, CYAN); drawFooter(); } // ===== PAGE 3 - HUMIDITY ===== void drawHumidity() { tft.fillScreen(BLACK); drawHeader("HUMIDITY", BLUE); int cx = 120; int cy = 125; int radius = 68; int progress = constrain((int)weather.humidity, 0, 100); int filled = map(progress, 0, 100, 0, 270); for (int i = 0; i < 270; i += 3) { float angle = (-135.0 + i) * PI / 180.0; int x = cx + cos(angle) * radius; int y = cy + sin(angle) * radius; uint16_t color = (i < filled) ? BLUE : DARK_GRAY; tft.fillCircle(x, y, 3, color); } drawCenteredText(String((int)weather.humidity) + "%", 105, 3, WHITE); String comfort; if (weather.humidity < 40) comfort = "DRY"; else if (weather.humidity < 70) comfort = "COMFORTABLE"; else if (weather.humidity < 85) comfort = "HUMID"; else comfort = "VERY HUMID"; drawCenteredText(comfort, 153, 1, CYAN); drawFooter(); } // ===== PAGE 4 - WIND ===== void drawWind() { tft.fillScreen(BLACK); drawHeader("WIND", GREEN); int cx = 120; int cy = 104; int radius = 49; // Compass tft.drawCircle(cx, cy, radius, DARK_GRAY); drawCenteredText("N", 43, 1, WHITE); drawCenteredText("S", 157, 1, WHITE); tft.setCursor(62, 101); tft.setTextSize(1); tft.setTextColor(WHITE); tft.print("W"); tft.setCursor(172, 101); tft.print("E"); // Direction arrow float angle = weather.windDirection * PI / 180.0; int x2 = cx + sin(angle) * 38; int y2 = cy - cos(angle) * 38; tft.drawLine(cx, cy, x2, y2, GREEN); tft.fillCircle(cx, cy, 5, GREEN); // Wind speed drawCenteredText(String(weather.windSpeed, 1) + " km/h", 174, 2, WHITE); drawCenteredText(windDirectionText(weather.windDirection), 198, 1, CYAN); drawFooter(); } // ===== PAGE 5 - RAIN ===== void drawRain() { tft.fillScreen(BLACK); drawHeader("RAIN", LIGHT_BLUE); int rain = weather.currentRainChance; // Static weather icon if (rain >= 60) { drawRainIcon(CENTER_X, 68); } else { drawCloud(CENTER_X, 73); } // Rain percentage drawCenteredText(String(rain) + "%", 105, 3, WHITE); drawCenteredText("RAIN CHANCE", 140, 1, LIGHT_BLUE); // Status if (rain >= 60) { drawCenteredText("HIGH", 158, 1, RED); } else if (rain >= 30) { drawCenteredText("MODERATE", 158, 1, YELLOW); } else { drawCenteredText("LOW", 158, 1, GREEN); } // Animation area if (rain >= 20) { initializeRainAnimation(); } drawFooter(); } // ===== INITIALIZE RAIN ANIMATION ===== void initializeRainAnimation() { for (int i = 0; i < RAIN_DROP_COUNT; i++) { rainX[i] = RAIN_AREA_X + random(RAIN_AREA_W); rainY[i] = RAIN_AREA_Y + random(RAIN_AREA_H); } rainInitialized = true; } // ===== UPDATE RAIN ANIMATION ===== void updateRainAnimation() { if (currentPage != PAGE_RAIN) return; if (weather.currentRainChance < 20) return; if (millis() - lastRainAnimation < RAIN_ANIMATION_INTERVAL) { return; } lastRainAnimation = millis(); uint16_t bg = BLACK; // Erase old drops for (int i = 0; i < RAIN_DROP_COUNT; i++) { tft.drawLine(rainX[i], rainY[i], rainX[i] - 3, rainY[i] + 7, bg); } // Move drops for (int i = 0; i < RAIN_DROP_COUNT; i++) { rainY[i] += 5; if (rainY[i] > RAIN_AREA_Y + RAIN_AREA_H) { rainX[i] = RAIN_AREA_X + random(RAIN_AREA_W); rainY[i] = RAIN_AREA_Y; } tft.drawLine(rainX[i], rainY[i], rainX[i] - 3, rainY[i] + 7, BLUE); } } // ===== PAGE 6 - NEXT HOURS ===== void drawHourly() { tft.fillScreen(BLACK); drawHeader("NEXT HOURS", PURPLE); for (int i = 0; i < 5; i++) { int y = 52 + i * 31; // Row separator if (i > 0) { tft.drawLine(25, y - 8, 215, y - 8, DARK_GRAY); } // Time tft.setCursor(25, y); tft.setTextSize(1); tft.setTextColor(WHITE); tft.print(weather.hourlyTime[i]); // Weather int code = weather.hourlyWeatherCode[i]; String condition; if (code == 0) condition = "SUN"; else if (code <= 3) condition = "CLOUD"; else if (code >= 95) condition = "STORM"; else condition = "RAIN"; tft.setCursor(75, y); tft.setTextColor(weatherColor(code)); tft.print(condition); // Temperature tft.setCursor(142, y); tft.setTextColor(WHITE); tft.print(weather.hourlyTemp[i], 0); tft.print("C"); // Rain tft.setCursor(180, y); tft.setTextColor(LIGHT_BLUE); tft.print(weather.hourlyRainChance[i]); tft.print("%"); } drawFooter(); } // ===== PAGE 7 - FORECAST ===== // UI FIX: // The card border (drawRoundRect) previously spanned x=15..225 // (width 210). For the TOP row, the corners of that rectangle // fell outside the visible round area of the screen and were // clipped. The card width below was reduced to 190px // (x=25..215) which keeps every corner, on every row, safely // inside the round bezel. Text start x was nudged in slightly // to match the new card padding. void drawForecast() { tft.fillScreen(BLACK); drawHeader("3-DAY FORECAST", CYAN); const char* labels[] = { "TODAY", "TOMORROW", "DAY 3" }; // Card geometry (UI fix: narrower so it never touches // the round bezel, even on the top row). const int CARD_X = 25; const int CARD_W = 190; for (int i = 0; i < 3; i++) { int y = 62 + i * 48; // Card tft.drawRoundRect(CARD_X, y - 5, CARD_W, 40, 8, DARK_GRAY); // Day tft.setCursor(CARD_X + 8, y + 4); tft.setTextSize(1); tft.setTextColor(WHITE); tft.print(labels[i]); // Temperature tft.setCursor(CARD_X + 65, y + 4); tft.print(weather.dailyTempMin[i], 0); tft.print("/"); tft.print(weather.dailyTempMax[i], 0); tft.print("C"); // Rain tft.setCursor(CARD_X + 120, y + 4); tft.setTextColor(LIGHT_BLUE); tft.print(weather.dailyRainChance[i]); tft.print("%"); // Weather tft.setCursor(CARD_X + 155, y + 4); tft.setTextColor(weatherColor(weather.dailyWeatherCode[i])); int code = weather.dailyWeatherCode[i]; if (code == 0) tft.print("SUN"); else if (code <= 3) tft.print("CLD"); else if (code >= 95) tft.print("STM"); else tft.print("RAIN"); } drawFooter(); } // ===== PAGE 8 - SYSTEM ===== void drawSystem() { tft.fillScreen(BLACK); drawHeader("SYSTEM", CYAN); // WIFI tft.setCursor(25, 57); tft.setTextSize(1); tft.setTextColor(WHITE); tft.print("WIFI"); tft.setCursor(125, 57); if (WiFi.status() == WL_CONNECTED) { tft.setTextColor(GREEN); tft.print("ONLINE"); } else { tft.setTextColor(RED); tft.print("OFFLINE"); } // RSSI tft.setCursor(25, 82); tft.setTextColor(WHITE); tft.print("RSSI"); tft.setCursor(125, 82); if (WiFi.status() == WL_CONNECTED) { tft.setTextColor(CYAN); tft.print(WiFi.RSSI()); tft.print(" dBm"); } else { tft.print("--"); } // API tft.setCursor(25, 107); tft.setTextColor(WHITE); tft.print("API"); tft.setCursor(125, 107); if (apiOnline) { tft.setTextColor(GREEN); tft.print("ONLINE"); } else { tft.setTextColor(RED); tft.print("OFFLINE"); } // LOCATION tft.setCursor(25, 132); tft.setTextColor(WHITE); tft.print("LOCATION"); tft.setCursor(125, 132); tft.setTextColor(CYAN); tft.print(LOCATION_NAME); // TEMP tft.setCursor(25, 157); tft.setTextColor(WHITE); tft.print("TEMP"); tft.setCursor(125, 157); tft.print(weather.temperature, 1); tft.print(" C"); // STATUS tft.setCursor(25, 182); tft.setTextColor(WHITE); tft.print("STATUS"); tft.setCursor(125, 182); if (weather.valid) { tft.setTextColor(GREEN); tft.print("READY"); } else { tft.setTextColor(RED); tft.print("NO DATA"); } drawFooter(); } // ===== RENDER PAGE ===== void renderPage() { // Rain animation must be initialized // only when entering the page. rainInitialized = false; switch (currentPage) { case PAGE_HOME: drawHome(); break; case PAGE_TEMP: drawTemperature(); break; case PAGE_HUMIDITY: drawHumidity(); break; case PAGE_WIND: drawWind(); break; case PAGE_RAIN: drawRain(); break; case PAGE_HOURLY: drawHourly(); break; case PAGE_FORECAST: drawForecast(); break; case PAGE_SYSTEM: drawSystem(); break; } } // ===== WEATHER API PATH ===== // ArduinoHttpClient keeps the host name apart from the path, // so only the part after the host name is built here. String getWeatherPath() { String path = "/v1/forecast"; path += "?latitude=" + String(LATITUDE, 4); path += "&longitude=" + String(LONGITUDE, 4); path += "&current=" "temperature_2m," "relative_humidity_2m," "apparent_temperature," "weather_code," "wind_speed_10m," "wind_direction_10m," "pressure_msl," "is_day"; path += "&hourly=" "temperature_2m," "precipitation_probability," "weather_code"; path += "&daily=" "temperature_2m_max," "temperature_2m_min," "precipitation_probability_max," "weather_code," "sunrise," "sunset," "uv_index_max"; path += "&timezone=" TIMEZONE; path += "&forecast_days=3"; // Only the next 12 hours are asked for. // The answer then starts at the current hour, and it stays // small enough for the 32 KB RAM of the Arduino UNO R4. path += "&forecast_hours=12"; path += "&temperature_unit=celsius"; path += "&wind_speed_unit=kmh"; return path; } // ===== COPY "HH:MM" OUT OF AN ISO TIME TEXT ===== // Input : 2026-09-18T14:00 // Output: 14:00 void copyClockText(const char* isoTime, char* out) { if (isoTime != NULL && strlen(isoTime) >= 16) { out[0] = isoTime[11]; out[1] = isoTime[12]; out[2] = ':'; out[3] = isoTime[14]; out[4] = isoTime[15]; out[5] = '\0'; return; } strcpy(out, "--:--"); } // ===== SET THE SOFTWARE CLOCK FROM AN ISO TIME TEXT ===== void setClockFrom(const char* isoTime) { if (isoTime == NULL || strlen(isoTime) < 16) { return; } int hour = (isoTime[11] - '0') * 10 + (isoTime[12] - '0'); int minute = (isoTime[14] - '0') * 10 + (isoTime[15] - '0'); if (hour < 0 || hour > 23 || minute < 0 || minute > 59) { return; } clockBaseSecond = (long)hour * 3600L + (long)minute * 60L; clockBaseMillis = millis(); } // ===== PARSE WEATHER ===== bool parseWeather(const String& payload) { // A filter keeps only the three blocks that are used. // The "..._units" blocks of the answer are dropped, so the // document stays small in the RAM of the Arduino UNO R4. JsonDocument filter; filter["current"] = true; filter["hourly"] = true; filter["daily"] = true; JsonDocument doc; DeserializationError error = deserializeJson(doc, payload, DeserializationOption::Filter(filter)); if (error) { Serial.print("JSON ERROR: "); Serial.println(error.c_str()); return false; } // CURRENT JsonObject current = doc["current"]; if (current.isNull()) { Serial.println("CURRENT DATA MISSING"); return false; } weather.temperature = current["temperature_2m"] | 0.0; weather.humidity = current["relative_humidity_2m"] | 0.0; weather.feelsLike = current["apparent_temperature"] | 0.0; weather.weatherCode = current["weather_code"] | 0; weather.windSpeed = current["wind_speed_10m"] | 0.0; weather.windDirection = current["wind_direction_10m"] | 0.0; weather.pressure = current["pressure_msl"] | 0.0; int isDay = current["is_day"] | 1; weather.isDay = (isDay == 1); // The answer carries the local time of the location. // It is used to start the software clock. const char* currentTime = current["time"] | ""; setClockFrom(currentTime); // HOURLY // "forecast_hours=12" makes the list start at the current // hour, so index 0 is now and index 11 is 11 hours later. JsonArray hourlyTime = doc["hourly"]["time"]; JsonArray hourlyTemp = doc["hourly"]["temperature_2m"]; JsonArray hourlyRain = doc["hourly"]["precipitation_probability"]; JsonArray hourlyCode = doc["hourly"]["weather_code"]; for (int i = 0; i < 12; i++) { // Safety check if (i >= (int)hourlyTemp.size()) { weather.hourlyTemp[i] = weather.temperature; weather.hourlyRainChance[i] = 0; weather.hourlyWeatherCode[i] = weather.weatherCode; strcpy(weather.hourlyTime[i], "--:--"); continue; } weather.hourlyTemp[i] = hourlyTemp[i] | weather.temperature; weather.hourlyRainChance[i] = hourlyRain[i] | 0; weather.hourlyWeatherCode[i] = hourlyCode[i] | weather.weatherCode; copyClockText(hourlyTime[i] | "", weather.hourlyTime[i]); } weather.currentRainChance = weather.hourlyRainChance[0]; // DAILY JsonArray dailyMax = doc["daily"]["temperature_2m_max"]; JsonArray dailyMin = doc["daily"]["temperature_2m_min"]; JsonArray dailyRain = doc["daily"]["precipitation_probability_max"]; JsonArray dailyCode = doc["daily"]["weather_code"]; JsonArray dailyDate = doc["daily"]["time"]; JsonArray dailySunrise = doc["daily"]["sunrise"]; JsonArray dailySunset = doc["daily"]["sunset"]; JsonArray dailyUV = doc["daily"]["uv_index_max"]; for (int i = 0; i < 3; i++) { weather.dailyTempMax[i] = dailyMax[i] | 0.0; weather.dailyTempMin[i] = dailyMin[i] | 0.0; weather.dailyRainChance[i] = dailyRain[i] | 0; weather.dailyWeatherCode[i] = dailyCode[i] | 0; weather.dailyUV[i] = dailyUV[i] | 0.0; const char* dateText = dailyDate[i] | ""; strncpy(weather.dailyDate[i], dateText, 10); weather.dailyDate[i][10] = '\0'; } if (dailySunrise.size() > 0) { copyClockText(dailySunrise[0] | "", weather.sunrise); } if (dailySunset.size() > 0) { copyClockText(dailySunset[0] | "", weather.sunset); } if (dailyUV.size() > 0) { weather.uvIndex = dailyUV[0] | 0.0; } weather.valid = true; return true; } // ===== FETCH WEATHER ===== bool fetchWeather() { if (WiFi.status() != WL_CONNECTED) { Serial.println("WIFI NOT CONNECTED"); apiOnline = false; screenDirty = true; return false; } Serial.println(); Serial.println("================================"); Serial.println("FETCHING WEATHER"); Serial.println("================================"); isFetching = true; // Loading screen is shown only once. drawLoadingScreen("FETCHING WEATHER..."); // WiFiSSLClient does the HTTPS part on the Arduino UNO R4. // The root certificates live in the WiFi firmware, so there // is no certificate to paste into this sketch. WiFiSSLClient sslClient; HttpClient http(sslClient, API_HOST, API_PORT); http.setTimeout(15000); http.setHttpResponseTimeout(15000); String path = getWeatherPath(); Serial.print("GET https://"); Serial.print(API_HOST); Serial.println(path); int error = http.get(path); if (error != 0) { Serial.print("HTTP REQUEST FAILED: "); Serial.println(error); http.stop(); isFetching = false; apiOnline = false; screenDirty = true; return false; } int statusCode = http.responseStatusCode(); Serial.print("HTTP CODE: "); Serial.println(statusCode); if (statusCode != 200) { Serial.println("API ERROR"); http.stop(); isFetching = false; apiOnline = false; screenDirty = true; return false; } // responseBody() also takes care of a chunked answer. String payload = http.responseBody(); http.stop(); Serial.print("PAYLOAD SIZE: "); Serial.println(payload.length()); bool result = parseWeather(payload); // The text is not needed any more, so the RAM is given back // before the screen is drawn. payload = ""; isFetching = false; if (result) { apiOnline = true; lastApiUpdate = millis(); Serial.println("WEATHER UPDATED"); Serial.print("Temperature: "); Serial.println(weather.temperature); Serial.print("Humidity: "); Serial.println(weather.humidity); Serial.print("Weather: "); Serial.println(weatherDescription(weather.weatherCode)); Serial.print("Wind: "); Serial.println(weather.windSpeed); Serial.print("Rain Chance: "); Serial.println(weather.currentRainChance); screenDirty = true; return true; } apiOnline = false; screenDirty = true; return false; } // ===== LOADING SCREEN ===== void drawLoadingScreen(String message) { tft.fillScreen(BLACK); drawCenteredText("SMART WEATHER", 48, 2, CYAN); drawCenteredText("STATION", 73, 2, WHITE); // Loading ring for (int i = 0; i < 12; i++) { float angle = i * 2.0 * PI / 12.0; int x = CENTER_X + cos(angle) * 42; int y = CENTER_Y + sin(angle) * 42; uint16_t color = (i == 0) ? CYAN : DARK_GRAY; tft.fillCircle(x, y, 4, color); } drawCenteredText(message, 182, 1, WHITE); } // ===== WIFI CONNECT ===== bool connectWiFi() { Serial.println(); Serial.println("CONNECTING TO WIFI..."); tft.fillScreen(BLACK); drawCenteredText("CONNECTING", 26, 2, CYAN); drawWifiIcon(CENTER_X, 95, 0); drawCenteredText("WI-FI", 145, 2, WHITE); drawCenteredText(WIFI_SSID, 178, 1, GRAY); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); unsigned long start = millis(); int iconFrame = 0; while (WiFi.status() != WL_CONNECTED && millis() - start < WIFI_CONNECT_TIMEOUT) { drawWifiIcon(CENTER_X, 95, iconFrame % 4); iconFrame++; delay(250); Serial.print("."); } Serial.println(); if (WiFi.status() == WL_CONNECTED) { wifiOnline = true; wifiConnecting = false; drawWifiIcon(CENTER_X, 95, 3); Serial.println("WIFI CONNECTED"); Serial.print("IP: "); Serial.println(WiFi.localIP()); Serial.print("RSSI: "); Serial.print(WiFi.RSSI()); Serial.println(" dBm"); return true; } wifiOnline = false; Serial.println("WIFI CONNECTION FAILED"); return false; } // ===== START WIFI RECONNECT ===== void startWiFiReconnect() { Serial.println("START WIFI RECONNECT"); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); wifiConnecting = true; wifiConnectStart = millis(); } // ===== HANDLE WIFI ===== void handleWiFi() { int status = WiFi.status(); // Connected if (status == WL_CONNECTED) { if (!wifiOnline) { Serial.println("WIFI CONNECTED"); screenDirty = true; } wifiOnline = true; wifiConnecting = false; return; } wifiOnline = false; // Currently connecting if (wifiConnecting) { if (millis() - wifiConnectStart > WIFI_CONNECT_TIMEOUT) { Serial.println("WIFI CONNECT TIMEOUT"); WiFi.disconnect(); wifiConnecting = false; lastWifiRetry = millis(); } return; } // Start another attempt if (millis() - lastWifiRetry >= WIFI_RETRY_INTERVAL) { lastWifiRetry = millis(); startWiFiReconnect(); } } // ===== CHECK THE WIFI MODULE ===== bool checkWiFiModule() { if (WiFi.status() == WL_NO_MODULE) { Serial.println("WIFI MODULE NOT FOUND"); Serial.println("Use an Arduino UNO R4 WiFi board."); return false; } Serial.print("WIFI FIRMWARE: "); Serial.println(WiFi.firmwareVersion()); return true; } // ===== BUTTON HANDLER ===== void handleButton() { button.loop(); // MUST call the loop() function first // The moment the button goes down, remember when it happened. if (button.isPressed()) buttonPressStart = millis(); // The action is chosen when the button comes back up. if (button.isReleased()) { unsigned long duration = millis() - buttonPressStart; if (duration >= VERY_LONG_PRESS_TIME) { // VERY LONG PRESS -> back to the home page currentPage = PAGE_HOME; screenDirty = true; Serial.println("VERY LONG PRESS -> HOME"); } else if (duration >= LONG_PRESS_TIME) { // LONG PRESS -> ask the weather API for fresh data pendingRefresh = true; Serial.println("LONG PRESS -> REFRESH"); } else { // SHORT PRESS -> next page currentPage = (Page)((currentPage + 1) % PAGE_COUNT); screenDirty = true; Serial.print("SHORT PRESS -> PAGE "); Serial.println(currentPage + 1); } } } // ===== PROCESS MANUAL REFRESH ===== void handleManualRefresh() { if (!pendingRefresh) return; pendingRefresh = false; fetchWeather(); } // ===== AUTO API UPDATE ===== void handleApiUpdate() { if (!weather.valid) { return; } if (millis() - lastApiUpdate >= API_UPDATE_INTERVAL) { fetchWeather(); } } // ===== CLOCK ===== // The clock starts from the local time that came with the // weather answer, then millis() moves it forward. String getTimeString() { if (clockBaseSecond < 0) { return "--:--"; } unsigned long elapsed = (millis() - clockBaseMillis) / 1000UL; long nowSecond = clockBaseSecond + (long)elapsed; // Wrap around at midnight. nowSecond %= 86400L; int hour = nowSecond / 3600L; int minute = (nowSecond % 3600L) / 60L; char buffer[6]; sprintf(buffer, "%02d:%02d", hour, minute); return String(buffer); } // ===== HANDLE CLOCK ===== void handleClock() { if (currentPage != PAGE_HOME) { return; } if (millis() - lastClockUpdate < CLOCK_UPDATE_INTERVAL) { return; } lastClockUpdate = millis(); if (weather.valid && !isFetching) { drawHomeClock(); } } // ===== SETUP ===== void setup() { Serial.begin(9600); delay(500); Serial.println(); Serial.println("================================"); Serial.println("SMART WEATHER STATION"); Serial.println(LOCATION_NAME); Serial.println("================================"); // BUTTON // ezButton already set the pin to INPUT_PULLUP in its constructor. button.setDebounceTime(BUTTON_DEBOUNCE); // TFT tft.begin(); tft.setRotation(2); tft.fillScreen(BLACK); // BOOT SCREEN drawCenteredText("SMART WEATHER", 65, 2, CYAN); drawCenteredText("STATION", 92, 2, WHITE); drawCenteredText(LOCATION_NAME, 135, 2, YELLOW); delay(1500); // WIFI if (!checkWiFiModule()) { tft.fillScreen(BLACK); drawCenteredText("NO WIFI MODULE", 100, 2, RED); drawCenteredText("NEED UNO R4 WIFI", 135, 1, WHITE); // Nothing else can be done without the WiFi module. while (true) { delay(1000); } } if (connectWiFi()) { delay(500); fetchWeather(); } else { tft.fillScreen(BLACK); drawCenteredText("WIFI ERROR", 82, 2, RED); drawCenteredText("CHECK SETTINGS", 120, 1, WHITE); drawCenteredText("RETRYING...", 150, 1, GRAY); delay(1500); } screenDirty = true; } // ===== LOOP ===== void loop() { // Input handleButton(); // WiFi handleWiFi(); // Manual refresh handleManualRefresh(); // Automatic API update handleApiUpdate(); // Clock handleClock(); // Static page rendering if (screenDirty && !isFetching) { renderPage(); screenDirty = false; } // Rain animation updateRainAnimation(); }

Detailed Instructions

Follow these instructions step by step:

  • If this is your first time using the Arduino Uno R4 WiFi, refer to the tutorial on setting up the environment for Arduino Uno R4 WiFi/Minima in the Arduino IDE.
  • Wire the components according to the provided diagram.
  • Connect the Arduino Uno R4 board to your computer using a USB cable.
  • Launch the Arduino IDE on your computer.
  • Select the appropriate Arduino Uno R4 board (Arduino Uno R4 WiFi) and COM port.
  • Navigate to the Libraries icon on the left bar of the Arduino IDE.
  • Search “DIYables TFT Round”, then find the DIYables_TFT_Round library by DIYables.
  • Click Install button to install the library.
Arduino UNO R4 TFT LCD library installation
  • You may be prompted to install additional library dependencies.
  • Click Install All button to install all library dependencies.
Arduino UNO R4 TFT LCD dependency installation
  • Search “ArduinoJson”, then find the ArduinoJson library by Benoit Blanchon and click Install.
  • Search “ArduinoHttpClient”, then find the ArduinoHttpClient library by Arduino and click Install.
  • Search “ezButton”, then find the ezButton library by ArduinoGetStarted and click Install.
  • Copy the above code and paste it into the Arduino IDE editor.
  • Change WIFI_SSID and WIFI_PASSWORD to your own WiFi name and password.
  • Change LATITUDE, LONGITUDE, LOCATION_NAME and TIMEZONE to your own city. See the section below.
  • Click the Upload button in Arduino IDE to upload the code to the Arduino UNO R4.
  • Open the Serial Monitor in the Arduino IDE and set the baud rate to 9600.
  • Wait a few seconds. The display shows the boot screen, then the WiFi screen, then the weather.
  • Press the button to move to the next page.
  • Check the result on the Serial Monitor.
Newbiely | Arduino IDE 2.3.8
──
File
Edit
Sketch
Tools
Help
Arduino Uno R4 WiFi
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'Arduino Uno R4 WiFi' on 'COM15')
New Line
9600 baud
================================ SMART WEATHER STATION SEOUL ================================ WIFI FIRMWARE: 0.5.0 CONNECTING TO WIFI... ..... WIFI CONNECTED IP: 192.168.0.24 RSSI: -47 dBm ================================ FETCHING WEATHER ================================ GET https://api.open-meteo.com/v1/forecast?latitude=37.5665&longitude=126.9780¤t=... HTTP CODE: 200 PAYLOAD SIZE: 1754 WEATHER UPDATED Temperature: 24.60 Humidity: 55.00 Weather: PARTLY CLOUDY Wind: 9.30 Rain Chance: 21 SHORT PRESS -> PAGE 2 SHORT PRESS -> PAGE 3 LONG PRESS -> REFRESH VERY LONG PRESS -> HOME
Ln 11, Col 1
Arduino Uno R4 WiFi on COM15
2

How To Use the Button

One button does three jobs. The difference is how long you hold it:

  • Short press (under 0.6 second): go to the next page. After the last page, it goes back to the home page.
  • Long press (0.6 to 2 seconds): ask the weather API for new data right now.
  • Very long press (over 2 seconds): jump back to the home page.

The code uses the ezButton library for the button. The library removes the contact bounce on its own, so one press is never counted twice, and the sketch only has to measure how long the button was held down.

ezButton button(PIN_BUTTON);

In setup(), set the debounce time. The library already set the pin to INPUT_PULLUP when the object was created, so no pinMode() line is needed.

button.setDebounceTime(40);

In the loop, call button.loop() first, then read the two events.

button.loop(); // MUST call the loop() function first if (button.isPressed()) buttonPressStart = millis(); if (button.isReleased()) { unsigned long duration = millis() - buttonPressStart; // duration decides the action }

If you want to learn more about this, see the Arduino UNO R4 - Button Long Press Short Press tutorial.

How To Change the Location

The code comes with Seoul, South Korea. To show your own city, change four lines at the top of the code:

#define LATITUDE 37.5665 #define LONGITUDE 126.9780 #define LOCATION_NAME "SEOUL" #define TIMEZONE "Asia%2FSeoul"
  • LATITUDE and LONGITUDE: the position of your city. You can find them on any map website.
  • LOCATION_NAME: the short name shown on the display. Keep it short so it fits on the round screen.
  • TIMEZONE: the time zone name, for example Europe/Berlin or America/New_York. The / character must be written as %2F, because the name goes into a web address. So Europe/Berlin becomes Europe%2FBerlin.

※ NOTE THAT:

The temperature is in Celsius and the wind speed is in km/h. To change them, edit the temperature_unit and wind_speed_unit parts in the getWeatherPath() function. For example, use fahrenheit and mph.

Code Explanation

Check the explanations given in the source code comments for each line!

Troubleshooting

  • The display stays black. Check the SCL and SDA wires. On the Arduino UNO R4, they must go to D13 and D11. These two pins cannot be moved to other pins.
  • The screen shows "NO WIFI MODULE". You are using an Arduino UNO R4 Minima. This project needs the Arduino UNO R4 WiFi.
  • The screen shows "WIFI ERROR". Check the WiFi name and the password. The Arduino UNO R4 WiFi only works with 2.4 GHz networks, not 5 GHz.
  • The Serial Monitor shows an HTTP error. Update the WiFi firmware of your Arduino UNO R4 WiFi. An old firmware may not hold the right root certificate. See how to upgrade the firmware on Arduino UNO R4 WiFi.
  • The clock is wrong. The clock uses the time that comes with the weather answer. Check that the TIMEZONE value matches your city.

Video Tutorial

※ OUR MESSAGES

  • As freelancers, We are AVAILABLE for HIRE. See how to outsource your project to us
  • Please feel free to share the link of this tutorial. However, Please do not use our content on any other websites. We invested a lot of effort and time to create the content, please respect our work!