mirror of
https://github.com/home-assistant/core.git
synced 2025-07-15 01:07:10 +00:00
Added the ability to Weather Underground to track severe weather alerts (#3505)
* Added the ability to Weather Underground to track severe weather alerts * * Added message on the advisory attr * Updated tests * * Making use of guard clause * Checking multiple_alerts prior loop * Using a better way to create dict * Fixed issue to set to None only the object that failed * Added unittest * Split update() method to different calls with their one throttle control to minimize API calls * Updated unittest and make sure the alert sensor will not return 'unknown' status' * Removed update() method from state property * Branch rebased and include Weather Underground attribution * Update wunderground.py
This commit is contained in:
parent
1bf5554017
commit
6fcb1b548e
@ -19,6 +19,7 @@ from homeassistant.util import Throttle
|
|||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
_RESOURCE = 'http://api.wunderground.com/api/{}/conditions/q/'
|
_RESOURCE = 'http://api.wunderground.com/api/{}/conditions/q/'
|
||||||
|
_ALERTS = 'http://api.wunderground.com/api/{}/alerts/q/'
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
CONF_ATTRIBUTION = "Data provided by the WUnderground weather service"
|
CONF_ATTRIBUTION = "Data provided by the WUnderground weather service"
|
||||||
@ -28,6 +29,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300)
|
|||||||
|
|
||||||
# Sensor types are defined like: Name, units
|
# Sensor types are defined like: Name, units
|
||||||
SENSOR_TYPES = {
|
SENSOR_TYPES = {
|
||||||
|
'alerts': ['Alerts', None],
|
||||||
'weather': ['Weather Summary', None],
|
'weather': ['Weather Summary', None],
|
||||||
'station_id': ['Station ID', None],
|
'station_id': ['Station ID', None],
|
||||||
'feelslike_c': ['Feels Like (°C)', TEMP_CELSIUS],
|
'feelslike_c': ['Feels Like (°C)', TEMP_CELSIUS],
|
||||||
@ -57,6 +59,14 @@ SENSOR_TYPES = {
|
|||||||
'solarradiation': ['Solar Radiation', None]
|
'solarradiation': ['Solar Radiation', None]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Alert Attributes
|
||||||
|
ALERTS_ATTRS = [
|
||||||
|
'date',
|
||||||
|
'description',
|
||||||
|
'expires',
|
||||||
|
'message',
|
||||||
|
]
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
vol.Required(CONF_API_KEY): cv.string,
|
vol.Required(CONF_API_KEY): cv.string,
|
||||||
vol.Optional(CONF_PWS_ID): cv.string,
|
vol.Optional(CONF_PWS_ID): cv.string,
|
||||||
@ -106,15 +116,31 @@ class WUndergroundSensor(Entity):
|
|||||||
return int(self.rest.data[self._condition][:-1])
|
return int(self.rest.data[self._condition][:-1])
|
||||||
else:
|
else:
|
||||||
return self.rest.data[self._condition]
|
return self.rest.data[self._condition]
|
||||||
else:
|
|
||||||
|
if self.rest.alerts and self._condition == 'alerts':
|
||||||
|
return len(self.rest.alerts)
|
||||||
return STATE_UNKNOWN
|
return STATE_UNKNOWN
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
"""Return the state attributes."""
|
"""Return the state attributes."""
|
||||||
return {
|
attrs = {}
|
||||||
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
|
|
||||||
}
|
attrs[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION
|
||||||
|
|
||||||
|
if not self.rest.alerts or self._condition != 'alerts':
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
multiple_alerts = len(self.rest.alerts) > 1
|
||||||
|
for data in self.rest.alerts:
|
||||||
|
for alert in ALERTS_ATTRS:
|
||||||
|
if data[alert]:
|
||||||
|
if multiple_alerts:
|
||||||
|
dkey = alert.capitalize() + '_' + data['type']
|
||||||
|
else:
|
||||||
|
dkey = alert.capitalize()
|
||||||
|
attrs[dkey] = data[alert]
|
||||||
|
return attrs
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def entity_picture(self):
|
def entity_picture(self):
|
||||||
@ -129,6 +155,9 @@ class WUndergroundSensor(Entity):
|
|||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Update current conditions."""
|
"""Update current conditions."""
|
||||||
|
if self._condition == 'alerts':
|
||||||
|
self.rest.update_alerts()
|
||||||
|
else:
|
||||||
self.rest.update()
|
self.rest.update()
|
||||||
|
|
||||||
|
|
||||||
@ -144,9 +173,10 @@ class WUndergroundData(object):
|
|||||||
self._latitude = hass.config.latitude
|
self._latitude = hass.config.latitude
|
||||||
self._longitude = hass.config.longitude
|
self._longitude = hass.config.longitude
|
||||||
self.data = None
|
self.data = None
|
||||||
|
self.alerts = None
|
||||||
|
|
||||||
def _build_url(self):
|
def _build_url(self, baseurl=_RESOURCE):
|
||||||
url = _RESOURCE.format(self._api_key)
|
url = baseurl.format(self._api_key)
|
||||||
if self._pws_id:
|
if self._pws_id:
|
||||||
url = url + 'pws:{}'.format(self._pws_id)
|
url = url + 'pws:{}'.format(self._pws_id)
|
||||||
else:
|
else:
|
||||||
@ -168,3 +198,18 @@ class WUndergroundData(object):
|
|||||||
_LOGGER.error("Check WUnderground API %s", err.args)
|
_LOGGER.error("Check WUnderground API %s", err.args)
|
||||||
self.data = None
|
self.data = None
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||||
|
def update_alerts(self):
|
||||||
|
"""Get the latest alerts data from WUnderground."""
|
||||||
|
try:
|
||||||
|
result = requests.get(self._build_url(_ALERTS), timeout=10).json()
|
||||||
|
if "error" in result['response']:
|
||||||
|
raise ValueError(result['response']["error"]
|
||||||
|
["description"])
|
||||||
|
else:
|
||||||
|
self.alerts = result["alerts"]
|
||||||
|
except ValueError as err:
|
||||||
|
_LOGGER.error("Check WUnderground API %s", err.args)
|
||||||
|
self.alerts = None
|
||||||
|
raise
|
||||||
|
@ -11,7 +11,7 @@ VALID_CONFIG_PWS = {
|
|||||||
'api_key': 'foo',
|
'api_key': 'foo',
|
||||||
'pws_id': 'bar',
|
'pws_id': 'bar',
|
||||||
'monitored_conditions': [
|
'monitored_conditions': [
|
||||||
'weather', 'feelslike_c'
|
'weather', 'feelslike_c', 'alerts'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,17 +19,19 @@ VALID_CONFIG = {
|
|||||||
'platform': 'wunderground',
|
'platform': 'wunderground',
|
||||||
'api_key': 'foo',
|
'api_key': 'foo',
|
||||||
'monitored_conditions': [
|
'monitored_conditions': [
|
||||||
'weather', 'feelslike_c'
|
'weather', 'feelslike_c', 'alerts'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
FEELS_LIKE = '40'
|
FEELS_LIKE = '40'
|
||||||
WEATHER = 'Clear'
|
WEATHER = 'Clear'
|
||||||
ICON_URL = 'http://icons.wxug.com/i/c/k/clear.gif'
|
ICON_URL = 'http://icons.wxug.com/i/c/k/clear.gif'
|
||||||
|
ALERT_MESSAGE = 'This is a test alert message'
|
||||||
|
|
||||||
|
|
||||||
def mocked_requests_get(*args, **kwargs):
|
def mocked_requests_get(*args, **kwargs):
|
||||||
"""Mock requests.get invocations."""
|
"""Mock requests.get invocations."""
|
||||||
|
# pylint: disable=too-few-public-methods
|
||||||
class MockResponse:
|
class MockResponse:
|
||||||
"""Class to represent a mocked response."""
|
"""Class to represent a mocked response."""
|
||||||
|
|
||||||
@ -61,7 +63,16 @@ def mocked_requests_get(*args, **kwargs):
|
|||||||
"feelslike_c": FEELS_LIKE,
|
"feelslike_c": FEELS_LIKE,
|
||||||
"weather": WEATHER,
|
"weather": WEATHER,
|
||||||
"icon_url": ICON_URL
|
"icon_url": ICON_URL
|
||||||
}
|
}, "alerts": [
|
||||||
|
{
|
||||||
|
"type": 'FLO',
|
||||||
|
"description": "Areal Flood Warning",
|
||||||
|
"date": "9:36 PM CDT on September 22, 2016",
|
||||||
|
"expires": "10:00 AM CDT on September 23, 2016",
|
||||||
|
"message": ALERT_MESSAGE,
|
||||||
|
},
|
||||||
|
|
||||||
|
],
|
||||||
}, 200)
|
}, 200)
|
||||||
else:
|
else:
|
||||||
return MockResponse({
|
return MockResponse({
|
||||||
@ -81,6 +92,7 @@ def mocked_requests_get(*args, **kwargs):
|
|||||||
class TestWundergroundSetup(unittest.TestCase):
|
class TestWundergroundSetup(unittest.TestCase):
|
||||||
"""Test the WUnderground platform."""
|
"""Test the WUnderground platform."""
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
DEVICES = []
|
DEVICES = []
|
||||||
|
|
||||||
def add_devices(self, devices):
|
def add_devices(self, devices):
|
||||||
@ -107,14 +119,13 @@ class TestWundergroundSetup(unittest.TestCase):
|
|||||||
self.add_devices, None))
|
self.add_devices, None))
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
wunderground.setup_platform(self.hass, VALID_CONFIG,
|
wunderground.setup_platform(self.hass, VALID_CONFIG,
|
||||||
self.add_devices,
|
self.add_devices, None))
|
||||||
None))
|
|
||||||
invalid_config = {
|
invalid_config = {
|
||||||
'platform': 'wunderground',
|
'platform': 'wunderground',
|
||||||
'api_key': 'BOB',
|
'api_key': 'BOB',
|
||||||
'pws_id': 'bar',
|
'pws_id': 'bar',
|
||||||
'monitored_conditions': [
|
'monitored_conditions': [
|
||||||
'weather', 'feelslike_c'
|
'weather', 'feelslike_c', 'alerts'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,11 +139,17 @@ class TestWundergroundSetup(unittest.TestCase):
|
|||||||
wunderground.setup_platform(self.hass, VALID_CONFIG, self.add_devices,
|
wunderground.setup_platform(self.hass, VALID_CONFIG, self.add_devices,
|
||||||
None)
|
None)
|
||||||
for device in self.DEVICES:
|
for device in self.DEVICES:
|
||||||
|
device.update()
|
||||||
self.assertTrue(str(device.name).startswith('PWS_'))
|
self.assertTrue(str(device.name).startswith('PWS_'))
|
||||||
if device.name == 'PWS_weather':
|
if device.name == 'PWS_weather':
|
||||||
self.assertEqual(ICON_URL, device.entity_picture)
|
self.assertEqual(ICON_URL, device.entity_picture)
|
||||||
self.assertEqual(WEATHER, device.state)
|
self.assertEqual(WEATHER, device.state)
|
||||||
self.assertIsNone(device.unit_of_measurement)
|
self.assertIsNone(device.unit_of_measurement)
|
||||||
|
elif device.name == 'PWS_alerts':
|
||||||
|
self.assertEqual(1, device.state)
|
||||||
|
self.assertEqual(ALERT_MESSAGE,
|
||||||
|
device.device_state_attributes['Message'])
|
||||||
|
self.assertIsNone(device.entity_picture)
|
||||||
else:
|
else:
|
||||||
self.assertIsNone(device.entity_picture)
|
self.assertIsNone(device.entity_picture)
|
||||||
self.assertEqual(FEELS_LIKE, device.state)
|
self.assertEqual(FEELS_LIKE, device.state)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user