Étiquette : esp32

Tutoriels

Comment installer et utiliser MicroPython sur un ESP32

Dans le premier projet de l’année, nous avons eu la possibilité d’utiliser un ESP32. Cependant, il fallait que cet ESP32 utilise MicroPython pour que le projet soit valable. Nous verrons donc comment installer et utiliser MicroPython sur un ESP32.

Installation de MicroPython

Avant toute chose, il faut télécharger plusieurs fichiers :

  • Le logiciel qui va permettre de flasher ESP32. Le terme flasher correspond à mettre à jour, modifier le micrologiciel d’un appareil
  • Et le micrologiciel qui va être flashé dans l’ESP32
  • Un logiciel pour décompresser les fichiers

Pour cela il vous faut accéder à ce lien qui renvoie vers le site Github et c’est là où vous trouverez le micrologiciel.

Une fois sur cette page, il vous faut cliquer sur le bouton Code puis sur Download ZIP pour télécharger le micrologiciel en format compressé.

Il faut également télécharger le logiciel qui va permettre de flasher l’ESP32. Il vous faut donc accéder à ce lien pour le télécharger.

Pour avoir la bonne version du logiciel qui est compatible avec notre ESP32, il faut trier à gauche par ESP32-S3 :

La page va s’actualiser et vous n’aurez qu’à cliquer sur le bouton Download à côté de Flash Download Tools.

Vous avez donc téléchargé les deux fichiers nécessaires. Cependant il vous faut les décompresser. Pour cela il vous faut un logiciel pour décompresser comme 7zip.

Vous vous retrouverez donc avec deux dossiers comme ceci :

À partir de maintenant il faut que votre ESP32 soit connecté en USB à votre ordinateur.

Nous allons maintenant installer le nouveau micrologiciel dans votre ESP32, pour cela, dans le dossier flash_download_tool_3.9.5 exécutez le fichier flash_download_tool_3.9.5.exe.

Un invite de commande et une petite fenêtre s’ouvre. Nous allons nous intéresser à la petite fenêtre :

Il faut paramétrer la fenêtre selon votre ESP32, dans mon cas il s’agit d’un ESP32-S3 je sélectionne donc ESP32-S3 dans le ChipType et dans LoadMode il faut mettre USB comme l’ESP32est connecté en USB sur l’ordinateur.

Appuyez sur OK.

Dans cette fenêtre il faut cocher la première case et dans la même ligne cliquer sur les 3 points afin de sélectionner le nouveau micrologiciel. Le micrologiciel se trouve dans l’autre dossier st7789s3_mpy-main et dans le sous dossier firmware et il s’agit du fichier firmware.bin . Et à droite du @ il faut mettre un 0. Vous pouvez savoir si vous avez fait une erreur si une des cases est en rouge si par exemple le chemin vers le fichier est mauvais ou si le numéro n’est pas valide. Vous devriez avoir plus ou moins ceci :

Par la suite en bas à droite dans la catégorie COM il faut choisir le port correspondant à celui de votre ESP32. Si vous ne le connaissez pas il vous faut ouvrir le gestionnaire de périphériques, pour ce faire, appuyez sur le bouton Windows + r et écrivez devmgmt.msc pour ouvrir la fenêtre. Et dans la catégorie Port, il faut trouver le Périphérique série USB

Dans mon cas il s’agit du COM5 donc je renseigne le COM5 dans l’application et dans la catégorie BAUD il faut mettre à 9216000.

Vous devriez avoir la même chose sauf peut-être pour le COM et dans ce cas là avant d’appuyer sur START il faut mettre l’ESP32 en mode téléchargement, pour ça il faut appuyer en même temps sur le bouton de reset sur le côté et celui de boot, qui est le plus proche du bouton de reset, en commençant par appuyer sur le bouton reset. Après cela vous pouvez appuyer sur START et le téléchargement commencera.

Utilisation de MicroPython

