lun. 08 juillet 2013
Python
Sur mon joli bureau openbox sur Crunchbang, j'aimerais pouvoir afficher à la demande les informations concernant la meteo au moyen d'un raccourci clavier ou d'un menu, puis refermer la fenêtre quand je n'en ai plus besoin. Evidemment, je veux des infos fraîches, avec un affichage sympa, et en consommant très peu de ressources machine.
Tout d'abord, le résultat final :
Pour réaliser cela, nous allons mettre en oeuvre plusieurs élements :
- un programme python qui va recueillir les informations météo sur un site spécialisé et stocker les informations dans un fichier texte
- une automatisation de ce programme sous forme d'un démon qui se lancera au démarrage de l'ordinateur
- un conky pour afficher à la demande la météo et la mettre en forme sur la base des informations contenues dans le fichier texte
Le tutoriel sera découpé en chacune de ces trois parties et nous commençons cette première partie par le programme python.
Wunderground
Wunderground sera notre fournisseur de données météorologiques. Pourquoi Wunderground, parce qu'il fournit une API, une sorte de clé, qui va nous permettre de récupérer facilement des informations météo complètes. En une seule requête, vous disposez de prévisions riches sur les quatre prochains jours.
Les informations ainsi récupérées sont disponibles dans un format xml ou json, facilement lisibles car balisées. Le programme a été écrit pour un format json, rien ne vous empêche de le transposer pour un format xml, les grands principes resteront les mêmes.
La première étape consiste à s'inscrire sur le site de wunderground à cette adresse pour obtenir une clé pour l'API. En bonus, vous avez à votre disposition des outils pour tester une requête et des exemples de code dans différents langages, dont Python.
L'inscription est gratuite dans la limite d'une utilisation non commerciale et de 10 requêtes par minute. Parfait pour un usage privé.
Le script
Le but de ce programme est de se connecter régulièrement au site wunderground, de récupérer les informations qui nous intéressent et de créer un fichier texte lisible par un conky.
Ce script est largement inspiré d'une part du tutoriel python sur full circle HS n°2 et sur ce forum.
Tous les commentaires sont dans le script.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180 | #!/usr/bin/python
# -*- coding: utf-8 -*-
#----------------------------------------------------
# Créé par letchap
# meteo.py
#----------------------------------------------------
""" récupère les informations météo grace à l'API du site wunderground.com """
#import pour les infos meteo
import os.path
import urllib2
import json
import sys
# import pour le démon
import time
from daemon import runner
#===========================================================
# La classe
#===========================================================
class App():
# Tout ça, c'est pour le démon
def __init__(self):
self.stdin_path = '/dev/null'
self.stdout_path = '/dev/null' # J'ai remplacé /dev/tty par dev/null pour éviter les plantages au démarrage par init.d
self.stderr_path = '/dev/null' # J'ai remplacé /dev/tty par dev/null pour éviter les plantages au démarrage par init.d
self.pidfile_path = '/tmp/meteo.pid'
self.pidfile_timeout = 5
# Bien appelé la fonction 'run' pour le démon
def run(self):
while True: # C'est le début de ma boucle pour démoniser mon programme
###############################################
# Le corps du programme #
###############################################
try:
# Je récupère les informations fournies par wunderground grâce à leur api, au format json,
# en une seule fois (forecast et conditions), et en français
# Un exemple de code est fourni sur le site wunderground
# Je charge ma page meteo
page_json = urllib2.urlopen('http://api.wunderground.com/api/macleapi/forecast/conditions/lang:FR/q/France/Paris.json?paris=I75003PA1')
# Je lis la page
json_string = page_json.read()
# Je mets cette page dans un parseur
parsed_json = json.loads(json_string)
# Et je peux fermer ma page meteo, je n'en ai plus besoin
page_json.close()
except:
print 'Les informations météo ne sont pas accessibles sur le site wunderground.com'
sys.exit(2) # pour sortir du programme si la requête n'aboutit pas
# Je récupère les informations du jour stokées sur le tag "current_observation"
# Je fais attention à avoir des variables uniques dans le cas où je fais une recherche sur une chaîne de
# caractère plus tard (avec un grep par exemple).
city = parsed_json['current_observation']['display_location']['city'] # la ville
last_observation = parsed_json['current_observation']['observation_time'] # l'heure dernière observation
current_temp = parsed_json['current_observation']['temp_c'] # la température en °C
current_weather = parsed_json['current_observation']['weather'] # le temps actuel
humidity = parsed_json['current_observation']['relative_humidity'] # le taux d'humidité en %
wind_kph = parsed_json['current_observation']['wind_kph'] # la vitesse du vent
wind_dir = parsed_json['current_observation']['wind_dir'] # l'orientation du vent
pressure_mb = parsed_json['current_observation']['pressure_mb'] # la pression atmosphérique
pressure_trend = parsed_json['current_observation']['pressure_trend'] # l'evolution pression atmosphérique
feelslike_c = parsed_json['current_observation']['feelslike_c'] # la température ressentie
visibility = parsed_json['current_observation']['visibility_km'] # la visibilité en km
UV = parsed_json['current_observation']['UV'] # l'indice UV
# Un petit test sur l'indice UV qui peut être négatif
if str(UV) == '-1':
UV = 0
# Une petite transformation de la tendance atmosphérique
if pressure_trend == '-':
pressure_trend = 'en baisse'
elif pressure_trend == '+':
pressure_trend = 'en hausse'
else:
pressure_trend = 'stable'
# J'écris ces informations dans un fichier qui servira plus tard pour le conky meteo.
# En ouvrant le fichier en mode 'w', j'écrase le fichier meteo.txt précédent
# Je transforme tous les chiffres en chaînes de caractères et j'encode tous les textes français en UTF8
# Je n'ai pas besoin de fermer le fichier en utilisant "with open"
with open('/home/letchap/tmp/meteo.txt', 'w') as f:
f.write("Meteo = " + current_weather.encode('utf8') + "\n")
f.write("Ville = " + city.encode('utf8') + "\n")
f.write("Derniere_observation = " + last_observation.encode('utf8') + "\n")
f.write("Temperature = " + str(current_temp) + " °C\n")
f.write("Ressentie = " + str(feelslike_c) + " °C\n")
f.write("Humidite = " + humidity + "\n")
f.write("Vent = " + str(wind_kph) + " km/h\n")
f.write("Dir_vent = " + wind_dir + "\n")
f.write("Pression = " + str(pressure_mb) + " mb\n")
f.write("Tend_pres = " + pressure_trend.encode('utf8') + "\n") #Ok, l'utf8 ne sert à rien là
f.write("Visibilite = " + str(visibility) + " km\n")
f.write("Indice_UV = " + str(UV) + "\n")
# Je récupère les prévisions sous le tag "simpleforecast", en bouclant sur chacune des périodes
forecast = parsed_json['forecast']['simpleforecast']['forecastday']
for i in forecast:
jour = i['date']['day'] # jour
mois = i['date']['month'] # mois
annee = i['date']['year'] # année
jour_sem = i['date']['weekday'] # jour de la semaine
period = i['period'] # période
tempmax = i['high']['celsius'] # température maximale
tempmin = i['low']['celsius'] # température minimale
condition = i['conditions'] # conditions
icon = i['icon'] # icone en lien avec condition
skyicon = i['skyicon'] # le couverture nuagueuse
pop = i['pop'] # probabilité de précipitation
hauteur_precip = i['qpf_allday']['mm'] # hauteur de précipitation pour la journée
hauteur_neige = i['snow_allday']['cm'] # hauteur de neige pour la journée
vent = i['avewind']['kph'] # vitesse moyenne du vent
vent_dir = i['avewind']['dir'] # direction du vent
tx_humidite = i['avehumidity'] # taux d'humidité
# Je définis chacune de mes 4 périodes
if period == 1:
date = 'jour1'
elif period == 2:
date = 'jour2'
elif period == 3:
date = 'jour3'
elif period == 4:
date = 'jour4'
# Encore un petit test pour les icones. Je combine icon et skyicon pour avoir la représentation graphique
# la plus proche de la réalité en particulier "partiellement couvert et pluvieux" qui n'existe pas
# D'abord je définis 3 listes pour l'orage, la pluie et la neige
orage = ['tstorms','chancetstorms','nt_tstorms', 'nt_chancetstorms']
pluie = ['rain','chancerain','nt_rain', 'nt_chancerain', ]
neige = ['snow','flurries','chancesnow','chanceflurries','nt_snow','nt_flurries','nt_chancesnow','nt_chanceflurries','sleet', 'nt_sleet','chancesleet','nt_chancesleet']
# puis je définis mes icones
if icon in orage:
icone = skyicon+"storm"
elif icon in pluie:
icone = skyicon+"rain"
elif icon in neige:
icone = skyicon+"snow"
else:
icone = icon
# J'écris à la suite, grâce à l'option 'a' append au lieu de 'w'
with open('/home/letchap/tmp/meteo.txt', 'a') as f:
f.write(date + "_jour = " + str(jour) + "\n")
f.write(date + "_mois = " + str(mois) + "\n")
f.write(date + "_annee = " + str(annee) + "\n")
f.write(date + "_jour_sem = " + jour_sem.encode('utf8') + "\n") # C'est du luxe, il n'y a pas d'accent dans les jours de la semaine
f.write(date + "_tempmax = " + str(tempmax) + " °C\n")
f.write(date + "_tempmin = " + str(tempmin) + " °C\n")
f.write(date + "_conditions = " + condition.encode('utf8') + "\n")
f.write(date + "_icone = " + icone + "\n")
f.write(date + "_pop = " + str(pop) + "%\n")
f.write(date + "_hauteur_precip = " + str(hauteur_precip) + " mm\n")
f.write(date + "_hauteur_neige = " + str(hauteur_neige) + " cm\n")
f.write(date + "_vent = " + str(vent) + " km/h\n")
f.write(date + "_dir_vent = " + vent_dir + "\n")
f.write(date + "_tx_himidite = " + str(tx_humidite) + "%\n")
############################################
# Le fin du programme #
############################################
time.sleep(120) # C'est la fin de ma boucle de démonisation. La temporisation est de 120 secondes
# Toujours commencer la lecture d'un programme python par la fin. C'est là qu'on lance le démon
app = App()
daemon_runner = runner.DaemonRunner(app)
daemon_runner.do_action()
|
Télécharger meteo.py
Pour mieux comprendre la façon dont sont récupérées les données, prenons un exemple avec un morceau de fichier json et le code Python correspondant. Nous allons récupérer l'information concernant la ville.
Le morceau du fichier JSON qui nous intéresse ressemble à çà :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | "current_observation": {
"image": {
"url": "http://icons-ak.wxug.com/graphics/wu2/logo_130x80.png",
"title": "Weather Underground",
"link": "http://www.wunderground.com"
},
"display_location": {
"full": "Paris, France",
"city": "Paris",
"state": "",
"state_name": "France",
"country": "FR",
"country_iso3166": "FR",
"zip": "00000",
"latitude": "48.86666489",
"longitude": "2.33333302",
"elevation": "47.00000000"
},
|
Pour trouver la bonne information, il suffit de suivre les branches de l'arbre. L'information "Paris" se trouve dans "city", qui se trouve dans "display_location" qui se trouve dans "current_observation". Ce qui se traduira en python par :
city = parsed_json['current_observation']['display_location']['city']
Le fichier texte final ressemble à ça :
Meteo = Ciel dégagé
Ville = Paris
Derniere_observation = Last Updated on juillet 9, 20:05 CEST
Temperature = 27.1 °C
Ressentie = 27.1 °C
Humidite = 56%
Vent = 14.5 km/h
Dir_vent = NE
Pression = 1020 mb
Tend_pres = stable
Visibilite = 10.0 km
Indice_UV = 0
jour1_jour = 9
jour1_mois = juillet
jour1_annee = 2013
jour1_jour_sem = mardi
jour1_tempmax = 29 °C
jour1_tempmin = 20 °C
jour1_conditions = Ciel dégagé
jour1_icone = clear
jour1_pop = 0%
jour1_hauteur_precip = 0.0 mm
jour1_hauteur_neige = 0 cm
jour1_vent = 16 km/h
jour1_dir_vent = NE
jour1_tx_himidite = 57%
jour2_jour = 10
jour2_mois = juillet
jour2_annee = 2013
jour2_jour_sem = mercredi
jour2_tempmax = 27 °C
jour2_tempmin = 16 °C
jour2_conditions = Ciel dégagé
jour2_icone = clear
jour2_pop = 0%
jour2_hauteur_precip = 0.0 mm
jour2_hauteur_neige = 0 cm
jour2_vent = 18 km/h
jour2_dir_vent = NNE
jour2_tx_himidite = 61%
jour3_jour = 11
jour3_mois = juillet
jour3_annee = 2013
jour3_jour_sem = jeudi
jour3_tempmax = 24 °C
jour3_tempmin = 15 °C
jour3_conditions = Ciel dégagé
jour3_icone = clear
jour3_pop = 0%
jour3_hauteur_precip = 0.0 mm
jour3_hauteur_neige = 0 cm
jour3_vent = 18 km/h
jour3_dir_vent = NNE
jour3_tx_himidite = 55%
jour4_jour = 12
jour4_mois = juillet
jour4_annee = 2013
jour4_jour_sem = vendredi
jour4_tempmax = 27 °C
jour4_tempmin = 16 °C
jour4_conditions = Ciel dégagé
jour4_icone = clear
jour4_pop = 0%
jour4_hauteur_precip = 0.0 mm
jour4_hauteur_neige = 0 cm
jour4_vent = 16 km/h
jour4_dir_vent = NNE
jour4_tx_himidite = 57%
Le démon
Afin de pouvoir interroger régulièrement le site Wunderground, nous allons "démoniser" le programme, c'est à dire que nous allons lui demander de se déclencher à intervalle régulier (dans le script 120 secondes)
Pour cela, il suffit de suivre l'exemple suivant, en ayant installé préalablement python-daemon par un sudo apt-get install python-daemon
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | import time
from daemon import runner
class App():
def __init__(self):
self.stdin_path = '/dev/null'
self.stdout_path = '/dev/null'
self.stderr_path = '/dev/null'
self.pidfile_path = '/tmp/meteo.pid'
self.pidfile_timeout = 5
def run(self):
while True:
# Le corps du programme
time.sleep(1200)
app = App()
daemon_runner = runner.DaemonRunner(app)
daemon_runner.do_action()
|
Le programme ainsi démonisé se lance par :
Vous voir le résultat par exemple par un :
| $ ps -ef | grep meteo
root 2413 1 0 22:30 ? 00:00:00 python /usr/sbin/meteo.py start
|
Il s'arrête par :
Nous pouvons bientôt passer à la deuxième étape : le lancement automatique du programme python au démarrage de l'ordinateur.