From 1be50d83dcecb8a5609df3f0b2bd78fad1087f6e Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 31 May 2015 22:54:16 +0200 Subject: [PATCH 01/13] add timestamp to short time --- homeassistant/util/dt.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/homeassistant/util/dt.py b/homeassistant/util/dt.py index fbe00c85527..f3c96368667 100644 --- a/homeassistant/util/dt.py +++ b/homeassistant/util/dt.py @@ -108,6 +108,14 @@ def str_to_datetime(dt_str): except ValueError: # If dt_str did not match our format return None +def timestamp_to_short_time_str(timestamp): + """ Converts a UNIX timestamp to a short time string format. + + @rtype: str + """ + return dt.datetime.fromtimestamp( + int(timestamp)).strftime(TIME_SHORT_STR_FORMAT) + def strip_microseconds(dattim): """ Returns a copy of dattime object but with microsecond set to 0. """ From a6b7f47d744e4bd1c3c8367a4be2b09f658f61a6 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 31 May 2015 22:55:02 +0200 Subject: [PATCH 02/13] add swiss public transport sensor --- .../sensor/swiss_public_transport.py | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 homeassistant/components/sensor/swiss_public_transport.py diff --git a/homeassistant/components/sensor/swiss_public_transport.py b/homeassistant/components/sensor/swiss_public_transport.py new file mode 100644 index 00000000000..ee162a37164 --- /dev/null +++ b/homeassistant/components/sensor/swiss_public_transport.py @@ -0,0 +1,148 @@ +""" +homeassistant.components.sensor.swiss_public_transport +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Swiss public transport sensor will give you the next two departure times +from a given location to another one. This sensor is limited to Switzerland. + +Configuration: + +To use the Swiss public transport sensor you will need to add something like +the following to your config/configuration.yaml + +sensor: + platform: swiss_public_transport + from: STATION_ID + to: STATION_ID + +Variables: + +from +*Required +Start station/stop of your trip. To search for the ID of the station, use the +an URL like this: http://transport.opendata.ch/v1/locations?query=Wankdorf +to query for the station. If the score is 100 ("score":"100" in the response), +it is a perfect match. + +to +*Required +Destination station/stop of the trip. Same procedure as for the start station. + +Details for the API : http://transport.opendata.ch +""" +import logging +from datetime import timedelta + +from homeassistant.util import Throttle +import homeassistant.util.dt as dt_util +from homeassistant.helpers.entity import Entity + +_LOGGER = logging.getLogger(__name__) +_RESOURCE = 'http://transport.opendata.ch/v1/' + +# Return cached results if last scan was less then this time ago +MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Get the Swiss public transport sensor. """ + + if None in (hass.config.latitude, hass.config.longitude): + _LOGGER.error("Latitude or longitude not set in Home Assistant config") + return False + + try: + # pylint: disable=unused-variable + from requests import get + + except ImportError: + _LOGGER.exception( + "Unable to import requests. " + "Did you maybe not install the 'Requests' package?") + + return None + + # journal contains [0] Station ID start, [1] Station ID destination + # [2] Station name start, and [3] Station name destination + journey = [] + journey.append(config.get('from', None)) + journey.append(config.get('to', None)) + try: + for location in [config.get('from', None), config.get('to', None)]: + # transport.opendata.ch doesn't play nice with requests.Session + result = get(_RESOURCE + 'locations?query=%s' % location) + journey.append(result.json()['stations'][0]['name']) + except KeyError: + _LOGGER.error( + "Unable to determine stations. " + "Check your settings and/or the availability of opendata.ch") + + return None + + dev = [] + data = PublicTransportData(journey) + dev.append(SwissPublicTransportSensor(data, journey)) + add_devices(dev) + + +# pylint: disable=too-few-public-methods +class SwissPublicTransportSensor(Entity): + """ Implements an Swiss public transport sensor. """ + + def __init__(self, data, journey): + self.data = data + self._name = journey[2] + '-' + journey[3] + self.update() + + @property + def name(self): + """ Returns the name. """ + return self._name + + @property + def state(self): + """ Returns the state of the device. """ + return self._state + + # pylint: disable=too-many-branches + def update(self): + """ Gets the latest data from opendata.ch and updates the states. """ + times = self.data.update() + if times is not None: + self._state = times[0] + ', ' + times[1] + + +# pylint: disable=too-few-public-methods +class PublicTransportData(object): + """ Class for handling the data retrieval. """ + + def __init__(self, journey): + self.times = ['n/a', 'n/a'] + self.start = journey[0] + self.destination = journey[1] + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + def update(self): + """ Gets the latest data from opendata.ch. """ + + from requests import get + + response = get( + _RESOURCE + + 'connections?' + + 'from=' + self.start + '&' + + 'to=' + self.destination + '&' + + 'fields[]=connections/from/departureTimestamp/&' + + 'fields[]=connections/') + + try: + self.times.insert(0, dt_util.timestamp_to_short_time_str( + response.json()['connections'][0]['from'] + ['departureTimestamp'])) + self.times.insert(1, dt_util.timestamp_to_short_time_str( + response.json()['connections'][1]['from'] + ['departureTimestamp'])) + return self.times + + except KeyError: + return self.times \ No newline at end of file From b1b0b2bc651031525cd5fb34802c4a83d459179a Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 31 May 2015 22:55:47 +0200 Subject: [PATCH 03/13] add newline at the end --- homeassistant/components/sensor/swiss_public_transport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/swiss_public_transport.py b/homeassistant/components/sensor/swiss_public_transport.py index ee162a37164..bd12cb84cf9 100644 --- a/homeassistant/components/sensor/swiss_public_transport.py +++ b/homeassistant/components/sensor/swiss_public_transport.py @@ -145,4 +145,4 @@ class PublicTransportData(object): return self.times except KeyError: - return self.times \ No newline at end of file + return self.times From fafea688e44f4fdb95f90c147f46863c6807bc74 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 31 May 2015 23:25:38 +0200 Subject: [PATCH 04/13] add newline --- homeassistant/util/dt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/util/dt.py b/homeassistant/util/dt.py index f3c96368667..dd9d06ef674 100644 --- a/homeassistant/util/dt.py +++ b/homeassistant/util/dt.py @@ -108,6 +108,7 @@ def str_to_datetime(dt_str): except ValueError: # If dt_str did not match our format return None + def timestamp_to_short_time_str(timestamp): """ Converts a UNIX timestamp to a short time string format. From c8c3b825e45f01888598745d25a6964ddc8864e1 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 31 May 2015 23:31:24 +0200 Subject: [PATCH 05/13] add swiss_public_transport.py --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index 4029b48d47c..e1a939dd03d 100644 --- a/.coveragerc +++ b/.coveragerc @@ -40,6 +40,7 @@ omit = homeassistant/components/sensor/mysensors.py homeassistant/components/sensor/openweathermap.py homeassistant/components/sensor/sabnzbd.py + homeassistant/components/sensor/swiss_public_transport.py homeassistant/components/sensor/systemmonitor.py homeassistant/components/sensor/time_date.py homeassistant/components/sensor/transmission.py From 038bfde6aa493107e5c779a800c9f31d5f85d791 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 1 Jun 2015 13:37:57 +0200 Subject: [PATCH 06/13] add shortcut --- homeassistant/components/sensor/swiss_public_transport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/swiss_public_transport.py b/homeassistant/components/sensor/swiss_public_transport.py index bd12cb84cf9..c48194d6eee 100644 --- a/homeassistant/components/sensor/swiss_public_transport.py +++ b/homeassistant/components/sensor/swiss_public_transport.py @@ -109,7 +109,7 @@ class SwissPublicTransportSensor(Entity): """ Gets the latest data from opendata.ch and updates the states. """ times = self.data.update() if times is not None: - self._state = times[0] + ', ' + times[1] + self._state = ', '.join(times) # pylint: disable=too-few-public-methods From 1aa98bea2be15d525622b82d5a39fd4b881647b3 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 1 Jun 2015 13:40:21 +0200 Subject: [PATCH 07/13] remove lat/long check --- homeassistant/components/sensor/swiss_public_transport.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/homeassistant/components/sensor/swiss_public_transport.py b/homeassistant/components/sensor/swiss_public_transport.py index c48194d6eee..4a4ac58eb3d 100644 --- a/homeassistant/components/sensor/swiss_public_transport.py +++ b/homeassistant/components/sensor/swiss_public_transport.py @@ -47,10 +47,6 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) def setup_platform(hass, config, add_devices, discovery_info=None): """ Get the Swiss public transport sensor. """ - if None in (hass.config.latitude, hass.config.longitude): - _LOGGER.error("Latitude or longitude not set in Home Assistant config") - return False - try: # pylint: disable=unused-variable from requests import get From da68e4ab11b668f1873a760a895b0dc44c7f670f Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 1 Jun 2015 13:47:32 +0200 Subject: [PATCH 08/13] update depenency handling (requests) --- .../components/sensor/swiss_public_transport.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/homeassistant/components/sensor/swiss_public_transport.py b/homeassistant/components/sensor/swiss_public_transport.py index 4a4ac58eb3d..41fb249316e 100644 --- a/homeassistant/components/sensor/swiss_public_transport.py +++ b/homeassistant/components/sensor/swiss_public_transport.py @@ -32,6 +32,7 @@ Details for the API : http://transport.opendata.ch """ import logging from datetime import timedelta +from requests import get from homeassistant.util import Throttle import homeassistant.util.dt as dt_util @@ -47,17 +48,6 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) def setup_platform(hass, config, add_devices, discovery_info=None): """ Get the Swiss public transport sensor. """ - try: - # pylint: disable=unused-variable - from requests import get - - except ImportError: - _LOGGER.exception( - "Unable to import requests. " - "Did you maybe not install the 'Requests' package?") - - return None - # journal contains [0] Station ID start, [1] Station ID destination # [2] Station name start, and [3] Station name destination journey = [] @@ -121,8 +111,6 @@ class PublicTransportData(object): def update(self): """ Gets the latest data from opendata.ch. """ - from requests import get - response = get( _RESOURCE + 'connections?' + From 9d1e881f12d94b729da190995b9ea1ef56dba02d Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 1 Jun 2015 13:49:08 +0200 Subject: [PATCH 09/13] use string formatting --- homeassistant/components/sensor/swiss_public_transport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/swiss_public_transport.py b/homeassistant/components/sensor/swiss_public_transport.py index 41fb249316e..2ec16f72452 100644 --- a/homeassistant/components/sensor/swiss_public_transport.py +++ b/homeassistant/components/sensor/swiss_public_transport.py @@ -77,7 +77,7 @@ class SwissPublicTransportSensor(Entity): def __init__(self, data, journey): self.data = data - self._name = journey[2] + '-' + journey[3] + self._name = '{}-{}'.format(journey[2], journey[3]) self.update() @property From 90d55ef9011af26f1b00db27900a08d244bd3323 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 1 Jun 2015 13:49:46 +0200 Subject: [PATCH 10/13] switch from error to execption for logger --- homeassistant/components/sensor/swiss_public_transport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/swiss_public_transport.py b/homeassistant/components/sensor/swiss_public_transport.py index 2ec16f72452..6e56a7bc458 100644 --- a/homeassistant/components/sensor/swiss_public_transport.py +++ b/homeassistant/components/sensor/swiss_public_transport.py @@ -59,7 +59,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): result = get(_RESOURCE + 'locations?query=%s' % location) journey.append(result.json()['stations'][0]['name']) except KeyError: - _LOGGER.error( + _LOGGER.exception( "Unable to determine stations. " "Check your settings and/or the availability of opendata.ch") From 1e5e06fef5cb60b8d7a07499c717d602c7acd9a0 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 1 Jun 2015 13:51:00 +0200 Subject: [PATCH 11/13] update journey --- homeassistant/components/sensor/swiss_public_transport.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/sensor/swiss_public_transport.py b/homeassistant/components/sensor/swiss_public_transport.py index 6e56a7bc458..f2d8b65a95f 100644 --- a/homeassistant/components/sensor/swiss_public_transport.py +++ b/homeassistant/components/sensor/swiss_public_transport.py @@ -50,9 +50,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # journal contains [0] Station ID start, [1] Station ID destination # [2] Station name start, and [3] Station name destination - journey = [] - journey.append(config.get('from', None)) - journey.append(config.get('to', None)) + journey = [config.get('from'), config.get('to')] try: for location in [config.get('from', None), config.get('to', None)]: # transport.opendata.ch doesn't play nice with requests.Session From c8111b988eba0d3fd1f27c6878ab928fb2c3b7fb Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 1 Jun 2015 14:02:27 +0200 Subject: [PATCH 12/13] Revert "add timestamp to short time" This reverts commit 1be50d83dcecb8a5609df3f0b2bd78fad1087f6e. Conflicts: homeassistant/util/dt.py --- homeassistant/util/dt.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/homeassistant/util/dt.py b/homeassistant/util/dt.py index dd9d06ef674..fbe00c85527 100644 --- a/homeassistant/util/dt.py +++ b/homeassistant/util/dt.py @@ -109,15 +109,6 @@ def str_to_datetime(dt_str): return None -def timestamp_to_short_time_str(timestamp): - """ Converts a UNIX timestamp to a short time string format. - - @rtype: str - """ - return dt.datetime.fromtimestamp( - int(timestamp)).strftime(TIME_SHORT_STR_FORMAT) - - def strip_microseconds(dattim): """ Returns a copy of dattime object but with microsecond set to 0. """ return dattim.replace(microsecond=0) From 458838e9c63688637bb765ce7d18142affb12148 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 2 Jun 2015 15:49:24 +0200 Subject: [PATCH 13/13] implement comments from #157 --- .../sensor/swiss_public_transport.py | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/sensor/swiss_public_transport.py b/homeassistant/components/sensor/swiss_public_transport.py index f2d8b65a95f..a9eda2754bd 100644 --- a/homeassistant/components/sensor/swiss_public_transport.py +++ b/homeassistant/components/sensor/swiss_public_transport.py @@ -92,16 +92,17 @@ class SwissPublicTransportSensor(Entity): def update(self): """ Gets the latest data from opendata.ch and updates the states. """ times = self.data.update() - if times is not None: + try: self._state = ', '.join(times) + except TypeError: + pass # pylint: disable=too-few-public-methods class PublicTransportData(object): - """ Class for handling the data retrieval. """ + """ Class for handling the data retrieval. """ def __init__(self, journey): - self.times = ['n/a', 'n/a'] self.start = journey[0] self.destination = journey[1] @@ -117,14 +118,15 @@ class PublicTransportData(object): 'fields[]=connections/from/departureTimestamp/&' + 'fields[]=connections/') - try: - self.times.insert(0, dt_util.timestamp_to_short_time_str( - response.json()['connections'][0]['from'] - ['departureTimestamp'])) - self.times.insert(1, dt_util.timestamp_to_short_time_str( - response.json()['connections'][1]['from'] - ['departureTimestamp'])) - return self.times + connections = response.json()['connections'][:2] + try: + return [ + dt_util.datetime_to_short_time_str( + dt_util.as_local(dt_util.utc_from_timestamp( + item['from']['departureTimestamp'])) + ) + for item in connections + ] except KeyError: - return self.times + return ['n/a']