Lors du projet 01, nous avons implémenté la partie ‘À domicile‘ d’un système d’alarme permettant de contrôler et de mesurer différents appareils y étant connectés et d’afficher, dans le terminal série, leurs états ou valeurs.
Par exemple, afficher si un interrupteur, une DEL ou un détecteur de mouvement était activé.
De plus, des informations tel que, date, heure, température, humidité, temps écoulé sont aussi mesurées, traitées et affichées.
Nous voici maintenant rendu à l’étape 2 (projet de session) de développement du projet: 👉 relier le système d’alarme (le UNO) à une centrale de surveillance (le MEGA).
Une connexion série (UART) sera utilisée pour transférer les différents états et valeurs des appareils du système d’alarme, vers la centrale.
Ce transfert vers la centrale sera réalisé en boucle, à raison d’environ une transmission à la seconde.
Une transmission/réception simple, avec une validation sur la longueur du message, sera implémentée entre les deux systèmes.
Le Mega reçoit les données, les valide, les affiche dans le terminal et finalement, transmet, à chaque minute, les informations vers une base de données du Web.
NOTE: Pour un produit réel, il faudrait développer un protocole de communication entre les deux systèmes, pour assurer l’intégrité des données. Par contre, ceci déborde des objectifs de notre cours.
NOTE: Les événements n’apparaissent pas automatiquement sur la page Web car elle est rechargée aux 10 secondes.
👉 Il faut modifier le projet du UNO (TP01.P2) pour y ajouter les éléments permettant de transmettre, à chaque seconde, les données du système vers la station MEGA. Voir les informations sur les structures de données à la section 4.
De plus, il faut changer la fonctionnalité de lecture du potentiomètre (valeur entre 1 et 10), qui devient la durée de l’alarme lors d’une détection de mouvement ou suite à l’utilisation du bouton de panique
Il faut aussi ajouter les sections suivantes:
Voici l’écran du UNO, version TP02:
NOTE: Les messages au bas de l’interface servent de traces diagnostiques et sont facultatifs.
RAPPEL: 👉 Le UNO ne possède qu’un seul port UART. Solution: la librairie ‘SoftwareSerial‘ pour la liaison avec le MEGA . IMPORTANT, IL FAUT UTILISER LES BROCHES 8 ET 9 du UNO pour la connexion série, reliées au port UART2 du MEGA.
SoftwareSerial lienAvecRecepteur(9, 8); // RX, TX ... lienAvecRecepteur.begin(57600); // 57600 est la vitesse maximum recommandée!
La station de contrôle (Arduino MEGA), sur réception d’un message de longueur valide – sizeof(Systeme) – du UNO, traitera les données reçues et les affichera dans le format suivant:
NOTE: Les symboles 🆘,6️⃣,♻️, ⭕, … sont des caractères emojis, accessibles sous Windows via les touches ‘WIN et ;‘. Ces caractères sont implémentés dans le terminal de PlatformIO. Par contre, il est possible qu’à l’occasion le rendu ne soit pas parfait.
NOTE: Dans cette partie du projet, la section en jaune au bas de l’écran, n’est pas à faire.
Pour la préparation de l’envoi des données du système UNO, il faut utiliser les structures de données suivantes:
NOTE IMPORTANTE: Le symbole 👉 indique un élément prescrit (obligatoire).
enum EtatAppareils { APP_LED1 = 0b1 << 0, // 0000 0001 APP_LED2 = 0b1 << 1, // 0000 0010 APP_RELAI = 0b1 << 2, // 0000 0100 APP_MOUVEMENT = 0b1 << 3, // 0000 1000 APP_MOUVEMENT_ARME = 0b1 << 4, // 0001 0000 APP_BOUTON_PANIQUE = 0b1 << 5, // 0010 0000 APP_RETRO_LCD = 0b1 << 6, // 0100 0000 APP_UNITE_TEMP = 0b1 << 7 // 1000 0000 }; enum Evenements { evenement_statut_appareils, // 0 evenement_temperature_depassee, // 1 evenement_detection_mouvement, // 2 evenement_bouton_panique, // 3 evenement_systeme_enligne, // 4 evenement_changement_etat, // 5 evenement_UNO_non_connecte, // 6 evenement_nb_codes // 7 }; #define MAX_CAR_NOM_SYSTEME 10 struct Appareils{ unsigned int etatAppareils; // Traitement sur les bits. // 2 octets float temperature; // 4 octets float humidite; // 4 octets int dureeAlarme; // 2 octets // -->> EN OPTION: // uint32_t pressionAtmospherique; // 4 octets // float altitude; // 4 octets }; struct Systeme { // ATTENTION de ne pas dépasser 9 caractères pour le nom du système!!! char nomSysteme[MAX_CAR_NOM_SYSTEME]; // 10 octets Evenements codeMessage; // 2 octets NOTE: Côté UNO, ce contenu n'est pas traité. byte heure; // 1 octet byte minute; // 1 octet byte seconde; // 1 octet Appareils appareils; // 12 octets };
👉 La communication entre les deux microcontrôleurs sera réalisée en utilisant les apprentissages du document suivant : Communication entre deux Arduino
Pour économiser sur les données envoyées sur le port série, l’état de tous les appareils est stocké dans une seule variable entière.
INDICE: Voici comment renseigner la variable:
// Allumer les différents bits de 'etats' en fonction de l'état des appareils etats |= digitalRead(LED01) ? APP_LED1 : 0; etats |= digitalRead(LED02) ? APP_LED2 : 0; etats |= digitalRead(RELAI) ? APP_RELAI : 0; etats |= mouvementDetecte ? APP_MOUVEMENT : 0; etats |= boutonPanique ? APP_PANIQUE_DECLENCHE : 0; etats |= retroEclairage ? APP_RETRO_LCD : 0; etats |= detecteurDeMouvement ? APP_MOUVEMENT_ARME : 0; etats |= affichageCelcius ? APP_UNITE_TEMP : 0; // Le résultat sera placé dans la propriété 'Systeme.appareils.etatAppareils'
NOTE: Les messages au bas de l’interface servent de traces diagnostiques et sont facultatifs.
👉 Il faut programmer une fonction qui permet d’afficher un texte à l’écran, à la position XY et qui complète la ligne, sur une longueur de n, avec un caractère. Si le caractère n’est pas précisé à l’appel alors ‘-‘ sera utilisé.
void afficherTexteAvecRemplissage(const char* texte, int longueur, int posX, int posY, char caractere = '-'); // Et des exemples d'utilisation: afficherTexteAvecRemplissage("LED1", LONGUEUR_LIGNE_POINT, POS_X, LIGNE_TEXTE_LED1,'='); afficherTexteAvecRemplissage("LED2", LONGUEUR_LIGNE_POINT, POS_X,LIGNE_TEXTE_LED2); afficherTexteAvecRemplissage("RELAI", LONGUEUR_LIGNE_POINT, POS_X,6,LIGNE_TEXTE_RELAI, '*'); afficherTexteAvecRemplissage("Unité de température", LONGUEUR_LIGNE_POINT, POS_X,LIGNE_TEXTE_TEMP, ' '); afficherTexteAvecRemplissage("Détecteur de mouvement activé", LONGUEUR_LIGNE_POINT, POS_X,LIGNE_TEXTE_MOUVEMENT);
Ce qui produira le résultat suivant:
Les émojis 1️⃣ à 🔟 sont utilisés pour l’affichage de la durée de l’alarme.
ASTUCE:
const char * graphDigits[] = {"", "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣","7️⃣", "8️⃣","9️⃣", "🔟"};
Rappel, les valeurs et états du système proviennent du UNO. Nous les avons reçus via la communication UART. Ils devraient être stockés dans un variable de type ‘Systeme’.
Pour les appareils de type ON/OFF, comme les LEDs le relai, le détecteur de mouvements, l’unité C/F, …, les informations seront stockés au niveau des bits de la propriété ‘etatAppareils‘.
Pour connaitre l’état de la LED1, il suffit de vérifier si le premier bit de la propriété est à ON.
void afficherSymboleSelonEtat(int posX, int posY, bool etat, const char * symboleON, const char * symboleOFF) { ... } // afficherSymboleSelonEtat void actualiserEtatDesAppareils() { uint8_t etats = unSysteme.appareils.etatAppareils; afficherSymboleSelonEtat(COLONNE_ETAT,LIGNE_LED1, (unSysteme.appareils.etatAppareils & APP_LED1), _ON , _OFF ); // Nous testons ici le bit 0 afficherSymboleSelonEtat(COLONNE_ETAT,LIGNE_LED2, (unSysteme.appareils.etatAppareils & APP_LED2), _ON , _OFF ); // Nous testons ici le bit 1 ... } // actualiserÉtatDesAppareils
#define _ON "♻️" // ou "🟢" Selon votre préférence #define _OFF "⭕" // ou "🔴" Selon votre préférence #define FAHRENHEIT "Fahrenheit" #define CELCIUS " Celcius " #define SOS "🆘" // À utiliser en cas de panique ou de mouvement, sinon afficher deux espaces ' ' const char * etatSysteme[] = {"🥵", "ℹ️", "😡","💀", "🆘", "🆗"}; // Détails à suivre ...
Animer les lignes ‘Bouton de panique’ et ‘Mouvement détecté’ en cas d’activité.
👉 Il est primordial de mettre en place une mécanique de validation des données reçues du UNO avant le traitement et ou l’affichage.
Nous assumerons que les données sont valides seulement si le nombre d’octets reçus est égal à la taille de la structure ‘Systeme’.
// Validation coté MEGA // Vérifier si des données sont disponibles sur le port du transmetteur if (transmetteur.available() == sizeof(unSysteme)) { // NOTE: unSysteme est une variable de type 'Systeme' // Si le bon nombre d'octets reçues, lire les données transmetteur.readBytes((char*)&unSysteme, sizeof(unSysteme)); // (char*) indique de traiter 'unSysteme' comme une suite d'octets. // Afficher/traiter les données reçues ... } else { // Sinon, effacer le buffer de réception while (transmetteur.available()) { transmetteur.read(); // Lire et ignorer les données du buffer } }
Il faut incrémenter et afficher un compteur à chaque réception validée.
Par exemple, l’image suivante indique que le MEGA a reçu 4076 transmissions de longueur valide.
Dans la partie 2 du projet, il faut envoyer les informations reçus par la station de surveillance (le MEGA) vers une base de données accessible via le module Grove WiFi et le réseau Internet.
NOTE: Nous avons pratiqué cette fonctionnalité dans le laboratoire ‘Le module Wifi‘.
Directive de branchement du module Wifi
👉 Le module ESPWifi doit être connecté au port UART3 du MEGA.
6.1 – Il est possible de consulter le contenu de cette base de donnée à partir de l’URL suivante: http://esh25.ve2cuy.com ou bien ici, pour plus de détails,
Ce qui devrait afficher ceci:
L’API est disponible via l’URL suivante http://esh25.ve2cuy.com/tp2-api.php.
C’est une API simple, qui permet d’ajouter des enregistrements à une base de données.
Pour ajouter les informations reçus par le MEGA à cette base de données, il suffit de construire une adresse Web avec les différentes informations du système.
ASTUCE 1: Passer du réseau Wifi Cégep à celui de la maison lors de la compilation:
#ifdef CEGEP char ssid[] = "CSTJ-UBI-D136"; char pass[] = "unMotDePasse"; #else char ssid[] = "MON-WIFI-MAISON"; char pass[] = "unMotDePasse"; #endif
ASTUCE 2: Passer du Wifi cégep au Wifi maison sans avoir à recompiler le programme:
char ssid[20]; char pass[20]; // Sélectionner le réseau Wifi à utiliser char optionWifi = 0; terminal << "1) Wifi Cegep, 2) Wifi maison? : "; while (terminal.available() == 0); // Attendre une touche du clavier ... optionWifi = terminal.read(); if (optionWifi == '1') { strcpy(ssid, "CSTJ-UBI-D136"); strcpy(pass,"motDePasse"); } else { strcpy(ssid, "Wifi-Maison"); strcpy(pass,"motDePasse"); }
La fonction suivante met en forme correctement les différents paramètres à soumettre à L’API.
// ------------------------------------------------------------------------------------------------------- // Auteur: Alain Boudreault // Cible: Vous pouvez utiliser cette fonction dans votre projet. // Description: Sert à construire la requête GET qui sera utilisée vers L'API web. // ATTENTION: Ne pas modifier cette fonction sinon l'API va refuser votre requête. // ------------------------------------------------------------------------------------------------------- void preparerRequetePourAPI( // ------------------------------------------------------------------------------------------------------- char *buffer, // Pour recevoir le texte de la requête GET vers l'API. int tailleBuffer, // Taille du buffer servant à recevoir le texte de la requête GET vers l'API Systeme unSysteme, // Une structure représentant les données reçus du UNO. Voir 'struct Systeme'. Evenements codeEvent, // Code d'événement, Voir 'enum Evenements' pour les codes disponibles. const char * uneNote, // Un texte quelconque. Ce texte sera stocké dans la base de données de l'API. const char * adresseIP // À vous de construire une chaine représentant l'adresse IP du votre module Wifi. ) // ------------------------------------------------------------------------------------------------------- { // Voir WiFi.localIP() pour l'adresse IP du module Wifi. snprintf(buffer, tailleBuffer, "GET /tp2-api.php?nomSys=%s&ipAdr=%s¬e=%s&statut=%d&app=%d&tmp=%d&hum=%d&dur=%d&hr=%02d:%02d:%02d HTTP/1.1", unSysteme.nomSysteme, adresseIP, // urlEncode(messages[code]).c_str(), urlEncode(uneNote).c_str(), // ¬e= codeEvent, // &statut= unSysteme.appareils.etatAppareils, // &app= int(round(unSysteme.appareils.temperature)), // &tmp= NOTE: Conversion en int car snprinf ne supporte pas les float sous Arduino. int(round(unSysteme.appareils.humidite)), // &hum= unSysteme.appareils.dureeAlarme, // &dur= int(unSysteme.heure), // $hr=00:00:00 int(unSysteme.minute), int(unSysteme.seconde) ); } // preparerRequetePourAPI
L’utilisation de cette fonction pour former l’URL vers l’API devrait produire une chaine comme:
GET /tp2-api.php?nomSys=E000001&ipAdr=192.168.10.127¬e=VE2CUY&statut=4&app=209&tmp=20&hum=45&dur=6&hr=18:36:23 HTTP/1.1
👉 NOTE IMPORTANTE: Si le nom du système ne correspond pas à un numéro de matricule valide, précédé de ‘E’, la requête sera rejetée par l’API.
De plus, l’API ignore les requêtes qui excèdes un maximum de 20 requêtes en 5 minutes. Donc, attention de ne pas envoyer des messages inutilement vers l’API.
Exemple de validation du nom du système en php:
// Vérifier que le nom du système est valide: $nomSysteme= "A1234567"; $tableauMatricules = ["E0000001", "!VE2CUY!", "E1010101", "..."]; if (in_array($nomSysteme, $tableauMatricules)) { echo "La requête est acceptée"; } else { echo "La requête est refusée!"; } // Vérifier que le maximum de requêtes n'a pas été dépassé en 5 minutes: $sql = "SELECT COUNT(*) as total FROM tbl_sys WHERE nomDuSysteme = ? AND TIMESTAMPDIFF(MINUTE, date, NOW()) <= 5"; if ( $total > 20 ) alors grrr ... ;-(
# Voici une requête valide: esh25.ve2cuy.com/tp2-api.php?nomSys=VE2CUY&ipAdr=254.192.250.127¬e=Syst%C3%A8me%20de%20surveillance%20par%20VE2CUY&statut=4&app=84&tmp=18&hum=46&dur=6&hr=13:37:36
Lorsque le MEGA reçoit le premier message du UNO, il doit envoyer une requête de type ‘evenement_systeme_enligne‘ vers l’API. Ce qui devrait afficher la ligne suivante sur la page de consultation des événements systèmes:
NOTE: Les événements sont renseignés par le paramètre ‘Evenements codeEvent’ de la fonction ‘preparerRequetePourAPI’.
À chaque minute, mais pas plus, le MEGA doit envoyer une requête de type ‘evenement_statut_appareils‘ vers l’API. Ce qui devrait afficher la ligne suivante sur la page de consultation des événements systèmes:
Un clic sur la ligne affichera plus de détails sur le système:
À la réception des informations du UNO, il faut vérifier s’il y a eu un changement sur un des bits de la propriété ‘etatAppareils‘ par rapport à la réception précédente. Si c’est le cas, le MEGA doit envoyer une requête de type ‘evenement_changement_etat‘ vers l’API. Ce qui devrait afficher la ligne suivante sur la page de consultation des événements systèmes:
Dans cet exemple, LED1 était à OFF à la lecture précédente. Il est passé à ON (via le menu 1 du UNO) donc la requête ‘evenement_changement_etat‘ a été envoyée.
// Par exemple, sendInfoTOWebAPI(evenement_changement_etat);
Si le détecteur de mouvement a été déclenché, le MEGA doit envoyer, immédiatement, une requête de type ‘evenement_detection_mouvement‘ vers l’API. Ce qui devrait afficher la ligne suivante sur la page de consultation des événements systèmes:
ATTENTION, il est IMPORTANT de ne pas renvoyer ce message avant que le détecteur de mouvements soit retourné à l’état OFF au moins une fois. Si votre logique ne fait que vérifier si le bit de l’appareil mouvement est ON, alors la requête risque d’être envoyée à chaque réception des données du UNO (aux secondes) ce qui aura comme conséquence de voir vos requêtes bloquées par l’API pendant un certain temps.
// Par exemple, static bool messageUrgent = true; // Envoyer immédiatement les données vers l'API sur détection de mouvements // NOTE: Il faut envoyer ce message qu'une seule fois pendant la durée de l'événement. if ((unSysteme.appareils.etatAppareils & APP_MOUVEMENT)) { if(messageUrgent) { sendInfoTOWebAPI(evenement_detection_mouvement); messageUrgent = false; } } else { messageUrgent = true; }
Idem que 8.4.
Si la valeur de la température est >= à 24c, le MEGA doit envoyer, immédiatement, une requête de type ‘evenement_temperature_depassee‘ vers l’API. Ce qui devrait afficher la ligne suivante sur la page de consultation des événements systèmes:
ATTENTION, il est IMPORTANT de ne pas renvoyer ce message avant que la température soit retournée à < 24. Si votre logique ne fait que vérifier si la température est >= 24, alors la requête risque d’être envoyée à chaque réception des données du UNO (aux secondes) ce qui aura comme conséquence de voir vos requêtes bloquées par l’API pendant un certain temps.
// Par exemple, static float temperaturtePrecedante = unSysteme.appareils.temperature; if (unSysteme.appareils.temperature > TEMPERATURE_MAXIMUM && temperaturtePrecedante <= TEMPERATURE_MAXIMUM ) { sendInfoTOWebAPI(evenement_temperature_depassee); temperaturtePrecedante = unSysteme.appareils.temperature; }
Si le MEGA ne reçoit aucun message du UNO pendant 5 minutes, il faut envoyer une requête de type ‘evenement_UNO_non_connecte‘ vers l’API. Ce qui devrait afficher la ligne suivante sur la page de consultation des événements systèmes:
La requête sera transmisse à chaque période de 5 minutes, tant que le UNO sera hors ligne.
// Par exemple, if (aucune_reception_depuis_5_minutes){ sendInfoTOWebAPI(evenement_UNO_non_connecte); }
Connecté sur D6 et D7 du Mega, le 4Digit affiche l’heure reçue du UNO et le symbole ‘:’ change d’état (ON/OFF) à chaque 1/2 seconde.
Connecté sur D4 et D5 du Mega, le NEOPixel affiche une animation différente selon les événements suivants:
ATTENTION: L’animation doit-être de courte durée sinon il y aura perte de données au niveau de la réception en provenance du UNO.
Un capteur RFID, connecté sur UART1 du MEGA, permet d’activer/désactiver l’envoi de messages vers l’API Web. Il y a un document de référence –> ici.
Pour relever ce défi, il faut emprunter un capteur et une carte.
NOTE: Le capteur vient avec une clé à code unique ayant le format suivant: ‘OctetDépart6900E6C9BBFDOctetFin. Il faut extraire les octets de buffer+1 à buffer-1.
Le système doit reconnaitre, comme valide, la clé que vous avez et la mienne qui possède le numéro suivant: 6A001BD067C6.
Il faut ajouter la ligne suivante à l’interface utilisateur de la station MEGA:
Il y a trois valeurs possibles comme état:
Il faut ajouter la ligne suivante à l’interface utilisateur de la station MEGA:
Pour obtenir le point boni pour le traitement de la pression atmosphérique et l’altitude, il faut que ces informations soient envoyées vers l’API Web.
Pour ce faire, il faut modifier la fonction ‘preparerRequetePourAPI‘, pour y ajouter les deux valeurs suivantes:
Voici une URL valide:
GET /tp2-api.php?nomSys=!VE2CUY!&ipAdr=192.168.1.191¬e=exemple&statut=4&app=23&tmp=22&hum=42&dur=7&hr=17:10:43&pre=99=&alt=175 HTTP/1.1
NOTE: Les défis doivent-être réalisés sans l’aide de l’enseignant.
No. | Description | Note | Boni |
---|---|---|---|
UNO | Ajout des éléments de la section 2.5 | 3 | |
Transmission des données vers la centrale. | 2 | ||
Défi optionnel | Remplacer le capteur de température par bmp280 et ajouter pression et altitude aux données transmises. Important, voir 8.12 | 1.5 | |
Défi optionnel | Interruption matériel pour le bouton de panique | 0.5 | |
MEGA – P1 | Fonctionnalité, optimisation du code source et documentation | 4 | |
Aucun message de type ‘Warning’ à la compilation | 2 | ||
5 | Éléments d’interface | 2 | |
5.1 | fonction afficherLigne | 2 | |
Réception et traitement des données reçues du UNO | 2 | ||
5.10 Défi optionnel | Animation des lignes ‘Bouton de panique’ et ‘Mouvement détecté’ | 1 | |
Partie 2 | |||
Traitement correct de l’adresse IP –> vers API | 1 | ||
8.1 | evenement_systeme_enligne –> vers API | 1 | |
8.2 | evenement_statut_appareils –> vers API | 1 | |
8.3 | evenement_changement_etat –> vers API | 2 | |
8.4 | evenement_dectection_mouvement –> vers API | 2 | |
8.5 | evenement_bouton_panique –> vers API | 1 | |
8.6 | evenement_temperature_depassee –> vers API | 1 | |
8.7 | UNO non relié au MEGA –> vers API | 1 | |
8.8 | Heure sur 4Digit | 1 | |
8.9 | Animation des événements sur NEO-Pixel | 2 | |
8.10 Defi optionnel | Lecteur de carte magnétique RFID activé/désactivé –> API | 1 | |
REMISE | Un fichier .zip incluant les deux projets et le fichier workspace de vscode | 1 | |
30 | |||
Total | 30/30 | 4 |