"""Support for the Yahoo! Weather service.""" from datetime import timedelta import logging import voluptuous as vol from yahooweather import ( # pylint: disable=import-error UNIT_C, UNIT_F, YahooWeather, get_woeid, ) from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS, CONF_NAME, TEMP_CELSIUS, UNIT_PERCENTAGE, ) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) ATTRIBUTION = "Weather details provided by Yahoo! Inc." CONF_FORECAST = "forecast" CONF_WOEID = "woeid" DEFAULT_NAME = "Yweather" MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=10) SENSOR_TYPES = { "weather_current": ["Current", None], "weather": ["Condition", None], "temperature": ["Temperature", "temperature"], "temp_min": ["Temperature min", "temperature"], "temp_max": ["Temperature max", "temperature"], "wind_speed": ["Wind speed", "speed"], "humidity": ["Humidity", UNIT_PERCENTAGE], "pressure": ["Pressure", "pressure"], "visibility": ["Visibility", "distance"], } PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Optional(CONF_WOEID): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_FORECAST, default=0): vol.All( vol.Coerce(int), vol.Range(min=0, max=5) ), vol.Required(CONF_MONITORED_CONDITIONS, default=[]): [vol.In(SENSOR_TYPES)], } ) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Yahoo! weather sensor.""" unit = hass.config.units.temperature_unit woeid = config.get(CONF_WOEID) forecast = config.get(CONF_FORECAST) name = config.get(CONF_NAME) yunit = UNIT_C if unit == TEMP_CELSIUS else UNIT_F SENSOR_TYPES["temperature"][1] = unit SENSOR_TYPES["temp_min"][1] = unit SENSOR_TYPES["temp_max"][1] = unit # If not exists a customer WOEID/calculation from Home Assistant if woeid is None: woeid = get_woeid(hass.config.latitude, hass.config.longitude) if woeid is None: _LOGGER.critical("Can't retrieve WOEID from yahoo!") return False yahoo_api = YahooWeatherData(woeid, yunit) if not yahoo_api.update(): _LOGGER.critical("Can't retrieve weather data from Yahoo!") return False if forecast >= len(yahoo_api.yahoo.Forecast): _LOGGER.error( "Yahoo! only support %d days forecast!", len(yahoo_api.yahoo.Forecast) ) return False dev = [] for variable in config[CONF_MONITORED_CONDITIONS]: dev.append(YahooWeatherSensor(yahoo_api, name, forecast, variable)) add_entities(dev, True) class YahooWeatherSensor(Entity): """Implementation of the Yahoo! weather sensor.""" def __init__(self, weather_data, name, forecast, sensor_type): """Initialize the sensor.""" self._client = name self._name = SENSOR_TYPES[sensor_type][0] self._type = sensor_type self._state = None self._unit = SENSOR_TYPES[sensor_type][1] self._data = weather_data self._forecast = forecast self._code = None @property def name(self): """Return the name of the sensor.""" return f"{self._client} {self._name}" @property def state(self): """Return the state of the device.""" return self._state @property def unit_of_measurement(self): """Return the unit of measurement of this entity, if any.""" return self._data.yahoo.Units.get(self._unit, self._unit) @property def entity_picture(self): """Return the entity picture to use in the frontend, if any.""" if self._code is None or "weather" not in self._type: return None return self._data.yahoo.getWeatherImage(self._code) @property def device_state_attributes(self): """Return the state attributes.""" attrs = {ATTR_ATTRIBUTION: ATTRIBUTION} if self._code is not None and "weather" in self._type: attrs["condition_code"] = self._code return attrs def update(self): """Get the latest data from Yahoo! and updates the states.""" self._data.update() if not self._data.yahoo.RawData: _LOGGER.info("Don't receive weather data from Yahoo!") return # Default code for weather image self._code = self._data.yahoo.Now["code"] # Read data if self._type == "weather_current": self._state = self._data.yahoo.Now["text"] elif self._type == "weather": self._code = self._data.yahoo.Forecast[self._forecast]["code"] self._state = self._data.yahoo.Forecast[self._forecast]["text"] elif self._type == "temperature": self._state = self._data.yahoo.Now["temp"] elif self._type == "temp_min": self._code = self._data.yahoo.Forecast[self._forecast]["code"] self._state = self._data.yahoo.Forecast[self._forecast]["low"] elif self._type == "temp_max": self._code = self._data.yahoo.Forecast[self._forecast]["code"] self._state = self._data.yahoo.Forecast[self._forecast]["high"] elif self._type == "wind_speed": self._state = round(float(self._data.yahoo.Wind["speed"]) / 1.61, 2) elif self._type == "humidity": self._state = self._data.yahoo.Atmosphere["humidity"] elif self._type == "pressure": self._state = round( float(self._data.yahoo.Atmosphere["pressure"]) / 33.8637526, 2 ) elif self._type == "visibility": self._state = round( float(self._data.yahoo.Atmosphere["visibility"]) / 1.61, 2 ) class YahooWeatherData: """Handle Yahoo! API object and limit updates.""" def __init__(self, woeid, temp_unit): """Initialize the data object.""" self._yahoo = YahooWeather(woeid, temp_unit) @property def yahoo(self): """Return Yahoo! API object.""" return self._yahoo @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): """Get the latest data from Yahoo!.""" return self._yahoo.updateWeather()