====== Création d'un script python ====== Au cours de cette deuxième séance, nous allons construire pas à pas un script python. ===== Pourquoi construire un script ? ===== ### Lors de la première séance, nous avons découvert comment lire les données du capteur DHT22 en mode interactif dans le terminal Python. Ce mode est idéal pour expérimenter, tester et comprendre le fonctionnement de la sonde. Mais il a ses limites : chaque mesure doit être lancée manuellement, les calculs doivent être refaits à la main, et les résultats ne sont pas sauvegardés. ### ### Avec un script **Python autonome**, on passe à l'étape supérieure : notre station météo devient **automatisée** et **réutilisable**. Le code tourne en boucle, effectue les relevés à intervalle régulier, calcule automatiquement le point de rosée et l’humidex, puis affiche les résultats joliment formatés. C’est une première vraie brique vers une **station météo libre et autonome**, que l’on peut faire évoluer ensuite (enregistrement des données, affichage web, alertes, etc.). On quitte l’expérimentation manuelle pour poser les bases d’un **service automatisé, éthique, et maîtrisé de bout en bout**. Bref : on libère notre météo. ### ===== Création du script ===== Créer un script et éditer le avec la commande suivante : nano meteo_dht22.py ===== Importation ===== Commençons par importer la **bibliothèque Adafruit_DHT**, qui permet de lire les données des capteurs DHT (température et humidité). import Adafruit_DHT __Imports supplémentaires__ : * **time** pour gérer les temporisations entre les mesures. * **math** pour les calculs scientifiques. * **datetime** pour afficher la date et l'heure des relevés. import time import math from datetime import datetime ==== Différences entre import et from ... import ... ==== Quand on écrit //import math//, on importe toute la bibliothèque, et on accède à ses fonctions avec le préfixe //math.// (ex : //math.log()//). Quand on écrit //from datetime import datetime//, on importe directement **une fonction ou une classe précise**, ce qui permet de l’utiliser sans préfixe (ex : //datetime.now()// au lieu de //datetime.datetime.now()//). La première forme est plus explicite et lisible dans les grands scripts, la seconde est plus concise quand on utilise souvent la même fonction. ===== Déclaration du capteur ===== DHT_SENSOR = Adafruit_DHT.DHT22 DHT_PIN = 4 ### Ici, on déclare deux **constantes** : DHT_SENSOR pour indiquer le **type de capteur** utilisé (le DHT22), et DHT_PIN pour spécifier sur quelle **broche GPIO** le capteur est branché. ### ### On parle de constante car ces valeurs ne **changent pas pendant l’exécution du script**. On les écrit en **majuscules** pour le signaler clairement (c’est une convention en Python). ### ### L’intérêt ? C’est plus lisible, plus facile à modifier si on change de capteur ou de broche, et ça évite de répéter ces valeurs partout dans le code. On améliore donc la clarté et la maintenabilité du programme. ### ===== Les fonctions ===== ### Dans le script, on trouve deux blocs qui commencent par def. Ce sont des **fonctions**, c’est-à-dire des morceaux de code **réutilisables** qui effectuent une tâche bien précise : ### def calculer_point_de_rosee(temp, hum): ... def calculer_humidex(temp, hum): --- ### Une fonction, c’est comme une **boîte à outils** : on lui donne des **données en entrée** (ici, la température et l’humidité), et elle nous renvoie un **résultat calculé** (le point de rosée ou l’humidex). On peut ensuite **réutiliser** cette fonction autant de fois qu’on veut, sans devoir réécrire le calcul à chaque fois. ### Les fonctions permettent de **structurer** le code, de le **rendre plus clair**, plus facile à maintenir et à faire évoluer. C’est un pas de plus vers un code propre, compréhensible def calculer_point_de_rosee(temp, hum): # Formule pour calculer le point de rosée alpha = 17.27 beta = 237.7 gamma = (alpha * temp) / (beta + temp) + math.log(hum / 100.0) point_de_rosee = (beta * gamma) / (alpha - gamma) return point_de_rosee ### Fonction pour calculer le **point de rosée**, une mesure liée à la condensation de l’humidité (formule d’**August-Roche-Magnus**). ### ### Le mot-clé **return** sert à **renvoyer un résultat** depuis une fonction. C’est ce que la fonction renvoie à celui qui l’a appelée. Sans **return**, la fonction ferait les calculs, mais ne transmettrait rien de ses résultats ! ### def calculer_humidex(temp, hum): # Formule pour calculer l'humidex humidex = temp + (5/9) * (6.11 * math.exp(5417.7530 * ((1/273.16) - (1/273.15))) - 10) return humidex ### Fonction pour calculer l’**humidex**, un indice qui combine température et humidité pour donner une idée de la **température ressentie**. ### ===== Boucle infinie ===== Dans notre script, on utilise une **boucle infinie** grâce à la ligne : while True: ### Cela signifie que le bloc de code qui suit va s’exécuter en **boucle, sans fin**, tant qu’on n’interrompt pas manuellement le programme. Cette technique est parfaite pour une **station météo autonome** : elle va relever les données, les afficher, attendre un peu… puis recommencer, encore et encore. C’est ce qui permet de transformer notre Raspberry Pi en véritable service météo libre et auto-hébergé, toujours actif tant que la machine est allumée. ### ==== Les variables ==== humidity, temperature = Adafruit_DHT.read_retry(DHT_SENSOR, DHT_PIN) ### on utilise deux **variables**, humidity et temperature. Une variable, c’est comme une **boîte** dans laquelle on peut **stocker une valeur** pour la réutiliser plus tard, contrairement aux constantes, ici la **valeur sera amenée à bouger**. La fonction read_retry() interroge le capteur et renvoie deux valeurs : l’humidité et la température mesurées. On les **dépose directement dans deux variables**, pour pouvoir les afficher et/ou faire des calculs, ### En nommant les variables de façon claire, on rend le code plus compréhensible et plus facile à relire. ==== Validité de la lecture ==== ### Une **condition** permets de vérifier si la **lecture de la sonde est valable**. Si la lecture est correcte, le programme continue normalement, sinon, un message d'erreur s'affiche. ### if humidity is not None and temperature is not None: # Suite du programme else: # Message en cas d'erreur ==== Date et heure ==== ### Dans notre station météo, on veut savoir quand chaque mesure a été prise. Pour ça, on utilise le module datetime, et plus précisément les lignes suivantes : ### now = datetime.now() date_heure = now.strftime("%d-%m-%Y %H:%M:%S") * datetime.now() récupère **la date et l’heure actuelles** du système. * strftime() permet de **formater** cette date dans un style plus lisible : ici, jour-mois-année heure:minute:seconde. ==== Calcul du point de rosée et de l'humidex ==== ### Après avoir lu la température et l'humidité, on utilise les fonctions qu’on a **définies plus haut** pour effectuer deux calculs : ### point_de_rosee = calculer_point_de_rosee(temperature, humidity) humidex = calculer_humidex(temperature, humidity) ### Ces lignes montrent comment on **réutilise nos fonctions** calculer_point_de_rosee() et calculer_humidex() en leur passant les **variables** temperature et humidity en **paramètres**. Les résultats obtenus sont ensuite **stockés dans deux nouvelles variables**, point_de_rosee et humidex. ### Cela montre la force des fonctions : une fois écrites, on peut les appeler facilement autant de fois qu’on veut, ce qui rend notre code modulaire, réutilisable et lisible. ==== Couleurs ANSI ==== Nous pouvons ajouter en haut de notre script de nouvelles constantes avec les couleurs : RED = "\033[91m" GREEN = "\033[92m" YELLOW = "\033[93m" BLUE = "\033[94m" MAGENTA = "\033[95m" RESET = "\033[0m" Grâce aux constantes, nous obtiendrons un code plus compréhensible. ==== Affichage des données ==== ### Nous utilisons les **couleurs** pour rendre l'affichage **plus clair et agréable à lire** dans le terminal. ### print(f"{BLUE}Date et heure:{RESET} {date_heure}") print(f"{GREEN}Température:{RESET} {temperature:.1f}°C") print(f"{YELLOW}Humidité:{RESET} {humidity:.1f}%") print(f"{RED}Point de rosée:{RESET} {point_de_rosee:.1f}°C") print(f"{MAGENTA}Humidex:{RESET} {humidex:.1f}") print("----") ### Les variables numériques sont affichés avec **une seule décimale** grâce à la syntaxe {temperature:.1f}. Ce format permet d’avoir des valeurs **précises mais lisibles**, sans afficher une longue série de chiffres inutiles. Chaque ligne correspond à une donnée spécifique. ### ==== Erreur de lecture de la sonde ==== ### En cas d’échec de la lecture, un message d’erreur est affiché. C’est important pour diagnostiquer les soucis matériels ou de câblage. ### else: print("Échec de la lecture du capteur") ==== Pause entre les lectures ==== ### Mettons une pause de **10 secondes** entre deux lectures, pour éviter de surcharger le capteur et ralentir le flux d’informations. ### time.sleep(10) ====== Bonus : Ajouter la pression atmosphérique avec l’API OpenWeather ====== Afficher la pression atmosphérique récupérée sur Internet, en complément des données relevées par la sonde DHT22 sur le Raspberry Pi. ===== Créer un compte sur OpenWeather ===== - Aller sur [[https://openweathermap.org|https://openweathermap.org]] - Cliquer sur "Sign in" en haut de la page. - Cliquer sur "Create an account". - Remplir le formulaire et valider le compte. - Cliquer sur "My API keys". {{ raspberry:openweather.png?400 }} 6. copier la clé affichée (garde-la précieusement !). ===== Installer la bibliothèque requests ===== ### Nous allons utiliser la **bibliothèque requests** pour faire des **appels à l’API**. Dans votre environnement virtuel Python, vous pouvez taper cette commande : ### pip3 install requests ### Dans le haut de votre script, ajoutez l'import de cette bibliothèque : ### import requests ===== Nouvelles constantes ===== Ajoutons 4 nouvelles constantes : * Dans la section des couleurs, vous pouvez ajouter une couleur pour l'affichage de la pression atmosphérique. CYAN = "\033[96m" * Créer une nouvelle section pour les **coordonnées GPS** d'Arles. # Coordonnées GPS d'Arles LATITUDE = 43.6768 LONGITUDE = 4.6303 * Enfin, ajouter la **clé API**. # Clé API API_KEY = "Votre_clé" **Attention** : Ici, nous mettons directement la clé API dans le code. En faisant cela, nous créons une faille de sécurité car nous exposons publiquement une donnée sensible. Nous verrons à la fin comment sécuriser notre clé API. ===== Création d'une nouvelle fonction ===== ### Cette fonction interroge le **service météo OpenWeather** pour récupérer la **pression atmosphérique actuelle** en fonction de la **position géographique (latitude/longitude)**. ### def get_pression_openweather(API_KEY, LATITUDE, LONGITUDE): Elle prend en paramètre : * API_KEY → la clé personnelle d’API fournie par OpenWeather, * LATITUDE → la latitude du lieu (ex : 43.676 pour Arles), * LONGITUDE → la longitude du lieu (ex : 4.630 pour Arles). ====URL de l'API==== url = f"https://api.openweathermap.org/data/2.5/weather?lat={LATTITUDE}&lon={LONGITUDE}&appid={API_KEY}&units=metric&lang=fr" On construit dynamiquement l’adresse de l’API avec les bonnes informations : * **lat** et **lon** pour la géolocalisation, * **appid** pour inclure la clé d’accès API, * **units=metric** pour avoir les données en °C et hPa, * **lang=fr** pour avoir les descriptions météo en français (utile si on affiche d'autres infos comme la description du ciel). ====Bloc try/except==== ### Le mot-clé try signifie : essaie d’exécuter les instructions suivantes. Cela permet d’éviter les **plantages du script** si une erreur survient (ex : pas de connexion Internet, clé API invalide…). ### try: .... code de la fonction .... except: .... code en cas d'échec de la fonction .... ====Réponse de l'API==== ===Appel de l'API=== ### On utilise la bibliothèque requests pour envoyer une requête HTTP vers l’URL de l’API OpenWeather. ### response = requests.get(url) ===Conversion de la réponse en JSON=== ### La méthode .json() transforme la réponse en **dictionnaire Python**. Cela nous permet de manipuler facilement les informations retournées par l’API. ### data = response.json() __Exemple d'une structure JSON__ : { "main": { "temp": 18.5, "pressure": 1013, "humidity": 82 } } ===Récupération de la pression atmosphérique=== pression = data["main"]["pressure"] ### Ici, on accède à la **donnée "pressure"** qui se trouve dans la **clé "main"** du dictionnaire. C’est cette valeur (en hPa) que l’on souhaite récupérer. ### ===Retour de la fonction=== return pression On **retourne la valeur de la pression**. Cela signifie que lorsque cette fonction est appelée, elle renverra un nombre (par exemple 1013). ===Gestion des erreurs=== except: return None ### Si quelque chose ne fonctionne pas (problème de réseau, mauvaise clé API, réponse inattendue, etc.), on retourne None. ### ====Affichage des données==== pression = recuperer_pression(API_KEY, LATITUDE, LONGITUDE) ### On utilise ici la fonction //recuperer_pression// (définie plus haut) pour **obtenir la pression atmosphérique actuelle** à partir des coordonnées géographiques (latitude et longitude) et de la clé API. Le résultat (la pression en hPa) est **stocké dans la variable pression**. ### if pression is not None: -> if humidity is not None and temperature is not None and pression is not None: ### On s’assure que la récupération des données a bien fonctionné. Si la valeur n’est pas None, cela veut dire que l’appel à l’API a réussi, donc on peut afficher la donnée. Sinon, on ne fait rien (ou on pourrait afficher un message d’erreur pour informer l’utilisateur). ### print(f"{CYAN}Pression atmosphérique:{RESET} {pression} hPa") ### On affiche la pression, en couleur cyan, grâce aux **constantes ANSI** définies auparavant (**CYAN et RESET**). La valeur est suivie de l’**unité hPa (hectopascals)**, utilisée en météorologie pour mesurer la pression atmosphérique. ###