« SE5 ECEAI/eceai 2023/2024/CharleuxHabre » : différence entre les versions

De wiki-se.plil.fr
Aller à la navigation Aller à la recherche
Aucun résumé des modifications
 
(40 versions intermédiaires par 2 utilisateurs non affichées)
Ligne 4 : Ligne 4 :


=Objectif=
=Objectif=
On souhaite lors de ce TP optimiser les performances et la consommation en distribuant la charge de calcul. On utilisera pour cela des capteurs alimentés par un STM32 connectés en série à un Raspberry PI. Ce Raspberry PI communiquera avec un protocole WIFI vers un serveur.
On souhaite lors de ce TP optimiser les performances et la consommation en distribuant la charge de calcul. On utilisera pour cela des capteurs alimentés par un STM32 connectés en série à un Raspberry PI. Ce Raspberry PI communiquera avec un protocole WIFI (on choisira MQTT) vers un serveur. Enfin on entrainera une IA afin de de traiter les données de notre capteur pour reconnaitre un mouvement ou une caracteristique physique.


=Compte rendu des séances=
=Compte rendu des séances=


==Séance du 04/12/2023==
==Séance du 04/12/2023==
===Configuration du serveur===


On créé une machine virtuelle xen '''JK''' sur le serveur chassiron :
On créé une machine virtuelle xen '''JK''' sur le serveur chassiron :
Ligne 33 : Ligne 35 :
On installe finalement Python et Mosquitto MQTT Broker pour plus tard.
On installe finalement Python et Mosquitto MQTT Broker pour plus tard.


* On réalise ensuite des tests sur le capteur en liaison avec la STM32 afin de comprendre son fonctionnement. On utilise en lisant la documentation constructeur le logiciel VL53L5CX nous permettant de visualiser graphiquement les données du capteur et ses caractéristiques en temps réel.
On pourra aussi se connecter en ssh à notre VM.
 
[[Fichier:Test_ssh_JK.png|centré|500px|Capture d'écran pour montrer l'accès en ssh.]]
 
===Découverte du capteur===
 
