Yandex.Weather компонент погоды для Home-assistant

12 апр. 2019 г.

В чате просили поделиться компонентом погоды для Yandex. Собственно делюсь.

В кабинете разработчика необходимо создать API ключ и выбрать тариф "Погода на вашем сайте".

Установка: содайте папку <hass config>/custom_components/yandex_weather. В папке содайте файл weather.py. Скопируйте код ниже в файл:

"""
Support for the Yandex.Weather with “Weather on your site” rate.
For more details about Yandex.Weather, please refer to the documentation at
https://tech.yandex.com/weather/

"""


import asyncio
import logging
import socket

import aiohttp
import async_timeout

from datetime import timedelta
import voluptuous as vol
import homeassistant.util.dt as dt_util

from homeassistant.const import (
    CONF_NAME, CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, TEMP_CELSIUS, STATE_UNKNOWN)
from homeassistant.components.weather import (
    ATTR_FORECAST_CONDITION, ATTR_FORECAST_TEMP, ATTR_FORECAST_TEMP_LOW,
    ATTR_FORECAST_TIME, ATTR_FORECAST_PRECIPITATION, ATTR_FORECAST_WIND_BEARING,
    ATTR_FORECAST_WIND_SPEED, ATTR_WEATHER_PRESSURE, ATTR_WEATHER_HUMIDITY, PLATFORM_SCHEMA, WeatherEntity)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util import Throttle


import homeassistant.helpers.config_validation as cv

_LOGGER = logging.getLogger(__name__)

TIME_STR_FORMAT = '%H:%M %d.%m.%Y'
DEFAULT_NAME = 'Yandex Weather'
ATTRIBUTION = 'Data provided by Yandex.Weather'

ATTR_FEELS_LIKE = 'feels_like'
ATTR_WEATHER_ICON = 'weather_icon'
ATTR_PRESSURE_MM = 'pressure_mm'
ATTR_OBS_TIME = 'observation_time'
ATTR_WEATHER_CON = 'weather_condition'
ATTR_PRECIPITATION_PROB = 'precipitation_probability'
ATTR_WIND_SPEED_MS = 'wind_speed_ms'

CONDITION_CLASSES = {
    'sunny': ['clear'],
    'partlycloudy': ['partly-cloudy'],
    'cloudy': ['cloudy', 'overcast'],
    'pouring': ['overcast-and-rain'],
    'rainy': ['cloudy-and-rain', 'overcast-and-light-rain', 'cloudy-and-light-rain', 'partly-cloudy-and-light-rain'],
    'lightning-rainy': ['overcast-thunderstorms-with-rain'],
    'snowy-rainy': ['overcast-and-wet-snow'],
    'snowy': ['cloudy-and-snow', 'overcast-and-light-snow', 'cloudy-and-light-snow', 'overcast-and-snow', 'partly-cloudy-and-snow', 'partly-cloudy-and-light-snow'],
}

DESCRIPTION_DIC = {
    'clear': 'Ясно',
    'partly-cloudy': 'Малооблачно',
    'cloudy': 'Облачно с прояснениями',
    'overcast': 'Пасмурно',
    'partly-cloudy-and-light-rain': 'Небольшой дождь',
    'partly-cloudy-and-rain': 'Дождь',
    'overcast-and-rain': 'Сильный дождь',
    'overcast-thunderstorms-with-rain': 'Сильный дождь, гроза',
    'cloudy-and-light-rain': 'Небольшой дождь',
    'overcast-and-light-rain': 'Небольшой дождь',
    'cloudy-and-rain': 'Дождь',
    'overcast-and-wet-snow': 'Дождь со снегом',
    'partly-cloudy-and-light-snow': 'Небольшой снег',
    'partly-cloudy-and-snow': 'Снег',
    'overcast-and-snow': 'Снегопад',
    'cloudy-and-light-snow': 'Небольшой снег',
    'overcast-and-light-snow': 'Небольшой снег',
    'cloudy-and-snow': 'Снег',
}

MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=30)

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({    
    vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
    vol.Required(CONF_API_KEY): cv.string,
    vol.Optional(CONF_LATITUDE): cv.latitude,
    vol.Optional(CONF_LONGITUDE): cv.longitude,
})

async def async_setup_platform(
    hass, config, async_add_entities, discovery_info=None):

    """Set up the Yandex.Weather weather platform."""
    longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
    latitude = config.get(CONF_LATITUDE, hass.config.latitude)
    name = config.get(CONF_NAME)
    api_key = config.get(CONF_API_KEY)
    session = async_get_clientsession(hass)
    loop = hass.loop    

    async_add_entities([YandexWeather(name, longitude, latitude, api_key, loop, session)], True)

