Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Support to keep track of user controlled buttons which can be used in automations."""
2 
3 from __future__ import annotations
4 
5 import logging
6 from typing import Self, cast
7 
8 import voluptuous as vol
9 
10 from homeassistant.components.button import SERVICE_PRESS, ButtonEntity
11 from homeassistant.const import (
12  ATTR_EDITABLE,
13  CONF_ICON,
14  CONF_ID,
15  CONF_NAME,
16  SERVICE_RELOAD,
17 )
18 from homeassistant.core import HomeAssistant, ServiceCall, callback
19 from homeassistant.helpers import collection
21 from homeassistant.helpers.entity_component import EntityComponent
22 from homeassistant.helpers.restore_state import RestoreEntity
24 from homeassistant.helpers.storage import Store
25 from homeassistant.helpers.typing import ConfigType, VolDictType
26 
27 DOMAIN = "input_button"
28 
29 _LOGGER = logging.getLogger(__name__)
30 
31 STORAGE_FIELDS: VolDictType = {
32  vol.Required(CONF_NAME): vol.All(str, vol.Length(min=1)),
33  vol.Optional(CONF_ICON): cv.icon,
34 }
35 
36 CONFIG_SCHEMA = vol.Schema(
37  {
38  DOMAIN: cv.schema_with_slug_keys(
39  vol.Any(
40  {
41  vol.Optional(CONF_NAME): cv.string,
42  vol.Optional(CONF_ICON): cv.icon,
43  },
44  None,
45  )
46  )
47  },
48  extra=vol.ALLOW_EXTRA,
49 )
50 
51 RELOAD_SERVICE_SCHEMA = vol.Schema({})
52 STORAGE_KEY = DOMAIN
53 STORAGE_VERSION = 1
54 
55 
56 class InputButtonStorageCollection(collection.DictStorageCollection):
57  """Input button collection stored in storage."""
58 
59  CREATE_UPDATE_SCHEMA = vol.Schema(STORAGE_FIELDS)
60 
61  async def _process_create_data(self, data: dict) -> dict[str, str]:
62  """Validate the config is valid."""
63  return self.CREATE_UPDATE_SCHEMACREATE_UPDATE_SCHEMA(data) # type: ignore[no-any-return]
64 
65  @callback
66  def _get_suggested_id(self, info: dict) -> str:
67  """Suggest an ID based on the config."""
68  return cast(str, info[CONF_NAME])
69 
70  async def _update_data(self, item: dict, update_data: dict) -> dict:
71  """Return a new updated data object."""
72  update_data = self.CREATE_UPDATE_SCHEMACREATE_UPDATE_SCHEMA(update_data)
73  return {CONF_ID: item[CONF_ID]} | update_data
74 
75 
76 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
77  """Set up an input button."""
78  component = EntityComponent[InputButton](_LOGGER, DOMAIN, hass)
79 
80  id_manager = collection.IDManager()
81 
82  yaml_collection = collection.YamlCollection(
83  logging.getLogger(f"{__name__}.yaml_collection"), id_manager
84  )
85  collection.sync_entity_lifecycle(
86  hass, DOMAIN, DOMAIN, component, yaml_collection, InputButton
87  )
88 
89  storage_collection = InputButtonStorageCollection(
90  Store(hass, STORAGE_VERSION, STORAGE_KEY),
91  id_manager,
92  )
93  collection.sync_entity_lifecycle(
94  hass, DOMAIN, DOMAIN, component, storage_collection, InputButton
95  )
96 
97  await yaml_collection.async_load(
98  [{CONF_ID: id_, **(conf or {})} for id_, conf in config.get(DOMAIN, {}).items()]
99  )
100  await storage_collection.async_load()
101 
102  collection.DictStorageCollectionWebsocket(
103  storage_collection, DOMAIN, DOMAIN, STORAGE_FIELDS, STORAGE_FIELDS
104  ).async_setup(hass)
105 
106  async def reload_service_handler(service_call: ServiceCall) -> None:
107  """Remove all input buttons and load new ones from config."""
108  conf = await component.async_prepare_reload(skip_reset=True)
109  if conf is None:
110  return
111  await yaml_collection.async_load(
112  [
113  {CONF_ID: id_, **(conf or {})}
114  for id_, conf in conf.get(DOMAIN, {}).items()
115  ]
116  )
117 
119  hass,
120  DOMAIN,
121  SERVICE_RELOAD,
122  reload_service_handler,
123  schema=RELOAD_SERVICE_SCHEMA,
124  )
125 
126  component.async_register_entity_service(SERVICE_PRESS, None, "_async_press_action")
127 
128  return True
129 
130 
131 # pylint: disable-next=hass-enforce-class-module
132 class InputButton(collection.CollectionEntity, ButtonEntity, RestoreEntity):
133  """Representation of a button."""
134 
135  _unrecorded_attributes = frozenset({ATTR_EDITABLE})
136 
137  _attr_should_poll = False
138  editable: bool
139 
140  def __init__(self, config: ConfigType) -> None:
141  """Initialize a button."""
142  self._config_config = config
143  self._attr_unique_id_attr_unique_id = config[CONF_ID]
144 
145  @classmethod
146  def from_storage(cls, config: ConfigType) -> Self:
147  """Return entity instance initialized from storage."""
148  button = cls(config)
149  button.editable = True
150  return button
151 
152  @classmethod
153  def from_yaml(cls, config: ConfigType) -> Self:
154  """Return entity instance initialized from yaml."""
155  button = cls(config)
156  button.entity_id = f"{DOMAIN}.{config[CONF_ID]}"
157  button.editable = False
158  return button
159 
160  @property
161  def name(self) -> str | None:
162  """Return name of the button."""
163  return self._config_config.get(CONF_NAME)
164 
165  @property
166  def icon(self) -> str | None:
167  """Return the icon to be used for this entity."""
168  return self._config_config.get(CONF_ICON)
169 
170  @property
171  def extra_state_attributes(self) -> dict[str, bool]:
172  """Return the state attributes of the entity."""
173  return {ATTR_EDITABLE: self.editable}
174 
175  async def async_press(self) -> None:
176  """Press the button.
177 
178  Left empty intentionally.
179  The input button itself doesn't trigger anything.
180  """
181 
182  async def async_update_config(self, config: ConfigType) -> None:
183  """Handle when the config is updated."""
184  self._config = config
185  self.async_write_ha_state()
dict _update_data(self, dict item, dict update_data)
Definition: __init__.py:70
None __init__(self, ConfigType config)
Definition: __init__.py:140
Self from_storage(cls, ConfigType config)
Definition: __init__.py:146
None async_update_config(self, ConfigType config)
Definition: __init__.py:182
Self from_yaml(cls, ConfigType config)
Definition: __init__.py:153
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:76
None async_register_admin_service(HomeAssistant hass, str domain, str service, Callable[[ServiceCall], Awaitable[None]|None] service_func, VolSchemaType schema=vol.Schema({}, extra=vol.PREVENT_EXTRA))
Definition: service.py:1121