V’Lille est le système de vélos en libre-service et de vélos en location longue durée de la Métropole Européenne de Lille (MEL). Je suis un adepte de ce service depuis son implantation à Lille que ce soit pour aller au boulot, rentrer de soirée ou tout simplement me promener…
Au delà de l’intérêt que j’ai pour ce concept pratique, j’exploite également ce service pour en créer de nouveaux avec les données numériques que les stations remontent. J’ai découvert grâce à l’article de ce blog, qu’il existe une API pour récupérer les données au format XML des stations V’Lille (dommage que ce soit le seul service en open data sur la Métropole):
- Liste des stations V’Lille (Id, emplacement géographique et nom) : http://vlille.fr/stations/xml-stations.aspx
- Les informations d’une station en particulier grâce à son Id : http://vlille.fr/stations/xml-station.aspx?borne=<ID>
Avec ces deux API, le langage Python et quelques librairies, je vais vous accompagner au travers de cet article à la création d’un client python MQTT qui publie les données des stations V’Lille en temps réel. Le tutoriel se passera sous Linux (Ubuntu/Debian/Raspbian), mais il est tout à fait adaptable sur d’autre plateforme.
Installation de Python-pip et des librairies
sudo apt-get update sudo apt-get install build-essential python3-pip python3-dev python3-setuptools
Pour installer la plupart des bibliothèques python, nous allons utiliser un gestionnaire de paquets écrits en Python nommé pip. L’utilitaire pip fonctionne de la même manière que le gestionnaire apt-get sous debian et ubuntu et permet d’installer, de supprimer ou de mettre à jour une librairie python avec une simple commande.
sudo pip3 install tornado sudo pip3 install paho-mqtt
Pour finir, pour développer en python, j’utilise l’IDE Eclipse et le module d’extension pydev, c’est ce que je vais d’ailleurs utiliser pour ce tutoriel. Néanmoins, vous pouvez utiliser un simple éditeur de texte.
Récupération de la liste des stations V’Lille
Utilisation de l’API stations V’Lille
En premier lieux, nous allons récupérer la liste de l’ensemble des stations V’Lille grâce à l’API suivante: http://vlille.fr/stations/xml-stations.aspx
Cette API, lorsqu’elle est appelée nous retourne au format XML l’ensemble des stations de la MEL (Métropole Européenne de Lille) avec leur identifiant et leurs coordonnées GPS. Si vous souhaitez avoir plus d’informations sur le XML, je vous invite à consulter l’article de Wikipedia, mais grosso modo, voila ce que l’on va obtenir:
<?xml version="1.0" encoding="utf-16"?> <markers center_lat="50.675" center_lng="3.1" zoom_level="12"> <marker id="1" lat="50.6419" lng="3.07599" name="Metropole Europeenne de Lille" /> <marker id="10" lat="50.6359" lng="3.06247" name="Rihour" /> <marker id="100" lat="50.6516" lng="3.08162" name="Saint Maur" /> <marker id="101" lat="50.6423" lng="3.09929" name="Mons Sarts" /> <marker id="102" lat="50.6424" lng="3.11084" name="Mairie de Mons" /> ... <marker id="96" lat="50.6454" lng="3.07512" name="Romarin" /> <marker id="97" lat="50.6594" lng="3.07406" name="La Madeleine Gare" /> <marker id="98" lat="50.6549" lng="3.06876" name="Place du Marche" /> <marker id="99" lat="50.6546" lng="3.07381" name="La Madeleine Mairie" /> </markers>
Pour récupérer ce document en XML, nous allons utiliser la bibliothèque Python Tornado. J’apprécie et utilise beaucoup cette bibliothèque python car elle offre la possibilité de créer d’une part un serveur web léger et scalable et permet également de développer un client HTTP. Pour ce tutoriel, je vais utiliser la fonctionnalité client http avec le module tornado.httpclient. Je ferai très prochainement un article sur la création d’un serveur web avec cette bibliothèque.
Voici le code python qui permet de récupérer dans une variable nommée stationsVlilleXML le document XML de la page http://vlille.fr/stations/xml-stations.aspx.
# -*- coding: utf-8 -*- from tornado import httpclient http_client = httpclient.HTTPClient() try: responseStations = http_client.fetch("http://vlille.fr/stations/xml-stations.aspx") stationsVlilleXML = responseStations.body print(stationsVlilleXML) except httpclient.HTTPError as e: # HTTPError is raised for non-200 responses; the response # can be found in e.response. print("Error: " + str(e)) except Exception as e: # Other errors are possible, such as IOError. print("Error: " + str(e)) http_client.close()
Je vous invite à copier/coller ce code dans un fichier nommé main.py et ensuite exécuter avec la commande python3 main.py . Pour le moment, la sortie du script python n’est pas très sexy, mais vous constaterez que nous avons bien reçu notre document XML sur l’ensemble des stations V’Lille de la MEL.
La seconde étape de notre tutoriel est de parser (analyser) ce document au format XML que nous avons reçu. Le but, vous l’aurez compris, est de récupérer les informations qui nous intéressent. L’avantage du langage python est qu’il est orienté objets, ce qui nous permettra de stocker ces informations sur les stations v’Lille dans des objets Python et de faire ensuite un tableau de stations v’Lille. Le XML est un format de donnée qui représente la donnée sous la forme d’un arbre.
Analyse du document XML des stations V’Lille
Pour parser ce document, nous allons utiliser la bibliothèque ElementTree qui est normalement installée par défaut sous ubuntu. Cette bibliothèque nous permet de naviguer au travers des différentes branches de l’arbre représenté par le document XML. Le document a pour racine « markers », et cette racine contient des enfants « marker » avec des attributs. Ce sont ces attributs qui nous intéressent car ils contiennent les paramètres suivants: lat, lng, id et name.
Attention, le document en XML contient à priori une erreur, il est marqué en encodage utf16 alors qu’il est en utf8, il faut donc modifier le document pour y spécifier le bon encodage.
Il faut importer la bibliothèque en haut du main.py
import xml.etree.ElementTree as ET
et à la fin du main.py insérer les lignes suivantes
stationsVlilleXML = stationsVlilleXML.replace('utf-16', 'utf-8') root = ET.fromstring(stationsVlilleXML) for child in root: print (child.attrib)
On récupère dans child.attrib, les informations qui vont nous être utiles pour créer nos objets stations V’Lille. Créons la classe station dans un fichier nommé station.py.
Création de la classe station dans station.py
Cette classe contient 3 méthodes:
- init: méthode qui permet d’instancier (créer) l’objet station
- __str__ : méthode qui est appelée lorsque l’on veut afficher l’objet, nous effectuons un rafraîchissement de la donnée et utilisons le format JSON pour l’affichage
- refresh: méthode qui sera implémentée par la suite et qui permettra de mettre à jour les données de la station
# -*- coding: utf-8 -*- import json class station(object): def __init__(self,idStation,nameStation,latStation,lngStation): self.idStation = idStation #id de la station self.nameStation= nameStation # nom de la station self.latStation = latStation # latitude de la station self.lngStation = lngStation # longitude de la station self.adressStation = None # adresse de la station self.statusStation = None # station en fonctionnement ou HS self.bikesStation = None # nombre de vélos self.attachsStation = None # nombre total d'attaches self.paiementStation = None # avec ou sans TPE self.lastupd = None # temps de la dernière donnée def __str__(self): self.refresh() return str(json.dumps(self, default=lambda o: o.__dict__, sort_keys=True)) def refresh(self): # TO DO return
Dans un premier temps, nous importons dans main.py la classe station, et dans un second temps on copie les informations du fichier XML dans un tableau (ici un dictionnaire en python) de station. Pour finir, à la fin du script python, on affiche les informations sur la station V’Lille de la Métropole Européenne de Lille (MEL) données par cette première API.
Finalement, nous obtenons le fichier main.py suivant:
# -*- coding: utf-8 -*- from tornado import httpclient import xml.etree.ElementTree as ET from station import station http_client = httpclient.HTTPClient() try: responseStations = http_client.fetch("http://vlille.fr/stations/xml-stations.aspx") stationsVlilleXML = responseStations.body #print(stationsVlilleXML) except httpclient.HTTPError as e: # HTTPError is raised for non-200 responses; the response # can be found in e.response. print("Error: " + str(e)) except Exception as e: # Other errors are possible, such as IOError. print("Error: " + str(e)) http_client.close() stationsVlilleXML = stationsVlilleXML.replace('utf-16', 'utf-8') stations = {} root = ET.fromstring(stationsVlilleXML) for child in root: #print (child.attrib) stations[int(child.attrib['id'])] = station(int(child.attrib['id']),child.attrib['name'],float(child.attrib['lng']),float(child.attrib['lat'])) # pour tester la station de la Metropole Europeenne de Lille (MEL), id=1 print(stations[1])
Ensuite, lorsque l’on exécute ce script avec la commande python3 main.py, on obtient:
{ "adressStation": null, "attachsStation": null, "bikesStation": null, "idStation": 1, "lastupd": null, "latStation": 3.07599, "lngStation": 50.6419, "nameStation": "Metropole Europeenne de Lille", "paiementStation": null, "statusStation": null }
Allons maintenant chercher les données qu’il nous manque…
Récupération des données d’une station V’Lille
Nous allons maintenant implémenter la fonction refresh de la classe station (dans station.py). Pour récupérer les informations d’une station V’Lille, nous avons à notre disposition l’API suivante: http://vlille.fr/stations/xml-station.aspx?borne=<ID>. ID représente l’identifiant de la station que nous avons récupéré dans l’étape précédente. Nous allons donc générer l’url sur laquelle nous allons faire des requêtes par la suite
url = "http://vlille.fr/stations/xml-station.aspx?borne=" + str(self.idStation)
On applique ensuite la même méthode avec la bibliothèque python tornado, pour récupérer un fichier XML propre à la station V’Lille. Par exemple, ici avec la station d’Euratechnologies (id=83):
<?xml version="1.0" encoding="utf-16"?> <station> <adress>AVENUE DE BRETAGNE (DEVANT L'ENTREE D'EURATECHNOLOGIE) </adress> <status>0</status> <bikes>19</bikes> <attachs>5</attachs> <paiement>AVEC_TPE</paiement> <lastupd>1 secondes</lastupd> </station>
Une fois la requête effectuée, on récupère le document XML dans une variable et de nouveau on effectue la correction utf16 vers utf8. Il faut ensuite récupérer les valeurs des paramètres du XML de nouveau avec la bibliothèque ElementTree et les stocker dans les variables de la classe. On obtient finalement le code suivant pour la méthode refresh de la classe station:
def refresh(self): url = "http://vlille.fr/stations/xml-station.aspx?borne=" + str(self.idStation) http_client = httpclient.HTTPClient() try: responseStation = http_client.fetch(url) infosStationXML = responseStation.body #print(stationsVlilleXML) except httpclient.HTTPError as e: # HTTPError is raised for non-200 responses; the response # can be found in e.response. print("Error: " + str(e)) except Exception as e: # Other errors are possible, such as IOError. print("Error: " + str(e)) http_client.close() infosStationXML = infosStationXML.replace('utf-16', 'utf-8') root = ET.fromstring(infosStationXML) self.adressStation = root.find('adress').text self.statusStation = int(root.find('status').text) self.bikesStation = int(root.find('bikes').text) self.attachsStation = int(root.find('attachs').text) self.paiementStation = root.find('paiement').text self.lastupd= root.find('lastupd').text return
Maintenant lorsque nous exécutons python3 main.py, on obtient:
{ "adressStation": "MEL RUE DU BALLON ", "attachsStation": 21, "bikesStation": 15, "idStation": 1, "lastupd": "45 secondes", "latStation": 3.07599, "lngStation": 50.6419, "nameStation": "Metropole Europeenne de Lille", "paiementStation": "AVEC_TPE", "statusStation": 0 }
Maintenant que nous avons toutes les informations nécessaires passons à l’envoi de ces données en MQTT, sur le serveur du frugal prototype: mqtt.frugalprototype.com
Envoi des données d’une station V’Lille en MQTT
Pour cette dernière étape, je vous propose de connecter la station V’Lille de la mairie de Roubaix en MQTT (identifiant 221). Nous allons utiliser la bibliothèque paho-mqtt qui existe dans beaucoup de langages dont le langage python. Pour ce tutoriel, un simple envoi MQTT est nécessaire, donc la bibliothèque paho-mqtt sera très peu utilisée, mais je ferai un tutoriel plus complet sur cette librairie. Pour ce faire, je vous propose d’utiliser la fonction single, qui sert à publier un simple message et se déconnecter instantanément.
single(topic, payload=None, qos=0, retain=False, hostname="localhost", port=1883)
topic: le topic dans lequel l’information va être publiée
payload: la donnée à envoyer
qos: qualité de service voulue
retain: message gardé ou non en mémoire dans le topic par le broker
hostname: broker MQTT
port: port du serveur MQTT
Je vous invite à lire l’article d’Ali Benfattoum sur le sujet des protocoles applicatifs pour l’IoT pour plus de renseignements sur les différents paramètres de cette méthode
Finalement on obtient:
# -*- coding: utf-8 -*- from tornado import httpclient import xml.etree.ElementTree as ET from station import station from time import sleep import paho.mqtt.publish as mqtt http_client = httpclient.HTTPClient() try: responseStations = http_client.fetch("http://vlille.fr/stations/xml-stations.aspx") stationsVlilleXML = responseStations.body #print(stationsVlilleXML) except httpclient.HTTPError as e: # HTTPError is raised for non-200 responses; the response # can be found in e.response. print("Error: " + str(e)) except Exception as e: # Other errors are possible, such as IOError. print("Error: " + str(e)) http_client.close() stationsVlilleXML = stationsVlilleXML.replace('utf-16', 'utf-8') stations = {} root = ET.fromstring(stationsVlilleXML) for child in root: #print (child.attrib) stations[int(child.attrib['id'])] = station(int(child.attrib['id']),child.attrib['name'],float(child.attrib['lng']),float(child.attrib['lat'])) while True: mqtt.single(topic="vlille/Roubaix_Mairie", payload=str(stations[221]), qos=0, retain=False, hostname="mqtt.frugalprototype.com", port=1883) sleep(60)
# -*- coding: utf-8 -*- import json from tornado import httpclient import xml.etree.ElementTree as ET class station(object): def __init__(self,idStation,nameStation,latStation,lngStation): self.idStation = idStation #id de la station self.nameStation= nameStation # nom de la station self.latStation = latStation # latitude de la station self.lngStation = lngStation # longitude de la station self.adressStation = None # adresse de la station self.statusStation = None # station en fonctionnement ou HS self.bikesStation = None # nombre de vélos self.attachsStation = None # nombre total d'attaches self.paiementStation = None # avec ou sans TPE self.lastupd = None # temps de la dernière donnée def __str__(self): self.refresh() return str(json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=2)) def refresh(self): url = "http://vlille.fr/stations/xml-station.aspx?borne=" + str(self.idStation) http_client = httpclient.HTTPClient() try: responseStation = http_client.fetch(url) infosStationXML = responseStation.body #print(stationsVlilleXML) except httpclient.HTTPError as e: # HTTPError is raised for non-200 responses; the response # can be found in e.response. print("Error: " + str(e)) except Exception as e: # Other errors are possible, such as IOError. print("Error: " + str(e)) http_client.close() infosStationXML = infosStationXML.replace('utf-16', 'utf-8') root = ET.fromstring(infosStationXML) self.adressStation = root.find('adress').text self.statusStation = int(root.find('status').text) self.bikesStation = int(root.find('bikes').text) self.attachsStation = int(root.find('attachs').text) self.paiementStation = root.find('paiement').text self.lastupd= root.find('lastupd').text return
Lancez votre service avec la commande python3 main.py et vous pourrez vérifier en vous connectant sur mqtt.frugalprototype.com que vos données V’Lille sont publiées toute les minutes en vous inscrivant par exemple au topic « vlille/# ».
Voici donc un premier client MQTT avec des données récupérées en open data, qui donnera naissance à un service pour les citoyens par le Citizen Clan.
Si des erreurs se sont glissées dans l’article, n’hésitez pas à nous en faire part.
Merci, et à très vite dans le futur sur Frugal Prototype!
Related Posts
14 août 2016
Développez votre propre API avec Node.js et Express
Chose promise, chose due : nous allons…