« Module ESP32-écran avec données météo publiques » : différence entre les versions
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}, | ||
{" | {"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 | server.send(200, "text/html", "<h2>Wi-Fi sauvegarde ok, redemarrage...</h2>"); | ||
Serial.println("[Wi-Fi] Formulaire reçu -> | 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 | server.send(200, "text/html", "<h2>Config meteo sauvegarde ok, redemarrage...</h2>"); | ||
Serial.println("[OpenWeather] Formulaire reçu -> | 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);
}
}