From 7b1e0e42f7a2ae1095fd15be6a62512fdaecc024 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 21 Apr 2022 18:31:15 +0200 Subject: [PATCH] Allow device conditions and triggers for unitless sensors (#70337) --- .../components/sensor/device_condition.py | 11 +++- .../components/sensor/device_trigger.py | 11 +++- .../sensor/test_device_condition.py | 57 ++++++++++++++++++- .../components/sensor/test_device_trigger.py | 57 ++++++++++++++++++- 4 files changed, 128 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/sensor/device_condition.py b/homeassistant/components/sensor/device_condition.py index 47ac6c048ce..70a265e3b25 100644 --- a/homeassistant/components/sensor/device_condition.py +++ b/homeassistant/components/sensor/device_condition.py @@ -16,14 +16,18 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import condition, config_validation as cv -from homeassistant.helpers.entity import get_device_class, get_unit_of_measurement +from homeassistant.helpers.entity import ( + get_capability, + get_device_class, + get_unit_of_measurement, +) from homeassistant.helpers.entity_registry import ( async_entries_for_device, async_get_registry, ) from homeassistant.helpers.typing import ConfigType -from . import DOMAIN, SensorDeviceClass +from . import ATTR_STATE_CLASS, DOMAIN, SensorDeviceClass # mypy: allow-untyped-defs, no-check-untyped-defs @@ -146,9 +150,10 @@ async def async_get_conditions( for entry in entries: device_class = get_device_class(hass, entry.entity_id) or DEVICE_CLASS_NONE + state_class = get_capability(hass, entry.entity_id, ATTR_STATE_CLASS) unit_of_measurement = get_unit_of_measurement(hass, entry.entity_id) - if not unit_of_measurement: + if not unit_of_measurement and not state_class: continue templates = ENTITY_CONDITIONS.get( diff --git a/homeassistant/components/sensor/device_trigger.py b/homeassistant/components/sensor/device_trigger.py index 80decdfcb7a..f90022cf5f3 100644 --- a/homeassistant/components/sensor/device_trigger.py +++ b/homeassistant/components/sensor/device_trigger.py @@ -17,10 +17,14 @@ from homeassistant.const import ( ) from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.entity import get_device_class, get_unit_of_measurement +from homeassistant.helpers.entity import ( + get_capability, + get_device_class, + get_unit_of_measurement, +) from homeassistant.helpers.entity_registry import async_entries_for_device -from . import DOMAIN, SensorDeviceClass +from . import ATTR_STATE_CLASS, DOMAIN, SensorDeviceClass # mypy: allow-untyped-defs, no-check-untyped-defs @@ -165,9 +169,10 @@ async def async_get_triggers(hass, device_id): for entry in entries: device_class = get_device_class(hass, entry.entity_id) or DEVICE_CLASS_NONE + state_class = get_capability(hass, entry.entity_id, ATTR_STATE_CLASS) unit_of_measurement = get_unit_of_measurement(hass, entry.entity_id) - if not unit_of_measurement: + if not unit_of_measurement and not state_class: continue templates = ENTITY_TRIGGERS.get( diff --git a/tests/components/sensor/test_device_condition.py b/tests/components/sensor/test_device_condition.py index 31a9f13938b..acba724c842 100644 --- a/tests/components/sensor/test_device_condition.py +++ b/tests/components/sensor/test_device_condition.py @@ -3,7 +3,12 @@ import pytest import homeassistant.components.automation as automation from homeassistant.components.device_automation import DeviceAutomationType -from homeassistant.components.sensor import DOMAIN, SensorDeviceClass +from homeassistant.components.sensor import ( + ATTR_STATE_CLASS, + DOMAIN, + SensorDeviceClass, + SensorStateClass, +) from homeassistant.components.sensor.device_condition import ENTITY_CONDITIONS from homeassistant.const import CONF_PLATFORM, PERCENTAGE, STATE_UNKNOWN from homeassistant.helpers import device_registry @@ -175,6 +180,56 @@ async def test_get_conditions_no_state(hass, device_reg, entity_reg): assert_lists_same(conditions, expected_conditions) +@pytest.mark.parametrize( + "state_class,unit,condition_types", + ( + (SensorStateClass.MEASUREMENT, None, ["is_value"]), + (SensorStateClass.TOTAL, None, ["is_value"]), + (SensorStateClass.TOTAL_INCREASING, None, ["is_value"]), + (SensorStateClass.MEASUREMENT, "dogs", ["is_value"]), + (None, None, []), + ), +) +async def test_get_conditions_no_unit_or_stateclass( + hass, + device_reg, + entity_reg, + state_class, + unit, + condition_types, +): + """Test we get the expected conditions from an entity with no unit or state class.""" + config_entry = MockConfigEntry(domain="test", data={}) + config_entry.add_to_hass(hass) + device_entry = device_reg.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + entity_reg.async_get_or_create( + DOMAIN, + "test", + "5678", + capabilities={ATTR_STATE_CLASS: state_class}, + device_id=device_entry.id, + unit_of_measurement=unit, + ) + expected_conditions = [ + { + "condition": "device", + "domain": DOMAIN, + "type": condition, + "device_id": device_entry.id, + "entity_id": f"{DOMAIN}.test_5678", + "metadata": {"secondary": False}, + } + for condition in condition_types + ] + conditions = await async_get_device_automations( + hass, DeviceAutomationType.CONDITION, device_entry.id + ) + assert_lists_same(conditions, expected_conditions) + + @pytest.mark.parametrize( "set_state,device_class_reg,device_class_state,unit_reg,unit_state", [ diff --git a/tests/components/sensor/test_device_trigger.py b/tests/components/sensor/test_device_trigger.py index c955f066dc9..eb46d7b458b 100644 --- a/tests/components/sensor/test_device_trigger.py +++ b/tests/components/sensor/test_device_trigger.py @@ -5,7 +5,12 @@ import pytest import homeassistant.components.automation as automation from homeassistant.components.device_automation import DeviceAutomationType -from homeassistant.components.sensor import DOMAIN, SensorDeviceClass +from homeassistant.components.sensor import ( + ATTR_STATE_CLASS, + DOMAIN, + SensorDeviceClass, + SensorStateClass, +) from homeassistant.components.sensor.device_trigger import ENTITY_TRIGGERS from homeassistant.const import CONF_PLATFORM, PERCENTAGE, STATE_UNKNOWN from homeassistant.helpers import device_registry @@ -138,6 +143,56 @@ async def test_get_triggers_hidden_auxiliary( assert_lists_same(triggers, expected_triggers) +@pytest.mark.parametrize( + "state_class,unit,trigger_types", + ( + (SensorStateClass.MEASUREMENT, None, ["value"]), + (SensorStateClass.TOTAL, None, ["value"]), + (SensorStateClass.TOTAL_INCREASING, None, ["value"]), + (SensorStateClass.MEASUREMENT, "dogs", ["value"]), + (None, None, []), + ), +) +async def test_get_triggers_no_unit_or_stateclass( + hass, + device_reg, + entity_reg, + state_class, + unit, + trigger_types, +): + """Test we get the expected triggers from an entity with no unit or state class.""" + config_entry = MockConfigEntry(domain="test", data={}) + config_entry.add_to_hass(hass) + device_entry = device_reg.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + entity_reg.async_get_or_create( + DOMAIN, + "test", + "5678", + capabilities={ATTR_STATE_CLASS: state_class}, + device_id=device_entry.id, + unit_of_measurement=unit, + ) + expected_triggers = [ + { + "platform": "device", + "domain": DOMAIN, + "type": trigger, + "device_id": device_entry.id, + "entity_id": f"{DOMAIN}.test_5678", + "metadata": {"secondary": False}, + } + for trigger in trigger_types + ] + triggers = await async_get_device_automations( + hass, DeviceAutomationType.TRIGGER, device_entry.id + ) + assert_lists_same(triggers, expected_triggers) + + @pytest.mark.parametrize( "set_state,device_class_reg,device_class_state,unit_reg,unit_state", [