summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ecl_maison.ino481
-rw-r--r--io_declarations.h34
2 files changed, 515 insertions, 0 deletions
diff --git a/ecl_maison.ino b/ecl_maison.ino
new file mode 100644
index 0000000..c28112f
--- /dev/null
+++ b/ecl_maison.ino
@@ -0,0 +1,481 @@
+#include <SPI.h>
+#include <EEPROM.h>
+#include <Ethernet.h>
+#include <EthernetUdp.h>
+#include <WebServer.h>
+
+/**************
+ * PARAMETRES *
+ **************/
+// Declaration des lignes, boutons, IO et enums associés
+#include "io_declarations.h"
+// Doit etre dans un fichier séparé car les prototypes des fonctions du fichier .ino
+// sont générés automatiquement et placés au dessus des déclarations des enums sinon.
+
+// Activation debug (1 = oui, 0 = non)
+#define DEBUG_VIA_SYSLOG 0
+#define DEBUG_VIA_SERIAL 0
+
+// Définition de la sortie pour la LED de statut (13 = LED soudée sur l'arduino)
+#define STATUS_LED 13
+
+// Paramètres Syslog (ip/port du serveur syslog-ng)
+static uint8_t ip_debug[] = { 192, 168, 1, 10 };
+static uint16_t port_debug = 514;
+
+// Paramètres Ethernet (mac et ip locales pour serveur web)
+static uint8_t mac[] = { 0xFE, 0xED, 0xDE, 0xAD, 0xBE, 0xEF };
+static uint8_t ip[] = { 192, 168, 1, 178 };
+
+// Paramètres détection alea
+#define ALEA_NB_APPELS_MAX 20
+#define ALEA_FENETRE_MILLIS 10000 // 5 secondes
+#define ALEA_BLOQUAGE_MILLIS 60000 // 1 minute
+
+// Pamètres détection entrées
+#define DUREE_MIN_BOUTON_PRESSE 50 // millisecondes
+
+// Paramètres WebServer
+#define PREFIX ""
+#define NAMELEN 32
+#define VALUELEN 32
+
+// Paramètres persistance
+#define EEPROM_BASE_ADDR_DATA 15
+#define CHECKSUM_INIT 10
+#define CHECKSUM_INVALID 0xFF
+#define CONSIGNE_PAR_DEFAUT HIGH
+
+/******************
+ * OBJETS GLOBAUX *
+ *****************/
+WebServer webserver(PREFIX, 80); // Serveur Web
+EthernetUDP Udp; // Socket UDP pour debug Syslog
+
+static uint8_t etat_entrees[NUM_ENTREES]; // Etat actuel des entrées
+static unsigned long millis_precedent_front_descendant[NUM_ENTREES]; // Stockage timing pour filtrage des entrées
+static uint8_t consigne_sorties[NUM_SORTIES]; // Consignes à appliquer sur les sorties
+
+
+/************************
+ * ROUTINES PRINCIPALES *
+ ************************/
+// Initialisation de l'ensemble des composants
+void setup(){
+ Ethernet.begin(mac, ip);
+ log_init();
+ webserver_setup();
+
+ // Restaurer de l'EEPROM les consignes et positionner les bascules D des pins de sortie
+ // N'applique pas la tension de sortie immédiatement car le paramètrage des pin en OUTPUT n'est pas encore fait
+ charger_consignes();
+ appliquer_consignes(false);
+
+ // Paramétrer le mode de toutes les pins utilisées en entrée
+ for (int i=0; i<NUM_ENTREES; i++){
+ etat_entrees[i]=HIGH;
+ millis_precedent_front_descendant[i]=0;
+ pinMode(pin_entrees[i], INPUT_PULLUP);
+ }
+
+ // Paramétrer le mode de toutes les pins utilisées en sortie
+ for (int i=0; i<NUM_SORTIES; i++){
+ pinMode(pin_sorties[i], OUTPUT);
+ }
+
+ // Flasher 10 fois rapidement la LED de l'arduino pour signaler la fin de l'initialisation
+ digitalWrite(STATUS_LED, LOW);
+ pinMode(STATUS_LED, OUTPUT);
+ int v = HIGH;
+ for (int i=0; i<20; i++) {
+ digitalWrite(STATUS_LED, v);
+ delay(100);
+ v = !v;
+ }
+}
+
+// Boucle principale de fonctionnement
+void loop(){
+ // Traiter les requêtes web
+ webserver.processConnection();
+
+ // Lire les entrées
+ entree_t evenement_entree = lire_entrees();
+
+ // Si un évènement est détecté...
+ if ( evenement_entree != -1 ) {
+ // Modifier les consignes en conséquence
+ modifier_consignes(evenement_entree);
+ // Appliquer les consignes sur les sorties
+ appliquer_consignes(false);
+ }
+}
+
+
+/**************************************************
+ * Logique d'action des entrées sur les consignes *
+ **************************************************/
+void modifier_consignes(entree_t evenement_entree) {
+ switch ( evenement_entree ) {
+
+ case bt_test_arduino:
+ consigne_sorties[led_arduino] = (consigne_sorties[led_arduino]==HIGH)?LOW:HIGH;
+ break;
+
+ case bt_ext_cote_rue:
+ consigne_sorties[rl_ecl_ext_jardin] = (consigne_sorties[rl_ecl_ext_jardin]==HIGH)?LOW:HIGH;
+ break;
+
+ }
+}
+
+
+/***************
+ * SERVEUR WEB *
+ ***************/
+// Définition de l'opérateur << pour envoyer facilement des chaînes de caractères vers le client web
+template<class T> inline Print &operator <<(Print &obj, T arg) { obj.print(arg); return obj; }
+
+// Code gérant la réponse à la requête HTTP permettant d'afficher l'état des consignes
+void servlet_list(WebServer &server, WebServer::ConnectionType type, char *url_tail, bool tail_complete) {
+ server.httpSuccess();
+
+ server << "<!DOCTYPE html>";
+ server << "<html>";
+ server << "<head><title>Domotique étage</title>";
+ server << "<style type=\"text/css\">";
+ server << "input{ width: 100px; height: 35px; border: solid 1px #000000; color: #000000; }";
+ server << ".on{ background: #a7ffa5; }";
+ server << ".on:hover{ background: #ffa8a8; }";
+ server << ".off{ background: #ffa8a8; }";
+ server << ".off:hover{ background: #a7ffa5; }";
+ server << "</style>";
+ server << "</head>";
+ server << "<body>";
+ server << "<h1>Domotique étage</h1>";
+ server << "<table>";
+
+ sortie_t empty = (sortie_t) -1;
+ // Salon
+ html_tr_6_sorties(server, led_arduino, rl_ecl_ext_jardin, empty, empty, empty, empty);
+ //html_tr_6_sorties(server, rl_sal_spot_left, empty, rl_sal_ecl_plaf, empty, rl_sal_spot_right, empty);
+ //html_tr_6_sorties(server, empty, rl_sam_gu10_1, rl_sam_gu10_3, rl_sam_gu10_5, empty, empty);
+ //html_tr_6_sorties(server, empty, empty, rl_sam_gu10_2, rl_sam_gu10_4, empty, empty);
+ //html_tr_6_sorties(server, empty, empty, empty, empty, rl_sas_gu10_left, empty);
+ //html_tr_6_sorties(server, rl_sam_pc_left, empty, empty, empty, rl_sas_gu10_right, rl_sas_ecl_plaf);
+ //html_tr_6_sorties(server, empty, empty, rl_cuis_gu10_2, rl_cuis_gu10_1, empty, rl_coul_plaf);
+ //html_tr_6_sorties(server, empty, rl_cuis_ecl_evier, empty, rl_cuis_ecl_plaf, empty, empty);
+
+ server << "</table>";
+ server << "</body>";
+ server << "</html>";
+}
+
+// Formattage d'une case de tableau
+void html_td_sortie(Print &p, sortie_t s) {
+ if ( s == -1 ) {
+ p << "<td>&nbsp;</td>";
+ } else {
+ p << "<td>";
+ p << "<input type=\"button\" value=\"" << desc_sorties[s] << "\" ";
+ p << "class=\"" << ( consigne_sorties[s]==HIGH ? "off" : "on") << "\" ";
+ p << "onclick=\"document.location = '/set?" << s;
+ p << "=" << ( consigne_sorties[s]==HIGH ? "0" : "1") << "'; return false;\" />";
+ p << "</td>";
+ }
+}
+
+// Formattage d'une ligne de tableau à 6 colonnes
+void html_tr_6_sorties(Print &p, sortie_t s1, sortie_t s2, sortie_t s3, sortie_t s4, sortie_t s5, sortie_t s6) {
+ p << "<tr>";
+ html_td_sortie(p, s1);
+ html_td_sortie(p, s2);
+ html_td_sortie(p, s3);
+ html_td_sortie(p, s4);
+ html_td_sortie(p, s5);
+ html_td_sortie(p, s6);
+ p << "</tr>";
+}
+
+// Code gérant la réponse à la requête HTTP permettant de modifier l'état d'une consigne
+void servlet_set(WebServer &server, WebServer::ConnectionType type, char *url_tail, bool tail_complete) {
+ URLPARAM_RESULT rc;
+ char name[NAMELEN]; int name_len;
+ char value[VALUELEN]; int value_len;
+
+ if (type == WebServer::HEAD) return;
+
+ while (strlen(url_tail)) {
+ rc = server.nextURLparam(&url_tail, name, NAMELEN, value, VALUELEN);
+ if (rc != URLPARAM_EOS) {
+ int key = strtoul(name, NULL, 10);
+ int val = strtoul(value, NULL, 10);
+ if ( key >= 0 && key < NUM_SORTIES ) {
+ consigne_sorties[key] = (val==1)?HIGH:LOW;
+ }
+ }
+ }
+ appliquer_consignes(true);
+ server.httpSeeOther(PREFIX "/");
+}
+
+// Initialisation et paramétrage du serveur Web
+void webserver_setup() {
+ webserver.begin();
+ webserver.setDefaultCommand(&servlet_list);
+ webserver.addCommand("set", &servlet_set);
+}
+
+/**********************
+ * I/O ET PERSISTENCE *
+ **********************/
+/* Lit les valeurs de consigne_sorties[] et :
+ - met à jour l'état des bascules de la sortie (pin) correspondante
+ - écrit cet état dans l'EEPROM
+
+ forcer : permet d'outrepasser le temps de pause en cas d'une précédente détection d'aléa (cf protection_alea())
+*/
+void appliquer_consignes(boolean forcer) {
+ uint8_t checksum ;
+ int i;
+
+ if (!protection_alea() && !forcer) return;
+
+ // Invalide les infos stockées dans l'EEPROM
+ EEPROM.write(EEPROM_BASE_ADDR_DATA, CHECKSUM_INVALID);
+
+ log_begin_line();
+ log_print("appliquer_consignes() : Sorties ");
+ checksum = CHECKSUM_INIT;
+ for (i=0; i<NUM_SORTIES; i++) {
+ digitalWrite(pin_sorties[i], consigne_sorties[i]);
+ if ( consigne_sorties[i] ) checksum++;
+ EEPROM.write(EEPROM_BASE_ADDR_DATA + 1 + i, consigne_sorties[i]);
+ switch (consigne_sorties[i]) {
+ case HIGH:
+ log_print("H");
+ break;
+ case LOW:
+ log_print("L");
+ break;
+ default:
+ log_print("#");
+ }
+ }
+
+ // Valide les infos stockées dans l'EEPROM
+ log_print(" checksum==");
+ EEPROM.write(EEPROM_BASE_ADDR_DATA, checksum);
+ log_printInt(checksum);
+
+ log_end_line();
+}
+
+/* Charge le tableau consigne_sorties[] depuis la sauvegarde contenue dans l'EEPROM
+ Permet de retrouver l'état des sorties actives / inactives lors d'un reboot ou après un défaut d'alimentation
+*/
+void charger_consignes() {
+ uint8_t checksum_lu, checksum_calcule;
+ int i;
+
+ checksum_calcule= CHECKSUM_INIT;
+ for (i=0; i<NUM_SORTIES; i++) {
+ consigne_sorties[i] = EEPROM.read(EEPROM_BASE_ADDR_DATA + 1 + i);
+ if ( consigne_sorties[i] ) checksum_calcule++;
+ }
+
+ checksum_lu = EEPROM.read(EEPROM_BASE_ADDR_DATA);
+ if ( checksum_lu != checksum_calcule ) {
+ for (i=0; i<NUM_SORTIES; i++) {
+ consigne_sorties[i] = CONSIGNE_PAR_DEFAUT;
+ }
+ }
+ log_begin_line();
+ log_print("charger_consignes() : checksum_lu==");
+ log_printInt(checksum_lu);
+ log_print(" checksum_calcule==");
+ log_printInt(checksum_calcule);
+ log_end_line();
+}
+
+
+/* Récupère l'état des pin d'entrée
+ Filtre les valeurs pour éviter les rebonds et faux contacts (mémorisé dans etat_entrees[])
+ Si un évènement vient de se produire :
+ Retourne le numéro de l'entrée
+ Sinon
+ Retourne -1
+*/
+entree_t lire_entrees() {
+ int i, evenement_entree = -1;
+ uint8_t precedent, actuel;
+ unsigned long maintenant;
+
+ log_begin_line();
+ log_print("lire_entrees() : Entrees ");
+
+ for (i=0; i<NUM_ENTREES; i++){
+ precedent = etat_entrees[i];
+ actuel = digitalRead(pin_entrees[i]);
+ maintenant = millis();
+ etat_entrees[i] = actuel;
+
+ if ( precedent==HIGH && actuel==LOW ) {
+ // Front descendant
+ millis_precedent_front_descendant[i] = maintenant;
+ log_print("\\");
+
+ } else if( precedent==LOW && actuel==HIGH ) {
+ // Front montant
+ millis_precedent_front_descendant[i] = 0;
+ log_print("/");
+
+ } else if ( precedent==HIGH && actuel==HIGH ) {
+ // Niveau haut
+ millis_precedent_front_descendant[i] = 0;
+ log_print("H");
+
+ } else if ( precedent==LOW && actuel==LOW ) {
+ // Niveau bas (bouton pressé)
+
+ // On filtre les rebonds ou les bruits : on prends en compte l'évènement
+ // que si la pression du bouton dure assez longtemps
+ if ( millis_precedent_front_descendant[i] != 0 ) {
+ // Le front descendant à été mémorisé
+ if ( (maintenant - millis_precedent_front_descendant[i]) > DUREE_MIN_BOUTON_PRESSE ) {
+ // L'entrée est restées assez longtemps en front bas
+ evenement_entree = (entree_t) i;
+ millis_precedent_front_descendant[i] = 0;
+ log_print("!");
+ } else {
+ log_print("w");
+ }
+ } else {
+ log_print("r");
+ }
+ }
+ }
+
+ log_print(" retour ");
+ log_printInt(evenement_entree);
+ log_end_line();
+
+ return (entree_t)evenement_entree;
+}
+
+
+/************
+ * SECURITE *
+ ************/
+/* Comptabilise le nombre d'évènements qui se produit sur les sorties
+ ( = nbre d'appels de protection_alea() par appliquer_consignes() )
+ - retourne false si un trop grand nombre de changements d'état à été détecté il y a moins de ALEA_BLOQUAGE_MILLIS ms
+ - retourne true si on est en situation normale
+*/
+boolean protection_alea() {
+ static uint8_t curseur_appels = 0;
+ static unsigned long millis_precedents_appels[ALEA_NB_APPELS_MAX];
+ static unsigned long millis_debut_blocage;
+
+ unsigned long millis_oldest, maintenant;
+
+ maintenant = millis();
+
+ // Ajout de cet appel ) protection_alea() dans le buffer rotatif millis_precedents_appels
+ millis_precedents_appels[curseur_appels] = maintenant;
+ curseur_appels = (curseur_appels + 1) % ALEA_NB_APPELS_MAX;
+
+ // Si on est en période de blocage, retourner false tout de suite. Si en fin de période, oublier le blocage
+ if ( millis_debut_blocage > 0 ) {
+ if ( maintenant - millis_debut_blocage < ALEA_BLOQUAGE_MILLIS ) {
+ return false;
+ } else {
+ millis_debut_blocage = 0;
+ log_begin_line();
+ log_print("protection_alea() : Fin de blocage");
+ log_end_line();
+ }
+ }
+
+ // Si le plus viel appel enregistré est moins vieux que (maintenant - ALEA_FENETRE_MILLIS)
+ // Alors c'est qu'on a une trop grande fréquence d'évènements, bloquer le système pdt ALEA_BLOQUAGE_MILLIS
+ millis_oldest = millis_precedents_appels[curseur_appels];
+ if ( millis_oldest > (maintenant - ALEA_FENETRE_MILLIS) ) {
+ millis_debut_blocage = maintenant;
+
+ log_begin_line();
+ log_print("protection_alea() : ");
+ log_printInt(ALEA_NB_APPELS_MAX);
+ log_print(" appels en moins de ");
+ log_printInt(ALEA_FENETRE_MILLIS);
+ log_print(" millisecondes. Blocage pendant ");
+ log_printInt(ALEA_BLOQUAGE_MILLIS / 1000);
+ log_print(" secondes");
+ log_end_line();
+ }
+
+ return true;
+}
+
+
+/*************
+ * DEBUGGING *
+ *************/
+void log_init() {
+#if DEBUG_VIA_SYSLOG == 1
+ Udp.begin(port_debug); // port local comme le port de destination (rsyslog)
+#endif
+#if DEBUG_VIA_SERIAL == 1
+ Serial.begin(9600);
+ while (!Serial) {
+ ; // wait for serial port to connect. Needed for Leonardo only
+ }
+#endif
+
+ log_begin_line();
+ log_print("log_init() : Mode debug initialisé");
+ log_end_line();
+}
+
+void log_begin_line() {
+#if DEBUG_VIA_SYSLOG == 1
+ Udp.beginPacket(ip_debug, port_debug);
+ Udp.print("<15> "); // Syslog header : facility * 8 (1 == user) + severity (7 == debug)
+ Udp.print("millis()==");
+ Udp.print(millis());
+ Udp.print(" ");
+#endif
+#if DEBUG_VIA_SERIAL == 1
+ Serial.print("millis()==");
+ Serial.print(millis());
+ Serial.print(" ");
+#endif
+}
+
+void log_print(const char *str) {
+#if DEBUG_VIA_SYSLOG == 1
+ Udp.print(str);
+#endif
+#if DEBUG_VIA_SERIAL == 1
+ Serial.print(str);
+#endif
+}
+
+void log_printInt(long val) {
+#if DEBUG_VIA_SYSLOG == 1
+ Udp.print(val);
+#endif
+#if DEBUG_VIA_SERIAL == 1
+ Serial.print(val);
+#endif
+}
+
+void log_end_line() {
+#if DEBUG_VIA_SYSLOG == 1
+ Udp.endPacket();
+#endif
+#if DEBUG_VIA_SERIAL == 1
+ Serial.println("");
+#endif
+}
+
diff --git a/io_declarations.h b/io_declarations.h
new file mode 100644
index 0000000..ea7ef74
--- /dev/null
+++ b/io_declarations.h
@@ -0,0 +1,34 @@
+
+// ========================================
+// == INTERRUPTEURS =======================
+// ========================================
+typedef enum {
+ bt_test_arduino, bt_ext_cote_rue,
+
+ NUM_ENTREES
+} entree_t;
+
+static uint8_t pin_entrees[NUM_ENTREES] = {
+ 0, 4,
+};
+
+
+// ========================================
+// == LIGNES ==============================
+// ========================================
+
+typedef enum {
+ led_arduino, rl_ecl_ext_jardin,
+
+ NUM_SORTIES
+} sortie_t;
+
+
+static uint8_t pin_sorties[NUM_SORTIES] = {
+ 13, 41,
+};
+
+static char desc_sorties[NUM_SORTIES][16] = {
+ "LED Arduino", "Jardin",
+};
+