Après avoir installé votre version de MicroPython vous avez probablement envie de l’utiliser. Et pour cela il vous faut un IDE comme Thonny mais il ne suffit pas de juste le démarrer, il faut configurer votre ESP32 dessus.

Pour configurer votre interpréteur afin d’utiliser l’ESP32 et non l’ordinateur il faut cliquer en bas à droite sur Python 3 local, qui correspond à votre python installé sur votre ordinateur. Cliquez ensuite sur Configurer l’interpréteur.

Dans le type d’interpréteur, il faut choisir MicroPython (ESP32) comme il s’agit d’un ESP32sous MicroPython. un nouveau paramètre apparait, il s’agit du port de l’ESP32 et il s’agit du même que sur le logiciel d’installation du micrologiciel sauf si vous avez changer de port l’ESP32. Vous devriez donc vous retrouver avec ceci :

Cliquez sur OK, la fenêtre va se fermer et retourner sur l’interface normal avec quelques changements sur ce qui est écrit dans la console et aussi en bas à droite dans l’interpréteur.

Vous pouvez taper help() pour avoir quelques informations mais vous y trouverez principalement de la documentation générique qui ne correspond pas forcément à l’ESP32, pour avoir quelque chose de plus détaillé, vous pouvez retourner sur le Github de la version du firmware, c’est en anglais mais vous aurez la documentation qui correspond le mieux à l’ESP32.

Conseils

Je vous conseille d’afficher les fichiers de l’ESP32, pour ça en haut dans affichage il faut cliquer sur Fichiers.

Cette fenêtre permet d’accéder aux fichiers présents sur l’ESP32, de les supprimer, de les télécharger ou de téléverser des fichiers de votre ordinateur vers l’ESP32.

Vous remarquerez deux fichiers importants : boot.py et main.py. Ces fichiers sont importants car ce sont des fichiers qui vont être exécutés automatiquement par l’ESP32 dès qu’il sera alimenté, dans ce cas, boot.py sera exécuté puis main.py dès que l’exécution de boot.py est terminée. Dans l’image ci-dessus les fichiers correspondent aux fichiers de ma Station Météo sur ESP32, le fichier boot est vide mais tout le code est contenu dans main.py, sachant que les autres fichiers sont soit des modules supplémentaires ou des images qui sont utilisés dans le code principal.

Également dans le dossier contenant le firmware soit st7789s3_mpy-main il y a un dossier exemples qui contient quelques exemples qui peuvent être utiles pour comprendre le fonctionnement des différentes fonctions et méthodes. Cela passe de l’affichage de l’heure, à de l’écriture, ou un jeu. Il y a aussi deux fichiers importants qui sont tft_buttons.py et tft_config.py qui sont les deux modules permettant de s’occuper de l’écran et des boutons et qui doivent donc être en tout temps dans l’ESP32.

Conclusion

Pour conclure, j’espère que ce tutoriel vous sera utile afin de comprendre le fonctionnement de MicroPython sur ESP32 et vous donnera envie d’en savoir plus sur ces derniers.

Projets

Un météorologue personnel en briquette !

Qui n’a jamais rêvé d’avoir un robot en LEGO qui lui annonce la météo ? Et bien même si ce n’est pas le cas, nous l’avons fait pour vous ! Venez découvrir un robot capable de comprendre son environnement pour vous donner la météo.

Fonctionnalités du robot

Notre robot au doux nom de ROBKEY est capable de :

  • déplacer ses bras et son bassin ;
  • reconnaitre l’endroit où il se trouve pour donner précisément la météo ;
  • afficher l’heure, la météo et la température ambiante ;
  • d’avoir quelques réactions faciales ;
  • et bien plus.

La conception du robot

Pour concevoir notre robot, nous avons dû prendre en compte plusieurs choses. Tout d’abord, les moteurs n’ont pas été particulièrement dur à installer car il ne font que la moitié de la taille de l’esp 32. Il nous a donc paru évident de mettre les moteurs des bras dans le torse du robot et le moteur du bassin dans le châssis de ses chenilles.

