Skip to content
Merged
82 changes: 40 additions & 42 deletions homeassistant/components/sensor/yr.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import asyncio
import logging

from datetime import timedelta
from random import randrange
from xml.parsers.expat import ExpatError

Expand All @@ -22,16 +21,17 @@
ATTR_ATTRIBUTION, CONF_NAME)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import (
async_track_point_in_utc_time, async_track_utc_time_change)
from homeassistant.helpers.event import (async_track_utc_time_change,
async_call_later)
from homeassistant.util import dt as dt_util

REQUIREMENTS = ['xmltodict==0.11.0']

_LOGGER = logging.getLogger(__name__)

CONF_ATTRIBUTION = "Weather forecast from yr.no, delivered by the Norwegian " \
"Meteorological Institute and the NRK."
CONF_ATTRIBUTION = "Weather forecast from met.no, delivered " \
"by the Norwegian Meteorological Institute."
# https://api.met.no/license_data.html

SENSOR_TYPES = {
'symbol': ['Symbol', None],
Expand Down Expand Up @@ -91,11 +91,10 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
async_add_devices(dev)

weather = YrData(hass, coordinates, forecast, dev)
# Update weather on the hour, spread seconds
async_track_utc_time_change(
hass, weather.async_update, minute=randrange(1, 10),
second=randrange(0, 59))
yield from weather.async_update()
async_track_utc_time_change(hass, weather.updating_devices, minute=31)
# wait 1 minute after start up before we fetch data,
# to avoid several restarts to fetch new data.
async_call_later(hass, 60, weather.fetching_data)

@Danielhiversen Danielhiversen Feb 16, 2018

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want this?
This show the weather info as unknown for the first minute.
It will reduce the load on their servers, but the user experience will be lower?

@Danielhiversen Danielhiversen Feb 16, 2018

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also makes the tests fail

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also just call it directly. The impact on their servers should be minimal as this is a one time call

@dgomes dgomes Feb 16, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about using the last value in recorder ? (like input_*)

If someone is testing/configuring and keeps restarting, he will keep hammering the endpoint



class YrSensor(Entity):
Expand Down Expand Up @@ -153,50 +152,49 @@ def __init__(self, hass, coordinates, forecast, devices):
self._url = 'https://aa015h6buqvih86i1.api.met.no/'\
'weatherapi/locationforecast/1.9/'
self._urlparams = coordinates
self._nextrun = None
self._forecast = forecast
self.devices = devices
self.data = {}
self.hass = hass

@asyncio.coroutine
def async_update(self, *_):
def fetching_data(self, *_):
"""Get the latest data from yr.no."""
import xmltodict

def try_again(err: str):
"""Retry in 15 minutes."""
_LOGGER.warning("Retrying in 15 minutes: %s", err)
self._nextrun = None
nxt = dt_util.utcnow() + timedelta(minutes=15)
if nxt.minute >= 15:
async_track_point_in_utc_time(self.hass, self.async_update,
nxt)

if self._nextrun is None or dt_util.utcnow() >= self._nextrun:
try:
websession = async_get_clientsession(self.hass)
with async_timeout.timeout(10, loop=self.hass.loop):
resp = yield from websession.get(
self._url, params=self._urlparams)
if resp.status != 200:
try_again('{} returned {}'.format(resp.url, resp.status))
return
text = yield from resp.text()

except (asyncio.TimeoutError, aiohttp.ClientError) as err:
try_again(err)
"""Retry in 15 to 20 minutes."""
minutes = 15 + randrange(6)
_LOGGER.error("Retrying in %i minutes: %s", minutes, err)
async_call_later(self.hass, minutes*60, self.fetching_data)
try:
websession = async_get_clientsession(self.hass)
with async_timeout.timeout(10, loop=self.hass.loop):
resp = yield from websession.get(
self._url, params=self._urlparams)
if resp.status != 200:
try_again('{} returned {}'.format(resp.url, resp.status))
return
text = yield from resp.text()

try:
self.data = xmltodict.parse(text)['weatherdata']
model = self.data['meta']['model']
if '@nextrun' not in model:
model = model[0]
self._nextrun = dt_util.parse_datetime(model['@nextrun'])
except (ExpatError, IndexError) as err:
try_again(err)
return
except (asyncio.TimeoutError, aiohttp.ClientError) as err:
try_again(err)
return

try:
self.data = xmltodict.parse(text)['weatherdata']
except (ExpatError, IndexError) as err:
try_again(err)
return

yield from self.updating_devices()
async_call_later(self.hass, 60*60, self.fetching_data)

@asyncio.coroutine
def updating_devices(self, *_):
"""Find the current data from self.data."""
if not self.data:
return

now = dt_util.utcnow()
forecast_time = now + dt_util.dt.timedelta(hours=self._forecast)
Expand Down