Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Support to keep track of user controlled booleans for within automation."""
2 
3 from __future__ import annotations
4 
5 import logging
6 from typing import Any, Self
7 
8 import voluptuous as vol
9 
10 from homeassistant.const import (
11  ATTR_EDITABLE,
12  CONF_ICON,
13  CONF_ID,
14  CONF_NAME,
15  SERVICE_RELOAD,
16  SERVICE_TOGGLE,
17  SERVICE_TURN_OFF,
18  SERVICE_TURN_ON,
19  STATE_ON,
20 )
21 from homeassistant.core import HomeAssistant, ServiceCall, callback
22 from homeassistant.helpers import collection
24 from homeassistant.helpers.entity import ToggleEntity
25 from homeassistant.helpers.entity_component import EntityComponent
26 from homeassistant.helpers.restore_state import RestoreEntity
28 from homeassistant.helpers.storage import Store
29 from homeassistant.helpers.typing import ConfigType, VolDictType
30 from homeassistant.loader import bind_hass
31 
32 DOMAIN = "input_boolean"
33 
34 _LOGGER = logging.getLogger(__name__)
35 
36 CONF_INITIAL = "initial"
37 
38 STORAGE_FIELDS: VolDictType = {
39  vol.Required(CONF_NAME): vol.All(str, vol.Length(min=1)),
40  vol.Optional(CONF_INITIAL): cv.boolean,
41  vol.Optional(CONF_ICON): cv.icon,
42 }
43 
44 CONFIG_SCHEMA = vol.Schema(
45  {
46  DOMAIN: cv.schema_with_slug_keys(
47  vol.Any(
48  {
49  vol.Optional(CONF_NAME): cv.string,
50  vol.Optional(CONF_INITIAL): cv.boolean,
51  vol.Optional(CONF_ICON): cv.icon,
52  },
53  None,
54  )
55  )
56  },
57  extra=vol.ALLOW_EXTRA,
58 )
59 
60 RELOAD_SERVICE_SCHEMA = vol.Schema({})
61 STORAGE_KEY = DOMAIN
62 STORAGE_VERSION = 1
63 
64 
65 class InputBooleanStorageCollection(collection.DictStorageCollection):
66  """Input boolean collection stored in storage."""
67 
68  CREATE_UPDATE_SCHEMA = vol.Schema(STORAGE_FIELDS)
69 
70  async def _process_create_data(self, data: dict) -> dict:
71  """Validate the config is valid."""
72  return self.CREATE_UPDATE_SCHEMACREATE_UPDATE_SCHEMA(data)
73 
74  @callback
75  def _get_suggested_id(self, info: dict) -> str:
76  """Suggest an ID based on the config."""
77  return info[CONF_NAME]
78 
79  async def _update_data(self, item: dict, update_data: dict) -> dict:
80  """Return a new updated data object."""
81  update_data = self.CREATE_UPDATE_SCHEMACREATE_UPDATE_SCHEMA(update_data)
82  return {CONF_ID: item[CONF_ID]} | update_data
83 
84 
85 @bind_hass
86 def is_on(hass: HomeAssistant, entity_id: str) -> bool:
87  """Test if input_boolean is True."""
88  return hass.states.is_state(entity_id, STATE_ON)
89 
90 
91 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
92  """Set up an input boolean."""
93  component = EntityComponent[InputBoolean](_LOGGER, DOMAIN, hass)
94 
95  id_manager = collection.IDManager()
96 
97  yaml_collection = collection.YamlCollection(
98  logging.getLogger(f"{__name__}.yaml_collection"), id_manager
99  )
100  collection.sync_entity_lifecycle(
101  hass, DOMAIN, DOMAIN, component, yaml_collection, InputBoolean
102  )
103 
104  storage_collection = InputBooleanStorageCollection(
105  Store(hass, STORAGE_VERSION, STORAGE_KEY),
106  id_manager,
107  )
108  collection.sync_entity_lifecycle(
109  hass, DOMAIN, DOMAIN, component, storage_collection, InputBoolean
110  )
111 
112  await yaml_collection.async_load(
113  [{CONF_ID: id_, **(conf or {})} for id_, conf in config.get(DOMAIN, {}).items()]
114  )
115  await storage_collection.async_load()
116 
117  collection.DictStorageCollectionWebsocket(
118  storage_collection, DOMAIN, DOMAIN, STORAGE_FIELDS, STORAGE_FIELDS
119  ).async_setup(hass)
120 
121  async def reload_service_handler(service_call: ServiceCall) -> None:
122  """Remove all input booleans and load new ones from config."""
123  conf = await component.async_prepare_reload(skip_reset=True)
124  if conf is None:
125  return
126  await yaml_collection.async_load(
127  [
128  {CONF_ID: id_, **(conf or {})}
129  for id_, conf in conf.get(DOMAIN, {}).items()
130  ]
131  )
132 
134  hass,
135  DOMAIN,
136  SERVICE_RELOAD,
137  reload_service_handler,
138  schema=RELOAD_SERVICE_SCHEMA,
139  )
140 
141  component.async_register_entity_service(SERVICE_TURN_ON, None, "async_turn_on")
142 
143  component.async_register_entity_service(SERVICE_TURN_OFF, None, "async_turn_off")
144 
145  component.async_register_entity_service(SERVICE_TOGGLE, None, "async_toggle")
146 
147  return True
148 
149 
150 class InputBoolean(collection.CollectionEntity, ToggleEntity, RestoreEntity):
151  """Representation of a boolean input."""
152 
153  _unrecorded_attributes = frozenset({ATTR_EDITABLE})
154 
155  _attr_should_poll = False
156  editable: bool
157 
158  def __init__(self, config: ConfigType) -> None:
159  """Initialize a boolean input."""
160  self._config_config = config
161  self._attr_is_on_attr_is_on = config.get(CONF_INITIAL, False)
162  self._attr_unique_id_attr_unique_id = config[CONF_ID]
163 
164  @classmethod
165  def from_storage(cls, config: ConfigType) -> Self:
166  """Return entity instance initialized from storage."""
167  input_bool = cls(config)
168  input_bool.editable = True
169  return input_bool
170 
171  @classmethod
172  def from_yaml(cls, config: ConfigType) -> Self:
173  """Return entity instance initialized from yaml."""
174  input_bool = cls(config)
175  input_bool.entity_id = f"{DOMAIN}.{config[CONF_ID]}"
176  input_bool.editable = False
177  return input_bool
178 
179  @property
180  def name(self) -> str | None:
181  """Return name of the boolean input."""
182  return self._config_config.get(CONF_NAME)
183 
184  @property
185  def icon(self) -> str | None:
186  """Return the icon to be used for this entity."""
187  return self._config_config.get(CONF_ICON)
188 
189  @property
190  def extra_state_attributes(self) -> dict[str, bool]:
191  """Return the state attributes of the entity."""
192  return {ATTR_EDITABLE: self.editable}
193 
194  async def async_added_to_hass(self) -> None:
195  """Call when entity about to be added to hass."""
196  # Don't restore if we got an initial value.
197  await super().async_added_to_hass()
198  if self._config_config.get(CONF_INITIAL) is not None:
199  return
200 
201  state = await self.async_get_last_stateasync_get_last_state()
202  self._attr_is_on_attr_is_on = state is not None and state.state == STATE_ON
203 
204  async def async_turn_on(self, **kwargs: Any) -> None:
205  """Turn the entity on."""
206  self._attr_is_on_attr_is_on = True
207  self.async_write_ha_stateasync_write_ha_state()
208 
209  async def async_turn_off(self, **kwargs: Any) -> None:
210  """Turn the entity off."""
211  self._attr_is_on_attr_is_on = False
212  self.async_write_ha_stateasync_write_ha_state()
213 
214  async def async_update_config(self, config: ConfigType) -> None:
215  """Handle when the config is updated."""
216  self._config_config = config
217  self.async_write_ha_stateasync_write_ha_state()
dict _update_data(self, dict item, dict update_data)
Definition: __init__.py:79
None async_update_config(self, ConfigType config)
Definition: __init__.py:214
None __init__(self, ConfigType config)
Definition: __init__.py:158
Self from_storage(cls, ConfigType config)
Definition: __init__.py:165
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:91
bool is_on(HomeAssistant hass, str entity_id)
Definition: __init__.py:86
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