Ensuite il a fallu installer planche de test qui a eu plusieurs positions possibles. Premièrement dans son dos car cela aurait permis de pouvoir faire tourner le bassin du robot à 360°, mais les câbles auraient difficilement bien tenu à l’esp 32 et au robot lui-même. Nous nous sommes finalement entendu à l’idée de mettre la planche sur la batterie des chenilles bien que cela empêche le robot de faire des tours sur lui même.

Maintenant, le plus important : le placement de l’ESP32. Nous avions deux idées, l’une le plaçait dans le torse du robot, l’autre faisait en sorte qu’il soit la tête légèrement rentrée dans les épaules. Nous avons opté pour la seconde option car celle-ci nous permet d’afficher un petit visage sur l’écran du microcontrôleur.

Pour conceptualiser l’apparence de notre robot, nous avons donc réalisé un schéma en 3D très simple sur Paint 3D en regroupant nos idées pour imaginer ce à quoi il ressemblera.

Construction de la structure du robot

Pour construire le robot, j’ai (Sylvain) commencé par regarder les constructions Lego que j’avais précédemment réalisées et qui pouvaient être détruite pour commencer à récupérer leurs pièces. Avec toutes ses matières premières je me suis laissé porter par mon imagination pour construire le LEGO le plus ressemblant à un robot en essayant de faire en sorte qu’il soit plutôt résistant (bon d’accord j’ai peut-être failli à cette tâche).

C’est grâce aux joies de la maladie que j’ai pu entamer la construction. Pour commencer, j’ai récupéré le châssis d’une autochenille radiocommandée en la modifiant pour la rendre plus compacte et en laissant un espace pour le moteur du bassin.

Ensuite j’ai commencé à faire ledit bassin, que j’élargis à la fin de la construction car je l’avais fait trop fin. Suivi du torse, le plus simple car il devait être creux pour laisser la places aux câbles et aux moteurs. Il fallait aussi préparer les trous pour les axes des bras.

Après j’ai fait les bras qui sont vraiment très simplistes mais à ce moment je commençais sérieusement à manquer de pièces plus techniques et même des plus simples pour faire quelque chose de plus sympathique. Mais quoi qu’il en est l’un des deux bras représente un soleil pour quand il fait beau, tandis que l’autre représente un ciel nuageux.

Pour finir j’ai réalisé la cage de la planche de test et solidifié les éléments les moins solides et résolu les quelques problèmes mécaniques rencontrés lors des premiers tests des moteurs.

L’électronique du robot

En parallèle de la construction du LEGO, je (Thomas) me suis lancé dans la conception électronique de celui-ci.

Pour cela, j’ai d’abord listé tout ce que je voulais intégrer à l’électronique :

  • 2 boutons,
  • 1 capteur de température,
  • 3 moteurs.

Pour m’aider dans la réalisation électronique, je me suis aidé de tous ces sites :

Allez c’est parti !

On commence par le branchement des boutons :

Les câbles blancs (entourés en rouge) récupèrent l’information de la pression du bouton et les câbles noirs (entourés en vert) s’occupe de fermer le circuit (de l’électricité passent dans le bouton, et il faut que l’électricité puisse faire une boucle). Si vous voulez, les boutons reçoivent de l’énergie par les câbles blancs et l’expulsent par les câbles noirs.

Concernant le branchement du capteur :

A droite à quoi ressemble le capteur de température de face et à gauche son branchement. La câble de gauche, en blanc récupère les mesures du capteur, celui du milieu en marron, alimente positivement le capteur (le capteur reçoit de l’énergie par ce câble) et le câble noir à droite du câble marron lui ferme la boucle (le capteur expulse l’énergie par ce câble.)

Et pour finir, les moteurs ont 3 câbles :

Le câble orange sert à contrôler le moteur, le rouge à l’alimenter et le marron à fermer la boucle. On refait ça 3 fois puisqu’on a 3 moteurs.

On branche tout ceci à la planche de test puis à l’ESP32, et voilà montage terminé :

