Merge pull request #2861 from arsaboo/patch-2
Wunderground weather sensor
This commit is contained in:
commit
c1653d2fca
2 changed files with 297 additions and 0 deletions
159
homeassistant/components/sensor/wunderground.py
Normal file
159
homeassistant/components/sensor/wunderground.py
Normal file
|
@ -0,0 +1,159 @@
|
|||
"""Support for Wunderground weather service."""
|
||||
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_PLATFORM, CONF_MONITORED_CONDITIONS,
|
||||
CONF_API_KEY, TEMP_FAHRENHEIT, TEMP_CELSIUS,
|
||||
STATE_UNKNOWN)
|
||||
|
||||
CONF_PWS_ID = 'pws_id'
|
||||
_RESOURCE = 'http://api.wunderground.com/api/{}/conditions/q/'
|
||||
_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': ['Precipitation Today', 'mm'],
|
||||
'precip_today_string': ['Precipitation today', None],
|
||||
'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=[]): vol.All(ensure_list, [vol.In(SENSOR_TYPES)]),
|
||||
})
|
||||
|
||||
|
||||
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))
|
||||
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)
|
||||
|
||||
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):
|
||||
"""Implementing the Wunderground sensor."""
|
||||
|
||||
def __init__(self, rest, condition):
|
||||
"""Initialize the sensor."""
|
||||
self.rest = rest
|
||||
self._condition = condition
|
||||
self._unit_of_measurement = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return "PWS_" + self._condition
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the sensor."""
|
||||
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):
|
||||
"""Return the entity picture."""
|
||||
if self._condition == 'weather':
|
||||
return self.rest.data['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()
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
|
||||
|
||||
class WUndergroundData(object):
|
||||
"""Get data from Wundeground."""
|
||||
|
||||
def __init__(self, hass, api_key, pws_id=None):
|
||||
"""Initialize the data object."""
|
||||
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):
|
||||
url = _RESOURCE.format(self._api_key)
|
||||
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."""
|
||||
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["current_observation"]
|
||||
except ValueError as err:
|
||||
_LOGGER.error("Check Wunderground API %s", err.args)
|
||||
self.data = None
|
||||
raise
|
138
tests/components/sensor/test_wunderground.py
Normal file
138
tests/components/sensor/test_wunderground.py
Normal file
|
@ -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)
|
Loading…
Add table
Reference in a new issue