Arduino UNO R4 - Intelligente Wetterstation

In dieser Anleitung lernen wir, wie man mit dem Arduino UNO R4 WiFi und einem runden 1,28-Zoll-TFT-LCD-Display eine intelligente Wetterstation baut. Die Station bezieht die aktuellen Wetterdaten aus dem Internet. Sie benötigt keinen Temperatur- oder Feuchtigkeitssensor. Im Einzelnen lernen wir:

Arduino UNO R4 intelligente Wetterstation mit rundem TFT-LCD-Display

Erforderliche Hardware

1×Arduino UNO R4 WiFi
1×Alternativ: DIYables STEM V4 IoT
1×Alternativ: DIYables STEM V4B IoT
1×USB-Kabel Typ-A zu Typ-C (für USB-A PC)
1×USB-Kabel Typ-C zu Typ-C (für USB-C PC)
1×1.28 Inch Round Circular TFT LCD Display Module
1×Push Button
1×Button Module Alternativ:
1×Breadboard
1×Verbindungskabel

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

1×(Empfohlen) Schraubklemmenblock-Shield für Arduino Uno R4
1×(Empfohlen) Sensors/Servo Expansion Shield for Arduino UNO R4
1×(Empfohlen) Breadboard-Shield für Arduino Uno R4
1×(Empfohlen) Gehäuse für Arduino Uno R4
1×(Empfohlen) Stromverteiler für Arduino Uno R4
1×(Empfohlen) Prototyping-Grundplatte & Breadboard-Kit für Arduino Uno

Oder Sie können die folgenden Kits kaufen:

1×DIYables STEM V4 IoT Starter-Kit (Arduino enthalten)
1×DIYables STEM V4B IoT Starter-Kit (Arduino enthalten)
1×DIYables Sensor-Kit (18 Sensoren/Displays)
Offenlegung: Einige der in diesem Abschnitt bereitgestellten Links sind Amazon-Affiliate-Links. Wir können eine Provision für Käufe erhalten, die über diese Links getätigt werden, ohne zusätzliche Kosten für Sie. Wir schätzen Ihre Unterstützung.
Arduino Uno R4 WiFi Compatible Boards

※ Notiz:

Dieses Projekt benötigt den Arduino UNO R4 WiFi. Der Arduino UNO R4 Minima hat kein WLAN und kann daher die Wetter-API nicht erreichen.

Über die intelligente Wetterstation

Eine normale Wetterstation misst mit einem Sensor die Luft in ihrer Umgebung. Diese Station funktioniert anders. Der Arduino UNO R4 fragt einen Wetterdienst im Internet ab, und der Dienst sendet das Wetter für Ihre Stadt zurück. Dadurch kann die Station auch Dinge anzeigen, die ein kleiner Sensor nicht messen kann, zum Beispiel die Regenwahrscheinlichkeit für die nächsten Stunden und die Vorhersage für die nächsten Tage.

Die Station hat acht Seiten. Ein Taster wechselt zwischen ihnen:

  • HOME - das Wettersymbol, die Temperatur, die Beschreibung des Himmels und eine Uhr.
  • TEMP - die Temperatur und die gefühlte Temperatur.
  • HUMIDITY - die Luftfeuchtigkeit als Ring und als Zahl.
  • WIND - die Windgeschwindigkeit und die Windrichtung als Kompass.
  • RAIN - die Regenwahrscheinlichkeit mit einer kleinen Regenanimation.
  • NEXT HOURS - die Temperatur und die Regenwahrscheinlichkeit für die nächsten Stunden.
  • FORECAST - die Höchst- und Tiefsttemperatur für die nächsten 3 Tage.
  • SYSTEM - der WLAN-Status, die Signalstärke und der API-Status.

Der Arduino UNO R4 fragt alle 10 Minuten neue Wetterdaten ab. Zwischen zwei Aktualisierungen läuft die Uhr auf der Startseite weiter.

Über die Open-Meteo-Wetter-API