Programmation du robot

Maintenant que le robot est construit et le montage électronique assemblé, il faut passer à la programmation de l’ESP32. Veuillez noter que depuis l’écriture de ce qui suit, le code a bénéficié de quelques mises à jours. Vous retrouverez l’archive en fin d’article avec les fichiers à jours.

Tout d’abord, il faut coder les briques de bases, qui vont nous permettre de manipuler différents outils, capteurs, actionneurs, api, etc.

  • C’est pour cela que nous créons un fichier buttons.py qui contient une classe Button dans laquelle nous initialisons 4 boutons : les 2 du microcontrôleur et les deux boutons ajoutés. Dans cette classe, vous verrez que chaque boutons a des caractéristiques supplémentaires. En effet il est possible de rendre un bouton poussoir équivalent à un levier. Nous créons différentes fonctions pour récupérer ou modifier des informations en rapport avec les boutons.
from machine import Pin
from time import sleep

# Importer les modules est inutile car ils sont déjà importés dans boot.py

class Buttons():
    def __init__(self):
        #self.name = "t-display-s3"
        
        # NomDuBouton : [                    PinUtilisé                     ,    mode    , value ]
        #      1      : [ Pin 3 en mode INPUT avec une résistance Pull-Down ,   levier   ,   0   ]
        # 1 : [ Pin(3, mode=Pin.IN, pull=Pin.PULL_DOWN), 1, 0 ]
        
        # Note : le chiffre de value dans la liste n'est pas pris en compte si le bouton est en mode "poussoir" -> On ne peut pas mettre None car si on décide de changer le mode du bouton, la valeur devient importante.
        
        
        # Pour une raison inconnue, "left" et "right" ne fonctionne plus.
        self.list_button = {"left" : [Pin(0, Pin.IN, Pin.PULL_UP),0,1],
                            "right" : [Pin(14, Pin.IN, Pin.PULL_UP),0,1],
                            1 : [Pin(2, mode=Pin.IN, pull=Pin.PULL_UP),0,1],
                            2 : [Pin(3, Pin.IN, Pin.PULL_UP),0,1]
                            }


    def getButton(self, button):
        return self.list_button[button]
    
    def setButton(self, button, pin, mode, value=None): # Le set fait également add.
        self.list_button[button] = [pin,mode,value]

    def setMode(self, button, val:bool): # Ce mode change le mode du bouton -> soit "levier" soit "poussoir"
        if isinstance(val, bool) or val in (0,1):
            self.list_button[button][1] = val
            if not(val):
                self.list_button[button][2] = 1
            return print("Mode définie sur : " + str(self.list_button[button][1]))
        else:
            raise ValueError("val must be a boolean or a integer included between 0 and 1.")
    
    def setValue(self, button, value):
        if self.list_button[button][1]:
            self.list_button[button][2] = value
            return print("Valeur définie sur : " + str(self.list_button[button][2])) 
        else:
            print("Button must be a toogle button, not a push button.")
            return False
    
    def getValue(self, button): # Renvoie la valeur du bouton
        if not(self.list_button[button][1]): # Si le statut de mode_toogle est égal à 0 <=> Si le bouton est en mode "poussoir" et non "levier"
            return self.list_button[button][0].value()
        else:
            if not(self.list_button[button][0].value()):
                self.list_button[button][2] = 0 if self.list_button[button][2] else 1
                sleep(0.25)
            return self.list_button[button][2]
    
    def isButtonPressed(self,button): # Renvoie True si le bouton est pressé (donc si value = 0) et False sinon
        return not(self.getValue(button))
  • Continuons avec le fichier temperature.py dans lequel nous avons une simple fonction qui nous renvoie les données récupérées par le capteur de température et d’humidité.
from time import sleep  #Inutile car importé dans boot.py

