« Module ESP32-écran avec données météo publiques » : différence entre les versions

De Wiki de Mémoire Vive
Aller à la navigation Aller à la recherche
Page créée avec « Une version plus générique de l'utilisation du module écran ESP32, avec des données météo publiques (Openweathermap). On peut faire fonctionner ce code n'importe où en reparamétrant le wifi. <pre> #include <WiFi.h> #include <WebServer.h> #include <Preferences.h> #include <TFT_eSPI.h> #include <TimeLib.h> #include <HTTPClient.h> #include <ArduinoJson.h> #include "time.h" // ====== Variables globales ====== WebServer server(80); Preferences preferences;... »
 
Aucun résumé des modifications
Ligne 34 : Ligne 34 :
   const char* label;
   const char* label;
   char value[32];
   char value[32];
  bool clickable;
};
};


Box boxes[rows * cols] = {
Box boxes[rows * cols] = {
   {"Temp Locale", "--"},
   {"Temp Locale", "--", false},
   {"Vent", "--"},
   {"Vent", "--", false},
   {"Heure", "00:00"},
   {"Heure", "00:00", false},
   {"Wi-Fi", "OFF"},
   {"Wi-Fi", "OFF", false},
   {"Lever Soleil", "--"},
   {"Lever Soleil", "--", false},
   {"Coucher Soleil", "--"},
   {"Coucher Soleil", "--", false},
   {"Humid.", ""},
   {"Humid.", "", false},
   {"Pression", ""},
   {"Pression", "", false},
   {"", ""},
   {"", "", false},
   {"Ciel", ""},
   {"Ciel", "", false},
   {"Case11", ""},
   {"Case11", "", false},
   {"Case12", ""},
   {"Case12", "", false},
   {"Case13", ""},
   {"Case13", "", false},
   {"Case14", ""},
   {"Case14", "", false},
   {"Case15", ""},
   {"Case15", "", false},
   {"Case16", ""}
   {"Reset Meteo", "Tap", true}   // ← Case tactile
 
};
};


Ligne 121 : Ligne 123 :
       password = server.arg("password");
       password = server.arg("password");
       saveWiFi(ssid, password);
       saveWiFi(ssid, password);
       server.send(200, "text/html", "<h2>Wi-Fi sauvegardé, redémarrage...</h2>");
       server.send(200, "text/html", "<h2>Wi-Fi sauvegarde ok, redemarrage...</h2>");
       Serial.println("[Wi-Fi] Formulaire reçu -> redémarrage");
       Serial.println("[Wi-Fi] Formulaire reçu -> redemarrage");
       delay(2000);
       delay(2000);
       ESP.restart();
       ESP.restart();
Ligne 186 : Ligne 188 :
       lon = server.arg("lon");
       lon = server.arg("lon");
       saveWeatherConfig(apiKey, lat, lon);
       saveWeatherConfig(apiKey, lat, lon);
       server.send(200, "text/html", "<h2>Config météo sauvegardée, redémarrage...</h2>");
       server.send(200, "text/html", "<h2>Config meteo sauvegarde ok, redemarrage...</h2>");
       Serial.println("[OpenWeather] Formulaire reçu -> redémarrage");
       Serial.println("[OpenWeather] Formulaire reçu -> redemarrage");
       delay(2000);
       delay(2000);
       ESP.restart();
       ESP.restart();
