Projet IMA3 P2, 2016/2017, TD2

De Wiki d'activités IMA

Projet IMA3-SC 2016/2017 : Station Météo

Description du système

Notre projet est de mettre en place une station météorologique permettant de connaître :

  • la température
  • la pression
  • l'humidité de l'air
  • la vitesse du vent
  • la luminosité extérieure

Les données de cette station météo seront visible par toute personne se connectant à une application web.

Cahier des charges

  • La station permettra de prendre des mesures chaque heure de manière automatique, mais il devra être possible d'utiliser une interface web afin de prendre des mesures de manière manuelle.
  • L'utilisateur pourra accéder à la base de donnée de la station à partir du site web et visualiser les variations des données (température, vitesse du vent, ...) en fonction du temps ou encore d'afficher toutes les données disponibles pour un jour donné.
  • La page d'accueil du site doit permettre la visualisation du temps qu'il fait (clair, grisâtre, pluvieux) et de la température actuelle. L'accès aux données enregistrées se fera via un champ de recherche situé plus bas sur la page.


Le capteur température-humidité-pression sera relié à une carte arduino Uno. L'anémomètre ainsi que le capteur de luminosité seront reliés directement à un FPGA. L'arduino sera elle aussi reliée au FPGA. En effet le capteurs mesurant la température, la pression et l'humidité de l'air communique par protocole SPI avec le microcontrôleur auquel il est relié. C'est pourquoi l'arduino, qui peut communiquer par SPI, fera office d'interface entre le capteur et le FPGA. La connexion entre le FPGA et la Raspberry Pi se fera par liaison série filaire. Il sera peut-être possible de remplacer, par la suite, la liaison filaire par une liaison sans-fil à l'aide de modules Xbee.

