262 lines
6.8 KiB
Python
262 lines
6.8 KiB
Python
"""General constants."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import base64
|
|
import logging
|
|
import pickle
|
|
import zlib
|
|
|
|
from collections.abc import Callable
|
|
from datetime import datetime
|
|
|
|
from homeassistant.components.weather import (
|
|
ATTR_WEATHER_TEMPERATURE_UNIT,
|
|
ATTR_WEATHER_WIND_SPEED_UNIT,
|
|
UNIT_CONVERSIONS,
|
|
)
|
|
from homeassistant.const import Platform
|
|
from homeassistant.helpers import selector
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
DOMAIN = "yandex_pogoda"
|
|
DEFAULT_NAME = "Yandex Pogoda"
|
|
ATTRIBUTION = "Data provided by Yandex Pogoda"
|
|
MANUFACTURER = "Yandex"
|
|
ENTRY_NAME = "name"
|
|
UPDATER = "updater"
|
|
|
|
ATTR_API_TEMPERATURE = "temperature"
|
|
ATTR_API_TEMPERATURE_MAX = "maxTemperature"
|
|
ATTR_API_TEMPERATURE_MIN = "minTemperature"
|
|
ATTR_API_FEELS_LIKE_TEMPERATURE = "feelsLike"
|
|
ATTR_API_WIND_SPEED = "windSpeed"
|
|
ATTR_API_WIND_BEARING = "windAngle"
|
|
ATTR_API_DAYTIME = "daytime"
|
|
ATTR_API_CONDITION = "condition"
|
|
ATTR_API_IMAGE = "icon"
|
|
ATTR_API_POLAR = "polar"
|
|
ATTR_API_SERVER_TIME = "serverTime"
|
|
ATTR_API_TIME = "time"
|
|
ATTR_API_SUNRISE_BEGIN_TIME = "sunriseTime"
|
|
ATTR_API_SUNRISE_END_TIME = "sunsetTime"
|
|
ATTR_API_YA_CONDITION = "yandex_condition"
|
|
ATTR_API_WIND_GUST = "windGust"
|
|
|
|
ATTR_FORECAST_DATA = "forecast"
|
|
ATTR_FORECAST_DATA_COMPRESSED = "forecast_compressed"
|
|
ATTR_FORECAST_HOURLY = "hourly"
|
|
ATTR_FORECAST_HOURLY_ICONS = "forecast_hourly_icons"
|
|
ATTR_FORECAST_TWICE_DAILY = "twice_daily"
|
|
ATTR_FORECAST_TWICE_DAILY_ICONS = "forecast_twice_daily_icons"
|
|
|
|
ATTR_MIN_FORECAST_TEMPERATURE = "min_forecast_temperature"
|
|
ATTR_WIND_INTERCARDINAL_DIRECTION = "wind_intercardinal"
|
|
|
|
UPDATE_LISTENER = "update_listener"
|
|
PLATFORMS = [Platform.SENSOR, Platform.WEATHER]
|
|
|
|
YA_CONDITIONS = [
|
|
"CLEAR",
|
|
"PARTLY_CLOUDY",
|
|
"CLOUDY",
|
|
"OVERCAST",
|
|
"LIGHT_RAIN",
|
|
"RAIN",
|
|
"HEAVY_RAIN",
|
|
"SHOWERS",
|
|
"SLEET",
|
|
"LIGHT_SNOW",
|
|
"SNOW",
|
|
"SNOWFALL",
|
|
"HAIL",
|
|
"THUNDERSTORM",
|
|
"THUNDERSTORM_WITH_RAIN",
|
|
"THUNDERSTORM_WITH_HAIL",
|
|
]
|
|
YA_CONDITION_STATE_MAP = {i: i.lower() for i in YA_CONDITIONS}
|
|
"""Mapping for yandex states"""
|
|
|
|
YA_CONDITION_STATES = list(YA_CONDITION_STATE_MAP.values())
|
|
|
|
WEATHER_STATES_CONVERSION = {
|
|
"CLEAR": {
|
|
"day": "sunny",
|
|
"night": "clear-night",
|
|
},
|
|
"PARTLY_CLOUDY": "partlycloudy",
|
|
"CLOUDY": "cloudy",
|
|
"OVERCAST": "cloudy",
|
|
"LIGHT_RAIN": "rainy",
|
|
"RAIN": "rainy",
|
|
"HEAVY_RAIN": "pouring",
|
|
"SHOWERS": "pouring",
|
|
"SLEET": "snowy-rainy",
|
|
"LIGHT_SNOW": "snowy",
|
|
"SNOW": "snowy",
|
|
"SNOWFALL": "snowy",
|
|
"HAIL": "hail",
|
|
"THUNDERSTORM": "lightning",
|
|
"THUNDERSTORM_WITH_RAIN": "lightning-rainy",
|
|
"THUNDERSTORM_WITH_HAIL": "lightning-rainy",
|
|
}
|
|
"""Map rich Yandex weather condition to ordinary HA"""
|
|
|
|
HA_WEATHER_STATES = [
|
|
i for i in WEATHER_STATES_CONVERSION.values() if isinstance(i, str)
|
|
] + list(WEATHER_STATES_CONVERSION["CLEAR"].values())
|
|
|
|
CONDITION_ICONS = {
|
|
"CLEAR": {
|
|
"day": "mdi:weather-sunny",
|
|
"night": "mdi:weather-night",
|
|
},
|
|
"PARTLY_CLOUDY": {
|
|
"day": "mdi:weather-partly-cloudy",
|
|
"night": "mdi:weather-night-partly-cloudy",
|
|
},
|
|
"CLOUDY": "mdi:weather-cloudy",
|
|
"OVERCAST": "mdi:weather-cloudy",
|
|
"LIGHT_RAIN": "mdi:weather-rainy",
|
|
"RAIN": "mdi:weather-rainy",
|
|
"HEAVY_RAIN": "mdi:weather-pouring",
|
|
"SHOWERS": "mdi:weather-pouring",
|
|
"SLEET": "mdi:weather-snowy-rainy",
|
|
"LIGHT_SNOW": "mdi:weather-snowy",
|
|
"SNOW": "mdi:weather-snowy",
|
|
"SNOWFALL": "mdi:weather-snowy-heavy",
|
|
"HAIL": "mdi:weather-hail",
|
|
"THUNDERSTORM": "mdi:weather-lightning",
|
|
"THUNDERSTORM_WITH_RAIN": "mdi:weather-lightning-rainy",
|
|
"THUNDERSTORM_WITH_HAIL": "mdi:weather-lightning-rainy",
|
|
}
|
|
"""Mapping for state icon"""
|
|
|
|
|
|
TEMPERATURE_CONVERTER = UNIT_CONVERSIONS[ATTR_WEATHER_TEMPERATURE_UNIT]
|
|
WIND_SPEED_CONVERTER = UNIT_CONVERSIONS[ATTR_WEATHER_WIND_SPEED_UNIT]
|
|
|
|
DEFAULT_UPDATES_PER_DAY = "standard"
|
|
UPDATES_PER_DAY_MAP = {
|
|
"rare": 6,
|
|
"standard": 12,
|
|
"often": 24,
|
|
}
|
|
UPDATES_PER_DAY = "updates_per_day"
|
|
UPDATES_PER_DAY_SELECTOR = selector.SelectSelector(
|
|
selector.SelectSelectorConfig(
|
|
options=["rare", "standard", "often"],
|
|
mode=selector.SelectSelectorMode.DROPDOWN,
|
|
translation_key=UPDATES_PER_DAY,
|
|
)
|
|
)
|
|
|
|
|
|
def convert_unit_value(
|
|
converter: Callable[[float, str, str], float],
|
|
val: float | None,
|
|
unit_from: str | None,
|
|
unit_to: str | None,
|
|
) -> float | None:
|
|
"""Weather factor unit converter."""
|
|
if val is not None and unit_from and unit_to:
|
|
return converter(val, unit_from, unit_to)
|
|
return None
|
|
|
|
|
|
def map_state(src: str, is_day: bool, mapping: dict | None = None) -> str:
|
|
"""Map weather condition based on WEATHER_STATES_CONVERSION.
|
|
|
|
:param src: str: Yandex weather state
|
|
:param is_day: bool: Is it day? Used for icons
|
|
:param mapping: use this dict for mapping
|
|
:return: str: Home Assistant weather state
|
|
"""
|
|
if mapping is None:
|
|
mapping = {}
|
|
|
|
try:
|
|
result: str | dict[str, str] = mapping[src]
|
|
except KeyError:
|
|
result = src
|
|
|
|
if type(result) is dict:
|
|
return result["day" if is_day else "night"]
|
|
|
|
return result
|
|
|
|
|
|
def get_wind_intercardinal_direction(wind_direction_degree: int) -> str:
|
|
"""Convert wind direction degree to named direction."""
|
|
|
|
if wind_direction_degree < 0 or wind_direction_degree > 360:
|
|
_LOGGER.error("Angle should be in [0, 360] range")
|
|
|
|
if wind_direction_degree <= 22.5:
|
|
return "n"
|
|
|
|
if wind_direction_degree <= 67.5:
|
|
return "ne"
|
|
|
|
if wind_direction_degree <= 112.5:
|
|
return "e"
|
|
|
|
if wind_direction_degree <= 157.5:
|
|
return "se"
|
|
|
|
if wind_direction_degree <= 202.5:
|
|
return "s"
|
|
|
|
if wind_direction_degree <= 247.5:
|
|
return "sw"
|
|
|
|
if wind_direction_degree <= 292.5:
|
|
return "w"
|
|
|
|
if wind_direction_degree <= 337.5:
|
|
return "nw"
|
|
|
|
return "n"
|
|
|
|
|
|
def compress_data(data: dict) -> str:
|
|
"""Compress dict to string."""
|
|
return base64.b64encode(zlib.compress(pickle.dumps(data))).decode("utf-8")
|
|
|
|
|
|
def decompress_data(compressed: str | None) -> dict:
|
|
"""Compress dict to string."""
|
|
if not compressed:
|
|
return {}
|
|
|
|
try:
|
|
return pickle.loads(zlib.decompress(base64.b64decode(compressed)))
|
|
except TypeError: # for backward compatibility
|
|
return {}
|
|
|
|
|
|
def is_daytime(
|
|
hour: datetime,
|
|
day: datetime,
|
|
sunrise_begin: datetime,
|
|
sunset_end: datetime,
|
|
polar: str,
|
|
) -> bool:
|
|
"""returns whether it is daytime for a given hour of the day"""
|
|
|
|
if polar:
|
|
if day == sunrise_begin and day == sunset_end:
|
|
return polar == "DAY"
|
|
|
|
if day == sunrise_begin:
|
|
return hour < sunset_end
|
|
|
|
if day == sunset_end:
|
|
return hour >= sunrise_begin
|
|
|
|
if sunrise_begin <= sunset_end:
|
|
return hour >= sunrise_begin and hour < sunset_end
|
|
else:
|
|
return hour < sunset_end or hour >= sunrise_begin
|