core/homeassistant/components/wunderground/sensor.py

820 lines
35 KiB
Python
Raw Normal View History

"""Support for WUnderground weather service."""
import asyncio
from datetime import timedelta
import logging
import re
import aiohttp
import async_timeout
2016-08-18 04:18:37 +00:00
import voluptuous as vol
from homeassistant.helpers.typing import HomeAssistantType, ConfigType
from homeassistant.components import sensor
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_MONITORED_CONDITIONS, CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE,
TEMP_FAHRENHEIT, TEMP_CELSIUS, LENGTH_INCHES, LENGTH_KILOMETERS,
LENGTH_MILES, LENGTH_FEET, ATTR_ATTRIBUTION)
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
_RESOURCE = 'http://api.wunderground.com/api/{}/{}/{}/q/'
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Data provided by the WUnderground weather service"
CONF_PWS_ID = 'pws_id'
CONF_LANG = 'lang'
DEFAULT_LANG = 'EN'
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5)
# Helper classes for declaring sensor configurations
class WUSensorConfig:
"""WU Sensor Configuration.
defines basic HA properties of the weather sensor and
stores callbacks that can parse sensor values out of
the json data received by WU API.
"""
def __init__(self, friendly_name, feature, value,
unit_of_measurement=None, entity_picture=None,
icon="mdi:gauge", device_state_attributes=None,
device_class=None):
"""Constructor.
Args:
friendly_name (string|func): Friendly name
feature (string): WU feature. See:
https://www.wunderground.com/weather/api/d/docs?d=data/index
value (function(WUndergroundData)): callback that
extracts desired value from WUndergroundData object
unit_of_measurement (string): unit of measurement
entity_picture (string): value or callback returning
URL of entity picture
icon (string): icon name or URL
device_state_attributes (dict): dictionary of attributes,
or callable that returns it
"""
self.friendly_name = friendly_name
self.unit_of_measurement = unit_of_measurement
self.feature = feature
self.value = value
self.entity_picture = entity_picture
self.icon = icon
self.device_state_attributes = device_state_attributes or {}
self.device_class = device_class
class WUCurrentConditionsSensorConfig(WUSensorConfig):
"""Helper for defining sensor configurations for current conditions."""
def __init__(self, friendly_name, field, icon="mdi:gauge",
unit_of_measurement=None, device_class=None):
"""Constructor.
Args:
friendly_name (string|func): Friendly name of sensor
field (string): Field name in the "current_observation"
dictionary.
icon (string): icon name or URL, if None sensor
will use current weather symbol
unit_of_measurement (string): unit of measurement
"""
super().__init__(
friendly_name,
"conditions",
value=lambda wu: wu.data['current_observation'][field],
icon=icon,
unit_of_measurement=unit_of_measurement,
entity_picture=lambda wu: wu.data['current_observation'][
'icon_url'] if icon is None else None,
device_state_attributes={
'date': lambda wu: wu.data['current_observation'][
'observation_time']
},
device_class=device_class
)
class WUDailyTextForecastSensorConfig(WUSensorConfig):
"""Helper for defining sensor configurations for daily text forecasts."""
def __init__(self, period, field, unit_of_measurement=None):
"""Constructor.
Args:
period (int): forecast period number
field (string): field name to use as value
unit_of_measurement(string): unit of measurement
"""
super().__init__(
friendly_name=lambda wu: wu.data['forecast']['txt_forecast'][
'forecastday'][period]['title'],
feature='forecast',
value=lambda wu: wu.data['forecast']['txt_forecast'][
'forecastday'][period][field],
entity_picture=lambda wu: wu.data['forecast']['txt_forecast'][
'forecastday'][period]['icon_url'],
unit_of_measurement=unit_of_measurement,
device_state_attributes={
'date': lambda wu: wu.data['forecast']['txt_forecast']['date']
}
)
class WUDailySimpleForecastSensorConfig(WUSensorConfig):
"""Helper for defining sensor configurations for daily simpleforecasts."""
def __init__(self, friendly_name, period, field, wu_unit=None,
ha_unit=None, icon=None, device_class=None):
"""Constructor.
Args:
period (int): forecast period number
field (string): field name to use as value
wu_unit (string): "fahrenheit", "celsius", "degrees" etc.
see the example json at:
https://www.wunderground.com/weather/api/d/docs?d=data/forecast&MR=1
ha_unit (string): corresponding unit in home assistant
title (string): friendly_name of the sensor
"""
super().__init__(
friendly_name=friendly_name,
feature='forecast',
value=(lambda wu: wu.data['forecast']['simpleforecast'][
'forecastday'][period][field][wu_unit])
if wu_unit else
(lambda wu: wu.data['forecast']['simpleforecast'][
'forecastday'][period][field]),
unit_of_measurement=ha_unit,
entity_picture=lambda wu: wu.data['forecast']['simpleforecast'][
'forecastday'][period]['icon_url'] if not icon else None,
icon=icon,
device_state_attributes={
'date': lambda wu: wu.data['forecast']['simpleforecast'][
'forecastday'][period]['date']['pretty']
},
device_class=device_class
)
class WUHourlyForecastSensorConfig(WUSensorConfig):
"""Helper for defining sensor configurations for hourly text forecasts."""
def __init__(self, period, field):
"""Constructor.
Args:
period (int): forecast period number
field (int): field name to use as value
"""
super().__init__(
friendly_name=lambda wu: "{} {}".format(
wu.data['hourly_forecast'][period]['FCTTIME'][
'weekday_name_abbrev'],
wu.data['hourly_forecast'][period]['FCTTIME'][
'civil']),
feature='hourly',
value=lambda wu: wu.data['hourly_forecast'][period][
field],
entity_picture=lambda wu: wu.data['hourly_forecast'][
period]["icon_url"],
device_state_attributes={
'temp_c': lambda wu: wu.data['hourly_forecast'][
period]['temp']['metric'],
'temp_f': lambda wu: wu.data['hourly_forecast'][
period]['temp']['english'],
'dewpoint_c': lambda wu: wu.data['hourly_forecast'][
period]['dewpoint']['metric'],
'dewpoint_f': lambda wu: wu.data['hourly_forecast'][
period]['dewpoint']['english'],
'precip_prop': lambda wu: wu.data['hourly_forecast'][
period]['pop'],
'sky': lambda wu: wu.data['hourly_forecast'][
period]['sky'],
'precip_mm': lambda wu: wu.data['hourly_forecast'][
period]['qpf']['metric'],
'precip_in': lambda wu: wu.data['hourly_forecast'][
period]['qpf']['english'],
'humidity': lambda wu: wu.data['hourly_forecast'][
period]['humidity'],
'wind_kph': lambda wu: wu.data['hourly_forecast'][
period]['wspd']['metric'],
'wind_mph': lambda wu: wu.data['hourly_forecast'][
period]['wspd']['english'],
'pressure_mb': lambda wu: wu.data['hourly_forecast'][
period]['mslp']['metric'],
'pressure_inHg': lambda wu: wu.data['hourly_forecast'][
period]['mslp']['english'],
'date': lambda wu: wu.data['hourly_forecast'][
period]['FCTTIME']['pretty'],
}
)
class WUAlmanacSensorConfig(WUSensorConfig):
"""Helper for defining field configurations for almanac sensors."""
def __init__(self, friendly_name, field, value_type, wu_unit,
unit_of_measurement, icon, device_class=None):
"""Constructor.
Args:
friendly_name (string|func): Friendly name
field (string): value name returned in 'almanac' dict
as returned by the WU API
value_type (string): "record" or "normal"
wu_unit (string): unit name in WU API
icon (string): icon name or URL
unit_of_measurement (string): unit of measurement
"""
super().__init__(
friendly_name=friendly_name,
feature="almanac",
value=lambda wu: wu.data['almanac'][field][value_type][wu_unit],
unit_of_measurement=unit_of_measurement,
icon=icon,
device_class="temperature"
)
class WUAlertsSensorConfig(WUSensorConfig):
"""Helper for defining field configuration for alerts."""
def __init__(self, friendly_name):
"""Constructor.
Args:
friendly_name (string|func): Friendly name
"""
super().__init__(
friendly_name=friendly_name,
feature="alerts",
value=lambda wu: len(wu.data['alerts']),
icon=lambda wu: "mdi:alert-circle-outline"
if wu.data['alerts'] else "mdi:check-circle-outline",
device_state_attributes=self._get_attributes
)
@staticmethod
def _get_attributes(rest):
attrs = {}
if 'alerts' not in rest.data:
return attrs
alerts = rest.data['alerts']
multiple_alerts = len(alerts) > 1
for data in 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
# Declaration of supported WU sensors
# (see above helper classes for argument explanation)
SENSOR_TYPES = {
'alerts': WUAlertsSensorConfig('Alerts'),
'dewpoint_c': WUCurrentConditionsSensorConfig(
'Dewpoint', 'dewpoint_c', 'mdi:water', TEMP_CELSIUS),
'dewpoint_f': WUCurrentConditionsSensorConfig(
'Dewpoint', 'dewpoint_f', 'mdi:water', TEMP_FAHRENHEIT),
'dewpoint_string': WUCurrentConditionsSensorConfig(
'Dewpoint Summary', 'dewpoint_string', 'mdi:water'),
'feelslike_c': WUCurrentConditionsSensorConfig(
'Feels Like', 'feelslike_c', 'mdi:thermometer', TEMP_CELSIUS),
'feelslike_f': WUCurrentConditionsSensorConfig(
'Feels Like', 'feelslike_f', 'mdi:thermometer', TEMP_FAHRENHEIT),
'feelslike_string': WUCurrentConditionsSensorConfig(
'Feels Like', 'feelslike_string', "mdi:thermometer"),
'heat_index_c': WUCurrentConditionsSensorConfig(
'Heat index', 'heat_index_c', "mdi:thermometer", TEMP_CELSIUS),
'heat_index_f': WUCurrentConditionsSensorConfig(
'Heat index', 'heat_index_f', "mdi:thermometer", TEMP_FAHRENHEIT),
'heat_index_string': WUCurrentConditionsSensorConfig(
'Heat Index Summary', 'heat_index_string', "mdi:thermometer"),
'elevation': WUSensorConfig(
'Elevation',
'conditions',
value=lambda wu: wu.data['current_observation'][
'observation_location']['elevation'].split()[0],
unit_of_measurement=LENGTH_FEET,
icon="mdi:elevation-rise"),
'location': WUSensorConfig(
'Location',
'conditions',
value=lambda wu: wu.data['current_observation'][
'display_location']['full'],
icon="mdi:map-marker"),
'observation_time': WUCurrentConditionsSensorConfig(
'Observation Time', 'observation_time', "mdi:clock"),
'precip_1hr_in': WUCurrentConditionsSensorConfig(
'Precipitation 1hr', 'precip_1hr_in', "mdi:umbrella", LENGTH_INCHES),
'precip_1hr_metric': WUCurrentConditionsSensorConfig(
'Precipitation 1hr', 'precip_1hr_metric', "mdi:umbrella", 'mm'),
'precip_1hr_string': WUCurrentConditionsSensorConfig(
'Precipitation 1hr', 'precip_1hr_string', "mdi:umbrella"),
'precip_today_in': WUCurrentConditionsSensorConfig(
'Precipitation Today', 'precip_today_in', "mdi:umbrella",
LENGTH_INCHES),
'precip_today_metric': WUCurrentConditionsSensorConfig(
'Precipitation Today', 'precip_today_metric', "mdi:umbrella", 'mm'),
'precip_today_string': WUCurrentConditionsSensorConfig(
'Precipitation Today', 'precip_today_string', "mdi:umbrella"),
'pressure_in': WUCurrentConditionsSensorConfig(
'Pressure', 'pressure_in', "mdi:gauge", 'inHg',
device_class="pressure"),
'pressure_mb': WUCurrentConditionsSensorConfig(
'Pressure', 'pressure_mb', "mdi:gauge", 'mb',
device_class="pressure"),
'pressure_trend': WUCurrentConditionsSensorConfig(
'Pressure Trend', 'pressure_trend', "mdi:gauge",
device_class="pressure"),
'relative_humidity': WUSensorConfig(
'Relative Humidity',
'conditions',
value=lambda wu: int(wu.data['current_observation'][
'relative_humidity'][:-1]),
unit_of_measurement='%',
icon="mdi:water-percent",
device_class="humidity"),
'station_id': WUCurrentConditionsSensorConfig(
'Station ID', 'station_id', "mdi:home"),
'solarradiation': WUCurrentConditionsSensorConfig(
'Solar Radiation', 'solarradiation', "mdi:weather-sunny", "w/m2"),
'temperature_string': WUCurrentConditionsSensorConfig(
'Temperature Summary', 'temperature_string', "mdi:thermometer"),
'temp_c': WUCurrentConditionsSensorConfig(
'Temperature', 'temp_c', "mdi:thermometer", TEMP_CELSIUS,
device_class="temperature"),
'temp_f': WUCurrentConditionsSensorConfig(
'Temperature', 'temp_f', "mdi:thermometer", TEMP_FAHRENHEIT,
device_class="temperature"),
'UV': WUCurrentConditionsSensorConfig(
'UV', 'UV', "mdi:sunglasses"),
'visibility_km': WUCurrentConditionsSensorConfig(
'Visibility (km)', 'visibility_km', "mdi:eye", LENGTH_KILOMETERS),
'visibility_mi': WUCurrentConditionsSensorConfig(
'Visibility (miles)', 'visibility_mi', "mdi:eye", LENGTH_MILES),
'weather': WUCurrentConditionsSensorConfig(
'Weather Summary', 'weather', None),
'wind_degrees': WUCurrentConditionsSensorConfig(
'Wind Degrees', 'wind_degrees', "mdi:weather-windy", "°"),
'wind_dir': WUCurrentConditionsSensorConfig(
'Wind Direction', 'wind_dir', "mdi:weather-windy"),
'wind_gust_kph': WUCurrentConditionsSensorConfig(
'Wind Gust', 'wind_gust_kph', "mdi:weather-windy", 'kph'),
'wind_gust_mph': WUCurrentConditionsSensorConfig(
'Wind Gust', 'wind_gust_mph', "mdi:weather-windy", 'mph'),
'wind_kph': WUCurrentConditionsSensorConfig(
'Wind Speed', 'wind_kph', "mdi:weather-windy", 'kph'),
'wind_mph': WUCurrentConditionsSensorConfig(
'Wind Speed', 'wind_mph', "mdi:weather-windy", 'mph'),
'wind_string': WUCurrentConditionsSensorConfig(
'Wind Summary', 'wind_string', "mdi:weather-windy"),
'temp_high_record_c': WUAlmanacSensorConfig(
lambda wu: 'High Temperature Record ({})'.format(
wu.data['almanac']['temp_high']['recordyear']),
'temp_high', 'record', 'C', TEMP_CELSIUS, 'mdi:thermometer'),
'temp_high_record_f': WUAlmanacSensorConfig(
lambda wu: 'High Temperature Record ({})'.format(
wu.data['almanac']['temp_high']['recordyear']),
'temp_high', 'record', 'F', TEMP_FAHRENHEIT, 'mdi:thermometer'),
'temp_low_record_c': WUAlmanacSensorConfig(
lambda wu: 'Low Temperature Record ({})'.format(
wu.data['almanac']['temp_low']['recordyear']),
'temp_low', 'record', 'C', TEMP_CELSIUS, 'mdi:thermometer'),
'temp_low_record_f': WUAlmanacSensorConfig(
lambda wu: 'Low Temperature Record ({})'.format(
wu.data['almanac']['temp_low']['recordyear']),
'temp_low', 'record', 'F', TEMP_FAHRENHEIT, 'mdi:thermometer'),
'temp_low_avg_c': WUAlmanacSensorConfig(
'Historic Average of Low Temperatures for Today',
'temp_low', 'normal', 'C', TEMP_CELSIUS, 'mdi:thermometer'),
'temp_low_avg_f': WUAlmanacSensorConfig(
'Historic Average of Low Temperatures for Today',
'temp_low', 'normal', 'F', TEMP_FAHRENHEIT, 'mdi:thermometer'),
'temp_high_avg_c': WUAlmanacSensorConfig(
'Historic Average of High Temperatures for Today',
'temp_high', 'normal', 'C', TEMP_CELSIUS, "mdi:thermometer"),
'temp_high_avg_f': WUAlmanacSensorConfig(
'Historic Average of High Temperatures for Today',
'temp_high', 'normal', 'F', TEMP_FAHRENHEIT, "mdi:thermometer"),
'weather_1d': WUDailyTextForecastSensorConfig(0, "fcttext"),
'weather_1d_metric': WUDailyTextForecastSensorConfig(0, "fcttext_metric"),
'weather_1n': WUDailyTextForecastSensorConfig(1, "fcttext"),
'weather_1n_metric': WUDailyTextForecastSensorConfig(1, "fcttext_metric"),
'weather_2d': WUDailyTextForecastSensorConfig(2, "fcttext"),
'weather_2d_metric': WUDailyTextForecastSensorConfig(2, "fcttext_metric"),
'weather_2n': WUDailyTextForecastSensorConfig(3, "fcttext"),
'weather_2n_metric': WUDailyTextForecastSensorConfig(3, "fcttext_metric"),
'weather_3d': WUDailyTextForecastSensorConfig(4, "fcttext"),
'weather_3d_metric': WUDailyTextForecastSensorConfig(4, "fcttext_metric"),
'weather_3n': WUDailyTextForecastSensorConfig(5, "fcttext"),
'weather_3n_metric': WUDailyTextForecastSensorConfig(5, "fcttext_metric"),
'weather_4d': WUDailyTextForecastSensorConfig(6, "fcttext"),
'weather_4d_metric': WUDailyTextForecastSensorConfig(6, "fcttext_metric"),
'weather_4n': WUDailyTextForecastSensorConfig(7, "fcttext"),
'weather_4n_metric': WUDailyTextForecastSensorConfig(7, "fcttext_metric"),
'weather_1h': WUHourlyForecastSensorConfig(0, "condition"),
'weather_2h': WUHourlyForecastSensorConfig(1, "condition"),
'weather_3h': WUHourlyForecastSensorConfig(2, "condition"),
'weather_4h': WUHourlyForecastSensorConfig(3, "condition"),
'weather_5h': WUHourlyForecastSensorConfig(4, "condition"),
'weather_6h': WUHourlyForecastSensorConfig(5, "condition"),
'weather_7h': WUHourlyForecastSensorConfig(6, "condition"),
'weather_8h': WUHourlyForecastSensorConfig(7, "condition"),
'weather_9h': WUHourlyForecastSensorConfig(8, "condition"),
'weather_10h': WUHourlyForecastSensorConfig(9, "condition"),
'weather_11h': WUHourlyForecastSensorConfig(10, "condition"),
'weather_12h': WUHourlyForecastSensorConfig(11, "condition"),
'weather_13h': WUHourlyForecastSensorConfig(12, "condition"),
'weather_14h': WUHourlyForecastSensorConfig(13, "condition"),
'weather_15h': WUHourlyForecastSensorConfig(14, "condition"),
'weather_16h': WUHourlyForecastSensorConfig(15, "condition"),
'weather_17h': WUHourlyForecastSensorConfig(16, "condition"),
'weather_18h': WUHourlyForecastSensorConfig(17, "condition"),
'weather_19h': WUHourlyForecastSensorConfig(18, "condition"),
'weather_20h': WUHourlyForecastSensorConfig(19, "condition"),
'weather_21h': WUHourlyForecastSensorConfig(20, "condition"),
'weather_22h': WUHourlyForecastSensorConfig(21, "condition"),
'weather_23h': WUHourlyForecastSensorConfig(22, "condition"),
'weather_24h': WUHourlyForecastSensorConfig(23, "condition"),
'weather_25h': WUHourlyForecastSensorConfig(24, "condition"),
'weather_26h': WUHourlyForecastSensorConfig(25, "condition"),
'weather_27h': WUHourlyForecastSensorConfig(26, "condition"),
'weather_28h': WUHourlyForecastSensorConfig(27, "condition"),
'weather_29h': WUHourlyForecastSensorConfig(28, "condition"),
'weather_30h': WUHourlyForecastSensorConfig(29, "condition"),
'weather_31h': WUHourlyForecastSensorConfig(30, "condition"),
'weather_32h': WUHourlyForecastSensorConfig(31, "condition"),
'weather_33h': WUHourlyForecastSensorConfig(32, "condition"),
'weather_34h': WUHourlyForecastSensorConfig(33, "condition"),
'weather_35h': WUHourlyForecastSensorConfig(34, "condition"),
'weather_36h': WUHourlyForecastSensorConfig(35, "condition"),
'temp_high_1d_c': WUDailySimpleForecastSensorConfig(
"High Temperature Today", 0, "high", "celsius", TEMP_CELSIUS,
"mdi:thermometer", device_class="temperature"),
'temp_high_2d_c': WUDailySimpleForecastSensorConfig(
"High Temperature Tomorrow", 1, "high", "celsius", TEMP_CELSIUS,
"mdi:thermometer", device_class="temperature"),
'temp_high_3d_c': WUDailySimpleForecastSensorConfig(
"High Temperature in 3 Days", 2, "high", "celsius", TEMP_CELSIUS,
"mdi:thermometer", device_class="temperature"),
'temp_high_4d_c': WUDailySimpleForecastSensorConfig(
"High Temperature in 4 Days", 3, "high", "celsius", TEMP_CELSIUS,
"mdi:thermometer", device_class="temperature"),
'temp_high_1d_f': WUDailySimpleForecastSensorConfig(
"High Temperature Today", 0, "high", "fahrenheit", TEMP_FAHRENHEIT,
"mdi:thermometer", device_class="temperature"),
'temp_high_2d_f': WUDailySimpleForecastSensorConfig(
"High Temperature Tomorrow", 1, "high", "fahrenheit", TEMP_FAHRENHEIT,
"mdi:thermometer", device_class="temperature"),
'temp_high_3d_f': WUDailySimpleForecastSensorConfig(
"High Temperature in 3 Days", 2, "high", "fahrenheit", TEMP_FAHRENHEIT,
"mdi:thermometer", device_class="temperature"),
'temp_high_4d_f': WUDailySimpleForecastSensorConfig(
"High Temperature in 4 Days", 3, "high", "fahrenheit", TEMP_FAHRENHEIT,
"mdi:thermometer", device_class="temperature"),
'temp_low_1d_c': WUDailySimpleForecastSensorConfig(
"Low Temperature Today", 0, "low", "celsius", TEMP_CELSIUS,
"mdi:thermometer", device_class="temperature"),
'temp_low_2d_c': WUDailySimpleForecastSensorConfig(
"Low Temperature Tomorrow", 1, "low", "celsius", TEMP_CELSIUS,
"mdi:thermometer", device_class="temperature"),
'temp_low_3d_c': WUDailySimpleForecastSensorConfig(
"Low Temperature in 3 Days", 2, "low", "celsius", TEMP_CELSIUS,
"mdi:thermometer", device_class="temperature"),
'temp_low_4d_c': WUDailySimpleForecastSensorConfig(
"Low Temperature in 4 Days", 3, "low", "celsius", TEMP_CELSIUS,
"mdi:thermometer", device_class="temperature"),
'temp_low_1d_f': WUDailySimpleForecastSensorConfig(
"Low Temperature Today", 0, "low", "fahrenheit", TEMP_FAHRENHEIT,
"mdi:thermometer", device_class="temperature"),
'temp_low_2d_f': WUDailySimpleForecastSensorConfig(
"Low Temperature Tomorrow", 1, "low", "fahrenheit", TEMP_FAHRENHEIT,
"mdi:thermometer", device_class="temperature"),
'temp_low_3d_f': WUDailySimpleForecastSensorConfig(
"Low Temperature in 3 Days", 2, "low", "fahrenheit", TEMP_FAHRENHEIT,
"mdi:thermometer", device_class="temperature"),
'temp_low_4d_f': WUDailySimpleForecastSensorConfig(
"Low Temperature in 4 Days", 3, "low", "fahrenheit", TEMP_FAHRENHEIT,
"mdi:thermometer", device_class="temperature"),
'wind_gust_1d_kph': WUDailySimpleForecastSensorConfig(
"Max. Wind Today", 0, "maxwind", "kph", "kph", "mdi:weather-windy"),
'wind_gust_2d_kph': WUDailySimpleForecastSensorConfig(
"Max. Wind Tomorrow", 1, "maxwind", "kph", "kph", "mdi:weather-windy"),
'wind_gust_3d_kph': WUDailySimpleForecastSensorConfig(
"Max. Wind in 3 Days", 2, "maxwind", "kph", "kph",
"mdi:weather-windy"),
'wind_gust_4d_kph': WUDailySimpleForecastSensorConfig(
"Max. Wind in 4 Days", 3, "maxwind", "kph", "kph",
"mdi:weather-windy"),
'wind_gust_1d_mph': WUDailySimpleForecastSensorConfig(
"Max. Wind Today", 0, "maxwind", "mph", "mph",
"mdi:weather-windy"),
'wind_gust_2d_mph': WUDailySimpleForecastSensorConfig(
"Max. Wind Tomorrow", 1, "maxwind", "mph", "mph",
"mdi:weather-windy"),
'wind_gust_3d_mph': WUDailySimpleForecastSensorConfig(
"Max. Wind in 3 Days", 2, "maxwind", "mph", "mph",
"mdi:weather-windy"),
'wind_gust_4d_mph': WUDailySimpleForecastSensorConfig(
"Max. Wind in 4 Days", 3, "maxwind", "mph", "mph",
"mdi:weather-windy"),
'wind_1d_kph': WUDailySimpleForecastSensorConfig(
"Avg. Wind Today", 0, "avewind", "kph", "kph",
"mdi:weather-windy"),
'wind_2d_kph': WUDailySimpleForecastSensorConfig(
"Avg. Wind Tomorrow", 1, "avewind", "kph", "kph",
"mdi:weather-windy"),
'wind_3d_kph': WUDailySimpleForecastSensorConfig(
"Avg. Wind in 3 Days", 2, "avewind", "kph", "kph",
"mdi:weather-windy"),
'wind_4d_kph': WUDailySimpleForecastSensorConfig(
"Avg. Wind in 4 Days", 3, "avewind", "kph", "kph",
"mdi:weather-windy"),
'wind_1d_mph': WUDailySimpleForecastSensorConfig(
"Avg. Wind Today", 0, "avewind", "mph", "mph",
"mdi:weather-windy"),
'wind_2d_mph': WUDailySimpleForecastSensorConfig(
"Avg. Wind Tomorrow", 1, "avewind", "mph", "mph",
"mdi:weather-windy"),
'wind_3d_mph': WUDailySimpleForecastSensorConfig(
"Avg. Wind in 3 Days", 2, "avewind", "mph", "mph",
"mdi:weather-windy"),
'wind_4d_mph': WUDailySimpleForecastSensorConfig(
"Avg. Wind in 4 Days", 3, "avewind", "mph", "mph",
"mdi:weather-windy"),
'precip_1d_mm': WUDailySimpleForecastSensorConfig(
"Precipitation Intensity Today", 0, 'qpf_allday', 'mm', 'mm',
"mdi:umbrella"),
'precip_2d_mm': WUDailySimpleForecastSensorConfig(
"Precipitation Intensity Tomorrow", 1, 'qpf_allday', 'mm', 'mm',
"mdi:umbrella"),
'precip_3d_mm': WUDailySimpleForecastSensorConfig(
"Precipitation Intensity in 3 Days", 2, 'qpf_allday', 'mm', 'mm',
"mdi:umbrella"),
'precip_4d_mm': WUDailySimpleForecastSensorConfig(
"Precipitation Intensity in 4 Days", 3, 'qpf_allday', 'mm', 'mm',
"mdi:umbrella"),
'precip_1d_in': WUDailySimpleForecastSensorConfig(
"Precipitation Intensity Today", 0, 'qpf_allday', 'in',
LENGTH_INCHES, "mdi:umbrella"),
'precip_2d_in': WUDailySimpleForecastSensorConfig(
"Precipitation Intensity Tomorrow", 1, 'qpf_allday', 'in',
LENGTH_INCHES, "mdi:umbrella"),
'precip_3d_in': WUDailySimpleForecastSensorConfig(
"Precipitation Intensity in 3 Days", 2, 'qpf_allday', 'in',
LENGTH_INCHES, "mdi:umbrella"),
'precip_4d_in': WUDailySimpleForecastSensorConfig(
"Precipitation Intensity in 4 Days", 3, 'qpf_allday', 'in',
LENGTH_INCHES, "mdi:umbrella"),
'precip_1d': WUDailySimpleForecastSensorConfig(
"Precipitation Probability Today", 0, "pop", None, "%",
"mdi:umbrella"),
'precip_2d': WUDailySimpleForecastSensorConfig(
"Precipitation Probability Tomorrow", 1, "pop", None, "%",
"mdi:umbrella"),
'precip_3d': WUDailySimpleForecastSensorConfig(
"Precipitation Probability in 3 Days", 2, "pop", None, "%",
"mdi:umbrella"),
'precip_4d': WUDailySimpleForecastSensorConfig(
"Precipitation Probability in 4 Days", 3, "pop", None, "%",
"mdi:umbrella"),
}
# Alert Attributes
ALERTS_ATTRS = [
'date',
'description',
'expires',
'message',
]
# Language Supported Codes
LANG_CODES = [
'AF', 'AL', 'AR', 'HY', 'AZ', 'EU',
'BY', 'BU', 'LI', 'MY', 'CA', 'CN',
'TW', 'CR', 'CZ', 'DK', 'DV', 'NL',
'EN', 'EO', 'ET', 'FA', 'FI', 'FR',
'FC', 'GZ', 'DL', 'KA', 'GR', 'GU',
'HT', 'IL', 'HI', 'HU', 'IS', 'IO',
'ID', 'IR', 'IT', 'JP', 'JW', 'KM',
'KR', 'KU', 'LA', 'LV', 'LT', 'ND',
'MK', 'MT', 'GM', 'MI', 'MR', 'MN',
'NO', 'OC', 'PS', 'GN', 'PL', 'BR',
'PA', 'RO', 'RU', 'SR', 'SK', 'SL',
'SP', 'SI', 'SW', 'CH', 'TL', 'TT',
'TH', 'TR', 'TK', 'UA', 'UZ', 'VU',
'CY', 'SN', 'JI', 'YI',
]
2016-08-18 16:47:52 +00:00
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_API_KEY): cv.string,
vol.Optional(CONF_PWS_ID): cv.string,
vol.Optional(CONF_LANG, default=DEFAULT_LANG): vol.All(vol.In(LANG_CODES)),
vol.Inclusive(CONF_LATITUDE, 'coordinates',
'Latitude and longitude must exist together'): cv.latitude,
vol.Inclusive(CONF_LONGITUDE, 'coordinates',
'Latitude and longitude must exist together'): cv.longitude,
vol.Required(CONF_MONITORED_CONDITIONS):
vol.All(cv.ensure_list, vol.Length(min=1), [vol.In(SENSOR_TYPES)])
2016-08-18 04:19:13 +00:00
})
async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
async_add_entities, discovery_info=None):
"""Set up the WUnderground sensor."""
latitude = config.get(CONF_LATITUDE, hass.config.latitude)
longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
pws_id = config.get(CONF_PWS_ID)
rest = WUndergroundData(
hass, config.get(CONF_API_KEY), pws_id,
config.get(CONF_LANG), latitude, longitude)
if pws_id is None:
unique_id_base = "@{:06f},{:06f}".format(longitude, latitude)
else:
# Manually specified weather station, use that for unique_id
unique_id_base = pws_id
sensors = []
2016-08-18 16:38:34 +00:00
for variable in config[CONF_MONITORED_CONDITIONS]:
sensors.append(WUndergroundSensor(hass, rest, variable,
unique_id_base))
await rest.async_update()
if not rest.data:
raise PlatformNotReady
async_add_entities(sensors, True)
class WUndergroundSensor(Entity):
2016-08-18 16:46:04 +00:00
"""Implementing the WUnderground sensor."""
def __init__(self, hass: HomeAssistantType, rest, condition,
unique_id_base: str):
"""Initialize the sensor."""
self.rest = rest
self._condition = condition
self._state = None
self._attributes = {ATTR_ATTRIBUTION: ATTRIBUTION}
self._icon = None
self._entity_picture = None
self._unit_of_measurement = self._cfg_expand("unit_of_measurement")
self.rest.request_feature(SENSOR_TYPES[condition].feature)
# This is only the suggested entity id, it might get changed by
# the entity registry later.
self.entity_id = sensor.ENTITY_ID_FORMAT.format('pws_' + condition)
self._unique_id = "{},{}".format(unique_id_base, condition)
self._device_class = self._cfg_expand("device_class")
def _cfg_expand(self, what, default=None):
"""Parse and return sensor data."""
cfg = SENSOR_TYPES[self._condition]
val = getattr(cfg, what)
if not callable(val):
return val
try:
val = val(self.rest)
except (KeyError, IndexError, TypeError, ValueError) as err:
_LOGGER.warning("Failed to expand cfg from WU API."
" Condition: %s Attr: %s Error: %s",
self._condition, what, repr(err))
val = default
return val
def _update_attrs(self):
"""Parse and update device state attributes."""
attrs = self._cfg_expand("device_state_attributes", {})
for (attr, callback) in attrs.items():
if callable(callback):
try:
self._attributes[attr] = callback(self.rest)
except (KeyError, IndexError, TypeError, ValueError) as err:
_LOGGER.warning("Failed to update attrs from WU API."
" Condition: %s Attr: %s Error: %s",
self._condition, attr, repr(err))
else:
self._attributes[attr] = callback
@property
def name(self):
"""Return the name of the sensor."""
return self._cfg_expand("friendly_name")
@property
def state(self):
"""Return the state of the sensor."""
return self._state
@property
def device_state_attributes(self):
"""Return the state attributes."""
return self._attributes
@property
def icon(self):
"""Return icon."""
return self._icon
@property
def entity_picture(self):
"""Return the entity picture."""
return self._entity_picture
@property
def unit_of_measurement(self):
"""Return the units of measurement."""
return self._unit_of_measurement
@property
def device_class(self):
"""Return the units of measurement."""
return self._device_class
2018-10-01 06:55:43 +00:00
async def async_update(self):
"""Update current conditions."""
2018-10-01 06:55:43 +00:00
await self.rest.async_update()
if not self.rest.data:
# no data, return
return
self._state = self._cfg_expand("value")
self._update_attrs()
self._icon = self._cfg_expand("icon", super().icon)
url = self._cfg_expand("entity_picture")
if isinstance(url, str):
self._entity_picture = re.sub(r'^http://', 'https://',
url, flags=re.IGNORECASE)
@property
def unique_id(self) -> str:
"""Return a unique ID."""
return self._unique_id
class WUndergroundData:
2016-08-18 16:46:04 +00:00
"""Get data from WUnderground."""
def __init__(self, hass, api_key, pws_id, lang, latitude, longitude):
"""Initialize the data object."""
2016-08-18 04:28:05 +00:00
self._hass = hass
self._api_key = api_key
self._pws_id = pws_id
self._lang = 'lang:{}'.format(lang)
self._latitude = latitude
self._longitude = longitude
self._features = set()
self.data = None
self._session = async_get_clientsession(self._hass)
def request_feature(self, feature):
"""Register feature to be fetched from WU API."""
self._features.add(feature)
def _build_url(self, baseurl=_RESOURCE):
url = baseurl.format(
self._api_key, '/'.join(sorted(self._features)), self._lang)
2016-08-18 04:22:11 +00:00
if self._pws_id:
2016-08-18 16:46:24 +00:00
url = url + 'pws:{}'.format(self._pws_id)
2016-08-18 04:22:11 +00:00
else:
url = url + '{},{}'.format(self._latitude, self._longitude)
return url + '.json'
@Throttle(MIN_TIME_BETWEEN_UPDATES)
async def async_update(self):
2016-08-18 16:46:04 +00:00
"""Get the latest data from WUnderground."""
try:
with async_timeout.timeout(10):
response = await self._session.get(self._build_url())
result = await response.json()
2016-08-18 04:32:42 +00:00
if "error" in result['response']:
raise ValueError(result['response']["error"]["description"])
self.data = result
except ValueError as err:
2016-08-18 16:46:04 +00:00
_LOGGER.error("Check WUnderground API %s", err.args)
except (asyncio.TimeoutError, aiohttp.ClientError) as err:
_LOGGER.error("Error fetching WUnderground data: %s", repr(err))