def getTemperatureAndHumidity(capteur): # Capteur doit etre sous cette forme : capteur = dht.DHT11(Pin(17)) -> Ne pas oublier d'importer le module dht
    try:
        capteur.measure() # Met à jour les données de températures et d'humidités
        sleep(1)
        return (capteur.temperature(),capteur.humidity())
    except:
        print("Valeurs non récupérées")
        return False
  • Egalement, à l’aide des fichiers motor.py et servo.py (trouvé sur internet), nous créons des fonctions qui réaliseront des déplacements particuliers :
from servo import Servo
import time

motor=Servo(pin=12) # A changer selon la broche utilisée
motorbd=Servo(pin=10)
motorbg=Servo(pin=11)
 
def droit(t=1):
    motor.move(285)
    time.sleep(t)

def bassin(t=2):
    motor.move(265)
    time.sleep(t)
    motor.move(305)
    time.sleep(t)

def baissebg(t=0.5):
    motorbg.move(335)
    time.sleep(t)
    
def levebg(t=0.5):
    motorbg.move(270)
    time.sleep(t)

def baissebd(t=0.5):
    motorbd.move(335)
    time.sleep(t)
    
def levebd(t=0.5):
    motorbd.move(270)
    time.sleep(t)
    
droit()
  • Pour finir, à l’aide du fichier connection.py, nous créons plusieurs fonctions en rapport avec la partie sans-fil de l’ESP32.
import network
import st7789 #En théorie inutile car déjà importé dans boot.py
from time import sleep

def connect_to_wifi(display, font, ssid, mdp):
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    if not wlan.isconnected():
        display.init()
        display.draw(font,"Connexion a un point",0,111)
        display.draw(font,"d'acces en cours",15,126)
        wlan.connect(ssid,mdp)
        while not wlan.isconnected():
            pass
    display.fill_rect(0,107,170,25,st7789.BLACK)
    display.draw(font,'Connecte !',40,111)
    sleep(0.8)
    display.fill_rect(0,107,170,10,st7789.BLACK)
    display.deinit()

def search_wlan():
    station = network.WLAN(network.STA_IF)
    station.active(True)
    return station.scan()

Passons aux modules qui vont utiliser des apis :

  • En premier lieu, nous avons le fichier geoloc.py dans lequel nous utilisons l’api de google qui à l’aide des réseaux wifi aux alentours est capable de situer l’ESP32. Dans ce fichier est présent une fonction qui récupère les coordonnées géographiques de l’ESP32.
from modules.connection import search_wlan
import ustruct as struct
import urequests as requests
import ujson as json
import modules.api_txt as api_txt


def getLocation(api_key): # Avec Google maps API + sécurité sur le nombre de requête.    
    
    if int(api_txt.get_api_counter()[0]) > 24000:
        print("Quota de demande dépassé !!! Vous ne pouvez pas faire de requête...")
        return False
    
    list_wlan = search_wlan()
    data = {
        "considerIp": False,
        "wifiAccessPoints": []
    }

    for wifi in list_wlan:
        entry = {
            "macAddress": "%02x:%02x:%02x:%02x:%02x:%02x" % struct.unpack("BBBBBB", wifi[1]),
            "signalStrength": wifi[3],
            "channel": wifi[2]
        }
        data["wifiAccessPoints"].append(entry)
    
    headers = {"Content-Type": "application/json"}
    url = "https://www.googleapis.com/geolocation/v1/geolocate?key=" + api_key
    response = requests.post(url, headers=headers, data=json.dumps(data))

    api_txt.add_api_counter(0)

    return json.loads(response.content)["location"]
  • En second et dernier lieu, il reste le fichier meteo.py qui va être capable d’utiliser une api de météorologie pour récupérer, analyser et mettre en forme des données en rapport avec la météo.
import urequests
import modules.api_txt as api_txt