class YandexWeather (WeatherEntity):
    """Representation of a weather entity."""
    def __init__(self, name: str, longitude: str, latitude: str, api_key: str, loop, session):
        self._name = name
        self._longitude = longitude
        self._latitude = latitude
        self._api_key = api_key
        self._hloop = loop
        self._hsession = session
        self._weather_data = YaWeather(self._latitude, self._longitude, self._api_key, self._hloop, session=self._hsession)
    
    @Throttle(MIN_TIME_BETWEEN_UPDATES)
    async def async_update(self):
        """Get the latest weather information."""
        await self._weather_data.get_weather()
    
    @property
    def name(self) -> str:
        """Return the name of the sensor."""
        return self._name

    @property
    def temperature(self) -> int:
        """Return the temperature."""
        if self._weather_data.current is not None:
            return self._weather_data.current['temp']
        return None
    
    @property
    def temperature_unit(self) -> str:
        """Return the unit of measurement."""
        return TEMP_CELSIUS
    
    @property
    def humidity(self) -> int:
        """Return the humidity."""
        if self._weather_data.current is not None:
            return self._weather_data.current['humidity']
        return None
    
    @property
    def wind_speed(self) -> float:
        """Return the wind speed."""
        if self._weather_data.current is not None:
            # Convert from m/s to km/h
            return round(self._weather_data.current['wind_speed'] * 18 / 5)
        return None
    
    @property
    def wind_bearing(self) -> str:
        """Return the wind speed."""
        if self._weather_data.current is not None:
            # The current wind bearing
            return self._weather_data.current['wind_dir']
        return None
    
    @property
    def pressure(self) -> int:
        """Return the pressure."""
        if self._weather_data.current is not None:
            return self._weather_data.current['pressure_pa']
        return None    
    
    @property
    def condition(self) -> str:
        if self._weather_data.current is not None:
            return next((
            k for k, v in CONDITION_CLASSES.items()
            if self._weather_data.current['condition'] in v), None)            
        return STATE_UNKNOWN

    @property
    def condition_icon(self) -> int:
        """Return the pressure."""
        if self._weather_data.current is not None:
            return self._weather_data.current['icon']
        return None 

    @property
    def forecast(self):
        """Return the forecast array."""
        if self._weather_data.forecast is not None:
            fcdata_out = []
            for data_in in self._weather_data.forecast['parts']:
                data_out = {}
                if (self._weather_data.forecast['parts'].index(data_in) == 0):
                    data_out[ATTR_FORECAST_TIME] = dt_util.utcnow()+timedelta(minutes=350)
                if (self._weather_data.forecast['parts'].index(data_in) == 1):
                    data_out[ATTR_FORECAST_TIME] = dt_util.utcnow()+timedelta(minutes=700)
                data_out[ATTR_FORECAST_TEMP] = data_in['temp_max']
                data_out[ATTR_FORECAST_TEMP_LOW] = data_in['temp_min']
                data_out[ATTR_FORECAST_CONDITION] = next((
                    k for k, v in CONDITION_CLASSES.items()
                    if data_in['condition'] in v), None)
                data_out[ATTR_PRESSURE_MM] = data_in['pressure_mm']
                data_out[ATTR_WEATHER_ICON] = data_in['icon']
                data_out[ATTR_FEELS_LIKE] = data_in['feels_like']
                data_out[ATTR_FORECAST_WIND_SPEED] = round(data_in['wind_speed'] * 18 / 5)
                data_out[ATTR_FORECAST_WIND_BEARING] = data_in['wind_dir']
                data_out[ATTR_FORECAST_PRECIPITATION] = data_in['prec_mm']
                data_out[ATTR_PRECIPITATION_PROB] = data_in['prec_prob']
                data_out[ATTR_WEATHER_CON] = DESCRIPTION_DIC[data_in['condition']]
                data_out[ATTR_WEATHER_PRESSURE] = data_in['pressure_pa']
                data_out[ATTR_WEATHER_HUMIDITY] = data_in['humidity']
                data_out[ATTR_WIND_SPEED_MS] = data_in['wind_speed']
                data_out['part_of_day'] = data_in['part_name']
                fcdata_out.append(data_out)

            return fcdata_out
    
    @property
    def attribution(self) -> str:
        """Return the attribution."""
        return ATTRIBUTION
    
    @property
    def device_state_attributes(self):
        """Return device specific state attributes."""
        if self._weather_data.current is not None:
            data = dict()
            data[ATTR_FEELS_LIKE] = self._weather_data.current['feels_like']
            data[ATTR_PRESSURE_MM] = self._weather_data.current['pressure_mm']
            data[ATTR_WIND_SPEED_MS] = self._weather_data.current['wind_speed']
            data[ATTR_WEATHER_ICON] = self._weather_data.current['icon']
            data[ATTR_OBS_TIME] = dt_util.as_local(dt_util.utc_from_timestamp(self._weather_data.current['obs_time'])).strftime(TIME_STR_FORMAT)
            data[ATTR_WEATHER_CON] = DESCRIPTION_DIC[self._weather_data.current['condition']]
            return data
        return None 


