diff options
-rw-r--r-- | ecl_maison.ino | 481 | ||||
-rw-r--r-- | io_declarations.h | 34 |
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> </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", +}; + |