def get_meteo(latitude, longitude): # Sur la prochaine heure
    
    if int(api_txt.get_api_counter()[1]) > 10000:
        print("Quota de demande dépassé !!! Vous ne pouvez pas faire de requête...")
        return False
    
    url = "https://api.open-meteo.com/v1/forecast?latitude={0}&longitude={1}&current=temperature_2m,precipitation&daily=temperature_2m_max,temperature_2m_min,precipitation_sum&timezone=auto&forecast_days=3".format(latitude,longitude)
    json_meteo = urequests.get(url).json()
    
    api_txt.add_api_counter(1)
    
    return {"current":
            {"temperature":
             [
                 json_meteo["current"]["temperature_2m"],
                 json_meteo["current_units"]["temperature_2m"]
                 ],
             "precipitation":
             [
                 json_meteo["current"]["precipitation"],
                 json_meteo["current_units"]["precipitation"]
                 ]
             },
            "demain":
            {"temperature": [round((json_meteo["daily"]["temperature_2m_min"][1] + json_meteo["daily"]["temperature_2m_max"][1]) / 2, 1), json_meteo["daily_units"]["temperature_2m_min"]],
             "precipitation":
             [
                 json_meteo["daily"]["precipitation_sum"][1],
                 json_meteo["daily_units"]["precipitation_sum"]
                 ]
             },
            "apres-demain":
            {"temperature": [round((json_meteo["daily"]["temperature_2m_min"][2] + json_meteo["daily"]["temperature_2m_max"][2]) / 2, 1), json_meteo["daily_units"]["temperature_2m_min"]],
             "precipitation":[
                 json_meteo["daily"]["precipitation_sum"][2],
                 json_meteo["daily_units"]["precipitation_sum"]
                 ]
             }
            }

Passons à la gestion graphique, nous avons récupérer le module tft_config.py fournit par les développeurs du pilotes graphiques pour l’esp32. Celui-ci permet de créer facilement une gestion de l’écran avec les bonnes caractéristiques. Nous n’afficherons pas le code ici, car ce n’est pas nous qui l’avons codé. Vous pouvez le retrouver dans l’archive à la fin de cet article ou ici.

Nous avons créé le fichier affichage.py qui contient un certain nombre de fonctions pour réaliser le rendu graphique pour l’ESP32. Il contient également le code qui récupère des images pour les animations du visage et des icones. Ces dessins et animations ont été réalisé par Ilana, ancienne première NSI.

import st7789
from time import sleep, localtime
import modules.motor as motor

last_temps = -1

def display_time(display,font,x,y):
    global last_temps
    temps = localtime()
    if last_temps < temps[5]:
        display.png("/images/icones/horloge.png",x,y,True)
        display.fill_rect(x+30,y+5,60+6,10,st7789.BLACK)
        display.draw(font, str(temps[3]) + ":" + str(temps[4]) + ":" + str(temps[5]),x+30,y+10)
    last_temps = temps[5]

def display_meteo(display,font,infos_meteo, x, y):
    display.draw(font,str(infos_meteo["temperature"][0]),x,y)
    display.draw(font, font.LAST,x + display.draw_len(font,str(infos_meteo["temperature"][0])), y)
    display.draw(font, " C",x + display.draw_len(font,str(infos_meteo["temperature"][0])), y)
    
    display.draw(font,str(infos_meteo["precipitation"][0]),x+80,y)
    display.draw(font,str(infos_meteo["precipitation"][1]),x + 80 + display.draw_len(font,str(infos_meteo["precipitation"][0])),y)

def display_temperature(display,font,infos_temperature,x,y):
    display.fill_rect(x,y,70,20,st7789.BLACK)
    display.png("/images/icones/temperature.png",x,y,True)
    display.draw(font,str(infos_temperature) + " C",x+30,y+9)
    display.draw(font,font.LAST,x+30+display.draw_len(font,str(infos_temperature)), + y+9)    
    
def meteo_icon(display,icone,x,y):
    display.fill_rect(x,y,50,50,st7789.BLACK)
    if icone == "pluie":
        display.png("/images/icones/mauvais_temps.png",x,y,True) #Nuage pluie gris pas content
    else:
        display.png("/images/icones/beau_temps.png",x,y,True) # Soleil content
        

