TP 1 : Développement d'une application de collecte de données et de supervision via sockets
🎯 Objectifs
- Se familiariser avec la programmation de sockets TCP/UDP
- Comprendre le modèle client-serveur
🔧 Matériel nécessaire
- Ordinateur avec Python 3.x installé
- Accès au réseau local
Important
L’évaluation de ce TP se fait pendant la séance. Pensez à faire valider régulièrement votre avancement auprès de l’enseignant.
Description de l’application de supervision
On souhaite collecter les tensions et courants d’un ensemble de capteurs placés sur un système de type micro-grid électrique. Ces données servent ensuite aux autres applications pour planifier, contrôler et optimiser les opérations du micro-grid. Afin de collecter ces données, nous allons développer une application Python utilisant les sockets pour récupérer les données des capteurs.
Note
Cette démarche est applicable à d’autres types de données et systèmes nécessitant une collecte et une supervision en temps réel.
L’application à développer collecte des données simulées provenant de deux capteurs :
- un capteur de tension qui envoie ses données via UDP ;
- un capteur de courant qui envoie ses données via TCP.
Chacun des capteurs génère des données aléatoires dans des plages spécifiques :
- Tension : 220 ± 10V ;
- Courant : 5 ± 1A.
Une fois générées, les données sont stockées dans un fichier json.
L’application doit récupérer les données, les afficher dans la console et permettre à l’utilisateur de visualiser les graphiques correspondants grâce à la bibliothèque matplotlib.
Note
Des squelettes de code ainsi que des exemples de code socket client/serveur sont disponibles sur Arche.
Tip
Pensez à tester vos codes dès que vous implémentez une nouvelle fonction pour faciliter le débogage.
Architecture de l’application
┌─────────────────┐ ┌─────────────────┐
│ voltageSensor │ UDP:8888 │ |
│ (serveur) │◄───────────────────┤ │
└─────────────────┘ │ Application │
│ supervision │
┌─────────────────┐ │ (clients) │
│ currentSensor │ TCP:9999 │ │
│ (serveur) │◄───────────────────┤ │
└─────────────────┘ └─────────────────┘
Partie 1 : Modules d’acquisition de données
Dans cette première partie, nous allons programmer les deux modules de simulation de capteurs.
La classe Sensor définie dans le fichier sensor.py fournit une structure de base pour les capteurs. Elle possède un attribut nb qui indique le nombre de valeurs à générer, ainsi qu’un attribut mesures qui stocke les valeurs générées dans un dictionnaire.
Q1 (2 pts). Créez un fichier voltageSensor.py contenant la définition de la classe voltageSensor qui hérite de la classe Sensor. Cette classe doit inclure une méthode generate() qui :
- génère
nbvaleurs aléatoires de tension tirées uniformément entre 210V et 230V ; - génère
nbinstants de mesure (en secondes) selon la formule \(i + \text{random}(0, 1), i \in [0, nb-1]\) ; - stocke ces mesures dans le dictionnaire
mesuresdont les clés sont les instants et les valeurs sont les tensions correspondantes ; - stocke le dictionnaire
mesuresdans un fichierjsonnommévoltage.json.
Note
Utilisez la fonction dump() du module json pour écrire un dictionnaire dans un fichier json.
Tip
Ajouter le paramètre indent=4 à la fonction dump() permet d’obtenir un fichier json plus lisible.
Les commandes
>>> vs = voltageSensor(5)
>>> vs.generate()
génèrent par exemple le fichier voltage.json suivant :
{
"0.1567103252601113": 225.55530087394834,
"1.2164839663235272": 224.70993120869522,
"2.8837368183033956": 215.0099734534407,
"3.129765176593175": 217.15948563534192,
"4.300469195840086": 215.3837650640686
}
Q2 (3 pts). Implémentez une méthode serve() qui crée un serveur UDP en écoute sur le port 8888. Ce serveur doit générer les mesures de tension et les envoyer au client dès qu’une requête est reçue.
Note
Utilisez la fonction dumps() du module json pour convertir un dictionnaire en chaîne de caractères JSON.
Note
Utilisez la méthode encode() pour encoder une chaîne de caractères au format utf-8 avant de l’envoyer via le socket.
Testez votre serveur UDP avec le script UDPclient.py fourni.
Warning
Vous devrez lancer plusieurs consoles (shell) pour exécuter les différents scripts (serveur et client).
Vous devriez obtenir une sortie similaire à celle-ci dans la console du client :
Input lowercase sentence: test
{
"0.1567103252601113": 225.55530087394834,
"1.2164839663235272": 224.70993120869522,
"2.8837368183033956": 215.0099734534407,
"3.129765176593175": 217.15948563534192,
"4.300469195840086": 215.3837650640686
}
Q3 (4 pts). Créez un fichier currentSensor.py contenant la définition de la classe currentSensor permettant de simuler un capteur de courant. Les instants de mesure doivent être identiques à ceux du capteur de tension. Ce capteur doit servir les données via un serveur TCP en écoute sur le port 9999.
Testez votre serveur TCP avec le script TCPclient.py fourni.
Partie 2 : Module de supervision
Dans cette seconde partie, nous allons développer le module de supervision qui récupère les données des deux capteurs, les affiche dans la console et permet de les visualiser graphiquement.
Q4 (2 pts). Dans le fichier app.py, coder la fonction getVoltage() qui :
- crée un client UDP vers un serveur écoutant sur le port
8888; - récupère les mesures du capteur de tension ;
- retourne un dictionnaire qui contient les données reçues.
Note
Utilisez la fonction loads() du module json pour convertir une chaîne de caractères JSON en dictionnaire Python.
Q5 (2 pts). Dans le fichier app.py, coder la fonction getCurrent() qui retourne un dictionnaire contenant les mesures du capteur de courant.
Le fichier app.py contient déjà deux fonctions :
displayData()permet d’afficher le contenu d’un dictionnaire de mesures donné en paramètre dans la console ;menu()affiche dans la console un menu demandant à l’utilisateur une opération à réaliser1 - Collecter données du capteur de tension 2 - Collecter données du capteur de courant 3 - Visualiser les données
Q6 (1 pt). Dans le fichier app.py, coder la fonction dump() qui :
- prend deux arguments en entrée :
filename: le nom du fichiermesures: un dictionnaire de mesures
- sauvegarde les données du dictionnaires
mesuresdans le fichier texte nomméfilenameselon le format suivant :0.1567103252601113 225.55530087394834 1.2164839663235272 224.70993120869522 2.8837368183033956 215.0099734534407 3.129765176593175 217.15948563534192 4.300469195840086 215.3837650640686
Vous pourrez tester votre fonction avec le code suivant :
>>> mesures = { "1": 10, "2": 20, "3": 30 }
>>> dump("test.txt", mesures)
Le fichier test.txt doit contenir :
1 10
2 20
3 30
Q7 (1pt). Complétez la fonction menu() et testez votre application de supervision en ayant exécuté au préalable les serveurs des capteurs de tension et de courant sur la même machine (localhost). L’application doit afficher le menu et permettre d’afficher les données récupérées dans la console et les sauvegarder dans des fichiers texte voltage.txt et current.txt.
Q8 (3pts). Modifiez votre application de supervision pour récupérer les données de deux capteurs dont les serveurs servent sur deux machines différentes de la machine locale. Après avoir lancé Wireshark, testez votre application, analysez les échanges de paquets entre les machines et expliquez-les à l’enseignant.
Tip
Utilisez des filtres d’affichage dans Wireshark pour ne visualiser que les paquets UDP et TCP échangés entre les machines.
Q9 (2pts). Dans le fichier app.py, coder la fonction visualize() qui utilise la bibliothèque matplotlib pour afficher trois graphiques à partir des dictionnaires collectés :
- un graphique de la tension en fonction du temps ;
- un graphique du courant en fonction du temps ;
- un graphique de la puissance instantanée en fonction du temps.
Warning
Assurez-vous que les capteurs soient paramétrés avec le même nb pour pouvoir calculer la puissance instantanée.