mirror of https://github.com/home-assistant/core
259 lines
8.7 KiB
Python
259 lines
8.7 KiB
Python
"""Weather entity for Apple WeatherKit integration."""
|
|
|
|
from typing import Any, cast
|
|
|
|
from apple_weatherkit import DataSetType
|
|
|
|
from homeassistant.components.weather import (
|
|
ATTR_CONDITION_CLOUDY,
|
|
ATTR_CONDITION_EXCEPTIONAL,
|
|
ATTR_CONDITION_FOG,
|
|
ATTR_CONDITION_HAIL,
|
|
ATTR_CONDITION_LIGHTNING,
|
|
ATTR_CONDITION_PARTLYCLOUDY,
|
|
ATTR_CONDITION_POURING,
|
|
ATTR_CONDITION_RAINY,
|
|
ATTR_CONDITION_SNOWY,
|
|
ATTR_CONDITION_SNOWY_RAINY,
|
|
ATTR_CONDITION_SUNNY,
|
|
ATTR_CONDITION_WINDY,
|
|
Forecast,
|
|
SingleCoordinatorWeatherEntity,
|
|
WeatherEntityFeature,
|
|
)
|
|
from homeassistant.config_entries import ConfigEntry
|
|
from homeassistant.const import (
|
|
UnitOfLength,
|
|
UnitOfPressure,
|
|
UnitOfSpeed,
|
|
UnitOfTemperature,
|
|
)
|
|
from homeassistant.core import HomeAssistant, callback
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
|
|
from .const import (
|
|
ATTR_CURRENT_WEATHER,
|
|
ATTR_FORECAST_DAILY,
|
|
ATTR_FORECAST_HOURLY,
|
|
ATTRIBUTION,
|
|
DOMAIN,
|
|
)
|
|
from .coordinator import WeatherKitDataUpdateCoordinator
|
|
from .entity import WeatherKitEntity
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
config_entry: ConfigEntry,
|
|
async_add_entities: AddEntitiesCallback,
|
|
) -> None:
|
|
"""Add a weather entity from a config_entry."""
|
|
coordinator: WeatherKitDataUpdateCoordinator = hass.data[DOMAIN][
|
|
config_entry.entry_id
|
|
]
|
|
|
|
async_add_entities([WeatherKitWeather(coordinator)])
|
|
|
|
|
|
condition_code_to_hass = {
|
|
"BlowingDust": ATTR_CONDITION_WINDY,
|
|
"Clear": ATTR_CONDITION_SUNNY,
|
|
"Cloudy": ATTR_CONDITION_CLOUDY,
|
|
"Foggy": ATTR_CONDITION_FOG,
|
|
"Haze": ATTR_CONDITION_FOG,
|
|
"MostlyClear": ATTR_CONDITION_SUNNY,
|
|
"MostlyCloudy": ATTR_CONDITION_CLOUDY,
|
|
"PartlyCloudy": ATTR_CONDITION_PARTLYCLOUDY,
|
|
"Smoky": ATTR_CONDITION_FOG,
|
|
"Breezy": ATTR_CONDITION_WINDY,
|
|
"Windy": ATTR_CONDITION_WINDY,
|
|
"Drizzle": ATTR_CONDITION_RAINY,
|
|
"HeavyRain": ATTR_CONDITION_POURING,
|
|
"IsolatedThunderstorms": ATTR_CONDITION_LIGHTNING,
|
|
"Rain": ATTR_CONDITION_RAINY,
|
|
"SunShowers": ATTR_CONDITION_RAINY,
|
|
"ScatteredThunderstorms": ATTR_CONDITION_LIGHTNING,
|
|
"StrongStorms": ATTR_CONDITION_LIGHTNING,
|
|
"Thunderstorms": ATTR_CONDITION_LIGHTNING,
|
|
"Frigid": ATTR_CONDITION_SNOWY,
|
|
"Hail": ATTR_CONDITION_HAIL,
|
|
"Hot": ATTR_CONDITION_SUNNY,
|
|
"Flurries": ATTR_CONDITION_SNOWY,
|
|
"Sleet": ATTR_CONDITION_SNOWY,
|
|
"Snow": ATTR_CONDITION_SNOWY,
|
|
"SunFlurries": ATTR_CONDITION_SNOWY,
|
|
"WintryMix": ATTR_CONDITION_SNOWY,
|
|
"Blizzard": ATTR_CONDITION_SNOWY,
|
|
"BlowingSnow": ATTR_CONDITION_SNOWY,
|
|
"FreezingDrizzle": ATTR_CONDITION_SNOWY_RAINY,
|
|
"FreezingRain": ATTR_CONDITION_SNOWY_RAINY,
|
|
"HeavySnow": ATTR_CONDITION_SNOWY,
|
|
"Hurricane": ATTR_CONDITION_EXCEPTIONAL,
|
|
"TropicalStorm": ATTR_CONDITION_EXCEPTIONAL,
|
|
}
|
|
|
|
|
|
def _map_daily_forecast(forecast: dict[str, Any]) -> Forecast:
|
|
return {
|
|
"datetime": forecast["forecastStart"],
|
|
"condition": condition_code_to_hass[forecast["conditionCode"]],
|
|
"native_temperature": forecast["temperatureMax"],
|
|
"native_templow": forecast["temperatureMin"],
|
|
"native_precipitation": forecast["precipitationAmount"],
|
|
"precipitation_probability": forecast["precipitationChance"] * 100,
|
|
"uv_index": forecast["maxUvIndex"],
|
|
}
|
|
|
|
|
|
def _map_hourly_forecast(forecast: dict[str, Any]) -> Forecast:
|
|
return {
|
|
"datetime": forecast["forecastStart"],
|
|
"condition": condition_code_to_hass[forecast["conditionCode"]],
|
|
"native_temperature": forecast["temperature"],
|
|
"native_apparent_temperature": forecast["temperatureApparent"],
|
|
"native_dew_point": forecast.get("temperatureDewPoint"),
|
|
"native_pressure": forecast["pressure"],
|
|
"native_wind_gust_speed": forecast.get("windGust"),
|
|
"native_wind_speed": forecast["windSpeed"],
|
|
"wind_bearing": forecast.get("windDirection"),
|
|
"humidity": forecast["humidity"] * 100,
|
|
"native_precipitation": forecast.get("precipitationAmount"),
|
|
"precipitation_probability": forecast["precipitationChance"] * 100,
|
|
"cloud_coverage": forecast["cloudCover"] * 100,
|
|
"uv_index": forecast["uvIndex"],
|
|
}
|
|
|
|
|
|
class WeatherKitWeather(
|
|
SingleCoordinatorWeatherEntity[WeatherKitDataUpdateCoordinator], WeatherKitEntity
|
|
):
|
|
"""Weather entity for Apple WeatherKit integration."""
|
|
|
|
_attr_attribution = ATTRIBUTION
|
|
|
|
_attr_name = None
|
|
|
|
_attr_native_temperature_unit = UnitOfTemperature.CELSIUS
|
|
_attr_native_pressure_unit = UnitOfPressure.MBAR
|
|
_attr_native_visibility_unit = UnitOfLength.KILOMETERS
|
|
_attr_native_wind_speed_unit = UnitOfSpeed.KILOMETERS_PER_HOUR
|
|
_attr_native_precipitation_unit = UnitOfLength.MILLIMETERS
|
|
|
|
def __init__(
|
|
self,
|
|
coordinator: WeatherKitDataUpdateCoordinator,
|
|
) -> None:
|
|
"""Initialize the platform with a coordinator."""
|
|
super().__init__(coordinator)
|
|
WeatherKitEntity.__init__(self, coordinator, unique_id_suffix=None)
|
|
|
|
@property
|
|
def supported_features(self) -> WeatherEntityFeature:
|
|
"""Determine supported features based on available data sets reported by WeatherKit."""
|
|
features = WeatherEntityFeature(0)
|
|
|
|
if not self.coordinator.supported_data_sets:
|
|
return features
|
|
|
|
if DataSetType.DAILY_FORECAST in self.coordinator.supported_data_sets:
|
|
features |= WeatherEntityFeature.FORECAST_DAILY
|
|
if DataSetType.HOURLY_FORECAST in self.coordinator.supported_data_sets:
|
|
features |= WeatherEntityFeature.FORECAST_HOURLY
|
|
return features
|
|
|
|
@property
|
|
def data(self) -> dict[str, Any]:
|
|
"""Return coordinator data."""
|
|
return self.coordinator.data
|
|
|
|
@property
|
|
def current_weather(self) -> dict[str, Any]:
|
|
"""Return current weather data."""
|
|
return self.data[ATTR_CURRENT_WEATHER]
|
|
|
|
@property
|
|
def condition(self) -> str | None:
|
|
"""Return the current condition."""
|
|
condition_code = cast(str, self.current_weather.get("conditionCode"))
|
|
condition = condition_code_to_hass[condition_code]
|
|
|
|
if condition == "sunny" and self.current_weather.get("daylight") is False:
|
|
condition = "clear-night"
|
|
|
|
return condition
|
|
|
|
@property
|
|
def native_temperature(self) -> float | None:
|
|
"""Return the current temperature."""
|
|
return self.current_weather.get("temperature")
|
|
|
|
@property
|
|
def native_apparent_temperature(self) -> float | None:
|
|
"""Return the current apparent_temperature."""
|
|
return self.current_weather.get("temperatureApparent")
|
|
|
|
@property
|
|
def native_dew_point(self) -> float | None:
|
|
"""Return the current dew_point."""
|
|
return self.current_weather.get("temperatureDewPoint")
|
|
|
|
@property
|
|
def native_pressure(self) -> float | None:
|
|
"""Return the current pressure."""
|
|
return self.current_weather.get("pressure")
|
|
|
|
@property
|
|
def humidity(self) -> float | None:
|
|
"""Return the current humidity."""
|
|
return cast(float, self.current_weather.get("humidity")) * 100
|
|
|
|
@property
|
|
def cloud_coverage(self) -> float | None:
|
|
"""Return the current cloud_coverage."""
|
|
return cast(float, self.current_weather.get("cloudCover")) * 100
|
|
|
|
@property
|
|
def uv_index(self) -> float | None:
|
|
"""Return the current uv_index."""
|
|
return self.current_weather.get("uvIndex")
|
|
|
|
@property
|
|
def native_visibility(self) -> float | None:
|
|
"""Return the current visibility."""
|
|
return cast(float, self.current_weather.get("visibility")) / 1000
|
|
|
|
@property
|
|
def native_wind_gust_speed(self) -> float | None:
|
|
"""Return the current wind_gust_speed."""
|
|
return self.current_weather.get("windGust")
|
|
|
|
@property
|
|
def native_wind_speed(self) -> float | None:
|
|
"""Return the current wind_speed."""
|
|
return self.current_weather.get("windSpeed")
|
|
|
|
@property
|
|
def wind_bearing(self) -> float | None:
|
|
"""Return the current wind_bearing."""
|
|
return self.current_weather.get("windDirection")
|
|
|
|
@callback
|
|
def _async_forecast_daily(self) -> list[Forecast] | None:
|
|
"""Return the daily forecast."""
|
|
daily_forecast = self.data.get(ATTR_FORECAST_DAILY)
|
|
if not daily_forecast:
|
|
return None
|
|
|
|
forecast = daily_forecast.get("days")
|
|
return [_map_daily_forecast(f) for f in forecast]
|
|
|
|
@callback
|
|
def _async_forecast_hourly(self) -> list[Forecast] | None:
|
|
"""Return the hourly forecast."""
|
|
hourly_forecast = self.data.get(ATTR_FORECAST_HOURLY)
|
|
if not hourly_forecast:
|
|
return None
|
|
|
|
forecast = hourly_forecast.get("hours")
|
|
return [_map_hourly_forecast(f) for f in forecast]
|