Add filter sensor device class from source entity (#44304)

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
pull/37800/head
Diogo Gomes 2020-12-19 16:51:24 +00:00 committed by GitHub
parent ccbf857266
commit b7d4c1826c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 95 additions and 15 deletions

View File

@ -11,8 +11,14 @@ from typing import Optional
import voluptuous as vol
from homeassistant.components import history
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.components.sensor import (
DEVICE_CLASSES as SENSOR_DEVICE_CLASSES,
DOMAIN as SENSOR_DOMAIN,
PLATFORM_SCHEMA,
)
from homeassistant.const import (
ATTR_DEVICE_CLASS,
ATTR_ENTITY_ID,
ATTR_ICON,
ATTR_UNIT_OF_MEASUREMENT,
@ -132,7 +138,9 @@ FILTER_TIME_THROTTLE_SCHEMA = FILTER_SCHEMA.extend(
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_ENTITY_ID): vol.Any(
cv.entity_domain(SENSOR_DOMAIN), cv.entity_domain(BINARY_SENSOR_DOMAIN)
),
vol.Optional(CONF_NAME): cv.string,
vol.Required(CONF_FILTERS): vol.All(
cv.ensure_list,
@ -178,16 +186,20 @@ class SensorFilter(Entity):
self._state = None
self._filters = filters
self._icon = None
self._device_class = None
@callback
def _update_filter_sensor_state_event(self, event):
"""Handle device state changes."""
_LOGGER.debug("Update filter on event: %s", event)
self._update_filter_sensor_state(event.data.get("new_state"))
@callback
def _update_filter_sensor_state(self, new_state, update_ha=True):
"""Process device state changes."""
if new_state is None or new_state.state in [STATE_UNKNOWN, STATE_UNAVAILABLE]:
self._state = new_state.state
self.async_write_ha_state()
return
temp_state = new_state
@ -214,6 +226,12 @@ class SensorFilter(Entity):
if self._icon is None:
self._icon = new_state.attributes.get(ATTR_ICON, ICON)
if (
self._device_class is None
and new_state.attributes.get(ATTR_DEVICE_CLASS) in SENSOR_DEVICE_CLASSES
):
self._device_class = new_state.attributes.get(ATTR_DEVICE_CLASS)
if self._unit_of_measurement is None:
self._unit_of_measurement = new_state.attributes.get(
ATTR_UNIT_OF_MEASUREMENT
@ -283,7 +301,8 @@ class SensorFilter(Entity):
# Replay history through the filter chain
for state in history_list:
self._update_filter_sensor_state(state, False)
if state.state not in [STATE_UNKNOWN, STATE_UNAVAILABLE, None]:
self._update_filter_sensor_state(state, False)
self.async_on_remove(
async_track_state_change_event(
@ -321,6 +340,11 @@ class SensorFilter(Entity):
"""Return the state attributes of the sensor."""
return {ATTR_ENTITY_ID: self._entity}
@property
def device_class(self):
"""Return device class."""
return self._device_class
class FilterState:
"""State abstraction for filter usage."""

View File

@ -14,7 +14,8 @@ from homeassistant.components.filter.sensor import (
TimeSMAFilter,
TimeThrottleFilter,
)
from homeassistant.const import SERVICE_RELOAD
from homeassistant.components.sensor import DEVICE_CLASS_TEMPERATURE
from homeassistant.const import SERVICE_RELOAD, STATE_UNAVAILABLE
import homeassistant.core as ha
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
@ -35,12 +36,6 @@ def values():
return values
async def init_recorder(hass):
"""Init the recorder for testing."""
await async_init_recorder_component(hass)
await hass.async_start()
async def test_setup_fail(hass):
"""Test if filter doesn't exist."""
config = {
@ -50,7 +45,6 @@ async def test_setup_fail(hass):
"filters": [{"filter": "nonexisting"}],
}
}
hass.config.components.add("history")
with assert_setup_component(0):
assert await async_setup_component(hass, "sensor", config)
await hass.async_block_till_done()
@ -70,7 +64,8 @@ async def test_chain(hass, values):
],
}
}
hass.config.components.add("history")
await async_init_recorder_component(hass)
with assert_setup_component(1, "sensor"):
assert await async_setup_component(hass, "sensor", config)
await hass.async_block_till_done()
@ -85,7 +80,6 @@ async def test_chain(hass, values):
async def test_chain_history(hass, values, missing=False):
"""Test if filter chaining works."""
await init_recorder(hass)
config = {
"history": {},
"sensor": {
@ -99,6 +93,9 @@ async def test_chain_history(hass, values, missing=False):
],
},
}
await async_init_recorder_component(hass)
assert_setup_component(1, "history")
t_0 = dt_util.utcnow() - timedelta(minutes=1)
t_1 = dt_util.utcnow() - timedelta(minutes=2)
t_2 = dt_util.utcnow() - timedelta(minutes=3)
@ -146,7 +143,6 @@ async def test_chain_history_missing(hass, values):
async def test_history_time(hass):
"""Test loading from history based on a time window."""
await init_recorder(hass)
config = {
"history": {},
"sensor": {
@ -156,6 +152,9 @@ async def test_history_time(hass):
"filters": [{"filter": "time_throttle", "window_size": "00:01"}],
},
}
await async_init_recorder_component(hass)
assert_setup_component(1, "history")
t_0 = dt_util.utcnow() - timedelta(minutes=1)
t_1 = dt_util.utcnow() - timedelta(minutes=2)
t_2 = dt_util.utcnow() - timedelta(minutes=3)
@ -184,6 +183,63 @@ async def test_history_time(hass):
assert "18.0" == state.state
async def test_setup(hass):
"""Test if filter attributes are inherited."""
config = {
"sensor": {
"platform": "filter",
"name": "test",
"entity_id": "sensor.test_monitored",
"filters": [
{"filter": "outlier", "window_size": 10, "radius": 4.0},
],
}
}
await async_init_recorder_component(hass)
with assert_setup_component(1, "sensor"):
assert await async_setup_component(hass, "sensor", config)
await hass.async_block_till_done()
hass.states.async_set(
"sensor.test_monitored",
1,
{"icon": "mdi:test", "device_class": DEVICE_CLASS_TEMPERATURE},
)
await hass.async_block_till_done()
state = hass.states.get("sensor.test")
assert state.attributes["icon"] == "mdi:test"
assert state.attributes["device_class"] == DEVICE_CLASS_TEMPERATURE
assert state.state == "1.0"
async def test_invalid_state(hass):
"""Test if filter attributes are inherited."""
config = {
"sensor": {
"platform": "filter",
"name": "test",
"entity_id": "sensor.test_monitored",
"filters": [
{"filter": "outlier", "window_size": 10, "radius": 4.0},
],
}
}
await async_init_recorder_component(hass)
with assert_setup_component(1, "sensor"):
assert await async_setup_component(hass, "sensor", config)
await hass.async_block_till_done()
hass.states.async_set("sensor.test_monitored", STATE_UNAVAILABLE)
await hass.async_block_till_done()
state = hass.states.get("sensor.test")
assert state.state == STATE_UNAVAILABLE
async def test_outlier(values):
"""Test if outlier filter works."""
filt = OutlierFilter(window_size=3, precision=2, entity=None, radius=4.0)
@ -316,7 +372,7 @@ def test_time_sma(values):
async def test_reload(hass):
"""Verify we can reload filter sensors."""
await init_recorder(hass)
await async_init_recorder_component(hass)
hass.states.async_set("sensor.test_monitored", 12345)
await async_setup_component(