« 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 |
||
| Ligne 514 : | Ligne 514 : | ||
{"Reveil", "", true}, // La valeur sera mise à jour avec timeStrReveil | {"Reveil", "", true}, // La valeur sera mise à jour avec timeStrReveil | ||
{"Case14", "", false}, | {"Case14", "", false}, | ||
{" | {"Reset Wifi", "Tap", true}, | ||
{"Reset Meteo", "Tap", true} // ← Case tactile | {"Reset Meteo", "Tap", true} // ← Case tactile | ||
| Ligne 1 007 : | Ligne 1 007 : | ||
delay(200); | delay(200); | ||
} | } | ||
// Action pour "Reset Meteo" | // Action pour "Reset Meteo" -> efface la config meteo puis redémarre l'ESP | ||
else if (strcmp(boxes[index].label, "Reset Meteo") == 0) { | else if (strcmp(boxes[index].label, "Reset Meteo") == 0) { | ||
Serial.println("[Touch] Réinitialisation OpenWeather !"); | Serial.println("[Touch] Réinitialisation OpenWeather ! -- redemarrage"); | ||
preferences.begin("openweather", false); | preferences.begin("openweather", false); | ||
preferences.clear(); | preferences.clear(); | ||
| Ligne 1 017 : | Ligne 1 017 : | ||
tft.setTextColor(TFT_RED, TFT_BLACK); | tft.setTextColor(TFT_RED, TFT_BLACK); | ||
tft.setTextDatum(MC_DATUM); | tft.setTextDatum(MC_DATUM); | ||
tft.drawString(" | tft.drawString("Redémmarrage de l'ESP pour reconfigurer la météo", | ||
screenW/2, screenH/2, 2); | 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(); | |||
} | } | ||
} | } | ||
| Ligne 1 077 : | Ligne 1 093 : | ||
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); | ||
} | |||
} | } | ||
} | } | ||
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);
}
}
}
}