Home Assistant Unofficial Reference 2024.12.1
device_trigger.py
Go to the documentation of this file.
1 """Provides device triggers for sensors."""
2 
3 import voluptuous as vol
4 
6  DEVICE_TRIGGER_BASE_SCHEMA,
7  InvalidDeviceAutomationConfig,
8  async_get_entity_registry_entry_or_raise,
9 )
11  numeric_state as numeric_state_trigger,
12 )
13 from homeassistant.const import (
14  CONF_ABOVE,
15  CONF_BELOW,
16  CONF_ENTITY_ID,
17  CONF_FOR,
18  CONF_TYPE,
19 )
20 from homeassistant.core import CALLBACK_TYPE, HomeAssistant
21 from homeassistant.exceptions import HomeAssistantError
22 from homeassistant.helpers import config_validation as cv, entity_registry as er
23 from homeassistant.helpers.entity import (
24  get_capability,
25  get_device_class,
26  get_unit_of_measurement,
27 )
28 from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo
29 from homeassistant.helpers.typing import ConfigType
30 
31 from . import ATTR_STATE_CLASS, DOMAIN, SensorDeviceClass
32 
33 DEVICE_CLASS_NONE = "none"
34 
35 CONF_APPARENT_POWER = "apparent_power"
36 CONF_AQI = "aqi"
37 CONF_AREA = "area"
38 CONF_ATMOSPHERIC_PRESSURE = "atmospheric_pressure"
39 CONF_BATTERY_LEVEL = "battery_level"
40 CONF_BLOOD_GLUCOSE_CONCENTRATION = "blood_glucose_concentration"
41 CONF_CO = "carbon_monoxide"
42 CONF_CO2 = "carbon_dioxide"
43 CONF_CONDUCTIVITY = "conductivity"
44 CONF_CURRENT = "current"
45 CONF_DATA_RATE = "data_rate"
46 CONF_DATA_SIZE = "data_size"
47 CONF_DISTANCE = "distance"
48 CONF_DURATION = "duration"
49 CONF_ENERGY = "energy"
50 CONF_FREQUENCY = "frequency"
51 CONF_GAS = "gas"
52 CONF_HUMIDITY = "humidity"
53 CONF_ILLUMINANCE = "illuminance"
54 CONF_IRRADIANCE = "irradiance"
55 CONF_MOISTURE = "moisture"
56 CONF_MONETARY = "monetary"
57 CONF_NITROGEN_DIOXIDE = "nitrogen_dioxide"
58 CONF_NITROGEN_MONOXIDE = "nitrogen_monoxide"
59 CONF_NITROUS_OXIDE = "nitrous_oxide"
60 CONF_OZONE = "ozone"
61 CONF_PH = "ph"
62 CONF_PM1 = "pm1"
63 CONF_PM10 = "pm10"
64 CONF_PM25 = "pm25"
65 CONF_POWER = "power"
66 CONF_POWER_FACTOR = "power_factor"
67 CONF_PRECIPITATION = "precipitation"
68 CONF_PRECIPITATION_INTENSITY = "precipitation_intensity"
69 CONF_PRESSURE = "pressure"
70 CONF_REACTIVE_POWER = "reactive_power"
71 CONF_SIGNAL_STRENGTH = "signal_strength"
72 CONF_SOUND_PRESSURE = "sound_pressure"
73 CONF_SPEED = "speed"
74 CONF_SULPHUR_DIOXIDE = "sulphur_dioxide"
75 CONF_TEMPERATURE = "temperature"
76 CONF_VALUE = "value"
77 CONF_VOLATILE_ORGANIC_COMPOUNDS = "volatile_organic_compounds"
78 CONF_VOLATILE_ORGANIC_COMPOUNDS_PARTS = "volatile_organic_compounds_parts"
79 CONF_VOLTAGE = "voltage"
80 CONF_VOLUME = "volume"
81 CONF_VOLUME_FLOW_RATE = "volume_flow_rate"
82 CONF_WATER = "water"
83 CONF_WEIGHT = "weight"
84 CONF_WIND_SPEED = "wind_speed"
85 
86 ENTITY_TRIGGERS = {
87  SensorDeviceClass.APPARENT_POWER: [{CONF_TYPE: CONF_APPARENT_POWER}],
88  SensorDeviceClass.AQI: [{CONF_TYPE: CONF_AQI}],
89  SensorDeviceClass.AREA: [{CONF_TYPE: CONF_AREA}],
90  SensorDeviceClass.ATMOSPHERIC_PRESSURE: [{CONF_TYPE: CONF_ATMOSPHERIC_PRESSURE}],
91  SensorDeviceClass.BATTERY: [{CONF_TYPE: CONF_BATTERY_LEVEL}],
92  SensorDeviceClass.BLOOD_GLUCOSE_CONCENTRATION: [
93  {CONF_TYPE: CONF_BLOOD_GLUCOSE_CONCENTRATION}
94  ],
95  SensorDeviceClass.CO: [{CONF_TYPE: CONF_CO}],
96  SensorDeviceClass.CO2: [{CONF_TYPE: CONF_CO2}],
97  SensorDeviceClass.CONDUCTIVITY: [{CONF_TYPE: CONF_CONDUCTIVITY}],
98  SensorDeviceClass.CURRENT: [{CONF_TYPE: CONF_CURRENT}],
99  SensorDeviceClass.DATA_RATE: [{CONF_TYPE: CONF_DATA_RATE}],
100  SensorDeviceClass.DATA_SIZE: [{CONF_TYPE: CONF_DATA_SIZE}],
101  SensorDeviceClass.DISTANCE: [{CONF_TYPE: CONF_DISTANCE}],
102  SensorDeviceClass.DURATION: [{CONF_TYPE: CONF_DURATION}],
103  SensorDeviceClass.ENERGY: [{CONF_TYPE: CONF_ENERGY}],
104  SensorDeviceClass.ENERGY_STORAGE: [{CONF_TYPE: CONF_ENERGY}],
105  SensorDeviceClass.FREQUENCY: [{CONF_TYPE: CONF_FREQUENCY}],
106  SensorDeviceClass.GAS: [{CONF_TYPE: CONF_GAS}],
107  SensorDeviceClass.HUMIDITY: [{CONF_TYPE: CONF_HUMIDITY}],
108  SensorDeviceClass.ILLUMINANCE: [{CONF_TYPE: CONF_ILLUMINANCE}],
109  SensorDeviceClass.IRRADIANCE: [{CONF_TYPE: CONF_IRRADIANCE}],
110  SensorDeviceClass.MOISTURE: [{CONF_TYPE: CONF_MOISTURE}],
111  SensorDeviceClass.MONETARY: [{CONF_TYPE: CONF_MONETARY}],
112  SensorDeviceClass.NITROGEN_DIOXIDE: [{CONF_TYPE: CONF_NITROGEN_DIOXIDE}],
113  SensorDeviceClass.NITROGEN_MONOXIDE: [{CONF_TYPE: CONF_NITROGEN_MONOXIDE}],
114  SensorDeviceClass.NITROUS_OXIDE: [{CONF_TYPE: CONF_NITROUS_OXIDE}],
115  SensorDeviceClass.OZONE: [{CONF_TYPE: CONF_OZONE}],
116  SensorDeviceClass.PH: [{CONF_TYPE: CONF_PH}],
117  SensorDeviceClass.PM1: [{CONF_TYPE: CONF_PM1}],
118  SensorDeviceClass.PM10: [{CONF_TYPE: CONF_PM10}],
119  SensorDeviceClass.PM25: [{CONF_TYPE: CONF_PM25}],
120  SensorDeviceClass.POWER: [{CONF_TYPE: CONF_POWER}],
121  SensorDeviceClass.POWER_FACTOR: [{CONF_TYPE: CONF_POWER_FACTOR}],
122  SensorDeviceClass.PRECIPITATION: [{CONF_TYPE: CONF_PRECIPITATION}],
123  SensorDeviceClass.PRECIPITATION_INTENSITY: [
124  {CONF_TYPE: CONF_PRECIPITATION_INTENSITY}
125  ],
126  SensorDeviceClass.PRESSURE: [{CONF_TYPE: CONF_PRESSURE}],
127  SensorDeviceClass.REACTIVE_POWER: [{CONF_TYPE: CONF_REACTIVE_POWER}],
128  SensorDeviceClass.SIGNAL_STRENGTH: [{CONF_TYPE: CONF_SIGNAL_STRENGTH}],
129  SensorDeviceClass.SOUND_PRESSURE: [{CONF_TYPE: CONF_SOUND_PRESSURE}],
130  SensorDeviceClass.SPEED: [{CONF_TYPE: CONF_SPEED}],
131  SensorDeviceClass.SULPHUR_DIOXIDE: [{CONF_TYPE: CONF_SULPHUR_DIOXIDE}],
132  SensorDeviceClass.TEMPERATURE: [{CONF_TYPE: CONF_TEMPERATURE}],
133  SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS: [
134  {CONF_TYPE: CONF_VOLATILE_ORGANIC_COMPOUNDS}
135  ],
136  SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS: [
137  {CONF_TYPE: CONF_VOLATILE_ORGANIC_COMPOUNDS_PARTS}
138  ],
139  SensorDeviceClass.VOLTAGE: [{CONF_TYPE: CONF_VOLTAGE}],
140  SensorDeviceClass.VOLUME: [{CONF_TYPE: CONF_VOLUME}],
141  SensorDeviceClass.VOLUME_STORAGE: [{CONF_TYPE: CONF_VOLUME}],
142  SensorDeviceClass.VOLUME_FLOW_RATE: [{CONF_TYPE: CONF_VOLUME_FLOW_RATE}],
143  SensorDeviceClass.WATER: [{CONF_TYPE: CONF_WATER}],
144  SensorDeviceClass.WEIGHT: [{CONF_TYPE: CONF_WEIGHT}],
145  SensorDeviceClass.WIND_SPEED: [{CONF_TYPE: CONF_WIND_SPEED}],
146  DEVICE_CLASS_NONE: [{CONF_TYPE: CONF_VALUE}],
147 }
148 
149 
150 TRIGGER_SCHEMA = vol.All(
151  DEVICE_TRIGGER_BASE_SCHEMA.extend(
152  {
153  vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid,
154  vol.Required(CONF_TYPE): vol.In(
155  [
156  CONF_APPARENT_POWER,
157  CONF_AQI,
158  CONF_AREA,
159  CONF_ATMOSPHERIC_PRESSURE,
160  CONF_BATTERY_LEVEL,
161  CONF_BLOOD_GLUCOSE_CONCENTRATION,
162  CONF_CO,
163  CONF_CO2,
164  CONF_CONDUCTIVITY,
165  CONF_CURRENT,
166  CONF_DATA_RATE,
167  CONF_DATA_SIZE,
168  CONF_DISTANCE,
169  CONF_DURATION,
170  CONF_ENERGY,
171  CONF_FREQUENCY,
172  CONF_GAS,
173  CONF_HUMIDITY,
174  CONF_ILLUMINANCE,
175  CONF_IRRADIANCE,
176  CONF_MOISTURE,
177  CONF_MONETARY,
178  CONF_NITROGEN_DIOXIDE,
179  CONF_NITROGEN_MONOXIDE,
180  CONF_NITROUS_OXIDE,
181  CONF_OZONE,
182  CONF_PH,
183  CONF_PM1,
184  CONF_PM10,
185  CONF_PM25,
186  CONF_POWER,
187  CONF_POWER_FACTOR,
188  CONF_PRECIPITATION,
189  CONF_PRECIPITATION_INTENSITY,
190  CONF_PRESSURE,
191  CONF_REACTIVE_POWER,
192  CONF_SIGNAL_STRENGTH,
193  CONF_SOUND_PRESSURE,
194  CONF_SPEED,
195  CONF_SULPHUR_DIOXIDE,
196  CONF_TEMPERATURE,
197  CONF_VOLATILE_ORGANIC_COMPOUNDS,
198  CONF_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
199  CONF_VOLTAGE,
200  CONF_VOLUME,
201  CONF_VOLUME_FLOW_RATE,
202  CONF_WATER,
203  CONF_WEIGHT,
204  CONF_WIND_SPEED,
205  CONF_VALUE,
206  ]
207  ),
208  vol.Optional(CONF_BELOW): vol.Any(vol.Coerce(float)),
209  vol.Optional(CONF_ABOVE): vol.Any(vol.Coerce(float)),
210  vol.Optional(CONF_FOR): cv.positive_time_period_dict,
211  }
212  ),
213  cv.has_at_least_one_key(CONF_BELOW, CONF_ABOVE),
214 )
215 
216 
218  hass: HomeAssistant,
219  config: ConfigType,
220  action: TriggerActionType,
221  trigger_info: TriggerInfo,
222 ) -> CALLBACK_TYPE:
223  """Listen for state changes based on configuration."""
224  numeric_state_config = {
225  numeric_state_trigger.CONF_PLATFORM: "numeric_state",
226  numeric_state_trigger.CONF_ENTITY_ID: config[CONF_ENTITY_ID],
227  }
228  if CONF_ABOVE in config:
229  numeric_state_config[numeric_state_trigger.CONF_ABOVE] = config[CONF_ABOVE]
230  if CONF_BELOW in config:
231  numeric_state_config[numeric_state_trigger.CONF_BELOW] = config[CONF_BELOW]
232  if CONF_FOR in config:
233  numeric_state_config[CONF_FOR] = config[CONF_FOR]
234 
235  numeric_state_config = await numeric_state_trigger.async_validate_trigger_config(
236  hass, numeric_state_config
237  )
238  return await numeric_state_trigger.async_attach_trigger(
239  hass, numeric_state_config, action, trigger_info, platform_type="device"
240  )
241 
242 
244  hass: HomeAssistant, device_id: str
245 ) -> list[dict[str, str]]:
246  """List device triggers."""
247  triggers: list[dict[str, str]] = []
248  entity_registry = er.async_get(hass)
249 
250  entries = [
251  entry
252  for entry in er.async_entries_for_device(entity_registry, device_id)
253  if entry.domain == DOMAIN
254  ]
255 
256  for entry in entries:
257  device_class = get_device_class(hass, entry.entity_id) or DEVICE_CLASS_NONE
258  state_class = get_capability(hass, entry.entity_id, ATTR_STATE_CLASS)
259  unit_of_measurement = get_unit_of_measurement(hass, entry.entity_id)
260 
261  if not unit_of_measurement and not state_class:
262  continue
263 
264  templates = ENTITY_TRIGGERS.get(
265  device_class, ENTITY_TRIGGERS[DEVICE_CLASS_NONE]
266  )
267 
268  triggers.extend(
269  {
270  **automation,
271  "platform": "device",
272  "device_id": device_id,
273  "entity_id": entry.id,
274  "domain": DOMAIN,
275  }
276  for automation in templates
277  )
278 
279  return triggers
280 
281 
283  hass: HomeAssistant, config: ConfigType
284 ) -> dict[str, vol.Schema]:
285  """List trigger capabilities."""
286 
287  try:
288  entry = async_get_entity_registry_entry_or_raise(hass, config[CONF_ENTITY_ID])
289  unit_of_measurement = get_unit_of_measurement(hass, entry.entity_id)
290  except HomeAssistantError:
291  unit_of_measurement = None
292 
293  if not unit_of_measurement:
295  f"No unit of measurement found for trigger entity {config[CONF_ENTITY_ID]}"
296  )
297 
298  return {
299  "extra_fields": vol.Schema(
300  {
301  vol.Optional(
302  CONF_ABOVE, description={"suffix": unit_of_measurement}
303  ): vol.Coerce(float),
304  vol.Optional(
305  CONF_BELOW, description={"suffix": unit_of_measurement}
306  ): vol.Coerce(float),
307  vol.Optional(CONF_FOR): cv.positive_time_period_dict,
308  }
309  )
310  }
er.RegistryEntry async_get_entity_registry_entry_or_raise(HomeAssistant hass, str entity_registry_id)
Definition: __init__.py:332
dict[str, vol.Schema] async_get_trigger_capabilities(HomeAssistant hass, ConfigType config)
CALLBACK_TYPE async_attach_trigger(HomeAssistant hass, ConfigType config, TriggerActionType action, TriggerInfo trigger_info)
list[dict[str, str]] async_get_triggers(HomeAssistant hass, str device_id)
str|None get_device_class(HomeAssistant hass, str entity_id)
Definition: entity.py:154
Any|None get_capability(HomeAssistant hass, str entity_id, str capability)
Definition: entity.py:139
str|None get_unit_of_measurement(HomeAssistant hass, str entity_id)
Definition: entity.py:184