Arduino UNO R4 - Station Météo Intelligente

Dans ce guide, nous allons apprendre à construire une station météo intelligente avec l'Arduino UNO R4 WiFi et un écran LCD TFT rond de 1,28 pouce. La station récupère la météo en direct depuis Internet. Elle n'a besoin d'aucun capteur de température ou d'humidité. En détail, nous allons apprendre :

Station météo intelligente Arduino UNO R4 avec écran LCD TFT rond

Matériel Requis

1×Arduino UNO R4 WiFi
1×Alternativement: DIYables STEM V4 IoT
1×Alternativement: DIYables STEM V4B IoT
1×Câble USB Type-A vers Type-C (pour PC USB-A)
1×Câble USB Type-C vers Type-C (pour PC USB-C)
1×1.28 Inch Round Circular TFT LCD Display Module
1×Push Button
1×Button Module Alternativement:
1×Plaque d'essai
1×Fils de connexion

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

1×Recommandé: Shield à bornier à vis pour Arduino Uno R4
1×Recommandé: Sensors/Servo Expansion Shield for Arduino UNO R4
1×Recommandé: Shield plaque d'essai pour Arduino Uno R4
1×Recommandé: Boîtier pour Arduino Uno R4
1×Recommandé: Répartiteur d'alimentation pour Arduino Uno R4
1×Recommandé: Kit plaque de base prototypage et plaque d'essai pour Arduino Uno

Ou vous pouvez acheter les kits suivants:

1×Kit de Démarrage DIYables STEM V4 IoT (Arduino inclus)
1×Kit de Démarrage DIYables STEM V4B IoT (Arduino inclus)
1×Kit de Capteurs DIYables (18 capteurs/écrans)
Divulgation : Certains des liens fournis dans cette section sont des liens affiliés Amazon. Nous pouvons recevoir une commission pour tout achat effectué via ces liens, sans coût supplémentaire pour vous. Nous vous remercions de votre soutien.
Arduino Uno R4 WiFi Compatible Boards

※ Note:

Ce projet nécessite l'Arduino UNO R4 WiFi. L'Arduino UNO R4 Minima n'a pas de WiFi, il ne peut donc pas accéder à l'API météo.

À propos de la Station Météo Intelligente

Une station météo normale utilise un capteur pour mesurer l'air qui l'entoure. Celle-ci fonctionne autrement. L'Arduino UNO R4 interroge un service météo sur Internet, et le service renvoie la météo de votre ville. Grâce à cela, la station peut aussi afficher des informations qu'un petit capteur ne peut pas mesurer, comme la probabilité de pluie pour les prochaines heures et les prévisions pour les prochains jours.

La station comporte huit pages. Un seul bouton-poussoir permet de passer de l'une à l'autre :

  • HOME - l'icône météo, la température, la description du ciel et une horloge.
  • TEMP - la température et la température ressentie ("feels like").
  • HUMIDITY - l'humidité sous forme d'anneau et de nombre.
  • WIND - la vitesse du vent et la direction du vent sous forme de boussole.
  • RAIN - la probabilité de pluie avec une petite animation de pluie.
  • NEXT HOURS - la température et la probabilité de pluie pour les prochaines heures.
  • FORECAST - la température la plus haute et la plus basse pour les 3 prochains jours.
  • SYSTEM - l'état du WiFi, le niveau du signal et l'état de l'API.

L'Arduino UNO R4 demande une nouvelle météo toutes les 10 minutes. Entre deux mises à jour, l'horloge de la page d'accueil continue de tourner.

À propos de l'API Météo Open-Meteo

Open-Meteo est un service météo gratuit. Il convient bien à une petite carte comme l'Arduino UNO R4 :

  • Pas de clé API : vous n'avez pas besoin de vous inscrire ni de coller une clé dans le code.
  • Gratuit pour un usage personnel : aucun coût pour un petit projet comme celui-ci.
  • JSON simple : la réponse est un court texte JSON que l'Arduino UNO R4 peut lire.
  • N'importe quel lieu : vous choisissez le lieu avec une latitude et une longitude.

L'Arduino UNO R4 envoie une requête HTTPS et reçoit une réponse d'environ 1,8 Ko. La réponse contient la météo actuelle, celle des 12 prochaines heures et celle des 3 prochains jours.

※ Note:

La réponse contient aussi l'heure locale du lieu demandé. Le code prend l'heure à partir de là, donc la station affiche une horloge sans client NTP et sans module RTC.

Schéma de Câblage

L'écran LCD TFT rond de 1,28 pouce utilise les broches SPI matérielles de l'Arduino UNO R4. Ces broches sont fixes : D13 est l'horloge et D11 est la ligne de données.

Broche LCD TFT Arduino UNO R4 Description
VCC 5V Alimentation
GND GND Masse
SCL D13 Horloge SPI (broche fixe)
SDA D11 MOSI SPI (broche fixe)
DC D9 Données/Commande
CS D10 Chip Select
RST D8 Réinitialisation

