Home Assistant Unofficial Reference 2024.12.1
button.py
Go to the documentation of this file.
1 """Support for Homekit buttons.
2 
3 These are mostly used where a HomeKit accessory exposes additional non-standard
4 characteristics that don't map to a Home Assistant feature.
5 """
6 
7 from __future__ import annotations
8 
9 from collections.abc import Callable
10 from dataclasses import dataclass
11 import logging
12 
13 from aiohomekit.model.characteristics import Characteristic, CharacteristicsTypes
14 
16  ButtonDeviceClass,
17  ButtonEntity,
18  ButtonEntityDescription,
19 )
20 from homeassistant.config_entries import ConfigEntry
21 from homeassistant.const import EntityCategory, Platform
22 from homeassistant.core import HomeAssistant, callback
23 from homeassistant.helpers.entity_platform import AddEntitiesCallback
24 from homeassistant.helpers.typing import ConfigType
25 
26 from . import KNOWN_DEVICES
27 from .connection import HKDevice
28 from .entity import CharacteristicEntity
29 
30 _LOGGER = logging.getLogger(__name__)
31 
32 
33 @dataclass(frozen=True)
35  """Describes Homekit button."""
36 
37  probe: Callable[[Characteristic], bool] | None = None
38  write_value: int | str | None = None
39 
40 
41 BUTTON_ENTITIES: dict[str, HomeKitButtonEntityDescription] = {
42  CharacteristicsTypes.VENDOR_HAA_SETUP: HomeKitButtonEntityDescription(
43  key=CharacteristicsTypes.VENDOR_HAA_SETUP,
44  name="Setup",
45  translation_key="setup",
46  entity_category=EntityCategory.CONFIG,
47  write_value="#HAA@trcmd", # codespell:ignore haa
48  ),
49  CharacteristicsTypes.VENDOR_HAA_UPDATE: HomeKitButtonEntityDescription(
50  key=CharacteristicsTypes.VENDOR_HAA_UPDATE,
51  name="Update",
52  device_class=ButtonDeviceClass.UPDATE,
53  entity_category=EntityCategory.CONFIG,
54  write_value="#HAA@trcmd", # codespell:ignore haa
55  ),
56  CharacteristicsTypes.IDENTIFY: HomeKitButtonEntityDescription(
57  key=CharacteristicsTypes.IDENTIFY,
58  name="Identify",
59  device_class=ButtonDeviceClass.IDENTIFY,
60  entity_category=EntityCategory.DIAGNOSTIC,
61  write_value=True,
62  ),
63 }
64 
65 
67  hass: HomeAssistant,
68  config_entry: ConfigEntry,
69  async_add_entities: AddEntitiesCallback,
70 ) -> None:
71  """Set up Homekit buttons."""
72  hkid: str = config_entry.data["AccessoryPairingID"]
73  conn: HKDevice = hass.data[KNOWN_DEVICES][hkid]
74 
75  @callback
76  def async_add_characteristic(char: Characteristic) -> bool:
77  entities: list[CharacteristicEntity] = []
78  info = {"aid": char.service.accessory.aid, "iid": char.service.iid}
79 
80  if description := BUTTON_ENTITIES.get(char.type):
81  entities.append(HomeKitButton(conn, info, char, description))
82  elif entity_type := BUTTON_ENTITY_CLASSES.get(char.type):
83  entities.append(entity_type(conn, info, char))
84  elif char.type == CharacteristicsTypes.THREAD_CONTROL_POINT:
85  if not conn.is_unprovisioned_thread_device:
86  return False
87  entities.append(
89  )
90  else:
91  return False
92 
93  for entity in entities:
94  conn.async_migrate_unique_id(
95  entity.old_unique_id, entity.unique_id, Platform.BUTTON
96  )
97 
98  async_add_entities(entities)
99  return True
100 
101  conn.add_char_factory(async_add_characteristic)
102 
103 
105  """Base class for all HomeKit buttons."""
106 
107 
108 class HomeKitButton(BaseHomeKitButton):
109  """Representation of a Button control on a homekit accessory."""
110 
111  entity_description: HomeKitButtonEntityDescription
112 
113  def __init__(
114  self,
115  conn: HKDevice,
116  info: ConfigType,
117  char: Characteristic,
118  description: HomeKitButtonEntityDescription,
119  ) -> None:
120  """Initialise a HomeKit button control."""
121  self.entity_descriptionentity_description = description
122  super().__init__(conn, info, char)
123 
124  def get_characteristic_types(self) -> list[str]:
125  """Define the homekit characteristics the entity is tracking."""
126  return [self._char_char.type]
127 
128  @property
129  def name(self) -> str:
130  """Return the name of the device if any."""
131  if name := self.accessoryaccessory.name:
132  return f"{name} {self.entity_description.name}"
133  return f"{self.entity_description.name}"
134 
135  async def async_press(self) -> None:
136  """Press the button."""
137  key = self.entity_descriptionentity_description.key
138  val = self.entity_descriptionentity_description.write_value
139  await self.async_put_characteristicsasync_put_characteristics({key: val})
140 
141 
143  """Representation of a Button control for Ecobee clear hold request."""
144 
145  def get_characteristic_types(self) -> list[str]:
146  """Define the homekit characteristics the entity is tracking."""
147  return []
148 
149  @property
150  def name(self) -> str:
151  """Return the name of the device if any."""
152  prefix = ""
153  if name := super().name:
154  prefix = name
155  return f"{prefix} Clear Hold"
156 
157  async def async_press(self) -> None:
158  """Press the button."""
159  key = self._char_char.type
160 
161  # If we just send true, the request doesn't always get executed by ecobee.
162  # Sending false value then true value will ensure that the hold gets cleared
163  # and schedule resumed.
164  # Ecobee seems to cache the state and not update it correctly, which
165  # causes the request to be ignored if it thinks it has no effect.
166 
167  for val in (False, True):
168  await self.async_put_characteristicsasync_put_characteristics({key: val})
169 
170 
172  """A button users can press to migrate their HomeKit BLE device to Thread."""
173 
174  _attr_entity_category = EntityCategory.CONFIG
175 
176  def get_characteristic_types(self) -> list[str]:
177  """Define the homekit characteristics the entity is tracking."""
178  return []
179 
180  @property
181  def name(self) -> str:
182  """Return the name of the device if any."""
183  prefix = ""
184  if name := super().name:
185  prefix = name
186  return f"{prefix} Provision Preferred Thread Credentials"
187 
188  async def async_press(self) -> None:
189  """Press the button."""
190  await self._accessory_accessory.async_thread_provision()
191 
192 
193 BUTTON_ENTITY_CLASSES: dict[str, type] = {
194  CharacteristicsTypes.VENDOR_ECOBEE_CLEAR_HOLD: HomeKitEcobeeClearHoldButton,
195 }
None __init__(self, HKDevice conn, ConfigType info, Characteristic char, HomeKitButtonEntityDescription description)
Definition: button.py:119
None async_put_characteristics(self, dict[str, Any] characteristics)
Definition: entity.py:125
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: button.py:70
bool async_add_characteristic(Characteristic char)
Definition: number.py:78