diff --git a/homeassistant/components/sensor/yr.py b/homeassistant/components/sensor/yr.py index 36dd193da97..8e1619a5941 100644 --- a/homeassistant/components/sensor/yr.py +++ b/homeassistant/components/sensor/yr.py @@ -36,17 +36,17 @@ sensor: """ import logging -import datetime -import urllib.request + import requests from homeassistant.const import ATTR_ENTITY_PICTURE from homeassistant.helpers.entity import Entity +from homeassistant.util import location, dt as dt_util _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['xmltodict', 'astral==0.8.1'] +REQUIREMENTS = ['xmltodict'] # Sensor types are defined like so: SENSOR_TYPES = { @@ -73,19 +73,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.error("Latitude or longitude not set in Home Assistant config") return False - from astral import Location, GoogleGeocoder - location = Location(('', '', hass.config.latitude, hass.config.longitude, - hass.config.time_zone, 0)) + elevation = config.get('elevation') - google = GoogleGeocoder() - try: - google._get_elevation(location) # pylint: disable=protected-access - _LOGGER.info( - 'Retrieved elevation from Google: %s', location.elevation) - elevation = location.elevation - except urllib.error.URLError: - # If no internet connection available etc. - elevation = 0 + if elevation is None: + elevation = location.elevation(hass.config.latitude, + hass.config.longitude) coordinates = dict(lat=hass.config.latitude, lon=hass.config.longitude, msl=elevation) @@ -116,9 +108,8 @@ class YrSensor(Entity): self.type = sensor_type self._state = None self._weather = weather - self._info = '' self._unit_of_measurement = SENSOR_TYPES[self.type][1] - self._update = datetime.datetime.fromtimestamp(0) + self._update = None self.update() @@ -134,14 +125,15 @@ class YrSensor(Entity): @property def state_attributes(self): """ Returns state attributes. """ - data = {} - data[''] = "Weather forecast from yr.no, delivered by the"\ - " Norwegian Meteorological Institute and the NRK" + data = { + 'about': "Weather forecast from yr.no, delivered by the" + " Norwegian Meteorological Institute and the NRK" + } if self.type == 'symbol': symbol_nr = self._state - data[ATTR_ENTITY_PICTURE] = "http://api.met.no/weatherapi/weathericon/1.1/" \ - "?symbol=" + str(symbol_nr) + \ - ";content_type=image/png" + data[ATTR_ENTITY_PICTURE] = \ + "http://api.met.no/weatherapi/weathericon/1.1/" \ + "?symbol={0};content_type=image/png".format(symbol_nr) return data @@ -150,76 +142,50 @@ class YrSensor(Entity): """ Unit of measurement of this entity, if any. """ return self._unit_of_measurement - @property - def should_poll(self): - """ Return True if entity has to be polled for state. """ - return True - - # pylint: disable=too-many-branches, too-many-return-statements def update(self): """ Gets the latest data from yr.no and updates the states. """ - self._weather.update() - now = datetime.datetime.now() + now = dt_util.utcnow() # check if data should be updated - if now <= self._update: + if self._update is not None and now <= self._update: return - time_data = self._weather.data['product']['time'] + self._weather.update() - # pylint: disable=consider-using-enumerate # find sensor - for k in range(len(time_data)): - valid_from = datetime.datetime.strptime(time_data[k]['@from'], - "%Y-%m-%dT%H:%M:%SZ") - valid_to = datetime.datetime.strptime(time_data[k]['@to'], - "%Y-%m-%dT%H:%M:%SZ") - self._update = valid_to - self._info = "Forecast between " + time_data[k]['@from'] \ - + " and " + time_data[k]['@to'] + ". " + for time_entry in self._weather.data['product']['time']: + valid_from = dt_util.str_to_datetime( + time_entry['@from'], "%Y-%m-%dT%H:%M:%SZ") + valid_to = dt_util.str_to_datetime( + time_entry['@to'], "%Y-%m-%dT%H:%M:%SZ") - temp_data = time_data[k]['location'] - if self.type not in temp_data and now >= valid_to: + loc_data = time_entry['location'] + + if self.type not in loc_data or now >= valid_to: continue + + self._update = valid_to + if self.type == 'precipitation' and valid_from < now: - self._state = temp_data[self.type]['@value'] - return + self._state = loc_data[self.type]['@value'] + break elif self.type == 'symbol' and valid_from < now: - self._state = temp_data[self.type]['@number'] - return - elif self.type == 'temperature': - self._state = temp_data[self.type]['@value'] - return + self._state = loc_data[self.type]['@number'] + break + elif self.type == ('temperature', 'pressure', 'humidity', + 'dewpointTemperature'): + self._state = loc_data[self.type]['@value'] + break elif self.type == 'windSpeed': - self._state = temp_data[self.type]['@mps'] - return - elif self.type == 'pressure': - self._state = temp_data[self.type]['@value'] - return + self._state = loc_data[self.type]['@mps'] + break elif self.type == 'windDirection': - self._state = float(temp_data[self.type]['@deg']) - return - elif self.type == 'humidity': - self._state = temp_data[self.type]['@value'] - return - elif self.type == 'fog': - self._state = temp_data[self.type]['@percent'] - return - elif self.type == 'cloudiness': - self._state = temp_data[self.type]['@percent'] - return - elif self.type == 'lowClouds': - self._state = temp_data[self.type]['@percent'] - return - elif self.type == 'mediumClouds': - self._state = temp_data[self.type]['@percent'] - return - elif self.type == 'highClouds': - self._state = temp_data[self.type]['@percent'] - return - elif self.type == 'dewpointTemperature': - self._state = temp_data[self.type]['@value'] - return + self._state = float(loc_data[self.type]['@deg']) + break + elif self.type in ('fog', 'cloudiness', 'lowClouds', + 'mediumClouds', 'highClouds'): + self._state = loc_data[self.type]['@percent'] + break # pylint: disable=too-few-public-methods @@ -230,14 +196,14 @@ class YrData(object): self._url = 'http://api.yr.no/weatherapi/locationforecast/1.9/?' \ 'lat={lat};lon={lon};msl={msl}'.format(**coordinates) - self._nextrun = datetime.datetime.fromtimestamp(0) + self._nextrun = None + self.data = {} self.update() def update(self): """ Gets the latest data from yr.no """ - now = datetime.datetime.now() # check if new will be available - if now <= self._nextrun: + if self._nextrun is not None and dt_util.utcnow() <= self._nextrun: return try: response = requests.get(self._url) @@ -252,5 +218,5 @@ class YrData(object): model = self.data['meta']['model'] if '@nextrun' not in model: model = model[0] - self._nextrun = datetime.datetime.strptime(model['@nextrun'], - "%Y-%m-%dT%H:%M:%SZ") + self._nextrun = dt_util.str_to_datetime(model['@nextrun'], + "%Y-%m-%dT%H:%M:%SZ") diff --git a/homeassistant/util/dt.py b/homeassistant/util/dt.py index 35795a7ae7f..a2c796c20eb 100644 --- a/homeassistant/util/dt.py +++ b/homeassistant/util/dt.py @@ -108,14 +108,14 @@ def datetime_to_date_str(dattim): return dattim.strftime(DATE_STR_FORMAT) -def str_to_datetime(dt_str): +def str_to_datetime(dt_str, dt_format=DATETIME_STR_FORMAT): """ Converts a string to a UTC datetime object. @rtype: datetime """ try: return dt.datetime.strptime( - dt_str, DATETIME_STR_FORMAT).replace(tzinfo=pytz.utc) + dt_str, dt_format).replace(tzinfo=pytz.utc) except ValueError: # If dt_str did not match our format return None diff --git a/homeassistant/util/location.py b/homeassistant/util/location.py index 398a0a0c56c..185745d9207 100644 --- a/homeassistant/util/location.py +++ b/homeassistant/util/location.py @@ -4,6 +4,8 @@ import collections import requests from vincenty import vincenty +ELEVATION_URL = 'http://maps.googleapis.com/maps/api/elevation/json' + LocationInfo = collections.namedtuple( "LocationInfo", @@ -34,3 +36,20 @@ def detect_location_info(): def distance(lat1, lon1, lat2, lon2): """ Calculate the distance in meters between two points. """ return vincenty((lat1, lon1), (lat2, lon2)) * 1000 + + +def elevation(latitude, longitude): + """ Return elevation for given latitude and longitude. """ + + req = requests.get(ELEVATION_URL, params={ + 'locations': '{},{}'.format(latitude, longitude), + 'sensor': 'false', + }) + + if req.status_code != 200: + return 0 + + try: + return int(float(req.json()['results'][0]['elevation'])) + except (ValueError, KeyError): + return 0 diff --git a/tests/components/sensor/test_yr.py b/tests/components/sensor/test_yr.py index aa9a5a59944..7e95194aa4b 100644 --- a/tests/components/sensor/test_yr.py +++ b/tests/components/sensor/test_yr.py @@ -15,12 +15,8 @@ class TestSensorYr(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name self.hass = ha.HomeAssistant() - latitude = 32.87336 - longitude = 117.22743 - - # Compare it with the real data - self.hass.config.latitude = latitude - self.hass.config.longitude = longitude + self.hass.config.latitude = 32.87336 + self.hass.config.longitude = 117.22743 def tearDown(self): # pylint: disable=invalid-name """ Stop down stuff we started. """ @@ -30,6 +26,7 @@ class TestSensorYr(unittest.TestCase): self.assertTrue(sensor.setup(self.hass, { 'sensor': { 'platform': 'yr', + 'elevation': 0, } })) state = self.hass.states.get('sensor.yr_symbol') @@ -42,7 +39,14 @@ class TestSensorYr(unittest.TestCase): self.assertTrue(sensor.setup(self.hass, { 'sensor': { 'platform': 'yr', - 'monitored_conditions': {'pressure', 'windDirection', 'humidity', 'fog', 'windSpeed'} + 'elevation': 0, + 'monitored_conditions': { + 'pressure', + 'windDirection', + 'humidity', + 'fog', + 'windSpeed' + } } })) state = self.hass.states.get('sensor.yr_symbol')