core/homeassistant/components/darksky/weather.py

265 lines
8.0 KiB
Python

"""Support for retrieving meteorological data from Dark Sky."""
from datetime import timedelta
import logging
import forecastio
from requests.exceptions import ConnectionError as ConnectError, HTTPError, Timeout
import voluptuous as vol
from homeassistant.components.weather import (
ATTR_FORECAST_CONDITION,
ATTR_FORECAST_PRECIPITATION,
ATTR_FORECAST_TEMP,
ATTR_FORECAST_TEMP_LOW,
ATTR_FORECAST_TIME,
ATTR_FORECAST_WIND_BEARING,
ATTR_FORECAST_WIND_SPEED,
PLATFORM_SCHEMA,
WeatherEntity,
)
from homeassistant.const import (
CONF_API_KEY,
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_MODE,
CONF_NAME,
PRESSURE_HPA,
PRESSURE_INHG,
TEMP_CELSIUS,
TEMP_FAHRENHEIT,
)
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
from homeassistant.util.dt import utc_from_timestamp
from homeassistant.util.pressure import convert as convert_pressure
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Powered by Dark Sky"
FORECAST_MODE = ["hourly", "daily"]
MAP_CONDITION = {
"clear-day": "sunny",
"clear-night": "clear-night",
"rain": "rainy",
"snow": "snowy",
"sleet": "snowy-rainy",
"wind": "windy",
"fog": "fog",
"cloudy": "cloudy",
"partly-cloudy-day": "partlycloudy",
"partly-cloudy-night": "partlycloudy",
"hail": "hail",
"thunderstorm": "lightning",
"tornado": None,
}
CONF_UNITS = "units"
DEFAULT_NAME = "Dark Sky"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_API_KEY): cv.string,
vol.Optional(CONF_LATITUDE): cv.latitude,
vol.Optional(CONF_LONGITUDE): cv.longitude,
vol.Optional(CONF_MODE, default="hourly"): vol.In(FORECAST_MODE),
vol.Optional(CONF_UNITS): vol.In(["auto", "si", "us", "ca", "uk", "uk2"]),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
}
)
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=3)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Dark Sky weather."""
latitude = config.get(CONF_LATITUDE, hass.config.latitude)
longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
name = config.get(CONF_NAME)
mode = config.get(CONF_MODE)
units = config.get(CONF_UNITS)
if not units:
units = "ca" if hass.config.units.is_metric else "us"
dark_sky = DarkSkyData(config.get(CONF_API_KEY), latitude, longitude, units)
add_entities([DarkSkyWeather(name, dark_sky, mode)], True)
class DarkSkyWeather(WeatherEntity):
"""Representation of a weather condition."""
def __init__(self, name, dark_sky, mode):
"""Initialize Dark Sky weather."""
self._name = name
self._dark_sky = dark_sky
self._mode = mode
self._ds_data = None
self._ds_currently = None
self._ds_hourly = None
self._ds_daily = None
@property
def available(self):
"""Return if weather data is available from Dark Sky."""
return self._ds_data is not None
@property
def attribution(self):
"""Return the attribution."""
return ATTRIBUTION
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def temperature(self):
"""Return the temperature."""
return self._ds_currently.get("temperature")
@property
def temperature_unit(self):
"""Return the unit of measurement."""
if self._dark_sky.units is None:
return None
return TEMP_FAHRENHEIT if "us" in self._dark_sky.units else TEMP_CELSIUS
@property
def humidity(self):
"""Return the humidity."""
return round(self._ds_currently.get("humidity") * 100.0, 2)
@property
def wind_speed(self):
"""Return the wind speed."""
return self._ds_currently.get("windSpeed")
@property
def wind_bearing(self):
"""Return the wind bearing."""
return self._ds_currently.get("windBearing")
@property
def ozone(self):
"""Return the ozone level."""
return self._ds_currently.get("ozone")
@property
def pressure(self):
"""Return the pressure."""
pressure = self._ds_currently.get("pressure")
if "us" in self._dark_sky.units:
return round(convert_pressure(pressure, PRESSURE_HPA, PRESSURE_INHG), 2)
return pressure
@property
def visibility(self):
"""Return the visibility."""
return self._ds_currently.get("visibility")
@property
def condition(self):
"""Return the weather condition."""
return MAP_CONDITION.get(self._ds_currently.get("icon"))
@property
def forecast(self):
"""Return the forecast array."""
# Per conversation with Joshua Reyes of Dark Sky, to get the total
# forecasted precipitation, you have to multiple the intensity by
# the hours for the forecast interval
def calc_precipitation(intensity, hours):
amount = None
if intensity is not None:
amount = round((intensity * hours), 1)
return amount if amount > 0 else None
data = None
if self._mode == "daily":
data = [
{
ATTR_FORECAST_TIME: utc_from_timestamp(
entry.d.get("time")
).isoformat(),
ATTR_FORECAST_TEMP: entry.d.get("temperatureHigh"),
ATTR_FORECAST_TEMP_LOW: entry.d.get("temperatureLow"),
ATTR_FORECAST_PRECIPITATION: calc_precipitation(
entry.d.get("precipIntensity"), 24
),
ATTR_FORECAST_WIND_SPEED: entry.d.get("windSpeed"),
ATTR_FORECAST_WIND_BEARING: entry.d.get("windBearing"),
ATTR_FORECAST_CONDITION: MAP_CONDITION.get(entry.d.get("icon")),
}
for entry in self._ds_daily.data
]
else:
data = [
{
ATTR_FORECAST_TIME: utc_from_timestamp(
entry.d.get("time")
).isoformat(),
ATTR_FORECAST_TEMP: entry.d.get("temperature"),
ATTR_FORECAST_PRECIPITATION: calc_precipitation(
entry.d.get("precipIntensity"), 1
),
ATTR_FORECAST_CONDITION: MAP_CONDITION.get(entry.d.get("icon")),
}
for entry in self._ds_hourly.data
]
return data
def update(self):
"""Get the latest data from Dark Sky."""
self._dark_sky.update()
self._ds_data = self._dark_sky.data
currently = self._dark_sky.currently
self._ds_currently = currently.d if currently else {}
self._ds_hourly = self._dark_sky.hourly
self._ds_daily = self._dark_sky.daily
class DarkSkyData:
"""Get the latest data from Dark Sky."""
def __init__(self, api_key, latitude, longitude, units):
"""Initialize the data object."""
self._api_key = api_key
self.latitude = latitude
self.longitude = longitude
self.requested_units = units
self.data = None
self.currently = None
self.hourly = None
self.daily = None
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Get the latest data from Dark Sky."""
try:
self.data = forecastio.load_forecast(
self._api_key, self.latitude, self.longitude, units=self.requested_units
)
self.currently = self.data.currently()
self.hourly = self.data.hourly()
self.daily = self.data.daily()
except (ConnectError, HTTPError, Timeout, ValueError) as error:
_LOGGER.error("Unable to connect to Dark Sky. %s", error)
self.data = None
@property
def units(self):
"""Get the unit system of returned data."""
if self.data is None:
return None
return self.data.json.get("flags").get("units")