« SE5 ECEAI/eceai 2023/2024/GodardDelcourt » : différence entre les versions
(68 versions intermédiaires par 2 utilisateurs non affichées) | |||
Ligne 1 : | Ligne 1 : | ||
=Introduction= | =<div class="mcwiki-header" style="border-radius: 30px; padding: 10px; font-family: Helvetica ; font-weight: bold; color: #FFFFFF; text-align: center; font-size: 90%; background: #FFC0CB; vertical-align: top; height:70%; width: 98%;"> Introduction </div>= | ||
==Objectifs du projet== | ==Objectifs du projet== | ||
Le projet vise à distribuer le calcul à différents endroits du réseau pour optimiser les performances et la sobriété énergétique. Un réseau d'objets connectés, de passerelles, et d'un serveur sera mis en place pour collecter, traiter, et évaluer les données environnementales. | Le projet vise à distribuer le calcul à différents endroits du réseau pour optimiser les performances et la sobriété énergétique. Un réseau d'objets connectés, de passerelles, et d'un serveur sera mis en place pour collecter, traiter, et évaluer les données environnementales.<br> | ||
Les données que nous avons décidé de traiter seront les différentes lettres de l'alphabet en langue des signes. L'utilisateur devra réaliser la lettre souhaitée au dessus du capteur, notre intelligence embarquée devra alors déterminer quelle lettre est réalisée et envoyer cette donnée sur un serveur pour afficher sur une page web le résultat. | |||
[[Fichier:Alphabet-dactylologique-nos-mains-vous-parlent.jpg|centré|sans_cadre|339x339px]] | |||
===Configuration de la Machine Virtuelle | =<div class="mcwiki-header" style="border-radius: 30px; padding: 10px; font-family: Helvetica ; font-weight: bold; color: #FFFFFF; text-align: center; font-size: 90%; background: #FFC0CB; vertical-align: top; height:70%; width: 98%;"> Mise en œuvre du réseau </div>= | ||
==Configuration de la Machine Virtuelle== | |||
Nous allons devoir créer une machine virtuelle sur Chassiron avec un accès internet IPv6. <br> | Nous allons devoir créer une machine virtuelle sur Chassiron avec un accès internet IPv6. <br> | ||
Ligne 40 : | Ligne 42 : | ||
Et nous allons aussi supprimer le fichier ''/etc/apt/apt.conf.d/01proxy''. | Et nous allons aussi supprimer le fichier ''/etc/apt/apt.conf.d/01proxy''. | ||
==Raspberry Pi== | |||
Nous allons tout d'abord devoir configurer la Raspberry Pi avec une image que nous avons récupéré sur internet. | |||
Ensuite, nous allons devoir connecter la Raspberry Pi au WiFi nommé WIFI_IE_1. Pour configurer la Raspberry Pi, nous allons la connecter en série à un ordinateur. Pour savoir quels pins il est nécessaire de connecter, il faut se fier à l'image ci-dessous. La partie alimentation n'est pas à connecter car trop faible pour alimenter la Raspberry Pi, nous allons donc devoir brancher sur secteur la carte grâce à une alimentation USB. | |||
[[Fichier:Learn_raspberry_pi_piconsole_bb.png|centré|sans_cadre|442x442px]] | |||
Après avoir connecté la Raspberry Pi sur l'ordinateur, nous pouvons vérifier sur quelle interface la carte est connectée avec la commande <syntaxhighlight lang="bash"> ls /dev </syntaxhighlight> | Après avoir connecté la Raspberry Pi sur l'ordinateur, nous pouvons vérifier sur quelle interface la carte est connectée avec la commande <syntaxhighlight lang="bash"> ls /dev </syntaxhighlight> | ||
Ligne 53 : | Ligne 55 : | ||
<syntaxhighlight lang="bash">minicom -s</syntaxhighlight> et les paramètres suivants ttyUSB0 et 115200 8N. | <syntaxhighlight lang="bash">minicom -s</syntaxhighlight> et les paramètres suivants ttyUSB0 et 115200 8N. | ||
Nous allons ensuite pouvoir configurer le wifi. Pour cela, nous allons dans un terminal de la Raspberry Pi, il faut alors taper la commande suivante afin d'accéder à la configuration du système : <br> | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
Ligne 60 : | Ligne 62 : | ||
Puis nous sommes allées dans ""System Options"" afin de pouvoir configurer les différentes informations qui vont nous permettre d'avoir le WiFi sur la carte.<br> | Puis nous sommes allées dans ""System Options"" afin de pouvoir configurer les différentes informations qui vont nous permettre d'avoir le WiFi sur la carte.<br> | ||
Pour vérifier si nos modifications ont bien fonctionné, nous avons réalisé la commande ip a pour vérifier | Pour vérifier si nos modifications ont bien fonctionné, nous avons réalisé la commande ''ip a'' pour vérifier si le wlan0 était bien Up, et nous avons aussi ping google.com pour vérifier si le WiFi fonctionnait. Tout cela était correct. | ||
'''Informations concernant la Raspberry Pi :'''<br> | '''Informations concernant la Raspberry Pi :'''<br> | ||
Ligne 66 : | Ligne 69 : | ||
User pour le SSH : pifou<br> | User pour le SSH : pifou<br> | ||
==Objets Connectés== | |||
Utilisation d'un STM32F401RE avec un capteur de distance (Nucleo-53L5A1). | Utilisation d'un STM32F401RE avec un capteur de distance (Nucleo-53L5A1). | ||
=<div class="mcwiki-header" style="border-radius: 30px; padding: 10px; font-family: Helvetica ; font-weight: bold; color: #FFFFFF; text-align: center; font-size: 90%; background: #FFC0CB; vertical-align: top; height:70%; width: 98%;"> Protocole de communication et acquisition des données. </div>= | |||
==Acquisition des données== | |||
Nous allons devoir développer un firmware embarqué afin collecter des données, nous allons pour cela utiliser un code STM32 Cube IDE qui va nous aider à collecter les données du capteur. | |||
La première étape consiste alors a installer STM32 Cube IDE pour l'objet connecté (STM32F401RE) avec capteur de distance (Nucleo-53L5A1). | |||
Cependant, nous avons eu de nombreux problème pour installer ce logiciel sur les zabeths de l'école. Nous avons donc du utiliser un ordinateur personnel sous windows afin d'installer STM32CubeIDE. Afin de téléverser le programme sur notre objet connecté muni du capteur, nous allons devoir connecter celui-ci en série avec notre ordinnateur. Le code sera par la suite placé sur l'objet grâce à l'application. | |||
Pour récupérer les données, nous allons utiliser un programme d'exemple fourni par STMicroelectronics. Pour vérifier le fonctionnement de ce code, nous avons lancé une liaison série sur l'objet connecté (grâce à Putty par exemple) et nous avons bien pu récupérer des données. | |||
Cependant, ce code ne nous convenait pas, nous avons donc du le modifier afin d'obtenir des données brutes affichées par paquets de 16 valeurs. | |||
<syntaxhighlight lang="bash" line="1" start="1"> | |||
static void print_result(RANGING_SENSOR_Result_t *Result) | |||
{ | |||
int8_t j, k, l; | |||
uint8_t zones_per_line; | |||
zones_per_line = ((Profile.RangingProfile == RS_PROFILE_8x8_AUTONOMOUS) || | |||
(Profile.RangingProfile == RS_PROFILE_8x8_CONTINUOUS)) ? 8 : 4; | |||
for (j = 0; j < Result->NumberOfZones; j += zones_per_line) | |||
{ | |||
for (l = 0; l < RANGING_SENSOR_NB_TARGET_PER_ZONE; l++) | |||
{ | |||
/* Print distance and status */ | |||
for (k = (zones_per_line - 1); k >= 0; k--) | |||
{ | |||
if((l!=0) || (k!=(zones_per_line - 1)) || (j!=0)){ | |||
printf(";"); | |||
} | |||
if (Result->ZoneResult[j+k].NumberOfTargets > 0) | |||
printf("%ld",(long)Result->ZoneResult[j+k].Distance[l]); | |||
else | |||
printf("0"); | |||
} | |||
} | |||
} | |||
printf("\r\n"); | |||
} | |||
</syntaxhighlight> | |||
==Protocole de communication== | |||
===Raspberry Pi & capteur=== | |||
Tout d'abord, nous allons devoir mettre en oeuvre un protocole de communication permettant l'envoi de données entre la carte (Raspberry Pi) et le capteur. | |||
Nous avons alors décidé de créer un script en Python qui permet au capteur de communiquer ses données à la Raspberry Pi, grâce à la liaison série entre les deux appareils. Comme nous avons vu précédemment, le code installé sur le capteur permet l'envoi de données brutes sur la liaison série, de la forme suivante : | |||
[[Fichier:Données capteur csv.png|900px|center]] | |||
Le code Python que nous avons implémenté sur la Raspberry Pi va permettre de récupérer ces données. Le script se trouve à l'endroit suivant sur la carte ''~/Documents/lecture/reader.py''. Lors de son lancement, celui ci va obtenir les données envoyées par le capteur, puis il va ranger ces données dans un fichier nommé ''store_data.csv''. Vous pouvez ainsi voir sur la photo ci-dessus l'affichage du fichier ''store_data.csv'' se trouvant sur la Raspberry Pi et contenant les données du capteur. | |||
Le script que nous avons écrit est le suivant : | |||
<syntaxhighlight lang="python3"> | |||
import serial | |||
ser = serial.Serial ( | |||
# Port série pour lire les données | |||
port='/dev/ttyACM0', | |||
# Débit de la communication série | |||
baudrate=460800, | |||
# Check pour la parité, ici on ne le fait pas | |||
parity=serial.PARITY_NONE, | |||
# Nombre de commandes séries à accepter avant expiration du délai | |||
timeout=1 | |||
) | |||
# Ouverture du fichier "store_data.csv" | |||
file = open(r"store_data.csv","w") | |||
# Mise en pause du programme pendant 1 seconde pour éviter de surcharger le port série | |||
while 1: | |||
x=ser.readline() | |||
output=str(x, 'UTF-8') | |||
file.write(output) | |||
print (x) | |||
file.close() | |||
</syntaxhighlight> | |||
===Raspberry Pi et serveur=== | |||
Nous allons ensuite mettre en oeuvre un protocole de communication entre la Raspberry Pi et le serveur que nous avons créé. D'après les consignes du sujet, nous devons faire en sorte que ce protocole soit facilement étendu à un grand nombre de Raspberry Pi. C'est pourquoi après quelques recherches, nous avons décide de réaliser un protocole de communication HTTP initié avec un script Python. | |||
Tout d'abord, un script Python sera réalisé sur notre serveur nommé delgodVM pour le spécialiser en un serveur web qui va nous permettre de gérer les requêtes HTTP. Ainsi, sur le serveur delgodVM, nous allons pouvoir lancer notre serveur Python HTTP avec le script créé et avoir le retour suivant : | |||
[[Fichier:Lancement serveur http.png|900px|center]] | |||
Le script est le suivant : | |||
<syntaxhighlight lang="python3"> | |||
import socket | |||
from http.server import HTTPServer, SimpleHTTPRequestHandler | |||
import json | |||
PORT = 8080 | |||
class MyHandler(SimpleHTTPRequestHandler): | |||
def do_GET(self): | |||
if self.path == '/ip': | |||
self.send_response(200) | |||
self.send_header('Content-type', 'text/html') | |||
self.end_headers() | |||
self.wfile.write(f'Your IP address is {self.client_address[0]}'.encode()) | |||
return | |||
else: | |||
return SimpleHTTPRequestHandler.do_GET(self) | |||
def do_POST(self): | |||
if self.path== '/data': | |||
content_length = int(self.headers['Content-Length']) | |||
data = self.rfile.read(content_length).decode('utf-8') | |||
data_dict = json.loads(data) | |||
print("Donnees recues:", data_dict) | |||
self.send_response(200) | |||
#Ouverture du fichier pour stocker les donnees | |||
file = open(r"store_data.csv","a") | |||
#Donnes ecrites dans le fichier .csv | |||
file.write(data_dict) | |||
else : | |||
self.send_response(404) | |||
self.end_headers() | |||
class HTTPServerV6(HTTPServer): | |||
address_family = socket.AF_INET6 | |||
def main(): | |||
server = HTTPServerV6(('::',PORT),MyHandler) | |||
print("Serveur actif sur le port :", PORT) | |||
server.serve_forever() | |||
if __name__ == '__main__': | |||
main() | |||
</syntaxhighlight> | |||
Pour vérifier (de plus) si notre serveur est bien lancé, nous allons essayer d'y accéder grâce à une page web sur une zabeth de l'école à l'adresse suivante sur le port 8080 configuré dans le fichier de configuration du serveur Python http : http://[2001:660:4401:6050:216:3eff:fe1a:f9c3]:8080 | |||
[[Fichier:Accès serveur web.png|900px|center]] | |||
De la Raspberry Pi, on vérifie qu’on peut bien accéder au serveur Python HTTP : | |||
[[Fichier:Test accès serveur avec curl.png|900px|center]] | |||
Pour envoyer des données de la Raspberry PI au serveur HTTP, nous avons créé un script Python qui va permettre d'envoyer ces données par des requêtes HTTP. Ce script sera bien évidemment lancé de la Raspberry PI. Pour que l'envoi de données fonctionne, nous devons alors lancer le script permettant le fonctionnement du serveur web sur le serveur delgodVM (commande python3 ./server.py). Et nous devons lancer simultanément le script reader.py sur la Raspberry PI. Le script est le suivant : | |||
<syntaxhighlight lang="python3"> | |||
import serial | |||
import requests | |||
ser = serial.Serial ( | |||
# Port série pour lire les data | |||
port='/dev/ttyACM0', | |||
# Débit de la communication série | |||
baudrate=460800, | |||
# Check ds parités, ici on ne le fait pas | |||
parity=serial.PARITY_NONE, | |||
# Number of serial commands to accept before timing out | |||
timeout=1 | |||
) | |||
# Open function to open the file "store_data.csv" | |||
#file = open(r"store_data.csv","w") | |||
= | url = "http://[2001:660:4401:6050:216:3eff:fe1a:f9c3]:8080/data" | ||
== | while 1: | ||
url = "http://[2001:660:4401:6050:216:3eff:fe1a:f9c3]:8080/data" | |||
x=ser.readline() | |||
output=str(x, 'UTF-8') | |||
# file.write(output) | |||
data = output | |||
response = requests.post(url,json=data) | |||
print (x) | |||
if reponse.status_code == 200: | |||
print("Donnees envoyees avec succes") | |||
else: | |||
print(f"Échec de l'envoi. Code de statut : {response.status_code}") | |||
print(response.text) | |||
#file.close() | |||
</syntaxhighlight> | |||
Au final ,nous arrivons bien à recevoir les données sur le serveur Xen, par un paquet unique. | |||
[[Fichier:Reception des données.png|900px|center]] | |||
==Stockage des Données== | ==Stockage des Données== | ||
=Entraînement du modèle d’apprentissage= | Notre serveur Xen reçoit maintenant les données de la carte grâce aux requêtes HTTP. | ||
Nous avons alors décidé de code un script python recevant les données et qui les stockes dans un fichier .csv sur le serveur afin de pouvoir par la suite utiliser les données du capteur. Ainsi, vous pouvez retrouver dans le script du server Python http, certaines parties correspondant à l'enregistrement des données reçues dans un fichier lui aussi nommé ''store_data.csv'' se retrouvant bien évidemment sur notre serveur Xen. Le code est le même que précédemment : <syntaxhighlight lang="python3"> | |||
import socket | |||
from http.server import HTTPServer, SimpleHTTPRequestHandler | |||
import json | |||
PORT = 8080 | |||
class MyHandler(SimpleHTTPRequestHandler): | |||
def do_GET(self): | |||
if self.path == '/ip': | |||
self.send_response(200) | |||
self.send_header('Content-type', 'text/html') | |||
self.end_headers() | |||
self.wfile.write(f'Your IP address is {self.client_address[0]}'.encode()) | |||
return | |||
else: | |||
return SimpleHTTPRequestHandler.do_GET(self) | |||
def do_POST(self): | |||
if self.path== '/data': | |||
content_length = int(self.headers['Content-Length']) | |||
data = self.rfile.read(content_length).decode('utf-8') | |||
data_dict = json.loads(data) | |||
print("Donnees recues:", data_dict) | |||
self.send_response(200) | |||
#Ouverture du fichier pour stocker les donnees | |||
file = open(r"store_data.csv","w") | |||
#Donnes ecrites dans le fichier .csv | |||
file.write(data_dict) | |||
else : | |||
self.send_response(404) | |||
self.end_headers() | |||
class HTTPServerV6(HTTPServer): | |||
address_family = socket.AF_INET6 | |||
def main(): | |||
server = HTTPServerV6(('::',PORT),MyHandler) | |||
print("Serveur actif sur le port :", PORT) | |||
server.serve_forever() | |||
if __name__ == '__main__': | |||
main() | |||
</syntaxhighlight> | |||
Et voici le contenu du fichier .csv sur notre serveur : | |||
[[Fichier:Fichier csv serveur.png|sans_cadre|1065x1065px]] | |||
Les données sont donc correctement transférées du capteur vers la Raspberry Pi en liaison série. Puis par des requêtes http, les données seront envoyées par la Raspberry Pi jusqu'au serveur qui stockera ces données dans un fichier .csv pour par la suite les analyser. | |||
=<div class="mcwiki-header" style="border-radius: 30px; padding: 10px; font-family: Helvetica ; font-weight: bold; color: #FFFFFF; text-align: center; font-size: 90%; background: #FFC0CB; vertical-align: top; height:70%; width: 98%;"> Entraînement du modèle d’apprentissage </div>= | |||
==Entraînement sur NanoEdge AI Studio== | |||
===Description=== | |||
Pour commencer la partie Entraînement, nous avons décidé de commencer par NanoEdge AI Studio. Ce logiciel développé par STMicroelectronics | |||
permet de "créer" une Intelligence Artificielle (IA), de manière simple et guidée. | |||
Pour créer cette IA, nous avons tout d'abord dû connecter la carte STMicroelectronics au PC et lancer NanoEdge AI Studio. | |||
===Signaux=== | |||
Après avoir créé un projet "n-Class", nous avons dû rentrer les signaux que nous voulions faire apprendre. Dans notre cas, ce sont les lettres de A à J qui vont être apprises. | |||
[[Fichier: SignauxNEAIS.png|500px]] | |||
Après avoir fait tous les signaux que nous avons besoins, nous devons lancer le Benchmark. | |||
===Benchmark=== | |||
Le Benchmark est une sorte d'étalon, c'est dans cette partie que l'IA cherche la meilleure librairie possible d'après les signaux envoyés dans la partie précédente. | |||
[[Fichier:Benchmark Delgod.png|500px]] | |||
===Validation=== | |||
Une fois que cette partie est terminée, nous pouvons passer à la partie Validation où nous pouvons importer un signal différent de ceux d'avant afin de choisir la meilleure librairie parmi celles trouvées dans la partie Benchmark. | |||
[[Fichier:Validation.png|500px]] | |||
===Emulator=== | |||
Enfin, nous pouvons tester en temps réel dans la partie Emulator si notre librairie a bien appris les signaux envoyés en entraînement. | |||
[[Fichier:EmulatorDelgod.png|500px]] | |||
===Résultats=== | |||
Nous avons filmé deux cas où l'IA reconnait les symboles C et I de la Langue des Signes Française (LSF) : | |||
[[Fichier:C.mp4]] [[Fichier:I.mp4]] | |||
Comme vous pouvez le voir, certaines lettres sont mieux perçues que d'autres, cela est dû au fait que NanoEdge AI Studio est très sensible à la luminosité, mais aussi à la position de notre main par rapport au capteur. | |||
==Entraînement avec un script Python== | |||
===Apprentissage avec des données sélectionnées=== | |||
Dans un premier temps, nous avons créé une intelligence artificielle qui apprend grâce à des fichiers CSV que nous avons fait. | |||
Nous avons choisi d'utiliser la librairie SciKit Learn et Random Forest pour développer notre IA. | |||
La première étape dans notre script Python est de lire tous les CSV que nous lui fournissons (c'est-à-dire les CSV correspondant aux lettres A à K de la Langue des Signes Française). Ensuite, nous concaténons tous les CSV pour en faire une sorte de "mini" base de données. Cette base de données va être scindée en deux : 80 % des données sont faites pour l'apprentissage, et les 20 % restants sont pour le test. | |||
Il faut savoir aussi que nous avons relié une lettre à un chiffre pour plus de facilité, nous avons donc : | |||
<code>Empty = 0, A = 1, B = 2, etc.....</code> | |||
Pour compliquer la tâche, nous avons décidé de mélanger toutes les données pour l'entraînement et le test, c'est-à-dire que le tableau de réponse ne sera pas sous la forme <code>[0 0 0 ... 1 1 1 .... 2 ....]</code> mais plutôt sous la forme : <code>[7 11 6 6 0 10 5 ...]</code> | |||
Concernant la phase d'apprentissage, nous avons au total 4 variables : | |||
* <code>X_train</code> : données que la carte a envoyées qui sont destinées à l'entrainement | |||
* <code>y_train</code> : tableau de réponse (ce qu'on attend comme résultat) | |||
* <code>X_test</code> : données que la cartée a envoyées qui sont destinées au test | |||
* <code>y_test</code> : tableau de réponse que l'IA a déterminé | |||
Enfin, nous avons mis le paramètre <code>accuracy</code> qui permet de déterminer le "score" de l'IA. | |||
<syntaxhighlight lang="bash"> | |||
def random_forest_predict(X_train,y_train,X_test,y_test): | |||
model = RandomForestClassifier() | |||
model.fit(X_train, y_train) | |||
y_pred = model.predict(X_test) | |||
accuracy = accuracy_score(y_test,y_pred) | |||
print(y_pred) | |||
print(f"Précision du modèle : {accuracy}") | |||
</syntaxhighlight> | |||
[[Fichier:AccuracyDelGod.png.png|500px]] | |||
Après le lancement du test, nous trouvons une accuracy de 1. Cette accuracy parfaite est sûrement due au fait que pour chaque lettre, nous étions dans les mêmes conditions (position, luminosité etc..) | |||
===Apprentissage avec des données en temps réel=== | |||
Après avoir entraîné notre IA, nous allons la tester avec les données qui proviennent directement de la carte avec le capteur Time of Flight. Pour cela, nous avons utilisé la librairie <code>Serial</code> de python pour obtenir les données en temps réel. Comme les données sont sous forme d’une longue chaîne de caractère, nous avons dû séparer la chaîne en une liste, transformer les chaînes de caractères en entiers et enfin transformer la liste en un tableau. | |||
Après avoir transformé les données dans le bon type, nous avons réutilisé la fonction <code>predict</code> pour que l’Intelligence Artificielle détecte quelle lettre nous avons fait. | |||
<syntaxhighlight lang="bash"> | |||
def random_predict_serial(X_train, y_train,model): | |||
ser = serial.Serial() | |||
ser.port = PORT | |||
ser.baudrate = BAUDRATE | |||
ser.timeout = 0.2 | |||
ser.open() | |||
print("Is serial open : ", ser.is_open) | |||
print(ser) | |||
lines = ser.readlines() | |||
for line in lines: | |||
# print(line) | |||
decoded_line = str(line, ENCODING)[:-2] | |||
print(decoded_line, " - ", len(decoded_line.split(","))) | |||
if(len(decoded_line.split(","))==64): | |||
test_line = decoded_line.split(",") | |||
test_line_int = [int(x) for x in test_line] | |||
test_array = np.array(test_line_int) | |||
test_array = test_array.reshape(1,-1) | |||
print("TYPE TEST :",type(test_array)) | |||
prediction = model.predict(test_array) | |||
print("La prediction :" ,prediction) | |||
ser.close() | |||
print("Is serial open : ", ser.is_open) | |||
</syntaxhighlight> | |||
Comme vous pouvez le voir, cette fonction redonne à la fin la prédiction du signal. | |||
[[Fichier:PredictionDelGod.png|700px]] | |||
Nous pouvons voir ici que le programme donne des prédictions que lorsque les données sont conformes et nous pouvons aussi remarquer que les prédictions restent les mêmes donc que l'IA est plutôt stable. | |||
Dans ce cas-ci, la prédiction était "0" ce qui signifie, comme vous l'avez vu plus haut, à Empty. | |||
=<div class="mcwiki-header" style="border-radius: 30px; padding: 10px; font-family: Helvetica ; font-weight: bold; color: #FFFFFF; text-align: center; font-size: 90%; background: #FFC0CB; vertical-align: top; height:70%; width: 98%;"> Mise en place du modèle embarqué et des modèles hybrides </div>= | |||
=Séance du 04/12/2023= | =Séance du 04/12/2023= |
Version actuelle datée du 28 janvier 2024 à 16:05
Introduction
Objectifs du projet
Le projet vise à distribuer le calcul à différents endroits du réseau pour optimiser les performances et la sobriété énergétique. Un réseau d'objets connectés, de passerelles, et d'un serveur sera mis en place pour collecter, traiter, et évaluer les données environnementales.
Les données que nous avons décidé de traiter seront les différentes lettres de l'alphabet en langue des signes. L'utilisateur devra réaliser la lettre souhaitée au dessus du capteur, notre intelligence embarquée devra alors déterminer quelle lettre est réalisée et envoyer cette donnée sur un serveur pour afficher sur une page web le résultat.
Mise en œuvre du réseau
Configuration de la Machine Virtuelle
Nous allons devoir créer une machine virtuelle sur Chassiron avec un accès internet IPv6.
Cette machine virtuelle sera déployée grâce à Xen et sera nommée "delgodVM".
La commande lancée pour créer la machine est la suivante :
xen-create-image --hostname delgodVM --force --dist bookworm --size 10G --memory 10G --dir /usr/local/xen --password glopglop --dhcp --bridge bridgeStudents
Nous allons par la suite devoir configurer son réseau, sa résolution DNS, et ses sources de paquets Debian.
Pour configurer le réseau de notre serveur, nous allons devoir modifier le fichier /etc/network/interfaces qui est un fichier de configuration réseau dans lequel les paramètres de l'interface sont spécifiés.
auto enX0
iface enX0 inet6 auto
Pour configurer la résolution DNS de notre serveur, nous allons modifier le fichier /etc/resolv.conf. Ce fichier permet de spécifier la configuration des serveurs de nom (DNS) pour résoudre les noms de domaine en adresses IP. Nous allons donc réaliser la configuration DNS suivante fournie dans le sujet :
domain plil.info
search plil.info
nameserver 2a01:c916:2047:c800:216:3eff:fe82:8a5c
Pour modifier les sources des paquets Debian, nous allons modifier le fichier /etc/apt/sources.list en rajoutant les paquets suivants :
deb http://deb.debian.org/debian bookworm main contrib non-free non-free-firmware
deb-src http://deb.debian.org/debian bookworm main contrib non-free non-free-firmware
Et nous allons aussi supprimer le fichier /etc/apt/apt.conf.d/01proxy.
Raspberry Pi
Nous allons tout d'abord devoir configurer la Raspberry Pi avec une image que nous avons récupéré sur internet.
Ensuite, nous allons devoir connecter la Raspberry Pi au WiFi nommé WIFI_IE_1. Pour configurer la Raspberry Pi, nous allons la connecter en série à un ordinateur. Pour savoir quels pins il est nécessaire de connecter, il faut se fier à l'image ci-dessous. La partie alimentation n'est pas à connecter car trop faible pour alimenter la Raspberry Pi, nous allons donc devoir brancher sur secteur la carte grâce à une alimentation USB.
Après avoir connecté la Raspberry Pi sur l'ordinateur, nous pouvons vérifier sur quelle interface la carte est connectée avec la commande
ls /dev
Par exemple, ici la carte était sur ttyUSB0. Nous pouvons ensuite lancer une liaison série avec minicom grâce à la commande
minicom -s
et les paramètres suivants ttyUSB0 et 115200 8N.
Nous allons ensuite pouvoir configurer le wifi. Pour cela, nous allons dans un terminal de la Raspberry Pi, il faut alors taper la commande suivante afin d'accéder à la configuration du système :
raspi-config
Puis nous sommes allées dans ""System Options"" afin de pouvoir configurer les différentes informations qui vont nous permettre d'avoir le WiFi sur la carte.
Pour vérifier si nos modifications ont bien fonctionné, nous avons réalisé la commande ip a pour vérifier si le wlan0 était bien Up, et nous avons aussi ping google.com pour vérifier si le WiFi fonctionnait. Tout cela était correct.
Informations concernant la Raspberry Pi :
Adresse ip : 172.26.145.129
User pour le SSH : pifou
Objets Connectés
Utilisation d'un STM32F401RE avec un capteur de distance (Nucleo-53L5A1).
Protocole de communication et acquisition des données.
Acquisition des données
Nous allons devoir développer un firmware embarqué afin collecter des données, nous allons pour cela utiliser un code STM32 Cube IDE qui va nous aider à collecter les données du capteur.
La première étape consiste alors a installer STM32 Cube IDE pour l'objet connecté (STM32F401RE) avec capteur de distance (Nucleo-53L5A1).
Cependant, nous avons eu de nombreux problème pour installer ce logiciel sur les zabeths de l'école. Nous avons donc du utiliser un ordinateur personnel sous windows afin d'installer STM32CubeIDE. Afin de téléverser le programme sur notre objet connecté muni du capteur, nous allons devoir connecter celui-ci en série avec notre ordinnateur. Le code sera par la suite placé sur l'objet grâce à l'application.
Pour récupérer les données, nous allons utiliser un programme d'exemple fourni par STMicroelectronics. Pour vérifier le fonctionnement de ce code, nous avons lancé une liaison série sur l'objet connecté (grâce à Putty par exemple) et nous avons bien pu récupérer des données.
Cependant, ce code ne nous convenait pas, nous avons donc du le modifier afin d'obtenir des données brutes affichées par paquets de 16 valeurs.
static void print_result(RANGING_SENSOR_Result_t *Result)
{
int8_t j, k, l;
uint8_t zones_per_line;
zones_per_line = ((Profile.RangingProfile == RS_PROFILE_8x8_AUTONOMOUS) ||
(Profile.RangingProfile == RS_PROFILE_8x8_CONTINUOUS)) ? 8 : 4;
for (j = 0; j < Result->NumberOfZones; j += zones_per_line)
{
for (l = 0; l < RANGING_SENSOR_NB_TARGET_PER_ZONE; l++)
{
/* Print distance and status */
for (k = (zones_per_line - 1); k >= 0; k--)
{
if((l!=0) || (k!=(zones_per_line - 1)) || (j!=0)){
printf(";");
}
if (Result->ZoneResult[j+k].NumberOfTargets > 0)
printf("%ld",(long)Result->ZoneResult[j+k].Distance[l]);
else
printf("0");
}
}
}
printf("\r\n");
}
Protocole de communication
Raspberry Pi & capteur
Tout d'abord, nous allons devoir mettre en oeuvre un protocole de communication permettant l'envoi de données entre la carte (Raspberry Pi) et le capteur.
Nous avons alors décidé de créer un script en Python qui permet au capteur de communiquer ses données à la Raspberry Pi, grâce à la liaison série entre les deux appareils. Comme nous avons vu précédemment, le code installé sur le capteur permet l'envoi de données brutes sur la liaison série, de la forme suivante :
Le code Python que nous avons implémenté sur la Raspberry Pi va permettre de récupérer ces données. Le script se trouve à l'endroit suivant sur la carte ~/Documents/lecture/reader.py. Lors de son lancement, celui ci va obtenir les données envoyées par le capteur, puis il va ranger ces données dans un fichier nommé store_data.csv. Vous pouvez ainsi voir sur la photo ci-dessus l'affichage du fichier store_data.csv se trouvant sur la Raspberry Pi et contenant les données du capteur.
Le script que nous avons écrit est le suivant :
import serial
ser = serial.Serial (
# Port série pour lire les données
port='/dev/ttyACM0',
# Débit de la communication série
baudrate=460800,
# Check pour la parité, ici on ne le fait pas
parity=serial.PARITY_NONE,
# Nombre de commandes séries à accepter avant expiration du délai
timeout=1
)
# Ouverture du fichier "store_data.csv"
file = open(r"store_data.csv","w")
# Mise en pause du programme pendant 1 seconde pour éviter de surcharger le port série
while 1:
x=ser.readline()
output=str(x, 'UTF-8')
file.write(output)
print (x)
file.close()
Raspberry Pi et serveur
Nous allons ensuite mettre en oeuvre un protocole de communication entre la Raspberry Pi et le serveur que nous avons créé. D'après les consignes du sujet, nous devons faire en sorte que ce protocole soit facilement étendu à un grand nombre de Raspberry Pi. C'est pourquoi après quelques recherches, nous avons décide de réaliser un protocole de communication HTTP initié avec un script Python.
Tout d'abord, un script Python sera réalisé sur notre serveur nommé delgodVM pour le spécialiser en un serveur web qui va nous permettre de gérer les requêtes HTTP. Ainsi, sur le serveur delgodVM, nous allons pouvoir lancer notre serveur Python HTTP avec le script créé et avoir le retour suivant :
Le script est le suivant :
import socket
from http.server import HTTPServer, SimpleHTTPRequestHandler
import json
PORT = 8080
class MyHandler(SimpleHTTPRequestHandler):
def do_GET(self):
if self.path == '/ip':
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(f'Your IP address is {self.client_address[0]}'.encode())
return
else:
return SimpleHTTPRequestHandler.do_GET(self)
def do_POST(self):
if self.path== '/data':
content_length = int(self.headers['Content-Length'])
data = self.rfile.read(content_length).decode('utf-8')
data_dict = json.loads(data)
print("Donnees recues:", data_dict)
self.send_response(200)
#Ouverture du fichier pour stocker les donnees
file = open(r"store_data.csv","a")
#Donnes ecrites dans le fichier .csv
file.write(data_dict)
else :
self.send_response(404)
self.end_headers()
class HTTPServerV6(HTTPServer):
address_family = socket.AF_INET6
def main():
server = HTTPServerV6(('::',PORT),MyHandler)
print("Serveur actif sur le port :", PORT)
server.serve_forever()
if __name__ == '__main__':
main()
Pour vérifier (de plus) si notre serveur est bien lancé, nous allons essayer d'y accéder grâce à une page web sur une zabeth de l'école à l'adresse suivante sur le port 8080 configuré dans le fichier de configuration du serveur Python http : http://[2001:660:4401:6050:216:3eff:fe1a:f9c3]:8080
De la Raspberry Pi, on vérifie qu’on peut bien accéder au serveur Python HTTP :
Pour envoyer des données de la Raspberry PI au serveur HTTP, nous avons créé un script Python qui va permettre d'envoyer ces données par des requêtes HTTP. Ce script sera bien évidemment lancé de la Raspberry PI. Pour que l'envoi de données fonctionne, nous devons alors lancer le script permettant le fonctionnement du serveur web sur le serveur delgodVM (commande python3 ./server.py). Et nous devons lancer simultanément le script reader.py sur la Raspberry PI. Le script est le suivant :
import serial
import requests
ser = serial.Serial (
# Port série pour lire les data
port='/dev/ttyACM0',
# Débit de la communication série
baudrate=460800,
# Check ds parités, ici on ne le fait pas
parity=serial.PARITY_NONE,
# Number of serial commands to accept before timing out
timeout=1
)
# Open function to open the file "store_data.csv"
#file = open(r"store_data.csv","w")
url = "http://[2001:660:4401:6050:216:3eff:fe1a:f9c3]:8080/data"
while 1:
url = "http://[2001:660:4401:6050:216:3eff:fe1a:f9c3]:8080/data"
x=ser.readline()
output=str(x, 'UTF-8')
# file.write(output)
data = output
response = requests.post(url,json=data)
print (x)
if reponse.status_code == 200:
print("Donnees envoyees avec succes")
else:
print(f"Échec de l'envoi. Code de statut : {response.status_code}")
print(response.text)
#file.close()
Au final ,nous arrivons bien à recevoir les données sur le serveur Xen, par un paquet unique.
Stockage des Données
Notre serveur Xen reçoit maintenant les données de la carte grâce aux requêtes HTTP.
Nous avons alors décidé de code un script python recevant les données et qui les stockes dans un fichier .csv sur le serveur afin de pouvoir par la suite utiliser les données du capteur. Ainsi, vous pouvez retrouver dans le script du server Python http, certaines parties correspondant à l'enregistrement des données reçues dans un fichier lui aussi nommé store_data.csv se retrouvant bien évidemment sur notre serveur Xen. Le code est le même que précédemment :
import socket
from http.server import HTTPServer, SimpleHTTPRequestHandler
import json
PORT = 8080
class MyHandler(SimpleHTTPRequestHandler):
def do_GET(self):
if self.path == '/ip':
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(f'Your IP address is {self.client_address[0]}'.encode())
return
else:
return SimpleHTTPRequestHandler.do_GET(self)
def do_POST(self):
if self.path== '/data':
content_length = int(self.headers['Content-Length'])
data = self.rfile.read(content_length).decode('utf-8')
data_dict = json.loads(data)
print("Donnees recues:", data_dict)
self.send_response(200)
#Ouverture du fichier pour stocker les donnees
file = open(r"store_data.csv","w")
#Donnes ecrites dans le fichier .csv
file.write(data_dict)
else :
self.send_response(404)
self.end_headers()
class HTTPServerV6(HTTPServer):
address_family = socket.AF_INET6
def main():
server = HTTPServerV6(('::',PORT),MyHandler)
print("Serveur actif sur le port :", PORT)
server.serve_forever()
if __name__ == '__main__':
main()
Et voici le contenu du fichier .csv sur notre serveur :
Les données sont donc correctement transférées du capteur vers la Raspberry Pi en liaison série. Puis par des requêtes http, les données seront envoyées par la Raspberry Pi jusqu'au serveur qui stockera ces données dans un fichier .csv pour par la suite les analyser.
Entraînement du modèle d’apprentissage
Entraînement sur NanoEdge AI Studio
Description
Pour commencer la partie Entraînement, nous avons décidé de commencer par NanoEdge AI Studio. Ce logiciel développé par STMicroelectronics
permet de "créer" une Intelligence Artificielle (IA), de manière simple et guidée.
Pour créer cette IA, nous avons tout d'abord dû connecter la carte STMicroelectronics au PC et lancer NanoEdge AI Studio.
Signaux
Après avoir créé un projet "n-Class", nous avons dû rentrer les signaux que nous voulions faire apprendre. Dans notre cas, ce sont les lettres de A à J qui vont être apprises.
Après avoir fait tous les signaux que nous avons besoins, nous devons lancer le Benchmark.
Benchmark
Le Benchmark est une sorte d'étalon, c'est dans cette partie que l'IA cherche la meilleure librairie possible d'après les signaux envoyés dans la partie précédente.
Validation
Une fois que cette partie est terminée, nous pouvons passer à la partie Validation où nous pouvons importer un signal différent de ceux d'avant afin de choisir la meilleure librairie parmi celles trouvées dans la partie Benchmark.
Emulator
Enfin, nous pouvons tester en temps réel dans la partie Emulator si notre librairie a bien appris les signaux envoyés en entraînement.
Résultats
Nous avons filmé deux cas où l'IA reconnait les symboles C et I de la Langue des Signes Française (LSF) :
Comme vous pouvez le voir, certaines lettres sont mieux perçues que d'autres, cela est dû au fait que NanoEdge AI Studio est très sensible à la luminosité, mais aussi à la position de notre main par rapport au capteur.
Entraînement avec un script Python
Apprentissage avec des données sélectionnées
Dans un premier temps, nous avons créé une intelligence artificielle qui apprend grâce à des fichiers CSV que nous avons fait.
Nous avons choisi d'utiliser la librairie SciKit Learn et Random Forest pour développer notre IA.
La première étape dans notre script Python est de lire tous les CSV que nous lui fournissons (c'est-à-dire les CSV correspondant aux lettres A à K de la Langue des Signes Française). Ensuite, nous concaténons tous les CSV pour en faire une sorte de "mini" base de données. Cette base de données va être scindée en deux : 80 % des données sont faites pour l'apprentissage, et les 20 % restants sont pour le test.
Il faut savoir aussi que nous avons relié une lettre à un chiffre pour plus de facilité, nous avons donc :
Empty = 0, A = 1, B = 2, etc.....
Pour compliquer la tâche, nous avons décidé de mélanger toutes les données pour l'entraînement et le test, c'est-à-dire que le tableau de réponse ne sera pas sous la forme [0 0 0 ... 1 1 1 .... 2 ....]
mais plutôt sous la forme : [7 11 6 6 0 10 5 ...]
Concernant la phase d'apprentissage, nous avons au total 4 variables :
X_train
: données que la carte a envoyées qui sont destinées à l'entrainementy_train
: tableau de réponse (ce qu'on attend comme résultat)X_test
: données que la cartée a envoyées qui sont destinées au testy_test
: tableau de réponse que l'IA a déterminé
Enfin, nous avons mis le paramètre accuracy
qui permet de déterminer le "score" de l'IA.
def random_forest_predict(X_train,y_train,X_test,y_test):
model = RandomForestClassifier()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test,y_pred)
print(y_pred)
print(f"Précision du modèle : {accuracy}")
Après le lancement du test, nous trouvons une accuracy de 1. Cette accuracy parfaite est sûrement due au fait que pour chaque lettre, nous étions dans les mêmes conditions (position, luminosité etc..)
Apprentissage avec des données en temps réel
Après avoir entraîné notre IA, nous allons la tester avec les données qui proviennent directement de la carte avec le capteur Time of Flight. Pour cela, nous avons utilisé la librairie Serial
de python pour obtenir les données en temps réel. Comme les données sont sous forme d’une longue chaîne de caractère, nous avons dû séparer la chaîne en une liste, transformer les chaînes de caractères en entiers et enfin transformer la liste en un tableau.
Après avoir transformé les données dans le bon type, nous avons réutilisé la fonction predict
pour que l’Intelligence Artificielle détecte quelle lettre nous avons fait.
def random_predict_serial(X_train, y_train,model):
ser = serial.Serial()
ser.port = PORT
ser.baudrate = BAUDRATE
ser.timeout = 0.2
ser.open()
print("Is serial open : ", ser.is_open)
print(ser)
lines = ser.readlines()
for line in lines:
# print(line)
decoded_line = str(line, ENCODING)[:-2]
print(decoded_line, " - ", len(decoded_line.split(",")))
if(len(decoded_line.split(","))==64):
test_line = decoded_line.split(",")
test_line_int = [int(x) for x in test_line]
test_array = np.array(test_line_int)
test_array = test_array.reshape(1,-1)
print("TYPE TEST :",type(test_array))
prediction = model.predict(test_array)
print("La prediction :" ,prediction)
ser.close()
print("Is serial open : ", ser.is_open)
Comme vous pouvez le voir, cette fonction redonne à la fin la prédiction du signal.
Nous pouvons voir ici que le programme donne des prédictions que lorsque les données sont conformes et nous pouvons aussi remarquer que les prédictions restent les mêmes donc que l'IA est plutôt stable.
Dans ce cas-ci, la prédiction était "0" ce qui signifie, comme vous l'avez vu plus haut, à Empty.
Mise en place du modèle embarqué et des modèles hybrides
Séance du 04/12/2023
AU cours de cette première séance, nous avons pu prendre en main les outils nécessaires à la réalisation de notre projet. L’objectif était de construire un serveur Xen et de configurer la RaspberryPi.
- Machine virtuelle sur Chassiron avec un accès internet IPv6.
- Image de la Raspberry Pi pour connexion avec le serveur via WiFi : nous avons installé une image ** sur la rapsberry pi
Séance du 18/12/2023
Au cours de cette séance, nous allons tout d'abord
- Développer un firmware embarqué pour collecter les données, avec STM32 Cube IDE et donc un programme permettant d'acquérir les données du capteur.
- Mettre en œuvre un protocole de communication entre les quatre objets et la Raspberry Pi en garantissant que toutes les données sont bien reçues. Utilisation de la liaison série entre la carte et le capteur. Sur le port ttyACM0, avec un script python lancé de la rapsberry. Les données du capteur sont ensuite stockées dans un fichier .csv.
Séance du 19/12/2023
- Mise en place d'un serveur web http sur le serveur delgodVM pour permettre la communication grâce à des requêtes http entre la raspberry pi et le serveur. Tout ceci est réalisé grâce des scripts python.
Séance du 20/12/2023
- Transmission des datas récupérées par le capteur de la raspberry pi vers le serveur http.
- Vérfication de la réception de ces données brutes
- Début de l'apprentissage de notre AI, reconnaissance de la langue des signes