Cette année nous avons eu la possibilité de réaliser un projet sur esp32 (microcontrôleurs), j’ai donc réalisé une mini station météo sans aucun capteur qui utilise internet pour récupérer la météo d’Avignon et l’afficher.
Sommaire
L’idée
Trouver quoi faire sur un esp32 n’a pas été tâche facile, en effet l’objectif final était d’utiliser au moins une API afin d’obtenir des données et par la suite de les traiter. J’ai donc fait en premier lieu des recherches afin de trouver des API utilisables en python et surtout gratuites. J’ai donc au final utilisé l’API du site openweather qui permet de recueillir la météo aux coordonnées renseignées et j’ai également utilisé un protocole permettant d’avoir l’heure.
Préparation de l’esp32
Pour pouvoir faire du Python sur mon eps32 j’ai dû le préparer, puisque à la base, l’esp32 se code avec du C j’ai donc installé MicroPython sur la carte, MicroPython est une version de Python qui est adapté aux microcontrôleurs et qui permet de passer du C au Python. Vous retrouverez probablement dans un tutoriel arrivant pendant les vacances de Noël comment faire.
Le script
Avant de présenter le script il faut savoir qu’un esp32 lorsqu’il est alimenté exécute automatiquement deux fichiers : le boot.py et le main.py. J’ai donc réalisé mon script dans le main.py et j’ai laissé le boot.py vierge.
########## IMPORTATIONS ########## import tft_config #Module natif qui prend en charge la configuration de l'écran import tft_buttons #Module natif qui prend en charge l'utilisation des boutons import st7789 #Module natif qui prend en charge une partie de l'affichage sur l'écran import vga2_8x16 #Police d'écriture native import vga2_8x8 #Police d'écriture native import vga2_bold_16x32 #Police d'écriture native import network #Module natif qui prend en charge la connexion à un point d'accès wifi from time import sleep #Le module time import ntptime #Module natif qui permet d'utiliser le protocole ntp (Network Time Protocol) from machine import RTC #Module natif qui permet de faire énormément de choses mais qui dans ce cas va régler l'horloge interne import json #Module natif qui permet de convertir le résultat des requêtes en un format utilisable en python import urequests #Module natif qui permet de faire des requêtes sur internet from login_wifi import SSID, PASSWORD #Fichier login_wifi.py qui permet de modifier les logins wifi de l'esp32 #(fait dans un autre fichiers pour réduire les risques d'erreurs)
On a au début du fichier les importations des modules et du fichier login_wifi.py.
########## ECRAN ########## ecran_initialise = False #Variable global pour savoir si l'écran est initialisé ou pas tft = tft_config.config(1) #Variable globale qui représente l'écran et qui en utilisant des méthodes permet de faire des choses avec def afficher_texte(txt, x=0, y=0, alaligne=False, police=vga2_8x16): #Fonction qui permet d'afficher du texte à l'écran global ecran_initialise if not ecran_initialise: initialisation() if not alaligne: tft.fill(st7789.BLACK) tft.text(police, txt, x, y, st7789.WHITE, st7789.BLACK) def deinit(): #Fonction qui déinitialise l'écran global ecran_initialise if ecran_initialise: tft.deinit() ecran_initialise = False def initialisation(): #Fonction qui initialise l'écran global ecran_initialise tft.init() ecran_initialise = True
On a ici les 3 fonctions qui s’occupent de l’écran : l’affichage du texte avec comme paramètres le texte à écrire, les coordonnées qui si elles ne sont pas fournies seront en 0, 0, si le texte est à la ligne et la police d’écriture. Sachant que le alaligne définit si lorsque le texte s’affiche l’écran sera remplit en noir avant l’affichage du texte. Il y a également les fonctions initialisation() et deinit() qui initialise et déinitialise l’écran.
########## INTERNET ########## wlan = network.WLAN(network.STA_IF) #Variable global qui permet de s'occuper d'accéder à internet wlan.active(True) #Activation du point d'accès def do_connect(ssid=SSID, password=PASSWORD): #Fonction qui permet de se connecter à un accès internet wlan.connect(ssid, password) #Lancement en arrière plan de la connexion essai = 0 while wlan.isconnected() == False: #Tant que la connexion n'est pas faite... essai += 1 afficher_texte('Connecting. ', 0, 0, True) #Affichage de message sleep(0.5) afficher_texte('Connecting.. ', 0, 0, True) sleep(0.5) afficher_texte('Connecting...', 0, 0, True) sleep(0.5) if essai == 10: #Si il y a 10 essais de connexions qui n'aboutissent pas... break #Sortie de la boucle while if essai == 10: #Si il y a 10 essais de connexions qui n'aboutissent pas... afficher_texte('Connexion impossible...', 0, 0) afficher_texte('Tentez de modifier login_wifi.py', 0, 20, True) #Affichage du message d'erreur... while True: #Puis une boucle infini pour ne pas exécuter le reste du code qui ne fonctionnera pas pass afficher_texte('Connected to :', 0, 0) #Sinon message qui indique que l'esp32 est connecté afficher_texte(ssid, 0, 20, True) sleep(1) tft.fill(st7789.BLACK) #Remplit l'écran en noir def disconnect(): #Fonction qui permet de se déconnecter d'un réseau wlan.disconnect() ##### Script du login_wifi.py ##### SSID='Le ssid de votre réseau (son nom)' PASSWORD='Et son mot de passe'
Ici ce sont les fonctions qui s’occupent du réseau de l’esp32 en se connectant au réseau définit dans le fichier login_wifi.py et qui tant que le réseau n’est pas connecté va afficher le texte ‘Connecting…’ et après 10 essais un message d’erreur s’affiche en expliquant comment changer les logins du réseau, sinon on affiche que la connexion s’est faite. Il y a aussi la fonction disconnect qui permet de se déconnecter du réseau.
########## HEURE ########## ntptime.host = 'ntp.unice.fr' #Définition du site où la demande de l'heure est faite def set_heure(): #Fonction qui règle l'heure de l'horloge interne en demandant au serveur if not wlan.isconnected(): #Si pas connecté, se connecte au réseau do_connect() ntptime.settime() #Règle l'horloge interne de l'esp32 selon l'heure donnée par le serveur def get_heure(): #Fonction qui renvoie une liste du format : #[année, mois, jour, jour de la semaine, heure, minute, seconde, milliseconde] heure = list(RTC().datetime()) #Récupération de l'heure interne heure[4] += 1 #Décalage horaire if heure[4] == 24: heure[4] = 0 if len(str(heure[6]))==1: #Ajout d'un 0 si l'heure, la minute ou la seconde ne comporte qu'un chiffre heure[6] = '0' + str(heure[6]) if len(str(heure[4]))==1: heure[4] = '0' + str(heure[4]) if len(str(heure[5]))==1: heure[5] = '0' + str(heure[5]) return heure def afficher_heure(): #Fonction qui permet d'afficher en gros au milieu de l'écran l'heure avec les secondes temps = get_heure() #Définition de l'heure actuelle afficher_texte("Menu ->", 264, 0, True) #Affichage permettant de savoir sur quel bouton appuyer pour retourner au menu while bouton_droite.value() == 1: #Tant que le bouton de droite n'est pas appuyé if get_heure()[6] != temps[6]: #Si l'heure actuelle diffère de celle définie en dehors de la boucle afficher_texte(f"{temps[4]} : {temps[5]} : {temps[6]}", 64, 69, True, vga2_bold_16x32) #Affichage de l'heure temps = get_heure() #Définition de l'heure pour recommencer la vérification du if menu() #Affichage du menu while bouton_droite.value() == 0: #Tant que le bouton de droite est appuyé, ne rien faire pass
Par la suite on a les fonctions qui s’occupent de l’heure utilisant le protocole ntp qui permet de récupérer l’heure en temps réel avec set_heure() qui règle l’horloge interne de l’esp32 et get_heure() qui récupère l’heure et qui va la retourner dans ce format :
[année, mois, jour, jour de la semaine, heure, minute, seconde, milliseconde]
Et afficher_heure() va afficher l’heure en s’actualisant chaque seconde et qui s’arrête lorsque l’on appuie sur le bouton de droite pour retourner au menu.
########## METEO ########## adresse_meteo = "https://api.openweathermap.org/data/2.5/weather?lat=43.95&lon=4.8167&appid=6dc180325a613e8fe2292078d342022a&lang=fr" #Variable globale qui définit le lien de l'API de météo def meteo(): #Fonction qui fait une requête au serveur de météo et qui renvoie une liste de certaines infos if not wlan.isconnected(): #Si pas connecté, se connecte au réseau do_connect() data = json.loads(urequests.get(adresse_meteo).text) #Fait la requête et la convertie pour être utilisable en python temperature = str(round(data.get("main").get("temp") - 273.15, 1)) #Température en kelvin puis en celsius if len(temperature) == 3: #Si la temp est de ce format : 1.2 fait de la temp ce format : 01.2 temperature = '0' + temperature humidite = str(data.get("main").get("humidity")) # Humidité if len(humidite) == 1: #Comme pour la temperature humidite = '0' + humidite vent_vitesse = data.get("wind").get("speed") * 3.6 # vitesse du vent en m/s puis en Km/h vent_orientation = data.get("wind").get("deg") # origine du vent en degré image = 'image/' + data.get('weather')[0].get('icon') + '.png' #Récupération du code de l'image puis mise dans le bon format description = data.get('weather')[0].get('description') #Description de la météo nom_ville = f"{data.get('name')}, {data.get('sys').get('country')}" #Récupération du nom de la ville return [temperature, humidite, str(round(vent_vitesse)), str(vent_orientation), image, description, nom_ville]
Pour la météo il y a deux fonctions et voici la première : meteo() va faire une demande API vers le site openweather afin de récupérer la météo d’Avignon.
def station_meteo(): #Fonction qui s'occupe d'afficher les données de la météo et qui s'actualise toutes les minutes actualisation = True #Variables qui dit si oui ou non la météo doit s'actualiser chaque minutes while actualisation: #Boucle while tant que actualisation == True data = meteo() #Récupère les infos de la météo heure = get_heure() #Récupère l'heure afficher_texte("Menu ->", 264, 0, False) #Affiche le texte pour le retour au menu longueur = int((320-(len(data[6]))*8)/2) #Affiche le nom de la ville for i in range(len(data[6])): #Lettre par lettre pour éviter les problèmes dû aux accents if data[6][i] == '\xe9': lettre = 130 elif data[6][i] == '\xe8': lettre = 138 elif data[6][i] == '\xe0': lettre = 133 elif data[6][i] == '\xe7': lettre = 135 else: lettre = data[6][i] afficher_texte(lettre, longueur, 5, True) longueur += 8 tft.fill_rect(0, 44, 320, 1, st7789.WHITE) #Fait une barre blanche pour séparer le nom de la ville du reste #Affichage de la température temp = [data[0][0]+data[0][1], data[0][2]+data[0][3]] afficher_texte('Temp', 28, 50, True) afficher_texte(temp[0], 20, 67, True, vga2_bold_16x32) afficher_texte(temp[1], 52, 81, True, vga2_8x16) afficher_texte('o', 52, 67, True, vga2_8x8) afficher_texte('C', 60, 67, True, vga2_8x16) #Affichage de l'heure et des minutes afficher_texte(f"{heure[4]} : {heure[5]}", 132, 25, True) #Affichage de l'humidité x = 20 if len(data[1]) == 3: x -= 8 afficher_texte("Humid", 24, 105, True) afficher_texte(data[1] + '%', x, 122, True, vga2_bold_16x32) #Affichage de l'image selon le code donné tft.png(data[4], 110, 30, True) #Affichage du vent avec sa vitesse et sa direction y = 67 if len(data[2]) == 1: x = 252 elif len(data[2]) == 2: x = 244 else: x = 236 afficher_texte('Vent', 260, 50, True) afficher_texte(data[2], x, y, True, vga2_bold_16x32) afficher_texte('Km/h', x + len(data[2])*16, y+14, True) y = 122 if len(data[3]) == 1: x = 264 elif len(data[3]) == 2: x = 256 else: x = 248 afficher_texte("Direc", 256, 105, True) afficher_texte(data[3], x, y, True, vga2_bold_16x32) afficher_texte('o', x + len(data[3])*16, y, True, vga2_8x8) #Affichage de la description qui correspond à l'image longueur = int((320-(len(data[5]))*8)/2) for i in range(len(data[5])): if data[5][i] == '\xe9': lettre = 130 elif data[5][i] == '\xe8': lettre = 138 elif data[5][i] == '\xe0': lettre = 133 elif data[5][i] == '\xe7': lettre = 135 else: lettre = data[5][i] afficher_texte(lettre, longueur, 108, True) longueur += 8 #Attente pour actualisation avec retour possible au menu while True: if get_heure()[5] != heure[5]: #Si la minute change on refait un tour de la boucle principale break if bouton_droite.value() == 0: #Si on appuie sur le bouton de droite on sort des boucles pour retourner au menu actualisation = False break menu() #Affichage du menu while bouton_droite.value() == 0: #Ne rien faire tant que le bouton de droite est appuyé pass
Et en deuxième fonction il y a station météo qui est une boucle while qui s’actualise chaque minute et qui affiche toutes les informations soit : le nom de la ville, l’heure, la température, le pourcentage d’humidité, la vitesse du vent et sa direction et pour finir une image représentant le temps et une description juste en dessous
########## BOUTONS ########## boutons = tft_buttons.Buttons() #Récupération des boutons de l'esp32 bouton_gauche = boutons.left #Définition des boutons dans des variables bouton_droite = boutons.right #bouton_gauche.value() == 0 si appuie, 1 si relâché #bouton_droite.value() == 0 si appuie, 1 si relâché def wait(): #Fonction qui permet de récupérer l'appui d'un bouton et while True: #qui attend son relâchement avant de renvoyer le bouton appuyé if bouton_gauche.value() == 0: while bouton_gauche.value() == 0: pass return 'gauche' if bouton_droite.value() == 0: while bouton_droite.value() == 0: pass return 'droite'
Il y a également la fonction qui s’occupe de récupérer l’appuie des boutons et les variables globales qui représentent les deux boutons
########## MENU ########## def menu(): #Fonction qui affiche le menu tft.fill(st7789.BLACK) #Remplit l'écran en noir longueur = 256 #Et affiche ce que font les boutons mot = "Météo ->" for i in range(len(mot)): if mot[i] == '\xe9': lettre = 130 else: lettre = mot[i] afficher_texte(lettre, longueur, 0, True) longueur += 8 afficher_texte("Heure ->", 256, 154, True) longueur = 56 mot = "Station Météo" for i in range(len(mot)): if mot[i] == '\xe9': lettre = 130 else: lettre = mot[i] afficher_texte(lettre, longueur, 69, True, vga2_bold_16x32) longueur += 16 afficher_texte("By Robin.C", 0, 154, True)
La fonction menu() affiche météo -> et heure -> au niveau des boutons pour indiquer à quoi ils servent, écrit en gros au milieu ‘Station Météo’ et en bas à gauche le nom du génie qui a écrit ce code ;).
########## MAIN ########## def main(): #Fonction principale qui s'occupe de tout if not wlan.isconnected(): #Si pas connecté, se connecte au réseau do_connect() set_heure() menu() while True: #Boucle infini qui appelle les fonctions bouton = wait() if bouton == 'gauche': tft.fill(st7789.BLACK) afficher_heure() if bouton == 'droite': tft.fill(st7789.BLACK) station_meteo()
Pour finir les fonctions il y a la fonction principale qui va connecter l’esp32 à internet, afficher le menu puis faire une boucle infini afin de choisir si on veut la météo ou l’heure et permettre de changer pendant l’exécution de ce qu’on a choisi.
########## DÉMARRAGE ########## #Essaie d'exécuter main() et si ça plante ou si l'exécution est interrompu #Déinitialise l'écran pour éviter des problèmes lors d'autres exécutions #Et désactive aussi le point d'accès pour encore une fois éviter des problèmes try: main() finally: deinit() wlan.active(False)
Et enfin le code qui permet d’appeler la fonction main() d’une certaine manière: main() est dans un try afin qu’il soit exécuté et le finally fait que lorsque l’exécution sort du try le code dans le finally et ce code qui est la déinitialisation de l’écran et la désactivation du point d’accès internet, cela permet de redémarrer l’exécution en évitant des possibles plantages.
Fichiers
Pour avoir cette magnifique station météo chez vous il vous faut simplement un esp32 avec micropython (sachant que pour ma part je possède un esp32 Lilygo T-display S3) et de télécharger les fichiers que vous pouvez retrouver en cliquant sur le bouton :
Conclusion
Pour conclure ce projet m’a permis d’apprendre énormément de choses sur le micropython et je suis assez content du résultat car lorsque j’ai commencé le projet le fait qu’il y ai peu de documentation et personne pour aider était dur, cependant en cherchant un peu partout et en essayant des choses j’ai réussi à comprendre plein de choses.
Rendu final
Étudiant en classe de terminale générale au lycée Louis Pasteur à Avignon (2023-2024).
En train de se balader dans le nether.