"""Support for retrieving meteorological data from Dark Sky.""" from datetime import datetime, timedelta import logging 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.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 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: datetime.fromtimestamp( 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: datetime.fromtimestamp( 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 self._ds_currently = self._dark_sky.currently.d 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.""" import forecastio 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")