« Module ESP32-écran avec données météo publiques » : différence entre les versions
Aller à la navigation
Aller à la recherche
Aucun résumé des modifications |
Aucun résumé des modifications |
||
| (3 versions intermédiaires par le même utilisateur non affichées) | |||
| Ligne 1 : | Ligne 1 : | ||
Une version plus générique de l'utilisation du module écran ESP32, avec des données météo publiques (Openweathermap). | 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. | On peut faire fonctionner ce code n'importe où en reparamétrant le wifi. | ||
| Ligne 447 : | Ligne 448 : | ||
tft.drawString("Reinit Config Meteo\nConnect Wi-Fi ESP32_Config_API\nPuis 192.168.4.1", | tft.drawString("Reinit Config Meteo\nConnect Wi-Fi ESP32_Config_API\nPuis 192.168.4.1", | ||
screenW/2, screenH/2, 2); | screenW/2, screenH/2, 2); | ||
} | |||
} | |||
</pre> | |||
Code avec 3 écrans, météo, heure et réveil. | |||
<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" | |||
#define PAGE_MAIN 0 | |||
#define PAGE_Heure 1 | |||
#define PAGE_METEO 2 | |||
#define PAGE_Reveil 3 | |||
// ====== 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; | |||
// Variables pour le réveil | |||
int heureReveil = 7; // Heure par défaut | |||
int minuteReveil = 0; // Minute par défaut | |||
bool reveilActif = false; | |||
char timeStrReveil[6]; // Format "HH:MM" pour l'affichage du réveil | |||
// ====== 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", true}, | |||
{"Wi-Fi", "OFF", false}, | |||
{"Lever Soleil", "--", false}, | |||
{"Coucher Soleil", "--", false}, | |||
{"Humid.", "", false}, | |||
{"Pression", "", false}, | |||
{"", "", false}, | |||
{"Ciel", "", false}, | |||
{"Case11", "", false}, | |||
{"Case12", "", false}, | |||
{"Reveil", "", true}, // La valeur sera mise à jour avec timeStrReveil | |||
{"Case14", "", false}, | |||
{"Reset Wifi", "Tap", true}, | |||
{"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 = "https://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(){ | |||
tft.fillScreen(TFT_BLACK); | |||
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]); | |||
} | |||
} | |||
} | |||
void drawPageHeure() { | |||
tft.fillScreen(TFT_BLACK); | |||
tft.setTextColor(TFT_WHITE, TFT_BLACK); | |||
tft.setTextDatum(MC_DATUM); | |||
tft.drawString("Page Heure", screenW/2, 20, 2); | |||
// Affiche l'heure (case "Heure") au centre de la page | |||
tft.setTextColor(TFT_CYAN, TFT_BLACK); | |||
tft.drawString(boxes[2].value, screenW/2, 80, 8); | |||
// Dessine un bouton "Retour" (rectangle + texte) | |||
tft.drawRect(10, screenH - 30, 80, 25, TFT_WHITE); | |||
tft.setTextDatum(TL_DATUM); | |||
tft.drawString("<- Retour", 15, screenH - 28, 2); | |||
} | |||
// ====== 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); | |||
// Charger les paramètres du réveil | |||
chargerReveil(); | |||
// Use this calibration code in setup(): | |||
uint16_t calData[5] = { 308, 3404, 466, 3148, 3 }; | |||
tft.setTouch(calData); | |||
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(); | |||
} | |||
// ====== Fonctions Réveil ====== | |||
void sauvegarderReveil() { | |||
preferences.begin("reveil", false); | |||
preferences.putInt("heure", heureReveil); | |||
preferences.putInt("minute", minuteReveil); | |||
preferences.putBool("actif", reveilActif); | |||
preferences.end(); | |||
} | |||
void chargerReveil() { | |||
preferences.begin("reveil", true); | |||
heureReveil = preferences.getInt("heure", 7); | |||
minuteReveil = preferences.getInt("minute", 0); | |||
reveilActif = preferences.getBool("actif", false); | |||
preferences.end(); | |||
// Formater l'heure du réveil | |||
snprintf(timeStrReveil, sizeof(timeStrReveil), "%02d:%02d", heureReveil, minuteReveil); | |||
} | |||
void drawPageReveil() { | |||
tft.fillScreen(TFT_BLACK); | |||
tft.setTextColor(TFT_WHITE, TFT_BLACK); | |||
tft.setTextDatum(MC_DATUM); | |||
tft.drawString("Reglage Reveil", screenW/2, 20, 2); | |||
// Affichage de l'heure de réveil | |||
char timeStr[6]; | |||
snprintf(timeStr, sizeof(timeStr), "%02d:%02d", heureReveil, minuteReveil); | |||
tft.setTextColor(TFT_CYAN, TFT_BLACK); | |||
tft.drawString(timeStr, screenW/2, 140, 8); | |||
// Boutons + et - pour les heures | |||
tft.drawRect(screenW/2 - 170, 150, 40, 40, TFT_WHITE); | |||
tft.drawString("-", screenW/2 - 150, 170, 4); | |||
tft.drawRect(screenW/2 - 170, 100, 40, 40, TFT_WHITE); | |||
tft.drawString("+", screenW/2 - 150, 120, 4); | |||
// Boutons + et - pour les minutes | |||
tft.drawRect(screenW/2 + 130, 150, 40, 40, TFT_WHITE); | |||
tft.drawString("-", screenW/2 + 150, 170, 4); | |||
tft.drawRect(screenW/2 + 130, 100, 40, 40, TFT_WHITE); | |||
tft.drawString("+", screenW/2 + 150, 120, 4); | |||
// Bouton ON/OFF | |||
tft.drawRect(screenW/2 - 50, 240, 100, 40, TFT_WHITE); | |||
tft.setTextColor(reveilActif ? TFT_GREEN : TFT_RED, TFT_BLACK); | |||
tft.drawString(reveilActif ? "ON" : "OFF", screenW/2, 260, 4); | |||
// Bouton Retour | |||
tft.drawRect(10, screenH - 30, 80, 25, TFT_WHITE); | |||
tft.setTextColor(TFT_WHITE, TFT_BLACK); | |||
tft.setTextDatum(TL_DATUM); | |||
tft.drawString("<- Retour", 15, screenH - 28, 2); | |||
} | |||
// ====== Loop ====== | |||
unsigned long lastUpdate = 0; | |||
unsigned long lastWeather = 0; | |||
unsigned long lastMessageDisplay = 0; | |||
void loop() { | |||
server.handleClient(); | |||
static int currentPage = PAGE_MAIN; | |||
static bool pageChanged = true; // Flag pour éviter les redessins inutiles | |||
// Vérification du réveil toutes les minutes | |||
if (reveilActif) { | |||
struct tm timeinfo; | |||
if (getLocalTime(&timeinfo)) { | |||
if (timeinfo.tm_hour == heureReveil && timeinfo.tm_min == minuteReveil) { | |||
// Ajouter ici votre code pour l'alarme (buzzer, LED, etc.) | |||
} | |||
} | |||
} | |||
// === Gestion de l'affichage === | |||
if (pageChanged) { | |||
switch (currentPage) { | |||
case PAGE_MAIN: | |||
drawAllBoxes(); | |||
break; | |||
case PAGE_Heure: | |||
drawPageHeure(); | |||
break; | |||
case PAGE_Reveil: | |||
drawPageReveil(); | |||
break; | |||
} | |||
pageChanged = false; | |||
} | |||
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); | |||
pageChanged = true; // <--- AJOUT : force le rafraîchissement | |||
} | |||
} | |||
// Météo toutes les 10 min | |||
if (millis() - lastWeather > 600000) { | |||
lastWeather = millis(); | |||
Serial.println("[Loop] Mise à jour météo..."); | |||
updateWeather(); | |||
pageChanged = true; // <--- AJOUT : force le rafraîchissement | |||
} | |||
// === Détection tactile === | |||
uint16_t tx, ty; | |||
if (tft.getTouch(&tx, &ty)) { | |||
// Vérifie d'abord le bouton "Retour" sur la page Heure | |||
if (currentPage == PAGE_Heure && tx >= 10 && tx <= 90 && ty >= (screenH - 30) && ty <= screenH) { | |||
currentPage = PAGE_MAIN; | |||
pageChanged = true; | |||
delay(200); | |||
} | |||
// Sinon, vérifie les autres boxes | |||
else { | |||
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) { | |||
// Action pour Heure | |||
if (strcmp(boxes[index].label, "Heure") == 0) { | |||
currentPage = PAGE_Heure; | |||
pageChanged = true; | |||
delay(200); | |||
} | |||
// Action pour Reveil | |||
else if (strcmp(boxes[index].label, "Reveil") == 0) { | |||
currentPage = PAGE_Reveil; | |||
pageChanged = true; | |||
delay(200); | |||
} | |||
// Action pour "Reset Meteo" -> efface la config meteo puis redémarre l'ESP | |||
else if (strcmp(boxes[index].label, "Reset Meteo") == 0) { | |||
Serial.println("[Touch] Réinitialisation OpenWeather ! -- redemarrage"); | |||
preferences.begin("openweather", false); | |||
preferences.clear(); | |||
preferences.end(); | |||
weatherConfigured = false; | |||
tft.fillScreen(TFT_BLACK); | |||
tft.setTextColor(TFT_RED, TFT_BLACK); | |||
tft.setTextDatum(MC_DATUM); | |||
tft.drawString("Redémmarrage de l'ESP pour reconfigurer la météo", | |||
screenW/2, screenH/2, 2); | |||
delay(2000); | |||
ESP.restart(); | |||
} | |||
// Action pour "Reset Wifi" -> efface la config wifi puis redémarre l'ESP | |||
else if (strcmp(boxes[index].label, "Reset Wifi") == 0) { | |||
Serial.println("[Touch] Réinitialisation Wifi ! -- redemarrage"); | |||
preferences.begin("wifi", false); | |||
preferences.clear(); | |||
preferences.end(); | |||
weatherConfigured = false; | |||
tft.fillScreen(TFT_BLACK); | |||
tft.setTextColor(TFT_RED, TFT_BLACK); | |||
tft.setTextDatum(MC_DATUM); | |||
tft.drawString("Redémmarrage de l'ESP pour reconfigurer le wifi", | |||
screenW/2, screenH/2, 2); | |||
delay(2000); | |||
ESP.restart(); | |||
} | |||
} | |||
} | |||
} | |||
if (currentPage == PAGE_Reveil) { | |||
if (tx >= 10 && tx <= 90 && ty >= (screenH - 30) && ty <= screenH) { | |||
// Bouton Retour | |||
currentPage = PAGE_MAIN; | |||
pageChanged = true; | |||
sauvegarderReveil(); | |||
} else if (tx >= screenW/2 - 170 && tx <= screenW/2 - 130) { | |||
if (ty >= 100 && ty <= 140) { | |||
// Augmenter les heures | |||
heureReveil = (heureReveil + 1) % 24; | |||
snprintf(timeStrReveil, sizeof(timeStrReveil), "%02d:%02d", heureReveil, minuteReveil); | |||
strcpy(boxes[12].value, timeStrReveil); // Mise à jour de la case réveil | |||
pageChanged = true; | |||
} else if (ty >= 150 && ty <= 190) { | |||
// Diminuer les heures | |||
heureReveil = (heureReveil - 1 + 24) % 24; | |||
snprintf(timeStrReveil, sizeof(timeStrReveil), "%02d:%02d", heureReveil, minuteReveil); | |||
strcpy(boxes[12].value, timeStrReveil); // Mise à jour de la case réveil | |||
pageChanged = true; | |||
} | |||
} else if (tx >= screenW/2 + 130 && tx <= screenW/2 + 170) { | |||
if (ty >= 100 && ty <= 140) { | |||
// Augmenter les minutes | |||
minuteReveil = (minuteReveil + 1) % 60; | |||
snprintf(timeStrReveil, sizeof(timeStrReveil), "%02d:%02d", heureReveil, minuteReveil); | |||
strcpy(boxes[12].value, timeStrReveil); // Mise à jour de la case réveil | |||
pageChanged = true; | |||
} else if (ty >= 150 && ty <= 190) { | |||
// Diminuer les minutes | |||
minuteReveil = (minuteReveil - 1 + 60) % 60; | |||
snprintf(timeStrReveil, sizeof(timeStrReveil), "%02d:%02d", heureReveil, minuteReveil); | |||
strcpy(boxes[12].value, timeStrReveil); // Mise à jour de la case réveil | |||
pageChanged = true; | |||
} | |||
} else if (tx >= screenW/2 - 50 && tx <= screenW/2 + 50 && | |||
ty >= 240 && ty <= 280) { | |||
// Toggle ON/OFF | |||
reveilActif = !reveilActif; | |||
pageChanged = true; | |||
} | |||
} | |||
delay(200); // anti-rebond | |||
} | |||
} | |||
else if (!weatherConfigured) { | |||
// Affiche le message de réinitialisation seulement toutes les 5 secondes | |||
if (millis() - lastMessageDisplay > 5000) { | |||
lastMessageDisplay = millis(); | |||
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> | ||
Dernière version du 28 octobre 2025 à 21:56
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);
}
}
Code avec 3 écrans, météo, heure et réveil.
#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"
#define PAGE_MAIN 0
#define PAGE_Heure 1
#define PAGE_METEO 2
#define PAGE_Reveil 3
// ====== 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;
// Variables pour le réveil
int heureReveil = 7; // Heure par défaut
int minuteReveil = 0; // Minute par défaut
bool reveilActif = false;
char timeStrReveil[6]; // Format "HH:MM" pour l'affichage du réveil
// ====== 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", true},
{"Wi-Fi", "OFF", false},
{"Lever Soleil", "--", false},
{"Coucher Soleil", "--", false},
{"Humid.", "", false},
{"Pression", "", false},
{"", "", false},
{"Ciel", "", false},
{"Case11", "", false},
{"Case12", "", false},
{"Reveil", "", true}, // La valeur sera mise à jour avec timeStrReveil
{"Case14", "", false},
{"Reset Wifi", "Tap", true},
{"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 = "https://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(){
tft.fillScreen(TFT_BLACK);
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]);
}
}
}
void drawPageHeure() {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextDatum(MC_DATUM);
tft.drawString("Page Heure", screenW/2, 20, 2);
// Affiche l'heure (case "Heure") au centre de la page
tft.setTextColor(TFT_CYAN, TFT_BLACK);
tft.drawString(boxes[2].value, screenW/2, 80, 8);
// Dessine un bouton "Retour" (rectangle + texte)
tft.drawRect(10, screenH - 30, 80, 25, TFT_WHITE);
tft.setTextDatum(TL_DATUM);
tft.drawString("<- Retour", 15, screenH - 28, 2);
}
// ====== 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);
// Charger les paramètres du réveil
chargerReveil();
// Use this calibration code in setup():
uint16_t calData[5] = { 308, 3404, 466, 3148, 3 };
tft.setTouch(calData);
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();
}
// ====== Fonctions Réveil ======
void sauvegarderReveil() {
preferences.begin("reveil", false);
preferences.putInt("heure", heureReveil);
preferences.putInt("minute", minuteReveil);
preferences.putBool("actif", reveilActif);
preferences.end();
}
void chargerReveil() {
preferences.begin("reveil", true);
heureReveil = preferences.getInt("heure", 7);
minuteReveil = preferences.getInt("minute", 0);
reveilActif = preferences.getBool("actif", false);
preferences.end();
// Formater l'heure du réveil
snprintf(timeStrReveil, sizeof(timeStrReveil), "%02d:%02d", heureReveil, minuteReveil);
}
void drawPageReveil() {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextDatum(MC_DATUM);
tft.drawString("Reglage Reveil", screenW/2, 20, 2);
// Affichage de l'heure de réveil
char timeStr[6];
snprintf(timeStr, sizeof(timeStr), "%02d:%02d", heureReveil, minuteReveil);
tft.setTextColor(TFT_CYAN, TFT_BLACK);
tft.drawString(timeStr, screenW/2, 140, 8);
// Boutons + et - pour les heures
tft.drawRect(screenW/2 - 170, 150, 40, 40, TFT_WHITE);
tft.drawString("-", screenW/2 - 150, 170, 4);
tft.drawRect(screenW/2 - 170, 100, 40, 40, TFT_WHITE);
tft.drawString("+", screenW/2 - 150, 120, 4);
// Boutons + et - pour les minutes
tft.drawRect(screenW/2 + 130, 150, 40, 40, TFT_WHITE);
tft.drawString("-", screenW/2 + 150, 170, 4);
tft.drawRect(screenW/2 + 130, 100, 40, 40, TFT_WHITE);
tft.drawString("+", screenW/2 + 150, 120, 4);
// Bouton ON/OFF
tft.drawRect(screenW/2 - 50, 240, 100, 40, TFT_WHITE);
tft.setTextColor(reveilActif ? TFT_GREEN : TFT_RED, TFT_BLACK);
tft.drawString(reveilActif ? "ON" : "OFF", screenW/2, 260, 4);
// Bouton Retour
tft.drawRect(10, screenH - 30, 80, 25, TFT_WHITE);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextDatum(TL_DATUM);
tft.drawString("<- Retour", 15, screenH - 28, 2);
}
// ====== Loop ======
unsigned long lastUpdate = 0;
unsigned long lastWeather = 0;
unsigned long lastMessageDisplay = 0;
void loop() {
server.handleClient();
static int currentPage = PAGE_MAIN;
static bool pageChanged = true; // Flag pour éviter les redessins inutiles
// Vérification du réveil toutes les minutes
if (reveilActif) {
struct tm timeinfo;
if (getLocalTime(&timeinfo)) {
if (timeinfo.tm_hour == heureReveil && timeinfo.tm_min == minuteReveil) {
// Ajouter ici votre code pour l'alarme (buzzer, LED, etc.)
}
}
}
// === Gestion de l'affichage ===
if (pageChanged) {
switch (currentPage) {
case PAGE_MAIN:
drawAllBoxes();
break;
case PAGE_Heure:
drawPageHeure();
break;
case PAGE_Reveil:
drawPageReveil();
break;
}
pageChanged = false;
}
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);
pageChanged = true; // <--- AJOUT : force le rafraîchissement
}
}
// Météo toutes les 10 min
if (millis() - lastWeather > 600000) {
lastWeather = millis();
Serial.println("[Loop] Mise à jour météo...");
updateWeather();
pageChanged = true; // <--- AJOUT : force le rafraîchissement
}
// === Détection tactile ===
uint16_t tx, ty;
if (tft.getTouch(&tx, &ty)) {
// Vérifie d'abord le bouton "Retour" sur la page Heure
if (currentPage == PAGE_Heure && tx >= 10 && tx <= 90 && ty >= (screenH - 30) && ty <= screenH) {
currentPage = PAGE_MAIN;
pageChanged = true;
delay(200);
}
// Sinon, vérifie les autres boxes
else {
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) {
// Action pour Heure
if (strcmp(boxes[index].label, "Heure") == 0) {
currentPage = PAGE_Heure;
pageChanged = true;
delay(200);
}
// Action pour Reveil
else if (strcmp(boxes[index].label, "Reveil") == 0) {
currentPage = PAGE_Reveil;
pageChanged = true;
delay(200);
}
// Action pour "Reset Meteo" -> efface la config meteo puis redémarre l'ESP
else if (strcmp(boxes[index].label, "Reset Meteo") == 0) {
Serial.println("[Touch] Réinitialisation OpenWeather ! -- redemarrage");
preferences.begin("openweather", false);
preferences.clear();
preferences.end();
weatherConfigured = false;
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_RED, TFT_BLACK);
tft.setTextDatum(MC_DATUM);
tft.drawString("Redémmarrage de l'ESP pour reconfigurer la météo",
screenW/2, screenH/2, 2);
delay(2000);
ESP.restart();
}
// Action pour "Reset Wifi" -> efface la config wifi puis redémarre l'ESP
else if (strcmp(boxes[index].label, "Reset Wifi") == 0) {
Serial.println("[Touch] Réinitialisation Wifi ! -- redemarrage");
preferences.begin("wifi", false);
preferences.clear();
preferences.end();
weatherConfigured = false;
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_RED, TFT_BLACK);
tft.setTextDatum(MC_DATUM);
tft.drawString("Redémmarrage de l'ESP pour reconfigurer le wifi",
screenW/2, screenH/2, 2);
delay(2000);
ESP.restart();
}
}
}
}
if (currentPage == PAGE_Reveil) {
if (tx >= 10 && tx <= 90 && ty >= (screenH - 30) && ty <= screenH) {
// Bouton Retour
currentPage = PAGE_MAIN;
pageChanged = true;
sauvegarderReveil();
} else if (tx >= screenW/2 - 170 && tx <= screenW/2 - 130) {
if (ty >= 100 && ty <= 140) {
// Augmenter les heures
heureReveil = (heureReveil + 1) % 24;
snprintf(timeStrReveil, sizeof(timeStrReveil), "%02d:%02d", heureReveil, minuteReveil);
strcpy(boxes[12].value, timeStrReveil); // Mise à jour de la case réveil
pageChanged = true;
} else if (ty >= 150 && ty <= 190) {
// Diminuer les heures
heureReveil = (heureReveil - 1 + 24) % 24;
snprintf(timeStrReveil, sizeof(timeStrReveil), "%02d:%02d", heureReveil, minuteReveil);
strcpy(boxes[12].value, timeStrReveil); // Mise à jour de la case réveil
pageChanged = true;
}
} else if (tx >= screenW/2 + 130 && tx <= screenW/2 + 170) {
if (ty >= 100 && ty <= 140) {
// Augmenter les minutes
minuteReveil = (minuteReveil + 1) % 60;
snprintf(timeStrReveil, sizeof(timeStrReveil), "%02d:%02d", heureReveil, minuteReveil);
strcpy(boxes[12].value, timeStrReveil); // Mise à jour de la case réveil
pageChanged = true;
} else if (ty >= 150 && ty <= 190) {
// Diminuer les minutes
minuteReveil = (minuteReveil - 1 + 60) % 60;
snprintf(timeStrReveil, sizeof(timeStrReveil), "%02d:%02d", heureReveil, minuteReveil);
strcpy(boxes[12].value, timeStrReveil); // Mise à jour de la case réveil
pageChanged = true;
}
} else if (tx >= screenW/2 - 50 && tx <= screenW/2 + 50 &&
ty >= 240 && ty <= 280) {
// Toggle ON/OFF
reveilActif = !reveilActif;
pageChanged = true;
}
}
delay(200); // anti-rebond
}
}
else if (!weatherConfigured) {
// Affiche le message de réinitialisation seulement toutes les 5 secondes
if (millis() - lastMessageDisplay > 5000) {
lastMessageDisplay = millis();
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);
}
}
}
}