Home Assistant Unofficial Reference 2024.12.1
trigger.py
Go to the documentation of this file.
1 """Offer knx telegram automation triggers."""
2 
3 from typing import Final
4 
5 import voluptuous as vol
6 from xknx.dpt import DPTBase
7 from xknx.telegram import Telegram, TelegramDirection
8 from xknx.telegram.address import DeviceGroupAddress, parse_device_group_address
9 from xknx.telegram.apci import GroupValueRead, GroupValueResponse, GroupValueWrite
10 
11 from homeassistant.const import CONF_PLATFORM, CONF_TYPE
12 from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback
13 from homeassistant.helpers import config_validation as cv
14 from homeassistant.helpers.dispatcher import async_dispatcher_connect
15 from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo
16 from homeassistant.helpers.typing import ConfigType, VolDictType
17 
18 from .const import DOMAIN
19 from .schema import ga_validator
20 from .telegrams import SIGNAL_KNX_TELEGRAM, TelegramDict, decode_telegram_payload
21 from .validation import dpt_base_type_validator
22 
23 TRIGGER_TELEGRAM: Final = "telegram"
24 
25 PLATFORM_TYPE_TRIGGER_TELEGRAM: Final = f"{DOMAIN}.{TRIGGER_TELEGRAM}"
26 
27 CONF_KNX_DESTINATION: Final = "destination"
28 CONF_KNX_GROUP_VALUE_WRITE: Final = "group_value_write"
29 CONF_KNX_GROUP_VALUE_READ: Final = "group_value_read"
30 CONF_KNX_GROUP_VALUE_RESPONSE: Final = "group_value_response"
31 CONF_KNX_INCOMING: Final = "incoming"
32 CONF_KNX_OUTGOING: Final = "outgoing"
33 
34 
35 TELEGRAM_TRIGGER_SCHEMA: VolDictType = {
36  vol.Optional(CONF_KNX_DESTINATION): vol.All(cv.ensure_list, [ga_validator]),
37  vol.Optional(CONF_KNX_GROUP_VALUE_WRITE, default=True): cv.boolean,
38  vol.Optional(CONF_KNX_GROUP_VALUE_RESPONSE, default=True): cv.boolean,
39  vol.Optional(CONF_KNX_GROUP_VALUE_READ, default=True): cv.boolean,
40  vol.Optional(CONF_KNX_INCOMING, default=True): cv.boolean,
41  vol.Optional(CONF_KNX_OUTGOING, default=True): cv.boolean,
42 }
43 # TRIGGER_SCHEMA is exclusive to triggers, the above are used in device triggers too
44 TRIGGER_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend(
45  {
46  vol.Required(CONF_PLATFORM): PLATFORM_TYPE_TRIGGER_TELEGRAM,
47  vol.Optional(CONF_TYPE, default=None): vol.Any(dpt_base_type_validator, None),
48  **TELEGRAM_TRIGGER_SCHEMA,
49  }
50 )
51 
52 
54  hass: HomeAssistant,
55  config: ConfigType,
56  action: TriggerActionType,
57  trigger_info: TriggerInfo,
58 ) -> CALLBACK_TYPE:
59  """Listen for telegrams based on configuration."""
60  _addresses: list[str] = config.get(CONF_KNX_DESTINATION, [])
61  dst_addresses: list[DeviceGroupAddress] = [
62  parse_device_group_address(address) for address in _addresses
63  ]
64  _transcoder = config.get(CONF_TYPE)
65  trigger_transcoder = DPTBase.parse_transcoder(_transcoder) if _transcoder else None
66 
67  job = HassJob(action, f"KNX trigger {trigger_info}")
68  trigger_data = trigger_info["trigger_data"]
69 
70  @callback
71  def async_call_trigger_action(
72  telegram: Telegram, telegram_dict: TelegramDict
73  ) -> None:
74  """Filter Telegram and call trigger action."""
75  payload_apci = type(telegram.payload)
76  if payload_apci is GroupValueWrite:
77  if config[CONF_KNX_GROUP_VALUE_WRITE] is False:
78  return
79  elif payload_apci is GroupValueResponse:
80  if config[CONF_KNX_GROUP_VALUE_RESPONSE] is False:
81  return
82  elif payload_apci is GroupValueRead:
83  if config[CONF_KNX_GROUP_VALUE_READ] is False:
84  return
85 
86  if telegram.direction is TelegramDirection.INCOMING:
87  if config[CONF_KNX_INCOMING] is False:
88  return
89  elif config[CONF_KNX_OUTGOING] is False:
90  return
91 
92  if dst_addresses and telegram.destination_address not in dst_addresses:
93  return
94 
95  if (
96  trigger_transcoder is not None
97  and payload_apci in (GroupValueWrite, GroupValueResponse)
98  and trigger_transcoder.value_type != telegram_dict["dpt_name"]
99  ):
100  decoded_payload = decode_telegram_payload(
101  payload=telegram.payload.value, # type: ignore[union-attr] # checked via payload_apci
102  transcoder=trigger_transcoder,
103  )
104  # overwrite decoded payload values in telegram_dict
105  telegram_trigger_data = {**trigger_data, **telegram_dict, **decoded_payload}
106  else:
107  telegram_trigger_data = {**trigger_data, **telegram_dict}
108 
109  hass.async_run_hass_job(job, {"trigger": telegram_trigger_data})
110 
112  hass,
113  signal=SIGNAL_KNX_TELEGRAM,
114  target=async_call_trigger_action,
115  )
DecodedTelegramPayload decode_telegram_payload(DPTArray|DPTBinary payload, type[DPTBase] transcoder)
Definition: telegrams.py:169
CALLBACK_TYPE async_attach_trigger(HomeAssistant hass, ConfigType config, TriggerActionType action, TriggerInfo trigger_info)
Definition: trigger.py:58
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103