Afficher la meteo avec conky et python 1ère partie


 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 :

Ecran conky

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 :

$ python meteo.py start

Vous voir le résultat par exemple par un :

1
2
$ ps -ef | grep meteo
root      2413     1  0 22:30 ?        00:00:00 python /usr/sbin/meteo.py start

Il s'arrête par :

$ python meteo.py stop

Nous pouvons bientôt passer à la deuxième étape : le lancement automatique du programme python au démarrage de l'ordinateur.