Add sensor state_class property (#50063)

* Add sensor state_class property

* STATE_CLASS_LATEST -> STATE_CLASS_MEASUREMENT

* Export sensor.state_class in capability_attributes

* Add STATE_CLASS_UNKNOWN

* Fix typing

* Update tests

* STATE_CLASS_UNKNOWN -> STATE_CLASS_OTHER

* Update homeassistant/components/sensor/__init__.py

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>

* Remove STATE_CLASS_OTHER

* Update tests

* Revert test changes

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
pull/50270/head
Erik Montnemery 2021-05-07 23:04:00 +02:00 committed by GitHub
parent cf96d86985
commit ba284c0d27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 41 additions and 7 deletions

View File

@ -6,6 +6,7 @@ from aiohue import AiohueException, Unauthorized
from aiohue.sensors import TYPE_ZLL_PRESENCE from aiohue.sensors import TYPE_ZLL_PRESENCE
import async_timeout import async_timeout
from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers import debounce, entity from homeassistant.helpers import debounce, entity
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@ -177,6 +178,11 @@ class GenericHueSensor(GenericHueDevice, entity.Entity):
or self.sensor.config.get("reachable", True) or self.sensor.config.get("reachable", True)
) )
@property
def state_class(self):
"""Return the state class of this entity, from STATE_CLASSES, if any."""
return STATE_CLASS_MEASUREMENT
async def async_added_to_hass(self): async def async_added_to_hass(self):
"""When entity is added to hass.""" """When entity is added to hass."""
self.async_on_remove( self.async_on_remove(

View File

@ -1,10 +1,14 @@
"""Component to interface with various sensors that can be monitored.""" """Component to interface with various sensors that can be monitored."""
from __future__ import annotations
from collections.abc import Mapping
from datetime import timedelta from datetime import timedelta
import logging import logging
from typing import Any, cast
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
DEVICE_CLASS_BATTERY, DEVICE_CLASS_BATTERY,
DEVICE_CLASS_CO, DEVICE_CLASS_CO,
@ -21,17 +25,19 @@ from homeassistant.const import (
DEVICE_CLASS_TIMESTAMP, DEVICE_CLASS_TIMESTAMP,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
) )
from homeassistant.core import HomeAssistant
from homeassistant.helpers.config_validation import ( # noqa: F401 from homeassistant.helpers.config_validation import ( # noqa: F401
PLATFORM_SCHEMA, PLATFORM_SCHEMA,
PLATFORM_SCHEMA_BASE, PLATFORM_SCHEMA_BASE,
) )
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.typing import ConfigType
# mypy: allow-untyped-defs, no-check-untyped-defs
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
ATTR_STATE_CLASS = "state_class"
DOMAIN = "sensor" DOMAIN = "sensor"
ENTITY_ID_FORMAT = DOMAIN + ".{}" ENTITY_ID_FORMAT = DOMAIN + ".{}"
@ -56,8 +62,15 @@ DEVICE_CLASSES = [
DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.In(DEVICE_CLASSES)) DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.In(DEVICE_CLASSES))
# The state represents a measurement in present time
STATE_CLASS_MEASUREMENT = "measurement"
async def async_setup(hass, config): STATE_CLASSES = [STATE_CLASS_MEASUREMENT]
STATE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.In(STATE_CLASSES))
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Track states and offer events for sensors.""" """Track states and offer events for sensors."""
component = hass.data[DOMAIN] = EntityComponent( component = hass.data[DOMAIN] = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL _LOGGER, DOMAIN, hass, SCAN_INTERVAL
@ -67,15 +80,30 @@ async def async_setup(hass, config):
return True return True
async def async_setup_entry(hass, entry): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up a config entry.""" """Set up a config entry."""
return await hass.data[DOMAIN].async_setup_entry(entry) component = cast(EntityComponent, hass.data[DOMAIN])
return await component.async_setup_entry(entry)
async def async_unload_entry(hass, entry): async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
return await hass.data[DOMAIN].async_unload_entry(entry) component = cast(EntityComponent, hass.data[DOMAIN])
return await component.async_unload_entry(entry)
class SensorEntity(Entity): class SensorEntity(Entity):
"""Base class for sensor entities.""" """Base class for sensor entities."""
@property
def state_class(self) -> str | None:
"""Return the state class of this entity, from STATE_CLASSES, if any."""
return None
@property
def capability_attributes(self) -> Mapping[str, Any] | None:
"""Return the capability attributes."""
if self.state_class:
return {ATTR_STATE_CLASS: self.state_class}
return None