class YaWeather(object):
    """A class for returning Yandex Weather data."""

    def __init__(self, lat: str, lon: str, api_key, loop, session='none'):
        """Initialize the class."""
        self._api = api_key
        self._lat = lat
        self._lon = lon
        self._loop = loop
        self._session = session
        self._current = {}
        self._forecast = {}
        

    async def get_weather(self):
        base_url="https://api.weather.yandex.ru/v1/informers?lat=%s&lon=%s" % (self._lat, self._lon)
        headers = {'X-Yandex-API-Key':self._api}       

        try:
            async with async_timeout.timeout(5, loop=self._loop):
                response = await self._session.get(base_url, headers=headers)
            data = await response.json()

            if ('status' not in data):
                self._current = data['fact']
                self._forecast = data['forecast']

            else:
                _LOGGER.error('Error fetching data from Yandex.Weather, %s, %s', data['status'],data['message'])

        except (asyncio.TimeoutError,
                aiohttp.ClientError, socket.gaierror) as error:
            _LOGGER.error('Error fetching data from Yandex.Weather, %s', error)

    @property
    def forecast(self):
        """Return forecast"""
        return self._forecast

    @property
    def current(self):
        """Return curent condition"""
        return self._current

В конфиге добавьте следующее:

weather:
  - platform: yandex_weather
    api_key: <Ваш API ключа>

У меня компонент работает уже пару месяцев. Тестировал только с бесплатным ключом. Предоставляется AS IS.

Стандартная карточка выглядит неочень мягко говоря для прогноза на два периода. По этому лучше использовать кастомную карточку:

https://github.com/kalkih/simple-weather-card

Выглядеть будет вот так:


Мы в Telegram - t.me/soprut
  1. Дмитрий Батюшин (ReD)
    Дмитрий Батюшин (ReD) 12 дней назад

    Отлично! Добавить еще бы скриншотик как выглядит в НА

  2. (lapatoc)
    (lapatoc) 12 дней назад

    Выглядит со стандартной карточкой убого. Использую кастомную - simple weather card 

    https://github.com/kalkih/simp...

    1000x_image.png?1555066655
  3. Вячеслав Фетисов (Ziracul)

    А где брать API ?

  4. (lapatoc)
    (lapatoc) 12 дней назад

    Тестовый ключ работает месяц, потом нужно перейти на тариф "погода на вашем сайте" 

  5. Дмитрий kontur (kontur)
    Дмитрий kontur (kontur) отредактировано 12 дней назад

    Плагин погоды:  https://github.com/naofireblade/homebridge-weather-plus

    В Homekit отображает текущую температуру и можно добавить прогрозы на несколько дней. В Eve отображает еще кучу параметров и рисует графики.

  6. (Mioze)
    (Mioze) отредактировано 10 дней назад

    Error fetching data from Yandex.Weather, 403, Forbidden

    к API сразу доступ выдает, или ждать надо? Не работает чет

    • (lapatoc)
      (lapatoc) 10 дней назад

      Надо ждать. До суток могут включать.

      • (Mioze)
        (Mioze) отредактировано 10 дней назад

        api вроде работает

        1000x_image.png?1555268505

        но ошибка так и есть 

        2019-04-14 21:06:35 ERROR (MainThread) [homeassistant.core] Error doing job: Task exception was never retrieved Traceback (most recent call last): File "/usr/local/lib/python3.7/site-packages/homeassistant/helpers/entity.py", line 225, in async_update_ha_state self._async_write_ha_state() File "/usr/local/lib/python3.7/site-packages/homeassistant/helpers/entity.py", line 248, in _async_write_ha_state state = self.state File "/usr/local/lib/python3.7/site-packages/homeassistant/components/weather/__init__.py", line 173, in state return self.condition File "/config/custom_components/yandex_weather/weather.py", line 168, in condition if self._weather_data.current['condition'] in v), None) File "/config/custom_components/yandex_weather/weather.py", line 168, in if self._weather_data.current['condition'] in v), None) KeyError: 'condition'

        • (rohas)
          (rohas) 6 дней назад

          Тоже уже 3-й день 

          Error fetching data from Yandex.Weather, 403, Forbidden

          :( Чет не получается докрутить....

  7. Артем Виноделов (martiniman)
    Артем Виноделов (martiniman) отредактировано 7 дней назад

    Будьте добры, для особо одаренных, покажите запрос с ключом в одну строку (или через curl).

    Попробую распарсить в openHAB.

  8. (Stant)
    (Stant) 6 дней назад

    Мне больше зашла карточка type: weather-forecast, красивая и больше данный показывает

К списку блогов

Похожие записи

13 февр. 2019 г.
Подключение радио на Xiaomi Gateway к Home Assistant c дальнейшим использованием в автоматизациях
7 февр. 2019 г.
Самое важное из апдейта Home Assistant 0.87 от 06.02.19.
19 апр. 2019 г.
Требуется ваше мнение!
6 февр. 2019 г.
Автоматизированное открытие/закрытие окна
21 мар. 2019 г.
Самое важное из апдейта Home Assistant 0.9 от 20.03.19.
21 февр. 2019 г.
Самое важное из апдейта Home Assistant 0.88 от 20.02.19.
4 апр. 2019 г.
Самое важное из апдейта Home Assistant 0.91-0.91.4 от 03.04.19.
19 янв. 2019 г.
Делюсь новостями по проекту.
27 янв. 2019 г.
Избавляемся от wi-fi реле на ESP в пользу zigbee без прошивок и танцев с бубном.
13 мар. 2019 г.
Самое важное из апдейта Home Assistant 0.89 от 06.03.19.