IMA5 2020/2021 P1

De Wiki de Projets IMA
Révision datée du 7 janvier 2021 à 18:01 par Egury (discussion | contributions) (Gestion des trames)
(diff) ← Version précédente | Voir la version actuelle (diff) | Version suivante → (diff)


Présentation générale

Description

Dans le cadre des projets de fin d'étude, j'ai choisi le projet "Réseau de capteurs autonomes pour mesures aquatiques". Ce dernier à pour but de mesurer le niveau d'eau dans une zone, de mettre en forme l'information et de l'envoyer sur un serveur réseau via la technologie LoRa. A l'aide d'un serveur applicatif et d'une application, l'information sera disponible à l'utilisateur sous diverses forme (information diverses sur la balise, affichage sur une carte...).

Objectifs

Le projet va se découper en quatre partie. Premièrement il va falloir concevoir un capteur permettant de mesurer le niveau de l'eau et aussi le système permettant d'envoyer l'information via un réseau LoRaWAN. Dans un second temps, une machine virtuelle est mis à ma disposition afin de deployer le serveur réseau, il est donc nécessaire de configurer ce dernier. Dans un troisième temps sur le même principe que le serveur reseau, une machine virtuelle est mise à ma disposition afin de deployer le serveur applicatif. Enfin pour la quatrième partie, une application web devra être mise au point afin de données l'accés aux informations à l'utilisateur.

Réalisation du Projet

Projet de fin d'étude

Début projet

Les semaines 1 et 2 ont été très limité en terme de travail à cause du Covid, cependant lors de la première semaine et apres rendez vous avec Mr Boé, j'ai pu dresser le cahier des charges et concernant la seconde semaine après un second rendez vous j'ai pas récuperer le matériel LoRaWAN et des microcontroleurs Mbed pour m'aider dans la première partie du projet mais aussi rédiger le cahier des spécifications.

Mise en place du réseau

Grâce à l'aide du binôme Loic Ringot et Théo Evrard, le serveur réseau à été configuré sur la machine virtuelle. Pour se faire dans un premier temps nous avons installé un serveur DHCP sur la machine virtuelle afin de pouvoir s'adresser aux différents équipements du réseau. Le serveur DHCP a été configuré de tel manière à attribuer des adresses allant de 192.168.42.1 à 192.168.42.9. Pour se faire, il a fallu dans un premier se connecter au serveur réseau en ssh puis de modifier configurer le serveur DHCP (/etc/dhcp/dhcpd.conf) et d'ajouter les lignes suivantes :

subnet 192.168.42.0 netmask 255.255.255.0{
range 192.168.42.1 192.168.42.9
}

La configuration du serveur réseau s'effectue en se connectant à ce dernier en ssh:

root@192.168.42.10

Il faut installer le "gateway bridge", le "network server" ainsi que "l'application server" disponible sur ChirpStack. Pour se faire nous avons suivi un tutoriel à l'adresse suivante:

https://www.youtube.com/watch?v=FnTP7t47DlI

ainsi que la documentation fournie par ChirpStack


Pour accéder au serveur réseau, nous passons par l'interface web en se connectant à l'adresse suivante (en passant par une zabeth):

172.26.189.22:8080 id: admin mdp: admin

A partir de l'interface web, nous pouvons configurer de nombreux éléments:

-Création d'un nouveau projet

-Ajouter des équipements aux réseau à l'aide des clefs

-Surveiller les informations qui circulent sur notre réseau

-...

Enfin, en nous connectant en ssh sur les deux gateways:

antenne: 192.168.42.1 mdp:pdmk-062E7A
passerelle intérieur: 192.168.42.2 mdp:pdmk-030970

Nous avons pu mettre à jour ces dernieres en suivant le wiki Kerlink La connexion au wiki Kerlink s'effectue avec:

Login : univlille
Mot de passe : IYBlN7QF06

Test du réseau

Pour tester le fonctionnement de notre Network Server nous avons utilisé un module GPS LoRa du commerce. En se connectant a notre Network Server (172.26.189.22:8000) nous avons pu créer une nouvelle application et ajouté un nouvel équipement. Se faisant notre Network Server génère deux clefs, une "application key" et une "Network Key". Ces deux clefs doivent être renseigné manuellement dans notre équipement. Pour se faire on peut le connecter à un ordinateur et y ajouter les clefs via son logiciel dédié. L'équipement possédant déjà un EUI permettant de reconnaître l'équipement, nous avons du renseigner ce dernier dans l'ajout de l'équipement sur le Network Server. Suite à ce test nous avons bien pu observer la circulation de nos trames et le bon fonctionnement de notre Network Server.

Fabrication du End-device

