Add 'forecast' ability to yr weather sensor (#8650)
* Add forecast option to YR sensor * Fix some style issues * Fix linting
This commit is contained in:
parent
86c06ad76e
commit
47dad547eb
2 changed files with 100 additions and 35 deletions
|
@ -50,12 +50,15 @@ SENSOR_TYPES = {
|
|||
'dewpointTemperature': ['Dewpoint temperature', '°C'],
|
||||
}
|
||||
|
||||
CONF_FORECAST = 'forecast'
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_MONITORED_CONDITIONS, default=['symbol']): vol.All(
|
||||
cv.ensure_list, vol.Length(min=1), [vol.In(SENSOR_TYPES.keys())]),
|
||||
vol.Optional(CONF_LATITUDE): cv.latitude,
|
||||
vol.Optional(CONF_LONGITUDE): cv.longitude,
|
||||
vol.Optional(CONF_ELEVATION): vol.Coerce(int),
|
||||
vol.Optional(CONF_FORECAST): vol.Coerce(int)
|
||||
})
|
||||
|
||||
|
||||
|
@ -65,6 +68,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
|||
latitude = config.get(CONF_LATITUDE, hass.config.latitude)
|
||||
longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
|
||||
elevation = config.get(CONF_ELEVATION, hass.config.elevation or 0)
|
||||
forecast = config.get(CONF_FORECAST, 0)
|
||||
|
||||
if None in (latitude, longitude):
|
||||
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
|
||||
|
@ -79,7 +83,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
|||
dev.append(YrSensor(sensor_type))
|
||||
async_add_devices(dev)
|
||||
|
||||
weather = YrData(hass, coordinates, 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),
|
||||
|
@ -137,12 +141,13 @@ class YrSensor(Entity):
|
|||
class YrData(object):
|
||||
"""Get the latest data and updates the states."""
|
||||
|
||||
def __init__(self, hass, coordinates, devices):
|
||||
def __init__(self, hass, coordinates, forecast, devices):
|
||||
"""Initialize the data object."""
|
||||
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
|
||||
|
@ -187,40 +192,58 @@ class YrData(object):
|
|||
return
|
||||
|
||||
now = dt_util.utcnow()
|
||||
forecast_time = now + dt_util.dt.timedelta(hours=self._forecast)
|
||||
|
||||
# Find the correct time entry. Since not all time entries contain all
|
||||
# types of data, we cannot just select one. Instead, we order them by
|
||||
# distance from the desired forecast_time, and for every device iterate
|
||||
# them in order of increasing distance, taking the first time_point
|
||||
# that contains the desired data.
|
||||
|
||||
ordered_entries = []
|
||||
|
||||
tasks = []
|
||||
# Update all devices
|
||||
for dev in self.devices:
|
||||
# Find sensor
|
||||
for time_entry in self.data['product']['time']:
|
||||
valid_from = dt_util.parse_datetime(time_entry['@from'])
|
||||
valid_to = dt_util.parse_datetime(time_entry['@to'])
|
||||
new_state = None
|
||||
|
||||
loc_data = time_entry['location']
|
||||
|
||||
if dev.type not in loc_data or now >= valid_to:
|
||||
if now >= valid_to:
|
||||
# Has already passed. Never select this.
|
||||
continue
|
||||
|
||||
if dev.type == 'precipitation' and valid_from < now:
|
||||
average_dist = (abs((valid_to - forecast_time).total_seconds()) +
|
||||
abs((valid_from - forecast_time).total_seconds()))
|
||||
|
||||
ordered_entries.append((average_dist, time_entry))
|
||||
|
||||
ordered_entries.sort(key=lambda item: item[0])
|
||||
|
||||
# Update all devices
|
||||
tasks = []
|
||||
if len(ordered_entries) > 0:
|
||||
for dev in self.devices:
|
||||
new_state = None
|
||||
|
||||
for (_, selected_time_entry) in ordered_entries:
|
||||
loc_data = selected_time_entry['location']
|
||||
|
||||
if dev.type not in loc_data:
|
||||
continue
|
||||
|
||||
if dev.type == 'precipitation':
|
||||
new_state = loc_data[dev.type]['@value']
|
||||
break
|
||||
elif dev.type == 'symbol' and valid_from < now:
|
||||
elif dev.type == 'symbol':
|
||||
new_state = loc_data[dev.type]['@number']
|
||||
break
|
||||
elif dev.type in ('temperature', 'pressure', 'humidity',
|
||||
'dewpointTemperature'):
|
||||
new_state = loc_data[dev.type]['@value']
|
||||
break
|
||||
elif dev.type in ('windSpeed', 'windGust'):
|
||||
new_state = loc_data[dev.type]['@mps']
|
||||
break
|
||||
elif dev.type == 'windDirection':
|
||||
new_state = float(loc_data[dev.type]['@deg'])
|
||||
break
|
||||
elif dev.type in ('fog', 'cloudiness', 'lowClouds',
|
||||
'mediumClouds', 'highClouds'):
|
||||
new_state = loc_data[dev.type]['@percent']
|
||||
|
||||
break
|
||||
|
||||
# pylint: disable=protected-access
|
||||
|
@ -228,5 +251,5 @@ class YrData(object):
|
|||
dev._state = new_state
|
||||
tasks.append(dev.async_update_ha_state())
|
||||
|
||||
if tasks:
|
||||
if len(tasks) > 0:
|
||||
yield from asyncio.wait(tasks, loop=self.hass.loop)
|
||||
|
|
|
@ -69,3 +69,45 @@ def test_custom_setup(hass, aioclient_mock):
|
|||
state = hass.states.get('sensor.yr_wind_speed')
|
||||
assert state.attributes.get('unit_of_measurement') == 'm/s'
|
||||
assert state.state == '3.5'
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_forecast_setup(hass, aioclient_mock):
|
||||
"""Test a custom setup with 24h forecast."""
|
||||
aioclient_mock.get('https://aa015h6buqvih86i1.api.met.no/'
|
||||
'weatherapi/locationforecast/1.9/',
|
||||
text=load_fixture('yr.no.json'))
|
||||
|
||||
config = {'platform': 'yr',
|
||||
'elevation': 0,
|
||||
'forecast': 24,
|
||||
'monitored_conditions': [
|
||||
'pressure',
|
||||
'windDirection',
|
||||
'humidity',
|
||||
'fog',
|
||||
'windSpeed']}
|
||||
hass.allow_pool = True
|
||||
with patch('homeassistant.components.sensor.yr.dt_util.utcnow',
|
||||
return_value=NOW), assert_setup_component(1):
|
||||
yield from async_setup_component(hass, 'sensor', {'sensor': config})
|
||||
|
||||
state = hass.states.get('sensor.yr_pressure')
|
||||
assert state.attributes.get('unit_of_measurement') == 'hPa'
|
||||
assert state.state == '1008.3'
|
||||
|
||||
state = hass.states.get('sensor.yr_wind_direction')
|
||||
assert state.attributes.get('unit_of_measurement') == '°'
|
||||
assert state.state == '148.9'
|
||||
|
||||
state = hass.states.get('sensor.yr_humidity')
|
||||
assert state.attributes.get('unit_of_measurement') == '%'
|
||||
assert state.state == '77.4'
|
||||
|
||||
state = hass.states.get('sensor.yr_fog')
|
||||
assert state.attributes.get('unit_of_measurement') == '%'
|
||||
assert state.state == '0.0'
|
||||
|
||||
state = hass.states.get('sensor.yr_wind_speed')
|
||||
assert state.attributes.get('unit_of_measurement') == 'm/s'
|
||||
assert state.state == '3.6'
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue