From 469d09582749849712794d626490afb33639ac13 Mon Sep 17 00:00:00 2001 From: arsaboo Date: Wed, 17 Aug 2016 15:06:12 -0400 Subject: [PATCH 01/30] Create initial Wunderground weather sensor --- .../components/sensor/wunderground.py | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 homeassistant/components/sensor/wunderground.py diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py new file mode 100644 index 00000000000..ac060c455a6 --- /dev/null +++ b/homeassistant/components/sensor/wunderground.py @@ -0,0 +1,141 @@ +"""Support for Wunderground weather service.""" +from datetime import timedelta +import logging +import requests +from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle +from homeassistant.const import CONF_API_KEY +from homeassistant.const import TEMP_FAHRENHEIT +from homeassistant.const import TEMP_CELSIUS + +CONF_PWS_ID = 'pws_id' +_URLCONST = '/conditions/q/pws:' +_RESOURCE = 'http://api.wunderground.com/api/' +_LOGGER = logging.getLogger(__name__) + +# Return cached results if last scan was less then this time ago. +MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300) + +# Sensor types are defined like: Name, units +SENSOR_TYPES = { + 'weather': ['Weather Summary', None], + 'station_id': ['Station ID', None], + 'feelslike_c': ['Feels Like (°C)', TEMP_CELSIUS], + 'feelslike_f': ['Feels Like (°F)', TEMP_FAHRENHEIT], + 'feelslike_string': ['Feels Like', None], + 'heat_index_c': ['Dewpoint (°C)', TEMP_CELSIUS], + 'heat_index_f': ['Dewpoint (°F)', TEMP_FAHRENHEIT], + 'heat_index_string': ['Heat Index Summary', None], + 'dewpoint_c': ['Dewpoint (°C)', TEMP_CELSIUS], + 'dewpoint_f': ['Dewpoint (°F)', TEMP_FAHRENHEIT], + 'dewpoint_string': ['Dewpoint Summary', None], + 'wind_kph': ['Wind Speed', 'kpH'], + 'wind_mph': ['Wind Speed', 'mpH'], + 'UV': ['UV', None], + 'pressure_in': ['Pressure', 'in'], + 'pressure_mb': ['Pressure', 'mbar'], + 'wind_dir': ['Wind Direction', None], + 'wind_string': ['Wind Summary', None], + 'temp_c': ['Temperature (°C)', TEMP_CELSIUS], + 'temp_f': ['Temperature (°F)', TEMP_FAHRENHEIT], + 'relative_humidity': ['Relative Humidity', '%'], + 'visibility_mi': ['Visibility (miles)', 'mi'], + 'visibility_km': ['Visibility (km)', 'km'], + 'precip_today_in': ['Precipation Today', 'in'], + 'precip_today_metric': ['Precipation Today', 'mm'], + 'precip_today_string': ['Precipation today', None], + 'solarradiation': ['Solar Radiation', None] +} + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the Wundeground sensor.""" + payload = config.get('payload', None) + rest = WUndergroundData(_RESOURCE, + config.get(CONF_PWS_ID), + config.get(CONF_API_KEY), + payload) + sensors = [] + for variable in config['monitored_conditions']: + if variable in SENSOR_TYPES: + sensors.append(WUndergroundSensor(rest, variable)) + else: + _LOGGER.error('Wunderground sensor: "%s" does not exist', variable) + response = requests.get(_RESOURCE + config.get(CONF_API_KEY) + + _URLCONST + config.get(CONF_PWS_ID) + + '.json', timeout=10) + if "error" in response.json()["response"]: + _LOGGER.error("Check your Wunderground API") + return False + else: + add_devices(sensors) + rest.update() + + +class WUndergroundSensor(Entity): + """Implementing the Wunderground sensor.""" + + def __init__(self, rest, condition): + """Initialize the sensor.""" + self.rest = rest + self._condition = condition + self._unit_of_measurement = None + self.update() + + @property + def name(self): + """Return the name of the sensor.""" + return "PWS_" + str(self._condition) + + @property + def state(self): + """Return the state of the sensor.""" + self.weather = self.rest.data + return self.weather[str(self._condition)] + + @property + def entity_picture(self): + """Return the entity picture.""" + self.weather = self.rest.data + if self._condition == 'weather': + return self.weather['icon_url'] + + @property + def unit_of_measurement(self): + """Return the units of measurement.""" + return SENSOR_TYPES[self._condition][1] + + def update(self): + """Update current conditions.""" + self.rest.update() + self._state = self.rest.data + +# pylint: disable=too-few-public-methods + + +class WUndergroundData(object): + """Get data from Wundeground.""" + + def __init__(self, resource, pws_id, api_key, data): + """Initialize the data object.""" + self._resource = resource + self._api_key = api_key + self._pws_id = pws_id + self.data = None + self.unit_system = None + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + def update(self): + """Get the latest data from wunderground""" + try: + result = requests.get(self._resource + self._api_key + + '/conditions/q/pws:' + self._pws_id + '.json', + timeout=10) + if "error" in result.json(): + raise ValueError(result.json()["response"]["error"] + ["description"]) + else: + self.data = result.json()["current_observation"] + except ValueError as err: + _LOGGER.error("Check Wunderground API %s", err.args) + self.data = None From aabeda2b604bc44996d0afc4cc149deb71157123 Mon Sep 17 00:00:00 2001 From: arsaboo Date: Wed, 17 Aug 2016 16:15:07 -0400 Subject: [PATCH 02/30] Update wunderground.py --- homeassistant/components/sensor/wunderground.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index ac060c455a6..ab0727775ce 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -114,7 +114,7 @@ class WUndergroundSensor(Entity): class WUndergroundData(object): - """Get data from Wundeground.""" + """Get data from Wunderground.""" def __init__(self, resource, pws_id, api_key, data): """Initialize the data object.""" @@ -129,8 +129,8 @@ class WUndergroundData(object): """Get the latest data from wunderground""" try: result = requests.get(self._resource + self._api_key + - '/conditions/q/pws:' + self._pws_id + '.json', - timeout=10) + '/conditions/q/pws:' + self._pws_id + + '.json', timeout=10) if "error" in result.json(): raise ValueError(result.json()["response"]["error"] ["description"]) From 1a34bc53015a2e9052ed5701aef48c2a4e11a44a Mon Sep 17 00:00:00 2001 From: arsaboo Date: Wed, 17 Aug 2016 16:31:36 -0400 Subject: [PATCH 03/30] Removed lynting issues --- homeassistant/components/sensor/wunderground.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index ab0727775ce..ebafca9768e 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -1,4 +1,4 @@ -"""Support for Wunderground weather service.""" +"""Support for Wunderground weather service. from datetime import timedelta import logging import requests @@ -90,15 +90,15 @@ class WUndergroundSensor(Entity): @property def state(self): """Return the state of the sensor.""" - self.weather = self.rest.data - return self.weather[str(self._condition)] + value = self.rest.data + return value[str(self._condition)] @property def entity_picture(self): """Return the entity picture.""" - self.weather = self.rest.data + value = self.rest.data if self._condition == 'weather': - return self.weather['icon_url'] + return value['icon_url'] @property def unit_of_measurement(self): @@ -114,7 +114,7 @@ class WUndergroundSensor(Entity): class WUndergroundData(object): - """Get data from Wunderground.""" + """Get data from Wundeground.""" def __init__(self, resource, pws_id, api_key, data): """Initialize the data object.""" @@ -129,7 +129,7 @@ class WUndergroundData(object): """Get the latest data from wunderground""" try: result = requests.get(self._resource + self._api_key + - '/conditions/q/pws:' + self._pws_id + + '/conditions/q/pws:' + self._pws_id + '.json', timeout=10) if "error" in result.json(): raise ValueError(result.json()["response"]["error"] From fae9267701a452bc98793c7c71d239963f549c1b Mon Sep 17 00:00:00 2001 From: arsaboo Date: Wed, 17 Aug 2016 16:41:22 -0400 Subject: [PATCH 04/30] Update wunderground.py --- homeassistant/components/sensor/wunderground.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index ebafca9768e..038b57b707f 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -49,7 +49,7 @@ SENSOR_TYPES = { def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Wundeground sensor.""" + """Setup the Wundeground sensor. payload = config.get('payload', None) rest = WUndergroundData(_RESOURCE, config.get(CONF_PWS_ID), From 75cd1f80635ea6e8b4225a941bed557a54242ade Mon Sep 17 00:00:00 2001 From: arsaboo Date: Wed, 17 Aug 2016 16:50:32 -0400 Subject: [PATCH 05/30] Update wunderground.py --- homeassistant/components/sensor/wunderground.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index 038b57b707f..0ae33a7e607 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -42,14 +42,15 @@ SENSOR_TYPES = { 'visibility_mi': ['Visibility (miles)', 'mi'], 'visibility_km': ['Visibility (km)', 'km'], 'precip_today_in': ['Precipation Today', 'in'], - 'precip_today_metric': ['Precipation Today', 'mm'], - 'precip_today_string': ['Precipation today', None], + 'precip_today_metric': ['Precipitation Today', 'mm'], + 'precip_today_string': ['Precipitation today', None], 'solarradiation': ['Solar Radiation', None] } def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Wundeground sensor. + payload = config.get('payload', None) rest = WUndergroundData(_RESOURCE, config.get(CONF_PWS_ID), From 4dff42e8bb7280b39f4d9f99af79beab315fc5b8 Mon Sep 17 00:00:00 2001 From: arsaboo Date: Wed, 17 Aug 2016 17:04:11 -0400 Subject: [PATCH 06/30] Update wunderground.py --- homeassistant/components/sensor/wunderground.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index 0ae33a7e607..a18397b8cd9 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -49,8 +49,7 @@ SENSOR_TYPES = { def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Wundeground sensor. - + """Setup the Wunderground sensor. payload = config.get('payload', None) rest = WUndergroundData(_RESOURCE, config.get(CONF_PWS_ID), From be57cd55c50e0435c602c10fe87dcff00cd47aa1 Mon Sep 17 00:00:00 2001 From: arsaboo Date: Wed, 17 Aug 2016 17:25:42 -0400 Subject: [PATCH 07/30] Update wunderground.py --- homeassistant/components/sensor/wunderground.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index a18397b8cd9..12f581c2e38 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -1,12 +1,10 @@ -"""Support for Wunderground weather service. +"""Support for Wunderground weather service.""" from datetime import timedelta import logging import requests from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -from homeassistant.const import CONF_API_KEY -from homeassistant.const import TEMP_FAHRENHEIT -from homeassistant.const import TEMP_CELSIUS +from homeassistant.const import (CONF_API_KEY, TEMP_FAHRENHEIT, TEMP_CELSIUS) CONF_PWS_ID = 'pws_id' _URLCONST = '/conditions/q/pws:' @@ -49,7 +47,7 @@ SENSOR_TYPES = { def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Wunderground sensor. + """Setup the Wunderground sensor.""" payload = config.get('payload', None) rest = WUndergroundData(_RESOURCE, config.get(CONF_PWS_ID), From a0bcd33b71e7b663648c99f8ea50d5078a262be6 Mon Sep 17 00:00:00 2001 From: arsaboo Date: Wed, 17 Aug 2016 17:48:37 -0400 Subject: [PATCH 08/30] Update wunderground.py --- homeassistant/components/sensor/wunderground.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index 12f581c2e38..a88f3e1310e 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -124,7 +124,7 @@ class WUndergroundData(object): @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): - """Get the latest data from wunderground""" + """Get the latest data from wunderground.""" try: result = requests.get(self._resource + self._api_key + '/conditions/q/pws:' + self._pws_id + From 333e3ba822be4375e298270b086b93e5d5cef484 Mon Sep 17 00:00:00 2001 From: "Teagan M. Glenn" Date: Wed, 17 Aug 2016 22:18:37 -0600 Subject: [PATCH 09/30] Add imports --- homeassistant/components/sensor/wunderground.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index a88f3e1310e..051f1f79341 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -2,9 +2,15 @@ from datetime import timedelta import logging import requests + +import voluptuous as vol + from homeassistant.helpers.entity import Entity +from homeassistant.helpers.config_validation import ensure_list from homeassistant.util import Throttle -from homeassistant.const import (CONF_API_KEY, TEMP_FAHRENHEIT, TEMP_CELSIUS) +from homeassistant.const import (CONF_PLATFORM, CONF_MONITORED_CONDITIONS, + CONF_API_KEY, TEMP_FAHRENHEIT, TEMP_CELSIUS, + STATE_UNKNOWN) CONF_PWS_ID = 'pws_id' _URLCONST = '/conditions/q/pws:' From b7809675ebd689d0877bf7f17ad508c090031203 Mon Sep 17 00:00:00 2001 From: "Teagan M. Glenn" Date: Wed, 17 Aug 2016 22:19:13 -0600 Subject: [PATCH 10/30] Config schema --- homeassistant/components/sensor/wunderground.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index 051f1f79341..04ee23e714b 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -51,6 +51,13 @@ SENSOR_TYPES = { 'solarradiation': ['Solar Radiation', None] } +PLATFORM_SCHEMA = vol.Schema({ + vol.Required(CONF_PLATFORM): "wunderground", + vol.Required(CONF_API_KEY): vol.Coerce(str), + CONF_PWS_ID: vol.Coerce(str), + vol.Required(CONF_MONITORED_CONDITIONS, default=[]): ensure_list, +}) + def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Wunderground sensor.""" From a09baf1d5a9819fadf1222ce9438d7c9eb5577be Mon Sep 17 00:00:00 2001 From: "Teagan M. Glenn" Date: Wed, 17 Aug 2016 22:19:57 -0600 Subject: [PATCH 11/30] Not using payload --- homeassistant/components/sensor/wunderground.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index 04ee23e714b..73fa69cf73c 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -61,7 +61,6 @@ PLATFORM_SCHEMA = vol.Schema({ def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Wunderground sensor.""" - payload = config.get('payload', None) rest = WUndergroundData(_RESOURCE, config.get(CONF_PWS_ID), config.get(CONF_API_KEY), From 53b97feb3cb3ec440959973321efc9906b168107 Mon Sep 17 00:00:00 2001 From: "Teagan M. Glenn" Date: Wed, 17 Aug 2016 22:20:22 -0600 Subject: [PATCH 12/30] Rename constant - make valid for lat/long too --- homeassistant/components/sensor/wunderground.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index 73fa69cf73c..84fe2bdbe25 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -13,7 +13,8 @@ from homeassistant.const import (CONF_PLATFORM, CONF_MONITORED_CONDITIONS, STATE_UNKNOWN) CONF_PWS_ID = 'pws_id' -_URLCONST = '/conditions/q/pws:' + +_URL_QUERY = '/conditions/q/' _RESOURCE = 'http://api.wunderground.com/api/' _LOGGER = logging.getLogger(__name__) From e4abecd35973528cd8fbf30354ff7f1fa4ccc774 Mon Sep 17 00:00:00 2001 From: "Teagan M. Glenn" Date: Wed, 17 Aug 2016 22:22:11 -0600 Subject: [PATCH 13/30] Build url helper method --- homeassistant/components/sensor/wunderground.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index 84fe2bdbe25..d5916f99ccc 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -135,6 +135,16 @@ class WUndergroundData(object): self.data = None self.unit_system = None + def _build_url(self): + print(self._api_key) + url = _RESOURCE + self._api_key + _URL_QUERY + if self._pws_id: + url = url + 'pws:' + self._pws_id + else: + url = url + '{},{}'.format(self._latitude, self._longitude) + + return url + '.json' + @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): """Get the latest data from wunderground.""" From 42caa310674702d02867f2339e0359cbf701fb5a Mon Sep 17 00:00:00 2001 From: "Teagan M. Glenn" Date: Wed, 17 Aug 2016 22:22:29 -0600 Subject: [PATCH 14/30] Unused variable --- homeassistant/components/sensor/wunderground.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index d5916f99ccc..4e94249f199 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -133,7 +133,6 @@ class WUndergroundData(object): self._api_key = api_key self._pws_id = pws_id self.data = None - self.unit_system = None def _build_url(self): print(self._api_key) From 1a8e17ce411debf631544d62d99a94c3ed1cc2a2 Mon Sep 17 00:00:00 2001 From: "Teagan M. Glenn" Date: Wed, 17 Aug 2016 22:28:05 -0600 Subject: [PATCH 15/30] Pass hass to constructor --- homeassistant/components/sensor/wunderground.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index 4e94249f199..b71510792cf 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -127,11 +127,13 @@ class WUndergroundSensor(Entity): class WUndergroundData(object): """Get data from Wundeground.""" - def __init__(self, resource, pws_id, api_key, data): + def __init__(self, hass, api_key, pws_id=None): """Initialize the data object.""" - self._resource = resource + self._hass = hass self._api_key = api_key self._pws_id = pws_id + self._latitude = hass.config.latitude + self._longitude = hass.config.longitude self.data = None def _build_url(self): From 563154c3c2778dd5755933014091f42079f6a021 Mon Sep 17 00:00:00 2001 From: "Teagan M. Glenn" Date: Wed, 17 Aug 2016 22:28:18 -0600 Subject: [PATCH 16/30] Validate configuration --- homeassistant/components/sensor/wunderground.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index b71510792cf..0eb9f4bd5a8 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -64,6 +64,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Wunderground sensor.""" rest = WUndergroundData(_RESOURCE, config.get(CONF_PWS_ID), + if not PLATFORM_SCHEMA(config): + return False + config.get(CONF_API_KEY), payload) sensors = [] From 62b00e1294a2bd8f8f9fda0194cbcb6c88cd7861 Mon Sep 17 00:00:00 2001 From: "Teagan M. Glenn" Date: Wed, 17 Aug 2016 22:29:00 -0600 Subject: [PATCH 17/30] Update invocation of WUndergroundData --- homeassistant/components/sensor/wunderground.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index 0eb9f4bd5a8..83457adc06f 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -62,13 +62,12 @@ PLATFORM_SCHEMA = vol.Schema({ def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Wunderground sensor.""" - rest = WUndergroundData(_RESOURCE, - config.get(CONF_PWS_ID), if not PLATFORM_SCHEMA(config): return False + rest = WUndergroundData(hass, config.get(CONF_API_KEY), - payload) + config.get(CONF_PWS_ID, None)) sensors = [] for variable in config['monitored_conditions']: if variable in SENSOR_TYPES: From 31237a891cb297d82de501c5041eb5d3dee05467 Mon Sep 17 00:00:00 2001 From: "Teagan M. Glenn" Date: Wed, 17 Aug 2016 22:29:25 -0600 Subject: [PATCH 18/30] Catch exception from update on initial platform setup --- homeassistant/components/sensor/wunderground.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index 83457adc06f..40768437f16 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -82,7 +82,16 @@ def setup_platform(hass, config, add_devices, discovery_info=None): return False else: add_devices(sensors) + + try: rest.update() + except ValueError as err: + _LOGGER.error("Received error from WUnderground: %s", err) + return False + + add_devices(sensors) + + return True class WUndergroundSensor(Entity): From 417711d6658d440556e6231ee8a580bd5d5d2add Mon Sep 17 00:00:00 2001 From: "Teagan M. Glenn" Date: Wed, 17 Aug 2016 22:29:37 -0600 Subject: [PATCH 19/30] Refactoring --- homeassistant/components/sensor/wunderground.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index 40768437f16..8ec6d54a6fe 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -74,14 +74,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None): sensors.append(WUndergroundSensor(rest, variable)) else: _LOGGER.error('Wunderground sensor: "%s" does not exist', variable) - response = requests.get(_RESOURCE + config.get(CONF_API_KEY) + - _URLCONST + config.get(CONF_PWS_ID) + - '.json', timeout=10) - if "error" in response.json()["response"]: - _LOGGER.error("Check your Wunderground API") - return False - else: - add_devices(sensors) try: rest.update() From afef255a25c545c5f08a5f2e665e4b01f43fd074 Mon Sep 17 00:00:00 2001 From: "Teagan M. Glenn" Date: Wed, 17 Aug 2016 22:29:49 -0600 Subject: [PATCH 20/30] Condition is already a string --- homeassistant/components/sensor/wunderground.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index 8ec6d54a6fe..79548b04089 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -99,7 +99,7 @@ class WUndergroundSensor(Entity): @property def name(self): """Return the name of the sensor.""" - return "PWS_" + str(self._condition) + return "PWS_" + self._condition @property def state(self): From ecb4eb843b9ab65e13f4534d9972007da1b85269 Mon Sep 17 00:00:00 2001 From: "Teagan M. Glenn" Date: Wed, 17 Aug 2016 22:30:03 -0600 Subject: [PATCH 21/30] Don't call update on init of sensor --- homeassistant/components/sensor/wunderground.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index 79548b04089..e238800c50c 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -94,7 +94,6 @@ class WUndergroundSensor(Entity): self.rest = rest self._condition = condition self._unit_of_measurement = None - self.update() @property def name(self): From dd14f90afb20c7d4d9a390cf77bcc0199503bbab Mon Sep 17 00:00:00 2001 From: "Teagan M. Glenn" Date: Wed, 17 Aug 2016 22:30:23 -0600 Subject: [PATCH 22/30] Error handling on state --- homeassistant/components/sensor/wunderground.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index e238800c50c..c7119dbdc62 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -103,8 +103,10 @@ class WUndergroundSensor(Entity): @property def state(self): """Return the state of the sensor.""" - value = self.rest.data - return value[str(self._condition)] + if self.rest.data and self._condition in self.rest.data: + return self.rest.data[self._condition] + else: + return STATE_UNKNOWN @property def entity_picture(self): From e54ba5ff72068071bce9e90dada17c6b72b1cc0e Mon Sep 17 00:00:00 2001 From: "Teagan M. Glenn" Date: Wed, 17 Aug 2016 22:31:28 -0600 Subject: [PATCH 23/30] No need no need to set variable --- homeassistant/components/sensor/wunderground.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index c7119dbdc62..a3980f4e327 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -111,9 +111,8 @@ class WUndergroundSensor(Entity): @property def entity_picture(self): """Return the entity picture.""" - value = self.rest.data if self._condition == 'weather': - return value['icon_url'] + return self.rest.data['icon_url'] @property def unit_of_measurement(self): From 466dd35f3d0b1b45f73ff9ccdd5078a00798dc6d Mon Sep 17 00:00:00 2001 From: "Teagan M. Glenn" Date: Wed, 17 Aug 2016 22:31:47 -0600 Subject: [PATCH 24/30] Don't set state on update - state already handles this --- homeassistant/components/sensor/wunderground.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index a3980f4e327..1243c55fa51 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -122,7 +122,6 @@ class WUndergroundSensor(Entity): def update(self): """Update current conditions.""" self.rest.update() - self._state = self.rest.data # pylint: disable=too-few-public-methods From d2ba8ee0a7f56c754fadffc0ddde62469cb7a518 Mon Sep 17 00:00:00 2001 From: "Teagan M. Glenn" Date: Wed, 17 Aug 2016 22:31:58 -0600 Subject: [PATCH 25/30] Reraise exception --- homeassistant/components/sensor/wunderground.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index 1243c55fa51..5c524c3f749 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -163,3 +163,4 @@ class WUndergroundData(object): except ValueError as err: _LOGGER.error("Check Wunderground API %s", err.args) self.data = None + raise From 87f81bf3b48e7dc2a62d5819382b42618541433d Mon Sep 17 00:00:00 2001 From: "Teagan M. Glenn" Date: Wed, 17 Aug 2016 22:32:19 -0600 Subject: [PATCH 26/30] Use url builder helper --- homeassistant/components/sensor/wunderground.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index 5c524c3f749..8dc3038cadb 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -152,11 +152,7 @@ class WUndergroundData(object): def update(self): """Get the latest data from wunderground.""" try: - result = requests.get(self._resource + self._api_key + - '/conditions/q/pws:' + self._pws_id + - '.json', timeout=10) - if "error" in result.json(): - raise ValueError(result.json()["response"]["error"] + result = requests.get(self._build_url(), timeout=10).json() ["description"]) else: self.data = result.json()["current_observation"] From 4e586c18ff4bf51401083a5b0dcec80efaabb864 Mon Sep 17 00:00:00 2001 From: "Teagan M. Glenn" Date: Wed, 17 Aug 2016 22:32:42 -0600 Subject: [PATCH 27/30] Check for error and pull obvservation --- homeassistant/components/sensor/wunderground.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index 8dc3038cadb..979bff40a89 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -153,9 +153,11 @@ class WUndergroundData(object): """Get the latest data from wunderground.""" try: result = requests.get(self._build_url(), timeout=10).json() + if "error" in result['response']: + raise ValueError(result['response']["error"] ["description"]) else: - self.data = result.json()["current_observation"] + self.data = result["current_observation"] except ValueError as err: _LOGGER.error("Check Wunderground API %s", err.args) self.data = None From a5fd04f2155ca7505143359293a5d7718f6b09e9 Mon Sep 17 00:00:00 2001 From: "Teagan M. Glenn" Date: Wed, 17 Aug 2016 22:33:39 -0600 Subject: [PATCH 28/30] Unit tests around wunderground --- tests/components/sensor/test_wunderground.py | 138 +++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 tests/components/sensor/test_wunderground.py diff --git a/tests/components/sensor/test_wunderground.py b/tests/components/sensor/test_wunderground.py new file mode 100644 index 00000000000..c6664b71254 --- /dev/null +++ b/tests/components/sensor/test_wunderground.py @@ -0,0 +1,138 @@ +"""The tests for the forecast.io platform.""" +import unittest + +from homeassistant.components.sensor import wunderground +from homeassistant.const import TEMP_CELSIUS +from homeassistant import core as ha + +VALID_CONFIG_PWS = { + 'platform': 'wunderground', + 'api_key': 'foo', + 'pws_id': 'bar', + 'monitored_conditions': [ + 'weather', 'feelslike_c' + ] +} + +VALID_CONFIG = { + 'platform': 'wunderground', + 'api_key': 'foo', + 'monitored_conditions': [ + 'weather', 'feelslike_c' + ] +} + +FEELS_LIKE = '40' +WEATHER = 'Clear' +ICON_URL = 'http://icons.wxug.com/i/c/k/clear.gif' + + +def mocked_requests_get(*args, **kwargs): + class MockResponse: + def __init__(self, json_data, status_code): + self.json_data = json_data + self.status_code = status_code + + def json(self): + return self.json_data + + if str(args[0]).startswith('http://api.wunderground.com/api/foo/'): + # Return valid response + print('VALID RESPONSE') + return MockResponse({ + "response": { + "version": "0.1", + "termsofService": + "http://www.wunderground.com/weather/api/d/terms.html", + "features": { + "conditions": 1 + } + }, "current_observation": { + "image": { + "url": + 'http://icons.wxug.com/graphics/wu2/logo_130x80.png', + "title": "Weather Underground", + "link": "http://www.wunderground.com" + }, + "feelslike_c": FEELS_LIKE, + "weather": WEATHER, + "icon_url": ICON_URL + } + }, 200) + else: + # Return invalid api key + print('INVALID RESPONSE') + return MockResponse({ + "response": { + "version": "0.1", + "termsofService": + "http://www.wunderground.com/weather/api/d/terms.html", + "features": {}, + "error": { + "type": "keynotfound", + "description": "this key does not exist" + } + } + }, 200) + + +class TestWundergroundSetup(unittest.TestCase): + """Test the wunderground platform.""" + + DEVICES = [] + + def add_devices(self, devices): + for device in devices: + self.DEVICES.append(device) + + def setUp(self): + """Initialize values for this testcase class.""" + self.DEVICES = [] + self.hass = ha.HomeAssistant() + self.key = 'foo' + self.config = VALID_CONFIG_PWS + self.lat = 37.8267 + self.lon = -122.423 + self.hass.config.latitude = self.lat + self.hass.config.longitude = self.lon + + @unittest.mock.patch('requests.get', side_effect=mocked_requests_get) + def test_setup(self, req_mock): + """Test that the component is loaded if passed in PSW Id.""" + print('1') + self.assertTrue( + wunderground.setup_platform(self.hass, VALID_CONFIG_PWS, + self.add_devices, None)) + print('2') + self.assertTrue( + wunderground.setup_platform(self.hass, VALID_CONFIG, + self.add_devices, + None)) + invalid_config = { + 'platform': 'wunderground', + 'api_key': 'BOB', + 'pws_id': 'bar', + 'monitored_conditions': [ + 'weather', 'feelslike_c' + ] + } + + self.assertFalse( + wunderground.setup_platform(self.hass, invalid_config, + self.add_devices, None)) + + @unittest.mock.patch('requests.get', side_effect=mocked_requests_get) + def test_sensor(self, req_mock): + wunderground.setup_platform(self.hass, VALID_CONFIG, self.add_devices, + None) + print(str(self.DEVICES)) + for device in self.DEVICES: + self.assertTrue(str(device.name).startswith('PWS_')) + if device.name == 'PWS_weather': + self.assertEqual(ICON_URL, device.entity_picture) + self.assertEqual(WEATHER, device.state) + self.assertIsNone(device.unit_of_measurement) + else: + self.assertIsNone(device.entity_picture) + self.assertEqual(FEELS_LIKE, device.state) + self.assertEqual(TEMP_CELSIUS, device.unit_of_measurement) From 90fdc8983840d403e43ddd2018a93f4a55d89152 Mon Sep 17 00:00:00 2001 From: arsaboo Date: Thu, 18 Aug 2016 09:59:41 -0400 Subject: [PATCH 29/30] Updated to address @balloob's comments --- homeassistant/components/sensor/wunderground.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index 979bff40a89..e727b5d79a8 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -13,9 +13,7 @@ from homeassistant.const import (CONF_PLATFORM, CONF_MONITORED_CONDITIONS, STATE_UNKNOWN) CONF_PWS_ID = 'pws_id' - -_URL_QUERY = '/conditions/q/' -_RESOURCE = 'http://api.wunderground.com/api/' +_RESOURCE = 'http://api.wunderground.com/api/{}/conditions/q/' _LOGGER = logging.getLogger(__name__) # Return cached results if last scan was less then this time ago. @@ -56,14 +54,13 @@ PLATFORM_SCHEMA = vol.Schema({ vol.Required(CONF_PLATFORM): "wunderground", vol.Required(CONF_API_KEY): vol.Coerce(str), CONF_PWS_ID: vol.Coerce(str), - vol.Required(CONF_MONITORED_CONDITIONS, default=[]): ensure_list, + vol.Required(CONF_MONITORED_CONDITIONS, + default=[]): vol.All(ensure_list, [vol.In(SENSOR_TYPES)]), }) def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Wunderground sensor.""" - if not PLATFORM_SCHEMA(config): - return False rest = WUndergroundData(hass, config.get(CONF_API_KEY), @@ -139,8 +136,7 @@ class WUndergroundData(object): self.data = None def _build_url(self): - print(self._api_key) - url = _RESOURCE + self._api_key + _URL_QUERY + url = _RESOURCE.format(self._api_key) if self._pws_id: url = url + 'pws:' + self._pws_id else: From 230dde4b5777d5ac6ecab2766cc89793f43fdcd8 Mon Sep 17 00:00:00 2001 From: arsaboo Date: Thu, 18 Aug 2016 10:12:56 -0400 Subject: [PATCH 30/30] Removed blank line (linting error) --- homeassistant/components/sensor/wunderground.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index e727b5d79a8..cb724247436 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -61,7 +61,6 @@ PLATFORM_SCHEMA = vol.Schema({ def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Wunderground sensor.""" - rest = WUndergroundData(hass, config.get(CONF_API_KEY), config.get(CONF_PWS_ID, None))