mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 17:27:52 +00:00
Weather unit conversion (#73441)
Co-authored-by: Erik <erik@montnemery.com>
This commit is contained in:
parent
bccec77e19
commit
90e1fb6ce2
@ -27,7 +27,14 @@ from homeassistant.components.weather import (
|
||||
WeatherEntity,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||
from homeassistant.const import (
|
||||
PRESSURE_HPA,
|
||||
PRESSURE_INHG,
|
||||
SPEED_METERS_PER_SECOND,
|
||||
SPEED_MILES_PER_HOUR,
|
||||
TEMP_CELSIUS,
|
||||
TEMP_FAHRENHEIT,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
@ -77,6 +84,8 @@ def setup_platform(
|
||||
1099,
|
||||
0.5,
|
||||
TEMP_CELSIUS,
|
||||
PRESSURE_HPA,
|
||||
SPEED_METERS_PER_SECOND,
|
||||
[
|
||||
[ATTR_CONDITION_RAINY, 1, 22, 15, 60],
|
||||
[ATTR_CONDITION_RAINY, 5, 19, 8, 30],
|
||||
@ -95,6 +104,8 @@ def setup_platform(
|
||||
987,
|
||||
4.8,
|
||||
TEMP_FAHRENHEIT,
|
||||
PRESSURE_INHG,
|
||||
SPEED_MILES_PER_HOUR,
|
||||
[
|
||||
[ATTR_CONDITION_SNOWY, 2, -10, -15, 60],
|
||||
[ATTR_CONDITION_PARTLYCLOUDY, 1, -13, -14, 25],
|
||||
@ -121,16 +132,20 @@ class DemoWeather(WeatherEntity):
|
||||
pressure,
|
||||
wind_speed,
|
||||
temperature_unit,
|
||||
pressure_unit,
|
||||
wind_speed_unit,
|
||||
forecast,
|
||||
):
|
||||
"""Initialize the Demo weather."""
|
||||
self._name = name
|
||||
self._condition = condition
|
||||
self._temperature = temperature
|
||||
self._temperature_unit = temperature_unit
|
||||
self._native_temperature = temperature
|
||||
self._native_temperature_unit = temperature_unit
|
||||
self._humidity = humidity
|
||||
self._pressure = pressure
|
||||
self._wind_speed = wind_speed
|
||||
self._native_pressure = pressure
|
||||
self._native_pressure_unit = pressure_unit
|
||||
self._native_wind_speed = wind_speed
|
||||
self._native_wind_speed_unit = wind_speed_unit
|
||||
self._forecast = forecast
|
||||
|
||||
@property
|
||||
@ -144,14 +159,14 @@ class DemoWeather(WeatherEntity):
|
||||
return False
|
||||
|
||||
@property
|
||||
def temperature(self):
|
||||
def native_temperature(self):
|
||||
"""Return the temperature."""
|
||||
return self._temperature
|
||||
return self._native_temperature
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
def native_temperature_unit(self):
|
||||
"""Return the unit of measurement."""
|
||||
return self._temperature_unit
|
||||
return self._native_temperature_unit
|
||||
|
||||
@property
|
||||
def humidity(self):
|
||||
@ -159,14 +174,24 @@ class DemoWeather(WeatherEntity):
|
||||
return self._humidity
|
||||
|
||||
@property
|
||||
def wind_speed(self):
|
||||
def native_wind_speed(self):
|
||||
"""Return the wind speed."""
|
||||
return self._wind_speed
|
||||
return self._native_wind_speed
|
||||
|
||||
@property
|
||||
def pressure(self):
|
||||
def native_wind_speed_unit(self):
|
||||
"""Return the wind speed."""
|
||||
return self._native_wind_speed_unit
|
||||
|
||||
@property
|
||||
def native_pressure(self):
|
||||
"""Return the pressure."""
|
||||
return self._pressure
|
||||
return self._native_pressure
|
||||
|
||||
@property
|
||||
def native_pressure_unit(self):
|
||||
"""Return the pressure."""
|
||||
return self._native_pressure_unit
|
||||
|
||||
@property
|
||||
def condition(self):
|
||||
|
@ -1,22 +1,47 @@
|
||||
"""Weather component that handles meteorological data for your location."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from contextlib import suppress
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
import inspect
|
||||
import logging
|
||||
from typing import Final, TypedDict, final
|
||||
from typing import Any, 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.const import (
|
||||
LENGTH_INCHES,
|
||||
LENGTH_KILOMETERS,
|
||||
LENGTH_MILES,
|
||||
LENGTH_MILLIMETERS,
|
||||
PRECISION_HALVES,
|
||||
PRECISION_TENTHS,
|
||||
PRECISION_WHOLE,
|
||||
PRESSURE_HPA,
|
||||
PRESSURE_INHG,
|
||||
PRESSURE_MBAR,
|
||||
PRESSURE_MMHG,
|
||||
SPEED_KILOMETERS_PER_HOUR,
|
||||
SPEED_METERS_PER_SECOND,
|
||||
SPEED_MILES_PER_HOUR,
|
||||
TEMP_CELSIUS,
|
||||
TEMP_FAHRENHEIT,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
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
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.util import (
|
||||
distance as distance_util,
|
||||
pressure as pressure_util,
|
||||
speed as speed_util,
|
||||
temperature as temperature_util,
|
||||
)
|
||||
|
||||
# mypy: allow-untyped-defs, no-check-untyped-defs
|
||||
|
||||
@ -40,21 +65,31 @@ ATTR_CONDITION_WINDY = "windy"
|
||||
ATTR_CONDITION_WINDY_VARIANT = "windy-variant"
|
||||
ATTR_FORECAST = "forecast"
|
||||
ATTR_FORECAST_CONDITION: Final = "condition"
|
||||
ATTR_FORECAST_NATIVE_PRECIPITATION: Final = "native_precipitation"
|
||||
ATTR_FORECAST_PRECIPITATION: Final = "precipitation"
|
||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY: Final = "precipitation_probability"
|
||||
ATTR_FORECAST_NATIVE_PRESSURE: Final = "native_pressure"
|
||||
ATTR_FORECAST_PRESSURE: Final = "pressure"
|
||||
ATTR_FORECAST_NATIVE_TEMP: Final = "native_temperature"
|
||||
ATTR_FORECAST_TEMP: Final = "temperature"
|
||||
ATTR_FORECAST_NATIVE_TEMP_LOW: Final = "native_templow"
|
||||
ATTR_FORECAST_TEMP_LOW: Final = "templow"
|
||||
ATTR_FORECAST_TIME: Final = "datetime"
|
||||
ATTR_FORECAST_WIND_BEARING: Final = "wind_bearing"
|
||||
ATTR_FORECAST_NATIVE_WIND_SPEED: Final = "native_wind_speed"
|
||||
ATTR_FORECAST_WIND_SPEED: Final = "wind_speed"
|
||||
ATTR_WEATHER_HUMIDITY = "humidity"
|
||||
ATTR_WEATHER_OZONE = "ozone"
|
||||
ATTR_WEATHER_PRESSURE = "pressure"
|
||||
ATTR_WEATHER_PRESSURE_UNIT = "pressure_unit"
|
||||
ATTR_WEATHER_TEMPERATURE = "temperature"
|
||||
ATTR_WEATHER_TEMPERATURE_UNIT = "temperature_unit"
|
||||
ATTR_WEATHER_VISIBILITY = "visibility"
|
||||
ATTR_WEATHER_VISIBILITY_UNIT = "visibility_unit"
|
||||
ATTR_WEATHER_WIND_BEARING = "wind_bearing"
|
||||
ATTR_WEATHER_WIND_SPEED = "wind_speed"
|
||||
ATTR_WEATHER_WIND_SPEED_UNIT = "wind_speed_unit"
|
||||
ATTR_WEATHER_PRECIPITATION_UNIT = "precipitation_unit"
|
||||
|
||||
DOMAIN = "weather"
|
||||
|
||||
@ -64,18 +99,83 @@ SCAN_INTERVAL = timedelta(seconds=30)
|
||||
|
||||
ROUNDING_PRECISION = 2
|
||||
|
||||
VALID_UNITS_PRESSURE: tuple[str, ...] = (
|
||||
PRESSURE_HPA,
|
||||
PRESSURE_MBAR,
|
||||
PRESSURE_INHG,
|
||||
PRESSURE_MMHG,
|
||||
)
|
||||
VALID_UNITS_TEMPERATURE: tuple[str, ...] = (
|
||||
TEMP_CELSIUS,
|
||||
TEMP_FAHRENHEIT,
|
||||
)
|
||||
VALID_UNITS_PRECIPITATION: tuple[str, ...] = (
|
||||
LENGTH_MILLIMETERS,
|
||||
LENGTH_INCHES,
|
||||
)
|
||||
VALID_UNITS_VISIBILITY: tuple[str, ...] = (
|
||||
LENGTH_KILOMETERS,
|
||||
LENGTH_MILES,
|
||||
)
|
||||
VALID_UNITS_WIND_SPEED: tuple[str, ...] = (
|
||||
SPEED_METERS_PER_SECOND,
|
||||
SPEED_KILOMETERS_PER_HOUR,
|
||||
SPEED_MILES_PER_HOUR,
|
||||
)
|
||||
|
||||
UNIT_CONVERSIONS: dict[str, Callable[[float, str, str], float]] = {
|
||||
ATTR_WEATHER_PRESSURE_UNIT: pressure_util.convert,
|
||||
ATTR_WEATHER_TEMPERATURE_UNIT: temperature_util.convert,
|
||||
ATTR_WEATHER_VISIBILITY_UNIT: distance_util.convert,
|
||||
ATTR_WEATHER_PRECIPITATION_UNIT: distance_util.convert,
|
||||
ATTR_WEATHER_WIND_SPEED_UNIT: speed_util.convert,
|
||||
}
|
||||
|
||||
VALID_UNITS: dict[str, tuple[str, ...]] = {
|
||||
ATTR_WEATHER_PRESSURE_UNIT: VALID_UNITS_PRESSURE,
|
||||
ATTR_WEATHER_TEMPERATURE_UNIT: VALID_UNITS_TEMPERATURE,
|
||||
ATTR_WEATHER_VISIBILITY_UNIT: VALID_UNITS_VISIBILITY,
|
||||
ATTR_WEATHER_PRECIPITATION_UNIT: VALID_UNITS_PRECIPITATION,
|
||||
ATTR_WEATHER_WIND_SPEED_UNIT: VALID_UNITS_WIND_SPEED,
|
||||
}
|
||||
|
||||
|
||||
def round_temperature(temperature: float | None, precision: float) -> float | None:
|
||||
"""Convert temperature into preferred precision for display."""
|
||||
if temperature is None:
|
||||
return None
|
||||
|
||||
# Round in the units appropriate
|
||||
if precision == PRECISION_HALVES:
|
||||
temperature = round(temperature * 2) / 2.0
|
||||
elif precision == PRECISION_TENTHS:
|
||||
temperature = round(temperature, 1)
|
||||
# Integer as a fall back (PRECISION_WHOLE)
|
||||
else:
|
||||
temperature = round(temperature)
|
||||
|
||||
return temperature
|
||||
|
||||
|
||||
class Forecast(TypedDict, total=False):
|
||||
"""Typed weather forecast dict."""
|
||||
"""Typed weather forecast dict.
|
||||
|
||||
All attributes are in native units and old attributes kept for backwards compatibility.
|
||||
"""
|
||||
|
||||
condition: str | None
|
||||
datetime: str
|
||||
precipitation_probability: int | None
|
||||
native_precipitation: float | None
|
||||
precipitation: float | None
|
||||
native_pressure: float | None
|
||||
pressure: float | None
|
||||
native_temperature: float | None
|
||||
temperature: float | None
|
||||
native_templow: float | None
|
||||
templow: float | None
|
||||
wind_bearing: float | str | None
|
||||
native_wind_speed: float | None
|
||||
wind_speed: float | None
|
||||
|
||||
|
||||
@ -114,38 +214,219 @@ class WeatherEntity(Entity):
|
||||
_attr_humidity: float | None = None
|
||||
_attr_ozone: float | None = None
|
||||
_attr_precision: float
|
||||
_attr_pressure: float | None = None
|
||||
_attr_pressure_unit: str | None = None
|
||||
_attr_pressure: float | None = (
|
||||
None # Provide backwards compatibility. Use _attr_native_pressure
|
||||
)
|
||||
_attr_pressure_unit: str | None = (
|
||||
None # Provide backwards compatibility. Use _attr_native_pressure_unit
|
||||
)
|
||||
_attr_state: None = None
|
||||
_attr_temperature_unit: str
|
||||
_attr_temperature: float | None
|
||||
_attr_visibility: float | None = None
|
||||
_attr_visibility_unit: str | None = None
|
||||
_attr_precipitation_unit: str | None = None
|
||||
_attr_temperature: float | None = (
|
||||
None # Provide backwards compatibility. Use _attr_native_temperature
|
||||
)
|
||||
_attr_temperature_unit: str | None = (
|
||||
None # Provide backwards compatibility. Use _attr_native_temperature_unit
|
||||
)
|
||||
_attr_visibility: float | None = (
|
||||
None # Provide backwards compatibility. Use _attr_native_visibility
|
||||
)
|
||||
_attr_visibility_unit: str | None = (
|
||||
None # Provide backwards compatibility. Use _attr_native_visibility_unit
|
||||
)
|
||||
_attr_precipitation_unit: str | None = (
|
||||
None # Provide backwards compatibility. Use _attr_native_precipitation_unit
|
||||
)
|
||||
_attr_wind_bearing: float | str | None = None
|
||||
_attr_wind_speed: float | None = None
|
||||
_attr_wind_speed_unit: str | None = None
|
||||
_attr_wind_speed: float | None = (
|
||||
None # Provide backwards compatibility. Use _attr_native_wind_speed
|
||||
)
|
||||
_attr_wind_speed_unit: str | None = (
|
||||
None # Provide backwards compatibility. Use _attr_native_wind_speed_unit
|
||||
)
|
||||
|
||||
_attr_native_pressure: float | None = None
|
||||
_attr_native_pressure_unit: str | None = None
|
||||
_attr_native_temperature: float | None = None
|
||||
_attr_native_temperature_unit: str | None = None
|
||||
_attr_native_visibility: float | None = None
|
||||
_attr_native_visibility_unit: str | None = None
|
||||
_attr_native_precipitation_unit: str | None = None
|
||||
_attr_native_wind_speed: float | None = None
|
||||
_attr_native_wind_speed_unit: str | None = None
|
||||
|
||||
_weather_option_temperature_unit: str | None = None
|
||||
_weather_option_pressure_unit: str | None = None
|
||||
_weather_option_visibility_unit: str | None = None
|
||||
_weather_option_precipitation_unit: str | None = None
|
||||
_weather_option_wind_speed_unit: str | None = None
|
||||
|
||||
def __init_subclass__(cls, **kwargs: Any) -> None:
|
||||
"""Post initialisation processing."""
|
||||
super().__init_subclass__(**kwargs)
|
||||
_reported = False
|
||||
if any(
|
||||
method in cls.__dict__
|
||||
for method in (
|
||||
"_attr_temperature",
|
||||
"temperature",
|
||||
"_attr_temperature_unit",
|
||||
"temperature_unit",
|
||||
"_attr_pressure",
|
||||
"pressure",
|
||||
"_attr_pressure_unit",
|
||||
"pressure_unit",
|
||||
"_attr_wind_speed",
|
||||
"wind_speed",
|
||||
"_attr_wind_speed_unit",
|
||||
"wind_speed_unit",
|
||||
"_attr_visibility",
|
||||
"visibility",
|
||||
"_attr_visibility_unit",
|
||||
"visibility_unit",
|
||||
"_attr_precipitation_unit",
|
||||
"precipitation_unit",
|
||||
)
|
||||
):
|
||||
if _reported is False:
|
||||
module = inspect.getmodule(cls)
|
||||
_reported = True
|
||||
if (
|
||||
module
|
||||
and module.__file__
|
||||
and "custom_components" in module.__file__
|
||||
):
|
||||
report_issue = "report it to the custom component author."
|
||||
else:
|
||||
report_issue = (
|
||||
"create a bug report at "
|
||||
"https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue"
|
||||
)
|
||||
_LOGGER.warning(
|
||||
"%s::%s is overriding deprecated methods on an instance of "
|
||||
"WeatherEntity, this is not valid and will be unsupported "
|
||||
"from Home Assistant 2022.10. Please %s",
|
||||
cls.__module__,
|
||||
cls.__name__,
|
||||
report_issue,
|
||||
)
|
||||
|
||||
async def async_internal_added_to_hass(self) -> None:
|
||||
"""Call when the sensor entity is added to hass."""
|
||||
await super().async_internal_added_to_hass()
|
||||
if not self.registry_entry:
|
||||
return
|
||||
self.async_registry_entry_updated()
|
||||
|
||||
@property
|
||||
def temperature(self) -> float | None:
|
||||
"""Return the platform temperature in native units (i.e. not converted)."""
|
||||
"""Return the temperature for backward compatibility.
|
||||
|
||||
Should not be set by integrations.
|
||||
"""
|
||||
return self._attr_temperature
|
||||
|
||||
@property
|
||||
def temperature_unit(self) -> str:
|
||||
def native_temperature(self) -> float | None:
|
||||
"""Return the temperature in native units."""
|
||||
if (temperature := self.temperature) is not None:
|
||||
return temperature
|
||||
|
||||
return self._attr_native_temperature
|
||||
|
||||
@property
|
||||
def native_temperature_unit(self) -> str | None:
|
||||
"""Return the native unit of measurement for temperature."""
|
||||
if (temperature_unit := self.temperature_unit) is not None:
|
||||
return temperature_unit
|
||||
|
||||
return self._attr_native_temperature_unit
|
||||
|
||||
@property
|
||||
def temperature_unit(self) -> str | None:
|
||||
"""Return the temperature unit for backward compatibility.
|
||||
|
||||
Should not be set by integrations.
|
||||
"""
|
||||
return self._attr_temperature_unit
|
||||
|
||||
@final
|
||||
@property
|
||||
def _default_temperature_unit(self) -> str:
|
||||
"""Return the default unit of measurement for temperature.
|
||||
|
||||
Should not be set by integrations.
|
||||
"""
|
||||
return self.hass.config.units.temperature_unit
|
||||
|
||||
@final
|
||||
@property
|
||||
def _temperature_unit(self) -> str:
|
||||
"""Return the converted unit of measurement for temperature.
|
||||
|
||||
Should not be set by integrations.
|
||||
"""
|
||||
if (
|
||||
weather_option_temperature_unit := self._weather_option_temperature_unit
|
||||
) is not None:
|
||||
return weather_option_temperature_unit
|
||||
|
||||
return self._default_temperature_unit
|
||||
|
||||
@property
|
||||
def pressure(self) -> float | None:
|
||||
"""Return the pressure in native units."""
|
||||
"""Return the pressure for backward compatibility.
|
||||
|
||||
Should not be set by integrations.
|
||||
"""
|
||||
return self._attr_pressure
|
||||
|
||||
@property
|
||||
def pressure_unit(self) -> str | None:
|
||||
def native_pressure(self) -> float | None:
|
||||
"""Return the pressure in native units."""
|
||||
if (pressure := self.pressure) is not None:
|
||||
return pressure
|
||||
|
||||
return self._attr_native_pressure
|
||||
|
||||
@property
|
||||
def native_pressure_unit(self) -> str | None:
|
||||
"""Return the native unit of measurement for pressure."""
|
||||
if (pressure_unit := self.pressure_unit) is not None:
|
||||
return pressure_unit
|
||||
|
||||
return self._attr_native_pressure_unit
|
||||
|
||||
@property
|
||||
def pressure_unit(self) -> str | None:
|
||||
"""Return the pressure unit for backward compatibility.
|
||||
|
||||
Should not be set by integrations.
|
||||
"""
|
||||
return self._attr_pressure_unit
|
||||
|
||||
@final
|
||||
@property
|
||||
def _default_pressure_unit(self) -> str:
|
||||
"""Return the default unit of measurement for pressure.
|
||||
|
||||
Should not be set by integrations.
|
||||
"""
|
||||
return PRESSURE_HPA if self.hass.config.units.is_metric else PRESSURE_INHG
|
||||
|
||||
@final
|
||||
@property
|
||||
def _pressure_unit(self) -> str:
|
||||
"""Return the converted unit of measurement for pressure.
|
||||
|
||||
Should not be set by integrations.
|
||||
"""
|
||||
if (
|
||||
weather_option_pressure_unit := self._weather_option_pressure_unit
|
||||
) is not None:
|
||||
return weather_option_pressure_unit
|
||||
|
||||
return self._default_pressure_unit
|
||||
|
||||
@property
|
||||
def humidity(self) -> float | None:
|
||||
"""Return the humidity in native units."""
|
||||
@ -153,14 +434,63 @@ class WeatherEntity(Entity):
|
||||
|
||||
@property
|
||||
def wind_speed(self) -> float | None:
|
||||
"""Return the wind speed in native units."""
|
||||
"""Return the wind_speed for backward compatibility.
|
||||
|
||||
Should not be set by integrations.
|
||||
"""
|
||||
return self._attr_wind_speed
|
||||
|
||||
@property
|
||||
def wind_speed_unit(self) -> str | None:
|
||||
def native_wind_speed(self) -> float | None:
|
||||
"""Return the wind speed in native units."""
|
||||
if (wind_speed := self.wind_speed) is not None:
|
||||
return wind_speed
|
||||
|
||||
return self._attr_native_wind_speed
|
||||
|
||||
@property
|
||||
def native_wind_speed_unit(self) -> str | None:
|
||||
"""Return the native unit of measurement for wind speed."""
|
||||
if (wind_speed_unit := self.wind_speed_unit) is not None:
|
||||
return wind_speed_unit
|
||||
|
||||
return self._attr_native_wind_speed_unit
|
||||
|
||||
@property
|
||||
def wind_speed_unit(self) -> str | None:
|
||||
"""Return the wind_speed unit for backward compatibility.
|
||||
|
||||
Should not be set by integrations.
|
||||
"""
|
||||
return self._attr_wind_speed_unit
|
||||
|
||||
@final
|
||||
@property
|
||||
def _default_wind_speed_unit(self) -> str:
|
||||
"""Return the default unit of measurement for wind speed.
|
||||
|
||||
Should not be set by integrations.
|
||||
"""
|
||||
return (
|
||||
SPEED_KILOMETERS_PER_HOUR
|
||||
if self.hass.config.units.is_metric
|
||||
else SPEED_MILES_PER_HOUR
|
||||
)
|
||||
|
||||
@final
|
||||
@property
|
||||
def _wind_speed_unit(self) -> str:
|
||||
"""Return the converted unit of measurement for wind speed.
|
||||
|
||||
Should not be set by integrations.
|
||||
"""
|
||||
if (
|
||||
weather_option_wind_speed_unit := self._weather_option_wind_speed_unit
|
||||
) is not None:
|
||||
return weather_option_wind_speed_unit
|
||||
|
||||
return self._default_wind_speed_unit
|
||||
|
||||
@property
|
||||
def wind_bearing(self) -> float | str | None:
|
||||
"""Return the wind bearing."""
|
||||
@ -173,24 +503,103 @@ class WeatherEntity(Entity):
|
||||
|
||||
@property
|
||||
def visibility(self) -> float | None:
|
||||
"""Return the visibility in native units."""
|
||||
"""Return the visibility for backward compatibility.
|
||||
|
||||
Should not be set by integrations.
|
||||
"""
|
||||
return self._attr_visibility
|
||||
|
||||
@property
|
||||
def visibility_unit(self) -> str | None:
|
||||
def native_visibility(self) -> float | None:
|
||||
"""Return the visibility in native units."""
|
||||
if (visibility := self.visibility) is not None:
|
||||
return visibility
|
||||
|
||||
return self._attr_native_visibility
|
||||
|
||||
@property
|
||||
def native_visibility_unit(self) -> str | None:
|
||||
"""Return the native unit of measurement for visibility."""
|
||||
if (visibility_unit := self.visibility_unit) is not None:
|
||||
return visibility_unit
|
||||
|
||||
return self._attr_native_visibility_unit
|
||||
|
||||
@property
|
||||
def visibility_unit(self) -> str | None:
|
||||
"""Return the visibility unit for backward compatibility.
|
||||
|
||||
Should not be set by integrations.
|
||||
"""
|
||||
return self._attr_visibility_unit
|
||||
|
||||
@final
|
||||
@property
|
||||
def _default_visibility_unit(self) -> str:
|
||||
"""Return the default unit of measurement for visibility.
|
||||
|
||||
Should not be set by integrations.
|
||||
"""
|
||||
return self.hass.config.units.length_unit
|
||||
|
||||
@final
|
||||
@property
|
||||
def _visibility_unit(self) -> str:
|
||||
"""Return the converted unit of measurement for visibility.
|
||||
|
||||
Should not be set by integrations.
|
||||
"""
|
||||
if (
|
||||
weather_option_visibility_unit := self._weather_option_visibility_unit
|
||||
) is not None:
|
||||
return weather_option_visibility_unit
|
||||
|
||||
return self._default_visibility_unit
|
||||
|
||||
@property
|
||||
def forecast(self) -> list[Forecast] | None:
|
||||
"""Return the forecast in native units."""
|
||||
return self._attr_forecast
|
||||
|
||||
@property
|
||||
def precipitation_unit(self) -> str | None:
|
||||
def native_precipitation_unit(self) -> str | None:
|
||||
"""Return the native unit of measurement for accumulated precipitation."""
|
||||
if (precipitation_unit := self.precipitation_unit) is not None:
|
||||
return precipitation_unit
|
||||
|
||||
return self._attr_native_precipitation_unit
|
||||
|
||||
@property
|
||||
def precipitation_unit(self) -> str | None:
|
||||
"""Return the precipitation unit for backward compatibility.
|
||||
|
||||
Should not be set by integrations.
|
||||
"""
|
||||
return self._attr_precipitation_unit
|
||||
|
||||
@final
|
||||
@property
|
||||
def _default_precipitation_unit(self) -> str:
|
||||
"""Return the default unit of measurement for precipitation.
|
||||
|
||||
Should not be set by integrations.
|
||||
"""
|
||||
return self.hass.config.units.accumulated_precipitation_unit
|
||||
|
||||
@final
|
||||
@property
|
||||
def _precipitation_unit(self) -> str:
|
||||
"""Return the converted unit of measurement for precipitation.
|
||||
|
||||
Should not be set by integrations.
|
||||
"""
|
||||
if (
|
||||
weather_option_precipitation_unit := self._weather_option_precipitation_unit
|
||||
) is not None:
|
||||
return weather_option_precipitation_unit
|
||||
|
||||
return self._default_precipitation_unit
|
||||
|
||||
@property
|
||||
def precision(self) -> float:
|
||||
"""Return the precision of the temperature value, after unit conversion."""
|
||||
@ -198,7 +607,7 @@ class WeatherEntity(Entity):
|
||||
return self._attr_precision
|
||||
return (
|
||||
PRECISION_TENTHS
|
||||
if self.hass.config.units.temperature_unit == TEMP_CELSIUS
|
||||
if self._temperature_unit == TEMP_CELSIUS
|
||||
else PRECISION_WHOLE
|
||||
)
|
||||
|
||||
@ -207,13 +616,24 @@ class WeatherEntity(Entity):
|
||||
def state_attributes(self):
|
||||
"""Return the state attributes, converted from native units to user-configured units."""
|
||||
data = {}
|
||||
if self.temperature is not None:
|
||||
data[ATTR_WEATHER_TEMPERATURE] = show_temp(
|
||||
self.hass,
|
||||
self.temperature,
|
||||
self.temperature_unit,
|
||||
self.precision,
|
||||
)
|
||||
|
||||
precision = self.precision
|
||||
|
||||
if (temperature := self.native_temperature) is not None:
|
||||
from_unit = self.native_temperature_unit or self._default_temperature_unit
|
||||
to_unit = self._temperature_unit
|
||||
try:
|
||||
temperature_f = float(temperature)
|
||||
value_temp = UNIT_CONVERSIONS[ATTR_WEATHER_TEMPERATURE_UNIT](
|
||||
temperature_f, from_unit, to_unit
|
||||
)
|
||||
data[ATTR_WEATHER_TEMPERATURE] = round_temperature(
|
||||
value_temp, precision
|
||||
)
|
||||
except (TypeError, ValueError):
|
||||
data[ATTR_WEATHER_TEMPERATURE] = temperature
|
||||
|
||||
data[ATTR_WEATHER_TEMPERATURE_UNIT] = self._temperature_unit
|
||||
|
||||
if (humidity := self.humidity) is not None:
|
||||
data[ATTR_WEATHER_HUMIDITY] = round(humidity)
|
||||
@ -221,77 +641,159 @@ class WeatherEntity(Entity):
|
||||
if (ozone := self.ozone) is not None:
|
||||
data[ATTR_WEATHER_OZONE] = ozone
|
||||
|
||||
if (pressure := self.pressure) is not None:
|
||||
if (unit := self.pressure_unit) is not None:
|
||||
pressure = round(
|
||||
self.hass.config.units.pressure(pressure, unit), ROUNDING_PRECISION
|
||||
if (pressure := self.native_pressure) is not None:
|
||||
from_unit = self.native_pressure_unit or self._default_pressure_unit
|
||||
to_unit = self._pressure_unit
|
||||
try:
|
||||
pressure_f = float(pressure)
|
||||
value_pressure = UNIT_CONVERSIONS[ATTR_WEATHER_PRESSURE_UNIT](
|
||||
pressure_f, from_unit, to_unit
|
||||
)
|
||||
data[ATTR_WEATHER_PRESSURE] = pressure
|
||||
data[ATTR_WEATHER_PRESSURE] = round(value_pressure, ROUNDING_PRECISION)
|
||||
except (TypeError, ValueError):
|
||||
data[ATTR_WEATHER_PRESSURE] = pressure
|
||||
|
||||
data[ATTR_WEATHER_PRESSURE_UNIT] = self._pressure_unit
|
||||
|
||||
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:
|
||||
if (unit := self.wind_speed_unit) is not None:
|
||||
wind_speed = round(
|
||||
self.hass.config.units.wind_speed(wind_speed, unit),
|
||||
ROUNDING_PRECISION,
|
||||
if (wind_speed := self.native_wind_speed) is not None:
|
||||
from_unit = self.native_wind_speed_unit or self._default_wind_speed_unit
|
||||
to_unit = self._wind_speed_unit
|
||||
try:
|
||||
wind_speed_f = float(wind_speed)
|
||||
value_wind_speed = UNIT_CONVERSIONS[ATTR_WEATHER_WIND_SPEED_UNIT](
|
||||
wind_speed_f, from_unit, to_unit
|
||||
)
|
||||
data[ATTR_WEATHER_WIND_SPEED] = wind_speed
|
||||
data[ATTR_WEATHER_WIND_SPEED] = round(
|
||||
value_wind_speed, ROUNDING_PRECISION
|
||||
)
|
||||
except (TypeError, ValueError):
|
||||
data[ATTR_WEATHER_WIND_SPEED] = wind_speed
|
||||
|
||||
if (visibility := self.visibility) is not None:
|
||||
if (unit := self.visibility_unit) is not None:
|
||||
visibility = round(
|
||||
self.hass.config.units.length(visibility, unit), ROUNDING_PRECISION
|
||||
data[ATTR_WEATHER_WIND_SPEED_UNIT] = self._wind_speed_unit
|
||||
|
||||
if (visibility := self.native_visibility) is not None:
|
||||
from_unit = self.native_visibility_unit or self._default_visibility_unit
|
||||
to_unit = self._visibility_unit
|
||||
try:
|
||||
visibility_f = float(visibility)
|
||||
value_visibility = UNIT_CONVERSIONS[ATTR_WEATHER_VISIBILITY_UNIT](
|
||||
visibility_f, from_unit, to_unit
|
||||
)
|
||||
data[ATTR_WEATHER_VISIBILITY] = visibility
|
||||
data[ATTR_WEATHER_VISIBILITY] = round(
|
||||
value_visibility, ROUNDING_PRECISION
|
||||
)
|
||||
except (TypeError, ValueError):
|
||||
data[ATTR_WEATHER_VISIBILITY] = visibility
|
||||
|
||||
data[ATTR_WEATHER_VISIBILITY_UNIT] = self._visibility_unit
|
||||
data[ATTR_WEATHER_PRECIPITATION_UNIT] = self._precipitation_unit
|
||||
|
||||
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,
|
||||
|
||||
temperature = forecast_entry.pop(
|
||||
ATTR_FORECAST_NATIVE_TEMP, forecast_entry.get(ATTR_FORECAST_TEMP)
|
||||
)
|
||||
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,
|
||||
|
||||
from_temp_unit = (
|
||||
self.native_temperature_unit or self._default_temperature_unit
|
||||
)
|
||||
to_temp_unit = self._temperature_unit
|
||||
|
||||
if temperature is None:
|
||||
forecast_entry[ATTR_FORECAST_TEMP] = None
|
||||
else:
|
||||
with suppress(TypeError, ValueError):
|
||||
temperature_f = float(temperature)
|
||||
value_temp = UNIT_CONVERSIONS[ATTR_WEATHER_TEMPERATURE_UNIT](
|
||||
temperature_f,
|
||||
from_temp_unit,
|
||||
to_temp_unit,
|
||||
)
|
||||
forecast_entry[ATTR_FORECAST_TEMP] = round_temperature(
|
||||
value_temp, precision
|
||||
)
|
||||
|
||||
if forecast_temp_low := forecast_entry.pop(
|
||||
ATTR_FORECAST_NATIVE_TEMP_LOW,
|
||||
forecast_entry.get(ATTR_FORECAST_TEMP_LOW),
|
||||
):
|
||||
with suppress(TypeError, ValueError):
|
||||
forecast_temp_low_f = float(forecast_temp_low)
|
||||
value_temp_low = UNIT_CONVERSIONS[
|
||||
ATTR_WEATHER_TEMPERATURE_UNIT
|
||||
](
|
||||
forecast_temp_low_f,
|
||||
from_temp_unit,
|
||||
to_temp_unit,
|
||||
)
|
||||
|
||||
forecast_entry[ATTR_FORECAST_TEMP_LOW] = round_temperature(
|
||||
value_temp_low, precision
|
||||
)
|
||||
|
||||
if forecast_pressure := forecast_entry.pop(
|
||||
ATTR_FORECAST_NATIVE_PRESSURE,
|
||||
forecast_entry.get(ATTR_FORECAST_PRESSURE),
|
||||
):
|
||||
from_pressure_unit = (
|
||||
self.native_pressure_unit or self._default_pressure_unit
|
||||
)
|
||||
if (
|
||||
native_pressure := forecast_entry.get(ATTR_FORECAST_PRESSURE)
|
||||
) is not None:
|
||||
if (unit := self.pressure_unit) is not None:
|
||||
pressure = round(
|
||||
self.hass.config.units.pressure(native_pressure, unit),
|
||||
ROUNDING_PRECISION,
|
||||
)
|
||||
forecast_entry[ATTR_FORECAST_PRESSURE] = pressure
|
||||
if (
|
||||
native_wind_speed := forecast_entry.get(ATTR_FORECAST_WIND_SPEED)
|
||||
) is not None:
|
||||
if (unit := self.wind_speed_unit) is not None:
|
||||
wind_speed = round(
|
||||
self.hass.config.units.wind_speed(native_wind_speed, unit),
|
||||
ROUNDING_PRECISION,
|
||||
)
|
||||
forecast_entry[ATTR_FORECAST_WIND_SPEED] = wind_speed
|
||||
if (
|
||||
native_precip := forecast_entry.get(ATTR_FORECAST_PRECIPITATION)
|
||||
) is not None:
|
||||
if (unit := self.precipitation_unit) is not None:
|
||||
precipitation = round(
|
||||
self.hass.config.units.accumulated_precipitation(
|
||||
native_precip, unit
|
||||
to_pressure_unit = self._pressure_unit
|
||||
with suppress(TypeError, ValueError):
|
||||
forecast_pressure_f = float(forecast_pressure)
|
||||
forecast_entry[ATTR_FORECAST_PRESSURE] = round(
|
||||
UNIT_CONVERSIONS[ATTR_WEATHER_PRESSURE_UNIT](
|
||||
forecast_pressure_f,
|
||||
from_pressure_unit,
|
||||
to_pressure_unit,
|
||||
),
|
||||
ROUNDING_PRECISION,
|
||||
)
|
||||
|
||||
if forecast_wind_speed := forecast_entry.pop(
|
||||
ATTR_FORECAST_NATIVE_WIND_SPEED,
|
||||
forecast_entry.get(ATTR_FORECAST_WIND_SPEED),
|
||||
):
|
||||
from_wind_speed_unit = (
|
||||
self.native_wind_speed_unit or self._default_wind_speed_unit
|
||||
)
|
||||
to_wind_speed_unit = self._wind_speed_unit
|
||||
with suppress(TypeError, ValueError):
|
||||
forecast_wind_speed_f = float(forecast_wind_speed)
|
||||
forecast_entry[ATTR_FORECAST_WIND_SPEED] = round(
|
||||
UNIT_CONVERSIONS[ATTR_WEATHER_WIND_SPEED_UNIT](
|
||||
forecast_wind_speed_f,
|
||||
from_wind_speed_unit,
|
||||
to_wind_speed_unit,
|
||||
),
|
||||
ROUNDING_PRECISION,
|
||||
)
|
||||
|
||||
if forecast_precipitation := forecast_entry.pop(
|
||||
ATTR_FORECAST_NATIVE_PRECIPITATION,
|
||||
forecast_entry.get(ATTR_FORECAST_PRECIPITATION),
|
||||
):
|
||||
from_precipitation_unit = (
|
||||
self.native_precipitation_unit
|
||||
or self._default_precipitation_unit
|
||||
)
|
||||
to_precipitation_unit = self._precipitation_unit
|
||||
with suppress(TypeError, ValueError):
|
||||
forecast_precipitation_f = float(forecast_precipitation)
|
||||
forecast_entry[ATTR_FORECAST_PRECIPITATION] = round(
|
||||
UNIT_CONVERSIONS[ATTR_WEATHER_PRECIPITATION_UNIT](
|
||||
forecast_precipitation_f,
|
||||
from_precipitation_unit,
|
||||
to_precipitation_unit,
|
||||
),
|
||||
ROUNDING_PRECISION,
|
||||
)
|
||||
forecast_entry[ATTR_FORECAST_PRECIPITATION] = precipitation
|
||||
|
||||
forecast.append(forecast_entry)
|
||||
|
||||
@ -309,3 +811,44 @@ class WeatherEntity(Entity):
|
||||
def condition(self) -> str | None:
|
||||
"""Return the current condition."""
|
||||
return self._attr_condition
|
||||
|
||||
@callback
|
||||
def async_registry_entry_updated(self) -> None:
|
||||
"""Run when the entity registry entry has been updated."""
|
||||
assert self.registry_entry
|
||||
self._weather_option_temperature_unit = None
|
||||
self._weather_option_pressure_unit = None
|
||||
self._weather_option_precipitation_unit = None
|
||||
self._weather_option_wind_speed_unit = None
|
||||
self._weather_option_visibility_unit = None
|
||||
if weather_options := self.registry_entry.options.get(DOMAIN):
|
||||
if (
|
||||
custom_unit_temperature := weather_options.get(
|
||||
ATTR_WEATHER_TEMPERATURE_UNIT
|
||||
)
|
||||
) and custom_unit_temperature in VALID_UNITS[ATTR_WEATHER_TEMPERATURE_UNIT]:
|
||||
self._weather_option_temperature_unit = custom_unit_temperature
|
||||
if (
|
||||
custom_unit_pressure := weather_options.get(ATTR_WEATHER_PRESSURE_UNIT)
|
||||
) and custom_unit_pressure in VALID_UNITS[ATTR_WEATHER_PRESSURE_UNIT]:
|
||||
self._weather_option_pressure_unit = custom_unit_pressure
|
||||
if (
|
||||
custom_unit_precipitation := weather_options.get(
|
||||
ATTR_WEATHER_PRECIPITATION_UNIT
|
||||
)
|
||||
) and custom_unit_precipitation in VALID_UNITS[
|
||||
ATTR_WEATHER_PRECIPITATION_UNIT
|
||||
]:
|
||||
self._weather_option_precipitation_unit = custom_unit_precipitation
|
||||
if (
|
||||
custom_unit_wind_speed := weather_options.get(
|
||||
ATTR_WEATHER_WIND_SPEED_UNIT
|
||||
)
|
||||
) and custom_unit_wind_speed in VALID_UNITS[ATTR_WEATHER_WIND_SPEED_UNIT]:
|
||||
self._weather_option_wind_speed_unit = custom_unit_wind_speed
|
||||
if (
|
||||
custom_unit_visibility := weather_options.get(
|
||||
ATTR_WEATHER_VISIBILITY_UNIT
|
||||
)
|
||||
) and custom_unit_visibility in VALID_UNITS[ATTR_WEATHER_VISIBILITY_UNIT]:
|
||||
self._weather_option_visibility_unit = custom_unit_visibility
|
||||
|
@ -46,7 +46,7 @@ async def test_weather_without_forecast(hass):
|
||||
assert state.attributes.get(ATTR_WEATHER_TEMPERATURE) == 22.6
|
||||
assert state.attributes.get(ATTR_WEATHER_VISIBILITY) == 16.1
|
||||
assert state.attributes.get(ATTR_WEATHER_WIND_BEARING) == 180
|
||||
assert state.attributes.get(ATTR_WEATHER_WIND_SPEED) == 4.03
|
||||
assert state.attributes.get(ATTR_WEATHER_WIND_SPEED) == 14.5 # 4.03 m/s -> km/h
|
||||
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
|
||||
|
||||
entry = registry.async_get("weather.home")
|
||||
@ -68,7 +68,7 @@ async def test_weather_with_forecast(hass):
|
||||
assert state.attributes.get(ATTR_WEATHER_TEMPERATURE) == 22.6
|
||||
assert state.attributes.get(ATTR_WEATHER_VISIBILITY) == 16.1
|
||||
assert state.attributes.get(ATTR_WEATHER_WIND_BEARING) == 180
|
||||
assert state.attributes.get(ATTR_WEATHER_WIND_SPEED) == 4.03
|
||||
assert state.attributes.get(ATTR_WEATHER_WIND_SPEED) == 14.5 # 4.03 m/s -> km/h
|
||||
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
|
||||
forecast = state.attributes.get(ATTR_FORECAST)[0]
|
||||
assert forecast.get(ATTR_FORECAST_CONDITION) == "lightning-rainy"
|
||||
@ -78,7 +78,7 @@ async def test_weather_with_forecast(hass):
|
||||
assert forecast.get(ATTR_FORECAST_TEMP_LOW) == 15.4
|
||||
assert forecast.get(ATTR_FORECAST_TIME) == "2020-07-26T05:00:00+00:00"
|
||||
assert forecast.get(ATTR_FORECAST_WIND_BEARING) == 166
|
||||
assert forecast.get(ATTR_FORECAST_WIND_SPEED) == 3.61
|
||||
assert forecast.get(ATTR_FORECAST_WIND_SPEED) == 13.0 # 3.61 m/s -> km/h
|
||||
|
||||
entry = registry.async_get("weather.home")
|
||||
assert entry
|
||||
|
@ -42,10 +42,10 @@ async def test_aemet_weather(hass):
|
||||
assert state.state == ATTR_CONDITION_SNOWY
|
||||
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
|
||||
assert state.attributes.get(ATTR_WEATHER_HUMIDITY) == 99.0
|
||||
assert state.attributes.get(ATTR_WEATHER_PRESSURE) == 100440.0
|
||||
assert state.attributes.get(ATTR_WEATHER_PRESSURE) == 1004.4 # 100440.0 Pa -> hPa
|
||||
assert state.attributes.get(ATTR_WEATHER_TEMPERATURE) == -0.7
|
||||
assert state.attributes.get(ATTR_WEATHER_WIND_BEARING) == 90.0
|
||||
assert state.attributes.get(ATTR_WEATHER_WIND_SPEED) == 4.17
|
||||
assert state.attributes.get(ATTR_WEATHER_WIND_SPEED) == 15.0 # 4.17 m/s -> km/h
|
||||
forecast = state.attributes.get(ATTR_FORECAST)[0]
|
||||
assert forecast.get(ATTR_FORECAST_CONDITION) == ATTR_CONDITION_PARTLYCLOUDY
|
||||
assert forecast.get(ATTR_FORECAST_PRECIPITATION) is None
|
||||
@ -57,7 +57,7 @@ async def test_aemet_weather(hass):
|
||||
== dt_util.parse_datetime("2021-01-10 00:00:00+00:00").isoformat()
|
||||
)
|
||||
assert forecast.get(ATTR_FORECAST_WIND_BEARING) == 45.0
|
||||
assert forecast.get(ATTR_FORECAST_WIND_SPEED) == 5.56
|
||||
assert forecast.get(ATTR_FORECAST_WIND_SPEED) == 20.0 # 5.56 m/s -> km/h
|
||||
|
||||
state = hass.states.get("weather.aemet_hourly")
|
||||
assert state is None
|
||||
|
@ -132,7 +132,7 @@ async def test_v3_weather(
|
||||
{
|
||||
ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY,
|
||||
ATTR_FORECAST_TIME: "2021-03-12T00:00:00-08:00",
|
||||
ATTR_FORECAST_PRECIPITATION: 0.0457,
|
||||
ATTR_FORECAST_PRECIPITATION: 0.05,
|
||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 25,
|
||||
ATTR_FORECAST_TEMP: 19.9,
|
||||
ATTR_FORECAST_TEMP_LOW: 12.1,
|
||||
@ -148,7 +148,7 @@ async def test_v3_weather(
|
||||
{
|
||||
ATTR_FORECAST_CONDITION: ATTR_CONDITION_RAINY,
|
||||
ATTR_FORECAST_TIME: "2021-03-14T00:00:00-08:00",
|
||||
ATTR_FORECAST_PRECIPITATION: 1.0744,
|
||||
ATTR_FORECAST_PRECIPITATION: 1.07,
|
||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 75,
|
||||
ATTR_FORECAST_TEMP: 6.4,
|
||||
ATTR_FORECAST_TEMP_LOW: 3.2,
|
||||
@ -156,7 +156,7 @@ async def test_v3_weather(
|
||||
{
|
||||
ATTR_FORECAST_CONDITION: ATTR_CONDITION_SNOWY,
|
||||
ATTR_FORECAST_TIME: "2021-03-15T00:00:00-07:00", # DST starts
|
||||
ATTR_FORECAST_PRECIPITATION: 7.3050,
|
||||
ATTR_FORECAST_PRECIPITATION: 7.3,
|
||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 95,
|
||||
ATTR_FORECAST_TEMP: 1.2,
|
||||
ATTR_FORECAST_TEMP_LOW: 0.2,
|
||||
@ -164,7 +164,7 @@ async def test_v3_weather(
|
||||
{
|
||||
ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY,
|
||||
ATTR_FORECAST_TIME: "2021-03-16T00:00:00-07:00",
|
||||
ATTR_FORECAST_PRECIPITATION: 0.0051,
|
||||
ATTR_FORECAST_PRECIPITATION: 0.01,
|
||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 5,
|
||||
ATTR_FORECAST_TEMP: 6.1,
|
||||
ATTR_FORECAST_TEMP_LOW: -1.6,
|
||||
@ -188,7 +188,7 @@ async def test_v3_weather(
|
||||
{
|
||||
ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY,
|
||||
ATTR_FORECAST_TIME: "2021-03-19T00:00:00-07:00",
|
||||
ATTR_FORECAST_PRECIPITATION: 0.1778,
|
||||
ATTR_FORECAST_PRECIPITATION: 0.18,
|
||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 45,
|
||||
ATTR_FORECAST_TEMP: 9.4,
|
||||
ATTR_FORECAST_TEMP_LOW: 4.7,
|
||||
@ -196,7 +196,7 @@ async def test_v3_weather(
|
||||
{
|
||||
ATTR_FORECAST_CONDITION: ATTR_CONDITION_RAINY,
|
||||
ATTR_FORECAST_TIME: "2021-03-20T00:00:00-07:00",
|
||||
ATTR_FORECAST_PRECIPITATION: 1.2319,
|
||||
ATTR_FORECAST_PRECIPITATION: 1.23,
|
||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 55,
|
||||
ATTR_FORECAST_TEMP: 5.0,
|
||||
ATTR_FORECAST_TEMP_LOW: 3.1,
|
||||
@ -204,7 +204,7 @@ async def test_v3_weather(
|
||||
{
|
||||
ATTR_FORECAST_CONDITION: ATTR_CONDITION_CLOUDY,
|
||||
ATTR_FORECAST_TIME: "2021-03-21T00:00:00-07:00",
|
||||
ATTR_FORECAST_PRECIPITATION: 0.0432,
|
||||
ATTR_FORECAST_PRECIPITATION: 0.04,
|
||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 20,
|
||||
ATTR_FORECAST_TEMP: 6.8,
|
||||
ATTR_FORECAST_TEMP_LOW: 0.9,
|
||||
@ -213,11 +213,11 @@ async def test_v3_weather(
|
||||
assert weather_state.attributes[ATTR_FRIENDLY_NAME] == "ClimaCell - Daily"
|
||||
assert weather_state.attributes[ATTR_WEATHER_HUMIDITY] == 24
|
||||
assert weather_state.attributes[ATTR_WEATHER_OZONE] == 52.625
|
||||
assert weather_state.attributes[ATTR_WEATHER_PRESSURE] == 1028.1246
|
||||
assert weather_state.attributes[ATTR_WEATHER_PRESSURE] == 1028.12
|
||||
assert weather_state.attributes[ATTR_WEATHER_TEMPERATURE] == 6.6
|
||||
assert weather_state.attributes[ATTR_WEATHER_VISIBILITY] == 9.9940
|
||||
assert weather_state.attributes[ATTR_WEATHER_VISIBILITY] == 9.99
|
||||
assert weather_state.attributes[ATTR_WEATHER_WIND_BEARING] == 320.31
|
||||
assert weather_state.attributes[ATTR_WEATHER_WIND_SPEED] == 14.6289
|
||||
assert weather_state.attributes[ATTR_WEATHER_WIND_SPEED] == 14.63
|
||||
assert weather_state.attributes[ATTR_CLOUD_COVER] == 100
|
||||
assert weather_state.attributes[ATTR_WIND_GUST] == 24.0758
|
||||
assert weather_state.attributes[ATTR_PRECIPITATION_TYPE] == "rain"
|
||||
|
@ -36,7 +36,7 @@ async def test_attributes(hass):
|
||||
assert data.get(ATTR_WEATHER_TEMPERATURE) == 21.6
|
||||
assert data.get(ATTR_WEATHER_HUMIDITY) == 92
|
||||
assert data.get(ATTR_WEATHER_PRESSURE) == 1099
|
||||
assert data.get(ATTR_WEATHER_WIND_SPEED) == 0.5
|
||||
assert data.get(ATTR_WEATHER_WIND_SPEED) == 1.8 # 0.5 m/s -> km/h
|
||||
assert data.get(ATTR_WEATHER_WIND_BEARING) is None
|
||||
assert data.get(ATTR_WEATHER_OZONE) is None
|
||||
assert data.get(ATTR_ATTRIBUTION) == "Powered by Home Assistant"
|
||||
@ -53,20 +53,3 @@ async def test_attributes(hass):
|
||||
data.get(ATTR_FORECAST)[6].get(ATTR_FORECAST_PRECIPITATION_PROBABILITY) == 100
|
||||
)
|
||||
assert len(data.get(ATTR_FORECAST)) == 7
|
||||
|
||||
|
||||
async def test_temperature_convert(hass):
|
||||
"""Test temperature conversion."""
|
||||
assert await async_setup_component(
|
||||
hass, weather.DOMAIN, {"weather": {"platform": "demo"}}
|
||||
)
|
||||
hass.config.units = METRIC_SYSTEM
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("weather.demo_weather_north")
|
||||
assert state is not None
|
||||
|
||||
assert state.state == "rainy"
|
||||
|
||||
data = state.attributes
|
||||
assert data.get(ATTR_WEATHER_TEMPERATURE) == -24.4
|
||||
|
@ -198,7 +198,7 @@ async def test_daily_forecast(hass):
|
||||
assert forecast.get(ATTR_FORECAST_TEMP) == 16.2
|
||||
assert forecast.get(ATTR_FORECAST_TEMP_LOW) == 10.6
|
||||
assert forecast.get(ATTR_FORECAST_PRECIPITATION_PROBABILITY) == "100.0"
|
||||
assert forecast.get(ATTR_FORECAST_WIND_SPEED) == "10"
|
||||
assert forecast.get(ATTR_FORECAST_WIND_SPEED) == 10.0
|
||||
assert forecast.get(ATTR_FORECAST_WIND_BEARING) == "S"
|
||||
|
||||
|
||||
@ -222,5 +222,5 @@ async def test_hourly_forecast(hass):
|
||||
assert forecast.get(ATTR_FORECAST_CONDITION) == "rainy"
|
||||
assert forecast.get(ATTR_FORECAST_TEMP) == 7.7
|
||||
assert forecast.get(ATTR_FORECAST_PRECIPITATION_PROBABILITY) == 80.0
|
||||
assert forecast.get(ATTR_FORECAST_WIND_SPEED) == "32.7"
|
||||
assert forecast.get(ATTR_FORECAST_WIND_SPEED) == 32.7
|
||||
assert forecast.get(ATTR_FORECAST_WIND_BEARING) == "S"
|
||||
|
@ -85,8 +85,8 @@ async def test_weather(hass: HomeAssistant, knx: KNXTestKit):
|
||||
state = hass.states.get("weather.test")
|
||||
assert state.attributes["temperature"] == 0.4
|
||||
assert state.attributes["wind_bearing"] == 270
|
||||
assert state.attributes["wind_speed"] == 1.4400000000000002
|
||||
assert state.attributes["pressure"] == 980.5824
|
||||
assert state.attributes["wind_speed"] == 1.44
|
||||
assert state.attributes["pressure"] == 980.58
|
||||
assert state.state is ATTR_CONDITION_SUNNY
|
||||
|
||||
# update from KNX - set rain alarm
|
||||
|
@ -99,13 +99,13 @@ async def test_v4_weather(hass: HomeAssistant) -> None:
|
||||
ATTR_FORECAST_TEMP: 45.9,
|
||||
ATTR_FORECAST_TEMP_LOW: 26.1,
|
||||
ATTR_FORECAST_WIND_BEARING: 239.6,
|
||||
ATTR_FORECAST_WIND_SPEED: 9.49,
|
||||
ATTR_FORECAST_WIND_SPEED: 34.16, # 9.49 m/s -> km/h
|
||||
}
|
||||
assert weather_state.attributes[ATTR_FRIENDLY_NAME] == "Tomorrow.io - Daily"
|
||||
assert weather_state.attributes[ATTR_WEATHER_HUMIDITY] == 23
|
||||
assert weather_state.attributes[ATTR_WEATHER_OZONE] == 46.53
|
||||
assert weather_state.attributes[ATTR_WEATHER_PRESSURE] == 3035.0
|
||||
assert weather_state.attributes[ATTR_WEATHER_PRESSURE] == 30.35
|
||||
assert weather_state.attributes[ATTR_WEATHER_TEMPERATURE] == 44.1
|
||||
assert weather_state.attributes[ATTR_WEATHER_VISIBILITY] == 8.15
|
||||
assert weather_state.attributes[ATTR_WEATHER_WIND_BEARING] == 315.14
|
||||
assert weather_state.attributes[ATTR_WEATHER_WIND_SPEED] == 9.33
|
||||
assert weather_state.attributes[ATTR_WEATHER_WIND_SPEED] == 33.59 # 9.33 m/s ->km/h
|
||||
|
@ -1,4 +1,6 @@
|
||||
"""The test for weather entity."""
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
from pytest import approx
|
||||
|
||||
@ -9,19 +11,44 @@ from homeassistant.components.weather import (
|
||||
ATTR_FORECAST_PRESSURE,
|
||||
ATTR_FORECAST_TEMP,
|
||||
ATTR_FORECAST_TEMP_LOW,
|
||||
ATTR_FORECAST_WIND_BEARING,
|
||||
ATTR_FORECAST_WIND_SPEED,
|
||||
ATTR_WEATHER_OZONE,
|
||||
ATTR_WEATHER_PRECIPITATION_UNIT,
|
||||
ATTR_WEATHER_PRESSURE,
|
||||
ATTR_WEATHER_PRESSURE_UNIT,
|
||||
ATTR_WEATHER_TEMPERATURE,
|
||||
ATTR_WEATHER_TEMPERATURE_UNIT,
|
||||
ATTR_WEATHER_VISIBILITY,
|
||||
ATTR_WEATHER_VISIBILITY_UNIT,
|
||||
ATTR_WEATHER_WIND_BEARING,
|
||||
ATTR_WEATHER_WIND_SPEED,
|
||||
ATTR_WEATHER_WIND_SPEED_UNIT,
|
||||
ROUNDING_PRECISION,
|
||||
Forecast,
|
||||
WeatherEntity,
|
||||
round_temperature,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_FRIENDLY_NAME,
|
||||
LENGTH_INCHES,
|
||||
LENGTH_KILOMETERS,
|
||||
LENGTH_MILES,
|
||||
LENGTH_MILLIMETERS,
|
||||
PRECISION_HALVES,
|
||||
PRECISION_TENTHS,
|
||||
PRECISION_WHOLE,
|
||||
PRESSURE_HPA,
|
||||
PRESSURE_INHG,
|
||||
PRESSURE_PA,
|
||||
SPEED_KILOMETERS_PER_HOUR,
|
||||
SPEED_METERS_PER_SECOND,
|
||||
SPEED_MILES_PER_HOUR,
|
||||
TEMP_CELSIUS,
|
||||
TEMP_FAHRENHEIT,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util.distance import convert as convert_distance
|
||||
from homeassistant.util.pressure import convert as convert_pressure
|
||||
@ -29,11 +56,75 @@ from homeassistant.util.speed import convert as convert_speed
|
||||
from homeassistant.util.temperature import convert as convert_temperature
|
||||
from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM
|
||||
|
||||
from tests.testing_config.custom_components.test import weather as WeatherPlatform
|
||||
|
||||
async def create_entity(hass, **kwargs):
|
||||
|
||||
class MockWeatherEntity(WeatherEntity):
|
||||
"""Mock a Weather Entity."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initiate Entity."""
|
||||
super().__init__()
|
||||
self._attr_condition = ATTR_CONDITION_SUNNY
|
||||
self._attr_native_precipitation_unit = LENGTH_MILLIMETERS
|
||||
self._attr_native_pressure = 10
|
||||
self._attr_native_pressure_unit = PRESSURE_HPA
|
||||
self._attr_native_temperature = 20
|
||||
self._attr_native_temperature_unit = TEMP_CELSIUS
|
||||
self._attr_native_visibility = 30
|
||||
self._attr_native_visibility_unit = LENGTH_KILOMETERS
|
||||
self._attr_native_wind_speed = 3
|
||||
self._attr_native_wind_speed_unit = SPEED_METERS_PER_SECOND
|
||||
self._attr_forecast = [
|
||||
Forecast(
|
||||
datetime=datetime(2022, 6, 20, 20, 00, 00),
|
||||
native_precipitation=1,
|
||||
native_temperature=20,
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
class MockWeatherEntityPrecision(WeatherEntity):
|
||||
"""Mock a Weather Entity with precision."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initiate Entity."""
|
||||
super().__init__()
|
||||
self._attr_condition = ATTR_CONDITION_SUNNY
|
||||
self._attr_native_temperature = 20.3
|
||||
self._attr_native_temperature_unit = TEMP_CELSIUS
|
||||
self._attr_precision = PRECISION_HALVES
|
||||
|
||||
|
||||
class MockWeatherEntityCompat(WeatherEntity):
|
||||
"""Mock a Weather Entity using old attributes."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initiate Entity."""
|
||||
super().__init__()
|
||||
self._attr_condition = ATTR_CONDITION_SUNNY
|
||||
self._attr_precipitation_unit = LENGTH_MILLIMETERS
|
||||
self._attr_pressure = 10
|
||||
self._attr_pressure_unit = PRESSURE_HPA
|
||||
self._attr_temperature = 20
|
||||
self._attr_temperature_unit = TEMP_CELSIUS
|
||||
self._attr_visibility = 30
|
||||
self._attr_visibility_unit = LENGTH_KILOMETERS
|
||||
self._attr_wind_speed = 3
|
||||
self._attr_wind_speed_unit = SPEED_METERS_PER_SECOND
|
||||
self._attr_forecast = [
|
||||
Forecast(
|
||||
datetime=datetime(2022, 6, 20, 20, 00, 00),
|
||||
precipitation=1,
|
||||
temperature=20,
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
async def create_entity(hass: HomeAssistant, **kwargs):
|
||||
"""Create the weather entity to run tests on."""
|
||||
kwargs = {"temperature": None, "temperature_unit": None, **kwargs}
|
||||
platform = getattr(hass.components, "test.weather")
|
||||
kwargs = {"native_temperature": None, "native_temperature_unit": None, **kwargs}
|
||||
platform: WeatherPlatform = getattr(hass.components, "test.weather")
|
||||
platform.init(empty=True)
|
||||
platform.ENTITIES.append(
|
||||
platform.MockWeatherMockForecast(
|
||||
@ -49,145 +140,741 @@ async def create_entity(hass, **kwargs):
|
||||
return entity0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("unit_system", [IMPERIAL_SYSTEM, METRIC_SYSTEM])
|
||||
async def test_temperature_conversion(
|
||||
hass,
|
||||
@pytest.mark.parametrize("native_unit", (TEMP_FAHRENHEIT, TEMP_CELSIUS))
|
||||
@pytest.mark.parametrize(
|
||||
"state_unit, unit_system",
|
||||
((TEMP_CELSIUS, METRIC_SYSTEM), (TEMP_FAHRENHEIT, IMPERIAL_SYSTEM)),
|
||||
)
|
||||
async def test_temperature(
|
||||
hass: HomeAssistant,
|
||||
enable_custom_integrations,
|
||||
native_unit: str,
|
||||
state_unit: str,
|
||||
unit_system,
|
||||
):
|
||||
"""Test temperature conversion."""
|
||||
"""Test temperature."""
|
||||
hass.config.units = unit_system
|
||||
native_value = 38
|
||||
native_unit = TEMP_FAHRENHEIT
|
||||
state_value = convert_temperature(native_value, native_unit, state_unit)
|
||||
|
||||
entity0 = await create_entity(
|
||||
hass, temperature=native_value, temperature_unit=native_unit
|
||||
hass, native_temperature=native_value, native_temperature_unit=native_unit
|
||||
)
|
||||
|
||||
state = hass.states.get(entity0.entity_id)
|
||||
forecast = state.attributes[ATTR_FORECAST][0]
|
||||
|
||||
expected = convert_temperature(
|
||||
native_value, native_unit, unit_system.temperature_unit
|
||||
)
|
||||
expected = state_value
|
||||
assert float(state.attributes[ATTR_WEATHER_TEMPERATURE]) == approx(
|
||||
expected, rel=0.1
|
||||
)
|
||||
assert state.attributes[ATTR_WEATHER_TEMPERATURE_UNIT] == state_unit
|
||||
assert float(forecast[ATTR_FORECAST_TEMP]) == approx(expected, rel=0.1)
|
||||
assert float(forecast[ATTR_FORECAST_TEMP_LOW]) == approx(expected, rel=0.1)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("unit_system", [IMPERIAL_SYSTEM, METRIC_SYSTEM])
|
||||
async def test_pressure_conversion(
|
||||
hass,
|
||||
@pytest.mark.parametrize("native_unit", (None,))
|
||||
@pytest.mark.parametrize(
|
||||
"state_unit, unit_system",
|
||||
((TEMP_CELSIUS, METRIC_SYSTEM), (TEMP_FAHRENHEIT, IMPERIAL_SYSTEM)),
|
||||
)
|
||||
async def test_temperature_no_unit(
|
||||
hass: HomeAssistant,
|
||||
enable_custom_integrations,
|
||||
native_unit: str,
|
||||
state_unit: str,
|
||||
unit_system,
|
||||
):
|
||||
"""Test pressure conversion."""
|
||||
"""Test temperature when the entity does not declare a native unit."""
|
||||
hass.config.units = unit_system
|
||||
native_value = 30
|
||||
native_unit = PRESSURE_INHG
|
||||
native_value = 38
|
||||
state_value = native_value
|
||||
|
||||
entity0 = await create_entity(
|
||||
hass, pressure=native_value, pressure_unit=native_unit
|
||||
hass, native_temperature=native_value, native_temperature_unit=native_unit
|
||||
)
|
||||
|
||||
state = hass.states.get(entity0.entity_id)
|
||||
forecast = state.attributes[ATTR_FORECAST][0]
|
||||
|
||||
expected = state_value
|
||||
assert float(state.attributes[ATTR_WEATHER_TEMPERATURE]) == approx(
|
||||
expected, rel=0.1
|
||||
)
|
||||
assert state.attributes[ATTR_WEATHER_TEMPERATURE_UNIT] == state_unit
|
||||
assert float(forecast[ATTR_FORECAST_TEMP]) == approx(expected, rel=0.1)
|
||||
assert float(forecast[ATTR_FORECAST_TEMP_LOW]) == approx(expected, rel=0.1)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("native_unit", (PRESSURE_INHG, PRESSURE_INHG))
|
||||
@pytest.mark.parametrize(
|
||||
"state_unit, unit_system",
|
||||
((PRESSURE_HPA, METRIC_SYSTEM), (PRESSURE_INHG, IMPERIAL_SYSTEM)),
|
||||
)
|
||||
async def test_pressure(
|
||||
hass: HomeAssistant,
|
||||
enable_custom_integrations,
|
||||
native_unit: str,
|
||||
state_unit: str,
|
||||
unit_system,
|
||||
):
|
||||
"""Test pressure."""
|
||||
hass.config.units = unit_system
|
||||
native_value = 30
|
||||
state_value = convert_pressure(native_value, native_unit, state_unit)
|
||||
|
||||
entity0 = await create_entity(
|
||||
hass, native_pressure=native_value, native_pressure_unit=native_unit
|
||||
)
|
||||
state = hass.states.get(entity0.entity_id)
|
||||
forecast = state.attributes[ATTR_FORECAST][0]
|
||||
|
||||
expected = convert_pressure(native_value, native_unit, unit_system.pressure_unit)
|
||||
expected = state_value
|
||||
assert float(state.attributes[ATTR_WEATHER_PRESSURE]) == approx(expected, rel=1e-2)
|
||||
assert float(forecast[ATTR_FORECAST_PRESSURE]) == approx(expected, rel=1e-2)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("unit_system", [IMPERIAL_SYSTEM, METRIC_SYSTEM])
|
||||
async def test_wind_speed_conversion(
|
||||
hass,
|
||||
@pytest.mark.parametrize("native_unit", (None,))
|
||||
@pytest.mark.parametrize(
|
||||
"state_unit, unit_system",
|
||||
((PRESSURE_HPA, METRIC_SYSTEM), (PRESSURE_INHG, IMPERIAL_SYSTEM)),
|
||||
)
|
||||
async def test_pressure_no_unit(
|
||||
hass: HomeAssistant,
|
||||
enable_custom_integrations,
|
||||
native_unit: str,
|
||||
state_unit: str,
|
||||
unit_system,
|
||||
):
|
||||
"""Test wind speed conversion."""
|
||||
"""Test pressure when the entity does not declare a native unit."""
|
||||
hass.config.units = unit_system
|
||||
native_value = 10
|
||||
native_unit = SPEED_METERS_PER_SECOND
|
||||
native_value = 30
|
||||
state_value = native_value
|
||||
|
||||
entity0 = await create_entity(
|
||||
hass, wind_speed=native_value, wind_speed_unit=native_unit
|
||||
hass, native_pressure=native_value, native_pressure_unit=native_unit
|
||||
)
|
||||
state = hass.states.get(entity0.entity_id)
|
||||
forecast = state.attributes[ATTR_FORECAST][0]
|
||||
|
||||
expected = state_value
|
||||
assert float(state.attributes[ATTR_WEATHER_PRESSURE]) == approx(expected, rel=1e-2)
|
||||
assert float(forecast[ATTR_FORECAST_PRESSURE]) == approx(expected, rel=1e-2)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"native_unit",
|
||||
(SPEED_MILES_PER_HOUR, SPEED_KILOMETERS_PER_HOUR, SPEED_METERS_PER_SECOND),
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"state_unit, unit_system",
|
||||
(
|
||||
(SPEED_KILOMETERS_PER_HOUR, METRIC_SYSTEM),
|
||||
(SPEED_MILES_PER_HOUR, IMPERIAL_SYSTEM),
|
||||
),
|
||||
)
|
||||
async def test_wind_speed(
|
||||
hass: HomeAssistant,
|
||||
enable_custom_integrations,
|
||||
native_unit: str,
|
||||
state_unit: str,
|
||||
unit_system,
|
||||
):
|
||||
"""Test wind speed."""
|
||||
hass.config.units = unit_system
|
||||
native_value = 10
|
||||
state_value = convert_speed(native_value, native_unit, state_unit)
|
||||
|
||||
entity0 = await create_entity(
|
||||
hass, native_wind_speed=native_value, native_wind_speed_unit=native_unit
|
||||
)
|
||||
|
||||
state = hass.states.get(entity0.entity_id)
|
||||
forecast = state.attributes[ATTR_FORECAST][0]
|
||||
|
||||
expected = convert_speed(native_value, native_unit, unit_system.wind_speed_unit)
|
||||
expected = state_value
|
||||
assert float(state.attributes[ATTR_WEATHER_WIND_SPEED]) == approx(
|
||||
expected, rel=1e-2
|
||||
)
|
||||
assert float(forecast[ATTR_FORECAST_WIND_SPEED]) == approx(expected, rel=1e-2)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("unit_system", [IMPERIAL_SYSTEM, METRIC_SYSTEM])
|
||||
async def test_visibility_conversion(
|
||||
hass,
|
||||
@pytest.mark.parametrize("native_unit", (None,))
|
||||
@pytest.mark.parametrize(
|
||||
"state_unit, unit_system",
|
||||
(
|
||||
(SPEED_KILOMETERS_PER_HOUR, METRIC_SYSTEM),
|
||||
(SPEED_MILES_PER_HOUR, IMPERIAL_SYSTEM),
|
||||
),
|
||||
)
|
||||
async def test_wind_speed_no_unit(
|
||||
hass: HomeAssistant,
|
||||
enable_custom_integrations,
|
||||
native_unit: str,
|
||||
state_unit: str,
|
||||
unit_system,
|
||||
):
|
||||
"""Test visibility conversion."""
|
||||
"""Test wind speed when the entity does not declare a native unit."""
|
||||
hass.config.units = unit_system
|
||||
native_value = 10
|
||||
native_unit = LENGTH_MILES
|
||||
state_value = native_value
|
||||
|
||||
entity0 = await create_entity(
|
||||
hass, visibility=native_value, visibility_unit=native_unit
|
||||
hass, native_wind_speed=native_value, native_wind_speed_unit=native_unit
|
||||
)
|
||||
|
||||
state = hass.states.get(entity0.entity_id)
|
||||
expected = convert_distance(native_value, native_unit, unit_system.length_unit)
|
||||
forecast = state.attributes[ATTR_FORECAST][0]
|
||||
|
||||
expected = state_value
|
||||
assert float(state.attributes[ATTR_WEATHER_WIND_SPEED]) == approx(
|
||||
expected, rel=1e-2
|
||||
)
|
||||
assert float(forecast[ATTR_FORECAST_WIND_SPEED]) == approx(expected, rel=1e-2)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("native_unit", (LENGTH_MILES, LENGTH_KILOMETERS))
|
||||
@pytest.mark.parametrize(
|
||||
"state_unit, unit_system",
|
||||
(
|
||||
(LENGTH_KILOMETERS, METRIC_SYSTEM),
|
||||
(LENGTH_MILES, IMPERIAL_SYSTEM),
|
||||
),
|
||||
)
|
||||
async def test_visibility(
|
||||
hass: HomeAssistant,
|
||||
enable_custom_integrations,
|
||||
native_unit: str,
|
||||
state_unit: str,
|
||||
unit_system,
|
||||
):
|
||||
"""Test visibility."""
|
||||
hass.config.units = unit_system
|
||||
native_value = 10
|
||||
state_value = convert_distance(native_value, native_unit, state_unit)
|
||||
|
||||
entity0 = await create_entity(
|
||||
hass, native_visibility=native_value, native_visibility_unit=native_unit
|
||||
)
|
||||
|
||||
state = hass.states.get(entity0.entity_id)
|
||||
expected = state_value
|
||||
assert float(state.attributes[ATTR_WEATHER_VISIBILITY]) == approx(
|
||||
expected, rel=1e-2
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("unit_system", [IMPERIAL_SYSTEM, METRIC_SYSTEM])
|
||||
async def test_precipitation_conversion(
|
||||
hass,
|
||||
@pytest.mark.parametrize("native_unit", (None,))
|
||||
@pytest.mark.parametrize(
|
||||
"state_unit, unit_system",
|
||||
(
|
||||
(LENGTH_KILOMETERS, METRIC_SYSTEM),
|
||||
(LENGTH_MILES, IMPERIAL_SYSTEM),
|
||||
),
|
||||
)
|
||||
async def test_visibility_no_unit(
|
||||
hass: HomeAssistant,
|
||||
enable_custom_integrations,
|
||||
native_unit: str,
|
||||
state_unit: str,
|
||||
unit_system,
|
||||
):
|
||||
"""Test precipitation conversion."""
|
||||
"""Test visibility when the entity does not declare a native unit."""
|
||||
hass.config.units = unit_system
|
||||
native_value = 30
|
||||
native_unit = LENGTH_MILLIMETERS
|
||||
native_value = 10
|
||||
state_value = native_value
|
||||
|
||||
entity0 = await create_entity(
|
||||
hass, precipitation=native_value, precipitation_unit=native_unit
|
||||
hass, native_visibility=native_value, native_visibility_unit=native_unit
|
||||
)
|
||||
|
||||
state = hass.states.get(entity0.entity_id)
|
||||
expected = state_value
|
||||
assert float(state.attributes[ATTR_WEATHER_VISIBILITY]) == approx(
|
||||
expected, rel=1e-2
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("native_unit", (LENGTH_INCHES, LENGTH_MILLIMETERS))
|
||||
@pytest.mark.parametrize(
|
||||
"state_unit, unit_system",
|
||||
(
|
||||
(LENGTH_MILLIMETERS, METRIC_SYSTEM),
|
||||
(LENGTH_INCHES, IMPERIAL_SYSTEM),
|
||||
),
|
||||
)
|
||||
async def test_precipitation(
|
||||
hass: HomeAssistant,
|
||||
enable_custom_integrations,
|
||||
native_unit: str,
|
||||
state_unit: str,
|
||||
unit_system,
|
||||
):
|
||||
"""Test precipitation."""
|
||||
hass.config.units = unit_system
|
||||
native_value = 30
|
||||
state_value = convert_distance(native_value, native_unit, state_unit)
|
||||
|
||||
entity0 = await create_entity(
|
||||
hass, native_precipitation=native_value, native_precipitation_unit=native_unit
|
||||
)
|
||||
|
||||
state = hass.states.get(entity0.entity_id)
|
||||
forecast = state.attributes[ATTR_FORECAST][0]
|
||||
|
||||
expected = convert_distance(
|
||||
native_value, native_unit, unit_system.accumulated_precipitation_unit
|
||||
)
|
||||
expected = state_value
|
||||
assert float(forecast[ATTR_FORECAST_PRECIPITATION]) == approx(expected, rel=1e-2)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("native_unit", (None,))
|
||||
@pytest.mark.parametrize(
|
||||
"state_unit, unit_system",
|
||||
(
|
||||
(LENGTH_MILLIMETERS, METRIC_SYSTEM),
|
||||
(LENGTH_INCHES, IMPERIAL_SYSTEM),
|
||||
),
|
||||
)
|
||||
async def test_precipitation_no_unit(
|
||||
hass: HomeAssistant,
|
||||
enable_custom_integrations,
|
||||
native_unit: str,
|
||||
state_unit: str,
|
||||
unit_system,
|
||||
):
|
||||
"""Test precipitation when the entity does not declare a native unit."""
|
||||
hass.config.units = unit_system
|
||||
native_value = 30
|
||||
state_value = native_value
|
||||
|
||||
entity0 = await create_entity(
|
||||
hass, native_precipitation=native_value, native_precipitation_unit=native_unit
|
||||
)
|
||||
|
||||
state = hass.states.get(entity0.entity_id)
|
||||
forecast = state.attributes[ATTR_FORECAST][0]
|
||||
|
||||
expected = state_value
|
||||
assert float(forecast[ATTR_FORECAST_PRECIPITATION]) == approx(expected, rel=1e-2)
|
||||
|
||||
|
||||
async def test_wind_bearing_and_ozone(
|
||||
hass: HomeAssistant,
|
||||
enable_custom_integrations,
|
||||
):
|
||||
"""Test wind bearing."""
|
||||
wind_bearing_value = 180
|
||||
ozone_value = 10
|
||||
|
||||
entity0 = await create_entity(
|
||||
hass, wind_bearing=wind_bearing_value, ozone=ozone_value
|
||||
)
|
||||
|
||||
state = hass.states.get(entity0.entity_id)
|
||||
assert float(state.attributes[ATTR_WEATHER_WIND_BEARING]) == 180
|
||||
assert float(state.attributes[ATTR_WEATHER_OZONE]) == 10
|
||||
|
||||
|
||||
async def test_none_forecast(
|
||||
hass,
|
||||
hass: HomeAssistant,
|
||||
enable_custom_integrations,
|
||||
):
|
||||
"""Test that conversion with None values succeeds."""
|
||||
entity0 = await create_entity(
|
||||
hass,
|
||||
pressure=None,
|
||||
pressure_unit=PRESSURE_INHG,
|
||||
wind_speed=None,
|
||||
wind_speed_unit=SPEED_METERS_PER_SECOND,
|
||||
precipitation=None,
|
||||
precipitation_unit=LENGTH_MILLIMETERS,
|
||||
native_pressure=None,
|
||||
native_pressure_unit=PRESSURE_INHG,
|
||||
native_wind_speed=None,
|
||||
native_wind_speed_unit=SPEED_METERS_PER_SECOND,
|
||||
native_precipitation=None,
|
||||
native_precipitation_unit=LENGTH_MILLIMETERS,
|
||||
)
|
||||
|
||||
state = hass.states.get(entity0.entity_id)
|
||||
forecast = state.attributes[ATTR_FORECAST][0]
|
||||
|
||||
assert forecast[ATTR_FORECAST_PRESSURE] is None
|
||||
assert forecast[ATTR_FORECAST_WIND_SPEED] is None
|
||||
assert forecast[ATTR_FORECAST_PRECIPITATION] is None
|
||||
assert forecast.get(ATTR_FORECAST_PRESSURE) is None
|
||||
assert forecast.get(ATTR_FORECAST_WIND_SPEED) is None
|
||||
assert forecast.get(ATTR_FORECAST_PRECIPITATION) is None
|
||||
|
||||
|
||||
async def test_custom_units(hass: HomeAssistant, enable_custom_integrations) -> None:
|
||||
"""Test custom unit."""
|
||||
wind_speed_value = 5
|
||||
wind_speed_unit = SPEED_METERS_PER_SECOND
|
||||
pressure_value = 110
|
||||
pressure_unit = PRESSURE_HPA
|
||||
temperature_value = 20
|
||||
temperature_unit = TEMP_CELSIUS
|
||||
visibility_value = 11
|
||||
visibility_unit = LENGTH_KILOMETERS
|
||||
precipitation_value = 1.1
|
||||
precipitation_unit = LENGTH_MILLIMETERS
|
||||
|
||||
set_options = {
|
||||
"wind_speed_unit": SPEED_MILES_PER_HOUR,
|
||||
"precipitation_unit": LENGTH_INCHES,
|
||||
"pressure_unit": PRESSURE_INHG,
|
||||
"temperature_unit": TEMP_FAHRENHEIT,
|
||||
"visibility_unit": LENGTH_MILES,
|
||||
}
|
||||
|
||||
entity_registry = er.async_get(hass)
|
||||
|
||||
entry = entity_registry.async_get_or_create("weather", "test", "very_unique")
|
||||
entity_registry.async_update_entity_options(entry.entity_id, "weather", set_options)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
platform: WeatherPlatform = getattr(hass.components, "test.weather")
|
||||
platform.init(empty=True)
|
||||
platform.ENTITIES.append(
|
||||
platform.MockWeatherMockForecast(
|
||||
name="Test",
|
||||
condition=ATTR_CONDITION_SUNNY,
|
||||
native_temperature=temperature_value,
|
||||
native_temperature_unit=temperature_unit,
|
||||
native_wind_speed=wind_speed_value,
|
||||
native_wind_speed_unit=wind_speed_unit,
|
||||
native_pressure=pressure_value,
|
||||
native_pressure_unit=pressure_unit,
|
||||
native_visibility=visibility_value,
|
||||
native_visibility_unit=visibility_unit,
|
||||
native_precipitation=precipitation_value,
|
||||
native_precipitation_unit=precipitation_unit,
|
||||
unique_id="very_unique",
|
||||
)
|
||||
)
|
||||
|
||||
entity0 = platform.ENTITIES[0]
|
||||
assert await async_setup_component(
|
||||
hass, "weather", {"weather": {"platform": "test"}}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity0.entity_id)
|
||||
forecast = state.attributes[ATTR_FORECAST][0]
|
||||
|
||||
expected_wind_speed = round(
|
||||
convert_speed(wind_speed_value, wind_speed_unit, SPEED_MILES_PER_HOUR),
|
||||
ROUNDING_PRECISION,
|
||||
)
|
||||
expected_temperature = convert_temperature(
|
||||
temperature_value, temperature_unit, TEMP_FAHRENHEIT
|
||||
)
|
||||
expected_pressure = round(
|
||||
convert_pressure(pressure_value, pressure_unit, PRESSURE_INHG),
|
||||
ROUNDING_PRECISION,
|
||||
)
|
||||
expected_visibility = round(
|
||||
convert_distance(visibility_value, visibility_unit, LENGTH_MILES),
|
||||
ROUNDING_PRECISION,
|
||||
)
|
||||
expected_precipitation = round(
|
||||
convert_distance(precipitation_value, precipitation_unit, LENGTH_INCHES),
|
||||
ROUNDING_PRECISION,
|
||||
)
|
||||
|
||||
assert float(state.attributes[ATTR_WEATHER_WIND_SPEED]) == approx(
|
||||
expected_wind_speed
|
||||
)
|
||||
assert float(state.attributes[ATTR_WEATHER_TEMPERATURE]) == approx(
|
||||
expected_temperature, rel=0.1
|
||||
)
|
||||
assert float(state.attributes[ATTR_WEATHER_PRESSURE]) == approx(expected_pressure)
|
||||
assert float(state.attributes[ATTR_WEATHER_VISIBILITY]) == approx(
|
||||
expected_visibility
|
||||
)
|
||||
assert float(forecast[ATTR_FORECAST_PRECIPITATION]) == approx(
|
||||
expected_precipitation, rel=1e-2
|
||||
)
|
||||
|
||||
assert (
|
||||
state.attributes[ATTR_WEATHER_PRECIPITATION_UNIT]
|
||||
== set_options["precipitation_unit"]
|
||||
)
|
||||
assert state.attributes[ATTR_WEATHER_PRESSURE_UNIT] == set_options["pressure_unit"]
|
||||
assert (
|
||||
state.attributes[ATTR_WEATHER_TEMPERATURE_UNIT]
|
||||
== set_options["temperature_unit"]
|
||||
)
|
||||
assert (
|
||||
state.attributes[ATTR_WEATHER_VISIBILITY_UNIT] == set_options["visibility_unit"]
|
||||
)
|
||||
assert (
|
||||
state.attributes[ATTR_WEATHER_WIND_SPEED_UNIT] == set_options["wind_speed_unit"]
|
||||
)
|
||||
|
||||
|
||||
async def test_backwards_compatibility(
|
||||
hass: HomeAssistant, enable_custom_integrations
|
||||
) -> None:
|
||||
"""Test backwards compatibility."""
|
||||
wind_speed_value = 5
|
||||
wind_speed_unit = SPEED_METERS_PER_SECOND
|
||||
pressure_value = 110000
|
||||
pressure_unit = PRESSURE_PA
|
||||
temperature_value = 20
|
||||
temperature_unit = TEMP_CELSIUS
|
||||
visibility_value = 11
|
||||
visibility_unit = LENGTH_KILOMETERS
|
||||
precipitation_value = 1
|
||||
precipitation_unit = LENGTH_MILLIMETERS
|
||||
|
||||
hass.config.units = METRIC_SYSTEM
|
||||
|
||||
platform: WeatherPlatform = getattr(hass.components, "test.weather")
|
||||
platform.init(empty=True)
|
||||
platform.ENTITIES.append(
|
||||
platform.MockWeatherMockForecastCompat(
|
||||
name="Test",
|
||||
condition=ATTR_CONDITION_SUNNY,
|
||||
temperature=temperature_value,
|
||||
temperature_unit=temperature_unit,
|
||||
wind_speed=wind_speed_value,
|
||||
wind_speed_unit=wind_speed_unit,
|
||||
pressure=pressure_value,
|
||||
pressure_unit=pressure_unit,
|
||||
visibility=visibility_value,
|
||||
visibility_unit=visibility_unit,
|
||||
precipitation=precipitation_value,
|
||||
precipitation_unit=precipitation_unit,
|
||||
unique_id="very_unique",
|
||||
)
|
||||
)
|
||||
platform.ENTITIES.append(
|
||||
platform.MockWeatherMockForecastCompat(
|
||||
name="Test2",
|
||||
condition=ATTR_CONDITION_SUNNY,
|
||||
temperature=temperature_value,
|
||||
temperature_unit=temperature_unit,
|
||||
wind_speed=wind_speed_value,
|
||||
pressure=pressure_value,
|
||||
visibility=visibility_value,
|
||||
precipitation=precipitation_value,
|
||||
unique_id="very_unique2",
|
||||
)
|
||||
)
|
||||
|
||||
entity0 = platform.ENTITIES[0]
|
||||
entity1 = platform.ENTITIES[1]
|
||||
assert await async_setup_component(
|
||||
hass, "weather", {"weather": {"platform": "test"}}
|
||||
)
|
||||
assert await async_setup_component(
|
||||
hass, "weather", {"weather": {"platform": "test2"}}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity0.entity_id)
|
||||
forecast = state.attributes[ATTR_FORECAST][0]
|
||||
state1 = hass.states.get(entity1.entity_id)
|
||||
forecast1 = state1.attributes[ATTR_FORECAST][0]
|
||||
|
||||
assert float(state.attributes[ATTR_WEATHER_WIND_SPEED]) == approx(
|
||||
wind_speed_value * 3.6
|
||||
)
|
||||
assert state.attributes[ATTR_WEATHER_WIND_SPEED_UNIT] == SPEED_KILOMETERS_PER_HOUR
|
||||
assert float(state.attributes[ATTR_WEATHER_TEMPERATURE]) == approx(
|
||||
temperature_value, rel=0.1
|
||||
)
|
||||
assert state.attributes[ATTR_WEATHER_TEMPERATURE_UNIT] == TEMP_CELSIUS
|
||||
assert float(state.attributes[ATTR_WEATHER_PRESSURE]) == approx(
|
||||
pressure_value / 100
|
||||
)
|
||||
assert state.attributes[ATTR_WEATHER_PRESSURE_UNIT] == PRESSURE_HPA
|
||||
assert float(state.attributes[ATTR_WEATHER_VISIBILITY]) == approx(visibility_value)
|
||||
assert state.attributes[ATTR_WEATHER_VISIBILITY_UNIT] == LENGTH_KILOMETERS
|
||||
assert float(forecast[ATTR_FORECAST_PRECIPITATION]) == approx(
|
||||
precipitation_value, rel=1e-2
|
||||
)
|
||||
assert state.attributes[ATTR_WEATHER_PRECIPITATION_UNIT] == LENGTH_MILLIMETERS
|
||||
|
||||
assert float(state1.attributes[ATTR_WEATHER_WIND_SPEED]) == approx(wind_speed_value)
|
||||
assert state1.attributes[ATTR_WEATHER_WIND_SPEED_UNIT] == SPEED_KILOMETERS_PER_HOUR
|
||||
assert float(state1.attributes[ATTR_WEATHER_TEMPERATURE]) == approx(
|
||||
temperature_value, rel=0.1
|
||||
)
|
||||
assert state1.attributes[ATTR_WEATHER_TEMPERATURE_UNIT] == TEMP_CELSIUS
|
||||
assert float(state1.attributes[ATTR_WEATHER_PRESSURE]) == approx(pressure_value)
|
||||
assert state1.attributes[ATTR_WEATHER_PRESSURE_UNIT] == PRESSURE_HPA
|
||||
assert float(state1.attributes[ATTR_WEATHER_VISIBILITY]) == approx(visibility_value)
|
||||
assert state1.attributes[ATTR_WEATHER_VISIBILITY_UNIT] == LENGTH_KILOMETERS
|
||||
assert float(forecast1[ATTR_FORECAST_PRECIPITATION]) == approx(
|
||||
precipitation_value, rel=1e-2
|
||||
)
|
||||
assert state1.attributes[ATTR_WEATHER_PRECIPITATION_UNIT] == LENGTH_MILLIMETERS
|
||||
|
||||
|
||||
async def test_backwards_compatibility_convert_values(
|
||||
hass: HomeAssistant, enable_custom_integrations
|
||||
) -> None:
|
||||
"""Test backward compatibility for converting values."""
|
||||
wind_speed_value = 5
|
||||
wind_speed_unit = SPEED_METERS_PER_SECOND
|
||||
pressure_value = 110000
|
||||
pressure_unit = PRESSURE_PA
|
||||
temperature_value = 20
|
||||
temperature_unit = TEMP_CELSIUS
|
||||
visibility_value = 11
|
||||
visibility_unit = LENGTH_KILOMETERS
|
||||
precipitation_value = 1
|
||||
precipitation_unit = LENGTH_MILLIMETERS
|
||||
|
||||
hass.config.units = IMPERIAL_SYSTEM
|
||||
|
||||
platform: WeatherPlatform = getattr(hass.components, "test.weather")
|
||||
platform.init(empty=True)
|
||||
platform.ENTITIES.append(
|
||||
platform.MockWeatherMockForecastCompat(
|
||||
name="Test",
|
||||
condition=ATTR_CONDITION_SUNNY,
|
||||
temperature=temperature_value,
|
||||
temperature_unit=temperature_unit,
|
||||
wind_speed=wind_speed_value,
|
||||
wind_speed_unit=wind_speed_unit,
|
||||
pressure=pressure_value,
|
||||
pressure_unit=pressure_unit,
|
||||
visibility=visibility_value,
|
||||
visibility_unit=visibility_unit,
|
||||
precipitation=precipitation_value,
|
||||
precipitation_unit=precipitation_unit,
|
||||
unique_id="very_unique",
|
||||
)
|
||||
)
|
||||
|
||||
entity0 = platform.ENTITIES[0]
|
||||
assert await async_setup_component(
|
||||
hass, "weather", {"weather": {"platform": "test"}}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity0.entity_id)
|
||||
|
||||
expected_wind_speed = round(
|
||||
convert_speed(wind_speed_value, wind_speed_unit, SPEED_MILES_PER_HOUR),
|
||||
ROUNDING_PRECISION,
|
||||
)
|
||||
expected_temperature = convert_temperature(
|
||||
temperature_value, temperature_unit, TEMP_FAHRENHEIT
|
||||
)
|
||||
expected_pressure = round(
|
||||
convert_pressure(pressure_value, pressure_unit, PRESSURE_INHG),
|
||||
ROUNDING_PRECISION,
|
||||
)
|
||||
expected_visibility = round(
|
||||
convert_distance(visibility_value, visibility_unit, LENGTH_MILES),
|
||||
ROUNDING_PRECISION,
|
||||
)
|
||||
expected_precipitation = round(
|
||||
convert_distance(precipitation_value, precipitation_unit, LENGTH_INCHES),
|
||||
ROUNDING_PRECISION,
|
||||
)
|
||||
|
||||
assert state.attributes == {
|
||||
ATTR_FORECAST: [
|
||||
{
|
||||
ATTR_FORECAST_PRECIPITATION: approx(expected_precipitation, rel=0.1),
|
||||
ATTR_FORECAST_PRESSURE: approx(expected_pressure, rel=0.1),
|
||||
ATTR_FORECAST_TEMP: approx(expected_temperature, rel=0.1),
|
||||
ATTR_FORECAST_TEMP_LOW: approx(expected_temperature, rel=0.1),
|
||||
ATTR_FORECAST_WIND_BEARING: None,
|
||||
ATTR_FORECAST_WIND_SPEED: approx(expected_wind_speed, rel=0.1),
|
||||
}
|
||||
],
|
||||
ATTR_FRIENDLY_NAME: "Test",
|
||||
ATTR_WEATHER_PRECIPITATION_UNIT: LENGTH_INCHES,
|
||||
ATTR_WEATHER_PRESSURE: approx(expected_pressure, rel=0.1),
|
||||
ATTR_WEATHER_PRESSURE_UNIT: PRESSURE_INHG,
|
||||
ATTR_WEATHER_TEMPERATURE: approx(expected_temperature, rel=0.1),
|
||||
ATTR_WEATHER_TEMPERATURE_UNIT: TEMP_FAHRENHEIT,
|
||||
ATTR_WEATHER_VISIBILITY: approx(expected_visibility, rel=0.1),
|
||||
ATTR_WEATHER_VISIBILITY_UNIT: LENGTH_MILES,
|
||||
ATTR_WEATHER_WIND_SPEED: approx(expected_wind_speed, rel=0.1),
|
||||
ATTR_WEATHER_WIND_SPEED_UNIT: SPEED_MILES_PER_HOUR,
|
||||
}
|
||||
|
||||
|
||||
async def test_backwards_compatibility_round_temperature(hass: HomeAssistant) -> None:
|
||||
"""Test backward compatibility for rounding temperature."""
|
||||
|
||||
assert round_temperature(20.3, PRECISION_HALVES) == 20.5
|
||||
assert round_temperature(20.3, PRECISION_TENTHS) == 20.3
|
||||
assert round_temperature(20.3, PRECISION_WHOLE) == 20
|
||||
assert round_temperature(None, PRECISION_WHOLE) is None
|
||||
|
||||
|
||||
async def test_attr(hass: HomeAssistant) -> None:
|
||||
"""Test the _attr attributes."""
|
||||
|
||||
weather = MockWeatherEntity()
|
||||
weather.hass = hass
|
||||
|
||||
assert weather.condition == ATTR_CONDITION_SUNNY
|
||||
assert weather.native_precipitation_unit == LENGTH_MILLIMETERS
|
||||
assert weather._precipitation_unit == LENGTH_MILLIMETERS
|
||||
assert weather.native_pressure == 10
|
||||
assert weather.native_pressure_unit == PRESSURE_HPA
|
||||
assert weather._pressure_unit == PRESSURE_HPA
|
||||
assert weather.native_temperature == 20
|
||||
assert weather.native_temperature_unit == TEMP_CELSIUS
|
||||
assert weather._temperature_unit == TEMP_CELSIUS
|
||||
assert weather.native_visibility == 30
|
||||
assert weather.native_visibility_unit == LENGTH_KILOMETERS
|
||||
assert weather._visibility_unit == LENGTH_KILOMETERS
|
||||
assert weather.native_wind_speed == 3
|
||||
assert weather.native_wind_speed_unit == SPEED_METERS_PER_SECOND
|
||||
assert weather._wind_speed_unit == SPEED_KILOMETERS_PER_HOUR
|
||||
|
||||
|
||||
async def test_attr_compatibility(hass: HomeAssistant) -> None:
|
||||
"""Test the _attr attributes in compatibility mode."""
|
||||
|
||||
weather = MockWeatherEntityCompat()
|
||||
weather.hass = hass
|
||||
|
||||
assert weather.condition == ATTR_CONDITION_SUNNY
|
||||
assert weather._precipitation_unit == LENGTH_MILLIMETERS
|
||||
assert weather.pressure == 10
|
||||
assert weather._pressure_unit == PRESSURE_HPA
|
||||
assert weather.temperature == 20
|
||||
assert weather._temperature_unit == TEMP_CELSIUS
|
||||
assert weather.visibility == 30
|
||||
assert weather.visibility_unit == LENGTH_KILOMETERS
|
||||
assert weather.wind_speed == 3
|
||||
assert weather._wind_speed_unit == SPEED_KILOMETERS_PER_HOUR
|
||||
|
||||
forecast_entry = [
|
||||
Forecast(
|
||||
datetime=datetime(2022, 6, 20, 20, 00, 00),
|
||||
precipitation=1,
|
||||
temperature=20,
|
||||
)
|
||||
]
|
||||
|
||||
assert weather.forecast == forecast_entry
|
||||
|
||||
assert weather.state_attributes == {
|
||||
ATTR_FORECAST: forecast_entry,
|
||||
ATTR_WEATHER_PRESSURE: 10.0,
|
||||
ATTR_WEATHER_PRESSURE_UNIT: PRESSURE_HPA,
|
||||
ATTR_WEATHER_TEMPERATURE: 20.0,
|
||||
ATTR_WEATHER_TEMPERATURE_UNIT: TEMP_CELSIUS,
|
||||
ATTR_WEATHER_VISIBILITY: 30.0,
|
||||
ATTR_WEATHER_VISIBILITY_UNIT: LENGTH_KILOMETERS,
|
||||
ATTR_WEATHER_WIND_SPEED: 3.0 * 3.6,
|
||||
ATTR_WEATHER_WIND_SPEED_UNIT: SPEED_KILOMETERS_PER_HOUR,
|
||||
ATTR_WEATHER_PRECIPITATION_UNIT: LENGTH_MILLIMETERS,
|
||||
}
|
||||
|
||||
|
||||
async def test_precision_for_temperature(hass: HomeAssistant) -> None:
|
||||
"""Test the precision for temperature."""
|
||||
|
||||
weather = MockWeatherEntityPrecision()
|
||||
weather.hass = hass
|
||||
|
||||
assert weather.condition == ATTR_CONDITION_SUNNY
|
||||
assert weather.native_temperature == 20.3
|
||||
assert weather._temperature_unit == TEMP_CELSIUS
|
||||
assert weather.precision == PRECISION_HALVES
|
||||
|
||||
assert weather.state_attributes[ATTR_WEATHER_TEMPERATURE] == 20.5
|
||||
|
@ -6,6 +6,11 @@ Call init before using it in your tests to ensure clean test data.
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components.weather import (
|
||||
ATTR_FORECAST_NATIVE_PRECIPITATION,
|
||||
ATTR_FORECAST_NATIVE_PRESSURE,
|
||||
ATTR_FORECAST_NATIVE_TEMP,
|
||||
ATTR_FORECAST_NATIVE_TEMP_LOW,
|
||||
ATTR_FORECAST_NATIVE_WIND_SPEED,
|
||||
ATTR_FORECAST_PRECIPITATION,
|
||||
ATTR_FORECAST_PRESSURE,
|
||||
ATTR_FORECAST_TEMP,
|
||||
@ -37,6 +42,80 @@ async def async_setup_platform(
|
||||
class MockWeather(MockEntity, WeatherEntity):
|
||||
"""Mock weather class."""
|
||||
|
||||
@property
|
||||
def native_temperature(self) -> float | None:
|
||||
"""Return the platform temperature."""
|
||||
return self._handle("native_temperature")
|
||||
|
||||
@property
|
||||
def native_temperature_unit(self) -> str | None:
|
||||
"""Return the unit of measurement for temperature."""
|
||||
return self._handle("native_temperature_unit")
|
||||
|
||||
@property
|
||||
def native_pressure(self) -> float | None:
|
||||
"""Return the pressure."""
|
||||
return self._handle("native_pressure")
|
||||
|
||||
@property
|
||||
def native_pressure_unit(self) -> str | None:
|
||||
"""Return the unit of measurement for pressure."""
|
||||
return self._handle("native_pressure_unit")
|
||||
|
||||
@property
|
||||
def humidity(self) -> float | None:
|
||||
"""Return the humidity."""
|
||||
return self._handle("humidity")
|
||||
|
||||
@property
|
||||
def native_wind_speed(self) -> float | None:
|
||||
"""Return the wind speed."""
|
||||
return self._handle("native_wind_speed")
|
||||
|
||||
@property
|
||||
def native_wind_speed_unit(self) -> str | None:
|
||||
"""Return the unit of measurement for wind speed."""
|
||||
return self._handle("native_wind_speed_unit")
|
||||
|
||||
@property
|
||||
def wind_bearing(self) -> float | str | None:
|
||||
"""Return the wind bearing."""
|
||||
return self._handle("wind_bearing")
|
||||
|
||||
@property
|
||||
def ozone(self) -> float | None:
|
||||
"""Return the ozone level."""
|
||||
return self._handle("ozone")
|
||||
|
||||
@property
|
||||
def native_visibility(self) -> float | None:
|
||||
"""Return the visibility."""
|
||||
return self._handle("native_visibility")
|
||||
|
||||
@property
|
||||
def native_visibility_unit(self) -> str | None:
|
||||
"""Return the unit of measurement for visibility."""
|
||||
return self._handle("native_visibility_unit")
|
||||
|
||||
@property
|
||||
def forecast(self) -> list[Forecast] | None:
|
||||
"""Return the forecast."""
|
||||
return self._handle("forecast")
|
||||
|
||||
@property
|
||||
def native_precipitation_unit(self) -> str | None:
|
||||
"""Return the native unit of measurement for accumulated precipitation."""
|
||||
return self._handle("native_precipitation_unit")
|
||||
|
||||
@property
|
||||
def condition(self) -> str | None:
|
||||
"""Return the current condition."""
|
||||
return self._handle("condition")
|
||||
|
||||
|
||||
class MockWeatherCompat(MockEntity, WeatherEntity):
|
||||
"""Mock weather class for backwards compatibility check."""
|
||||
|
||||
@property
|
||||
def temperature(self) -> float | None:
|
||||
"""Return the platform temperature."""
|
||||
@ -99,7 +178,7 @@ class MockWeather(MockEntity, WeatherEntity):
|
||||
|
||||
@property
|
||||
def precipitation_unit(self) -> str | None:
|
||||
"""Return the native unit of measurement for accumulated precipitation."""
|
||||
"""Return the unit of measurement for accumulated precipitation."""
|
||||
return self._handle("precipitation_unit")
|
||||
|
||||
@property
|
||||
@ -111,6 +190,26 @@ class MockWeather(MockEntity, WeatherEntity):
|
||||
class MockWeatherMockForecast(MockWeather):
|
||||
"""Mock weather class with mocked forecast."""
|
||||
|
||||
@property
|
||||
def forecast(self) -> list[Forecast] | None:
|
||||
"""Return the forecast."""
|
||||
return [
|
||||
{
|
||||
ATTR_FORECAST_NATIVE_TEMP: self.native_temperature,
|
||||
ATTR_FORECAST_NATIVE_TEMP_LOW: self.native_temperature,
|
||||
ATTR_FORECAST_NATIVE_PRESSURE: self.native_pressure,
|
||||
ATTR_FORECAST_NATIVE_WIND_SPEED: self.native_wind_speed,
|
||||
ATTR_FORECAST_WIND_BEARING: self.wind_bearing,
|
||||
ATTR_FORECAST_NATIVE_PRECIPITATION: self._values.get(
|
||||
"native_precipitation"
|
||||
),
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
class MockWeatherMockForecastCompat(MockWeatherCompat):
|
||||
"""Mock weather class with mocked forecast for compatibility check."""
|
||||
|
||||
@property
|
||||
def forecast(self) -> list[Forecast] | None:
|
||||
"""Return the forecast."""
|
||||
|
Loading…
x
Reference in New Issue
Block a user