* On réalise ensuite des tests sur le capteur en liaison avec la STM32 afin de comprendre son fonctionnement en suivant [https://www.st.com/resource/en/product_presentation/quick_start_guide_x-nucleo-53l5a1.pdf ce guide]. On utilise en lisant la documentation constructeur le logiciel VL53L5CX nous permettant de visualiser graphiquement les données du capteur et ses caractéristiques en temps réel.


On télécharge par la suite les différents logiciels nécessaires pour programmer la STM32 (STM32 Cube IDE, Nano Edge Studio, STM32 Cube MX).
On télécharge par la suite les différents logiciels nécessaires pour programmer la STM32 (STM32 Cube IDE, Nano Edge Studio, STM32 Cube MX).


On veut utiliser un Raspberry PI pour communiquer avec le capteur, on installe donc par la même occasion RaspberryPiOS sur une carte MicroSD. Mais avant de l'installer sur la carte MicroSD, on active la connexion en SSH et la connexion à un réseau WIFI, ce qui nous évite d'avoir à nous embêter plus tard. Une fois la carte MicroSD insérée dans le Raspberry Pi, on peut s'y connecter en SSH et éventuellement via VNC Viewer pour avoir une interface graphique. En nous y connectant en SSH, in y installe Python avec les librairies nécessaires
===Configuration du Raspberry===
 
On veut utiliser un Raspberry PI pour communiquer avec le capteur, on installe donc par la même occasion RaspberryPiOS sur une carte MicroSD. Mais avant de l'installer sur la carte MicroSD, on active la connexion en SSH et la connexion à un réseau WIFI, ce qui nous évite d'avoir à nous embêter plus tard. Une fois la carte MicroSD insérée dans le Raspberry Pi, on peut s'y connecter en SSH et éventuellement via VNC Viewer pour avoir une interface graphique. En nous y connectant en SSH, on y installe Python avec les librairies nécessaires
[[Fichier:vnc_viewer_illus.png|centré|300px|Capture d'écran du logiciel VNC viewer affichant l'interface de la Raspberry]]


==Séance du 18/12/2023==
==Séance du 18/12/2023==


On programme la STM32 avec l'IDE fournit par ST, celui-ci permet de compiler et de téléverser le programme vers la carte. On garde le programme d'exemple qui affiche les valeurs du capteurs en 4*4 sur le port UART2. En vérifiant la vitesse de communication du port (460800 baud) avec STM32 Cube MX, on peut visualiser le tableau de donnée avec PuTTY ou Python (sur Windows).
===Programmtion et utilisation de notre capteur===
 
On programme la STM32 avec l'IDE fournit par ST, celui-ci permet de compiler et de téléverser le programme vers la carte. On garde le programme d'exemple (53L5A1_SimpleRanging) qui affiche les valeurs du capteurs en 4*4 sur le port UART2. En vérifiant la vitesse de communication du port (460800 baud) avec STM32 Cube MX, on peut visualiser le tableau de donnée avec PuTTY ou Python (sur Windows).
[[Fichier:STM32CubeMX 53L5A1 SimpleRanging USART2 baudrate.png|vignette|Capture d'écran du logiciel STM32 Cube MX affichant la vitesse de communication de USART2]]
 
===Protocole de communication===


Nous décidons de mettre en forme le tableau de donnée après l'acquisition sur la Raspberry Pi avec un script Python.
Nous décidons de mettre en forme le tableau de donnée après l'acquisition sur la Raspberry Pi avec un script Python.
Ligne 52 : Ligne 68 :


==Séance du 19/12/2023==
==Séance du 19/12/2023==
===Entrainement de l'IA===


On souhaite maintenant entraîner une IA afin de reconnaître des similarités. On enregistre un mouvement que l'on répète 5 à 10 fois et on importe ces données dans NanoEdge AI Studio. Notre but est de détecter et différencier des mouvements de main effectués de la droite vers la gauche aux mouvements effectués de la gauche vers la droite.
On souhaite maintenant entraîner une IA afin de reconnaître des similarités. On enregistre un mouvement que l'on répète 5 à 10 fois et on importe ces données dans NanoEdge AI Studio. Notre but est de détecter et différencier des mouvements de main effectués de la droite vers la gauche aux mouvements effectués de la gauche vers la droite.
Ligne 59 : Ligne 77 :


==Séance du 20/12/2023==
==Séance du 20/12/2023==
===Problèmes et solutions===


On constate que lors de la dernière séance, nous avions formatté les données en mettant une matrice de valeurs par ligne à la place d'une suite d'au moins 16 valeurs par axe. On corrige nos datasets, puis réentraînons l'IA avec les fichiers correctement formattés.
On constate que lors de la dernière séance, nous avions formatté les données en mettant une matrice de valeurs par ligne à la place d'une suite d'au moins 16 valeurs par axe. On corrige nos datasets, puis réentraînons l'IA avec les fichiers correctement formattés.
Ligne 66 : Ligne 86 :
Cependant, il nous faut relativiser quant à ce résultat bien optimiste. En effectuant des tests avec des valeurs différentes de celles de nos fichiers qui ont servi à entraîner l'IA peu importe le mouvement effectué, il était toujours classé dans la même catégorie. On a alors entraîné à nouveau une IA qui serait capable de différencier si un mouvement est effectué par rapport à l'absence de mouvement Encore une fois, les résultats affichés nous promettaient plus de 99% de succès, mais en testant avec de nouvelles valeurs, nous avons observé le même phénomène que précédemment.
Cependant, il nous faut relativiser quant à ce résultat bien optimiste. En effectuant des tests avec des valeurs différentes de celles de nos fichiers qui ont servi à entraîner l'IA peu importe le mouvement effectué, il était toujours classé dans la même catégorie. On a alors entraîné à nouveau une IA qui serait capable de différencier si un mouvement est effectué par rapport à l'absence de mouvement Encore une fois, les résultats affichés nous promettaient plus de 99% de succès, mais en testant avec de nouvelles valeurs, nous avons observé le même phénomène que précédemment.


On s'est alors dit que le problème pourrait provenir de la détection de mouvements et avons alors testé la détection de mouvements statiques. En faisant un ultime benchmarking avec un dataset comprenant soit la détection d'une main posée au-dessus soit l'absence de main. Cette fois-ci, les résultats se sont avérés concluant, on a alors décidé de nous baser sur la librairie fournie suite à cet entraînement.
On s'est alors dit que le problème pourrait provenir de la détection de mouvements et avons alors testé la détection de positions statiques. En faisant un ultime benchmarking avec un dataset comprenant soit la détection d'une main posée au-dessus soit l'absence de main. Cette fois-ci, les résultats se sont avérés concluant, on a alors décidé de nous baser sur la librairie fournie suite à cet entraînement.
 


 
On remarque qu'il n'est pas nécessaire de compiler manuellement un exécutable important la librairie statique. Nano Edge Studio fournit un émulateur de STM32 exécutable sur Linux ou Windows :
On remarque qu'il n'est pas nécessaire de compiler manuellement un exécutable important la librairie statique. Nano Edge Studio fournit un émulateur exécutable sur Linux ou Windows :
<code>
<code>
./NanoEdgeAI_Oneclass_Emulator  neai_oneclass --knowledge_path knowledge.dat --file coords.txt
./NanoEdgeAI_Oneclass_Emulator  neai_oneclass --knowledge_path knowledge.dat --file coords.txt
</code>
</code>


On utilisera cet émulateur sur le serveur et la Raspberry, on peut le transférer avec ces commandes :
On utilisera cet émulateur sur le serveur. On peut le transférer avec ces commandes :
<code>
<code>
scp -r -6 NanoEdgeAI_Emulator_Unix/ root@\[2001:660:4401:6050:216:3eff:fe28:dcac\]:/root/
scp -r -6 NanoEdgeAI_Emulator_Unix/ root@\[2001:660:4401:6050:216:3eff:fe28:dcac\]:/root/
</code>
</code>
On a aussi tenté de faire fonctionner l'émulateur sur Raspberry Pi, cependant, étant donné qu'il a été fait pour tourner sur des architectures x86, cela n'a pas fonctionné sur la Raspberry Pi. On tentera, sans succès, de faire fonctionner un émulateur d'architecture x86 sur lequel on aurait pu faire fonctionner l'émulateur de STM32.
Pour finaliser notre connexion on hébergera une page web sur le serveur, qui affichera l'information envoyée par l'IA, c'est à dire si une main se trouve au-dessus du capteur ou pas. La connexion avec cette page se fera toujours en MQTT, sur le port 8000. Cependant, nous ne parviendrons pas à établir une connexion en IPv6 avec le serveur, mais seulement en IPv4 en 127.0.0.1 (localhost) pour tester le fait que la page s'actualise correctement avec un code Python.
==Suite du projet==
===Serveur web===
Pour pouvoir visualiser le résultat plus facilement, on peut creer une page web :
*Pour tester le fonctionnement, on publiera manuellement un message sur un topic MQTT que l'on viendra recuperer sur une page web avec un script python.
<div style="display: flex;">
    [[File:Mqtt-explorerIE.png|thumb|Publication d'un message sur le topic IE/test en MQTT|300px]]
    [[File:PagewebIE.png|thumb|Reception du message sur une page web|300px]]
</div>
===Démonstration===
[[Fichier:Blåhaj.jpg|droite|vignette|200px|Blåhaj]]
À des fins de démonstrations, on décide d'améliorer notre IA afin de détecter différentes peluches (Blåhaj, Blåhaj bébé et Molang déguisé en abeille). Il s'agit d'une situation sans solution algorithmique triviale, ce qui donne plus d'intérêt à l'utilisation d'intelligence artificielle par rapport à nos essais précédents.
[[Fichier:Blåhaj bébé.jpg|gauche|vignette|250px|Blåhaj bébé]][[Fichier:Molang déguisé en abeille.jpg|droite|vignette|200px|Molang déguisé en abeille]]
Nous entraînons notre IA avec une vue de face sur chacune de ces 3 peluches et avec un dataset sans peluche. Il est intéressant dans cette expérimentation d'observer le comportement de l'IA face à deux peluches quasiment identiques (Blåhaj) mais de dimensions différentes. Nous avons choisi d'entraîner l'IA dans NanoEdge AI Studio avec des datasets contenant 8 lignes de 32 valeurs par axe (avec 16 axes).
[[Fichier:NanoEdgeAIStudio Benchmark.png|gauche|vignette|Benchmark de l'IA]]
Au terme de l'entraînement, l'IA estimera sa précision à environ 75%, mais comme vu précédemment, il s'agit d'une estimation assez optimiste. De plus, le graphe représentant les estimations montre bien le fait que l'IA a plus de mal à détecter certaines peluches (notamment la peluche Molang).
[[Fichier:NanoEdgeAIStudio Validation.png|vignette|Validation de l'IA]]
Nous effectuerons ensuite des essais sur 5 librairies différentes en nous servant d'un nouveau dataset pour chaque peluche. Nous constatons que la précision chute à 37.5% sauf pour la librairie 2, basée sur le modèle SEFR, qui obtient une précision de 62.5%. C'est donc cette dernière que nous conserverons pour la suite de nos tests.
Voici deux vidéos montrant les résultats obtenus par 2 prises de mesure pour le Blåhaj bébé et Molang déguisé en abeille :
[[Fichier:IA Blåhaj bébé.mp4|vignette|Détection de Blåhaj bébé|centré]]
[[Fichier:IA Molang déguisé en abeille.mp4|vignette|Détection de Molang déguisé en abeille|centré]]On peut voir sur ces vidéos que l'IA hésite dans ces deux cas entre deux peluches, mais donne toujours la bonne réponse. Sur des essais pendant de plus longues durées, l'IA parvenait généralement à correctement associer la peluche à son nom. Nous pouvons donc en conclure que nous avons su correctement paramétrer puis entraîner l'intelligence artificielle. Et même si les résultats ne sont pas parfaits, au vu de la taille de la base de données et de la puissance de l'appareil sur lequel l'IA est censée fonctionner, on peut considérer ces résultats comme satisfaisants.
[[Fichier:NanoEdgeAIStudio Émulation.png|centré|vignette|600x600px|Test de reconnaissance de Blåhaj bébé]]
==Annexes==
===Programme Python exécuté sur la Raspberry Pi :===
<syntaxhighlight lang="python3">
import paho.mqtt.client as mqtt
import serial.tools.list_ports
import serial
import sys
def on_connect(client, userdata, flags, rc):
    if rc == 0:
        print("Connecté au serveur MQTT")
    else:
        print(f"Échec de la connexion avec le code de retour {rc}")
def on_disconnect(client, userdata, rc):
    print("Déconnecté du serveur MQTT")
def ecouter_uart(port, baudrate):
    try:
        # serial communication
        ser = serial.Serial(port, baudrate)
        print(f"Écoute en cours sur {port} à {baudrate} bps. Appuyez sur Ctrl+C pour arrêter.")
       
        # MQTT client config
        client = mqtt.Client()
        client.on_connect = on_connect
        client.on_disconnect = on_disconnect
        client.username_pw_set(mqtt_username, mqtt_password)
        client.connect(broker_address, 1883, 60)
        client.loop_start()
       
        try:
            must_write=False # if should add a new line
            with open(fichier, 'r') as f:
                if sum(len(line) for line in f):
                    must_write=True
           
            if must_write: # adds a new line
                with open(fichier, 'a') as f:
                    f.write("\n")
        except FileNotFoundError:
            with open(fichier, 'w') as f: # creates the file
                pass
       
        # some vars
        line=""
        values=[]
        last_first_char=" "
        buffer_size=0
        messages_to_send=[]
       
        while True: # infinite loop...
           
            # reads a Byte
            data = ser.read()
            try:
                data=data.decode('ascii')
            except UnicodeDecodeError:
                data=chr(int(data.hex(), 16)-128)
           
            line+=str(data)
            if data=="\n":
                if line[0]=="|":
                    # if there are values to be read
                    line=line.split()
                    if len(line)>5 and len(line)<21:
                        # if there are some undefined values (X) replaces them by -1
                        num_of_X=0
                        for i,e in enumerate(line):
                            if e=="X":
                                if num_of_X%2==0:
                                    line=line[:i+1]+["X"]+line[i+1:]
                                num_of_X+=1
                    if len(line)==21:
                        # reads the values on the current line
                        error=False
                        for e in (2, 7, 12, 17):
                            if line[e]=="X":
                                line[e]="-1"
                            else:
                                try:
                                    line[e]=line[e].split("\x1b")[0]
                                    if line[e]=="X":
                                        line[e]="-1"
                                except ValueError:
                                    error=True
                        if not error:
                            new_line=[line[e] for e in (2, 7, 12, 17)]
                            values.append([int(e) for e in new_line])
                    last_first_char="|"
                elif line[0]=="-":
                    last_first_char=" "
                elif line[:2]==" -":
                    # if starting to read a new matrix of values
                    if last_first_char!="|" and len(values)==4:
                        print(values) # show then send the previous matrix
                        messages_to_send.append(";".join([";".join([str(e) for e in v]) for v in values]))
                        values=[]
                    last_first_char="-"
                else:
                    last_first_char=line[0]
                line=""
                if len(messages_to_send)==32:
                    # if we have 32 values per axis
                    messages_to_send=";".join(messages_to_send)
                    print(messages_to_send)
                    with open(fichier, 'a') as f:
                        f.write(f"{messages_to_send};") # writes data on a file
                    client.publish(topic, messages_to_send) # sends message through MQTT
                    messages_to_send=[]
               
    # in case of interruption
    except KeyboardInterrupt:
        print("Arrêt de l'écoute.")
    except Exception as e:
        print(f"Erreur: {e}")
    finally:
        # securely closing connections
        if ser.is_open:
            ser.close()
        client.loop_stop()
        client.disconnect()
if __name__ == "__main__":
   
    # MQTT parameters
    broker_address = "2001:660:4401:6050:216:3eff:fe28:dcac" # chassiron VM
    topic = "sensor1"
    mqtt_username = "pifou"
    mqtt_password = "" # confidential
   
    # other params
    port = serial.tools.list_ports.comports(include_links=False)[0].name #/dev/ttyACM0
    if sys.platform.startswith("linux"):
        port = "/dev/" + port
    debit_bauds = 460800
    fichier = "coords.csv"
   
    ecouter_uart(port, debit_bauds)
</syntaxhighlight>
===Programme Python exécuté sur la machine virtuelle :===
<syntaxhighlight lang="python3">
import paho.mqtt.client as mqtt
import os
import subprocess
import json
# MQTT broker settings
broker_address = "2001:660:4401:6050:216:3eff:fe28:dcac"
port = 1883  # MQTT default port
username = "pifou"
password = "pasglop"
topic = "sensor1"
output_file = "coords2.txt"  # File to store received values
def on_message(client, userdata, message):
    received_value = message.payload.decode()
    # Write the received value to a file
    with open(output_file, "w") as file:
        file.write(received_value + "\n")
    #sys
    command = "./NanoEdgeAI_Class_Emulator neai_classification --knowledge_path knowledge.dat --file " + output_file
    process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    output, error = process.communicate()
    output = output.decode('utf-8')
    error = error.decode('utf-8')
    if len(error) <= 0:
        error = "None."
    output = json.loads(output)
    result = output["results"][0]["class_name"]
    if result == "coords-Rien":
        print("rien")
    elif result == "coords-Pasrien":
        print("main")
# MQTT client setup
client = mqtt.Client()
client.username_pw_set(username=username, password=password)
client.on_message = on_message
# Connect to the MQTT broker
client.connect(broker_address, port=port)
client.subscribe(topic)
# Loop to continuously listen for incoming messages
client.loop_forever()
</syntaxhighlight>
===Programme Python pour le serveur web (testé seulement en ipv4)===
<syntaxhighlight lang="python3">
from flask import Flask, render_template
import json
import paho.mqtt.client as mqtt
app = Flask(__name__)
mqtt_broker_address = "192.168.1.49"
mqtt_topic = "IE/test"
mqtt_username = ""
mqtt_password = ""
mqtt_client = mqtt.Client()
latest_message = ""
def on_message(client, userdata, msg):
    global latest_message
    payload = msg.payload.decode('utf-8')
    print(f"Received message: {payload}")
    latest_message = payload
@app.route('/')
def index():
    return render_template('index.html', message=latest_message)
if __name__ == '__main__':
    mqtt_client.username_pw_set(username=mqtt_username, password=mqtt_password)
    mqtt_client.connect(mqtt_broker_address, 1883, 60)
    mqtt_client.subscribe(mqtt_topic)
    mqtt_client.on_message = on_message
    mqtt_client.loop_start()
    app.run(debug=True)
</syntaxhighlight>
===Page web===
<syntaxhighlight lang="html">
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MQTT Topic Display</title>
    <style>
        body {
            background-color: #1a1a1a;
            color: #ffffff;
            font-weight: bold;
            font-family: 'Arial', sans-serif;
            margin: 0;
            padding: 20px;
            text-align: center;
        }
        h1 {
            color: #33ccff;
        }
        #mqtt-data {
            margin-top: 20px;
            font-size: 20px;
        }
    </style>
</head>
<body>
    <h1>MQTT Topic Display</h1>
    <div id="mqtt-data">{{ message }}</div>
    <script>
        setInterval(function() {
            location.reload();
        }, 5000);  // Reload every 5 seconds
    </script>
</body>
</html>
</syntaxhighlight>

Version actuelle datée du 28 janvier 2024 à 21:33

Binôme

  • Julien CHARLEUX
  • Karl HABRE

Objectif

On souhaite lors de ce TP optimiser les performances et la consommation en distribuant la charge de calcul. On utilisera pour cela des capteurs alimentés par un STM32 connectés en série à un Raspberry PI. Ce Raspberry PI communiquera avec un protocole WIFI (on choisira MQTT) vers un serveur. Enfin on entrainera une IA afin de de traiter les données de notre capteur pour reconnaitre un mouvement ou une caracteristique physique.

Compte rendu des séances

Séance du 04/12/2023

Configuration du serveur

On créé une machine virtuelle xen JK sur le serveur chassiron :

xen-create-image --hostname=JK --force --dist=bookworm --size=10G --memory=10G --dir=/usr/local/xen --password= --dhcp --bridge=bridgeStudents

On configure ensuite la VM pour avoir accès à internet en IPV6 :

  • La configuration de la carte réseau en IPV6 s’effectue dans le fichier /etc/network/interfaces :

auto enX0

iface enX0 inet6 auto

  • On configure le DNS dans le fichier /etc/resolv.conf :

domain plil.info

search plil.info

nameserver 2a01:c916:2047:c800:216:3eff:fe82:8a5c

  • On ajoute les sources de paquets et mis à jour dans le fichier /etc/apt/sources.list :

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

  • Enfin on supprime /etc/apt/apt.conf.d/01proxy pour pouvoir utiliser le gestionnaire de paquet apt. On peut par la suite autoriser l'accès root en ssh.

On installe finalement Python et Mosquitto MQTT Broker pour plus tard.

On pourra aussi se connecter en ssh à notre VM.

Capture d'écran pour montrer l'accès en ssh.

Découverte du capteur

  • On réalise ensuite des tests sur le capteur en liaison avec la STM32 afin de comprendre son fonctionnement en suivant ce guide. On utilise en lisant la documentation constructeur le logiciel VL53L5CX nous permettant de visualiser graphiquement les données du capteur et ses caractéristiques en temps réel.

On télécharge par la suite les différents logiciels nécessaires pour programmer la STM32 (STM32 Cube IDE, Nano Edge Studio, STM32 Cube MX).

Configuration du Raspberry

On veut utiliser un Raspberry PI pour communiquer avec le capteur, on installe donc par la même occasion RaspberryPiOS sur une carte MicroSD. Mais avant de l'installer sur la carte MicroSD, on active la connexion en SSH et la connexion à un réseau WIFI, ce qui nous évite d'avoir à nous embêter plus tard. Une fois la carte MicroSD insérée dans le Raspberry Pi, on peut s'y connecter en SSH et éventuellement via VNC Viewer pour avoir une interface graphique. En nous y connectant en SSH, on y installe Python avec les librairies nécessaires

Capture d'écran du logiciel VNC viewer affichant l'interface de la Raspberry

Séance du 18/12/2023

Programmtion et utilisation de notre capteur

On programme la STM32 avec l'IDE fournit par ST, celui-ci permet de compiler et de téléverser le programme vers la carte. On garde le programme d'exemple (53L5A1_SimpleRanging) qui affiche les valeurs du capteurs en 4*4 sur le port UART2. En vérifiant la vitesse de communication du port (460800 baud) avec STM32 Cube MX, on peut visualiser le tableau de donnée avec PuTTY ou Python (sur Windows).

Capture d'écran du logiciel STM32 Cube MX affichant la vitesse de communication de USART2

Protocole de communication

Nous décidons de mettre en forme le tableau de donnée après l'acquisition sur la Raspberry Pi avec un script Python.

Afin de communiquer ces données de la Raspberry PI au serveur, on décide d'utiliser le protocole MQTT, celui-ci nous semble adapté pour l'IOT et les communications à faible délais. On a alors installé Mosquitto MQTT Broker sur notre serveur et ajouté un mot de passe.

L'utilisation de MQTT permet aussi de simplifier l'ajout d'un autre Raspberry par le futur puisqu'il est facilement possible de gérer plusieurs appareils notamment avec le système de topics.

Une fois les données formatées avec Python sur la Raspberry PI, elles sont publiées sur un topic MQTT (sensor1) propre au capteur, hébergé sur le serveur. On peut récupérer ces données en écoutant le topic si on se situe sur le même réseau avec le mot de passe. On peut alors réaliser un autre script permettant de récupérer les données et les stocker dans un fichier côté serveur.

Séance du 19/12/2023

Entrainement de l'IA

On souhaite maintenant entraîner une IA afin de reconnaître des similarités. On enregistre un mouvement que l'on répète 5 à 10 fois et on importe ces données dans NanoEdge AI Studio. Notre but est de détecter et différencier des mouvements de main effectués de la droite vers la gauche aux mouvements effectués de la gauche vers la droite.

On passe notre séance à comprendre comment mettre en forme nos datasets pour les implémenter dans NanoEdge AI Studio, et finissons par réussir à faire un benchmarking, mais nous rendrons compte plus tard que nous avions incorrectement formatté les données. Le logiciel nous a tout de même donné en sortie une librairie (fichiers .a, .h..) que l'on peut importer dans un fichier C. On devrait pouvoir utiliser la fonction définie dans le .h pour détecter notre mouvement.

Séance du 20/12/2023

Problèmes et solutions

On constate que lors de la dernière séance, nous avions formatté les données en mettant une matrice de valeurs par ligne à la place d'une suite d'au moins 16 valeurs par axe. On corrige nos datasets, puis réentraînons l'IA avec les fichiers correctement formattés.

Les résultats nous semblaient peu satisfaisant (de l'ordre de 50% de succès, soit du quasi aléatoire). On avait en fait créé un projet de type 1-Class Classification au lieu de n-Class Classification. En faisant à nouveau un benchmarking, on obtient un score proche de 100%.

Cependant, il nous faut relativiser quant à ce résultat bien optimiste. En effectuant des tests avec des valeurs différentes de celles de nos fichiers qui ont servi à entraîner l'IA peu importe le mouvement effectué, il était toujours classé dans la même catégorie. On a alors entraîné à nouveau une IA qui serait capable de différencier si un mouvement est effectué par rapport à l'absence de mouvement Encore une fois, les résultats affichés nous promettaient plus de 99% de succès, mais en testant avec de nouvelles valeurs, nous avons observé le même phénomène que précédemment.

On s'est alors dit que le problème pourrait provenir de la détection de mouvements et avons alors testé la détection de positions statiques. En faisant un ultime benchmarking avec un dataset comprenant soit la détection d'une main posée au-dessus soit l'absence de main. Cette fois-ci, les résultats se sont avérés concluant, on a alors décidé de nous baser sur la librairie fournie suite à cet entraînement.

On remarque qu'il n'est pas nécessaire de compiler manuellement un exécutable important la librairie statique. Nano Edge Studio fournit un émulateur de STM32 exécutable sur Linux ou Windows : ./NanoEdgeAI_Oneclass_Emulator neai_oneclass --knowledge_path knowledge.dat --file coords.txt

On utilisera cet émulateur sur le serveur. On peut le transférer avec ces commandes : scp -r -6 NanoEdgeAI_Emulator_Unix/ root@\[2001:660:4401:6050:216:3eff:fe28:dcac\]:/root/

On a aussi tenté de faire fonctionner l'émulateur sur Raspberry Pi, cependant, étant donné qu'il a été fait pour tourner sur des architectures x86, cela n'a pas fonctionné sur la Raspberry Pi. On tentera, sans succès, de faire fonctionner un émulateur d'architecture x86 sur lequel on aurait pu faire fonctionner l'émulateur de STM32.

Pour finaliser notre connexion on hébergera une page web sur le serveur, qui affichera l'information envoyée par l'IA, c'est à dire si une main se trouve au-dessus du capteur ou pas. La connexion avec cette page se fera toujours en MQTT, sur le port 8000. Cependant, nous ne parviendrons pas à établir une connexion en IPv6 avec le serveur, mais seulement en IPv4 en 127.0.0.1 (localhost) pour tester le fait que la page s'actualise correctement avec un code Python.

Suite du projet

Serveur web

Pour pouvoir visualiser le résultat plus facilement, on peut creer une page web :

  • Pour tester le fonctionnement, on publiera manuellement un message sur un topic MQTT que l'on viendra recuperer sur une page web avec un script python.
Publication d'un message sur le topic IE/test en MQTT
Reception du message sur une page web

Démonstration

Blåhaj

À des fins de démonstrations, on décide d'améliorer notre IA afin de détecter différentes peluches (Blåhaj, Blåhaj bébé et Molang déguisé en abeille). Il s'agit d'une situation sans solution algorithmique triviale, ce qui donne plus d'intérêt à l'utilisation d'intelligence artificielle par rapport à nos essais précédents.

Blåhaj bébé
Molang déguisé en abeille








Nous entraînons notre IA avec une vue de face sur chacune de ces 3 peluches et avec un dataset sans peluche. Il est intéressant dans cette expérimentation d'observer le comportement de l'IA face à deux peluches quasiment identiques (Blåhaj) mais de dimensions différentes. Nous avons choisi d'entraîner l'IA dans NanoEdge AI Studio avec des datasets contenant 8 lignes de 32 valeurs par axe (avec 16 axes).

Benchmark de l'IA


Au terme de l'entraînement, l'IA estimera sa précision à environ 75%, mais comme vu précédemment, il s'agit d'une estimation assez optimiste. De plus, le graphe représentant les estimations montre bien le fait que l'IA a plus de mal à détecter certaines peluches (notamment la peluche Molang).

Validation de l'IA



Nous effectuerons ensuite des essais sur 5 librairies différentes en nous servant d'un nouveau dataset pour chaque peluche. Nous constatons que la précision chute à 37.5% sauf pour la librairie 2, basée sur le modèle SEFR, qui obtient une précision de 62.5%. C'est donc cette dernière que nous conserverons pour la suite de nos tests.

Voici deux vidéos montrant les résultats obtenus par 2 prises de mesure pour le Blåhaj bébé et Molang déguisé en abeille :

On peut voir sur ces vidéos que l'IA hésite dans ces deux cas entre deux peluches, mais donne toujours la bonne réponse. Sur des essais pendant de plus longues durées, l'IA parvenait généralement à correctement associer la peluche à son nom. Nous pouvons donc en conclure que nous avons su correctement paramétrer puis entraîner l'intelligence artificielle. Et même si les résultats ne sont pas parfaits, au vu de la taille de la base de données et de la puissance de l'appareil sur lequel l'IA est censée fonctionner, on peut considérer ces résultats comme satisfaisants.

Test de reconnaissance de Blåhaj bébé

Annexes

Programme Python exécuté sur la Raspberry Pi :

import paho.mqtt.client as mqtt
import serial.tools.list_ports
import serial
import sys

def on_connect(client, userdata, flags, rc):
    if rc == 0:
        print("Connecté au serveur MQTT")
    else:
        print(f"Échec de la connexion avec le code de retour {rc}")

def on_disconnect(client, userdata, rc):
    print("Déconnecté du serveur MQTT")

def ecouter_uart(port, baudrate):
    try:
        # serial communication
        ser = serial.Serial(port, baudrate)
        print(f"Écoute en cours sur {port} à {baudrate} bps. Appuyez sur Ctrl+C pour arrêter.")
        
        # MQTT client config
        client = mqtt.Client()
        client.on_connect = on_connect
        client.on_disconnect = on_disconnect
        client.username_pw_set(mqtt_username, mqtt_password)
        client.connect(broker_address, 1883, 60)
        client.loop_start()
        
        try:
            must_write=False # if should add a new line
            with open(fichier, 'r') as f:
                if sum(len(line) for line in f):
                    must_write=True
            
            if must_write: # adds a new line
                with open(fichier, 'a') as f:
                    f.write("\n")
        except FileNotFoundError:
            with open(fichier, 'w') as f: # creates the file
                pass
        
        # some vars
        line=""
        values=[]
        last_first_char=" "
        buffer_size=0
        messages_to_send=[]
        
        while True: # infinite loop...
            
            # reads a Byte
            data = ser.read()
            try:
                data=data.decode('ascii')
            except UnicodeDecodeError:
                data=chr(int(data.hex(), 16)-128)
            
            line+=str(data)
            if data=="\n":
                if line[0]=="|":
                    # if there are values to be read
                    line=line.split()
                    if len(line)>5 and len(line)<21:
                        # if there are some undefined values (X) replaces them by -1
                        num_of_X=0
                        for i,e in enumerate(line):
                            if e=="X":
                                if num_of_X%2==0:
                                    line=line[:i+1]+["X"]+line[i+1:]
                                num_of_X+=1
                    if len(line)==21:
                        # reads the values on the current line
                        error=False
                        for e in (2, 7, 12, 17):
                            if line[e]=="X":
                                line[e]="-1"
                            else:
                                try:
                                    line[e]=line[e].split("\x1b")[0]
                                    if line[e]=="X":
                                        line[e]="-1"
                                except ValueError:
                                    error=True
                        if not error:
                            new_line=[line[e] for e in (2, 7, 12, 17)]
                            values.append([int(e) for e in new_line])
                    last_first_char="|"
                elif line[0]=="-":
                    last_first_char=" "
                elif line[:2]==" -":
                    # if starting to read a new matrix of values
                    if last_first_char!="|" and len(values)==4:
                        print(values) # show then send the previous matrix
                        messages_to_send.append(";".join([";".join([str(e) for e in v]) for v in values]))
                        values=[]
                    last_first_char="-"
                else:
                    last_first_char=line[0]
                line=""
                if len(messages_to_send)==32:
                    # if we have 32 values per axis
                    messages_to_send=";".join(messages_to_send)
                    print(messages_to_send)
                    with open(fichier, 'a') as f:
                        f.write(f"{messages_to_send};") # writes data on a file
                    client.publish(topic, messages_to_send) # sends message through MQTT
                    messages_to_send=[]
                
    # in case of interruption
    except KeyboardInterrupt:
        print("Arrêt de l'écoute.")
    except Exception as e:
        print(f"Erreur: {e}")
    finally:
        # securely closing connections
        if ser.is_open:
            ser.close()
        client.loop_stop()
        client.disconnect()

if __name__ == "__main__":
    
    # MQTT parameters
    broker_address = "2001:660:4401:6050:216:3eff:fe28:dcac" # chassiron VM
    topic = "sensor1"
    mqtt_username = "pifou"
    mqtt_password = "" # confidential
    
    # other params
    port = serial.tools.list_ports.comports(include_links=False)[0].name #/dev/ttyACM0
    if sys.platform.startswith("linux"):
        port = "/dev/" + port
    debit_bauds = 460800
    fichier = "coords.csv"
    
    ecouter_uart(port, debit_bauds)

Programme Python exécuté sur la machine virtuelle :

import paho.mqtt.client as mqtt
import os
import subprocess
import json

# MQTT broker settings
broker_address = "2001:660:4401:6050:216:3eff:fe28:dcac"
port = 1883  # MQTT default port
username = "pifou"
password = "pasglop"
topic = "sensor1"
output_file = "coords2.txt"  # File to store received values

def on_message(client, userdata, message):
    received_value = message.payload.decode()

    # Write the received value to a file
    with open(output_file, "w") as file:
        file.write(received_value + "\n")

    #sys
    command = "./NanoEdgeAI_Class_Emulator neai_classification --knowledge_path knowledge.dat --file " + output_file
    process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    output, error = process.communicate()
    output = output.decode('utf-8')
    error = error.decode('utf-8')
    if len(error) <= 0:
        error = "None."
    output = json.loads(output)
    result = output["results"][0]["class_name"]
    if result == "coords-Rien":
        print("rien")
    elif result == "coords-Pasrien":
        print("main")


# MQTT client setup
client = mqtt.Client()
client.username_pw_set(username=username, password=password)
client.on_message = on_message

# Connect to the MQTT broker
client.connect(broker_address, port=port)
client.subscribe(topic)

# Loop to continuously listen for incoming messages
client.loop_forever()

Programme Python pour le serveur web (testé seulement en ipv4)

from flask import Flask, render_template
import json
import paho.mqtt.client as mqtt

app = Flask(__name__)

mqtt_broker_address = "192.168.1.49"
mqtt_topic = "IE/test"
mqtt_username = ""
mqtt_password = ""

mqtt_client = mqtt.Client()

latest_message = ""

def on_message(client, userdata, msg):
    global latest_message
    payload = msg.payload.decode('utf-8')
    print(f"Received message: {payload}")
    latest_message = payload

@app.route('/')
def index():
    return render_template('index.html', message=latest_message)

if __name__ == '__main__':
    mqtt_client.username_pw_set(username=mqtt_username, password=mqtt_password)
    mqtt_client.connect(mqtt_broker_address, 1883, 60)
    mqtt_client.subscribe(mqtt_topic)
    mqtt_client.on_message = on_message
    mqtt_client.loop_start()

    app.run(debug=True)

Page web

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MQTT Topic Display</title>
    <style>
        body {
            background-color: #1a1a1a;
            color: #ffffff;
            font-weight: bold;
            font-family: 'Arial', sans-serif;
            margin: 0;
            padding: 20px;
            text-align: center;
        }

        h1 {
            color: #33ccff;
        }

        #mqtt-data {
            margin-top: 20px;
            font-size: 20px;
        }
    </style>
</head>
<body>
    <h1>MQTT Topic Display</h1>
    <div id="mqtt-data">{{ message }}</div>

    <script>
        setInterval(function() {
            location.reload();
        }, 5000);  // Reload every 5 seconds
    </script>
</body>
</html>