Pour le bouton, vous pouvez choisir l'un des deux composants. Les deux fonctionnent de la même façon dans le projet. Une seule ligne de code est différente.

Câblage avec un Bouton-Poussoir

Un bouton-poussoir a deux broches et aucune résistance. Il relie D2 à GND lorsque vous appuyez dessus. L'Arduino UNO R4 active sa propre résistance de rappel interne, vous n'avez donc besoin d'ajouter aucune résistance.

Schéma de câblage station météo intelligente Arduino UNO R4 avec bouton-poussoir

Cette image a été créée avec Fritzing. Cliquez pour agrandir l'image.

Broche du bouton Arduino UNO R4 Description
Broche 1 D2 Entrée du bouton avec résistance de rappel interne
Broche 2 GND Masse

Voici ce que le code utilise par défaut :

ezButton button(PIN_BUTTON);

Câblage avec un Module Bouton

Un module bouton a trois broches et sa propre résistance sur la carte. Il a besoin d'une alimentation, et sa broche OUT va à D2. Le module donne LOW lorsque vous n'appuyez pas, et HIGH lorsque vous appuyez.

Schéma de câblage station météo intelligente Arduino UNO R4 avec module bouton

Cette image a été créée avec Fritzing. Cliquez pour agrandir l'image.

Voir Comment alimenter l'Arduino UNO R4..

Broche du module bouton Arduino UNO R4 Description
VCC 5V Alimentation
GND GND Masse
OUT D2 Signal du bouton, LOW au repos, HIGH lorsqu'il est pressé

Le module a déjà une résistance, donc la résistance interne ne doit pas être utilisée. Modifiez une ligne dans le code :

ezButton button(PIN_BUTTON, EXTERNAL_PULLDOWN);

Comment Programmer la Station Météo Intelligente

Incluez les cinq bibliothèques.

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

Créez l'objet écran avec les broches de réinitialisation, de données/commande et de sélection de puce.

DIYables_TFT_GC9A01_Round tft(8, 9, 10);

Définissez le nom de votre WiFi, le mot de passe de votre WiFi et votre lieu.

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

Démarrez l'écran et le WiFi.

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

Ouvrez une connexion HTTPS vers le serveur météo. Sur l'Arduino UNO R4, WiFiSSLClient gère la partie HTTPS. Les certificats racine sont déjà présents dans le firmware WiFi, vous n'avez donc pas besoin de coller un certificat dans le code.

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

Envoyez la requête et lisez la réponse.

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

Lisez la réponse JSON. L'Arduino UNO R4 n'a que 32 Ko de RAM, donc un filtre est utilisé. Le filtre conserve les trois blocs dont le code a besoin et supprime le reste.

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

Extrayez une valeur de la réponse.

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

Dessinez-la sur l'écran rond.

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

Code Arduino UNO R4 - Station Météo Intelligente

