Mark temperature sensors as STATE_CLASS_MEASUREMENT (#50889)

* Mark temperature sensors as STATE_CLASS_MEASUREMENT

* Fix broadlink tests

* Tweak Hue changes
This commit is contained in:
Erik Montnemery 2021-05-21 11:44:34 +02:00 committed by GitHub
parent 6f26687aa7
commit 73d7a754e8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 113 additions and 19 deletions

View file

@ -8,6 +8,7 @@ from homeassistant.components.sensor import (
DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_TEMPERATURE,
PLATFORM_SCHEMA,
STATE_CLASS_MEASUREMENT,
SensorEntity,
)
from homeassistant.const import CONF_HOST, PERCENTAGE, TEMP_CELSIUS
@ -20,11 +21,16 @@ from .helpers import import_device
_LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = {
"temperature": ("Temperature", TEMP_CELSIUS, DEVICE_CLASS_TEMPERATURE),
"air_quality": ("Air Quality", None, None),
"humidity": ("Humidity", PERCENTAGE, DEVICE_CLASS_HUMIDITY),
"light": ("Light", None, DEVICE_CLASS_ILLUMINANCE),
"noise": ("Noise", None, None),
"temperature": (
"Temperature",
TEMP_CELSIUS,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
),
"air_quality": ("Air Quality", None, None, None),
"humidity": ("Humidity", PERCENTAGE, DEVICE_CLASS_HUMIDITY, None),
"light": ("Light", None, DEVICE_CLASS_ILLUMINANCE, None),
"noise": ("Noise", None, None, None),
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
@ -101,6 +107,11 @@ class BroadlinkSensor(SensorEntity):
"""Return device class."""
return SENSOR_TYPES[self._monitored_condition][2]
@property
def state_class(self):
"""Return state class."""
return SENSOR_TYPES[self._monitored_condition][3]
@property
def device_info(self):
"""Return device info."""

View file

@ -14,7 +14,11 @@ from pydeconz.sensor import (
Thermostat,
)
from homeassistant.components.sensor import DOMAIN, SensorEntity
from homeassistant.components.sensor import (
DOMAIN,
STATE_CLASS_MEASUREMENT,
SensorEntity,
)
from homeassistant.const import (
ATTR_TEMPERATURE,
ATTR_VOLTAGE,
@ -60,6 +64,10 @@ ICON = {
Temperature: "mdi:thermometer",
}
STATE_CLASS = {
Temperature: STATE_CLASS_MEASUREMENT,
}
UNIT_OF_MEASUREMENT = {
Consumption: ENERGY_KILO_WATT_HOUR,
Humidity: PERCENTAGE,
@ -161,6 +169,11 @@ class DeconzSensor(DeconzDevice, SensorEntity):
"""Return the icon to use in the frontend."""
return ICON.get(type(self._device))
@property
def state_class(self):
"""Return the state class of the sensor."""
return STATE_CLASS.get(type(self._device))
@property
def unit_of_measurement(self):
"""Return the unit of measurement of this sensor."""
@ -233,6 +246,11 @@ class DeconzTemperature(DeconzDevice, SensorEntity):
"""Return the class of the sensor."""
return DEVICE_CLASS_TEMPERATURE
@property
def state_class(self):
"""Return the state class of the sensor."""
return STATE_CLASS_MEASUREMENT
@property
def unit_of_measurement(self):
"""Return the unit of measurement of this sensor."""

View file

@ -6,7 +6,7 @@ from aiohue.sensors import (
TYPE_ZLL_TEMPERATURE,
)
from homeassistant.components.sensor import SensorEntity
from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT, SensorEntity
from homeassistant.const import (
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_ILLUMINANCE,
@ -87,6 +87,11 @@ class HueTemperature(GenericHueGaugeSensorEntity):
return self.sensor.temperature / 100
@property
def state_class(self):
"""Return the state class of the sensor."""
return STATE_CLASS_MEASUREMENT
class HueBattery(GenericHueSensor, SensorEntity):
"""Battery class for when a batt-powered device is only represented as an event."""

View file

@ -151,6 +151,7 @@ class BlockAttributeDescription:
unit: None | str | Callable[[dict], str] = None
value: Callable[[Any], Any] = lambda val: val
device_class: str | None = None
state_class: str | None = None
default_enabled: bool = True
available: Callable[[aioshelly.Block], bool] | None = None
# Callable (settings, block), return true if entity should be removed

View file

@ -135,6 +135,7 @@ SENSORS = {
unit=temperature_unit,
value=lambda value: round(value, 1),
device_class=sensor.DEVICE_CLASS_TEMPERATURE,
state_class=sensor.STATE_CLASS_MEASUREMENT,
available=lambda block: block.extTemp != 999,
),
("sensor", "humidity"): BlockAttributeDescription(
@ -231,6 +232,11 @@ class ShellyRestSensor(ShellyRestAttributeEntity, SensorEntity):
"""Return value of sensor."""
return self.attribute_value
@property
def state_class(self):
"""State class of sensor."""
return self.description.state_class
@property
def unit_of_measurement(self):
"""Return unit of sensor."""

View file

@ -4,7 +4,7 @@ from __future__ import annotations
from hatasmota import const as hc, status_sensor
from homeassistant.components import sensor
from homeassistant.components.sensor import SensorEntity
from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT, SensorEntity
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_BILLION,
@ -46,6 +46,7 @@ from .discovery import TASMOTA_DISCOVERY_ENTITY_NEW
from .mixins import TasmotaAvailability, TasmotaDiscoveryUpdate
DEVICE_CLASS = "device_class"
STATE_CLASS = "state_class"
ICON = "icon"
# A Tasmota sensor type may be mapped to either a device class or an icon, not both
@ -89,7 +90,10 @@ SENSOR_DEVICE_CLASS_ICON_MAP = {
hc.SENSOR_STATUS_SIGNAL: {DEVICE_CLASS: DEVICE_CLASS_SIGNAL_STRENGTH},
hc.SENSOR_STATUS_RSSI: {ICON: "mdi:access-point"},
hc.SENSOR_STATUS_SSID: {ICON: "mdi:access-point-network"},
hc.SENSOR_TEMPERATURE: {DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE},
hc.SENSOR_TEMPERATURE: {
DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
STATE_CLASS: STATE_CLASS_MEASUREMENT,
},
hc.SENSOR_TODAY: {DEVICE_CLASS: DEVICE_CLASS_POWER},
hc.SENSOR_TOTAL: {DEVICE_CLASS: DEVICE_CLASS_POWER},
hc.SENSOR_TOTAL_START_TIME: {ICON: "mdi:progress-clock"},
@ -172,6 +176,14 @@ class TasmotaSensor(TasmotaAvailability, TasmotaDiscoveryUpdate, SensorEntity):
)
return class_or_icon.get(DEVICE_CLASS)
@property
def state_class(self) -> str | None:
"""Return the state class of the sensor."""
class_or_icon = SENSOR_DEVICE_CLASS_ICON_MAP.get(
self._tasmota_entity.quantity, {}
)
return class_or_icon.get(STATE_CLASS)
@property
def entity_registry_enabled_default(self) -> bool:
"""Return if the entity should be enabled when first added to the entity registry."""

View file

@ -12,7 +12,11 @@ from miio.gateway.gateway import (
)
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity
from homeassistant.components.sensor import (
PLATFORM_SCHEMA,
STATE_CLASS_MEASUREMENT,
SensorEntity,
)
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import (
ATTR_BATTERY_LEVEL,
@ -66,11 +70,15 @@ class SensorType:
unit: str = None
icon: str = None
device_class: str = None
state_class: str = None
GATEWAY_SENSOR_TYPES = {
"temperature": SensorType(
unit=TEMP_CELSIUS, icon=None, device_class=DEVICE_CLASS_TEMPERATURE
unit=TEMP_CELSIUS,
icon=None,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
"humidity": SensorType(
unit=PERCENTAGE, icon=None, device_class=DEVICE_CLASS_HUMIDITY
@ -245,6 +253,11 @@ class XiaomiGatewaySensor(XiaomiGatewayDevice, SensorEntity):
"""Return the device class of this entity."""
return GATEWAY_SENSOR_TYPES[self._data_key].device_class
@property
def state_class(self):
"""Return the state class of this entity."""
return GATEWAY_SENSOR_TYPES[self._data_key].state_class
@property
def state(self):
"""Return the state of the sensor."""

View file

@ -15,6 +15,7 @@ from homeassistant.components.sensor import (
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
DOMAIN,
STATE_CLASS_MEASUREMENT,
SensorEntity,
)
from homeassistant.config_entries import ConfigEntry
@ -101,6 +102,7 @@ class Sensor(ZhaEntity, SensorEntity):
_device_class: str | None = None
_divisor: int = 1
_multiplier: int = 1
_state_class: str | None = None
_unit: str | None = None
def __init__(
@ -126,6 +128,11 @@ class Sensor(ZhaEntity, SensorEntity):
"""Return device class from component DEVICE_CLASSES."""
return self._device_class
@property
def state_class(self) -> str | None:
"""Return the state class of this entity, from STATE_CLASSES, if any."""
return self._state_class
@property
def unit_of_measurement(self) -> str | None:
"""Return the unit of measurement of this entity."""
@ -285,6 +292,7 @@ class Temperature(Sensor):
SENSOR_ATTR = "measured_value"
_device_class = DEVICE_CLASS_TEMPERATURE
_divisor = 100
_state_class = STATE_CLASS_MEASUREMENT
_unit = TEMP_CELSIUS

View file

@ -14,6 +14,7 @@ from homeassistant.components.sensor import (
DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_POWER,
DOMAIN as SENSOR_DOMAIN,
STATE_CLASS_MEASUREMENT,
SensorEntity,
)
from homeassistant.config_entries import ConfigEntry
@ -87,6 +88,7 @@ class ZwaveSensorBase(ZWaveBaseEntity, SensorEntity):
super().__init__(config_entry, client, info)
self._name = self.generate_name(include_value_name=True)
self._device_class = self._get_device_class()
self._state_class = self._get_state_class()
def _get_device_class(self) -> str | None:
"""
@ -113,11 +115,29 @@ class ZwaveSensorBase(ZWaveBaseEntity, SensorEntity):
return DEVICE_CLASS_ILLUMINANCE
return None
def _get_state_class(self) -> str | None:
"""
Get the state class of the sensor.
This should be run once during initialization so we don't have to calculate
this value on every state update.
"""
if isinstance(self.info.primary_value.property_, str):
property_lower = self.info.primary_value.property_.lower()
if "temperature" in property_lower:
return STATE_CLASS_MEASUREMENT
return None
@property
def device_class(self) -> str | None:
"""Return the device class of the sensor."""
return self._device_class
@property
def state_class(self) -> str | None:
"""Return the state class of the sensor."""
return self._state_class
@property
def entity_registry_enabled_default(self) -> bool:
"""Return if the entity should be enabled when first added to the entity registry."""

View file

@ -27,7 +27,7 @@ async def test_a1_sensor_setup(hass):
assert mock_api.check_sensors_raw.call_count == 1
device_entry = device_registry.async_get_device({(DOMAIN, mock_entry.unique_id)})
entries = async_entries_for_device(entity_registry, device_entry.id)
sensors = {entry for entry in entries if entry.domain == SENSOR_DOMAIN}
sensors = [entry for entry in entries if entry.domain == SENSOR_DOMAIN]
assert len(sensors) == 5
sensors_and_states = {
@ -62,7 +62,7 @@ async def test_a1_sensor_update(hass):
device_entry = device_registry.async_get_device({(DOMAIN, mock_entry.unique_id)})
entries = async_entries_for_device(entity_registry, device_entry.id)
sensors = {entry for entry in entries if entry.domain == SENSOR_DOMAIN}
sensors = [entry for entry in entries if entry.domain == SENSOR_DOMAIN]
assert len(sensors) == 5
mock_api.check_sensors_raw.return_value = {
@ -104,7 +104,7 @@ async def test_rm_pro_sensor_setup(hass):
assert mock_api.check_sensors.call_count == 1
device_entry = device_registry.async_get_device({(DOMAIN, mock_entry.unique_id)})
entries = async_entries_for_device(entity_registry, device_entry.id)
sensors = {entry for entry in entries if entry.domain == SENSOR_DOMAIN}
sensors = [entry for entry in entries if entry.domain == SENSOR_DOMAIN]
assert len(sensors) == 1
sensors_and_states = {
@ -127,7 +127,7 @@ async def test_rm_pro_sensor_update(hass):
device_entry = device_registry.async_get_device({(DOMAIN, mock_entry.unique_id)})
entries = async_entries_for_device(entity_registry, device_entry.id)
sensors = {entry for entry in entries if entry.domain == SENSOR_DOMAIN}
sensors = [entry for entry in entries if entry.domain == SENSOR_DOMAIN]
assert len(sensors) == 1
mock_api.check_sensors.return_value = {"temperature": 25.8}
@ -159,7 +159,7 @@ async def test_rm_pro_filter_crazy_temperature(hass):
device_entry = device_registry.async_get_device({(DOMAIN, mock_entry.unique_id)})
entries = async_entries_for_device(entity_registry, device_entry.id)
sensors = {entry for entry in entries if entry.domain == SENSOR_DOMAIN}
sensors = [entry for entry in entries if entry.domain == SENSOR_DOMAIN]
assert len(sensors) == 1
mock_api.check_sensors.return_value = {"temperature": -7}
@ -189,7 +189,7 @@ async def test_rm_mini3_no_sensor(hass):
assert mock_api.check_sensors.call_count <= 1
device_entry = device_registry.async_get_device({(DOMAIN, mock_entry.unique_id)})
entries = async_entries_for_device(entity_registry, device_entry.id)
sensors = {entry for entry in entries if entry.domain == SENSOR_DOMAIN}
sensors = [entry for entry in entries if entry.domain == SENSOR_DOMAIN]
assert len(sensors) == 0
@ -207,7 +207,7 @@ async def test_rm4_pro_hts2_sensor_setup(hass):
assert mock_api.check_sensors.call_count == 1
device_entry = device_registry.async_get_device({(DOMAIN, mock_entry.unique_id)})
entries = async_entries_for_device(entity_registry, device_entry.id)
sensors = {entry for entry in entries if entry.domain == SENSOR_DOMAIN}
sensors = [entry for entry in entries if entry.domain == SENSOR_DOMAIN]
assert len(sensors) == 2
sensors_and_states = {
@ -233,7 +233,7 @@ async def test_rm4_pro_hts2_sensor_update(hass):
device_entry = device_registry.async_get_device({(DOMAIN, mock_entry.unique_id)})
entries = async_entries_for_device(entity_registry, device_entry.id)
sensors = {entry for entry in entries if entry.domain == SENSOR_DOMAIN}
sensors = [entry for entry in entries if entry.domain == SENSOR_DOMAIN]
assert len(sensors) == 2
mock_api.check_sensors.return_value = {"temperature": 16.8, "humidity": 34.0}