Pour le end-device je dispose d'une carte STM32F411RE, d'un shield SX1276MB1MAS et d'un capteur ultrason HC-SR04. Le développement du programme se fait sur STM32Cube. Un premier programme à été mis en place sans difficultés pour récupérer les valeurs du HC-SR04 en s'appuyant sur l'exemple proposé par Arduino. En revanche les problèmes sont arrivés lors du développement concernant la partie LoRaWAN. En effet STM32 propose un package LoRaWAN pour faciliter le développement d'application, le problème étant que ce package concerne seulement les cartes STM32LXXX. J'ai essayé longuement d'adapter ce package pour ma carte STM32F411RE en modifiant notamment le Makefile, les .flash mais aussi les fichiers d'inclusion sans succès malheureusement. La solution envisagée est donc un changement de carte au profit d'une STM32LXXX.

Après changé notre carte pour une STM32L152RE, j'ai repris le package proposé par STM32 et dans le fichier Commissioning.h j'ai rensigné les clefs

LORAWAN_APP_KEY
LORAWAN_NWK_KEY

Il n'y a que ces deux à renseigner étant donné que notre équipement fonctionne sur une version inférieur à la 1.1. A noter que le champ device EUI semble ne pas fonctionner. Nous avons du récuper l'EUI sur une gateway et renseigner ce dernier dans l'équipement que j'ai crée pour mon application sur l'interface web.

Concernant le programme de l'équipement en lui même le programme est relativement simle, en se servant de la bibliothèque HAL je declare 3 PIN en input et je renvoie ainsi l'état des capteurs de manière périodique en me servant de la fonction "Send" du package STM32.


Partie application WEB

La principale difficulté de cette partie et l'absence totale de connaissances dans le développement d'application WEB. J'ai donc dû apprendre les langage HTML / CSS / PHP (quelques notions en JS) pour réaliser cette partie. Je disposais de quelques notions en base de données et SQL suit à mon année d'IMA3.

Le but de l'application WEB est de permettre de rendre exploitable pour l'utilisateurs les informations renvoyés par les balises. Pour se faire l'application doit contenir une page de monitoring permettant de surveiller l'état de toutes les balises. De plus elle doit contenir une partie historique permettant d'accéder à l'historique d'une balise en particulier (en effectuant une recherche par id/localisation/date). Enfin elle doit aussi proposer une partie permettant d'afficher une carte avec dessus les balises et leur état.

A l'aide de Bootstrap j'ai mis en place une application WEB avec ces trois partie.


Partie serveur d'application

Mise en place de l'environnement

Concernant la partie serveur, j'ai mis en place sur une VM (172.26.189.23) un serveur apache et Postgres pour la gestion des bases mais aussi un interpreteur php. Pour apache j'ai simplement mis tous mes fichiers html/php dans /var/www/html qui est le repertoir par defaut. Celui est configuré pour démarrer au lancement de la VM. Concernant Postgres j'ai d'abord crée un nouvel utilisateur en me servant de l'utilisateur crée par defaut (su - postgres):

createuser -d -P root (mdp: glopglop)

Puis j'ai créé une nouvelle base:

createdb -O root pfe

Pour se connecter a la base:

