Add filter sensor device class from source entity (#44304)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>pull/37800/head
parent
ccbf857266
commit
b7d4c1826c
|
@ -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."""
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue