From 01c4c7b3cae21635e35c195b6759d2fc398846bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Werner?= Date: Mon, 28 Oct 2013 18:30:31 +0100 Subject: [PATCH] Add velib backend --- modules/velib/__init__.py | 24 +++++++++ modules/velib/backend.py | 93 +++++++++++++++++++++++++++++++++ modules/velib/browser.py | 47 +++++++++++++++++ modules/velib/pages.py | 107 ++++++++++++++++++++++++++++++++++++++ modules/velib/test.py | 35 +++++++++++++ modules/velib/velib.png | Bin 0 -> 2154 bytes 6 files changed, 306 insertions(+) create mode 100644 modules/velib/__init__.py create mode 100644 modules/velib/backend.py create mode 100644 modules/velib/browser.py create mode 100644 modules/velib/pages.py create mode 100644 modules/velib/test.py create mode 100644 modules/velib/velib.png diff --git a/modules/velib/__init__.py b/modules/velib/__init__.py new file mode 100644 index 00000000..be50b34c --- /dev/null +++ b/modules/velib/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 dud +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + + +from .backend import VelibBackend + + +__all__ = ['VelibBackend'] diff --git a/modules/velib/backend.py b/modules/velib/backend.py new file mode 100644 index 00000000..a046538c --- /dev/null +++ b/modules/velib/backend.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 dud +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + + +from weboob.tools.backend import BaseBackend +from weboob.capabilities.gauge import ICapGauge, GaugeSensor, Gauge, SensorNotFound + +from .browser import VelibBrowser + +import re + +__all__ = ['VelibBackend'] + + +class VelibBackend(BaseBackend, ICapGauge): + NAME = 'velib' + DESCRIPTION = u'get Vélib\' information' + MAINTAINER = u'Herve Werner' + EMAIL = 'dud225@hotmail.com' + VERSION = '0.g' + LICENSE = 'AGPLv3' + + BROWSER = VelibBrowser + STORAGE = {'boards' : {}} + + + def iter_gauges(self, pattern=None): + if pattern is None: + for gauge in self.browser.get_station_list(): + yield gauge + else: + lowpattern = pattern.lower() + for gauge in self.browser.get_station_list(): + if lowpattern in gauge.name.lower() or lowpattern in gauge.city.lower(): + yield gauge + + def iter_sensors(self, gauge, pattern=None): + if not isinstance(gauge, Gauge): + gauge = self._get_gauge_by_id(gauge) + if gauge is None: + raise SensorNotFound() + + gauge.sensors = self.browser.get_station_infos(gauge) + if pattern is None: + for sensor in gauge.sensors: + yield sensor + else: + lowpattern = pattern.lower() + for sensor in gauge.sensors: + if lowpattern in sensor.name.lower(): + yield sensor + + def get_last_measure(self, sensor): + if not isinstance(sensor, GaugeSensor): + sensor = self._get_sensor_by_id(sensor) + if sensor is None: + raise SensorNotFound() + return sensor.lastvalue + + def _get_gauge_by_id(self, id): + for gauge in self.browser.get_station_list(): + if id == gauge.id: + return gauge + return None + + def _get_sensor_by_id(self, id): + reSensorId = re.search ('(\d+)-((bikes|attach|status))', id, re.IGNORECASE) + if reSensorId: + gauge = reSensorId.group(1) + pattern = reSensorId.group(2) + sensor_generator = self.iter_sensors(gauge, pattern) + if sensor_generator: + return next(sensor_generator) + else: + return None + else: + return None diff --git a/modules/velib/browser.py b/modules/velib/browser.py new file mode 100644 index 00000000..1e7fe792 --- /dev/null +++ b/modules/velib/browser.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 dud +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + + +from weboob.tools.browser import BaseBrowser + +from .pages import ListStationsPage, InfoStationPage + + +__all__ = ['VelibBrowser'] + + +class VelibBrowser(BaseBrowser): + PROTOCOL = 'http' + DOMAIN = 'www.velib.paris.fr/service' + ENCODING = None + + PAGES = { + '%s://%s/stationdetails/paris/.*' % (PROTOCOL, DOMAIN): InfoStationPage, + '%s://%s/carto' % (PROTOCOL, DOMAIN): ListStationsPage, + } + + def get_station_list(self): + if not self.is_on_page(ListStationsPage): + self.location(u'%s://%s/carto' % (self.PROTOCOL, self.DOMAIN)) + return self.page.get_station_list() + + def get_station_infos(self, gauge): + self.location('%s://%s/stationdetails/paris/%s' % (self.PROTOCOL, self.DOMAIN, gauge.id)) + return self.page.get_station_infos(gauge.id) + diff --git a/modules/velib/pages.py b/modules/velib/pages.py new file mode 100644 index 00000000..1e900df4 --- /dev/null +++ b/modules/velib/pages.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 dud +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + + +from weboob.tools.browser import BasePage +from weboob.capabilities.gauge import Gauge, GaugeMeasure, GaugeSensor +from weboob.capabilities.base import NotLoaded +import datetime +import re + +__all__ = ['InfoStationPage', 'ListStationsPage'] + +AdresseStation = {} + +class InfoStationPage(BasePage): + def _create_bikes_sensor(self, value, gauge_id, last_update, adresse): + levelbikes = GaugeSensor(gauge_id + '-bikes') + levelbikes.name = u'Bikes' + levelbikes.address = u'%s' % adresse + lastvalue = GaugeMeasure() + lastvalue.level = float(value) + lastvalue.date = last_update + if lastvalue.level < 1: + lastvalue.alarm = u'Empty station' + levelbikes.lastvalue = lastvalue + levelbikes.history = NotLoaded + levelbikes.gaugeid = gauge_id + return levelbikes + + def _create_attach_sensor(self, value, gauge_id, last_update, adresse): + levelattach = GaugeSensor(gauge_id + '-attach') + levelattach.name = u'Attach' + levelattach.address = u'%s' % adresse + lastvalue = GaugeMeasure() + if lastvalue.level < 1: + lastvalue.alarm = u'Full station' + lastvalue.level = float(value) + lastvalue.date = last_update + levelattach.lastvalue = lastvalue + levelattach.history = NotLoaded + levelattach.gaugeid = gauge_id + return levelattach + + def _create_status_sensor(self, value, gauge_id, last_update, adresse): + levelstatus = GaugeSensor(gauge_id + '-status') + levelstatus.name = u'Status' + levelstatus.address = u'%s' % adresse + lastvalue = GaugeMeasure() + status = float(value) + if status == 0: + status = 1 + else: + status = -1 + if lastvalue.level < 1: + lastvalue.alarm = u'Not available station' + lastvalue.level = float(status) + lastvalue.date = last_update + levelstatus.lastvalue = lastvalue + levelstatus.history = NotLoaded + levelstatus.gaugeid = gauge_id + return levelstatus + + def _get_last_update(self, last_update): + return datetime.datetime.now() - datetime.timedelta(seconds=int(re.match(r'\d+', last_update).group(0))) + + def get_station_infos(self, gauge_id): + sensors = [] + + last_update = datetime.datetime.fromtimestamp(float(self.parser.select(self.document.getroot(), 'updated', 1).text)) + adresse = AdresseStation[gauge_id] + + sensors.append(self._create_bikes_sensor(self.parser.select(self.document.getroot(), 'available', 1).text, gauge_id, last_update, adresse)) + sensors.append(self._create_attach_sensor(self.parser.select(self.document.getroot(), 'free', 1).text, gauge_id, last_update, adresse)) + sensors.append(self._create_status_sensor(self.parser.select(self.document.getroot(), 'open', 1).text, gauge_id, last_update, adresse)) + + return sensors + + +class ListStationsPage(BasePage): + def get_station_list(self): + gauges = [] + for marker in self.parser.select(self.document.getroot(), 'marker'): + gauge = Gauge(int(marker.get('number'))) + gauge.name = unicode(marker.get('address')).rsplit('-', 1)[0] + full_address = re.search(r'\d\d\d\d\d.*', unicode(marker.get('fulladdress'))) + if full_address: + gauge.city = full_address.group() + gauge.object = u'velib' + gauges.append(gauge) + AdresseStation[marker.get('number')] = marker.get('fulladdress') + return gauges diff --git a/modules/velib/test.py b/modules/velib/test.py new file mode 100644 index 00000000..f12bc569 --- /dev/null +++ b/modules/velib/test.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 dud +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + +from weboob.tools.test import BackendTest + + +class VelibTest(BackendTest): + BACKEND = 'velib' + + def test_velib(self): + l = list(self.backend.iter_gauges()) + self.assertTrue(len(l) > 0) + + gauge = l[0] + s = list(self.backend.iter_sensors(gauge)) + self.assertTrue(len(s) > 0) + + sensor = s[0] + self.assertTrue(self.backend.get_last_measure(sensor.id) is not None) diff --git a/modules/velib/velib.png b/modules/velib/velib.png new file mode 100644 index 0000000000000000000000000000000000000000..724e339ccdd01f0ffd1623c6b24b157dfb12ea44 GIT binary patch literal 2154 zcmV-w2$lDVP)v_PRK zLWw|-2o^!8AmM6a#h@5S@TG|)O7wxmnD~H7K_&5p;N2MU_MlRH5-dcd6tp&Ip_GE$ zEG;6oHBwQqEeY&6|Ln}}?C$KP=bScM%8=!p?#@p7fAjs{|IKVyBZRSx{rU|X2n+yD`5y_}qdake-nSL? ze~7f&r>3Pc2C%?5``L_Z2DJ(Q$5Mmk0{uzQABZ@`QY9rt%()SB+CY$PBY5DsY~;4i z0tPGjiIw*_^O`9NjQ=1`I9-n~0Pnv1n)3WJo7TI|eY$dyvTym0YS+0Jv;t+{*GZ@A z+!GtND*HB6y=8!+i8wWQ{%VG@@0PslhHhx~>M&p!plU{*YkYS3O#AiKm?*gV4OjqB z-FfNf>EYK`H)ozWl3?I6K$m7-6C&Wl09h*r3IJI8g3^F+b@%TGrf6y~fHWW{H#?XC z!qaR1=_1^rQv-w*j@+6!Ej{EYwE&SUvV0%yc&fgceWO-bi!1|j z^0LfX#$Vp3?F4Mfr7f|M!;%TfP9aSK133LtRa~;g*Rr?@cJ5PHdp7z3fUIRtcWxK zxLC=i02oF`3XRWh-pHYBUm!C(gPp(Dbq!88w7|cYu2@W<#)mP)6if#zq6|PRE1d^# z&tp91KROHMKX#w%o+Yd2!q0n;!QL-wEer_pWJRO_$j;3&*YlQtC$N0&Qkee06wmu+ zEu99j>=^9&aGxcbME7QeU|h6ne=4M=rn)vgkn(=Pi#BcUWEeYnjKLgfN65Z;qDL2i zSXPX0cM)3<3m%^ndW_i*PPcEH5%;!PWK$qUh0R*!844fE$$-)0MuZ+?(u`tWL5R62 z>MEsC1ORt?T`ML`OXQ6Uy6?9W$qsbMJemS!L1k6}CHPqPc=l?Qw#6FN^ zpW|Wx&en5pKWPhI*w*s(E8w2_Q+hRyv#i}Udm=o&c{OCxbpV?+X~xjt^iW{Jw1%1! z(#%_>lsz=zl^`wvmaSRDg7s>vC^N#e@)uWlj#E@z2=kWDay;xH(AjYrK3M-Hw6*@} zJ2rxJc>i%_DHYr5&J+j@!0nW_eyw2ius{~z><1Z|sdFY8OJMPsi_hA{<__4p?i1MY z=59|Q_EpwF_mv*Y{Fsl+blX#B zf3RtV954`~WJO#)po`Y~y4?+o=3&H8W(h${^e0^q{gHaOGJ8e9!%sguPpjQE{xY^7tj*i;+{*wtLM zyj5t0ESkiT|9T<-VB58=x>sU|=@3Tz_V1`u)Nf)gZcJB4z$I$drU!BT#fXX(2?2n! z)Y*PHu%DRq(z4*($MKFHJjG{AmxAJ{;Hd2&dj#dChpq$(5^;iZ+ZlodXm`;*huO;7 z`BAk?#by8Qa&?6GvqDN86)O@309Ssf!eLq9Z?Jlx@xVzbUqRvDbyr|stPlVReNeF? zDOMPLcNi9g8K;YbDT|iy@9x?To~&?+rkWK=S_@Qf|1l}+2hVW`C|Duowt^M1XnM0E zNemFiD3ONucLk)9yoB|3eBcsIFIL38A(BW*Ove3YFdnRMil&Mc@&-v#0Kh*$Zv6ga zT=$}(!u#WCDpt5QJrt}U$?yR#5esRLlg%C{E96ZNFIK1?j3f*I8l&HTZSU1R*dOmp zYLkLfvqDdv4^a7nzU-eUD_o+PbO63N63Q3}-?JaolKOBIgL}379rM}Y@S=RS#f6F= zZ_V#70PwxKq`ftQ!Pd1kK*Za5IR$E9@U<{907*qoM6N<$f{Doy@&Et; literal 0 HcmV?d00001