Système d'affichage dynamique : Différence entre versions
(→Raspberry pi) |
(→Visuel) |
||
(48 révisions intermédiaires par 2 utilisateurs non affichées) | |||
Ligne 7 : | Ligne 7 : | ||
* Bash | * Bash | ||
* Python | * Python | ||
+ | |||
+ | Technologies utilisées : | ||
+ | * WSGI Flask | ||
+ | * Serveur Web Apache | ||
== Matériel == | == Matériel == | ||
− | * [https:// | + | * [https://fr.wikipedia.org/wiki/Raspberry_Pi#Mod%C3%A8le_Zero_2_WH Raspberry Pi Zero 2 WH] + Câble d'alimentation |
− | + | * [https://wiki.dfrobot.com/ESP32_E_ink_Screen_4.7inch_SKU_DFR0835 E ink Screen 4.7inch SKU DFR0835 + ESP32] | |
− | * [https://wiki.dfrobot.com/ESP32_E_ink_Screen_4.7inch_SKU_DFR0835 E ink Screen 4.7inch SKU DFR0835] | ||
* Boite : PLA, bois et vis | * Boite : PLA, bois et vis | ||
* 1 Petite breadboard | * 1 Petite breadboard | ||
Ligne 35 : | Ligne 38 : | ||
Beaucoup de R&D. | Beaucoup de R&D. | ||
+ | |||
+ | Préparation de documents de suivi pour le client avant les rendez-vous. | ||
+ | |||
+ | Choix d'utiliser la methode agile. Retour client et sprint hebdomadaire. | ||
Pour plus de précision techniques, n'hésitez pas à allez voir notre [https://gitlab.imt-atlantique.fr/tn6_c_e_jj/carae_systeme_affichage_dynamique Gitlab] <br> | Pour plus de précision techniques, n'hésitez pas à allez voir notre [https://gitlab.imt-atlantique.fr/tn6_c_e_jj/carae_systeme_affichage_dynamique Gitlab] <br> | ||
Ligne 42 : | Ligne 49 : | ||
==== Raspberry pi ==== | ==== Raspberry pi ==== | ||
− | Il s'agit d'un | + | Il s'agit d'un micro-ordinateur Raspberry PI Zero 2 WH qui nous sert de serveur. Ce choix a été fait car la puissance de calcule et la mémoire RAM de l'esp32 étaient trop faibles, nous avons donc décidé de réaliser le [https://parseur.com/fr/blog/parsing-de-donnees parsing] sur notre serveur. Nous avons choisi ce model de Raspberry car il possède un module Wifi et ne coûte pas trop cher. |
− | Notre serveur tourne | + | Notre serveur tourne avec la distribution Raspbian, des scripts Bash, un serveur web Apache et Flask ([https://en.wikipedia.org/wiki/Web_Server_Gateway_Interface WSGI] python). Tutoriel pas à pas [https://gitlab.imt-atlantique.fr/tn6_c_e_jj/carae_systeme_affichage_dynamique/-/blob/main/serveur/README.md?ref_type=heads ici]. |
− | Sur ce raspberry, nous faisons tourner | + | Sur ce raspberry, nous faisons tourner une application <b>python</b> qui nous permet de récupérer (Parser) les informations provenant de la page web des réservations des salles CARAE à l'IMT. Cette application converti les données bruts tirés de la page html en fichier JSON contenant la salle, l'heure et le nom des réservations. |
(Exemple de fichier JSON : {salle : D02-121A [{heure : 10:00 à 11:00}{nom: Thierry Margoulin}] | (Exemple de fichier JSON : {salle : D02-121A [{heure : 10:00 à 11:00}{nom: Thierry Margoulin}] | ||
− | + | Cette application python utilise plusieurs bibliothèques : | |
− | [https://pypi.org/project/requests/ requests] : Requêtes Http. | + | * [https://pypi.org/project/requests/ requests] : Requêtes Http. |
− | [https://docs.python.org/3/library/re.html re ] : Expression régulière. | + | * [https://docs.python.org/3/library/re.html re ] : Expression régulière. |
− | [https://docs.python.org/fr/3/library/json.html json ] : Json. | + | * [https://docs.python.org/fr/3/library/json.html json ] : Json. |
− | [https://pypi.org/project/beautifulsoup4/ beautifulsoup] : Parsing. | + | * [https://pypi.org/project/beautifulsoup4/ beautifulsoup] : Parsing. |
==== ESP32 ==== | ==== ESP32 ==== | ||
− | Notre micro-controlleur, lui aussi équipé de la wifi | + | Notre micro-controlleur, lui aussi équipé de la wifi, contrôle l'affichage sur notre écran e-ink. Il reçoit les JSON de la Raspberry Pi et extrait les données pour les afficher sur notre écran E-Ink. |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | Notre code est construit grâce à l'import de plusieurs bibliothèques : | |
− | [https://docs.arduino.cc/libraries/ | + | * [https://arduinojson.org/ ArduinoJson] : Permet à notre code Arduino de comprendre et de deserialiser (Découper) les données Jsons reçues. |
− | + | * [https://docs.arduino.cc/language-reference/en/functions/communication/wire/ Wire] : Permet la communication. | |
− | [https://docs.arduino.cc/libraries/ | + | * [https://docs.arduino.cc/libraries/wifi/ WiFi] : Permet la connection réseau. |
− | + | * [https://docs.arduino.cc/libraries/httpclient/ HTTPClient] : Permet les requêtes http. | |
− | [https://github.com/ | + | * [https://github.com/Xinyuan-LilyGO/LilyGo-EPD47 LilyGo-EPD47] : Driver pour notre écran 4.7 pouces |
+ | * [https://docs.arduino.cc/language-reference/en/functions/communication/SPI/ SPI] : Permet la communication via SPI (Serial Peripheral Interface) | ||
=== La boîte === | === La boîte === | ||
Ligne 77 : | Ligne 79 : | ||
Après une ébauche faite sur [https://www.tinkercad.com/ Tinkercad] | Après une ébauche faite sur [https://www.tinkercad.com/ Tinkercad] | ||
Premier prototypage fait avec du carton. N'ayant pas encore reçu l'écran, nôtre affichage est fait avec un écran led de 16 colonnes et 2 lignes. | Premier prototypage fait avec du carton. N'ayant pas encore reçu l'écran, nôtre affichage est fait avec un écran led de 16 colonnes et 2 lignes. | ||
+ | |||
+ | Les découpes de cartons ont été faites grâce à une [https://wiki.lesfabriquesduponant.net/index.php?title=Trotec_Speedy_100R,_Laser découpeuse laser] | ||
== Problèmes rencontrés == | == Problèmes rencontrés == | ||
=== Physique === | === Physique === | ||
− | <font color="#e31419">Attention</font> sur le Raspberry Pi zero 2 l'appareil ne boot pas si il est branché en usb sur un PC. Veillez à l'alimenter (sur le bon port 5v) via une prise secteur. | + | <font color="#e31419">Attention</font> sur le Raspberry Pi zero 2 l'appareil ne boot pas si il est branché en usb sur un PC. Veillez à l'alimenter (sur le bon port 5v) via une prise secteur. <br> |
− | Problèmes d'alimentations : Prix de la pose de prise de courant à l'IMT | + | Problèmes d'alimentations : Prix de la pose de prise de courant à l'IMT<br> |
− | Problèmes d'accès internet : Prix de la pose d'un câble RJ45 POE, problème de sécurité si Wifi | + | Problèmes d'accès internet : Prix de la pose d'un câble RJ45 POE, problème de sécurité si Wifi<br> |
− | Delai de livraison : Ecran E-ink 10 jours | + | Delai de livraison : Ecran E-ink 10 jours<br> |
=== Code === | === Code === | ||
Ligne 92 : | Ligne 96 : | ||
==== Bash ==== | ==== Bash ==== | ||
+ | Mise en place des mises à jour quotidiennes via une tâche cron -> passer par systemd (Systemd Timer) | ||
==== Python ==== | ==== Python ==== | ||
Ligne 122 : | Ligne 127 : | ||
* i = on augmente de manière incrémentale pour chercher chaque "réservations", on va chercher l'élément dans le 3eme niveau de l'objet json. | * i = on augmente de manière incrémentale pour chercher chaque "réservations", on va chercher l'élément dans le 3eme niveau de l'objet json. | ||
["reservations"](niveau 1)[i](niveau 2)["heure"](niveau3) | ["reservations"](niveau 1)[i](niveau 2)["heure"](niveau3) | ||
+ | |||
+ | ==== ecran e-ink ==== | ||
+ | |||
+ | Nous avons été incapable de faire fonctionner correctement l'écran à l'aide de l'IDE Arduino. Nous avons donc changé de logiciel pour [https://platformio.org/ PlatfromeIO].<br> | ||
+ | Library utilisée : [https://github.com/Xinyuan-LilyGO/LilyGo-EPD47 LilygoEPD47] | ||
== Déployement == | == Déployement == | ||
Ligne 130 : | Ligne 140 : | ||
==== Configuration Raspberry ==== | ==== Configuration Raspberry ==== | ||
− | + | [https://gitlab.imt-atlantique.fr/tn6_c_e_jj/carae_systeme_affichage_dynamique/-/tree/main/serveur?ref_type=heads Le tuto pas à pas] | |
==== Python ==== | ==== Python ==== | ||
+ | L'application python et le WSGI : [https://gitlab.imt-atlantique.fr/tn6_c_e_jj/carae_systeme_affichage_dynamique/-/tree/main/serveur?ref_type=heads ici] <br> | ||
+ | <span style="color:red">N'oubliez pas la liste des paquets utilisés</span> : | ||
+ | [[Fichier:TN6 CARAE requirements python.txt|vignette|centré|Requirements python]] | ||
− | + | Le WSGI room212a.wsgi | |
+ | <pre> | ||
+ | import sys | ||
+ | sys.path.insert(0, '/home/room/carae') | ||
+ | from room212a import app as application | ||
+ | </pre> | ||
+ | |||
+ | Application python room212a.py : | ||
+ | <pre> | ||
+ | from flask import Flask | ||
+ | import requests | ||
+ | import re | ||
+ | import json | ||
+ | |||
+ | from bs4 import BeautifulSoup | ||
+ | |||
+ | app = Flask(__name__) | ||
+ | |||
+ | @app.route("/") | ||
+ | def hello() -> str: | ||
+ | |||
+ | URL = "votre url ici" | ||
+ | page = requests.get(URL) | ||
+ | |||
+ | # part 2 de la création du soup object | ||
+ | # l'objet soup prend page.content as input | ||
+ | # content plutot que text, on évite les soucis d'encodage | ||
+ | # html.parser = class constructor | ||
+ | soup = BeautifulSoup(page.content, "html.parser") | ||
+ | |||
+ | |||
+ | #on cherche la balise <a> & l'élément id=afficherBoutonSelection1 pour récupérer le n° de la salle | ||
+ | salle = soup.find('a', id='afficherBoutonSelection1') | ||
+ | numero_salle = salle.get_text() | ||
+ | |||
+ | #on cherche tous les éléments avec le mot "type." (le . c'est pour n'importe quel caractère) pour récupérer la classe de la réservation | ||
+ | elements = soup.find_all( | ||
+ | class_=re.compile(r'\btype.\b') | ||
+ | ) | ||
+ | #Objet unique pour la salle | ||
+ | data_json = { | ||
+ | "test": "hello world", | ||
+ | "salle": { | ||
+ | "numero": numero_salle | ||
+ | }, | ||
+ | "reservations": [] | ||
+ | } | ||
+ | |||
+ | #array pour les réservations | ||
+ | for data in elements: | ||
+ | reservation = { | ||
+ | "heure": data.contents[0], | ||
+ | # "service": data.contents[2], | ||
+ | "nom": data.contents[4], | ||
+ | # "outil": data.contents[6], | ||
+ | } | ||
+ | data_json["reservations"].append(reservation) | ||
+ | #Ajout des données nom et heures extraites de la page dans le data_python | ||
+ | return data_json | ||
+ | |||
+ | if __name__ == "__main__": | ||
+ | app.run(debug = True) | ||
+ | </pre> | ||
=== Client : ESP32 === | === Client : ESP32 === | ||
Ligne 143 : | Ligne 218 : | ||
== Visuel == | == Visuel == | ||
− | + | [[Fichier:TN6 CARAE shema technique Excalidraw.jpg|frameless|400px|center|Schéma dispositif]] | |
− | + | [[Fichier:TN6 CARAE shema boite Tinkercad.stl]] | |
− | + | ||
− | + | == Recommandation == | |
+ | # Travailler avec des distributions Linux <!-- Windows ça pue --> | ||
+ | # La partie serveur peut très bien être hébergée sur une VM dédiée | ||
+ | # Avant toute chose, vérifiez '''toujours''' que votre matériel fonctionne correctement | ||
== License == | == License == |
Version actuelle datée du 26 mai 2025 à 11:58
Sommaire
[masquer]Description
Dans le cadre du tremplin numérique n°6 nous réalisons un projet professionnel du 22/04/2025 au 28/05/2025. Celui-ci est issu d'un appel à projet lancé par l'association les Petits Débrouillards Grand Ouest (antenne de Brest) aux entreprises locales (plus ou moins) afin que nous (les stagiaires) répondions au mieux aux besoins exprimés.
La commande consiste à fournir un moniteur d'affichage du planning des salles de travail du CARAE afin de savoir rapidement si celles-ci sont réservées ou libres.Le moniteur sera dans un boîtier fixé au mur dans le couloir du CARAE.
Langages utilisés :
- Arduino
- Bash
- Python
Technologies utilisées :
- WSGI Flask
- Serveur Web Apache
Matériel
- Raspberry Pi Zero 2 WH + Câble d'alimentation
- E ink Screen 4.7inch SKU DFR0835 + ESP32
- Boite : PLA, bois et vis
- 1 Petite breadboard
- 8 Câbles dupont M-F
Budget ≈ 250€
Contraintes
- Faible consommation électrique
- Lisibilité des informations :
- Status libre/occupée
- Date
- Horaires
- Nom de la personne ayant réservée
- Sécurité du dispositif
- mise à jour
- anti-vol (accroché au mur)
- Solidité
Étapes de réalisations
Premier rendez-vous avec le client, prise d'infos et réalisation d'un cahier des charges.
Beaucoup de R&D.
Préparation de documents de suivi pour le client avant les rendez-vous.
Choix d'utiliser la methode agile. Retour client et sprint hebdomadaire.
Pour plus de précision techniques, n'hésitez pas à allez voir notre Gitlab
Le code
Raspberry pi
Il s'agit d'un micro-ordinateur Raspberry PI Zero 2 WH qui nous sert de serveur. Ce choix a été fait car la puissance de calcule et la mémoire RAM de l'esp32 étaient trop faibles, nous avons donc décidé de réaliser le parsing sur notre serveur. Nous avons choisi ce model de Raspberry car il possède un module Wifi et ne coûte pas trop cher.
Notre serveur tourne avec la distribution Raspbian, des scripts Bash, un serveur web Apache et Flask (WSGI python). Tutoriel pas à pas ici.
Sur ce raspberry, nous faisons tourner une application python qui nous permet de récupérer (Parser) les informations provenant de la page web des réservations des salles CARAE à l'IMT. Cette application converti les données bruts tirés de la page html en fichier JSON contenant la salle, l'heure et le nom des réservations. (Exemple de fichier JSON : {salle : D02-121A [{heure : 10:00 à 11:00}{nom: Thierry Margoulin}]
Cette application python utilise plusieurs bibliothèques :
- requests : Requêtes Http.
- re : Expression régulière.
- json : Json.
- beautifulsoup : Parsing.
ESP32
Notre micro-controlleur, lui aussi équipé de la wifi, contrôle l'affichage sur notre écran e-ink. Il reçoit les JSON de la Raspberry Pi et extrait les données pour les afficher sur notre écran E-Ink.
Notre code est construit grâce à l'import de plusieurs bibliothèques :
- ArduinoJson : Permet à notre code Arduino de comprendre et de deserialiser (Découper) les données Jsons reçues.
- Wire : Permet la communication.
- WiFi : Permet la connection réseau.
- HTTPClient : Permet les requêtes http.
- LilyGo-EPD47 : Driver pour notre écran 4.7 pouces
- SPI : Permet la communication via SPI (Serial Peripheral Interface)
La boîte
Après une ébauche faite sur Tinkercad Premier prototypage fait avec du carton. N'ayant pas encore reçu l'écran, nôtre affichage est fait avec un écran led de 16 colonnes et 2 lignes.
Les découpes de cartons ont été faites grâce à une découpeuse laser
Problèmes rencontrés
Physique
Attention sur le Raspberry Pi zero 2 l'appareil ne boot pas si il est branché en usb sur un PC. Veillez à l'alimenter (sur le bon port 5v) via une prise secteur.
Problèmes d'alimentations : Prix de la pose de prise de courant à l'IMT
Problèmes d'accès internet : Prix de la pose d'un câble RJ45 POE, problème de sécurité si Wifi
Delai de livraison : Ecran E-ink 10 jours
Code
Afin d'éviter les problèmes d'incompatibilité dûs aux versions des logiciels veillez à vous mettre d'accord en amont. Le raspberry étant sous raspbian (dérivé de debian) la mise à jour des paquets évolue relativement lentement comparée à des distributions axées pour le developement (Par ex : Fedora)
Bash
Mise en place des mises à jour quotidiennes via une tâche cron -> passer par systemd (Systemd Timer)
Python
- Le nom de la class HTML des information qui nous intéresse est : Typex (le x est un variable correspondant à une lettre de l'alphabet)
- Astuce : ne pas chercher le"typeG", mais passer par regex pour chercher \btype.\ (le . permet de rechercher n'importe quel caractère)
- Soucis de conversion en js pourquoi ? Recherche des éléments 0,1,2,3 de l'array (tableau) MAIS il y a des balises
dans l'html qui compte pour un élément.
Pour trouver les données qu'il nous faut, il suffit de rechercher les éléments 0,2,4,6.
Esp 32
- Soucis d'import du code de Arduino IDE vers l'esp 32 :
Télécharger le driver de l'ESP32 en DEV mode
- Impossible d'importer le code -> L'esp32 n'est pas en mode téléchargement. Solution : Appuyer physiquement sur le bouton boot de l'esp32 pendant l'upload du code
- Compilation du code très long : hardware personnel vetuste -> temps de compilation très long
- Mémoire limitéd (512ko) -> on déporte un maximum de calculs sur le RaspberryPI (Server side)
- Interprétation des données reçues sur l'esp : ça marche pas. Solution :
.as<String> (dans le code arduino) sinon soucis de format (String salle = doc["salle"]["numero"].as<String>();)
- soucis pour récupérer les infos dans un nested array (3eme niveau) solution :
for (int i = 0; i <= 3; i++) { String reservation = doc["reservations"][i].as<String>(); String heure = doc["reservations"][i]["heure"].as<String>(); String nom = doc["reservations"][i]["nom"].as<String>(); if (reservation == "null") break;
- i = on augmente de manière incrémentale pour chercher chaque "réservations", on va chercher l'élément dans le 3eme niveau de l'objet json.
["reservations"](niveau 1)[i](niveau 2)["heure"](niveau3)
ecran e-ink
Nous avons été incapable de faire fonctionner correctement l'écran à l'aide de l'IDE Arduino. Nous avons donc changé de logiciel pour PlatfromeIO.
Library utilisée : LilygoEPD47
Déployement
Le raspberry contient un serveur web (apache) et une application python communiquant via WSGI.
L'ESP32 en tant que client demande (requête http) les éléments à afficher (au format JSON) au serveur. Il se charge ensuite de les afficher sur l'écran e-ink. Le tout en language Arduino.
Serveur : raspberry pi
Configuration Raspberry
Python
L'application python et le WSGI : ici
N'oubliez pas la liste des paquets utilisés :
Fichier:TN6 CARAE requirements python.txt
Le WSGI room212a.wsgi
import sys sys.path.insert(0, '/home/room/carae') from room212a import app as application
Application python room212a.py :
from flask import Flask import requests import re import json from bs4 import BeautifulSoup app = Flask(__name__) @app.route("/") def hello() -> str: URL = "votre url ici" page = requests.get(URL) # part 2 de la création du soup object # l'objet soup prend page.content as input # content plutot que text, on évite les soucis d'encodage # html.parser = class constructor soup = BeautifulSoup(page.content, "html.parser") #on cherche la balise <a> & l'élément id=afficherBoutonSelection1 pour récupérer le n° de la salle salle = soup.find('a', id='afficherBoutonSelection1') numero_salle = salle.get_text() #on cherche tous les éléments avec le mot "type." (le . c'est pour n'importe quel caractère) pour récupérer la classe de la réservation elements = soup.find_all( class_=re.compile(r'\btype.\b') ) #Objet unique pour la salle data_json = { "test": "hello world", "salle": { "numero": numero_salle }, "reservations": [] } #array pour les réservations for data in elements: reservation = { "heure": data.contents[0], # "service": data.contents[2], "nom": data.contents[4], # "outil": data.contents[6], } data_json["reservations"].append(reservation) #Ajout des données nom et heures extraites de la page dans le data_python return data_json if __name__ == "__main__": app.run(debug = True)
Client : ESP32
Programation
Affichage E-INK
Visuel
Fichier:TN6 CARAE shema boite Tinkercad.stl
Recommandation
- Travailler avec des distributions Linux
- La partie serveur peut très bien être hébergée sur une VM dédiée
- Avant toute chose, vérifiez toujours que votre matériel fonctionne correctement
License
Ce projet est sous license GNU GPL v3.
Auteurs
- Corto 🍓
- Etienne 🐧
- Jean-Jacques ⭐