mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Clean up YR sensor
This commit is contained in:
parent
9e1ecd7124
commit
ab5a3f9de3
@ -36,17 +36,17 @@ sensor:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
import datetime
|
|
||||||
import urllib.request
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from homeassistant.const import ATTR_ENTITY_PICTURE
|
from homeassistant.const import ATTR_ENTITY_PICTURE
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.util import location, dt as dt_util
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
REQUIREMENTS = ['xmltodict', 'astral==0.8.1']
|
REQUIREMENTS = ['xmltodict']
|
||||||
|
|
||||||
# Sensor types are defined like so:
|
# Sensor types are defined like so:
|
||||||
SENSOR_TYPES = {
|
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")
|
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
from astral import Location, GoogleGeocoder
|
elevation = config.get('elevation')
|
||||||
location = Location(('', '', hass.config.latitude, hass.config.longitude,
|
|
||||||
hass.config.time_zone, 0))
|
|
||||||
|
|
||||||
google = GoogleGeocoder()
|
if elevation is None:
|
||||||
try:
|
elevation = location.elevation(hass.config.latitude,
|
||||||
google._get_elevation(location) # pylint: disable=protected-access
|
hass.config.longitude)
|
||||||
_LOGGER.info(
|
|
||||||
'Retrieved elevation from Google: %s', location.elevation)
|
|
||||||
elevation = location.elevation
|
|
||||||
except urllib.error.URLError:
|
|
||||||
# If no internet connection available etc.
|
|
||||||
elevation = 0
|
|
||||||
|
|
||||||
coordinates = dict(lat=hass.config.latitude,
|
coordinates = dict(lat=hass.config.latitude,
|
||||||
lon=hass.config.longitude, msl=elevation)
|
lon=hass.config.longitude, msl=elevation)
|
||||||
@ -116,9 +108,8 @@ class YrSensor(Entity):
|
|||||||
self.type = sensor_type
|
self.type = sensor_type
|
||||||
self._state = None
|
self._state = None
|
||||||
self._weather = weather
|
self._weather = weather
|
||||||
self._info = ''
|
|
||||||
self._unit_of_measurement = SENSOR_TYPES[self.type][1]
|
self._unit_of_measurement = SENSOR_TYPES[self.type][1]
|
||||||
self._update = datetime.datetime.fromtimestamp(0)
|
self._update = None
|
||||||
|
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
@ -134,14 +125,15 @@ class YrSensor(Entity):
|
|||||||
@property
|
@property
|
||||||
def state_attributes(self):
|
def state_attributes(self):
|
||||||
""" Returns state attributes. """
|
""" Returns state attributes. """
|
||||||
data = {}
|
data = {
|
||||||
data[''] = "Weather forecast from yr.no, delivered by the"\
|
'about': "Weather forecast from yr.no, delivered by the"
|
||||||
" Norwegian Meteorological Institute and the NRK"
|
" Norwegian Meteorological Institute and the NRK"
|
||||||
|
}
|
||||||
if self.type == 'symbol':
|
if self.type == 'symbol':
|
||||||
symbol_nr = self._state
|
symbol_nr = self._state
|
||||||
data[ATTR_ENTITY_PICTURE] = "http://api.met.no/weatherapi/weathericon/1.1/" \
|
data[ATTR_ENTITY_PICTURE] = \
|
||||||
"?symbol=" + str(symbol_nr) + \
|
"http://api.met.no/weatherapi/weathericon/1.1/" \
|
||||||
";content_type=image/png"
|
"?symbol={0};content_type=image/png".format(symbol_nr)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@ -150,76 +142,50 @@ class YrSensor(Entity):
|
|||||||
""" Unit of measurement of this entity, if any. """
|
""" Unit of measurement of this entity, if any. """
|
||||||
return self._unit_of_measurement
|
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):
|
def update(self):
|
||||||
""" Gets the latest data from yr.no and updates the states. """
|
""" Gets the latest data from yr.no and updates the states. """
|
||||||
|
|
||||||
self._weather.update()
|
now = dt_util.utcnow()
|
||||||
now = datetime.datetime.now()
|
|
||||||
# check if data should be updated
|
# check if data should be updated
|
||||||
if now <= self._update:
|
if self._update is not None and now <= self._update:
|
||||||
return
|
return
|
||||||
|
|
||||||
time_data = self._weather.data['product']['time']
|
self._weather.update()
|
||||||
|
|
||||||
# pylint: disable=consider-using-enumerate
|
|
||||||
# find sensor
|
# find sensor
|
||||||
for k in range(len(time_data)):
|
for time_entry in self._weather.data['product']['time']:
|
||||||
valid_from = datetime.datetime.strptime(time_data[k]['@from'],
|
valid_from = dt_util.str_to_datetime(
|
||||||
"%Y-%m-%dT%H:%M:%SZ")
|
time_entry['@from'], "%Y-%m-%dT%H:%M:%SZ")
|
||||||
valid_to = datetime.datetime.strptime(time_data[k]['@to'],
|
valid_to = dt_util.str_to_datetime(
|
||||||
"%Y-%m-%dT%H:%M:%SZ")
|
time_entry['@to'], "%Y-%m-%dT%H:%M:%SZ")
|
||||||
self._update = valid_to
|
|
||||||
self._info = "Forecast between " + time_data[k]['@from'] \
|
|
||||||
+ " and " + time_data[k]['@to'] + ". "
|
|
||||||
|
|
||||||
temp_data = time_data[k]['location']
|
loc_data = time_entry['location']
|
||||||
if self.type not in temp_data and now >= valid_to:
|
|
||||||
|
if self.type not in loc_data or now >= valid_to:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
self._update = valid_to
|
||||||
|
|
||||||
if self.type == 'precipitation' and valid_from < now:
|
if self.type == 'precipitation' and valid_from < now:
|
||||||
self._state = temp_data[self.type]['@value']
|
self._state = loc_data[self.type]['@value']
|
||||||
return
|
break
|
||||||
elif self.type == 'symbol' and valid_from < now:
|
elif self.type == 'symbol' and valid_from < now:
|
||||||
self._state = temp_data[self.type]['@number']
|
self._state = loc_data[self.type]['@number']
|
||||||
return
|
break
|
||||||
elif self.type == 'temperature':
|
elif self.type == ('temperature', 'pressure', 'humidity',
|
||||||
self._state = temp_data[self.type]['@value']
|
'dewpointTemperature'):
|
||||||
return
|
self._state = loc_data[self.type]['@value']
|
||||||
|
break
|
||||||
elif self.type == 'windSpeed':
|
elif self.type == 'windSpeed':
|
||||||
self._state = temp_data[self.type]['@mps']
|
self._state = loc_data[self.type]['@mps']
|
||||||
return
|
break
|
||||||
elif self.type == 'pressure':
|
|
||||||
self._state = temp_data[self.type]['@value']
|
|
||||||
return
|
|
||||||
elif self.type == 'windDirection':
|
elif self.type == 'windDirection':
|
||||||
self._state = float(temp_data[self.type]['@deg'])
|
self._state = float(loc_data[self.type]['@deg'])
|
||||||
return
|
break
|
||||||
elif self.type == 'humidity':
|
elif self.type in ('fog', 'cloudiness', 'lowClouds',
|
||||||
self._state = temp_data[self.type]['@value']
|
'mediumClouds', 'highClouds'):
|
||||||
return
|
self._state = loc_data[self.type]['@percent']
|
||||||
elif self.type == 'fog':
|
break
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
@ -230,14 +196,14 @@ class YrData(object):
|
|||||||
self._url = 'http://api.yr.no/weatherapi/locationforecast/1.9/?' \
|
self._url = 'http://api.yr.no/weatherapi/locationforecast/1.9/?' \
|
||||||
'lat={lat};lon={lon};msl={msl}'.format(**coordinates)
|
'lat={lat};lon={lon};msl={msl}'.format(**coordinates)
|
||||||
|
|
||||||
self._nextrun = datetime.datetime.fromtimestamp(0)
|
self._nextrun = None
|
||||||
|
self.data = {}
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
""" Gets the latest data from yr.no """
|
""" Gets the latest data from yr.no """
|
||||||
now = datetime.datetime.now()
|
|
||||||
# check if new will be available
|
# check if new will be available
|
||||||
if now <= self._nextrun:
|
if self._nextrun is not None and dt_util.utcnow() <= self._nextrun:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
response = requests.get(self._url)
|
response = requests.get(self._url)
|
||||||
@ -252,5 +218,5 @@ class YrData(object):
|
|||||||
model = self.data['meta']['model']
|
model = self.data['meta']['model']
|
||||||
if '@nextrun' not in model:
|
if '@nextrun' not in model:
|
||||||
model = model[0]
|
model = model[0]
|
||||||
self._nextrun = datetime.datetime.strptime(model['@nextrun'],
|
self._nextrun = dt_util.str_to_datetime(model['@nextrun'],
|
||||||
"%Y-%m-%dT%H:%M:%SZ")
|
"%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
@ -108,14 +108,14 @@ def datetime_to_date_str(dattim):
|
|||||||
return dattim.strftime(DATE_STR_FORMAT)
|
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.
|
""" Converts a string to a UTC datetime object.
|
||||||
|
|
||||||
@rtype: datetime
|
@rtype: datetime
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return dt.datetime.strptime(
|
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
|
except ValueError: # If dt_str did not match our format
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -4,6 +4,8 @@ import collections
|
|||||||
import requests
|
import requests
|
||||||
from vincenty import vincenty
|
from vincenty import vincenty
|
||||||
|
|
||||||
|
ELEVATION_URL = 'http://maps.googleapis.com/maps/api/elevation/json'
|
||||||
|
|
||||||
|
|
||||||
LocationInfo = collections.namedtuple(
|
LocationInfo = collections.namedtuple(
|
||||||
"LocationInfo",
|
"LocationInfo",
|
||||||
@ -34,3 +36,20 @@ def detect_location_info():
|
|||||||
def distance(lat1, lon1, lat2, lon2):
|
def distance(lat1, lon1, lat2, lon2):
|
||||||
""" Calculate the distance in meters between two points. """
|
""" Calculate the distance in meters between two points. """
|
||||||
return vincenty((lat1, lon1), (lat2, lon2)) * 1000
|
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
|
||||||
|
@ -15,12 +15,8 @@ class TestSensorYr(unittest.TestCase):
|
|||||||
|
|
||||||
def setUp(self): # pylint: disable=invalid-name
|
def setUp(self): # pylint: disable=invalid-name
|
||||||
self.hass = ha.HomeAssistant()
|
self.hass = ha.HomeAssistant()
|
||||||
latitude = 32.87336
|
self.hass.config.latitude = 32.87336
|
||||||
longitude = 117.22743
|
self.hass.config.longitude = 117.22743
|
||||||
|
|
||||||
# Compare it with the real data
|
|
||||||
self.hass.config.latitude = latitude
|
|
||||||
self.hass.config.longitude = longitude
|
|
||||||
|
|
||||||
def tearDown(self): # pylint: disable=invalid-name
|
def tearDown(self): # pylint: disable=invalid-name
|
||||||
""" Stop down stuff we started. """
|
""" Stop down stuff we started. """
|
||||||
@ -30,6 +26,7 @@ class TestSensorYr(unittest.TestCase):
|
|||||||
self.assertTrue(sensor.setup(self.hass, {
|
self.assertTrue(sensor.setup(self.hass, {
|
||||||
'sensor': {
|
'sensor': {
|
||||||
'platform': 'yr',
|
'platform': 'yr',
|
||||||
|
'elevation': 0,
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
state = self.hass.states.get('sensor.yr_symbol')
|
state = self.hass.states.get('sensor.yr_symbol')
|
||||||
@ -42,7 +39,14 @@ class TestSensorYr(unittest.TestCase):
|
|||||||
self.assertTrue(sensor.setup(self.hass, {
|
self.assertTrue(sensor.setup(self.hass, {
|
||||||
'sensor': {
|
'sensor': {
|
||||||
'platform': 'yr',
|
'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')
|
state = self.hass.states.get('sensor.yr_symbol')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user