354 lines
12 KiB
C++
354 lines
12 KiB
C++
#include <Arduino.h>
|
|
#include <ESP8266WiFi.h>
|
|
#include <WiFiManager.h>
|
|
#include <EEPROM.h>
|
|
#include <ESP8266WebServer.h>
|
|
#include <ArduinoJson.h>
|
|
#include <Bounce2.h>
|
|
#include <NTPClient.h>
|
|
#include <time.h>
|
|
#include <WiFiUdp.h>
|
|
|
|
// Définition des broches
|
|
#define RELAY_PIN 12
|
|
#define BUTTON_PIN 0
|
|
#define LED_PIN 13
|
|
|
|
// EEPROM
|
|
#define EEPROM_SIZE 8 // Taille nécessaire pour stocker 4 octets pour la durée et 4 octets pour l'heure d'allumage
|
|
|
|
// Configuration WiFi
|
|
const char* ssid = "WifiHome2";
|
|
const char* password = "louca2212";
|
|
|
|
// Variables globales
|
|
Bounce buttonDebouncer;
|
|
ESP8266WebServer server(80); // Serveur Web sur le port 80
|
|
WiFiUDP ntpUDP;
|
|
NTPClient timeClient(ntpUDP, "pool.ntp.org", 3600); // Serveur NTP avec décalage UTC+1
|
|
bool relayState = false;
|
|
unsigned long relayTimer = 0;
|
|
unsigned long relayDuration = 60000; // 60 secondes par défaut
|
|
bool saveSuccess = false; // Indique si l'EEPROM a été sauvegardée
|
|
int relayStartHour = 0; // Heure d'allumage (par défaut 00)
|
|
int relayStartMinute = 0; // Minute d'allumage (par défaut 00)
|
|
unsigned long lastNTPUpdate = 0; // Temps de la dernière mise à jour NTP (en ms)
|
|
|
|
|
|
// Fonction pour mettre à jour le client NTP toutes les 30 secondes
|
|
void updateNTP() {
|
|
unsigned long currentMillis = millis();
|
|
if (currentMillis - lastNTPUpdate >= 30000 || lastNTPUpdate == 0) {
|
|
timeClient.update();
|
|
lastNTPUpdate = currentMillis;
|
|
unsigned long rawTime = timeClient.getEpochTime();
|
|
Serial.println("Mise à jour NTP effectuée. Temps brut reçu : " + String(rawTime));
|
|
Serial.println("Temps formaté (UTC) : " + String(rawTime % 86400 / 3600) + ":" + String(rawTime % 3600 / 60));
|
|
|
|
|
|
}
|
|
}
|
|
|
|
// Fonction pour basculer l'état du relais
|
|
void toggleRelay() {
|
|
relayState = !relayState;
|
|
digitalWrite(RELAY_PIN, relayState);
|
|
if (relayState) {
|
|
relayTimer = millis();
|
|
Serial.println("Relais activé");
|
|
} else {
|
|
Serial.println("Relais désactivé");
|
|
}
|
|
}
|
|
|
|
// Fonction pour sauvegarder les paramètres dans l'EEPROM
|
|
void saveSettings() {
|
|
EEPROM.write(0, relayDuration / 60000); // Sauvegarde la durée en minutes
|
|
EEPROM.write(1, relayStartHour); // Sauvegarde l'heure
|
|
EEPROM.write(2, relayStartMinute); // Sauvegarde les minutes
|
|
EEPROM.commit();
|
|
Serial.println("Paramètres sauvegardés dans l'EEPROM");
|
|
}
|
|
|
|
// Fonction pour charger les paramètres depuis l'EEPROM
|
|
void loadSettings() {
|
|
relayDuration = EEPROM.read(0) * 60000; // Chargement de la durée en ms
|
|
relayStartHour = EEPROM.read(1); // Chargement de l'heure
|
|
relayStartMinute = EEPROM.read(2); // Chargement des minutes
|
|
|
|
// Vérifier les valeurs invalides et appliquer des valeurs par défaut
|
|
if (relayDuration == 0 || relayDuration > 10800000) relayDuration = 60000; // Valeur par défaut 60 secondes
|
|
if (relayStartHour < 0 || relayStartHour >= 24) relayStartHour = 0; // Heure par défaut 00
|
|
if (relayStartMinute < 0 || relayStartMinute >= 60) relayStartMinute = 0; // Minute par défaut 00
|
|
|
|
Serial.println("Paramètres chargés :");
|
|
Serial.println("Durée du relais : " + String(relayDuration / 60000) + " minutes");
|
|
Serial.println("Heure d'allumage : " + String(relayStartHour) + ":" + String(relayStartMinute));
|
|
}
|
|
|
|
// Fonction pour mettre à jour la LED en fonction de l'état
|
|
void updateLED() {
|
|
if (WiFi.status() != WL_CONNECTED) {
|
|
digitalWrite(LED_PIN, LOW); // LED éteinte si déconnecté
|
|
} else if (relayState) {
|
|
digitalWrite(LED_PIN, LOW); // LED allumée si relais activé
|
|
} else {
|
|
digitalWrite(LED_PIN, HIGH); // LED éteinte si relais désactivé
|
|
}
|
|
}
|
|
|
|
// Gestion des clics du bouton
|
|
void handleButton() {
|
|
if (buttonDebouncer.fell()) {
|
|
Serial.println("Bouton pressé");
|
|
toggleRelay(); // Bascule le relais
|
|
}
|
|
}
|
|
|
|
// Fonction pour obtenir l'heure au format HH:MM
|
|
String getFormattedTime() {
|
|
unsigned long rawTime = timeClient.getEpochTime();
|
|
unsigned long adjustedTime = rawTime ;
|
|
int hours = (adjustedTime % 86400L) / 3600; // Heures
|
|
int minutes = (adjustedTime % 3600) / 60; // Minutes
|
|
|
|
char buffer[6];
|
|
snprintf(buffer, sizeof(buffer), "%02d:%02d", hours, minutes);
|
|
return String(buffer);
|
|
}
|
|
|
|
// Calculer le délai en minutes avant le prochain allumage
|
|
int calculateMinutesUntilNextStart() {
|
|
unsigned long currentTime = timeClient.getEpochTime();
|
|
int currentHour = (currentTime % 86400L) / 3600;
|
|
int currentMinute = (currentTime % 3600) / 60;
|
|
|
|
// Convertir l'heure actuelle et l'heure de démarrage en minutes depuis minuit
|
|
int currentTotalMinutes = currentHour * 60 + currentMinute;
|
|
int startTotalMinutes = relayStartHour * 60 + relayStartMinute;
|
|
|
|
// Calculer la différence
|
|
int delay = startTotalMinutes - currentTotalMinutes;
|
|
if (delay < 0) {
|
|
delay += 1440; // Ajouter 24 heures si le délai est négatif (allumage le lendemain)
|
|
}
|
|
return delay;
|
|
}
|
|
|
|
int calculateMinutesUntilNextStop() {
|
|
int delayUntilStart = calculateMinutesUntilNextStart();
|
|
if (relayState) {
|
|
// Si le relais est déjà activé, calcule le temps restant jusqu'à son extinction
|
|
unsigned long elapsedMillis = millis() - relayTimer;
|
|
return max(0L, (long)((relayDuration - elapsedMillis) / 60000));
|
|
} else {
|
|
// Si le relais est désactivé, ajoute la durée d'activation au délai jusqu'au prochain allumage
|
|
return delayUntilStart + (relayDuration / 60000);
|
|
}
|
|
}
|
|
|
|
// Page principale
|
|
void handleMainPage() {
|
|
updateNTP(); // Mettre à jour le client NTP si nécessaire
|
|
String currentTime = getFormattedTime();
|
|
int nextStart = calculateMinutesUntilNextStart();
|
|
int nextStop = calculateMinutesUntilNextStop();
|
|
|
|
String relayStateColor = relayState ? "green" : "red"; // Détermine la couleur du texte
|
|
String relayStateText = relayState ? "ACTIVÉ" : "DÉSACTIVÉ";
|
|
|
|
String html = R"rawliteral(
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Relais Sonoff</title>
|
|
<style>
|
|
body { font-family: Arial, sans-serif; text-align: center; margin-top: 50px; }
|
|
button { padding: 10px 20px; font-size: 16px; margin: 20px; cursor: pointer; }
|
|
#relayState { font-weight: bold; color: RELAY_COLOR; }
|
|
</style>
|
|
<script>
|
|
setInterval(() => {
|
|
location.reload(); // Rafraîchit la page toutes les secondes
|
|
}, 10000);
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<h1>Relais Sonoff</h1>
|
|
<p>Heure : <span id="currentTime">TIME</span></p>
|
|
<p><strong>État du relais :</strong> <span id="relayState">RELAY_STATE</span></p>
|
|
<p>Délai avant prochain allumage du relais : <span id="nextStart">NEXT_START</span> minutes</p>
|
|
<p>Délai avant prochaine extinction du relais : <span id="nextStop">NEXT_STOP</span> minutes</p>
|
|
<p><button onclick="location.href='/settings'">Paramètres</button></p>
|
|
</body>
|
|
</html>
|
|
)rawliteral";
|
|
|
|
html.replace("TIME", currentTime);
|
|
html.replace("RELAY_STATE", relayStateText);
|
|
html.replace("RELAY_COLOR", relayStateColor);
|
|
html.replace("NEXT_START", String(nextStart));
|
|
html.replace("NEXT_STOP", String(nextStop));
|
|
server.send(200, "text/html", html);
|
|
}
|
|
|
|
// Page des paramètres
|
|
void handleSettingsPage() {
|
|
// Charger les paramètres depuis l'EEPROM
|
|
loadSettings();
|
|
|
|
// Générer la page HTML
|
|
String html = R"rawliteral(
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Paramètres Relais</title>
|
|
<style>
|
|
body { font-family: Arial, sans-serif; text-align: center; margin-top: 50px; }
|
|
button { padding: 10px 20px; font-size: 16px; margin: 20px; cursor: pointer; }
|
|
</style>
|
|
<script>
|
|
function saveSettings() {
|
|
const duration = parseInt(document.getElementById('duration').value);
|
|
const startHour = parseInt(document.getElementById('startHour').value);
|
|
const startMinute = parseInt(document.getElementById('startMinute').value);
|
|
|
|
if (isNaN(duration) || duration < 0 || duration > 180) {
|
|
alert('Erreur : Durée invalide (entre 0 et 180 minutes).');
|
|
return;
|
|
}
|
|
if (isNaN(startHour) || startHour < 0 || startHour > 23) {
|
|
alert('Erreur : Heure invalide (entre 0 et 23).');
|
|
return;
|
|
}
|
|
if (isNaN(startMinute) || startMinute < 0 || startMinute > 59) {
|
|
alert('Erreur : Minute invalide (entre 0 et 59).');
|
|
return;
|
|
}
|
|
|
|
fetch('/save', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
relayDuration: duration,
|
|
relayStartHour: startHour,
|
|
relayStartMinute: startMinute
|
|
})
|
|
}).then(() => {
|
|
location.href = '/';
|
|
});
|
|
}
|
|
|
|
function goBack() {
|
|
location.href = '/';
|
|
}
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<h1>Paramètres Relais</h1>
|
|
<p>Durée (minutes) : <input type="text" id="duration" value="DURATION"></p>
|
|
<p>Heure d'allumage : <input type="text" id="startHour" value="HOUR" size="2"> : <input type="text" id="startMinute" value="MINUTE" size="2"></p>
|
|
<p><button onclick="saveSettings()">Enregistrer</button></p>
|
|
<p><button onclick="goBack()">Retour</button></p>
|
|
</body>
|
|
</html>
|
|
)rawliteral";
|
|
|
|
html.replace("DURATION", String(relayDuration / 60000));
|
|
html.replace("HOUR", String(relayStartHour));
|
|
html.replace("MINUTE", String(relayStartMinute));
|
|
server.send(200, "text/html", html);
|
|
}
|
|
|
|
// Sauvegarde des paramètres
|
|
void handleSaveSettings() {
|
|
JsonDocument doc;
|
|
DeserializationError error = deserializeJson(doc, server.arg("plain"));
|
|
if (error) {
|
|
server.send(400, "text/plain", "Erreur JSON");
|
|
return;
|
|
}
|
|
|
|
relayDuration = doc["relayDuration"].as<unsigned long>() * 60000;
|
|
relayStartHour = doc["relayStartHour"].as<int>();
|
|
relayStartMinute = doc["relayStartMinute"].as<int>();
|
|
|
|
saveSettings();
|
|
server.send(200, "text/plain", "Paramètres sauvegardés.");
|
|
}
|
|
|
|
|
|
|
|
// Initialisation
|
|
void setup() {
|
|
Serial.begin(115200);
|
|
Serial.println("Démarrage du programme...");
|
|
|
|
// Initialisation des broches
|
|
pinMode(RELAY_PIN, OUTPUT);
|
|
pinMode(BUTTON_PIN, INPUT_PULLUP);
|
|
pinMode(LED_PIN, OUTPUT);
|
|
digitalWrite(RELAY_PIN, LOW);
|
|
digitalWrite(LED_PIN, LOW);
|
|
Serial.println("Broches configurées");
|
|
|
|
// Configuration du bouton avec Bounce2
|
|
buttonDebouncer.attach(BUTTON_PIN);
|
|
buttonDebouncer.interval(25);
|
|
Serial.println("Bouton configuré");
|
|
|
|
// Connexion WiFi avec WiFiManager
|
|
Serial.println("Connexion au WiFi...");
|
|
WiFiManager wifiManager;
|
|
wifiManager.autoConnect("SonoffRelay", "password");
|
|
Serial.println("Connecté au WiFi : " + String(WiFi.SSID()) + " - IP : " + WiFi.localIP().toString());
|
|
|
|
EEPROM.begin(EEPROM_SIZE);
|
|
loadSettings();
|
|
|
|
|
|
// Initialisation du client NTP
|
|
timeClient.begin();
|
|
updateNTP();
|
|
|
|
server.on("/", handleMainPage);
|
|
server.on("/settings", handleSettingsPage);
|
|
server.on("/save", HTTP_POST, handleSaveSettings);
|
|
|
|
server.begin();
|
|
Serial.println("Serveur web démarré");
|
|
}
|
|
|
|
// Boucle principale
|
|
void loop() {
|
|
buttonDebouncer.update();
|
|
handleButton();
|
|
|
|
// Mise à jour de l'heure NTP
|
|
timeClient.update();
|
|
|
|
// Gère les requêtes HTTP des clients
|
|
server.handleClient();
|
|
|
|
// Obtenir l'heure actuelle
|
|
unsigned long rawTime = timeClient.getEpochTime();
|
|
int currentHour = (rawTime % 86400L) / 3600; // Heures actuelles
|
|
int currentMinute = (rawTime % 3600) / 60; // Minutes actuelles
|
|
|
|
// Vérifier si l'heure actuelle correspond à l'heure d'allumage
|
|
if (!relayState && currentHour == relayStartHour && currentMinute == relayStartMinute) {
|
|
Serial.println("Allumage automatique du relais à l'heure programmée");
|
|
toggleRelay(); // Allume le relais
|
|
}
|
|
|
|
// Gestion du relais (arrêt automatique après la durée spécifiée)
|
|
if (relayState && millis() - relayTimer >= relayDuration) {
|
|
Serial.println("Extinction automatique du relais après la durée configurée");
|
|
toggleRelay(); // Éteint le relais
|
|
}
|
|
|
|
// Mise à jour de la LED
|
|
updateLED();
|
|
} |