ESP32-C3 et API météorologique
Cet article détaille comment interroger une API de météorologie avec un micro-contrôleur ESP32-C3 équipé d’une puce wifi donc. J’ai naturellement utilisé la nouvelle PYBSTICK-ESP32-C3 de Garatronic, que l’on retrouve en vente sur le shop de McHobby. ELle a l’avantage d’être minuscule, et d’embarquer 2 boutons poussoirs ainsi qu’une led RGB bien pratiques pour mon futur projet.
configuration
Il faut dans un premier temps configurer le micro-contrôleur pour qu’il accède au WIFI. Tout est expliqué dans cet article dans la section WIFI. Créez le programme boot.py à la racine du micro-contrôleur afin qu’il puisse se connecter au WIFI à chaque reboot, avec la led RGB comme témoin de couleur:
- bleu: non connecté.
- bleu clignotant: en cours de connection.
- vert: connecté
- rouge: erreur de connection
############################
# Connexion Wifi
############################
WIFI_SSID = "MON_SSID"
WIFI_PASSWORD = "MON_PASSWORD_WIFI"
MAX_ITERATION = 20
import time, network
from RGBLed import RGB_led
#RGB led setup
led = RGB_led()
led.color((0,0,64)) #blue color
#wifi setup
station = network.WLAN(network.STA_IF)
station.active(True)
print("Scanning for WiFi networks, please wait...")
authmodes = ['Open', 'WEP', 'WPA-PSK' 'WPA2-PSK4', 'WPA/WPA2-PSK']
for (ssid, bssid, channel, RSSI, authmode, hidden) in station.scan():
print("* {:s}".format(ssid))
#try to connect to WiFi
nb_iteration=0
while not station.isconnected():
# Try to connect to WiFi access point
print("Connecting...")
try:
station.connect(WIFI_SSID, WIFI_PASSWORD)
except:
pass
nb_iteration+=1
if (nb_iteration==MAX_ITERATION):
break
time.sleep(0.5)
led.blink((0,0,64))
led.color((0,0,64)) #blue color
API météo
J’utilise l’API de météo fournie par meteo-concept. Il y a l’offre “basique” gratuite qui permet d’interroger la météo 500 fois par jour max avec les prévisions sur les 12 prochaines heures, pour n’importe quelle ville: c’est largement suffisant, on peut mettre en place un service d’interrogation toutes les 3mn par exemple qui peut ainsi tourner h24 7J/7.
Pour pouvoir interroger cette API il faut obtenir un token (une grande chaîne de caractères) qui sera par la suite recopiée dans le code ci-dessous.
Il faut aussi aller rechercher le code INSEE de votre ville (une chaîne de 5 nombres): à ne pas confondre avec le code postal (plusieurs villes partagent le même code postal). Pour l’obtenir, c’est par ici.
code micro-python
Créons un nouveau programme sur l’ESP32-C3, nommez-le comme bon vous semble (si vous le nommez main.py il sera exécuté à chaque redémarrage après le boot)
bibliothèques
Il faut d’abord importer les bibliothèques suivantes:
import urequests as requests
import json
test wifi
Nous allons ensuite tester la connection WIFI, allumer la led RGB en conséquence, afficher l’adresse IP.
# Display connection details
if station.isconnected():
print("Connected!")
print("My IP Address:", station.ifconfig()[0])
led.color((64,0,0)) #green color
else:
print("connection to wifi failed!")
led.color((0,54,0)) #red color
classe Meteo()
Cette classe va contenir les méthodes qui vont interroger l’API et fournir des données météorologiques pour une ville donnée (code INSEE) avec une prévisions heure par heure (forecast)
Pensez à bien replacer “MON-TOKEN” par le token que vous avez récupéré lors de votre inscription.
Dans cette classe on retrouve:
- self.dic_STATUS: Un dictionnaire qui permet de décoder les codes retour de l’API
- self.dic_WEATHER: un dictionnaire des codes de bulletin météo fourni par l’API.
- get_forecast(self): méthode qui met à jour la prévision météo sur les 12 prochaines heures dans self.forecast.
- decode_detail_meteo(self, id_forecast=0): méthode qui décode la variable self.forecast et retourne l’heure de prédiction, le bulletin, la température, l’humidité, la probabilité qu’il pleuve, et la vitesse moyenne du vent.
class Meteo():
""" recup données API meto https://api.meteo-concept.com/
"""
def __init__(self, insee=None):
""" constructeur """
self.insee = insee # code INSEE de la ville à récupérer sous https://public.opendatasoft.com/explore/dataset/correspondance-code-insee-code-postal/table/
self.forecast = None # prévisions météo forecast des 12 prochaines heures
#Token à obtenir sur API meto https://api.meteo-concept.com/
self.mytoken = "MON-TOKEN"
self.URLAPI = 'https://api.meteo-concept.com/api'
self.GETHOURS = '/forecast/nextHours'
self.HOURLY = '&hourly=true' #GET forecast every hour
self.TOKEN = '?token='+self.mytoken
self.SEARCH = '&insee='+self.insee
self.api_meteo_url = self.URLAPI+self.GETHOURS+self.TOKEN+self.HOURLY+self.SEARCH
#--dictionnaire des codes retours API
self.dic_STATUS= {
200: "Ok",
400: "Paramètre manquant ou valeur incorrecte: vérifier le token",
401: "Échec de l'authentification: token absent ou invalide",
403: "Action non autorisée",
404: "URL inconnue",
500: "Erreur interne au serveur",
503: "API momentanément indisponible",
}
#--dictionnaire des bulletins météo de l'api meteo-concept
self.dic_WEATHER = {
0: "Soleil",
1: "Peu nuageux",
2: "Ciel voilé",
3: "Nuageux",
4: "Très nuageux",
5: "Couvert",
6: "Brouillard",
7: "Brouillard givrant",
10: "Pluie faible",
11: "Pluie modérée",
12: "Pluie forte",
13: "Pluie faible verglaçante",
14: "Pluie modérée verglaçante",
15: "Pluie forte verglaçante",
16: "Bruine",
20: "Neige faible",
21: "Neige modérée",
22: "Neige forte",
30: "Pluie et neige mêlées faibles",
31: "Pluie et neige mêlées modérées",
32: "Pluie et neige mêlées fortes",
40: "Averses de pluie locales et faibles",
41: "Averses de pluie locales",
42: "Averses locales et fortes",
43: "Averses de pluie faibles",
44: "Averses de pluie",
45: "Averses de pluie fortes",
46: "Averses de pluie faibles et fréquentes",
47: "Averses de pluie fréquentes",
48: "Averses de pluie fortes et fréquentes",
60: "Averses de neige localisées et faibles",
61: "Averses de neige localisées",
62: "Averses de neige localisées et fortes",
63: "Averses de neige faibles",
64: "Averses de neige",
65: "Averses de neige fortes",
66: "Averses de neige faibles et fréquentes",
67: "Averses de neige fréquentes",
68: "Averses de neige fortes et fréquentes",
70: "Averses de pluie et neige mêlées localisées et faibles",
71: "Averses de pluie et neige mêlées localisées",
72: "Averses de pluie et neige mêlées localisées et fortes",
73: "Averses de pluie et neige mêlées faibles",
74: "Averses de pluie et neige mêlées",
75: "Averses de pluie et neige mêlées fortes",
76: "Averses de pluie et neige mêlées faibles et nombreuses",
77: "Averses de pluie et neige mêlées fréquentes",
78: "Averses de pluie et neige mêlées fortes et fréquentes",
100: "Orages faibles et locaux",
101: "Orages locaux",
102: "Orages fort et locaux",
103: "Orages faibles",
104: "Orages",
105: "Orages forts",
106: "Orages faibles et fréquents",
107: "Orages fréquents",
108: "Orages forts et fréquents",
120: "Orages faibles et locaux de neige ou grésil",
121: "Orages locaux de neige ou grésil",
122: "Orages locaux de neige ou grésil",
123: "Orages faibles de neige ou grésil",
124: "Orages de neige ou grésil",
125: "Orages de neige ou grésil",
126: "Orages faibles et fréquents de neige ou grésil",
127: "Orages fréquents de neige ou grésil",
128: "Orages fréquents de neige ou grésil",
130: "Orages faibles et locaux de pluie et neige mêlées ou grésil",
131: "Orages locaux de pluie et neige mêlées ou grésil",
132: "Orages fort et locaux de pluie et neige mêlées ou grésil",
133: "Orages faibles de pluie et neige mêlées ou grésil",
134: "Orages de pluie et neige mêlées ou grésil",
135: "Orages forts de pluie et neige mêlées ou grésil",
136: "Orages faibles et fréquents de pluie et neige mêlées ou grésil",
137: "Orages fréquents de pluie et neige mêlées ou grésil",
138: "Orages forts et fréquents de pluie et neige mêlées ou grésil",
140: "Pluies orageuses",
141: "Pluie et neige mêlées à caractère orageux",
142: "Neige à caractère orageux",
210: "Pluie faible intermittente",
211: "Pluie modérée intermittente",
212: "Pluie forte intermittente",
220: "Neige faible intermittente",
221: "Neige modérée intermittente",
222: "Neige forte intermittente",
230: "Pluie et neige mêlées",
231: "Pluie et neige mêlées",
232: "Pluie et neige mêlées",
235: "Averses de grêle",
}
def get_forecast(self):
''' appel API: fourni le forecast des 12 prochaines heures sur un code insee
'''
#appel API
self.forecast, status = None, None
try:
weather_data = requests.get(self.api_meteo_url)
except:
print('Pas de connexion WIFI')
return status
#code retour appel API
status = weather_data.status_code
if (status != 200):
print('code retour API:', status, self.dic_STATUS[status])
print('Pb connexion API: vérifier le token ou la connexion WIFI:')
else:
self.forecast = weather_data.json()['forecast']
self.city = weather_data.json()['city']['name']
return status
def decode_detail_meteo(self, id_forecast=0):
''' décode les données météo des forecast obtenus
id_forecast=0: heure en cours
'''
h, buletin, temp, hum , probarain, wind = None, None, None, None, None, None
#on vérifie que l'id-forecast ne dépasse pas le nombre de forecast retournés.
if (id_forecast >= len(self.forecast)):
id_forecast = 0
h = self.forecast[id_forecast]['datetime'] # heure prévision
buletin = self.forecast[id_forecast]['weather'] # code buletin
temp = self.forecast[id_forecast]['temp2m'] # température
hum = self.forecast[id_forecast]['rh2m'] # humidité
probarain = self.forecast[id_forecast]['probarain'] # probabilité pluie
wind = self.forecast[id_forecast]['wind10m'] # vitesse moyenne du vent
return h, buletin, temp, hum, probarain, wind
boucle principale
Il suffit ensuite d’instancier un objet de classe Meteo() avec en paramètre le code INSEE de votre ville. La boucle principale va décoder le retour de l’API et si le status est OK afficher les valeurs météorologiques. Dans cet exemple je recommence toutes les 5 secondes mais avec l’offre basique il faudra veiller à patienter 3mn entre chaque interrogation.
meteo = Meteo(insee='MON CODE INSEE')
while True:
status = meteo.get_forecast(); # obtention du forecast pour les 12 prochaines heures
if (status == 200) : # retour API 200 = ok
h, buletin, temp, hum, probarain, wind = meteo.decode_detail_meteo(id_forecast=0)
print('prévision météo', h, 'à', meteo.city, ':', meteo.dic_WEATHER[buletin], ',',
temp, '°C, humidité', hum,'%, proba pluie',probarain,'% , vent moyen', wind,'km/h' )
time.sleep(5) # wait 5 secondes
Si vous avez suivi mes précédentes aventures vous aurez compris que je fais faire une nouvelle version de mon pimometre avec un ESP32-C3 à la place d’un raspberry pi zero.