mirror of
https://github.com/home-assistant/core.git
synced 2025-07-17 02:07:09 +00:00
Allow customizing sensor state precision (#86074)
* Allow customizing sensor precision * Don't convert integer strings to floats * Tweak converting sensor state to number * Drop default rounding to 2 decimals * Adjust test * Tweak rounding, improve test coverage * Don't convert to a number if not necessary * Raise if native_precision is set and state is not numeric * Address review comments * Address comments, simplify * Don't call property twice * Make exception more helpful
This commit is contained in:
parent
ba63a9600e
commit
086a6460ef
@ -8,7 +8,7 @@ from dataclasses import dataclass
|
|||||||
from datetime import date, datetime, timedelta, timezone
|
from datetime import date, datetime, timedelta, timezone
|
||||||
from decimal import Decimal, InvalidOperation as DecimalInvalidOperation
|
from decimal import Decimal, InvalidOperation as DecimalInvalidOperation
|
||||||
import logging
|
import logging
|
||||||
from math import floor, log10
|
from math import ceil, floor, log10
|
||||||
from typing import Any, Final, cast, final
|
from typing import Any, Final, cast, final
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
@ -133,11 +133,12 @@ class SensorEntityDescription(EntityDescription):
|
|||||||
"""A class that describes sensor entities."""
|
"""A class that describes sensor entities."""
|
||||||
|
|
||||||
device_class: SensorDeviceClass | None = None
|
device_class: SensorDeviceClass | None = None
|
||||||
suggested_unit_of_measurement: str | None = None
|
|
||||||
last_reset: datetime | None = None
|
last_reset: datetime | None = None
|
||||||
|
native_precision: int | None = None
|
||||||
native_unit_of_measurement: str | None = None
|
native_unit_of_measurement: str | None = None
|
||||||
state_class: SensorStateClass | str | None = None
|
|
||||||
options: list[str] | None = None
|
options: list[str] | None = None
|
||||||
|
state_class: SensorStateClass | str | None = None
|
||||||
|
suggested_unit_of_measurement: str | None = None
|
||||||
unit_of_measurement: None = None # Type override, use native_unit_of_measurement
|
unit_of_measurement: None = None # Type override, use native_unit_of_measurement
|
||||||
|
|
||||||
|
|
||||||
@ -147,6 +148,7 @@ class SensorEntity(Entity):
|
|||||||
entity_description: SensorEntityDescription
|
entity_description: SensorEntityDescription
|
||||||
_attr_device_class: SensorDeviceClass | None
|
_attr_device_class: SensorDeviceClass | None
|
||||||
_attr_last_reset: datetime | None
|
_attr_last_reset: datetime | None
|
||||||
|
_attr_native_precision: int | None
|
||||||
_attr_native_unit_of_measurement: str | None
|
_attr_native_unit_of_measurement: str | None
|
||||||
_attr_native_value: StateType | date | datetime | Decimal = None
|
_attr_native_value: StateType | date | datetime | Decimal = None
|
||||||
_attr_options: list[str] | None
|
_attr_options: list[str] | None
|
||||||
@ -160,6 +162,7 @@ class SensorEntity(Entity):
|
|||||||
_invalid_state_class_reported = False
|
_invalid_state_class_reported = False
|
||||||
_invalid_unit_of_measurement_reported = False
|
_invalid_unit_of_measurement_reported = False
|
||||||
_last_reset_reported = False
|
_last_reset_reported = False
|
||||||
|
_sensor_option_precision: int | None = None
|
||||||
_sensor_option_unit_of_measurement: str | None | UndefinedType = UNDEFINED
|
_sensor_option_unit_of_measurement: str | None | UndefinedType = UNDEFINED
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
@ -337,6 +340,60 @@ class SensorEntity(Entity):
|
|||||||
"""Return the value reported by the sensor."""
|
"""Return the value reported by the sensor."""
|
||||||
return self._attr_native_value
|
return self._attr_native_value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_precision(self) -> int | None:
|
||||||
|
"""Return the number of digits after the decimal point for the sensor's state.
|
||||||
|
|
||||||
|
If native_precision is None, no rounding is done unless the sensor is subject
|
||||||
|
to unit conversion.
|
||||||
|
|
||||||
|
The display precision is influenced by unit conversion, a sensor which has
|
||||||
|
native_unit_of_measurement 'Wh' and is converted to 'kWh' will have its
|
||||||
|
native_precision increased by 3.
|
||||||
|
"""
|
||||||
|
if hasattr(self, "_attr_native_precision"):
|
||||||
|
return self._attr_native_precision
|
||||||
|
if hasattr(self, "entity_description"):
|
||||||
|
return self.entity_description.native_precision
|
||||||
|
return None
|
||||||
|
|
||||||
|
@final
|
||||||
|
@property
|
||||||
|
def precision(self) -> int | None:
|
||||||
|
"""Return the number of digits after the decimal point for the sensor's state.
|
||||||
|
|
||||||
|
This is the precision after unit conversion.
|
||||||
|
"""
|
||||||
|
# Highest priority, for registered entities: precision set by user
|
||||||
|
if self._sensor_option_precision is not None:
|
||||||
|
return self._sensor_option_precision
|
||||||
|
|
||||||
|
# Second priority, native precision
|
||||||
|
if (precision := self.native_precision) is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
device_class = self.device_class
|
||||||
|
native_unit_of_measurement = self.native_unit_of_measurement
|
||||||
|
unit_of_measurement = self.unit_of_measurement
|
||||||
|
|
||||||
|
if (
|
||||||
|
native_unit_of_measurement != unit_of_measurement
|
||||||
|
and device_class in UNIT_CONVERTERS
|
||||||
|
):
|
||||||
|
converter = UNIT_CONVERTERS[device_class]
|
||||||
|
|
||||||
|
# Scale the precision when converting to a larger or smaller unit
|
||||||
|
# For example 1.1 Wh should be rendered as 0.0011 kWh, not 0.0 kWh
|
||||||
|
ratio_log = log10(
|
||||||
|
converter.get_unit_ratio(
|
||||||
|
native_unit_of_measurement, unit_of_measurement
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ratio_log = floor(ratio_log) if ratio_log > 0 else ceil(ratio_log)
|
||||||
|
precision = max(0, precision + ratio_log)
|
||||||
|
|
||||||
|
return precision
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_unit_of_measurement(self) -> str | None:
|
def native_unit_of_measurement(self) -> str | None:
|
||||||
"""Return the unit of measurement of the sensor, if any."""
|
"""Return the unit of measurement of the sensor, if any."""
|
||||||
@ -399,7 +456,7 @@ class SensorEntity(Entity):
|
|||||||
|
|
||||||
@final
|
@final
|
||||||
@property
|
@property
|
||||||
def state(self) -> Any:
|
def state(self) -> Any: # noqa: C901
|
||||||
"""Return the state of the sensor and perform unit conversions, if needed."""
|
"""Return the state of the sensor and perform unit conversions, if needed."""
|
||||||
native_unit_of_measurement = self.native_unit_of_measurement
|
native_unit_of_measurement = self.native_unit_of_measurement
|
||||||
unit_of_measurement = self.unit_of_measurement
|
unit_of_measurement = self.unit_of_measurement
|
||||||
@ -508,17 +565,36 @@ class SensorEntity(Entity):
|
|||||||
)
|
)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
# If the sensor has neither a device class, a state class nor
|
precision = self.precision
|
||||||
# a unit_of measurement then there are no further checks or conversions
|
|
||||||
if not device_class and not state_class and not unit_of_measurement:
|
# If the sensor has neither a device class, a state class, a unit of measurement
|
||||||
|
# nor a precision then there are no further checks or conversions
|
||||||
|
if (
|
||||||
|
not device_class
|
||||||
|
and not state_class
|
||||||
|
and not unit_of_measurement
|
||||||
|
and precision is None
|
||||||
|
):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
if not self._invalid_numeric_value_reported and not isinstance(
|
# From here on a numerical value is expected
|
||||||
value, (int, float, Decimal)
|
numerical_value: int | float | Decimal
|
||||||
):
|
if not isinstance(value, (int, float, Decimal)):
|
||||||
try:
|
try:
|
||||||
_ = float(value) # type: ignore[arg-type]
|
if isinstance(value, str) and "." not in value:
|
||||||
except (TypeError, ValueError):
|
numerical_value = int(value)
|
||||||
|
else:
|
||||||
|
numerical_value = float(value) # type:ignore[arg-type]
|
||||||
|
except (TypeError, ValueError) as err:
|
||||||
|
# Raise if precision is not None, for other cases log a warning
|
||||||
|
if precision is not None:
|
||||||
|
raise ValueError(
|
||||||
|
f"Sensor {self.entity_id} has device class {device_class}, "
|
||||||
|
f"state class {state_class} unit {unit_of_measurement} and "
|
||||||
|
f"precision {precision} thus indicating it has a numeric value;"
|
||||||
|
f" however, it has the non-numeric value: {value} "
|
||||||
|
f"({type(value)})"
|
||||||
|
) from err
|
||||||
# This should raise in Home Assistant Core 2023.4
|
# This should raise in Home Assistant Core 2023.4
|
||||||
self._invalid_numeric_value_reported = True
|
self._invalid_numeric_value_reported = True
|
||||||
report_issue = self._suggest_report_issue()
|
report_issue = self._suggest_report_issue()
|
||||||
@ -536,39 +612,43 @@ class SensorEntity(Entity):
|
|||||||
report_issue,
|
report_issue,
|
||||||
)
|
)
|
||||||
return value
|
return value
|
||||||
|
else:
|
||||||
|
numerical_value = value
|
||||||
|
|
||||||
if (
|
if (
|
||||||
native_unit_of_measurement != unit_of_measurement
|
native_unit_of_measurement != unit_of_measurement
|
||||||
and device_class in UNIT_CONVERTERS
|
and device_class in UNIT_CONVERTERS
|
||||||
):
|
):
|
||||||
|
# Unit conversion needed
|
||||||
converter = UNIT_CONVERTERS[device_class]
|
converter = UNIT_CONVERTERS[device_class]
|
||||||
|
|
||||||
value_s = str(value)
|
if precision is None:
|
||||||
prec = len(value_s) - value_s.index(".") - 1 if "." in value_s else 0
|
# Deduce the precision by finding the decimal point, if any
|
||||||
|
value_s = str(value)
|
||||||
# Scale the precision when converting to a larger unit
|
precision = (
|
||||||
# For example 1.1 Wh should be rendered as 0.0011 kWh, not 0.0 kWh
|
len(value_s) - value_s.index(".") - 1 if "." in value_s else 0
|
||||||
ratio_log = max(
|
|
||||||
0,
|
|
||||||
log10(
|
|
||||||
converter.get_unit_ratio(
|
|
||||||
native_unit_of_measurement, unit_of_measurement
|
|
||||||
)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
prec = prec + floor(ratio_log)
|
|
||||||
|
|
||||||
# Suppress ValueError (Could not convert sensor_value to float)
|
|
||||||
with suppress(ValueError):
|
|
||||||
value_f = float(value) # type: ignore[arg-type]
|
|
||||||
value_f_new = converter.convert(
|
|
||||||
value_f,
|
|
||||||
native_unit_of_measurement,
|
|
||||||
unit_of_measurement,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Round to the wanted precision
|
# Scale the precision when converting to a larger unit
|
||||||
value = round(value_f_new) if prec == 0 else round(value_f_new, prec)
|
# For example 1.1 Wh should be rendered as 0.0011 kWh, not 0.0 kWh
|
||||||
|
ratio_log = max(
|
||||||
|
0,
|
||||||
|
log10(
|
||||||
|
converter.get_unit_ratio(
|
||||||
|
native_unit_of_measurement, unit_of_measurement
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
precision = precision + floor(ratio_log)
|
||||||
|
|
||||||
|
converted_numerical_value = converter.convert(
|
||||||
|
float(numerical_value),
|
||||||
|
native_unit_of_measurement,
|
||||||
|
unit_of_measurement,
|
||||||
|
)
|
||||||
|
value = f"{converted_numerical_value:.{precision}f}"
|
||||||
|
elif precision is not None:
|
||||||
|
value = f"{numerical_value:.{precision}f}"
|
||||||
|
|
||||||
# Validate unit of measurement used for sensors with a device class
|
# Validate unit of measurement used for sensors with a device class
|
||||||
if (
|
if (
|
||||||
@ -608,6 +688,15 @@ class SensorEntity(Entity):
|
|||||||
|
|
||||||
return super().__repr__()
|
return super().__repr__()
|
||||||
|
|
||||||
|
def _custom_precision_or_none(self) -> int | None:
|
||||||
|
"""Return a custom precisions or None if not set."""
|
||||||
|
assert self.registry_entry
|
||||||
|
if (sensor_options := self.registry_entry.options.get(DOMAIN)) and (
|
||||||
|
precision := sensor_options.get("precision")
|
||||||
|
) is not None:
|
||||||
|
return int(precision)
|
||||||
|
return None
|
||||||
|
|
||||||
def _custom_unit_or_undef(
|
def _custom_unit_or_undef(
|
||||||
self, primary_key: str, secondary_key: str
|
self, primary_key: str, secondary_key: str
|
||||||
) -> str | None | UndefinedType:
|
) -> str | None | UndefinedType:
|
||||||
@ -628,6 +717,7 @@ class SensorEntity(Entity):
|
|||||||
@callback
|
@callback
|
||||||
def async_registry_entry_updated(self) -> None:
|
def async_registry_entry_updated(self) -> None:
|
||||||
"""Run when the entity registry entry has been updated."""
|
"""Run when the entity registry entry has been updated."""
|
||||||
|
self._sensor_option_precision = self._custom_precision_or_none()
|
||||||
self._sensor_option_unit_of_measurement = self._custom_unit_or_undef(
|
self._sensor_option_unit_of_measurement = self._custom_unit_or_undef(
|
||||||
DOMAIN, CONF_UNIT_OF_MEASUREMENT
|
DOMAIN, CONF_UNIT_OF_MEASUREMENT
|
||||||
)
|
)
|
||||||
|
@ -20,7 +20,7 @@ async def test_airzone_create_sensors(
|
|||||||
|
|
||||||
# Zones
|
# Zones
|
||||||
state = hass.states.get("sensor.despacho_temperature")
|
state = hass.states.get("sensor.despacho_temperature")
|
||||||
assert state.state == "21.2"
|
assert state.state == "21.20"
|
||||||
|
|
||||||
state = hass.states.get("sensor.despacho_humidity")
|
state = hass.states.get("sensor.despacho_humidity")
|
||||||
assert state.state == "36"
|
assert state.state == "36"
|
||||||
|
@ -41,23 +41,29 @@ from tests.common import mock_restore_cache_with_extra_data
|
|||||||
UnitOfTemperature.FAHRENHEIT,
|
UnitOfTemperature.FAHRENHEIT,
|
||||||
UnitOfTemperature.FAHRENHEIT,
|
UnitOfTemperature.FAHRENHEIT,
|
||||||
100,
|
100,
|
||||||
100,
|
"100",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
US_CUSTOMARY_SYSTEM,
|
US_CUSTOMARY_SYSTEM,
|
||||||
UnitOfTemperature.CELSIUS,
|
UnitOfTemperature.CELSIUS,
|
||||||
UnitOfTemperature.FAHRENHEIT,
|
UnitOfTemperature.FAHRENHEIT,
|
||||||
38,
|
38,
|
||||||
100,
|
"100",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
METRIC_SYSTEM,
|
METRIC_SYSTEM,
|
||||||
UnitOfTemperature.FAHRENHEIT,
|
UnitOfTemperature.FAHRENHEIT,
|
||||||
UnitOfTemperature.CELSIUS,
|
UnitOfTemperature.CELSIUS,
|
||||||
100,
|
100,
|
||||||
38,
|
"38",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
METRIC_SYSTEM,
|
||||||
|
UnitOfTemperature.CELSIUS,
|
||||||
|
UnitOfTemperature.CELSIUS,
|
||||||
|
38,
|
||||||
|
"38",
|
||||||
),
|
),
|
||||||
(METRIC_SYSTEM, UnitOfTemperature.CELSIUS, UnitOfTemperature.CELSIUS, 38, 38),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_temperature_conversion(
|
async def test_temperature_conversion(
|
||||||
@ -85,7 +91,7 @@ async def test_temperature_conversion(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get(entity0.entity_id)
|
state = hass.states.get(entity0.entity_id)
|
||||||
assert float(state.state) == approx(float(state_value))
|
assert state.state == state_value
|
||||||
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == state_unit
|
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == state_unit
|
||||||
|
|
||||||
|
|
||||||
@ -407,7 +413,7 @@ async def test_restore_sensor_restore_state(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"device_class,native_unit,custom_unit,state_unit,native_value,custom_value",
|
"device_class, native_unit, custom_unit, state_unit, native_value, custom_state",
|
||||||
[
|
[
|
||||||
# Smaller to larger unit, InHg is ~33x larger than hPa -> 1 more decimal
|
# Smaller to larger unit, InHg is ~33x larger than hPa -> 1 more decimal
|
||||||
(
|
(
|
||||||
@ -416,7 +422,7 @@ async def test_restore_sensor_restore_state(
|
|||||||
UnitOfPressure.INHG,
|
UnitOfPressure.INHG,
|
||||||
UnitOfPressure.INHG,
|
UnitOfPressure.INHG,
|
||||||
1000.0,
|
1000.0,
|
||||||
29.53,
|
"29.53",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
SensorDeviceClass.PRESSURE,
|
SensorDeviceClass.PRESSURE,
|
||||||
@ -424,7 +430,15 @@ async def test_restore_sensor_restore_state(
|
|||||||
UnitOfPressure.HPA,
|
UnitOfPressure.HPA,
|
||||||
UnitOfPressure.HPA,
|
UnitOfPressure.HPA,
|
||||||
1.234,
|
1.234,
|
||||||
12.34,
|
"12.340",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
SensorDeviceClass.ATMOSPHERIC_PRESSURE,
|
||||||
|
UnitOfPressure.HPA,
|
||||||
|
UnitOfPressure.MMHG,
|
||||||
|
UnitOfPressure.MMHG,
|
||||||
|
1000,
|
||||||
|
"750",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
SensorDeviceClass.PRESSURE,
|
SensorDeviceClass.PRESSURE,
|
||||||
@ -432,7 +446,7 @@ async def test_restore_sensor_restore_state(
|
|||||||
UnitOfPressure.MMHG,
|
UnitOfPressure.MMHG,
|
||||||
UnitOfPressure.MMHG,
|
UnitOfPressure.MMHG,
|
||||||
1000,
|
1000,
|
||||||
750,
|
"750",
|
||||||
),
|
),
|
||||||
# Not a supported pressure unit
|
# Not a supported pressure unit
|
||||||
(
|
(
|
||||||
@ -441,7 +455,7 @@ async def test_restore_sensor_restore_state(
|
|||||||
"peer_pressure",
|
"peer_pressure",
|
||||||
UnitOfPressure.HPA,
|
UnitOfPressure.HPA,
|
||||||
1000,
|
1000,
|
||||||
1000,
|
"1000",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
SensorDeviceClass.TEMPERATURE,
|
SensorDeviceClass.TEMPERATURE,
|
||||||
@ -449,7 +463,7 @@ async def test_restore_sensor_restore_state(
|
|||||||
UnitOfTemperature.FAHRENHEIT,
|
UnitOfTemperature.FAHRENHEIT,
|
||||||
UnitOfTemperature.FAHRENHEIT,
|
UnitOfTemperature.FAHRENHEIT,
|
||||||
37.5,
|
37.5,
|
||||||
99.5,
|
"99.5",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
SensorDeviceClass.TEMPERATURE,
|
SensorDeviceClass.TEMPERATURE,
|
||||||
@ -457,7 +471,7 @@ async def test_restore_sensor_restore_state(
|
|||||||
UnitOfTemperature.CELSIUS,
|
UnitOfTemperature.CELSIUS,
|
||||||
UnitOfTemperature.CELSIUS,
|
UnitOfTemperature.CELSIUS,
|
||||||
100,
|
100,
|
||||||
38.0,
|
"38",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -469,7 +483,7 @@ async def test_custom_unit(
|
|||||||
custom_unit,
|
custom_unit,
|
||||||
state_unit,
|
state_unit,
|
||||||
native_value,
|
native_value,
|
||||||
custom_value,
|
custom_state,
|
||||||
):
|
):
|
||||||
"""Test custom unit."""
|
"""Test custom unit."""
|
||||||
entity_registry = er.async_get(hass)
|
entity_registry = er.async_get(hass)
|
||||||
@ -495,12 +509,184 @@ async def test_custom_unit(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get(entity0.entity_id)
|
state = hass.states.get(entity0.entity_id)
|
||||||
assert float(state.state) == approx(float(custom_value))
|
assert state.state == custom_state
|
||||||
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == state_unit
|
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == state_unit
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"native_unit,custom_unit,state_unit,native_value,custom_value,device_class",
|
"device_class,native_unit,custom_unit,native_value,native_precision,default_state,custom_state",
|
||||||
|
[
|
||||||
|
(
|
||||||
|
SensorDeviceClass.ATMOSPHERIC_PRESSURE,
|
||||||
|
UnitOfPressure.HPA,
|
||||||
|
UnitOfPressure.INHG,
|
||||||
|
1000.0,
|
||||||
|
2,
|
||||||
|
"1000.00", # Native precision is 2
|
||||||
|
"29.530", # One digit of precision added when converting
|
||||||
|
),
|
||||||
|
(
|
||||||
|
SensorDeviceClass.ATMOSPHERIC_PRESSURE,
|
||||||
|
UnitOfPressure.INHG,
|
||||||
|
UnitOfPressure.HPA,
|
||||||
|
29.9211,
|
||||||
|
3,
|
||||||
|
"29.921", # Native precision is 3
|
||||||
|
"1013.24", # One digit of precision removed when converting
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_native_precision_scaling(
|
||||||
|
hass,
|
||||||
|
enable_custom_integrations,
|
||||||
|
device_class,
|
||||||
|
native_unit,
|
||||||
|
custom_unit,
|
||||||
|
native_value,
|
||||||
|
native_precision,
|
||||||
|
default_state,
|
||||||
|
custom_state,
|
||||||
|
):
|
||||||
|
"""Test native precision is influenced by unit conversion."""
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
|
||||||
|
entry = entity_registry.async_get_or_create("sensor", "test", "very_unique")
|
||||||
|
platform = getattr(hass.components, "test.sensor")
|
||||||
|
platform.init(empty=True)
|
||||||
|
platform.ENTITIES["0"] = platform.MockSensor(
|
||||||
|
name="Test",
|
||||||
|
native_value=str(native_value),
|
||||||
|
native_precision=native_precision,
|
||||||
|
native_unit_of_measurement=native_unit,
|
||||||
|
device_class=device_class,
|
||||||
|
unique_id="very_unique",
|
||||||
|
)
|
||||||
|
|
||||||
|
entity0 = platform.ENTITIES["0"]
|
||||||
|
assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(entity0.entity_id)
|
||||||
|
assert state.state == default_state
|
||||||
|
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == native_unit
|
||||||
|
|
||||||
|
entity_registry.async_update_entity_options(
|
||||||
|
entry.entity_id, "sensor", {"unit_of_measurement": custom_unit}
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(entity0.entity_id)
|
||||||
|
assert state.state == custom_state
|
||||||
|
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == custom_unit
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"device_class,native_unit,custom_precision,native_value,default_state,custom_state",
|
||||||
|
[
|
||||||
|
(
|
||||||
|
SensorDeviceClass.ATMOSPHERIC_PRESSURE,
|
||||||
|
UnitOfPressure.HPA,
|
||||||
|
4,
|
||||||
|
1000.0,
|
||||||
|
"1000.000",
|
||||||
|
"1000.0000",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_custom_precision_native_precision(
|
||||||
|
hass,
|
||||||
|
enable_custom_integrations,
|
||||||
|
device_class,
|
||||||
|
native_unit,
|
||||||
|
custom_precision,
|
||||||
|
native_value,
|
||||||
|
default_state,
|
||||||
|
custom_state,
|
||||||
|
):
|
||||||
|
"""Test custom precision."""
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
|
||||||
|
entry = entity_registry.async_get_or_create("sensor", "test", "very_unique")
|
||||||
|
platform = getattr(hass.components, "test.sensor")
|
||||||
|
platform.init(empty=True)
|
||||||
|
platform.ENTITIES["0"] = platform.MockSensor(
|
||||||
|
name="Test",
|
||||||
|
native_value=str(native_value),
|
||||||
|
native_precision=3,
|
||||||
|
native_unit_of_measurement=native_unit,
|
||||||
|
device_class=device_class,
|
||||||
|
unique_id="very_unique",
|
||||||
|
)
|
||||||
|
|
||||||
|
entity0 = platform.ENTITIES["0"]
|
||||||
|
assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(entity0.entity_id)
|
||||||
|
assert state.state == default_state
|
||||||
|
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == native_unit
|
||||||
|
|
||||||
|
entity_registry.async_update_entity_options(
|
||||||
|
entry.entity_id, "sensor", {"precision": custom_precision}
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(entity0.entity_id)
|
||||||
|
assert state.state == custom_state
|
||||||
|
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == native_unit
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"device_class,native_unit,custom_precision,native_value,custom_state",
|
||||||
|
[
|
||||||
|
(
|
||||||
|
SensorDeviceClass.ATMOSPHERIC_PRESSURE,
|
||||||
|
UnitOfPressure.HPA,
|
||||||
|
4,
|
||||||
|
1000.0,
|
||||||
|
"1000.0000",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_custom_precision_no_native_precision(
|
||||||
|
hass,
|
||||||
|
enable_custom_integrations,
|
||||||
|
device_class,
|
||||||
|
native_unit,
|
||||||
|
custom_precision,
|
||||||
|
native_value,
|
||||||
|
custom_state,
|
||||||
|
):
|
||||||
|
"""Test custom precision."""
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
|
||||||
|
entry = entity_registry.async_get_or_create("sensor", "test", "very_unique")
|
||||||
|
entity_registry.async_update_entity_options(
|
||||||
|
entry.entity_id, "sensor", {"precision": custom_precision}
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
platform = getattr(hass.components, "test.sensor")
|
||||||
|
platform.init(empty=True)
|
||||||
|
platform.ENTITIES["0"] = platform.MockSensor(
|
||||||
|
name="Test",
|
||||||
|
native_value=str(native_value),
|
||||||
|
native_unit_of_measurement=native_unit,
|
||||||
|
device_class=device_class,
|
||||||
|
unique_id="very_unique",
|
||||||
|
)
|
||||||
|
|
||||||
|
entity0 = platform.ENTITIES["0"]
|
||||||
|
assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(entity0.entity_id)
|
||||||
|
assert state.state == custom_state
|
||||||
|
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == native_unit
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"native_unit, custom_unit, state_unit, native_value, native_state, custom_state, device_class",
|
||||||
[
|
[
|
||||||
# Distance
|
# Distance
|
||||||
(
|
(
|
||||||
@ -508,7 +694,8 @@ async def test_custom_unit(
|
|||||||
UnitOfLength.MILES,
|
UnitOfLength.MILES,
|
||||||
UnitOfLength.MILES,
|
UnitOfLength.MILES,
|
||||||
1000,
|
1000,
|
||||||
621,
|
"1000",
|
||||||
|
"621",
|
||||||
SensorDeviceClass.DISTANCE,
|
SensorDeviceClass.DISTANCE,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@ -516,7 +703,8 @@ async def test_custom_unit(
|
|||||||
UnitOfLength.INCHES,
|
UnitOfLength.INCHES,
|
||||||
UnitOfLength.INCHES,
|
UnitOfLength.INCHES,
|
||||||
7.24,
|
7.24,
|
||||||
2.85,
|
"7.24",
|
||||||
|
"2.85",
|
||||||
SensorDeviceClass.DISTANCE,
|
SensorDeviceClass.DISTANCE,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@ -524,7 +712,8 @@ async def test_custom_unit(
|
|||||||
"peer_distance",
|
"peer_distance",
|
||||||
UnitOfLength.KILOMETERS,
|
UnitOfLength.KILOMETERS,
|
||||||
1000,
|
1000,
|
||||||
1000,
|
"1000",
|
||||||
|
"1000",
|
||||||
SensorDeviceClass.DISTANCE,
|
SensorDeviceClass.DISTANCE,
|
||||||
),
|
),
|
||||||
# Energy
|
# Energy
|
||||||
@ -533,7 +722,8 @@ async def test_custom_unit(
|
|||||||
UnitOfEnergy.MEGA_WATT_HOUR,
|
UnitOfEnergy.MEGA_WATT_HOUR,
|
||||||
UnitOfEnergy.MEGA_WATT_HOUR,
|
UnitOfEnergy.MEGA_WATT_HOUR,
|
||||||
1000,
|
1000,
|
||||||
1.0,
|
"1000",
|
||||||
|
"1.000",
|
||||||
SensorDeviceClass.ENERGY,
|
SensorDeviceClass.ENERGY,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@ -541,7 +731,8 @@ async def test_custom_unit(
|
|||||||
UnitOfEnergy.MEGA_WATT_HOUR,
|
UnitOfEnergy.MEGA_WATT_HOUR,
|
||||||
UnitOfEnergy.MEGA_WATT_HOUR,
|
UnitOfEnergy.MEGA_WATT_HOUR,
|
||||||
1000,
|
1000,
|
||||||
278,
|
"1000",
|
||||||
|
"278",
|
||||||
SensorDeviceClass.ENERGY,
|
SensorDeviceClass.ENERGY,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@ -549,7 +740,8 @@ async def test_custom_unit(
|
|||||||
"BTU",
|
"BTU",
|
||||||
UnitOfEnergy.KILO_WATT_HOUR,
|
UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
1000,
|
1000,
|
||||||
1000,
|
"1000",
|
||||||
|
"1000",
|
||||||
SensorDeviceClass.ENERGY,
|
SensorDeviceClass.ENERGY,
|
||||||
),
|
),
|
||||||
# Power factor
|
# Power factor
|
||||||
@ -558,7 +750,8 @@ async def test_custom_unit(
|
|||||||
PERCENTAGE,
|
PERCENTAGE,
|
||||||
PERCENTAGE,
|
PERCENTAGE,
|
||||||
1.0,
|
1.0,
|
||||||
100,
|
"1.0",
|
||||||
|
"100.0",
|
||||||
SensorDeviceClass.POWER_FACTOR,
|
SensorDeviceClass.POWER_FACTOR,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@ -566,7 +759,8 @@ async def test_custom_unit(
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
100,
|
100,
|
||||||
1,
|
"100",
|
||||||
|
"1.00",
|
||||||
SensorDeviceClass.POWER_FACTOR,
|
SensorDeviceClass.POWER_FACTOR,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@ -574,7 +768,8 @@ async def test_custom_unit(
|
|||||||
None,
|
None,
|
||||||
"Cos φ",
|
"Cos φ",
|
||||||
1.0,
|
1.0,
|
||||||
1.0,
|
"1.0",
|
||||||
|
"1.0",
|
||||||
SensorDeviceClass.POWER_FACTOR,
|
SensorDeviceClass.POWER_FACTOR,
|
||||||
),
|
),
|
||||||
# Pressure
|
# Pressure
|
||||||
@ -584,7 +779,8 @@ async def test_custom_unit(
|
|||||||
UnitOfPressure.INHG,
|
UnitOfPressure.INHG,
|
||||||
UnitOfPressure.INHG,
|
UnitOfPressure.INHG,
|
||||||
1000.0,
|
1000.0,
|
||||||
29.53,
|
"1000.0",
|
||||||
|
"29.53",
|
||||||
SensorDeviceClass.PRESSURE,
|
SensorDeviceClass.PRESSURE,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@ -592,7 +788,8 @@ async def test_custom_unit(
|
|||||||
UnitOfPressure.HPA,
|
UnitOfPressure.HPA,
|
||||||
UnitOfPressure.HPA,
|
UnitOfPressure.HPA,
|
||||||
1.234,
|
1.234,
|
||||||
12.34,
|
"1.234",
|
||||||
|
"12.340",
|
||||||
SensorDeviceClass.PRESSURE,
|
SensorDeviceClass.PRESSURE,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@ -600,7 +797,8 @@ async def test_custom_unit(
|
|||||||
UnitOfPressure.MMHG,
|
UnitOfPressure.MMHG,
|
||||||
UnitOfPressure.MMHG,
|
UnitOfPressure.MMHG,
|
||||||
1000,
|
1000,
|
||||||
750,
|
"1000",
|
||||||
|
"750",
|
||||||
SensorDeviceClass.PRESSURE,
|
SensorDeviceClass.PRESSURE,
|
||||||
),
|
),
|
||||||
# Not a supported pressure unit
|
# Not a supported pressure unit
|
||||||
@ -609,7 +807,8 @@ async def test_custom_unit(
|
|||||||
"peer_pressure",
|
"peer_pressure",
|
||||||
UnitOfPressure.HPA,
|
UnitOfPressure.HPA,
|
||||||
1000,
|
1000,
|
||||||
1000,
|
"1000",
|
||||||
|
"1000",
|
||||||
SensorDeviceClass.PRESSURE,
|
SensorDeviceClass.PRESSURE,
|
||||||
),
|
),
|
||||||
# Speed
|
# Speed
|
||||||
@ -618,7 +817,8 @@ async def test_custom_unit(
|
|||||||
UnitOfSpeed.MILES_PER_HOUR,
|
UnitOfSpeed.MILES_PER_HOUR,
|
||||||
UnitOfSpeed.MILES_PER_HOUR,
|
UnitOfSpeed.MILES_PER_HOUR,
|
||||||
100,
|
100,
|
||||||
62,
|
"100",
|
||||||
|
"62",
|
||||||
SensorDeviceClass.SPEED,
|
SensorDeviceClass.SPEED,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@ -626,7 +826,8 @@ async def test_custom_unit(
|
|||||||
UnitOfVolumetricFlux.INCHES_PER_HOUR,
|
UnitOfVolumetricFlux.INCHES_PER_HOUR,
|
||||||
UnitOfVolumetricFlux.INCHES_PER_HOUR,
|
UnitOfVolumetricFlux.INCHES_PER_HOUR,
|
||||||
78,
|
78,
|
||||||
0.13,
|
"78",
|
||||||
|
"0.13",
|
||||||
SensorDeviceClass.SPEED,
|
SensorDeviceClass.SPEED,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@ -634,7 +835,8 @@ async def test_custom_unit(
|
|||||||
"peer_distance",
|
"peer_distance",
|
||||||
UnitOfSpeed.KILOMETERS_PER_HOUR,
|
UnitOfSpeed.KILOMETERS_PER_HOUR,
|
||||||
100,
|
100,
|
||||||
100,
|
"100",
|
||||||
|
"100",
|
||||||
SensorDeviceClass.SPEED,
|
SensorDeviceClass.SPEED,
|
||||||
),
|
),
|
||||||
# Volume
|
# Volume
|
||||||
@ -643,7 +845,8 @@ async def test_custom_unit(
|
|||||||
UnitOfVolume.CUBIC_FEET,
|
UnitOfVolume.CUBIC_FEET,
|
||||||
UnitOfVolume.CUBIC_FEET,
|
UnitOfVolume.CUBIC_FEET,
|
||||||
100,
|
100,
|
||||||
3531,
|
"100",
|
||||||
|
"3531",
|
||||||
SensorDeviceClass.VOLUME,
|
SensorDeviceClass.VOLUME,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@ -651,7 +854,8 @@ async def test_custom_unit(
|
|||||||
UnitOfVolume.FLUID_OUNCES,
|
UnitOfVolume.FLUID_OUNCES,
|
||||||
UnitOfVolume.FLUID_OUNCES,
|
UnitOfVolume.FLUID_OUNCES,
|
||||||
2.3,
|
2.3,
|
||||||
77.8,
|
"2.3",
|
||||||
|
"77.8",
|
||||||
SensorDeviceClass.VOLUME,
|
SensorDeviceClass.VOLUME,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@ -659,7 +863,8 @@ async def test_custom_unit(
|
|||||||
"peer_distance",
|
"peer_distance",
|
||||||
UnitOfVolume.CUBIC_METERS,
|
UnitOfVolume.CUBIC_METERS,
|
||||||
100,
|
100,
|
||||||
100,
|
"100",
|
||||||
|
"100",
|
||||||
SensorDeviceClass.VOLUME,
|
SensorDeviceClass.VOLUME,
|
||||||
),
|
),
|
||||||
# Weight
|
# Weight
|
||||||
@ -668,7 +873,8 @@ async def test_custom_unit(
|
|||||||
UnitOfMass.OUNCES,
|
UnitOfMass.OUNCES,
|
||||||
UnitOfMass.OUNCES,
|
UnitOfMass.OUNCES,
|
||||||
100,
|
100,
|
||||||
3.5,
|
"100",
|
||||||
|
"3.5",
|
||||||
SensorDeviceClass.WEIGHT,
|
SensorDeviceClass.WEIGHT,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@ -676,7 +882,8 @@ async def test_custom_unit(
|
|||||||
UnitOfMass.GRAMS,
|
UnitOfMass.GRAMS,
|
||||||
UnitOfMass.GRAMS,
|
UnitOfMass.GRAMS,
|
||||||
78,
|
78,
|
||||||
2211,
|
"78",
|
||||||
|
"2211",
|
||||||
SensorDeviceClass.WEIGHT,
|
SensorDeviceClass.WEIGHT,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@ -684,7 +891,8 @@ async def test_custom_unit(
|
|||||||
"peer_distance",
|
"peer_distance",
|
||||||
UnitOfMass.GRAMS,
|
UnitOfMass.GRAMS,
|
||||||
100,
|
100,
|
||||||
100,
|
"100",
|
||||||
|
"100",
|
||||||
SensorDeviceClass.WEIGHT,
|
SensorDeviceClass.WEIGHT,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -696,7 +904,8 @@ async def test_custom_unit_change(
|
|||||||
custom_unit,
|
custom_unit,
|
||||||
state_unit,
|
state_unit,
|
||||||
native_value,
|
native_value,
|
||||||
custom_value,
|
native_state,
|
||||||
|
custom_state,
|
||||||
device_class,
|
device_class,
|
||||||
):
|
):
|
||||||
"""Test custom unit changes are picked up."""
|
"""Test custom unit changes are picked up."""
|
||||||
@ -716,7 +925,7 @@ async def test_custom_unit_change(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get(entity0.entity_id)
|
state = hass.states.get(entity0.entity_id)
|
||||||
assert float(state.state) == approx(float(native_value))
|
assert state.state == native_state
|
||||||
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == native_unit
|
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == native_unit
|
||||||
|
|
||||||
entity_registry.async_update_entity_options(
|
entity_registry.async_update_entity_options(
|
||||||
@ -725,7 +934,7 @@ async def test_custom_unit_change(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get(entity0.entity_id)
|
state = hass.states.get(entity0.entity_id)
|
||||||
assert float(state.state) == approx(float(custom_value))
|
assert state.state == custom_state
|
||||||
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == state_unit
|
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == state_unit
|
||||||
|
|
||||||
entity_registry.async_update_entity_options(
|
entity_registry.async_update_entity_options(
|
||||||
@ -734,19 +943,19 @@ async def test_custom_unit_change(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get(entity0.entity_id)
|
state = hass.states.get(entity0.entity_id)
|
||||||
assert float(state.state) == approx(float(native_value))
|
assert state.state == native_state
|
||||||
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == native_unit
|
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == native_unit
|
||||||
|
|
||||||
entity_registry.async_update_entity_options("sensor.test", "sensor", None)
|
entity_registry.async_update_entity_options("sensor.test", "sensor", None)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get(entity0.entity_id)
|
state = hass.states.get(entity0.entity_id)
|
||||||
assert float(state.state) == approx(float(native_value))
|
assert state.state == native_state
|
||||||
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == native_unit
|
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == native_unit
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"unit_system, native_unit, automatic_unit, suggested_unit, custom_unit, native_value, automatic_value, suggested_value, custom_value, device_class",
|
"unit_system, native_unit, automatic_unit, suggested_unit, custom_unit, native_value, native_state, automatic_state, suggested_state, custom_state, device_class",
|
||||||
[
|
[
|
||||||
# Distance
|
# Distance
|
||||||
(
|
(
|
||||||
@ -756,9 +965,10 @@ async def test_custom_unit_change(
|
|||||||
UnitOfLength.METERS,
|
UnitOfLength.METERS,
|
||||||
UnitOfLength.YARDS,
|
UnitOfLength.YARDS,
|
||||||
1000,
|
1000,
|
||||||
621,
|
"1000",
|
||||||
1000000,
|
"621",
|
||||||
1093613,
|
"1000000",
|
||||||
|
"1093613",
|
||||||
SensorDeviceClass.DISTANCE,
|
SensorDeviceClass.DISTANCE,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -772,9 +982,10 @@ async def test_unit_conversion_priority(
|
|||||||
suggested_unit,
|
suggested_unit,
|
||||||
custom_unit,
|
custom_unit,
|
||||||
native_value,
|
native_value,
|
||||||
automatic_value,
|
native_state,
|
||||||
suggested_value,
|
automatic_state,
|
||||||
custom_value,
|
suggested_state,
|
||||||
|
custom_state,
|
||||||
device_class,
|
device_class,
|
||||||
):
|
):
|
||||||
"""Test priority of unit conversion."""
|
"""Test priority of unit conversion."""
|
||||||
@ -826,7 +1037,7 @@ async def test_unit_conversion_priority(
|
|||||||
|
|
||||||
# Registered entity -> Follow automatic unit conversion
|
# Registered entity -> Follow automatic unit conversion
|
||||||
state = hass.states.get(entity0.entity_id)
|
state = hass.states.get(entity0.entity_id)
|
||||||
assert float(state.state) == approx(float(automatic_value))
|
assert state.state == automatic_state
|
||||||
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == automatic_unit
|
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == automatic_unit
|
||||||
# Assert the automatic unit conversion is stored in the registry
|
# Assert the automatic unit conversion is stored in the registry
|
||||||
entry = entity_registry.async_get(entity0.entity_id)
|
entry = entity_registry.async_get(entity0.entity_id)
|
||||||
@ -836,12 +1047,12 @@ async def test_unit_conversion_priority(
|
|||||||
|
|
||||||
# Unregistered entity -> Follow native unit
|
# Unregistered entity -> Follow native unit
|
||||||
state = hass.states.get(entity1.entity_id)
|
state = hass.states.get(entity1.entity_id)
|
||||||
assert float(state.state) == approx(float(native_value))
|
assert state.state == native_state
|
||||||
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == native_unit
|
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == native_unit
|
||||||
|
|
||||||
# Registered entity with suggested unit
|
# Registered entity with suggested unit
|
||||||
state = hass.states.get(entity2.entity_id)
|
state = hass.states.get(entity2.entity_id)
|
||||||
assert float(state.state) == approx(float(suggested_value))
|
assert state.state == suggested_state
|
||||||
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == suggested_unit
|
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == suggested_unit
|
||||||
# Assert the suggested unit is stored in the registry
|
# Assert the suggested unit is stored in the registry
|
||||||
entry = entity_registry.async_get(entity2.entity_id)
|
entry = entity_registry.async_get(entity2.entity_id)
|
||||||
@ -851,7 +1062,7 @@ async def test_unit_conversion_priority(
|
|||||||
|
|
||||||
# Unregistered entity with suggested unit
|
# Unregistered entity with suggested unit
|
||||||
state = hass.states.get(entity3.entity_id)
|
state = hass.states.get(entity3.entity_id)
|
||||||
assert float(state.state) == approx(float(suggested_value))
|
assert state.state == suggested_state
|
||||||
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == suggested_unit
|
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == suggested_unit
|
||||||
|
|
||||||
# Set a custom unit, this should have priority over the automatic unit conversion
|
# Set a custom unit, this should have priority over the automatic unit conversion
|
||||||
@ -861,7 +1072,7 @@ async def test_unit_conversion_priority(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get(entity0.entity_id)
|
state = hass.states.get(entity0.entity_id)
|
||||||
assert float(state.state) == approx(float(custom_value))
|
assert state.state == custom_state
|
||||||
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == custom_unit
|
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == custom_unit
|
||||||
|
|
||||||
entity_registry.async_update_entity_options(
|
entity_registry.async_update_entity_options(
|
||||||
@ -870,7 +1081,7 @@ async def test_unit_conversion_priority(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get(entity2.entity_id)
|
state = hass.states.get(entity2.entity_id)
|
||||||
assert float(state.state) == approx(float(custom_value))
|
assert state.state == custom_state
|
||||||
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == custom_unit
|
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == custom_unit
|
||||||
|
|
||||||
|
|
||||||
@ -964,7 +1175,7 @@ async def test_unit_conversion_priority_suggested_unit_change(
|
|||||||
UnitOfLength.KILOMETERS,
|
UnitOfLength.KILOMETERS,
|
||||||
UnitOfLength.MILES,
|
UnitOfLength.MILES,
|
||||||
1000,
|
1000,
|
||||||
621,
|
621.0,
|
||||||
SensorDeviceClass.DISTANCE,
|
SensorDeviceClass.DISTANCE,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@ -1219,7 +1430,7 @@ async def test_device_classes_with_invalid_unit_of_measurement(
|
|||||||
(date(2012, 11, 10), "2012-11-10"),
|
(date(2012, 11, 10), "2012-11-10"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_non_numeric_validation(
|
async def test_non_numeric_validation_warn(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
caplog: pytest.LogCaptureFixture,
|
caplog: pytest.LogCaptureFixture,
|
||||||
enable_custom_integrations: None,
|
enable_custom_integrations: None,
|
||||||
@ -1253,6 +1464,51 @@ async def test_non_numeric_validation(
|
|||||||
) in caplog.text
|
) in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"device_class,state_class,unit,precision", ((None, None, None, 1),)
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"native_value,expected",
|
||||||
|
[
|
||||||
|
("abc", "abc"),
|
||||||
|
("13.7.1", "13.7.1"),
|
||||||
|
(datetime(2012, 11, 10, 7, 35, 1), "2012-11-10 07:35:01"),
|
||||||
|
(date(2012, 11, 10), "2012-11-10"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_non_numeric_validation_raise(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
enable_custom_integrations: None,
|
||||||
|
native_value: Any,
|
||||||
|
expected: str,
|
||||||
|
device_class: SensorDeviceClass | None,
|
||||||
|
state_class: SensorStateClass | None,
|
||||||
|
unit: str | None,
|
||||||
|
precision,
|
||||||
|
) -> None:
|
||||||
|
"""Test error on expected numeric entities."""
|
||||||
|
platform = getattr(hass.components, "test.sensor")
|
||||||
|
platform.init(empty=True)
|
||||||
|
platform.ENTITIES["0"] = platform.MockSensor(
|
||||||
|
name="Test",
|
||||||
|
device_class=device_class,
|
||||||
|
native_precision=precision,
|
||||||
|
native_unit_of_measurement=unit,
|
||||||
|
native_value=native_value,
|
||||||
|
state_class=state_class,
|
||||||
|
)
|
||||||
|
entity0 = platform.ENTITIES["0"]
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(entity0.entity_id)
|
||||||
|
assert state is None
|
||||||
|
|
||||||
|
assert ("Error adding entities for domain sensor with platform test") in caplog.text
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"device_class,state_class,unit",
|
"device_class,state_class,unit",
|
||||||
[
|
[
|
||||||
|
@ -98,6 +98,11 @@ class MockSensor(MockEntity, SensorEntity):
|
|||||||
"""Return the last_reset of this sensor."""
|
"""Return the last_reset of this sensor."""
|
||||||
return self._handle("last_reset")
|
return self._handle("last_reset")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_precision(self):
|
||||||
|
"""Return the number of digits after the decimal point."""
|
||||||
|
return self._handle("native_precision")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_unit_of_measurement(self):
|
def native_unit_of_measurement(self):
|
||||||
"""Return the native unit_of_measurement of this sensor."""
|
"""Return the native unit_of_measurement of this sensor."""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user