Open-Meteo ist ein kostenloser Wetterdienst. Er passt gut zu einem kleinen Board wie dem Arduino UNO R4:

  • Kein API-Schlüssel: Sie müssen sich nicht registrieren und keinen Schlüssel in den Code einfügen.
  • Kostenlos für den privaten Gebrauch: keine Kosten für ein kleines Projekt wie dieses.
  • Einfaches JSON: Die Antwort ist ein kurzer JSON-Text, den der Arduino UNO R4 lesen kann.
  • Jeder Ort: Sie wählen den Ort über Breiten- und Längengrad.

Der Arduino UNO R4 sendet eine HTTPS-Anfrage und erhält eine Antwort von etwa 1,8 KB zurück. Die Antwort enthält das aktuelle Wetter, die nächsten 12 Stunden und die nächsten 3 Tage.

※ Notiz:

Die Antwort enthält auch die Ortszeit des angefragten Ortes. Der Code übernimmt die Uhrzeit von dort, sodass die Station eine Uhr ohne NTP-Client und ohne RTC-Modul anzeigt.

Verdrahtungsdiagramm

Das runde 1,28-Zoll-TFT-LCD-Display nutzt die Hardware-SPI-Pins des Arduino UNO R4. Diese Pins sind fest vorgegeben: D13 ist der Takt und D11 die Datenleitung.

TFT-LCD-Pin Arduino UNO R4 Beschreibung
VCC 5V Stromversorgung
GND GND Masse
SCL D13 SPI-Takt (fester Pin)
SDA D11 SPI MOSI (fester Pin)
DC D9 Daten/Befehl
CS D10 Chip Select
RST D8 Reset

Für den Taster können Sie eines von zwei Bauteilen wählen. Beide funktionieren im Projekt auf die gleiche Weise. Nur eine Codezeile ist anders.

Verdrahtung mit einem Taster

Ein Taster hat zwei Pins und keinen Widerstand. Er verbindet D2 mit GND, wenn Sie ihn drücken. Der Arduino UNO R4 aktiviert seinen eigenen internen Pull-up-Widerstand, sodass Sie keinen Widerstand hinzufügen müssen.

Schaltplan Arduino UNO R4 intelligente Wetterstation mit Taster

Dieses Bild wurde mit Fritzing erstellt. Klicken Sie, um das Bild zu vergrößern.

Taster-Pin Arduino UNO R4 Beschreibung
Pin 1 D2 Tastereingang mit internem Pull-up
Pin 2 GND Masse

Das verwendet der Code standardmäßig:

ezButton button(PIN_BUTTON);

Verdrahtung mit einem Tastermodul

Ein Tastermodul hat drei Pins und einen eigenen Widerstand auf der Platine. Es benötigt eine Stromversorgung, und sein OUT-Pin wird mit D2 verbunden. Das Modul liefert LOW, wenn Sie nicht drücken, und HIGH, wenn Sie drücken.

Schaltplan Arduino UNO R4 intelligente Wetterstation mit Tastermodul

Dieses Bild wurde mit Fritzing erstellt. Klicken Sie, um das Bild zu vergrößern.

Siehe Der beste Weg, den Arduino Uno R4 und andere Komponenten mit Strom zu versorgen.

Tastermodul-Pin Arduino UNO R4 Beschreibung
VCC 5V Stromversorgung
GND GND Masse
OUT D2 Tastersignal, LOW wenn losgelassen, HIGH wenn gedrückt

Das Modul hat bereits einen Widerstand, daher darf der interne nicht verwendet werden. Ändern Sie eine Zeile im Code:

ezButton button(PIN_BUTTON, EXTERNAL_PULLDOWN);

Wie man die intelligente Wetterstation programmiert

Binden Sie die fünf Bibliotheken ein.

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

Erstellen Sie das Display-Objekt mit den Pins für Reset, Daten/Befehl und Chip Select.

DIYables_TFT_GC9A01_Round tft(8, 9, 10);

Legen Sie Ihren WLAN-Namen, Ihr WLAN-Passwort und Ihren Ort fest.

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

Starten Sie das Display und das WLAN.

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

Öffnen Sie eine HTTPS-Verbindung zum Wetterserver. Beim Arduino UNO R4 übernimmt WiFiSSLClient den HTTPS-Teil. Die Root-Zertifikate sind bereits in der WLAN-Firmware enthalten, sodass Sie kein Zertifikat in den Code einfügen müssen.

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

Senden Sie die Anfrage und lesen Sie die Antwort.

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