psql pfe (depuis l'utilsateur root ou postgres)

Enfin j'y ai ajouté mes tables:

CREATE TABLE balises (ID serial PRIMARY KEY, Pays varchar(50) NOT NULL, Ville varchar(50) NOT NULL, Localisation varchar(50) NOT NULL, Coordonnees varchar(50) NOT NULL,Niveau int NOT NULL, Inondee varchar(50) NOT NULL);
CREATE TABLE historique_balise (ID serial PRIMARY KEY, id_balise int NOT NULL, Date date NOT NULL, Niveau int NOT NULL, Inondee varchar(50) NOT NULL);


La base contient deux tables, une première ayant pour clef primaire l'id de la balise et contenant des informations diverses sur cette dernière telles que le pays, la ville, la localisation, les coordonnée, le niveau d'eau mais surtout si cette dernière est sous l'eau ou non. La seconde table concerne l'historique des balises, elle a pour clef primaire l'indice du relevé et contient des informations comme le numéro de balise, la date du relevé etc, l'état de la balise etc..

Pour la communication entre le serveur d'application et le serveur réseau, j'ai dans un premier temps mis en place un script python pour récuperer les relevés des capteurs via l'API REST:

import requests
import json
#on recupere le token
headers = {
   'Content-Type': 'application/json',
   'Accept': 'application/json',
}
data = '{"email": "admin","password": "admin" }'
response = requests.post('http://172.26.189.22:8080/api/internal/login', headers=headers, data=data)
resp=response.json()
#print(resp['jwt'])
#diverses information sur un capteur
headers = {
   'Accept': 'application/json',
   'Grpc-Metadata-Authorization': 'Bearer'+resp['jwt'],
}
response = requests.get('http://172.26.189.22:8080/api/devices/0b00f4ff703a8fc5', headers=headers)
resp=response.json()
print(resp)

Ce script permet de un premier temps de récuperer le token pour avoir accés à l'API. Celui ci est a renseigner dans toute les requetes http. En se connectant à l'API via l'interface graphique (172.26.189.22:8080/api), on peut récuperer toutes les requetes possible en cURL, j'ai donc simplement utilisé un convertisseur cURL vers python pour mettre mes requetes en place.

Malheureusement cette solution permet de récuperer de nombreuses informations concernant l'application mais pas les relevés des capteurs. Pour récupérer ces derniers, je suis passé par l'interface graphique du serveur d'application (172.26.189.22:8080) afin d'ajouter une intégration HTTP. Il faut simplement spécifier le format de l'information (JSON dans mon cas) et l'URL à laquelle doit être envoyé la requête http POST. Dans mon cas cela sera:

http://172.26.189.23/pfe/gestionPOST.php

La base du fichier gestionPOST.php ressemble à cela:

<?php
header("content-type: application/json");
$json = file_get_contents("php://input");
$obj = json_decode($json);
$decoded = base64_decode(json_encode($obj->data));
$fp = @fopen("toto.txt","a");
fwrite($fp,$decoded);
fwrite($fp,"\r\n");
fclose($fp);
?>

Il permet via le flux php://input de récupérer le contenu de la requête POST et de l'afficher.

La prochaine étape concerne la gestion des informations avec notamment l'ajout en base selon le type d'information recu.

Gestion des trames

Les trames sont envoyés au format suivant:

id:niveau1:niveau2:niveau3

A travers le script php permettant la gestion des requetes HTTP Post, en se servant de la fonction preg_match avec le regex suivant on peut récuperer toutes les informations necessaires au traitement:

"#^([0-9]+):([0-1]{1}):([0-1]{1}):([0-1]{1})$#"


Chaque nouvelle mesure envoyée sur le serveur d'application est renseignée en base grace à la requêtes SQL suivante:

UPDATE balises
SET inondee=:in, niveau=:niv
WHERE id=:id;

Les requêtes en php sont réalisées par le biais des fonctions query, prepare et execute.

Toutes les requêtes SQL sont regroupés dans un même fichier par soucis de clarté.

Dès qu'une nouvelle mesures est transmise au script php, on récupère la date actuelle et on la compare avec le dernier relevé de cette balise enregistré dans la table historique, si le dernier relevé date de la veille on ajoute une nouvelle entrée dans la table historique_balise:

$ajrd=date("Y-m-d");
$requete=$bdd->prepare('SELECT MAX(date) AS max_date
                        FROM historique_balise
                        WHERE id_balise=:id');
$requete->execute(array('id' => $id));
$resultat=$requete->fetch();
if($ajrd>$resultat['max_date']  || $resultat==FALSE){
     $requete=$bdd->prepare('INSERT INTO historique_balise
                             VALUES (DEFAULT,:id,:d,:niv,:in)');
     $requete->execute(array('id'=>$id,'d'=>$ajrd, 'niv'=>$niveau, 'in'=>$inondee));
}

Lors d'une première entrée, la condition $resultat==false permet d'ajouter pour la première fois la balise dans l'historique


Historique des balises

L'utilisateur peut accéder à l'ensemble des relevés des balises. Les relevés pouvant être nombreux, l'utilisateur à différents filtres à sa disposition. En effet il peut filtrer les relevés par balises, par localisation, mais surtout par date. Il peut renseigner soit une plage de date, soit une seule date pour avoir tous les relevés jusqu'a cette date ou tous les relevés à partir de cette date.

Affichage sur la carte

En passant par l'API google maps il est possible d'affichir une carte sur l'appplication web et d'apporter des modifications à cette dernière

A l'aide de JavaScript, je peux afficher sur ma carte les balises ainsi que leur etat sur la carte. Pour se faire, avant que le code JS soit executé par le client, ce dernier est preparé par un code PHP permettant de parcourir la base et de recupérer les balises, leur état et leur coordonnées avant de les afficher avec un indice couleur selon leur niveau d'inondation:

<?php
$reponse=$bdd->query('SELECT COUNT(*) FROM balises');
$nbMarqueur=$reponse->fetch();
?>
var nbMarqueur=<?php echo $nbMarqueur['count'];?>;
var locations = new Array(nbMarqueur);
var coordonnees, coord1, coord2;
var heatMapData = new Array(nbMarqueur);
<?php
 $reponse=$bdd->query('SELECT localisation, coordonnees, niveau FROM balises');
?>
var infos =JSON.parse('<?php echo json_encode($reponse->fetchAll(), true); ?>');
console.log(nbMarqueur);
for(var i=0; i<nbMarqueur; i++){
locations[i]=new Array(4);
locations[i][0]=infos[i]['localisation'];
coordonnees=infos[i]['coordonnees'];
var pos=coordonnees.indexOf(",");
coord1=coordonnees.slice(0,pos-1);
coord2=coordonnees.slice(pos+1);
locations[i][1]=coord1;
locations[i][2]=coord2,
locations[i][3]=i;
heatMapData[i]={location: new google.maps.LatLng(coord1, coord2), weight: infos[i]['niveau']};
}


Gestion utilisateur

Premièrement, la gestion utilisateur ne doit être accessible qu'aux personnes en ayant les droits, il est donc nécessaire de mettre en place un système de login. Tout se joue avec la fonction session_start(), avant d'envoyer la page concernant le gestion des capteurs au client, on verifie d'abord si ce dernier est connecté, i-e si la variable $_SESSION['login'] existe, sinon on redirige ce dernier vers l'ecran de connexion.

L'utilisateur entre login et mot de passe et on compare ces derniers a ce qui est stocké dans la base de données. Par mesure de sécurité des données de l'utilisateur et en cas de faille de la base, le mot de passe est stocké en hashé. Une fois connecté l'utilisateur à trois actions possibles, il peut ajouter une balise, modifier une balise et enfin supprimer une balise. En cas d'erreur de saisie, cette dernière est notifiée à l'utilisateur.


Note a moi même: travail a effectuer en priorité:

gerer la partie historique des balises en base (quasi fini, gérer les heures pour ajouter de manière plus fréquentes) FAIT
mettre en place un mode administrateur sur l'appli pour par exemple ajouter ou retirer des balises (a voir si utile avec les profs) FAIT
gerer sur la carte l'affichage des balises et l'etat de la route FAIT
mettre toutes les requetes SQL dans un même fichier (fait pour la recherche historique) FAIT
mise en place d'une appli android
concevoir le capteur
gestion ajout nouveau capteur (voir message down) FAIT


Connexion à l'application

Pour rendre l'application accessible depuis l'extérieur et sans passer par le proxy de l'école, j'ai réutilisé la marchine virtuelle deployé en PRA, a savoir trompettedelamortdans mon cas, avec comme ip public 193.48.57.188

Dans bind9, j'ai ajouté un nouvel enregistrement dans /etc/bind/db.trompettedelamort.site

pfe     IN	CNAME	ns1

En faisait cela, le nom pfe.trompettedelamort.site nous retourne bien l'adresse vers le site.

Ensuite, il reste juste a rediriger l'utilisateur vers le bon site lorsqu'il saisie pfe.trompettedelamort.site. Pour cela, la machine trompettedelamort fait office de reverse proxy, dans /etc/apache2/sites-availables ilfaut donc ajouter une nouvelle configuration:

<VirtualHost *:80>
       ServerName pfe.trompettedelamort.site
       ProxyPreserveHost On
       ProxyPass / http://172.26.189.23:80/
       ProxyPassReverse / http://172.26.189.23:80/
</VirtualHost>

Ainsi lorsqu'un utilisateur souhaite accéder à pfe.trompettedelamort.site, ce dernier est résolue en 193.48.57.188:80 qui lui même redirige vers 172.26.189.23:80 qui correspond bien à l'adresse privé de notre machine hebergeant notre site.

Petit problème, le routeur de l'ecole bloque l'accés de trompettedelamort (193.48.57.188) vers la machine hebergeant notre site (172.26.189.23). La solution consiste donc à s'attribuer une adresse sur le vlan des ZABETH et d'ajouter une route statique vers le réseau de la machine hebergeant notre site:

up ip address add dev eth1 172.26.145.241/24
up ip route add 172.26.188.0/22 via 172.26.145.254
down ip address del dev eth1 172.26.145.241/24
down ip route del 172.26.188.0/22 via 172.26.145.254


Mise à jour suite aux remarques

Premièrement, la structure des trams a été modifiée pour être réduite à son minimum, à savoir 4 octets. Désormais ces octets contiennent uniquement les valeurs des capteur, l'id est récupéré directement dans la requête POST:

$json = file_get_contents("php://input");
$obj = json_decode($json);
$id = bin2hex(base64_decode(json_encode($obj->devEUI)));


Deuxièmement, les id de balises qui s'auto-incrementés en base ne fonctionnent plus comme ça. L'id de la balise étant directement récupéré dans la requête POST, celui-ci est donc devenu la clef primaire de la table balises.

Troisièmement, la table historique balise a été modifié, elle stocke désormais les relevés avec la date mais aussi avec l'heure du relevé. Dans l'ancien système des mesures étaient envoyés par les balises de manière périodique, maintenant un deuxième mode est possible (et c'est celui retenu) qui permet d'envoyer une mesure uniquement quand un niveau change, le reste du temps le capteur est en mode low_power, permettant ainsi d'economiser grandement la batterie. Ainsi le nombre de relevés étant moindre, ceux-ci sont tous stockés en base.