Projet IMA3 P6, 2016/2017, TD2
Sommaire
Projet IMA3-SC 2016/2017 : <Réveil bien être>
Cahier des charges
Description du système
Pour le projet de Système communicant, nous avons décidé de réaliser un reveil bien être, permettant d'avoir une gestion du sommeil en temps réel ainsi qu'une personnalisation du réveil. L'idée générale est d'avoir un contrôle sur son sommeil et sur l'environnement qui nous entoure sans devoir passer par un site web ou un application mobile tout en ayant un design simple et reposant.
Ce réveil devra répondre à différents critères :
-Afficher l'heure actuelle
-Permettre de régler une horloge
-LEDs qui s'éclaire par intensité croissante
-Driver FM ou buzzer afin de jouer un son lorsque l'heure de l'horloge est atteinte
-Affichage de la météo (température et temps)
-Bouton heure du coucher, heure du réveil (Statistique)
-Température et humidité de la pièce
L'objet sera de forme ovale avec une partie plate afin d'avoir de la stabilité, le matériau qui l'entoure se devra d'être translucide pour laisser passer la lumière mais aussi opaque afin de ne pas avoir une vue sur l'électronique interne. Un ouverture sur l'avant permettra de laisser passer l'afficheur de l'heure ainsi que l'affichage des valeurs de la température, de l'humidité ainsi que de la météo. Une série de bouton permettront d'incrémenter l'heure ou l'horloge. De plus un bouton proche d'un LED de couleur permettra d'afficher si la gestion du sommeil est active ou non. Cette gestion s’arrête lors de l'appuie sur le bouton avec extinction de la LED ou lorsque l'horloge sonne. Les LEDS seront «blanches » et commenceront à éclaire une heure avant l'heure du réveil d'intensité croissante afin d'obtenir une lumière croissante semblable au lever du soleil et éviter le réveil brutal. Le buzzer ou le driver FM permettrait d'émettre un son lorsque l'heure du réveil est atteinte. Le choix de l'un ou l'autre sera en fonction de l'avancement et de la difficulté.
Le matériel
- 1 Arduino Uno (Remplacé par un FPGA)
- 1 Raspeberry Pi version 2 ou 3
- 1 câble d'alimentation ou Batterie
- 1 câble Ethernet
- 6 LEDs Blanches
- 1 LED de couleur
- 4 Afficheurs ou Un écran 3 ou 4 pouces
- 1 Capteur d'humidité
- 1 Capteur de température (Possibilité de les fabriquer)
- Carcasse en plastique
- 1 Buzzer ou 1 driver radio
- Résistances de différentes valeurs
Séance 1
Préparation de la séance
En amont de la séance, nous avons commencé à effectuer un travail de modélisation de notre système. Il nous faut une application Web permettant de régler le réveil de manière efficace. Nous avons envisagé les réglages suivants : réglage de l'heure de réveil, réglage de l'heure, choix de la mélodie au réveil. Nous avons également envisagé de mettre en place des statistiques sur le temps de sommeil. Nous aurons donc besoin d'une base de données pour sauvegarder des données utilisateur.
De plus, nous avons également bien avancé le programme sur Arduino et ce dernier est déjà en mesure d'effectuer les différentes actions requises du réveil : allumage des leds, gestion de l'heure etc...
Partie électronique
Cette séance a été l'occasion d'aborder un bon nombre de questions pratiques concernant le projet. Nous avons beaucoup profité de cette séance pour aborder la partie électronique car il s'agit de la partie la plus difficile à réaliser en-dehors de Polytech. Nous avons tout d'abord effectué un choix de capteurs pour obtenir les informations qui seront données à l'utilisateur. Nous avons choisi un capteur d'humidité et de température : le DH11 de Aosong Electronics. Ce capteur a l'avantage de nous donner deux informations intéressantes et d'être assez fréquemment utilisé. Il ne nous a donc pas été difficile de trouver des exemples d'utilisation sur internet.
Nous avons également choisi un bloc afficheur 7-segments permettant d'afficher 4 digits, que nous utiliserons pour tout nos affichages. Malheureusement, nous ne pourrons pas avoir de séparateur ":" entre nos heures et nos minutes mais cette version sera suffisante pour un prototype.
Après avoir réglé ces premiers détails, nous nous sommes intéressés à la lecture et au contrôle de ces périphériques à l'aide de l'Arduino. Nous avons placé nos composants sur la plaque à essais et avons en premier lieu tenté d'afficher des chiffres sur l'afficheur 7 segments. Il a pour cela fallu choisir un mode de communication entre l'Arduino et l'afficheur et brancher les pins correspondant.. Nous avons pris un peu de temps pour obtenir un affichage correct à cause du fonctionnement particulier de l'afficheur : nos digits avaient tendance à se décaler vers la droite à chaque nouvel affichage.
Cerceau de Leds blanches en allumage progressif lors de l'envoi d'un char "blanc" / int fonctionne aussi
Utilisation du DH11 pour la température et l'humidité.
Utilisation de l'afficheur 7-seg avec l'arduino.
Modification d'architecture: Un FPGA remplace l'arduino pour la gestion de l'allumage des Leds et la gestion des afficheurs
Initiation FPGA
Partie informatique
La partie informatique de la première séance a été assez limitée car nous nous sommes principalement attardés sur l'électronique. Cependant, nous y avons pris plusieurs décisions importantes pour la suite du projet. Nous avons en effet décidé d'utiliser une architecture Node.js au lieu de la structure classique C/PHP. Ce choix est motivé en partie par le fait qu'un des membres de nos groupes utilisera ce framework dans le cadre de son stage, mais s'est également imposé comme plus pratique et moderne. Même si des fichiers d'exemple C nous étaient fournis pour la communication par port série ainsi que les websockets, nous avons préféré découvrir une technologie différente, quitte à devoir tout apprendre par nous-mêmes.
Cette première séance en informatique a donc essentiellement été utilisée pour se renseigner et lire de la documentation sur le fonctionnement de node.js, en particulier les modules serialport (port série) et socket.io (websockets). A l'issue de cette séance, nous avons créé sur le Gitlab les fichier source et avons essayé quelques exemples. Node.js n'étant pas installé sur les PC de la salle de projet, nous n'avons pas pu avancer beaucoup plus sur le développement du serveur durant la séance.
Séance 2
Préparation de la séance
Suite à la séance précédente, la préparation de cette séance a principalement été axée sur le développement de l'application web. Il était en effet plus difficile d'avancer sur la partie électronique car nous n'avions pas le matériel. Nous avons toutefois pu avancer un peu sur le code Arduino sur le modèle d'un des membres du groupe, notamment pour l'allumage progressif des LEDS.
Pour la partie informatique, nous avons commencé le développement à proprement parler sur un PC portable. Nous sommes conscients que des adaptations seront nécessaires lors de l'utilisation sur le Raspberry Pi mais il est plus simple pour commencer de pouvoir tester le programme en direct sur un PC. Nous avons donc installé node.js sur un PC personnel et avons commencé à faire différents tests sur le module socket.io. Nous avons donc créé un certain nombre de fichiers d'exemple afin d'en comprendre précisément l'utilisation. L'existence d'un code javascript correspondant côté client a grandement facilité la tâche car la partie client est principalement réduite à :
<script src="/socket.io/socket.io.js" type="text/javascript"></script>
var socket = io.connect('http://172.26.79.17:8080');
Toutefois, le développement côté client a été plus difficile et il a fallu commencer à déterminer un protocole d'échange de données. En javascript, il est facile de transférer des objets par websockets mais il nous fallait déterminer comment envoyer uniquement les informations nécessaires au moment opportun. La gestion du temps réel a également été assez difficile. En effet, node.js se comporte différemment de ce qu'on a l'habitude d'utiliser car il s'agit d'un processus non bloquant : lorsqu'une opération longue est effectuée, le programme continue et revient à la ligne correspondante une fois l'opération terminée. Il est donc essentiel de ne pas tenter d'accéder aux variables pendant le traitement d'un message, par exemple. En outre, node.js utilise énormément de fonctions de callback, c'est à dire des fonctions qui sont appelées une fois qu'un événement donné est survenu. La maitrise de ce principe est essentielle car socket.io s'appuie énormément dessus, notamment avec les méthodes :
- socket.on();
- socket.emit();
- socket.broadcast();
Avant le début de cette séance, nous avons divers fichiers d'exemple fonctionnels qu'il nous faut maintenant assembler pour obtenir un ensemble cohérent et répondant au cahier des charges.
Concernant la partie électronique, des calculs ont été fais afin d'avoir un allumage progressif et constant des leds sur une période d'une heure compris entre l'heure de l'alarme-1 et l'heure de l'alarme dans le but d'avoir une luminosité constante et non agressive. De plus, une lecture de la doc technique du buzzer nous a permis de trouver une fréquence d'utilisation semblable à l'alarme d'un réveil classique avec la possibilité si le temps le permet de créer une mélodie.
Partie électronique
Le matériel étant disponible en salle de TP, nous avons tester l'allumage des leds progressif sur différentes périodes afin de valider le choix en extrapolant pour une heure de temps.
Pour cela, nous avons défini les variables suivantes :
int led = 11;
int brightness = 0;
int fadeAmount = 5;
int buzzer = 2;
Nous avons brancher le buzzer sur la pin2 de l'arduino et la led sur la pin 11.
La pin 11 accepte une valeur analogique, ce qui est nécessaire si nous voulons pouvoir changer l'intensité des leds.
Dans le setup, nous avons définie c'est deux pins en sortie afin d'envoyer des valeurs aux deux composants,en code arduino, cela se définit par:
pinMode(led, OUTPUT); pinMode(buzzer,OUTPUT);
Pour obtenir un affichage progressif sur une heure, nous utilisons:
analogWrite(led, brightness); brightness = brightness + fadeAmount; int x=1;
if(x=1)
{ brightness = brightness + fadeAmount; delay(2823); if(brightness=255) { x=0; brightness=0; }
En effet, lors de l'envoie de la commande pour l'allumage, nous rentrons dans une boucle grâce à la variable X, permettant d’incrémenter la luminosité jusqu'à sa valeur max au bout d'une heure puis la variable x passe à 0 ce qui permet de sortir de la boucle et entraine l'extinction des leds.
Pour finir avec le buzzer, nous utilisons la fonction TONE permettant d'envoyer une fréquence à une sortie, ici le buzzer avec une fréquence de 440 hertz
tone(buzzer, 440); noTone(buzzer);
Partie informatique
L'objectif de cette seconde séance était de commencer à mettre en commun tous nos fichiers d'exemple dans un ensemble cohérent. Nous avons créé le fichier principal server.js visible sur le Gitlab ainsi que le fichier dashboard.ejs.
Le format Embedded Javascript Template est un des langages de templates HTML les plus utilisés avec Javascript. Il permet de générer une page HTML, écrite à l'avance, en lui passant divers paramètres. Ce langage permet également l'utilisation de boucles et de conditions qui facilitent très nettement la création de pages lorsqu'on souhaite y insérer des valeurs pré-enregistrées. Dans notre cas, l'objectif est d'utiliser ce langage pour renseigner les choix de sonneries dans le champ de sélection. Nous avons donc créé la première partie de notre page destinée à l'application finale : le choix des sonneries.
Pour le test, nous avons mis trois sonneries différentes. Lorsque l'utilisateur appuie sur le bouton, un message est émis par les websockets contenant le choix de sonnerie ainsi qu'un message texte. Cette partie était relativement simple car pour le moment il ne s'agissait que d'un nom de fichier, mais il était évident que nous devions être en mesure de lire ce fichier. Afin de lire le fichier audio, nous sommes penchés sur le module play-sound.
Ce module possède comme avantage d'être simple d'utilisation, mais il est très récent et donc assez peu documenté et utilisé de manière générale. Il a donc été assez difficile de trouver des exemples, d'autant que nous voulions lire le fichier audio directement dans le navigateur et non sur le PC. Nous avons réussi à ouvrir et lancer la lecture du fichier sur le PC, mais ce dernier se lisait à une vitesse environ 2x inférieure à la vitesse normale, et ce quels que soient nos réglages. Nous en avons conclu qu'il s'agissait d'un problème interne au module et potentiellement lié au format audio. Vu que la lecture sur le PC n'est pas notre but final, nous ne nous sommes pas particulièrement attardés sur ce problème.
Séance 3
Préparation de la séance
Durant cette semaine, nous avons assez peu avancé sur le développement de l'application web, et plutôt sur sa forme et sa conception. Nous avons créé la feuille de style CSS pour la page, d'abord très simpliste, et avons amélioré le HTML.
Afin de pouvoir intégrer la feuille de style, il nous a fallu redéfinir le répertoire du serveur. En effet, de base, le serveur est créé dans un répertoire différent du répertoire du projet et le lien vers la feuille de style est donc erroné. Le module express pour node.js nous a permis de redéfinir le répertoire de lancement du serveur et ainsi de garantir tous les liens entre les fichiers avec la ligne :
app.use(express.static(__dirname + "/views"));
Nous avons profité de cette amélioration pour revoir la structure du programme. Nous avons créé un dossier views pour la partie EJS/CSS.
La semaine était consacré au capteur de température et à l'utilisation de l'afficheur et des différentes fonctions qui le composeront. En effet celui-ci devra afficher l'heure puis switcher sur la température et ce sur un cycle régulier. Pour cela, il fallait se documenter sur l'afficheur de sparkfun ainsi que sur l'utilisation du DH11 et prévoir l'utilisation de la température, puis en complément l'humidité.
Partie électronique
Concernant le DHT11, il existe une librairie pour arduino permettant en fonction du modèle de visualiser la température et l'humidité.
#include "DHT.h"
Nous avons donc branché la pin1 et la pin 4 du composant à l'alimentation(5V car 3.3V ne fonctionne que partiellement) et la masse. Puis la seconde pin sera connectée à la pin 5 de l'arduino.
Pour ce faire nous le déclarons de cette manière :
DHT dht(5,DHT11); //Pin et type du capteur
Contrairement à l'utilisation précédente, nous n'allons pas déclarer le DHT comme une entrée-> pinMode(dht,INPUT) mais en utilisant
dht.begin();
Le code permettant l'affichage et le calcul est:
void loop()
{
float h = dht.readHumidity(); float t = dht.readTemperature(); if (isnan(t) || isnan(h)) { Serial.println("Erreur"); } else { Serial.print("Humidité: "); Serial.print(h); Serial.print(" %\t"); Serial.print("Temperature: "); Serial.print(t); Serial.println(" *C"); }
}
Nous stockons nos deux valeurs dans les variable h et t puis nous les affichons sur le moniteur série de l'arduino que l'on initialise avec serial.begin
Concernant l'afficheur, nous avons définis plusieurs variables ainsi que des defines pour l'initialisation.
define AFF_RESET 0x76
define AFF_CTRL_LUM 0xFA
define AFF_CTRL_BAUDRATE 0x7F
define AFF_CRTL_DP 0x77
define DP_1 0x01 // 0x01 -> point décimal de gauche 1/4
define DP_2 0x02 // 0x02 -> point décimal de 2/4
define DP_3 0x04 // 0x04 -> point décimal de 3/4
define DP_4 0x08 // 0x08 -> point décimal de droite 4/4
define DP_HORAIRE 0x10 // 0x10 -> double point horaire
define DP_APOSTROPHE 0x20 // 0x20 -> apostrophe
define AFF_CTRL_DIGITS_1 0x7B
define AFF_CTRL_DIGITS_2 0x7C
define AFF_CTRL_DIGITS_3 0x7D
define AFF_CTRL_DIGITS_4 0x7E
Nous avons ensuite défini un serial.write(AFF_RESET) qui permet lors de l'allumage de l'arduino de tester chaque digit de l'afficheur.
Pour écrire sur les digits nous utilisons la fonction :
void set_digits(char d1, char d2, char d3, char d4) {
Serial.write(d1); Serial.write(d2); Serial.write(d3); Serial.write(d4);
}
Concernant la luminosité nous utilisons:
void set_luminosity(byte lum) {
Serial.write(AFF_CTRL_LUM); Serial.write(lum);
}
La fonction suivante autorise l'utilisation de point décimaux, utiles lors de l'affichage de float comme pour la température
void set_dp(byte value) {
Serial.write(AFF_CRTL_DP); Serial.write(value);
}
Nous allons maintenant définir les deux fonctions principales de l'afficheur à savoir, l'affichage de la température et l'affichage de l'heure:
void affiche_temperature(int temp, char unite) {
if(temp > 999) temp = 999; if(temp < -99) temp = -99; char tmp[4] = { 0 }; sprintf(tmp, "%3d", temp); set_dp(DP_APOSTROPHE); set_digits(tmp[0], tmp[1], tmp[2], unite);
}
void affiche_heure(byte heures, byte minutes) {
if(heures > 99) heures = 99; if(minutes > 99) minutes = 99; char tmpA[3] = { 0 }, tmpB[3] = { 0 }; sprintf(tmpA, "%02d", heures); sprintf(tmpB, "%02d", minutes); set_dp(DP_HORAIRE); set_digits(tmpA[0], tmpA[1], tmpB[0], tmpB[1]);
}
Elle permette de définir les champs d'utilisations des fonctions, par défaut nous avons choisis 999 et -99 pour la températures et 99 pour les heures et minutes. Bien que nous aurions pu utiliser 0 à 50 pour la température ainsi qu'une tolérance 24 et 60 pour les heures et minutes car nous n'utilisons que l'affichage de l'heure et non un compteur ou un chronomètre. Cependant dans le but d'une démarche d'amélioration et d'utilité, une plage plus grande permet de changer de capteur ou d'utilisation plus facilement.
Partie informatique
C'est pendant cette séance que nous avons commencé à travailler sur la Raspberry Pi. Une bonne partie de la séance a donc été consacrée au paramétrage et à l'installation. Nous avons effectué les branchements de la Raspberry et avons effectué les modifications permettant de se connecter au réseau. Un accès à internet était absolument indispensable pour nous car nous devions installer node.js. L'installation de node.js s'est passés sans problème, cependant certains modules n'ont pas pu s'installer tels que play-sound et serialport. En effet, ces derniers reposent sur des fichiers déjà présents sur le PC et qui ne font pas partie de l'architecture Raspberry Pi. Pour le moment, nous avons décidé de ne pas nous attarder sur ces problèmes et de plutôt se concentrer sur le réglage de l'heure.
L'heure est bien évidemment une problématique majeure pour un réveil, et d'autant plus sur un Raspberry Pi. Il est nécessaire d'avoir une connexion internet pour le réglage de l'heure car il n'y a pas d'horloge matérielle. Nous nous sommes en premier lieu attaqués au réglage de l'heure de réveil. Nous avons décidé d'utiliser un champ HTML5 de type time
. Même si ce champ n'est actuellement pas supporté par firefox et est remplacé par un champ de texte, une bonne partie des navigateurs modernes possède un champ spécifique. En particulier pour les utilisateurs sur mobile, le datepicker des navigateurs portables sera plus pratique que deux champs séparés "heures" et "minutes".
Nous avons écrit les fonctions d'envoi et de réception par websockets pour le choix de l'heure de réveil. Pour le moment, le serveur affiche simplement l'heure reçue dans la console. Le formatage de cette heure a été assez difficile car nous voulons impérativement un affichage de la forme HH:MM mais le champ time renvoie des données sous forme d'entiers qui sont donc tronqués si inférieurs à 10. Nous avons fini par convertir ces valeurs en string, car de toute façon la conversion s'effectue de manière naturelle avec javascript.
Séance supplémentaire 1
Partie électronique
Nous nous sommes intéressés à la liaison série ainsi qu'à la communication entre la partie électronique et informatique. Pour cela, il fallait définir la liaison série et le programme principale en fonction des variables reçues et envoyées. Nous avions donc le code suivant:
if ( Serial.available() ) { Serial.readBytes(buffheure,4);
}
En fonction de la lecture de buffheure, nous pouvions aller dans les différentes options en effet si le premier bit de buffheure est égale à 5, nous allumons progressivement les heures
if(buffheure[0]=='5')
{ analogWrite(led, brightness); brightness = brightness + fadeAmount; int x=1; }
if(x=1)
{ brightness = brightness + fadeAmount; //delay(2823); if(brightness=255) { x=0; brightness=0; } }
Si celui-ci est égale à 6, nous utilisons le buzzer:
if(buffheure[0]=='6')
{ tone(buzzer, 440); } else{ noTone(buzzer); }
Et lors d'une utilisation "classique",
Nous avions:
if(i<5){
affiche_temperature(t,'C'); } else{ if(buffheure[0]<='2'){ heure=buffheure[0]*10+buffheure[1]; minute=buffheure[2]*10+buffheure[3]; heure=heure-16; minute=minute-16; } affiche_heure(heure,minute); } i++; if(i>=10)i=0;
Qui permet l'affichage en cycle de l'heure et de la température ainsi que la modification de l'heure si nécessaire. Lorsque nous recevions l'heure, la valeur reçue était égale à valeurenvoyée+16 c'est pourquoi nous devions soustraire 16 aux heures et minutes.
Le programme principale complet est défini si dessous :
void loop() {
byte buffheure[4]; float h = dht.readHumidity(); float t = dht.readTemperature(); if ( Serial.available() ) { Serial.readBytes(buffheure,4); if(x=1) { brightness = brightness + fadeAmount; //delay(2823); if(brightness=255) { x=0; brightness=0; } } if(buffheure[0]=='5') { analogWrite(led, brightness); brightness = brightness + fadeAmount; int x=1; } else if(buffheure[0]=='6') { tone(buzzer, 440); } else{ noTone(buzzer); } if(i<5){ affiche_temperature(t,'C'); //Serial.println(t); } else{ if(buffheure[0]<='2'){ heure=buffheure[0]*10+buffheure[1]; minute=buffheure[2]*10+buffheure[3]; heure=heure-16; minute=minute-16; } affiche_heure(heure,minute); } i++; if(i>=10)i=0; }
}
Ceci correspond à l'affichage sur l'écran, voici l'affichage sur le 7-segment :
Partie informatique
Comme nous avions seulement commencé à travailler sur la Raspberry Pi à la dernière séance, il restait beaucoup de travail à faire en-dehors des séances, en particulier au niveau de l'architecture du projet. L'application requiert une base de données pour stocker les informations de l'utilisateur, et doit être capable de communiquer avec l'Arduino et/ou le FPGA via le port série. Il était donc nécessaire d'avoir accès à la plateforme définitive du projet pour pouvoir mettre en place ces fonctionnalités.
L'application Web a été la première étape de notre développement hors séances, car il s'agit d'une partie essentielle et vraisemblablement la plus simple.
Au niveau des fonctionnalités, nous avons tout d'abord ajouté le champ de réglage de l'heure. Ce dernier offre une possibilité de réglage automatique basé sur l'heure de la Raspberry Pi à l'heure de Paris, et de réglage manuel. Ce réglage se présente sous la forme d'un champ HTML time comme pour la sélection de l'heure de réveil, et ne permet pas de régler les secondes car nous n'avons pas jugé cela nécessaire. L'heure réglée est sauvegardée sous la forme d'une différence en heures et en minutes : l'heure de l'utilisateur est calculée à partir de l'heure de la Raspberry Pi en appliquant les différences données. Pour l'heure de réveil et le réglage de l'heure, nous avons ajouté un message de vérification envoyé par websockets qui permet de valider ou non le réglage.
Dans un second temps, nous avons décidé de retirer la fonctionnalité de sélection de la sonnerie : étant donné que nous n'avons qu'un buzzer à notre disposition, ce n'était pas très utile et ce champ de formulaire n'était de toute façon pas encore relié à l'application Node.js. Notre application comporte donc deux fonctionnalités principales sur lesquelles l'utilisateur peut agir : le réglage de l'heure et le choix de l'heure de réveil. La dernière fonctionnalité importante à mettre en place était l'affichage de la température. Cette information doit être envoyée par la carte Arduino, qui la lit sur le capteur DHT11, et reçue par la Raspberry Pi via le port série. C'est une des fonctionnalités impliquant le plus d'éléments, c'est pourquoi c'est la dernière que nous avons mise en place. Au niveau logiciel, l'Arduino envoie des données par le port série à chaque nouvel affichage sur l'afficheur 7 segments. Les données de température sont envoyées uniquement lorsque la température est affichée, c'est à dire pendant 5 secondes toutes les 10 secondes. Nous avons jugé que cet intervalle sans information était acceptable car il est rare que la température change de plusieurs degrés en l'espace de 5 secondes. Lors de l'affichage de la température, l'Arduino envoie deux chiffres représentant les degrés et le caractère 'C' permettant d'afficher le symbole Celcius. C'est grâce à ce symbole que l'application récupère la température avec le code suivant :
port.on('data', function(data){ received=received+data; if(received.length>2){ for(i=received.length-2;i<=received.length;i++){ if(received[i]=='C'){ temperature=received[i-2]+received[i-1]; received=""; } } } });
Lorsque l'on reçoit des données depuis l'Arduino, on ajoute ces données à une variable received. Si cette chaîne de caractères a une longueur de plus de 3, alors il peut y avoir des données de température complètes. On parcourt alors la chaîne, et si l'on trouve le caractère 'C', on récupère les deux caractères précédents dans le buffer et on le vide. Cette technique permet à notre application d'avoir un fonctionnement assez souple et de s'adapter aux envois pas toujours réguliers de l'Arduino. Une fois reçue l'information de température est envoyée à l'application Web par websockets avec la même technique que l'heure.
Enfin, nous avons modifié légèrement le design pour améliorer l'expérience utilisateur. Dans ce même temps, nous avons rajouté un calque qui recouvre le dashboard si le réveil est déconnecté. Cette technique permet à l'utilisateur d'être immédiatement mis au courant en cas de problème et de connaître la marche à suivre pour le résoudre.
La base de données est une des contraintes que nous avons choisi de nous rajouter pour ce projet. Sa mise en place s'est faite en parallèle du développement de l'application Node.js.
Pour la base de données, nous avons choisi Postgres car c'est le système que nous avons utilisé jusqu'à maintenant. Ce SGBD est assez lourd et pourrait probablement être remplacé avantageusement par MySQL ou SQLite par souci de légèreté. Cependant, dans le cas d'une amélioration future de l'application, par exemple pour permettre à plusieurs utilisateurs d'utiliser le même serveur simultanément, ce système pourrait s'avérer adapté. De même, si nous mettons en place des statistiques sur le sommeil, il deviendra nécessaire de stocker une grande quantité de données. Afin d'accéder à postgres, nous avons utilisé le module pg pour node.js qui offre une utilisation simple et adaptée à nos besoins. Pour pouvoir connecter notre application à notre base de données, il a fallu modifier les autorisations d'accès dans le fichier pg_hba.conf
. Notre base de données est très simple : elle contient deux tables, une contenant l'heure de réveil, sous forme de champ time et une autre le réglage de l'heure par l'utilisateur sous forme d'une différence en heures et d'une différence en minutes, tous les deux en integer.
Enfin, le programme server.js représente le coeur de notre projet car il permet de faire la liaison entre tous les éléments, et d'administrer le fonctionnement général du système. Étant donné l'avancement du projet à la fin de la troisième séance, le programme Node.js a été énormément modifié durant les semaines suivantes. Nous en expliquerons donc le fonctionnement global dans cette partie.
Le premier objectif de ce code est l'initialisation de tous les éléments du système. Notre projet doit être en mesure de fonctionner pendant des durées très longues sans rencontrer d'erreur, il est donc très important d'effectuer toutes les vérifications nécessaires lors de son lancement. Dans un premier temps, le programme charge tous les modules complémentaires npm dont il a besoin pour fonctionner, et crée l'instance de l'application Web. Un certain nombre de variables globalzs sont utilisées, notamment l'heure du système, la température et différents indicateurs d'état du système. Le programme tente ensuite de se connecter à la base de données. Il y a alors un certain nombre d'issues possibles :
- Si la connexion rencontre une erreur, le programme lance un Timeout qui effectue une nouvelle tentative de connexion après 10 secondes. - Si la connexion ne rencontre pas d'erreur, le programme effectue les requêtes nécessaires à la récupération des informations d'heure et d'heure de réveil.
Une fois encore, il y a plusieurs issues possibles :
- Si une requête rencontre une erreur, on en déduit que la table n'existe pas. On laisse alors la variable not_set correspondante à 1 qui permet d'indiquer au système qu'il faut utiliser les valeurs par défaut.
Une requête est envoyée à la base de données pour créer la table souhaitée et l'exécution continue.
- Si une requête ne rencontre pas d'erreur et retourne un résultat, alors on récupère les valeurs dans les variables correspondantes et on initialise la variable not_set correspondante à 0,
indiquant qu'il ne faut plus utiliser les valeurs par défaut.
- Si une requête ne rencontre pas d'erreur mais retourne un résultat vide, on en déduit que la table existe mais ne contient aucune valeur. Si aucune ligne de valeurs n'est reçue, la variable 'not_set
est laissée à 1 et le code utilisera donc les valeurs par défaut.
Une fois cette première étape effectuée, le programme initialise la liaison série avec la Raspberry Pi. Aucune vérification particulière n'est effectuée à cet endroit, car la vérification est faite à chaque appel ultérieur au port série. En cas d'erreur, une alerte est quand même affichée dans la console.
Le programme fait ensuite appel au template EJS contenant le code HTML de la page. Il lui passe comme paramètres les valeurs d'heure et de minute de réveil qui sont ainsi affichées dans la page.
La partie la plus importante du code est l'initialisation et l'utilisation des websockets. En effet, la quasi totalité des appels aux websockets sont contenus dans la fonction de callback de l'initialisation. Cette initialisation se présente de la façon suivante :
io = require('socket.io').listen(server) io.sockets.on('connection', function(socket){ //Function body });
Le websocket est en fait un objet du module io, faisant appel à une la méthode on. Le corps de la fonction indiqué, contient la majeure partie du code du projet. Lors de la première connexion, un message de bienvenue est envoyé à l'utilisateur. Ensuite, le websocket se met en écoute des messages envoyés par l'application Web. Ces messages peuvent être de deux types : set_heure_reveil pour régler l'heure de réveil ou set_heure pour régler l'heure du système. Dans chacun des cas, plusieurs actions sont alors nécessaires. Dans un premier temps, le programme transforme les données reçues en données insérables dans la base de données, c'est à dire une chaîne de caractères au format HH:MM pour l'heure de réveil et deux entiers pour l'heure. Une fois ces variables initialisées, il y a deux possibilités. Si l'on a trouvé lors de l'initialisation que la table est vide, on utilise une requête INSERT INTO. Sinon, on utilise une requête UPDATE. Ce fonctionnement permet de ne jamais avoir plus d'une ligne de valeurs dans notre base de données. Dans le cas où une erreur est rencontrée, on affiche un message d'erreur dans la console et un message d'erreur est renvoyé via websockets. Si aucune erreur n'est rencontrée, les valeurs ont correctement été mises à jour dans la base de données. Les variables utiles sont alors mises à jour. Si la table était vide, on repasse à 0 la variable not_set correspondante pour ne pas réutiliser une méthode INSERT lors du prochain appel.
Enfin, la fonction def_heure est le coeur du projet. C'est cette fonction qui s'exécute toutes les secondes afin de mettre à jour chacun des éléments du système. Son premier rôle est de calculer, à partir des différences, l'heure devant être affichée à l'utilisateur. Cette heure est formatée à l'aide d'une fonction checkTime pour être dans un format d'affichage normalisé et pratique à utiliser. Une fois ce calcul effectué, l'heure est envoyée par websockets à l'application, ainsi que la température. Ensuite, en fonction de diverses conditions sur l'heure, les données sont envoyées via le port série à l'Arduino. Si l'heure correspond au moment de démarrer l'allumage des LEDS, le message '5555' est envoyé. Ce message a l'avantage de contenir 4 caractères, et donc de rentrer dans le format de lecture de notre Arduino, sans correspondre à une heure possible. Si l'heure actuelle correspond à l'heure du réveil, le message '6666' est envoyé, permettant de déclencher le buzzer. Enfin, lorsqu'on ne rencontre aucun cas particulier, le message envoyé est une chaîne de caractères de format HHMM contenant l'heure et la minute actuelle. Si l'on rencontre une erreur lors de l'envoi des données, une variable erreur est initialisée à 1 et on appelle la fonction reset_serial() qui ferme la connexion et effectue des tentatives de reconnexion à intervalles réguliers. L'appel régulier à la fonction def_heure est interrompu pendant cette période de déconnexion pour éviter des erreurs à répétition. Le passage à 1 de la variable erreur permet de déclencher une condition qui envoie un signal par websockets à l'application et affiche le calque d'erreur mentionné plus haut.
Enfin, la dernière ligne du code est :
server.listen(8080);
Cette ligne permet de lancer le serveur sur le port 8080 et donc de lancer le système dans son ensemble. Le processus d'initialisation dure quelques secondes.