def meteo_widget(display,font,infos_meteo,x,y):
    display.fill_rect(x,y,170,200,st7789.BLACK)
    
    
    display.draw(font, "Auj.",x,y)
    display_meteo(display,font,infos_meteo["current"],x, y+20) #x,y+20)
    
    display.draw(font, "Dem.", x, y+40) #x+50, y)
    display_meteo(display,font,infos_meteo["demain"], x, y+60) #x+50,y+20)
    
    display.draw(font, "Apr-Dem.", x, y+80) #x+90, y)
    display_meteo(display,font,infos_meteo["apres-demain"], x, y+100) #x+90,y+20)

def main_menu(display,font,infos_temp,infos_meteo):
    display_time(display,font, 10, 111) # x = 52 pour un semblant de centrage
    display_temperature(display,font,infos_temp,10,151)
    
    if infos_meteo["current"]["precipitation"][0] > 0:
        icone = "pluie"
        motor.baissebg()
        motor.levebd()
    else:
        icone = "soleil"
        motor.baissebd()
        motor.levebg()
    
    meteo_widget(display,font,infos_meteo,10,191)
    meteo_icon(display,icone,110,111)

def animation(display,name:str,iteration:int,i_fixe:int): # list_anim : {"content":(2,1),"endormi":(4,1),"somnole":(3,1),"observe":(0,0),"monocle":(2,1)}
    display.fill_rect(0,0,170,105,st7789.BLACK)
    for i in range(2,iteration+1):
        display.png("/images/" + name + "/" + name + str(i) + ".png",0,0)
        if name == "endormi":
            sleep(0.3)
    for i in range(iteration,0,-1):
        display.png("/images/" + name + "/" + name + str(i) + ".png",0,0)
        if name == "endormi":
            sleep(0.3)
    display.png("/images/" + name + "/" + name + str(i_fixe) + ".png",0,0)
    

Pour finir nous avons le fichier api_txt.py qui s’occupe de gérer des fichiers de textes importants pour le fonctionnement du programme, présents dans le dossier data.

"""
api[0] => Google Maps API
api[1] => Météo API
"""

def get_api_counter():
    fichier_r = open("data/counter-api.txt",'r')
    liste = fichier_r.read().split(",")
    fichier_r.close()
    return liste

def add_api_counter(indice_api): # ne fonctionne qu'avec deux apis
    fichier_w = open("data/counter-api.txt",'w')
    val = get_api_counter()
    val[indice_api] = int(val[indice_api]) + 1
    
    fichier_w.write(str(val[0]) + "," + str(val[1]))
    fichier_w.close()

def set_api_counter(indice_api,value): # ne fonctionne qu'avec deux apis
    fichier_w = open("data/counter-api.txt",'w')
    api_value = get_api_counter()
    api_value[indice_api] = str(value)
    fichier_w.write(api_value[0] + "," + api_value[1])
    fichier_w.close()

Maintenant que toutes nos briques sont construites, il ne reste qu’à écrire le fichier python principal qui va organiser tout le programme tel un chef d’orchestre.

Assemblage final

Maintenant, il faut simplement assembler notre montage électronique à notre construction LEGO. Puis à brancher notre ESP32 à un câble, lui injecter le code, et tout en le gardant brancher, le lancer, et la magie opère !

Fichiers

Veuillez noter que vous devrez ajouter votre propre clé api Google Maps API pour que la position soit mise à jour (dans boot.py à la ligne 21). Sinon vous pouvez changer manuellement la position dans le fichier old-location.txt se trouvant dans le dossier data. Veuillez respecter l’ordre suivant et ne surtout rien ajouter d’autre dans le fichier, pas même un espace :

latitude,longitude

Vous devrez également ajouter vos propres informations pour un point d’accès dans le fichier boot.py au niveau de la ligne 19 : wlan_info = ("SSID","MDP")
Le projet ne fonctionne pas sans une connexion à internet !!

Projets

Une station météo sur un ESP32

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.

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