core/homeassistant/components/metoffice/sensor.py

202 lines
6.0 KiB
Python
Raw Normal View History

"""Support for UK Met Office weather service."""
from datetime import timedelta
import logging
import datapoint as dp
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
2019-07-31 19:25:30 +00:00
ATTR_ATTRIBUTION,
CONF_API_KEY,
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_MONITORED_CONDITIONS,
CONF_NAME,
SPEED_MILES_PER_HOUR,
2019-07-31 19:25:30 +00:00
TEMP_CELSIUS,
)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
2019-07-31 19:25:30 +00:00
ATTR_LAST_UPDATE = "last_update"
ATTR_SENSOR_ID = "sensor_id"
ATTR_SITE_ID = "site_id"
ATTR_SITE_NAME = "site_name"
ATTRIBUTION = "Data provided by the Met Office"
CONDITION_CLASSES = {
2019-07-31 19:25:30 +00:00
"cloudy": ["7", "8"],
"fog": ["5", "6"],
"hail": ["19", "20", "21"],
"lightning": ["30"],
"lightning-rainy": ["28", "29"],
"partlycloudy": ["2", "3"],
"pouring": ["13", "14", "15"],
"rainy": ["9", "10", "11", "12"],
"snowy": ["22", "23", "24", "25", "26", "27"],
"snowy-rainy": ["16", "17", "18"],
"sunny": ["0", "1"],
"windy": [],
"windy-variant": [],
"exceptional": [],
}
DEFAULT_NAME = "Met Office"
VISIBILITY_CLASSES = {
2019-07-31 19:25:30 +00:00
"VP": "<1",
"PO": "1-4",
"MO": "4-10",
"GO": "10-20",
"VG": "20-40",
"EX": ">40",
}
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=35)
# Sensor types are defined like: Name, units
SENSOR_TYPES = {
2019-07-31 19:25:30 +00:00
"name": ["Station Name", None],
"weather": ["Weather", None],
"temperature": ["Temperature", TEMP_CELSIUS],
"feels_like_temperature": ["Feels Like Temperature", TEMP_CELSIUS],
"wind_speed": ["Wind Speed", SPEED_MILES_PER_HOUR],
2019-07-31 19:25:30 +00:00
"wind_direction": ["Wind Direction", None],
"wind_gust": ["Wind Gust", SPEED_MILES_PER_HOUR],
2019-07-31 19:25:30 +00:00
"visibility": ["Visibility", None],
"visibility_distance": ["Visibility Distance", "km"],
"uv": ["UV", None],
"precipitation": ["Probability of Precipitation", "%"],
"humidity": ["Humidity", "%"],
}
2019-07-31 19:25:30 +00:00
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_API_KEY): cv.string,
vol.Required(CONF_MONITORED_CONDITIONS, default=[]): vol.All(
cv.ensure_list, [vol.In(SENSOR_TYPES)]
),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
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,
}
)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Met Office sensor platform."""
api_key = config.get(CONF_API_KEY)
latitude = config.get(CONF_LATITUDE, hass.config.latitude)
longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
name = config.get(CONF_NAME)
datapoint = dp.connection(api_key=api_key)
if None in (latitude, longitude):
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
return
try:
2019-07-31 19:25:30 +00:00
site = datapoint.get_nearest_site(latitude=latitude, longitude=longitude)
except dp.exceptions.APIException as err:
_LOGGER.error("Received error from Met Office Datapoint: %s", err)
return
if not site:
_LOGGER.error("Unable to get nearest Met Office forecast site")
return
data = MetOfficeCurrentData(hass, datapoint, site)
data.update()
if data.data is None:
return
sensors = []
for variable in config[CONF_MONITORED_CONDITIONS]:
sensors.append(MetOfficeCurrentSensor(site, data, variable, name))
add_entities(sensors, True)
class MetOfficeCurrentSensor(Entity):
"""Implementation of a Met Office current sensor."""
def __init__(self, site, data, condition, name):
"""Initialize the sensor."""
self._condition = condition
self.data = data
self._name = name
self.site = site
@property
def name(self):
"""Return the name of the sensor."""
2019-07-31 19:25:30 +00:00
return "{} {}".format(self._name, SENSOR_TYPES[self._condition][0])
@property
def state(self):
"""Return the state of the sensor."""
2019-07-31 19:25:30 +00:00
if self._condition == "visibility_distance" and hasattr(
self.data.data, "visibility"
):
return VISIBILITY_CLASSES.get(self.data.data.visibility.value)
if hasattr(self.data.data, self._condition):
variable = getattr(self.data.data, self._condition)
2019-07-31 19:25:30 +00:00
if self._condition == "weather":
return [
k
for k, v in CONDITION_CLASSES.items()
if self.data.data.weather.value in v
][0]
return variable.value
return None
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return SENSOR_TYPES[self._condition][1]
@property
def device_state_attributes(self):
"""Return the state attributes of the device."""
attr = {}
attr[ATTR_ATTRIBUTION] = ATTRIBUTION
attr[ATTR_LAST_UPDATE] = self.data.data.date
attr[ATTR_SENSOR_ID] = self._condition
attr[ATTR_SITE_ID] = self.site.id
attr[ATTR_SITE_NAME] = self.site.name
return attr
def update(self):
"""Update current conditions."""
self.data.update()
class MetOfficeCurrentData:
"""Get data from Datapoint."""
def __init__(self, hass, datapoint, site):
"""Initialize the data object."""
self._datapoint = datapoint
self._site = site
self.data = None
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Get the latest data from Datapoint."""
try:
2019-07-31 19:25:30 +00:00
forecast = self._datapoint.get_forecast_for_site(self._site.id, "3hourly")
self.data = forecast.now()
except (ValueError, dp.exceptions.APIException) as err:
_LOGGER.error("Check Met Office %s", err.args)
self.data = None