/* * Ce code Arduino UNO R4 a été développé par newbiely.fr * Ce code Arduino UNO R4 est mis à disposition du public sans aucune restriction. * Pour des instructions complètes et des schémas de câblage, veuillez visiter: * https://newbiely.fr/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(); }

Étapes Rapides

Suivez ces instructions étape par étape :

  • Si c'est la première fois que vous utilisez l'Arduino Uno R4 WiFi, reportez-vous au tutoriel sur Arduino UNO R4 - Installation du logiciel..
  • Câblez les composants selon le schéma fourni.
  • Connectez la carte Arduino Uno R4 à votre ordinateur à l'aide d'un câble USB.
  • Lancez l'IDE Arduino sur votre ordinateur.
  • Sélectionnez la carte Arduino Uno R4 appropriée (Arduino Uno R4 WiFi) et le port COM.
  • Accédez à l'icône Libraries dans la barre de gauche de l'IDE Arduino.
  • Recherchez “DIYables TFT Round”, puis trouvez la bibliothèque DIYables_TFT_Round de DIYables.
  • Cliquez sur le bouton Install pour installer la bibliothèque.
Installation de la bibliothèque LCD TFT pour Arduino UNO R4
  • Il peut vous être demandé d'installer des dépendances de bibliothèque supplémentaires.
  • Cliquez sur le bouton Install All pour installer toutes les dépendances de bibliothèque.
Installation des dépendances LCD TFT pour Arduino UNO R4
  • Recherchez “ArduinoJson”, puis trouvez la bibliothèque ArduinoJson de Benoit Blanchon et cliquez sur Install.
  • Recherchez “ArduinoHttpClient”, puis trouvez la bibliothèque ArduinoHttpClient d'Arduino et cliquez sur Install.
  • Recherchez “ezButton”, puis trouvez la bibliothèque ezButton d'ArduinoGetStarted et cliquez sur Install.
  • Copiez le code ci-dessus et collez-le dans l'éditeur de l'IDE Arduino.
  • Remplacez WIFI_SSID et WIFI_PASSWORD par le nom et le mot de passe de votre propre WiFi.
  • Remplacez LATITUDE, LONGITUDE, LOCATION_NAME et TIMEZONE par les valeurs de votre propre ville. Voir la section ci-dessous.
  • Cliquez sur le bouton Upload dans l'IDE Arduino pour téléverser le code sur l'Arduino UNO R4.
  • Ouvrez le Moniteur Série dans l'IDE Arduino et réglez le débit en bauds sur 9600.
  • Attendez quelques secondes. L'écran affiche l'écran de démarrage, puis l'écran WiFi, puis la météo.
  • Appuyez sur le bouton pour passer à la page suivante.
  • Consultez le résultat dans le Moniteur Série.
∞
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

Comment Utiliser le Bouton

Un seul bouton remplit trois fonctions. La différence tient à la durée de l'appui :

  • Appui court (moins de 0,6 seconde) : passer à la page suivante. Après la dernière page, on revient à la page d'accueil.
  • Appui long (0,6 à 2 secondes) : demander immédiatement de nouvelles données à l'API météo.
  • Appui très long (plus de 2 secondes) : revenir directement à la page d'accueil.

Le code utilise la bibliothèque ezButton pour le bouton. La bibliothèque supprime d'elle-même les rebonds de contact, donc un appui n'est jamais compté deux fois, et le sketch n'a qu'à mesurer combien de temps le bouton a été maintenu enfoncé.

ezButton button(PIN_BUTTON);

Dans setup(), définissez le temps d'anti-rebond. La bibliothèque a déjà configuré la broche en INPUT_PULLUP lors de la création de l'objet, donc aucune ligne pinMode() n'est nécessaire.

button.setDebounceTime(40);

Dans la boucle, appelez d'abord button.loop(), puis lisez les deux événements.

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 }

Si vous voulez en savoir plus à ce sujet, consultez le tutoriel Arduino UNO R4 - Bouton - Appui long et court..

Comment Changer le Lieu

Le code est configuré pour Séoul, en Corée du Sud. Pour afficher votre propre ville, modifiez quatre lignes en haut du code :

#define LATITUDE 37.5665 #define LONGITUDE 126.9780 #define LOCATION_NAME "SEOUL" #define TIMEZONE "Asia%2FSeoul"
  • LATITUDE et LONGITUDE : la position de votre ville. Vous pouvez les trouver sur n'importe quel site de cartographie.
  • LOCATION_NAME : le nom court affiché à l'écran. Gardez-le court pour qu'il tienne sur l'écran rond.
  • TIMEZONE : le nom du fuseau horaire, par exemple Europe/Berlin ou America/New_York. Le caractère / doit être écrit %2F, car le nom est inséré dans une adresse web. Ainsi, Europe/Berlin devient Europe%2FBerlin.

※ Note:

La température est en Celsius et la vitesse du vent en km/h. Pour les changer, modifiez les parties temperature_unit et wind_speed_unit dans la fonction getWeatherPath(). Par exemple, utilisez fahrenheit et mph.

Explication du Code

Consultez les explications données dans les commentaires du code source pour chaque ligne !

Dépannage

  • L'écran reste noir. Vérifiez les fils SCL et SDA. Sur l'Arduino UNO R4, ils doivent aller à D13 et D11. Ces deux broches ne peuvent pas être déplacées vers d'autres broches.
  • L'écran affiche "NO WIFI MODULE". Vous utilisez un Arduino UNO R4 Minima. Ce projet nécessite l'Arduino UNO R4 WiFi.
  • L'écran affiche "WIFI ERROR". Vérifiez le nom du WiFi et le mot de passe. L'Arduino UNO R4 WiFi ne fonctionne qu'avec les réseaux 2,4 GHz, pas 5 GHz.
  • Le Moniteur Série affiche une erreur HTTP. Mettez à jour le firmware WiFi de votre Arduino UNO R4 WiFi. Un ancien firmware peut ne pas contenir le bon certificat racine. Consultez Comment mettre à jour le firmware sur les cartes Arduino Uno R4 WiFi/DIYables STEM V4 IoT..
  • L'horloge est incorrecte. L'horloge utilise l'heure fournie avec la réponse météo. Vérifiez que la valeur TIMEZONE correspond à votre ville.

Vidéo

Regardez ci-dessous la vidéo étape par étape de ce projet.

Commentaires

※ NOS MESSAGES

  • N'hésitez pas à partager le lien de ce tutoriel. Cependant, veuillez ne pas utiliser notre contenu sur d'autres sites web. Nous avons investi beaucoup d'efforts et de temps pour créer ce contenu, veuillez respecter notre travail !