Le matériel

  • 1 Raspberry Pi
  • 1 Arduino Uno
  • 1 Shield Xbee (pour l'arduino)
  • 1 adaptateur Xbee-USB (pour la raspberry)
  • 1 XBee T et R
  • 1 Capteur température, pression et humidité (BME280)
  • 1 Anémomètre fabriqué avec 4 émetteurs/récepteurs ultrason (HC-SR04)
  • 1 capteur de luminosité (GA1A1S202WP)

Git du projet

L'intégralité du code source du projet se trouve sur l'archive.

Archive du projet

L'archive du projet contient tous les codes sources. Se trouvant dans le dossier /appWeb :

  • index.html, page sur laquelle seront affichée les données.
  • style.css, un peu de CSS pour mettre en forme la page

/appWeb/js

  • script.js, le script javascript qui gère la communication entre la page et le serveurs.

/prog

  • main.c, le script principale C qui gère la communication entre les capteurs et l'application Web.
  • serial.c/serial.h, bibliothèque d'accès aux port série sur une machine linux.

/code arduino

  • serialArduino/, dossier contenant le programme arduino permettant de récupérer les données du capteur BME280, qui les envoient par liaison série à la carte Raspberry Pi. Xbee pris en charge.
  • Plusieurs dossier et fichiers contenant la librairie du capteur BME280, les test du port série, de la communication série par Xbee, etc...

Réalisation

Partie informatique

L'application web

aperçu de l'application web connectée au serveur websockets

L'application web consiste en une page web codé en HTML. La partie dynamique de la page est codée en JavaScript avec l'aide de la bibliothèque Jquery.

La page web contient :
- Un indicateur permettant d'informer l'utilisateur de l'état de la connexion au serveur websockets
- Un tableau présentant les relevé météo des capteurs
- Un bouton permettant de rafraîchir les données
- Un aperçu de l'état du ciel, déterminé en fonction des capteurs

L'indicateur permet de savoir si l'application est connectée au serveur Websockets, celui-ci se met en rouge si la connexion est absente ou si il y a un problème. Le tableau affiche des valeurs entières. Ces valeurs n'ont donc qu'un cardinal restreint de 256 valeurs. Ceci constitue une limite principale de notre système, la précision.

Le bouton qui permet de rafraîchir les données est appelle lors du clique, une fonction javascript chargé d'envoyer un signal "GET" au serveur websocket. Le programme codé en C, envoie alors les données sous forme de 3 octets. Ces 3 octets représentent dans l'ordre, la pression, la température et l’humidité.

Pour gérer les octets de donnée, nous avons utilisé un tableau typé javascript de type Uint8ClampedArray. Toute donnée de ce tableau est de taille 8 bits et sa valeur est un entier non-signé.

Cette structure est très pratique dans notre cas puisque elle permet à la fois de formater la requête "GET" pour l'envoi au serveur websocket mais aussi les donnée reçu du serveur websocket, pour la lecture. La communication entre le site et le serveur se fait par envoi et réception d'une suite d'octets.

Dans le cas de l'émission, chaque lettre du mot "GET" sont converties en un octet. Ces octets sont mis bout à bout en utilisant un tableau typé. Il suffit juste d'envoyer le buffer de données du tableau en argument à la fonction send(), qui envoie les donnée au serveur.

Dans le cas de la réception, le serveur envoie les octets les uns à la suite des autres. L'application web les récupères sous forme d'un buffer. On crée un autre tableau typé à partir de ce buffer, il "découpe" la chaîne d'octet ce qui permet de les différencier et facilite leurs traitement. Enfin on affiche ces octets dans les bonnes cases du tableau.

Étant donné que les valeurs échangée ne vont que de 0 à 255, il a fallut effectuer quelques conversions. La pression moyenne au niveau de la mer vaut généralement 1013 hPa. Les relevé météo n'ont jamais relevé de pression en dessous de 845 hPa ni au dessus de 1100 hPa. Partant de ce principe, la valeur affichée sur le site ne sortira jamais de cette intervalle de taille 255.

Pour la température, l'intervalle va de -128 à +127 degré.

La mesure de l'humidité ne pose aucun problème puisqu'elle s'effectue en pourcent.

Ces conversions s'effectue avant l'affichage, par le script javascript.

Le côté serveur (Raspberry Pi)

Aperçu de la réponse du serveur websocket quand il a reçu une requête de l'application web

Partie electronique

Le montage électrique final est en 2 partie, la partie Arduino et la partie FPGA. Le montage Arduino est composé de la partie capteurs et la partie réception constitué d'un module Xbee fixé à un shield, relié à la Raspberry Pi. Nous utilisons une liaison sans-fil Xbee pour récupérer les données des capteurs. Les Xbee sont des modules échangeant des données par communication série.


Voici la 1ere partie, la partie sensorielle :

Partie émetteur du montage Arduino

Voici la 2e partie, la réception sur la Raspberry Pi

Partie récepteur du montage Arduino

La 2e partie, programmé par FPGA, est l'anémomètre. Ce circuit composé de 2 capteurs ultrasons, permet de détecter la vitesse du vent. Le principe est simple. Le vent est un déplacement de particules qui constituent l'air. Le son est le déplacement d'une onde mécanique dans l'air. Si les particules sont en mouvement pendant le déplacement de l'onde mécanique, alors la vitesse de cette onde est modifiée. Le premier capteur émet une impulsion, au même moment, on déclenche un chronomètre. Ce chronomètre s'arrête quand le second capteur reçoit l'impulsion. Il suffit de mesurer la vitesse de l'impulsion. En effectuant sa différence avec la vitesse d'une onde mécanique dans l'air quand les particules sont immobiles, on obtient la vitesse du vent.

Montage de l'anémomètre, ce montage permet de détecter la vitesse du vent dans une direction (au sens vectoriel)

Séance 1

Partie électronique

Lors de cette première séance, nous avons pu récupérer et nous informer sur les capteurs BME280 et GA1A1S202WP. Après avoir regardé les documentations de ces deux capteurs, nous avons téléchargé la librairie permettant de faire fonctionner le capteur BME280 (https://github.com/adafruit/Adafruit_BME280_Library).

Capteur Adafruit BME280. Mesure la pression, la température et l'humiditée

Une fois câblé comme il le fallait, nous avons donc d'abord testé le capteur BME280 et écrit un petit script Arduino qui affichait donc la température, la pression et l'humidité. Puis nous avons branché le capteur de luminosité (GA1A1S202WP) seul à l'arduino, et lancé un nouveau script qui affichait alors la luminosité en Lux de l'objet vers lequel on pointait le capteur. On a ainsi pu constaté une grande différence entre un ciel nuageux, le soleil, et la luminosité de la pièce. Ces observation permettront à l'application web de déduire si le temps est clair ou nuageux uniquement grâce aux relevés du luxmètre.

Capteur Adafruit GA1A12S202, mesure la lumière

Partie informatique

Nous avons effectué les test des capteurs BME280 et luxmètres sur une carte programmable Arduino Uno. Ces test nous ont permis de bien comprendre le fonctionnement de ces 2 capteurs. Les données du capteur de luminosité se lisent très facilement sur un port analogique. NOTE:Nous aurions pu le faire fonctionner par FPGA à l'aide d'un convertisseurs analogique numérique.

Le capteur BME280, contrôlé par une puce, communique par protocole I2C ou SPI, nous devons donc utiliser une carte Arduino pour extraire les données du capteurs, du flot d'information envoyé par la puce. De plus, les données des capteurs sont envoyées à la carte arduino, uniquement si une demande de transfert a été envoyé à la puce du capteur. Le programme qui permet de lire les valeurs est donc complexe. Nous utilisons donc les bilbiothèques Adafruit_Sensor.h et Adafruit_BME280.h pour nous faciliter le travail.

Séance 2

Partie électronique

Dans le but de faire notre anémomètre, nous avons commencé à faire un montage avec deux capteurs HC-SR04 : nous devons en placer un face à l'autre, le premier captant les ultrasons du second. En effet, si on sait que v = d/t où v est une vitesse, d une distance et t un temps, le temps mis par les ultrasons pour aller d'un point A vers un point B en étant portés par un vent allant à une vitesse v est : tab = d/(v+c) où c est la vitesse du son. Dans l'autre sens, on obtient tba = d/(c-v) puisque le vent s'oppose à la propagation du son dans ce cas.

Câblage niveau Arduino
Câblage niveau capteur ultrason

Partie informatique

Initialisation et appairage des 2 modules Xbee. Ces modules servent à rendre sans-fil la connexion entre les capteurs et la Raspberry Pi sur laquelle se trouve le serveur websocket et l'application web. Ce choix est motivé par notre volonté d'avoir une station météo réaliste et portable. Ainsi des mesures peuvent être prise n'importe où. Des difficultés de configuration ont rendu la réalisation de la tâche, très longue.


Préparation du serveur websocket. Nous utilisons un ordinateur Raspberry Pi comme "cerveau" de notre station météo. Elle se chargera de récupérer les données des capteurs et de les afficher sur l'application web. Nous avons modifié les différents fichiers de configuration. Nous avons notamment modifié les paramètres IP de la carte pour qu'elle soit accessible depuis les ordinateurs de Polytech. L'IP attribuée pour notre carte sera 172.26.79.15

Séance 3

Partie électronique

Lors de cette séance, on est passé sur Altium pour remplacer l'Arduino est le code par du FPGA. On a tout d'abord commencer par réduire la vitesse de l'horloge en la divisant successivement après avoir fait le TP de prise en main. Pour cela, on est passé par différente méthode : via un compteur pour commencer, mais on était obligé de passer par l'ordinateur pour définir la valeur d’arrêt. Puis on a trouvé la méthode des blocs diviseur qui était beaucoup plus simple à mettre en place. Pour visualiser, on utilisait les DEL.

Premiers essai

Partie informatique

Création du squelette HTML du site web et du début du script JavaScript. Nous utilisons uniquement la bibliothèque Jquery pour faciliter la manipulation du code HTML à travers JavaScript. Dans le fichier /js/script.js, nous gérons la communication par le protocole websocket du côté application web. Nous l'initialisons, initialisons la fonction d'envoi et l'algorithme à exécuter quand l'évènement de réception de données est déclenché. Pour le moment nous ne savons pas comment exactement envoyer les donnée, quel objet transmettre à la méthode send(), pour avoir un bon traitement des données par le serveur.

Travail complémentaire

Cette étape représente une majeure partie de l'avancée du projet.

Partie électronique

Il est plus ou moins facile de gérer les capteurs ultrason Il suffit d'envoyer un signal de temps haut 10µs et temps bas supérieurs à 60ms sur la pâte "trig" du capteur et il émet une onde. Lorsque l'onde est captée le capteur émet sur la pâte "echo" un signal dont le temps haut est proportionnel à la distance. (suite à un problème, on n'a pas pu faire une capture d'écran du schéma définitif)

Partie informatique

Nous avons décidé du protocole de communication entre application web et capteurs. L'application web doit envoyer le message 'GET' par websocket au serveur. Le programme du serveur envoie la requête 'GET' à l’Arduino, qui envoie un buffer d'octets. Chaque octet représente la valeur d'un capteur. Dans l'ordre d'envoi : la pression, la température, l'humidité, la luminosité. Le programme de la Raspberry Pi a pour rôle de transférer ces données à l'application web, sous le même format et dans le même ordre. Le script JavaScript décompose le buffer de donnée et affiche le résultat dans les bonnes cases du tableau.

Programme serveur

Le fichier /etc/rc.local a été modifié et contient la ligne permettant de lancer le programme du serveur websockets à l'allumage de la Rapsberry Pi

   /var/www/prog/./app.exe

Le fichier main.c contient le code source permettant de faire fonctionner le serveur websocket et la communication série. La partie série a été relativement facile à mettre en place puisqu'elle est basé sur l'utilisation des fichier en langage C. En revanche la partie websocket a été la plus compliqué à mettre en place puisqu'il faut suivre un certain protocole pour récupérer les données reçu et envoyer des données.

Partie websockets

L'étape de réception websocket permet de récupérer le mot 'GET'. Pour lire des données reçu il faut allouer de la mémoire de la taille du buffer et ajouter la taille des chaînes d'octets enveloppant le buffer. A partir de la on peut stocker dans cette case le contenu ayant pour adresse "in", étant un argument de la fonction de réception. Dans notre cas, le buffer a pour taille "len"

   requete=malloc(len+LWS_SEND_BUFFER_PRE_PADDING+LWS_SEND_BUFFER_POST_PADDING);
   memcpy(requete,in,len);

L'étape d'envoi permet d'envoyer les données des capteurs vers l'application web. Pour envoyer un message, il faut allouer une case mémoire de taille égale à la taille du buffer à envoyer, plus les octets enveloppant le message.

   unsigned char *buf = malloc(LWS_SEND_BUFFER_PRE_PADDING + length + LWS_SEND_BUFFER_POST_PADDING);

Nous avons choisi le type char (allant de -127 à 127) car il équivaut à une variable de taille 1 octet. Nous l'avons mis unsigned (allant de 0 à 255) uniquement pour pouvoir correctement lire les valeurs des données dans la console. Ceci nous a permis de vérifier que nous recevions les bonnes donnée par le port série. Il faut savoir que le formatage des données n'est utile que si nous voulons afficher les valeurs. Comme nous échangeons 4 octets, nous aurions pu stocker le message entier dans une unique variable de type "int" puisque ce type de donnée en C est précisément de taille 4 octets.


Ensuite il faut copier le message que l'on souhaite envoyer dans la case mémoire située à l'adresse de la mémoire alloué plus la taille des octets de début :

   memcpy (buf + LWS_SEND_BUFFER_PRE_PADDING, reponse, length );

"length" étant la taille du message sans les octets entourant ce message.

Enfin, on envoie le message avec une fonction fournie par la librairie libwebsockets. Le message se trouve alors à l'adresse buf + LWS_SEND_BUFFER_PRE_PADDING, définie précédemment  :

   libwebsocket_write(wsi, buf + LWS_SEND_BUFFER_PRE_PADDING, length, LWS_WRITE_BINARY); //LWS_WRITE_TEXT


Partie série

La fonction getDataFromSensors() gère la communication série entre la Raspberry Pi et l'arduino/FPGA.

Pour envoyer un octets en série, il faut juste faire un write(). Dans notre cas nous en avons 3, correspondant au message 'GET'. sizeOfRequest vaut 3.

   for(i = 0; i < sizeOfRequest;i++)
   {
       if(write(sd,(void*)&request[i],sizeof(char))!=1)
       { 
           perror("main.write"); exit(-1); 
       }
   }

Pour recevoir un octet c'est le même principe mais avec la fonction read(). Nous en recevons 4. Ce morceau de code est placé juste après la partie qui envoie un message, car le circuit arduino/FPGA envoie directement les données dès qu'ils reçoivent 'GET' :

   for(i=0;i < 4;i++)
   {//on lit ce que nous envoie l'arduino à la suite de la requete GET
       if(read(sd,(void*)&reponse[i],sizeof(unsigned char))!=1)
       { 
           perror("main.read"); exit(-1); 
       }
   }

Programme application web

Le choix de la méthode utilisée pour récupérer les données a pris beaucoup de temps. Après des recherches sur le fonctionnement du transfert par websockets, nous avons décider d'utiliser un tableau typé Javascript, plus précisément un tableau d'entier non signé sur 1 octet. Les tableaux typé javascript possèdent une méthode qui renvoie leur contenu sous forme de buffer, ce qui est très pratique dans le cas de la communication websockets. On initialise donc le websockets pour communiquer par buffer de tableaux :

   window.WebSocket=(window.WebSocket||window.MozWebSocket);
   var websocket=new WebSocket('ws://192.168.1.111:9000','myprotocol');
   websocket.binaryType =  'arraybuffer';

Dans le cas de l'envoi de la requête 'GET', on initialise le tableaux de la sorte :

   var requete = new Uint8ClampedArray(3);
   requete[0] = 'G'.charCodeAt(0);
   requete[1] = 'E'.charCodeAt(0);
   requete[2] = 'T'.charCodeAt(0);

Le mot clamped signifie que si la valeur assignée est inférieur à 0 ou supérieurs à 255, alors la valeur de la case sera automatiquement 0 ou 255. La fonction charCodeAt() retourne le code Ascii du caractère.

Il suffit juste de donner le buffer en argument de la fonction d'envoi pour envoyer les donnée :

   function requeteEnvoi(){
       websocket.send(requete.buffer);
   }

Pour distinguer les octets reçu, il suffit de donner le buffer de donnée reçu en argument du constructeur du tableau typé :

   var reponse = new Uint8Array(message.data);

On a alors accès à chaque octets grâce à un indice.

   var temp = reponse[1];
   var hum = reponse[2];
   var press = reponse[0]+845;
   var lum = (reponse[3]*10000/255).toFixed(2);

On ajoute 845 à la mesure de la pression car la valeur d'un octet ne dépasse jamais 255, or la pression atmosphérique moyenne est de 1013 hPa. La valeur affichée de la pression se situera toujours dans l'intervalle 845 hPa/1100 hPa. De même pour la luminosité, elle ne sera affichée que sur un intervalle allant de 0 à 10 000 Lux, avec pour pas minimale 0.0255 Lux.

Conclusion

Ce projet a permis de nous familiariser avec le protocole série et le protocole websockets, ce dernier étant encore en développement et de plus en plus utilisé au sein des applications web. Plus précisément nous avons vu comment échanger des données par 2 protocoles différents dans un même programme codé en langage C. Nous avons aussi pu découvrir la programmation de circuit logique FPGA, permettant d'avoir un aspect plus visuel de ce que nous programmons.

Nous n'avons pas pu respecter l'intégralité du cahier des charges. L'anémomètre n'est pas opérationnel car le circuit FPGA n'est pas finit. Seul le circuit envoyant des impulsions de 10 µs sur la broche trig est terminé. (Nous n'avons d'ailleurs pas pu ajouter le fichier sur le git ni prendre des captures plus récente car nous l'avons oublié dans la salle des consoles FPGA). Le circuit des capteurs devait être le FPGA et non l'arduino. Nous avons quand même dû l'utiliser pour lire les valeurs du BME280 qui communique en SPI ou I2C. Il aurait été possible d'utiliser le capteur de luminosité avec le FPGA, en réalisant un convertisseur analogique/numérique.

Toutefois, les grandes lignes du cahier des charges sont respectées. Notre application web est fonctionnelle, le protocole série sans-fil est utilisé pour faire communiquer la Raspberry Pi et l'Arduino/FPGA, l'application web communique grâce au protocole websockets. L’utilisateur de notre application peut connaître la météo de la région lilloise depuis n'importe quel endroit sur Terre, à l'unique condition que la Raspberry Pi soit allumée et connecté à internet.

Nous aurions pu améliorer l'application en y intégrant une base de donnée, l'utilisateur aurait pu sélectionner une date et une heure pour ainsi avoir les relevés météo. Il aurait été aussi possible d'envoyer les données sur des variables de plusieurs octets, permettant d'augmenter la précisions des mesures de luminosité et de pression par exemple.