Ligne 280 : Ligne 282 :
// ====== TFT ======
// ====== TFT ======
void drawBox(int x, int y, int w, int h, Box b){
void drawBox(int x, int y, int w, int h, Box b){
   tft.drawRect(x, y, w, h, TFT_WHITE);
   tft.drawRect(x, y, w, h, b.clickable ? TFT_GREEN : TFT_WHITE);


   // ===== Label =====
   // ===== Label =====
Ligne 304 : Ligne 306 :
   int cy = y + (h - th) / 2 + th / 4;
   int cy = y + (h - th) / 2 + th / 4;
   tft.setCursor(cx, cy);
   tft.setCursor(cx, cy);
   tft.setTextColor(TFT_CYAN, TFT_BLACK);
 
   // === Couleur spéciale pour Wi-Fi ===
  if (strcmp(b.label, "Wi-Fi") == 0) {
    if (strcmp(b.value, "ON") == 0)
      tft.setTextColor(TFT_GREEN, TFT_BLACK);
    else
      tft.setTextColor(TFT_RED, TFT_BLACK);
  } else {
    tft.setTextColor(TFT_CYAN, TFT_BLACK);
  }
 
   tft.print(b.value);
   tft.print(b.value);
}
}
Ligne 372 : Ligne 384 :
unsigned long lastWeather = 0;
unsigned long lastWeather = 0;


