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:
parent
6f26687aa7
commit
73d7a754e8
10 changed files with 113 additions and 19 deletions
|
@ -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."""
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue