mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 22:27:07 +00:00
Fix tomorrowio sensor units and conversions (#69166)
Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
parent
a95b4f6763
commit
6965a6d13b
@ -34,7 +34,6 @@ from homeassistant.const import (
|
|||||||
PRESSURE_INHG,
|
PRESSURE_INHG,
|
||||||
SPEED_METERS_PER_SECOND,
|
SPEED_METERS_PER_SECOND,
|
||||||
SPEED_MILES_PER_HOUR,
|
SPEED_MILES_PER_HOUR,
|
||||||
TEMP_CELSIUS,
|
|
||||||
TEMP_FAHRENHEIT,
|
TEMP_FAHRENHEIT,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@ -42,7 +41,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||||||
from homeassistant.util import slugify
|
from homeassistant.util import slugify
|
||||||
from homeassistant.util.distance import convert as distance_convert
|
from homeassistant.util.distance import convert as distance_convert
|
||||||
from homeassistant.util.pressure import convert as pressure_convert
|
from homeassistant.util.pressure import convert as pressure_convert
|
||||||
from homeassistant.util.temperature import convert as temp_convert
|
|
||||||
|
|
||||||
from . import TomorrowioDataUpdateCoordinator, TomorrowioEntity
|
from . import TomorrowioDataUpdateCoordinator, TomorrowioEntity
|
||||||
from .const import (
|
from .const import (
|
||||||
@ -81,48 +79,61 @@ class TomorrowioSensorEntityDescription(SensorEntityDescription):
|
|||||||
|
|
||||||
unit_imperial: str | None = None
|
unit_imperial: str | None = None
|
||||||
unit_metric: str | None = None
|
unit_metric: str | None = None
|
||||||
metric_conversion: Callable[[float], float] | float = 1.0
|
multiplication_factor: Callable[[float], float] | float | None = None
|
||||||
is_metric_check: bool | None = None
|
metric_conversion: Callable[[float], float] | float | None = None
|
||||||
value_map: Any | None = None
|
value_map: Any | None = None
|
||||||
|
|
||||||
|
def __post_init__(self) -> None:
|
||||||
|
"""Handle post init."""
|
||||||
|
if (self.unit_imperial is None and self.unit_metric is not None) or (
|
||||||
|
self.unit_imperial is not None and self.unit_metric is None
|
||||||
|
):
|
||||||
|
raise ValueError(
|
||||||
|
"Entity descriptions must include both imperial and metric units or "
|
||||||
|
"they must both be None"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# From https://cfpub.epa.gov/ncer_abstracts/index.cfm/fuseaction/display.files/fileID/14285
|
||||||
|
# x ug/m^3 = y ppb * molecular weight / 24.45
|
||||||
|
def convert_ppb_to_ugm3(molecular_weight: int | float) -> Callable[[float], float]:
|
||||||
|
"""Return function to convert ppb to ug/m^3."""
|
||||||
|
return lambda x: (x * molecular_weight) / 24.45
|
||||||
|
|
||||||
|
|
||||||
SENSOR_TYPES = (
|
SENSOR_TYPES = (
|
||||||
TomorrowioSensorEntityDescription(
|
TomorrowioSensorEntityDescription(
|
||||||
key=TMRW_ATTR_FEELS_LIKE,
|
key=TMRW_ATTR_FEELS_LIKE,
|
||||||
name="Feels Like",
|
name="Feels Like",
|
||||||
unit_imperial=TEMP_FAHRENHEIT,
|
native_unit_of_measurement=TEMP_FAHRENHEIT,
|
||||||
unit_metric=TEMP_CELSIUS,
|
|
||||||
metric_conversion=lambda val: temp_convert(val, TEMP_FAHRENHEIT, TEMP_CELSIUS),
|
|
||||||
is_metric_check=True,
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
),
|
),
|
||||||
TomorrowioSensorEntityDescription(
|
TomorrowioSensorEntityDescription(
|
||||||
key=TMRW_ATTR_DEW_POINT,
|
key=TMRW_ATTR_DEW_POINT,
|
||||||
name="Dew Point",
|
name="Dew Point",
|
||||||
unit_imperial=TEMP_FAHRENHEIT,
|
native_unit_of_measurement=TEMP_FAHRENHEIT,
|
||||||
unit_metric=TEMP_CELSIUS,
|
|
||||||
metric_conversion=lambda val: temp_convert(val, TEMP_FAHRENHEIT, TEMP_CELSIUS),
|
|
||||||
is_metric_check=True,
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
),
|
),
|
||||||
|
# Data comes in as inHg
|
||||||
TomorrowioSensorEntityDescription(
|
TomorrowioSensorEntityDescription(
|
||||||
key=TMRW_ATTR_PRESSURE_SURFACE_LEVEL,
|
key=TMRW_ATTR_PRESSURE_SURFACE_LEVEL,
|
||||||
name="Pressure (Surface Level)",
|
name="Pressure (Surface Level)",
|
||||||
unit_metric=PRESSURE_HPA,
|
native_unit_of_measurement=PRESSURE_HPA,
|
||||||
metric_conversion=lambda val: pressure_convert(
|
multiplication_factor=lambda val: pressure_convert(
|
||||||
val, PRESSURE_INHG, PRESSURE_HPA
|
val, PRESSURE_INHG, PRESSURE_HPA
|
||||||
),
|
),
|
||||||
is_metric_check=True,
|
|
||||||
device_class=SensorDeviceClass.PRESSURE,
|
device_class=SensorDeviceClass.PRESSURE,
|
||||||
),
|
),
|
||||||
|
# Data comes in as BTUs/(hr * ft^2)
|
||||||
|
# https://www.theunitconverter.com/watt-square-meter-to-btu-hour-square-foot-conversion/
|
||||||
TomorrowioSensorEntityDescription(
|
TomorrowioSensorEntityDescription(
|
||||||
key=TMRW_ATTR_SOLAR_GHI,
|
key=TMRW_ATTR_SOLAR_GHI,
|
||||||
name="Global Horizontal Irradiance",
|
name="Global Horizontal Irradiance",
|
||||||
unit_imperial=IRRADIATION_BTUS_PER_HOUR_SQUARE_FOOT,
|
unit_imperial=IRRADIATION_BTUS_PER_HOUR_SQUARE_FOOT,
|
||||||
unit_metric=IRRADIATION_WATTS_PER_SQUARE_METER,
|
unit_metric=IRRADIATION_WATTS_PER_SQUARE_METER,
|
||||||
metric_conversion=3.15459,
|
metric_conversion=3.15459,
|
||||||
is_metric_check=True,
|
|
||||||
),
|
),
|
||||||
|
# Data comes in as miles
|
||||||
TomorrowioSensorEntityDescription(
|
TomorrowioSensorEntityDescription(
|
||||||
key=TMRW_ATTR_CLOUD_BASE,
|
key=TMRW_ATTR_CLOUD_BASE,
|
||||||
name="Cloud Base",
|
name="Cloud Base",
|
||||||
@ -131,8 +142,8 @@ SENSOR_TYPES = (
|
|||||||
metric_conversion=lambda val: distance_convert(
|
metric_conversion=lambda val: distance_convert(
|
||||||
val, LENGTH_MILES, LENGTH_KILOMETERS
|
val, LENGTH_MILES, LENGTH_KILOMETERS
|
||||||
),
|
),
|
||||||
is_metric_check=True,
|
|
||||||
),
|
),
|
||||||
|
# Data comes in as miles
|
||||||
TomorrowioSensorEntityDescription(
|
TomorrowioSensorEntityDescription(
|
||||||
key=TMRW_ATTR_CLOUD_CEILING,
|
key=TMRW_ATTR_CLOUD_CEILING,
|
||||||
name="Cloud Ceiling",
|
name="Cloud Ceiling",
|
||||||
@ -141,14 +152,13 @@ SENSOR_TYPES = (
|
|||||||
metric_conversion=lambda val: distance_convert(
|
metric_conversion=lambda val: distance_convert(
|
||||||
val, LENGTH_MILES, LENGTH_KILOMETERS
|
val, LENGTH_MILES, LENGTH_KILOMETERS
|
||||||
),
|
),
|
||||||
is_metric_check=True,
|
|
||||||
),
|
),
|
||||||
TomorrowioSensorEntityDescription(
|
TomorrowioSensorEntityDescription(
|
||||||
key=TMRW_ATTR_CLOUD_COVER,
|
key=TMRW_ATTR_CLOUD_COVER,
|
||||||
name="Cloud Cover",
|
name="Cloud Cover",
|
||||||
unit_imperial=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
unit_metric=PERCENTAGE,
|
|
||||||
),
|
),
|
||||||
|
# Data comes in as MPH
|
||||||
TomorrowioSensorEntityDescription(
|
TomorrowioSensorEntityDescription(
|
||||||
key=TMRW_ATTR_WIND_GUST,
|
key=TMRW_ATTR_WIND_GUST,
|
||||||
name="Wind Gust",
|
name="Wind Gust",
|
||||||
@ -156,7 +166,6 @@ SENSOR_TYPES = (
|
|||||||
unit_metric=SPEED_METERS_PER_SECOND,
|
unit_metric=SPEED_METERS_PER_SECOND,
|
||||||
metric_conversion=lambda val: distance_convert(val, LENGTH_MILES, LENGTH_METERS)
|
metric_conversion=lambda val: distance_convert(val, LENGTH_MILES, LENGTH_METERS)
|
||||||
/ 3600,
|
/ 3600,
|
||||||
is_metric_check=True,
|
|
||||||
),
|
),
|
||||||
TomorrowioSensorEntityDescription(
|
TomorrowioSensorEntityDescription(
|
||||||
key=TMRW_ATTR_PRECIPITATION_TYPE,
|
key=TMRW_ATTR_PRECIPITATION_TYPE,
|
||||||
@ -165,51 +174,54 @@ SENSOR_TYPES = (
|
|||||||
device_class="tomorrowio__precipitation_type",
|
device_class="tomorrowio__precipitation_type",
|
||||||
icon="mdi:weather-snowy-rainy",
|
icon="mdi:weather-snowy-rainy",
|
||||||
),
|
),
|
||||||
|
# Data comes in as ppb
|
||||||
|
# Molecular weight of Ozone is 48
|
||||||
TomorrowioSensorEntityDescription(
|
TomorrowioSensorEntityDescription(
|
||||||
key=TMRW_ATTR_OZONE,
|
key=TMRW_ATTR_OZONE,
|
||||||
name="Ozone",
|
name="Ozone",
|
||||||
unit_metric=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
metric_conversion=2.03,
|
multiplication_factor=convert_ppb_to_ugm3(48),
|
||||||
is_metric_check=True,
|
|
||||||
device_class=SensorDeviceClass.OZONE,
|
device_class=SensorDeviceClass.OZONE,
|
||||||
),
|
),
|
||||||
|
# Data comes in as ug/ft^3
|
||||||
TomorrowioSensorEntityDescription(
|
TomorrowioSensorEntityDescription(
|
||||||
key=TMRW_ATTR_PARTICULATE_MATTER_25,
|
key=TMRW_ATTR_PARTICULATE_MATTER_25,
|
||||||
name="Particulate Matter < 2.5 μm",
|
name="Particulate Matter < 2.5 μm",
|
||||||
unit_metric=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
metric_conversion=3.2808399**3,
|
multiplication_factor=3.2808399**3,
|
||||||
is_metric_check=True,
|
|
||||||
device_class=SensorDeviceClass.PM25,
|
device_class=SensorDeviceClass.PM25,
|
||||||
),
|
),
|
||||||
|
# Data comes in as ug/ft^3
|
||||||
TomorrowioSensorEntityDescription(
|
TomorrowioSensorEntityDescription(
|
||||||
key=TMRW_ATTR_PARTICULATE_MATTER_10,
|
key=TMRW_ATTR_PARTICULATE_MATTER_10,
|
||||||
name="Particulate Matter < 10 μm",
|
name="Particulate Matter < 10 μm",
|
||||||
unit_metric=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
metric_conversion=3.2808399**3,
|
multiplication_factor=3.2808399**3,
|
||||||
is_metric_check=True,
|
|
||||||
device_class=SensorDeviceClass.PM10,
|
device_class=SensorDeviceClass.PM10,
|
||||||
),
|
),
|
||||||
|
# Data comes in as ppb
|
||||||
|
# Molecular weight of Nitrogen Dioxide is 46.01
|
||||||
TomorrowioSensorEntityDescription(
|
TomorrowioSensorEntityDescription(
|
||||||
key=TMRW_ATTR_NITROGEN_DIOXIDE,
|
key=TMRW_ATTR_NITROGEN_DIOXIDE,
|
||||||
name="Nitrogen Dioxide",
|
name="Nitrogen Dioxide",
|
||||||
unit_metric=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
metric_conversion=1.95,
|
multiplication_factor=convert_ppb_to_ugm3(46.01),
|
||||||
is_metric_check=True,
|
|
||||||
device_class=SensorDeviceClass.NITROGEN_DIOXIDE,
|
device_class=SensorDeviceClass.NITROGEN_DIOXIDE,
|
||||||
),
|
),
|
||||||
|
# Data comes in as ppb
|
||||||
TomorrowioSensorEntityDescription(
|
TomorrowioSensorEntityDescription(
|
||||||
key=TMRW_ATTR_CARBON_MONOXIDE,
|
key=TMRW_ATTR_CARBON_MONOXIDE,
|
||||||
name="Carbon Monoxide",
|
name="Carbon Monoxide",
|
||||||
unit_imperial=CONCENTRATION_PARTS_PER_MILLION,
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||||
unit_metric=CONCENTRATION_PARTS_PER_MILLION,
|
multiplication_factor=1 / 1000,
|
||||||
device_class=SensorDeviceClass.CO,
|
device_class=SensorDeviceClass.CO,
|
||||||
),
|
),
|
||||||
|
# Molecular weight of Sulphur Dioxide is 64.07
|
||||||
TomorrowioSensorEntityDescription(
|
TomorrowioSensorEntityDescription(
|
||||||
key=TMRW_ATTR_SULPHUR_DIOXIDE,
|
key=TMRW_ATTR_SULPHUR_DIOXIDE,
|
||||||
name="Sulphur Dioxide",
|
name="Sulphur Dioxide",
|
||||||
unit_metric=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
metric_conversion=2.71,
|
multiplication_factor=convert_ppb_to_ugm3(64.07),
|
||||||
is_metric_check=True,
|
|
||||||
device_class=SensorDeviceClass.SULPHUR_DIOXIDE,
|
device_class=SensorDeviceClass.SULPHUR_DIOXIDE,
|
||||||
),
|
),
|
||||||
TomorrowioSensorEntityDescription(
|
TomorrowioSensorEntityDescription(
|
||||||
@ -289,6 +301,16 @@ async def async_setup_entry(
|
|||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_conversion(
|
||||||
|
value: float | int, conversion: Callable[[float], float] | float
|
||||||
|
) -> float:
|
||||||
|
"""Handle conversion of a value based on conversion type."""
|
||||||
|
if callable(conversion):
|
||||||
|
return round(conversion(float(value)), 2)
|
||||||
|
|
||||||
|
return round(float(value) * conversion, 2)
|
||||||
|
|
||||||
|
|
||||||
class BaseTomorrowioSensorEntity(TomorrowioEntity, SensorEntity):
|
class BaseTomorrowioSensorEntity(TomorrowioEntity, SensorEntity):
|
||||||
"""Base Tomorrow.io sensor entity."""
|
"""Base Tomorrow.io sensor entity."""
|
||||||
|
|
||||||
@ -311,47 +333,42 @@ class BaseTomorrowioSensorEntity(TomorrowioEntity, SensorEntity):
|
|||||||
f"{self._config_entry.unique_id}_{slugify(description.name)}"
|
f"{self._config_entry.unique_id}_{slugify(description.name)}"
|
||||||
)
|
)
|
||||||
self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: self.attribution}
|
self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: self.attribution}
|
||||||
# Fallback to metric always in case imperial isn't defined (for metric only
|
if self.entity_description.native_unit_of_measurement is None:
|
||||||
# sensors)
|
self._attr_native_unit_of_measurement = (
|
||||||
self._attr_native_unit_of_measurement = (
|
description.unit_metric
|
||||||
description.unit_metric
|
if hass.config.units.is_metric
|
||||||
if hass.config.units.is_metric
|
else description.unit_imperial
|
||||||
else description.unit_imperial
|
)
|
||||||
) or description.unit_metric
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def _state(self) -> str | int | float | None:
|
def _state(self) -> int | float | None:
|
||||||
"""Return the raw state."""
|
"""Return the raw state."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> str | int | float | None:
|
def native_value(self) -> str | int | float | None:
|
||||||
"""Return the state."""
|
"""Return the state."""
|
||||||
state = self._state
|
state = self._state
|
||||||
|
desc = self.entity_description
|
||||||
|
|
||||||
|
if state is None:
|
||||||
|
return state
|
||||||
|
|
||||||
|
if desc.value_map is not None:
|
||||||
|
return desc.value_map(state).name.lower()
|
||||||
|
|
||||||
|
if desc.multiplication_factor is not None:
|
||||||
|
state = handle_conversion(state, desc.multiplication_factor)
|
||||||
|
|
||||||
# If an imperial unit isn't provided, we always want to convert to metric since
|
# If an imperial unit isn't provided, we always want to convert to metric since
|
||||||
# that is what the UI expects
|
# that is what the UI expects
|
||||||
if state is not None and (
|
if (
|
||||||
(
|
desc.metric_conversion
|
||||||
self.entity_description.metric_conversion != 1.0
|
and desc.unit_imperial is not None
|
||||||
and self.entity_description.is_metric_check is not None
|
and desc.unit_imperial != desc.unit_metric
|
||||||
and self.hass.config.units.is_metric
|
and self.hass.config.units.is_metric
|
||||||
== self.entity_description.is_metric_check
|
|
||||||
)
|
|
||||||
or (
|
|
||||||
self.entity_description.unit_imperial is None
|
|
||||||
and self.entity_description.unit_metric is not None
|
|
||||||
)
|
|
||||||
):
|
):
|
||||||
conversion = self.entity_description.metric_conversion
|
return handle_conversion(state, desc.metric_conversion)
|
||||||
# When conversion is a callable, we assume it's a single input function
|
|
||||||
if callable(conversion):
|
|
||||||
return round(conversion(float(state)), 2)
|
|
||||||
|
|
||||||
return round(float(state) * conversion, 2)
|
|
||||||
|
|
||||||
if self.entity_description.value_map is not None and state is not None:
|
|
||||||
return self.entity_description.value_map(state).name.lower()
|
|
||||||
|
|
||||||
return state
|
return state
|
||||||
|
|
||||||
@ -360,6 +377,8 @@ class TomorrowioSensorEntity(BaseTomorrowioSensorEntity):
|
|||||||
"""Sensor entity that talks to Tomorrow.io v4 API to retrieve non-weather data."""
|
"""Sensor entity that talks to Tomorrow.io v4 API to retrieve non-weather data."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _state(self) -> str | int | float | None:
|
def _state(self) -> int | float | None:
|
||||||
"""Return the raw state."""
|
"""Return the raw state."""
|
||||||
return self._get_current_property(self.entity_description.key)
|
val = self._get_current_property(self.entity_description.key)
|
||||||
|
assert not isinstance(val, str)
|
||||||
|
return val
|
||||||
|
@ -5,6 +5,8 @@ from datetime import datetime
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||||
from homeassistant.components.tomorrowio.config_flow import (
|
from homeassistant.components.tomorrowio.config_flow import (
|
||||||
_get_config_schema,
|
_get_config_schema,
|
||||||
@ -17,6 +19,7 @@ from homeassistant.components.tomorrowio.const import (
|
|||||||
DEFAULT_TIMESTEP,
|
DEFAULT_TIMESTEP,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
)
|
)
|
||||||
|
from homeassistant.components.tomorrowio.sensor import TomorrowioSensorEntityDescription
|
||||||
from homeassistant.config_entries import SOURCE_USER
|
from homeassistant.config_entries import SOURCE_USER
|
||||||
from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME
|
from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME
|
||||||
from homeassistant.core import HomeAssistant, State, callback
|
from homeassistant.core import HomeAssistant, State, callback
|
||||||
@ -139,10 +142,10 @@ def check_sensor_state(hass: HomeAssistant, entity_name: str, value: str):
|
|||||||
async def test_v4_sensor(hass: HomeAssistant) -> None:
|
async def test_v4_sensor(hass: HomeAssistant) -> None:
|
||||||
"""Test v4 sensor data."""
|
"""Test v4 sensor data."""
|
||||||
await _setup(hass, V4_FIELDS, API_V4_ENTRY_DATA)
|
await _setup(hass, V4_FIELDS, API_V4_ENTRY_DATA)
|
||||||
check_sensor_state(hass, O3, "94.46")
|
check_sensor_state(hass, O3, "91.35")
|
||||||
check_sensor_state(hass, CO, "0.63")
|
check_sensor_state(hass, CO, "0.0")
|
||||||
check_sensor_state(hass, NO2, "20.81")
|
check_sensor_state(hass, NO2, "20.08")
|
||||||
check_sensor_state(hass, SO2, "4.47")
|
check_sensor_state(hass, SO2, "4.32")
|
||||||
check_sensor_state(hass, PM25, "5.3")
|
check_sensor_state(hass, PM25, "5.3")
|
||||||
check_sensor_state(hass, PM10, "20.13")
|
check_sensor_state(hass, PM10, "20.13")
|
||||||
check_sensor_state(hass, MEP_AQI, "23")
|
check_sensor_state(hass, MEP_AQI, "23")
|
||||||
@ -164,3 +167,9 @@ async def test_v4_sensor(hass: HomeAssistant) -> None:
|
|||||||
check_sensor_state(hass, CLOUD_CEILING, "1.19")
|
check_sensor_state(hass, CLOUD_CEILING, "1.19")
|
||||||
check_sensor_state(hass, WIND_GUST, "5.65")
|
check_sensor_state(hass, WIND_GUST, "5.65")
|
||||||
check_sensor_state(hass, PRECIPITATION_TYPE, "rain")
|
check_sensor_state(hass, PRECIPITATION_TYPE, "rain")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_entity_description() -> None:
|
||||||
|
"""Test improper entity description raises."""
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
TomorrowioSensorEntityDescription("a", unit_imperial="b")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user