void loop(){
void loop() {
   server.handleClient(); // Toujours gérer le serveur
   server.handleClient(); // Toujours gérer le serveur


   if(wifiConfigured && weatherConfigured){
   if (wifiConfigured && weatherConfigured) {
     // Heure toutes les 10s
     // Heure toutes les 10s
     if(millis() - lastUpdate > 10000){
     if (millis() - lastUpdate > 10000) {
       lastUpdate = millis();
       lastUpdate = millis();
       struct tm timeinfo;
       struct tm timeinfo;
       if(getLocalTime(&timeinfo)){
       if (getLocalTime(&timeinfo)) {
         snprintf(boxes[2].value, sizeof(boxes[2].value), "%02d:%02d", timeinfo.tm_hour, timeinfo.tm_min);
         snprintf(boxes[2].value, sizeof(boxes[2].value), "%02d:%02d", timeinfo.tm_hour, timeinfo.tm_min);
       }
       }
Ligne 386 : Ligne 398 :


     // Météo toutes les 10 min
     // Météo toutes les 10 min
     if(millis() - lastWeather > 600000){
     if (millis() - lastWeather > 600000) {
       lastWeather = millis();
       lastWeather = millis();
       Serial.println("[Loop] Mise à jour météo...");
       Serial.println("[Loop] Mise à jour météo...");
Ligne 393 : Ligne 405 :


     drawAllBoxes();
     drawAllBoxes();
    // === Détection tactile ===
    uint16_t tx, ty;
    if (tft.getTouch(&tx, &ty)) {
      for (int r = 0; r < rows; r++) {
        for (int c = 0; c < cols; c++) {
          int index = r * cols + c;
          int boxX = c * (boxW + 10) + 5;
          int boxY = r * (boxH + 10) + 5;
          if (tx >= boxX && tx <= boxX + boxW &&
              ty >= boxY && ty <= boxY + boxH &&
              boxes[index].clickable) {
            if (strcmp(boxes[index].label, "Reset Meteo") == 0) {
              Serial.println("[Touch] Réinitialisation OpenWeather !");
              preferences.begin("openweather", false);
              preferences.clear();
              preferences.end();
              weatherConfigured = false; // la météo n'est plus configurée
              // Affiche message TFT
              tft.fillScreen(TFT_BLACK);
              tft.setTextColor(TFT_RED, TFT_BLACK);
              tft.setTextDatum(MC_DATUM); // centre le texte
              tft.drawString("Reinit Config Meteo\nConnect Wi-Fi ESP32_Config_API\nPuis 192.168.4.1",
                            screenW/2, screenH/2, 2);
            }
          }
        }
      }
    }
    delay(200); // anti-rebond
  }
  else if (!weatherConfigured) {
    // Affiche le message de réinitialisation en continu si besoin
    tft.fillScreen(TFT_BLACK);
    tft.setTextColor(TFT_RED, TFT_BLACK);
    tft.setTextDatum(MC_DATUM);
    tft.drawString("Reinit Config Meteo\nConnect Wi-Fi ESP32_Config_API\nPuis 192.168.4.1",
                  screenW/2, screenH/2, 2);
   }
   }
}
}
</pre>
</pre>

Version du 5 septembre 2025 à 18:38

Une version plus générique de l'utilisation du module écran ESP32, avec des données météo publiques (Openweathermap).

On peut faire fonctionner ce code n'importe où en reparamétrant le wifi.

#include <WiFi.h>
#include <WebServer.h>
#include <Preferences.h>
#include <TFT_eSPI.h>
#include <TimeLib.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include "time.h"

// ====== Variables globales ======
WebServer server(80);
Preferences preferences;
TFT_eSPI tft = TFT_eSPI();

String ssid, password;
String apiKey, lat, lon;
bool wifiConfigured = false;
bool weatherConfigured = false;

// ====== Dashboard ======
const int screenW = 480;
const int screenH = 320;
const int cols = 4;
const int rows = 4;
const int boxW = screenW / cols - 10;
const int boxH = screenH / rows - 10;

struct Box {
  const char* label;
  char value[32];
  bool clickable;
};

Box boxes[rows * cols] = {
  {"Temp Locale", "--", false},
  {"Vent", "--", false},
  {"Heure", "00:00", false},
  {"Wi-Fi", "OFF", false},
  {"Lever Soleil", "--", false},
  {"Coucher Soleil", "--", false},
  {"Humid.", "", false},
  {"Pression", "", false},
  {"", "", false},
  {"Ciel", "", false},
  {"Case11", "", false},
  {"Case12", "", false},
  {"Case13", "", false},
  {"Case14", "", false},
  {"Case15", "", false},
  {"Reset Meteo", "Tap", true}   // ← Case tactile

};

// ====== HTML Wi-Fi ======
const char* htmlWiFiForm = R"rawliteral(
<!DOCTYPE html>
<html>
<body>
<h2>Configurer le Wi-Fi</h2>
<form action="/save" method="POST">
  SSID:<br>
  <input type="text" name="ssid"><br>
  Mot de passe:<br>
  <input type="password" name="password"><br><br>
  <input type="submit" value="Sauvegarder">
</form>
</body>
</html>
)rawliteral";

// ====== HTML OpenWeather ======
const char* htmlApiForm = R"rawliteral(
<!DOCTYPE html>
<html>
<body>
<h2>Configurer OpenWeather</h2>
<form action="/saveApi" method="POST">
  API Key:<br>
  <input type="text" name="apikey"><br>
  Latitude:<br>
  <input type="text" name="lat"><br>
  Longitude:<br>
  <input type="text" name="lon"><br><br>
  <input type="submit" value="Sauvegarder">
</form>
</body>
</html>
)rawliteral";

// ====== Fonctions Wi-Fi ======
void saveWiFi(String s, String p){
  preferences.begin("wifi", false);
  preferences.putString("ssid", s);
  preferences.putString("password", p);
  preferences.end();
  Serial.println("[Wi-Fi] Paramètres sauvegardés en flash");
}

bool loadWiFi(){
  preferences.begin("wifi", true);
  ssid = preferences.getString("ssid", "");
  password = preferences.getString("password", "");
  preferences.end();
  Serial.print("[Wi-Fi] Chargement depuis flash -> SSID: "); Serial.println(ssid);
  return ssid != "" && password != "";
}

void setupWiFiAP(){
  WiFi.softAP("ESP32_Config");
  Serial.println("[Wi-Fi] Point d’accès activé : ESP32_Config");
  Serial.println("[Wi-Fi] Connectez-vous à 192.168.4.1 pour configurer le Wi-Fi");

  server.on("/", [](){ server.send(200, "text/html", htmlWiFiForm); });

  server.on("/save", HTTP_POST, [](){
    if(server.hasArg("ssid") && server.hasArg("password")){
      ssid = server.arg("ssid");
      password = server.arg("password");
      saveWiFi(ssid, password);
      server.send(200, "text/html", "<h2>Wi-Fi sauvegarde ok, redemarrage...</h2>");
      Serial.println("[Wi-Fi] Formulaire reçu -> redemarrage");
      delay(2000);
      ESP.restart();
    }
  });

  server.begin();
  Serial.println("[Wi-Fi] Serveur Web démarré pour configuration");
}

void connectWiFi(){
  if(loadWiFi()){
    Serial.print("[Wi-Fi] Tentative de connexion à : "); Serial.println(ssid);
    WiFi.begin(ssid.c_str(), password.c_str());
    int tries = 0;
    while(WiFi.status() != WL_CONNECTED && tries < 20){
      delay(500);
      Serial.print(".");
      tries++;
    }
    if(WiFi.status() == WL_CONNECTED){
      Serial.println("\n[Wi-Fi] Connecté avec succès!");
      wifiConfigured = true;
      strcpy(boxes[3].value, "ON");
      return;
    }
    Serial.println("\n[Wi-Fi] Échec de connexion Wi-Fi");
  }
  setupWiFiAP();
}

// ====== Fonctions OpenWeather ======
void saveWeatherConfig(String key, String latitude, String longitude){
  preferences.begin("openweather", false);
  preferences.putString("apikey", key);
  preferences.putString("lat", latitude);
  preferences.putString("lon", longitude);
  preferences.end();
  Serial.println("[OpenWeather] Config sauvegardée en flash");
}

bool loadWeatherConfig(){
  preferences.begin("openweather", true);
  apiKey = preferences.getString("apikey", "");
  lat = preferences.getString("lat", "");
  lon = preferences.getString("lon", "");
  preferences.end();
  Serial.print("[OpenWeather] Chargement depuis flash -> API: "); Serial.println(apiKey);
  return apiKey != "" && lat != "" && lon != "";
}

void configWeather(){
  WiFi.softAP("ESP32_Config_API");
  Serial.println("[OpenWeather] Point d’accès activé : ESP32_Config_API");
  Serial.println("[OpenWeather] Connectez-vous à 192.168.4.1 pour configurer OpenWeather");

  server.on("/", [](){ server.send(200, "text/html", htmlApiForm); });

  server.on("/saveApi", HTTP_POST, [](){
    if(server.hasArg("apikey") && server.hasArg("lat") && server.hasArg("lon")){
      apiKey = server.arg("apikey");
      lat = server.arg("lat");
      lon = server.arg("lon");
      saveWeatherConfig(apiKey, lat, lon);
      server.send(200, "text/html", "<h2>Config meteo sauvegarde ok, redemarrage...</h2>");
      Serial.println("[OpenWeather] Formulaire reçu -> redemarrage");
      delay(2000);
      ESP.restart();
    }
  });

  server.begin();
  Serial.println("[OpenWeather] Serveur Web démarré pour configuration");
}

// ====== OpenWeather ======
void updateWeather() {
  if (WiFi.status() != WL_CONNECTED || apiKey == "") return;

  HTTPClient http;
  String url = "http://api.openweathermap.org/data/2.5/weather?lat=" + lat + "&lon=" + lon +
               "&units=metric&appid=" + apiKey + "&lang=fr";
  Serial.print("[OpenWeather] URL: "); Serial.println(url);

  http.begin(url);
  int httpCode = http.GET();
  Serial.print("[OpenWeather] HTTP code: "); Serial.println(httpCode);

  if (httpCode == 200) {
    String payload = http.getString();
    Serial.println("[OpenWeather] JSON complet:");
    Serial.println(payload);

    StaticJsonDocument<2048> doc;
    DeserializationError error = deserializeJson(doc, payload);
    if (error) {
      Serial.print("[OpenWeather] Erreur JSON: "); Serial.println(error.c_str());
      http.end();
      return;
    }

    // Données météo
    float temp       = doc["main"]["temp"];
    float feels_like = doc["main"]["feels_like"];
    float wind       = doc["wind"]["speed"];
    int windDeg      = doc["wind"]["deg"];
    int humidity     = doc["main"]["humidity"];
    int pressure     = doc["main"]["pressure"];
    long sunrise     = doc["sys"]["sunrise"];
    long sunset      = doc["sys"]["sunset"];
    const char* city = doc["name"];
    const char* weatherDesc = doc["weather"][0]["description"];

// Conversion lever/coucher soleil en heure locale (NTP + fuseau pris en compte)
struct tm ts;
char buf[10];

time_t sunrise_ts = (time_t)sunrise;
time_t sunset_ts  = (time_t)sunset;

// Lever du soleil
localtime_r(&sunrise_ts, &ts);
strftime(buf, sizeof(buf), "%H:%M", &ts);
snprintf(boxes[4].value, sizeof(boxes[4].value), "%s", buf);

// Coucher du soleil
localtime_r(&sunset_ts, &ts);
strftime(buf, sizeof(buf), "%H:%M", &ts);
snprintf(boxes[5].value, sizeof(boxes[5].value), "%s", buf);


    // Mettre à jour le tableau de bord
    snprintf(boxes[0].value, sizeof(boxes[0].value), "%.1f°C", temp);
    snprintf(boxes[1].value, sizeof(boxes[1].value), "%.1f m/s %d°", wind, windDeg);
    snprintf(boxes[6].value, sizeof(boxes[6].value), "%d%%", humidity);
    snprintf(boxes[7].value, sizeof(boxes[7].value), "%dhPa", pressure);
    snprintf(boxes[8].value, sizeof(boxes[8].value), "%s", city);
    snprintf(boxes[9].value, sizeof(boxes[9].value), "%s", weatherDesc);

    // Traces console
    Serial.print("[OpenWeather] Température: "); Serial.println(temp);
    Serial.print("[OpenWeather] Vent: "); Serial.print(wind); Serial.print(" m/s "); Serial.println(windDeg);
    Serial.print("[OpenWeather] Humidité: "); Serial.println(humidity);
    Serial.print("[OpenWeather] Pression: "); Serial.println(pressure);
    Serial.print("[OpenWeather] Lever soleil: "); Serial.println(boxes[4].value);
    Serial.print("[OpenWeather] Coucher soleil: "); Serial.println(boxes[5].value);
    Serial.print("[OpenWeather] Ville: "); Serial.println(city);
    Serial.print("[OpenWeather] Description: "); Serial.println(weatherDesc);

  } else {
    Serial.println("[OpenWeather] Erreur HTTP ou clé API invalide");
  }

  http.end();
}

// ====== TFT ======
void drawBox(int x, int y, int w, int h, Box b){
  tft.drawRect(x, y, w, h, b.clickable ? TFT_GREEN : TFT_WHITE);  

  // ===== Label =====
  tft.setTextFont(2);
  tft.setTextColor(TFT_YELLOW, TFT_BLACK);
  tft.setCursor(x + 5, y + 5);
  tft.print(b.label);

  // ===== Valeur =====
  int fontSize = 4; // taille initiale
  tft.setTextFont(fontSize);
  int tw = tft.textWidth(b.value);

  // Réduire la taille si le texte dépasse la largeur de la case
  while(tw > w - 10 && fontSize > 1){
    fontSize--;
    tft.setTextFont(fontSize);
    tw = tft.textWidth(b.value);
  }

  int th = tft.fontHeight();
  int cx = x + (w - tw) / 2;
  int cy = y + (h - th) / 2 + th / 4;
  tft.setCursor(cx, cy);

  // === Couleur spéciale pour Wi-Fi ===
  if (strcmp(b.label, "Wi-Fi") == 0) {
    if (strcmp(b.value, "ON") == 0)
      tft.setTextColor(TFT_GREEN, TFT_BLACK);
    else
      tft.setTextColor(TFT_RED, TFT_BLACK);
  } else {
    tft.setTextColor(TFT_CYAN, TFT_BLACK);
  }

  tft.print(b.value);
}

void drawAllBoxes(){
  for (int r = 0; r < rows; r++){
    for (int c = 0; c < cols; c++){
      int index = r * cols + c;
      int x = c * (boxW + 10) + 5;
      int y = r * (boxH + 10) + 5;
      drawBox(x, y, boxW, boxH, boxes[index]);
    }
  }
}

// ====== Setup ======
void setup(){
  Serial.begin(115200);
  delay(2000);
  Serial.println("=== ESP32 Tableau de bord météo ===");
  Serial.print("Nom du fichier source : "); Serial.println(__FILE__);

  tft.init();
  tft.setRotation(1);
  tft.fillScreen(TFT_BLACK);

  connectWiFi();

  if(!loadWeatherConfig()){
    configWeather();
  } else {
    weatherConfigured = true;
    Serial.println("[Setup] OpenWeather déjà configuré");
    // Appel immédiat pour récupérer la météo dès le démarrage
    Serial.println("[Setup] Premier appel OpenWeather immédiat");
    updateWeather();    
  }

// Config NTP et attente de synchronisation
configTime(3600, 3600, "pool.ntp.org", "time.nist.gov");
Serial.println("[Setup] NTP configuré, attente de synchronisation...");

struct tm timeinfo;
int retry = 0;
const int retry_max = 10;
while(!getLocalTime(&timeinfo) && retry < retry_max){
    Serial.println("[Setup] En attente de la synchronisation NTP...");
    delay(1000);
    retry++;
}

if(retry == retry_max){
    Serial.println("[Setup] Attention: NTP non synchronisé !");
} else {
    Serial.println("[Setup] Heure NTP synchronisée !");
    if(weatherConfigured){
        Serial.println("[Setup] Premier appel OpenWeather immédiat");
        updateWeather();
    }
}

  drawAllBoxes();
}

// ====== Loop ======
unsigned long lastUpdate = 0;
unsigned long lastWeather = 0;

void loop() {
  server.handleClient(); // Toujours gérer le serveur

  if (wifiConfigured && weatherConfigured) {
    // Heure toutes les 10s
    if (millis() - lastUpdate > 10000) {
      lastUpdate = millis();
      struct tm timeinfo;
      if (getLocalTime(&timeinfo)) {
        snprintf(boxes[2].value, sizeof(boxes[2].value), "%02d:%02d", timeinfo.tm_hour, timeinfo.tm_min);
      }
    }

    // Météo toutes les 10 min
    if (millis() - lastWeather > 600000) {
      lastWeather = millis();
      Serial.println("[Loop] Mise à jour météo...");
      updateWeather();
    }

    drawAllBoxes();

    // === Détection tactile ===
    uint16_t tx, ty;
    if (tft.getTouch(&tx, &ty)) {
      for (int r = 0; r < rows; r++) {
        for (int c = 0; c < cols; c++) {
          int index = r * cols + c;
          int boxX = c * (boxW + 10) + 5;
          int boxY = r * (boxH + 10) + 5;
          if (tx >= boxX && tx <= boxX + boxW &&
              ty >= boxY && ty <= boxY + boxH &&
              boxes[index].clickable) {

            if (strcmp(boxes[index].label, "Reset Meteo") == 0) {
              Serial.println("[Touch] Réinitialisation OpenWeather !");
              preferences.begin("openweather", false);
              preferences.clear();
              preferences.end();

              weatherConfigured = false; // la météo n'est plus configurée

              // Affiche message TFT
              tft.fillScreen(TFT_BLACK);
              tft.setTextColor(TFT_RED, TFT_BLACK);
              tft.setTextDatum(MC_DATUM); // centre le texte
              tft.drawString("Reinit Config Meteo\nConnect Wi-Fi ESP32_Config_API\nPuis 192.168.4.1", 
                             screenW/2, screenH/2, 2);
            }
          }
        }
      }
    }

    delay(200); // anti-rebond
  } 
  else if (!weatherConfigured) {
    // Affiche le message de réinitialisation en continu si besoin
    tft.fillScreen(TFT_BLACK);
    tft.setTextColor(TFT_RED, TFT_BLACK);
    tft.setTextDatum(MC_DATUM);
    tft.drawString("Reinit Config Meteo\nConnect Wi-Fi ESP32_Config_API\nPuis 192.168.4.1", 
                   screenW/2, screenH/2, 2);
  }
}