Lesen Sie die JSON-Antwort. Der Arduino UNO R4 hat nur 32 KB RAM, deshalb wird ein Filter verwendet. Der Filter behält die drei Blöcke, die der Code benötigt, und verwirft den Rest.

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

Entnehmen Sie einen Wert aus der Antwort.

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

Zeichnen Sie ihn auf das runde Display.

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

Arduino UNO R4 Code - Intelligente Wetterstation

/* * Dieser Arduino UNO R4 Code wurde von newbiely.de entwickelt * Dieser Arduino UNO R4 Code wird der Öffentlichkeit ohne jegliche Einschränkung zur Verfügung gestellt. * Für vollständige Anleitungen und Schaltpläne besuchen Sie bitte: * https://newbiely.de/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(); }

Schnelle Schritte

Befolgen Sie diese Anweisungen Schritt für Schritt:

  • Wenn Sie den Arduino Uno R4 WiFi zum ersten Mal verwenden, lesen Sie das Tutorial zum Einrichten der Umgebung für Arduino Uno R4 WiFi/Minima in der Arduino IDE.
  • Verdrahten Sie die Bauteile gemäß dem bereitgestellten Schaltplan.
  • Verbinden Sie das Arduino Uno R4 Board über ein USB-Kabel mit Ihrem Computer.
  • Starten Sie die Arduino IDE auf Ihrem Computer.
  • Wählen Sie das passende Arduino Uno R4 Board (Arduino Uno R4 WiFi) und den COM-Port aus.
  • Klicken Sie auf das Symbol Libraries in der linken Leiste der Arduino IDE.
  • Suchen Sie nach “DIYables TFT Round” und finden Sie die Bibliothek DIYables_TFT_Round von DIYables.
  • Klicken Sie auf die Schaltfläche Install, um die Bibliothek zu installieren.
Installation der TFT-LCD-Bibliothek für Arduino UNO R4
  • Möglicherweise werden Sie aufgefordert, zusätzliche Bibliotheksabhängigkeiten zu installieren.
  • Klicken Sie auf die Schaltfläche Install All, um alle Bibliotheksabhängigkeiten zu installieren.
Installation der TFT-LCD-Abhängigkeiten für Arduino UNO R4
  • Suchen Sie nach “ArduinoJson”, finden Sie die Bibliothek ArduinoJson von Benoit Blanchon und klicken Sie auf Install.
  • Suchen Sie nach “ArduinoHttpClient”, finden Sie die Bibliothek ArduinoHttpClient von Arduino und klicken Sie auf Install.
  • Suchen Sie nach “ezButton”, finden Sie die Bibliothek ezButton von ArduinoGetStarted und klicken Sie auf Install.
  • Kopieren Sie den obigen Code und fügen Sie ihn in den Editor der Arduino IDE ein.
  • Ändern Sie WIFI_SSID und WIFI_PASSWORD in Ihren eigenen WLAN-Namen und Ihr Passwort.
  • Ändern Sie LATITUDE, LONGITUDE, LOCATION_NAME und TIMEZONE auf Ihre eigene Stadt. Siehe den Abschnitt unten.
  • Klicken Sie in der Arduino IDE auf die Schaltfläche Upload, um den Code auf den Arduino UNO R4 hochzuladen.
  • Öffnen Sie den seriellen Monitor in der Arduino IDE und stellen Sie die Baudrate auf 9600 ein.
  • Warten Sie einige Sekunden. Das Display zeigt zuerst den Startbildschirm, dann den WLAN-Bildschirm und dann das Wetter.
  • Drücken Sie den Taster, um zur nächsten Seite zu wechseln.
  • Überprüfen Sie das Ergebnis im seriellen 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

Wie man den Taster verwendet

Ein Taster erledigt drei Aufgaben. Der Unterschied liegt darin, wie lange Sie ihn gedrückt halten:

  • Kurzer Druck (unter 0,6 Sekunden): zur nächsten Seite wechseln. Nach der letzten Seite geht es zurück zur Startseite.
  • Langer Druck (0,6 bis 2 Sekunden): sofort neue Daten von der Wetter-API anfordern.
  • Sehr langer Druck (über 2 Sekunden): zurück zur Startseite springen.

Der Code verwendet für den Taster die Bibliothek ezButton. Die Bibliothek entfernt das Kontaktprellen selbstständig, sodass ein Druck nie doppelt gezählt wird, und der Sketch muss nur messen, wie lange der Taster gedrückt gehalten wurde.

ezButton button(PIN_BUTTON);

Legen Sie in setup() die Entprellzeit fest. Die Bibliothek hat den Pin bereits beim Erstellen des Objekts auf INPUT_PULLUP gesetzt, daher ist keine pinMode()-Zeile nötig.

button.setDebounceTime(40);

Rufen Sie in der Schleife zuerst button.loop() auf und lesen Sie dann die beiden Ereignisse aus.

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 }

Wenn Sie mehr darüber erfahren möchten, lesen Sie das Tutorial Arduino UNO R4 - Taster langer Druck, kurzer Druck.

Wie man den Ort ändert

Der Code ist auf Seoul, Südkorea, eingestellt. Um Ihre eigene Stadt anzuzeigen, ändern Sie vier Zeilen am Anfang des Codes:

#define LATITUDE 37.5665 #define LONGITUDE 126.9780 #define LOCATION_NAME "SEOUL" #define TIMEZONE "Asia%2FSeoul"
  • LATITUDE und LONGITUDE: die Position Ihrer Stadt. Sie finden diese Werte auf jeder Kartenwebsite.
  • LOCATION_NAME: der kurze Name, der auf dem Display angezeigt wird. Halten Sie ihn kurz, damit er auf den runden Bildschirm passt.
  • TIMEZONE: der Name der Zeitzone, zum Beispiel Europe/Berlin oder America/New_York. Das Zeichen / muss als %2F geschrieben werden, da der Name in eine Webadresse eingefügt wird. So wird aus Europe/Berlin Europe%2FBerlin.

※ Notiz:

Die Temperatur wird in Celsius und die Windgeschwindigkeit in km/h angegeben. Um dies zu ändern, bearbeiten Sie die Teile temperature_unit und wind_speed_unit in der Funktion getWeatherPath(). Verwenden Sie zum Beispiel fahrenheit und mph.

Code-Erklärung

Lesen Sie die Erklärungen in den Kommentaren des Quellcodes zu jeder Zeile!

Fehlerbehebung

  • Das Display bleibt schwarz. Prüfen Sie die Leitungen SCL und SDA. Beim Arduino UNO R4 müssen sie mit D13 und D11 verbunden sein. Diese beiden Pins können nicht auf andere Pins verlegt werden.
  • Der Bildschirm zeigt "NO WIFI MODULE". Sie verwenden einen Arduino UNO R4 Minima. Dieses Projekt benötigt den Arduino UNO R4 WiFi.
  • Der Bildschirm zeigt "WIFI ERROR". Prüfen Sie den WLAN-Namen und das Passwort. Der Arduino UNO R4 WiFi funktioniert nur mit 2,4-GHz-Netzwerken, nicht mit 5 GHz.
  • Der serielle Monitor zeigt einen HTTP-Fehler. Aktualisieren Sie die WLAN-Firmware Ihres Arduino UNO R4 WiFi. Eine alte Firmware enthält möglicherweise nicht das richtige Root-Zertifikat. Siehe wie man die Firmware des Arduino UNO R4 WiFi aktualisiert.
  • Die Uhrzeit ist falsch. Die Uhr verwendet die Zeit, die mit der Wetterantwort geliefert wird. Prüfen Sie, ob der Wert von TIMEZONE zu Ihrer Stadt passt.

Video Tutorial

Wir erwägen die Erstellung von Video-Tutorials. Wenn Sie Video-Tutorials für wichtig halten, abonnieren Sie bitte unseren YouTube-Kanal , um uns zu motivieren, die Videos zu erstellen.

Sehen Sie sich unten die Schritt-für-Schritt-Videoanleitung zu diesem Projekt an.

※ UNSERE NACHRICHTEN

  • Sie können gerne den Link zu diesem Tutorial teilen. Bitte verwenden Sie jedoch unsere Inhalte nicht auf anderen Websites. Wir haben viel Mühe und Zeit in die Erstellung der Inhalte investiert, bitte respektieren Sie unsere Arbeit!