core/homeassistant/components/weather/__init__.py

239 lines
7.4 KiB
Python

"""Weather component that handles meteorological data for your location."""
from __future__ import annotations
from dataclasses import dataclass
from datetime import timedelta
import logging
from typing import Final, TypedDict, final
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PRECISION_TENTHS, PRECISION_WHOLE, TEMP_CELSIUS
from homeassistant.core import HomeAssistant
from homeassistant.helpers.config_validation import ( # noqa: F401
PLATFORM_SCHEMA,
PLATFORM_SCHEMA_BASE,
)
from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.temperature import display_temp as show_temp
# mypy: allow-untyped-defs, no-check-untyped-defs
_LOGGER = logging.getLogger(__name__)
ATTR_CONDITION_CLASS = "condition_class"
ATTR_CONDITION_CLEAR_NIGHT = "clear-night"
ATTR_CONDITION_CLOUDY = "cloudy"
ATTR_CONDITION_EXCEPTIONAL = "exceptional"
ATTR_CONDITION_FOG = "fog"
ATTR_CONDITION_HAIL = "hail"
ATTR_CONDITION_LIGHTNING = "lightning"
ATTR_CONDITION_LIGHTNING_RAINY = "lightning-rainy"
ATTR_CONDITION_PARTLYCLOUDY = "partlycloudy"
ATTR_CONDITION_POURING = "pouring"
ATTR_CONDITION_RAINY = "rainy"
ATTR_CONDITION_SNOWY = "snowy"
ATTR_CONDITION_SNOWY_RAINY = "snowy-rainy"
ATTR_CONDITION_SUNNY = "sunny"
ATTR_CONDITION_WINDY = "windy"
ATTR_CONDITION_WINDY_VARIANT = "windy-variant"
ATTR_FORECAST = "forecast"
ATTR_FORECAST_CONDITION: Final = "condition"
ATTR_FORECAST_PRECIPITATION: Final = "precipitation"
ATTR_FORECAST_PRECIPITATION_PROBABILITY: Final = "precipitation_probability"
ATTR_FORECAST_PRESSURE: Final = "pressure"
ATTR_FORECAST_TEMP: Final = "temperature"
ATTR_FORECAST_TEMP_LOW: Final = "templow"
ATTR_FORECAST_TIME: Final = "datetime"
ATTR_FORECAST_WIND_BEARING: Final = "wind_bearing"
ATTR_FORECAST_WIND_SPEED: Final = "wind_speed"
ATTR_WEATHER_HUMIDITY = "humidity"
ATTR_WEATHER_OZONE = "ozone"
ATTR_WEATHER_PRESSURE = "pressure"
ATTR_WEATHER_TEMPERATURE = "temperature"
ATTR_WEATHER_VISIBILITY = "visibility"
ATTR_WEATHER_WIND_BEARING = "wind_bearing"
ATTR_WEATHER_WIND_SPEED = "wind_speed"
DOMAIN = "weather"
ENTITY_ID_FORMAT = DOMAIN + ".{}"
SCAN_INTERVAL = timedelta(seconds=30)
class Forecast(TypedDict, total=False):
"""Typed weather forecast dict."""
condition: str | None
datetime: str
precipitation_probability: int | None
precipitation: float | None
pressure: float | None
temperature: float | None
templow: float | None
wind_bearing: float | str | None
wind_speed: float | None
async def async_setup(hass, config):
"""Set up the weather component."""
component = hass.data[DOMAIN] = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL
)
await component.async_setup(config)
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up a config entry."""
component: EntityComponent = hass.data[DOMAIN]
return await component.async_setup_entry(entry)
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
component: EntityComponent = hass.data[DOMAIN]
return await component.async_unload_entry(entry)
@dataclass
class WeatherEntityDescription(EntityDescription):
"""A class that describes weather entities."""
class WeatherEntity(Entity):
"""ABC for weather data."""
entity_description: WeatherEntityDescription
_attr_condition: str | None
_attr_forecast: list[Forecast] | None = None
_attr_humidity: float | None = None
_attr_ozone: float | None = None
_attr_precision: float
_attr_pressure: float | None = None
_attr_state: None = None
_attr_temperature_unit: str
_attr_temperature: float | None
_attr_visibility: float | None = None
_attr_wind_bearing: float | str | None = None
_attr_wind_speed: float | None = None
@property
def temperature(self) -> float | None:
"""Return the platform temperature."""
return self._attr_temperature
@property
def temperature_unit(self) -> str:
"""Return the unit of measurement."""
return self._attr_temperature_unit
@property
def pressure(self) -> float | None:
"""Return the pressure."""
return self._attr_pressure
@property
def humidity(self) -> float | None:
"""Return the humidity."""
return self._attr_humidity
@property
def wind_speed(self) -> float | None:
"""Return the wind speed."""
return self._attr_wind_speed
@property
def wind_bearing(self) -> float | str | None:
"""Return the wind bearing."""
return self._attr_wind_bearing
@property
def ozone(self) -> float | None:
"""Return the ozone level."""
return self._attr_ozone
@property
def visibility(self) -> float | None:
"""Return the visibility."""
return self._attr_visibility
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast."""
return self._attr_forecast
@property
def precision(self) -> float:
"""Return the precision of the temperature value."""
if hasattr(self, "_attr_precision"):
return self._attr_precision
return (
PRECISION_TENTHS
if self.temperature_unit == TEMP_CELSIUS
else PRECISION_WHOLE
)
@final
@property
def state_attributes(self):
"""Return the state attributes."""
data = {}
if self.temperature is not None:
data[ATTR_WEATHER_TEMPERATURE] = show_temp(
self.hass, self.temperature, self.temperature_unit, self.precision
)
if (humidity := self.humidity) is not None:
data[ATTR_WEATHER_HUMIDITY] = round(humidity)
if (ozone := self.ozone) is not None:
data[ATTR_WEATHER_OZONE] = ozone
if (pressure := self.pressure) is not None:
data[ATTR_WEATHER_PRESSURE] = pressure
if (wind_bearing := self.wind_bearing) is not None:
data[ATTR_WEATHER_WIND_BEARING] = wind_bearing
if (wind_speed := self.wind_speed) is not None:
data[ATTR_WEATHER_WIND_SPEED] = wind_speed
if (visibility := self.visibility) is not None:
data[ATTR_WEATHER_VISIBILITY] = visibility
if self.forecast is not None:
forecast = []
for forecast_entry in self.forecast:
forecast_entry = dict(forecast_entry)
forecast_entry[ATTR_FORECAST_TEMP] = show_temp(
self.hass,
forecast_entry[ATTR_FORECAST_TEMP],
self.temperature_unit,
self.precision,
)
if ATTR_FORECAST_TEMP_LOW in forecast_entry:
forecast_entry[ATTR_FORECAST_TEMP_LOW] = show_temp(
self.hass,
forecast_entry[ATTR_FORECAST_TEMP_LOW],
self.temperature_unit,
self.precision,
)
forecast.append(forecast_entry)
data[ATTR_FORECAST] = forecast
return data
@property
@final
def state(self) -> str | None:
"""Return the current state."""
return self.condition
@property
def condition(self) -> str | None:
"""Return the current condition."""
return self._attr_condition