Weather unit conversion (#73441)

Co-authored-by: Erik <erik@montnemery.com>
This commit is contained in:
G Johansson 2022-06-23 10:48:30 +02:00 committed by GitHub
parent bccec77e19
commit 90e1fb6ce2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1531 additions and 194 deletions

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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."""