Add missing device class triggers (#88316)

* Add constant tests for sensor device classes

* Add missing strings

* Adjust tests

* Add missing conditions

* Add missing trigger

* Cleanup
pull/88503/head
epenet 2023-02-20 08:30:51 +01:00 committed by GitHub
parent 69e42d0e4d
commit 488d78571e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 113 additions and 6 deletions

View File

@ -32,6 +32,7 @@ from . import ATTR_STATE_CLASS, DOMAIN, SensorDeviceClass
DEVICE_CLASS_NONE = "none"
CONF_IS_APPARENT_POWER = "is_apparent_power"
CONF_IS_AQI = "is_aqi"
CONF_IS_ATMOSPHERIC_PRESSURE = "is_atmospheric_pressure"
CONF_IS_BATTERY_LEVEL = "is_battery_level"
CONF_IS_CO = "is_carbon_monoxide"
@ -40,6 +41,7 @@ CONF_IS_CURRENT = "is_current"
CONF_IS_DATA_RATE = "is_data_rate"
CONF_IS_DATA_SIZE = "is_data_size"
CONF_IS_DISTANCE = "is_distance"
CONF_IS_DURATION = "is_duration"
CONF_IS_ENERGY = "is_energy"
CONF_IS_FREQUENCY = "is_frequency"
CONF_IS_HUMIDITY = "is_humidity"
@ -47,6 +49,7 @@ CONF_IS_GAS = "is_gas"
CONF_IS_ILLUMINANCE = "is_illuminance"
CONF_IS_IRRADIANCE = "is_irradiance"
CONF_IS_MOISTURE = "is_moisture"
CONF_IS_MONETARY = "is_monetary"
CONF_IS_NITROGEN_DIOXIDE = "is_nitrogen_dioxide"
CONF_IS_NITROGEN_MONOXIDE = "is_nitrogen_monoxide"
CONF_IS_NITROUS_OXIDE = "is_nitrous_oxide"
@ -75,6 +78,7 @@ CONF_IS_WIND_SPEED = "is_wind_speed"
ENTITY_CONDITIONS = {
SensorDeviceClass.APPARENT_POWER: [{CONF_TYPE: CONF_IS_APPARENT_POWER}],
SensorDeviceClass.AQI: [{CONF_TYPE: CONF_IS_AQI}],
SensorDeviceClass.ATMOSPHERIC_PRESSURE: [{CONF_TYPE: CONF_IS_ATMOSPHERIC_PRESSURE}],
SensorDeviceClass.BATTERY: [{CONF_TYPE: CONF_IS_BATTERY_LEVEL}],
SensorDeviceClass.CO: [{CONF_TYPE: CONF_IS_CO}],
@ -83,6 +87,7 @@ ENTITY_CONDITIONS = {
SensorDeviceClass.DATA_RATE: [{CONF_TYPE: CONF_IS_DATA_RATE}],
SensorDeviceClass.DATA_SIZE: [{CONF_TYPE: CONF_IS_DATA_SIZE}],
SensorDeviceClass.DISTANCE: [{CONF_TYPE: CONF_IS_DISTANCE}],
SensorDeviceClass.DURATION: [{CONF_TYPE: CONF_IS_DURATION}],
SensorDeviceClass.ENERGY: [{CONF_TYPE: CONF_IS_ENERGY}],
SensorDeviceClass.FREQUENCY: [{CONF_TYPE: CONF_IS_FREQUENCY}],
SensorDeviceClass.GAS: [{CONF_TYPE: CONF_IS_GAS}],
@ -90,6 +95,7 @@ ENTITY_CONDITIONS = {
SensorDeviceClass.ILLUMINANCE: [{CONF_TYPE: CONF_IS_ILLUMINANCE}],
SensorDeviceClass.IRRADIANCE: [{CONF_TYPE: CONF_IS_IRRADIANCE}],
SensorDeviceClass.MOISTURE: [{CONF_TYPE: CONF_IS_MOISTURE}],
SensorDeviceClass.MONETARY: [{CONF_TYPE: CONF_IS_MONETARY}],
SensorDeviceClass.NITROGEN_DIOXIDE: [{CONF_TYPE: CONF_IS_NITROGEN_DIOXIDE}],
SensorDeviceClass.NITROGEN_MONOXIDE: [{CONF_TYPE: CONF_IS_NITROGEN_MONOXIDE}],
SensorDeviceClass.NITROUS_OXIDE: [{CONF_TYPE: CONF_IS_NITROUS_OXIDE}],
@ -128,6 +134,7 @@ CONDITION_SCHEMA = vol.All(
vol.Required(CONF_TYPE): vol.In(
[
CONF_IS_APPARENT_POWER,
CONF_IS_AQI,
CONF_IS_ATMOSPHERIC_PRESSURE,
CONF_IS_BATTERY_LEVEL,
CONF_IS_CO,
@ -136,6 +143,7 @@ CONDITION_SCHEMA = vol.All(
CONF_IS_DATA_RATE,
CONF_IS_DATA_SIZE,
CONF_IS_DISTANCE,
CONF_IS_DURATION,
CONF_IS_ENERGY,
CONF_IS_FREQUENCY,
CONF_IS_GAS,
@ -143,6 +151,7 @@ CONDITION_SCHEMA = vol.All(
CONF_IS_ILLUMINANCE,
CONF_IS_IRRADIANCE,
CONF_IS_MOISTURE,
CONF_IS_MONETARY,
CONF_IS_NITROGEN_DIOXIDE,
CONF_IS_NITROGEN_MONOXIDE,
CONF_IS_NITROUS_OXIDE,

View File

@ -31,6 +31,7 @@ from . import ATTR_STATE_CLASS, DOMAIN, SensorDeviceClass
DEVICE_CLASS_NONE = "none"
CONF_APPARENT_POWER = "apparent_power"
CONF_AQI = "aqi"
CONF_ATMOSPHERIC_PRESSURE = "atmospheric_pressure"
CONF_BATTERY_LEVEL = "battery_level"
CONF_CO = "carbon_monoxide"
@ -39,6 +40,7 @@ CONF_CURRENT = "current"
CONF_DATA_RATE = "data_rate"
CONF_DATA_SIZE = "data_size"
CONF_DISTANCE = "distance"
CONF_DURATION = "duration"
CONF_ENERGY = "energy"
CONF_FREQUENCY = "frequency"
CONF_GAS = "gas"
@ -46,6 +48,7 @@ CONF_HUMIDITY = "humidity"
CONF_ILLUMINANCE = "illuminance"
CONF_IRRADIANCE = "irradiance"
CONF_MOISTURE = "moisture"
CONF_MONETARY = "monetary"
CONF_NITROGEN_DIOXIDE = "nitrogen_dioxide"
CONF_NITROGEN_MONOXIDE = "nitrogen_monoxide"
CONF_NITROUS_OXIDE = "nitrous_oxide"
@ -74,6 +77,7 @@ CONF_WIND_SPEED = "wind_speed"
ENTITY_TRIGGERS = {
SensorDeviceClass.APPARENT_POWER: [{CONF_TYPE: CONF_APPARENT_POWER}],
SensorDeviceClass.AQI: [{CONF_TYPE: CONF_AQI}],
SensorDeviceClass.ATMOSPHERIC_PRESSURE: [{CONF_TYPE: CONF_ATMOSPHERIC_PRESSURE}],
SensorDeviceClass.BATTERY: [{CONF_TYPE: CONF_BATTERY_LEVEL}],
SensorDeviceClass.CO: [{CONF_TYPE: CONF_CO}],
@ -82,6 +86,7 @@ ENTITY_TRIGGERS = {
SensorDeviceClass.DATA_RATE: [{CONF_TYPE: CONF_DATA_RATE}],
SensorDeviceClass.DATA_SIZE: [{CONF_TYPE: CONF_DATA_SIZE}],
SensorDeviceClass.DISTANCE: [{CONF_TYPE: CONF_DISTANCE}],
SensorDeviceClass.DURATION: [{CONF_TYPE: CONF_DURATION}],
SensorDeviceClass.ENERGY: [{CONF_TYPE: CONF_ENERGY}],
SensorDeviceClass.FREQUENCY: [{CONF_TYPE: CONF_FREQUENCY}],
SensorDeviceClass.GAS: [{CONF_TYPE: CONF_GAS}],
@ -89,6 +94,7 @@ ENTITY_TRIGGERS = {
SensorDeviceClass.ILLUMINANCE: [{CONF_TYPE: CONF_ILLUMINANCE}],
SensorDeviceClass.IRRADIANCE: [{CONF_TYPE: CONF_IRRADIANCE}],
SensorDeviceClass.MOISTURE: [{CONF_TYPE: CONF_MOISTURE}],
SensorDeviceClass.MONETARY: [{CONF_TYPE: CONF_MONETARY}],
SensorDeviceClass.NITROGEN_DIOXIDE: [{CONF_TYPE: CONF_NITROGEN_DIOXIDE}],
SensorDeviceClass.NITROGEN_MONOXIDE: [{CONF_TYPE: CONF_NITROGEN_MONOXIDE}],
SensorDeviceClass.NITROUS_OXIDE: [{CONF_TYPE: CONF_NITROUS_OXIDE}],
@ -128,6 +134,7 @@ TRIGGER_SCHEMA = vol.All(
vol.Required(CONF_TYPE): vol.In(
[
CONF_APPARENT_POWER,
CONF_AQI,
CONF_ATMOSPHERIC_PRESSURE,
CONF_BATTERY_LEVEL,
CONF_CO,
@ -136,6 +143,7 @@ TRIGGER_SCHEMA = vol.All(
CONF_DATA_RATE,
CONF_DATA_SIZE,
CONF_DISTANCE,
CONF_DURATION,
CONF_ENERGY,
CONF_FREQUENCY,
CONF_GAS,
@ -143,6 +151,7 @@ TRIGGER_SCHEMA = vol.All(
CONF_ILLUMINANCE,
CONF_IRRADIANCE,
CONF_MOISTURE,
CONF_MONETARY,
CONF_NITROGEN_DIOXIDE,
CONF_NITROGEN_MONOXIDE,
CONF_NITROUS_OXIDE,

View File

@ -3,6 +3,7 @@
"device_automation": {
"condition_type": {
"is_apparent_power": "Current {entity_name} apparent power",
"is_aqi": "Current {entity_name} air quality index",
"is_atmospheric_pressure": "Current {entity_name} atmospheric pressure",
"is_battery_level": "Current {entity_name} battery level",
"is_carbon_monoxide": "Current {entity_name} carbon monoxide concentration level",
@ -11,6 +12,7 @@
"is_data_rate": "Current {entity_name} data rate",
"is_data_size": "Current {entity_name} data size",
"is_distance": "Current {entity_name} distance",
"is_duration": "Current {entity_name} duration",
"is_energy": "Current {entity_name} energy",
"is_frequency": "Current {entity_name} frequency",
"is_gas": "Current {entity_name} gas",
@ -18,6 +20,7 @@
"is_illuminance": "Current {entity_name} illuminance",
"is_irradiance": "Current {entity_name} irradiance",
"is_moisture": "Current {entity_name} moisture",
"is_monetary": "Current {entity_name} money",
"is_nitrogen_dioxide": "Current {entity_name} nitrogen dioxide concentration level",
"is_nitrogen_monoxide": "Current {entity_name} nitrogen monoxide concentration level",
"is_nitrous_oxide": "Current {entity_name} nitrous oxide concentration level",
@ -27,6 +30,8 @@
"is_pm25": "Current {entity_name} PM2.5 concentration level",
"is_power": "Current {entity_name} power",
"is_power_factor": "Current {entity_name} power factor",
"is_precipitation": "Current {entity_name} precipitation",
"is_precipitation_intensity": "Current {entity_name} precipitation intensity",
"is_pressure": "Current {entity_name} pressure",
"is_reactive_power": "Current {entity_name} reactive power",
"is_signal_strength": "Current {entity_name} signal strength",
@ -39,17 +44,21 @@
"is_voltage": "Current {entity_name} voltage",
"is_volume": "Current {entity_name} volume",
"is_water": "Current {entity_name} water",
"is_weight": "Current {entity_name} weight"
"is_weight": "Current {entity_name} weight",
"is_wind_speed": "Current {entity_name} wind speed"
},
"trigger_type": {
"apparent_power": "{entity_name} apparent power changes",
"aqi": "{entity_name} air quality index changes",
"atmospheric_pressure": "{entity_name} atmospheric pressure changes",
"battery_level": "{entity_name} battery level changes",
"carbon_monoxide": "{entity_name} carbon monoxide concentration changes",
"carbon_dioxide": "{entity_name} carbon dioxide concentration changes",
"current": "{entity_name} current changes",
"data_rate": "{entity_name} data rate changes",
"data_size": "{entity_name} data size changes",
"distance": "{entity_name} distance changes",
"duration": "{entity_name} duration changes",
"energy": "{entity_name} energy changes",
"frequency": "{entity_name} frequency changes",
"gas": "{entity_name} gas changes",
@ -57,6 +66,7 @@
"illuminance": "{entity_name} illuminance changes",
"irradiance": "{entity_name} irradiance changes",
"moisture": "{entity_name} moisture changes",
"monetary": "{entity_name} money changes",
"nitrogen_dioxide": "{entity_name} nitrogen dioxide concentration changes",
"nitrogen_monoxide": "{entity_name} nitrogen monoxide concentration changes",
"nitrous_oxide": "{entity_name} nitrous oxide concentration changes",
@ -66,6 +76,8 @@
"pm25": "{entity_name} PM2.5 concentration changes",
"power": "{entity_name} power changes",
"power_factor": "{entity_name} power factor changes",
"precipitation": "{entity_name} precipitation changes",
"precipitation_intensity": "{entity_name} precipitation intensity changes",
"pressure": "{entity_name} pressure changes",
"reactive_power": "{entity_name} reactive power changes",
"signal_strength": "{entity_name} signal strength changes",
@ -78,7 +90,8 @@
"voltage": "{entity_name} voltage changes",
"volume": "{entity_name} volume changes",
"water": "{entity_name} water changes",
"weight": "{entity_name} weight changes"
"weight": "{entity_name} weight changes",
"wind_speed": "{entity_name} wind speed changes"
}
},
"state": {

View File

@ -8,13 +8,16 @@ from homeassistant.components.sensor import (
DOMAIN,
SensorDeviceClass,
SensorStateClass,
device_condition,
)
from homeassistant.components.sensor.const import NON_NUMERIC_DEVICE_CLASSES
from homeassistant.components.sensor.device_condition import ENTITY_CONDITIONS
from homeassistant.const import CONF_PLATFORM, PERCENTAGE, STATE_UNKNOWN, EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.entity_registry import RegistryEntryHider
from homeassistant.setup import async_setup_component
from homeassistant.util.json import load_json
from tests.common import (
MockConfigEntry,
@ -28,11 +31,47 @@ from tests.testing_config.custom_components.test.sensor import UNITS_OF_MEASUREM
@pytest.fixture
def calls(hass):
def calls(hass: HomeAssistant) -> list[ServiceCall]:
"""Track calls to a mock service."""
return async_mock_service(hass, "test", "automation")
@pytest.mark.parametrize(
"device_class",
[
device_class
for device_class in SensorDeviceClass
if device_class not in NON_NUMERIC_DEVICE_CLASSES
],
)
def test_matches_device_classes(device_class: SensorDeviceClass) -> None:
"""Ensure device class constants are declared in device_condition module."""
# Ensure it has corresponding CONF_IS_*** constant
constant_name = {
SensorDeviceClass.BATTERY: "CONF_IS_BATTERY_LEVEL",
SensorDeviceClass.CO: "CONF_IS_CO",
SensorDeviceClass.CO2: "CONF_IS_CO2",
}.get(device_class, f"CONF_IS_{device_class.value.upper()}")
assert hasattr(device_condition, constant_name), f"Missing constant {constant_name}"
# Ensure it has correct value
constant_value = {
SensorDeviceClass.BATTERY: "is_battery_level",
}.get(device_class, f"is_{device_class.value}")
assert getattr(device_condition, constant_name) == constant_value
# Ensure it is present in ENTITY_CONDITIONS
assert device_class in ENTITY_CONDITIONS
# Ensure it is present in CONDITION_SCHEMA
schema_types = (
device_condition.CONDITION_SCHEMA.validators[0].schema["type"].container
)
assert constant_value in schema_types
# Ensure it is present in string.json
strings = load_json("homeassistant/components/sensor/strings.json")
assert constant_value in strings["device_automation"]["condition_type"]
async def test_get_conditions(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,

View File

@ -10,14 +10,17 @@ from homeassistant.components.sensor import (
DOMAIN,
SensorDeviceClass,
SensorStateClass,
device_trigger,
)
from homeassistant.components.sensor.const import NON_NUMERIC_DEVICE_CLASSES
from homeassistant.components.sensor.device_trigger import ENTITY_TRIGGERS
from homeassistant.const import CONF_PLATFORM, PERCENTAGE, STATE_UNKNOWN, EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.entity_registry import RegistryEntryHider
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
from homeassistant.util.json import load_json
from tests.common import (
MockConfigEntry,
@ -32,11 +35,45 @@ from tests.testing_config.custom_components.test.sensor import UNITS_OF_MEASUREM
@pytest.fixture
def calls(hass):
def calls(hass: HomeAssistant) -> list[ServiceCall]:
"""Track calls to a mock service."""
return async_mock_service(hass, "test", "automation")
@pytest.mark.parametrize(
"device_class",
[
device_class
for device_class in SensorDeviceClass
if device_class not in NON_NUMERIC_DEVICE_CLASSES
],
)
def test_matches_device_classes(device_class: SensorDeviceClass) -> None:
"""Ensure device class constants are declared in device_trigger module."""
# Ensure it has corresponding CONF_*** constant
constant_name = {
SensorDeviceClass.BATTERY: "CONF_BATTERY_LEVEL",
SensorDeviceClass.CO: "CONF_CO",
SensorDeviceClass.CO2: "CONF_CO2",
}.get(device_class, f"CONF_{device_class.value.upper()}")
assert hasattr(device_trigger, constant_name), f"Missing constant {constant_name}"
# Ensure it has correct value
constant_value = {
SensorDeviceClass.BATTERY: "battery_level",
}.get(device_class, device_class.value)
assert getattr(device_trigger, constant_name) == constant_value
# Ensure it is present in ENTITY_TRIGGERS
assert device_class in ENTITY_TRIGGERS
# Ensure it is present in TRIGGER_SCHEMA
schema_types = device_trigger.TRIGGER_SCHEMA.validators[0].schema["type"].container
assert constant_value in schema_types
# Ensure it is present in string.json
strings = load_json("homeassistant/components/sensor/strings.json")
assert constant_value in strings["device_automation"]["trigger_type"]
async def test_get_triggers(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,