Home Assistant Unofficial Reference 2024.12.1
lock.py
Go to the documentation of this file.
1 """Support for locks which integrates with other components."""
2 
3 from __future__ import annotations
4 
5 from typing import TYPE_CHECKING, Any
6 
7 import voluptuous as vol
8 
10  PLATFORM_SCHEMA as LOCK_PLATFORM_SCHEMA,
11  LockEntity,
12  LockEntityFeature,
13  LockState,
14 )
15 from homeassistant.const import (
16  ATTR_CODE,
17  CONF_NAME,
18  CONF_OPTIMISTIC,
19  CONF_UNIQUE_ID,
20  CONF_VALUE_TEMPLATE,
21  STATE_ON,
22 )
23 from homeassistant.core import HomeAssistant, callback
24 from homeassistant.exceptions import ServiceValidationError, TemplateError
26 from homeassistant.helpers.entity_platform import AddEntitiesCallback
27 from homeassistant.helpers.script import Script
28 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
29 
30 from .const import DOMAIN
31 from .template_entity import (
32  TEMPLATE_ENTITY_AVAILABILITY_SCHEMA_LEGACY,
33  TemplateEntity,
34  rewrite_common_legacy_to_modern_conf,
35 )
36 
37 CONF_CODE_FORMAT_TEMPLATE = "code_format_template"
38 CONF_LOCK = "lock"
39 CONF_UNLOCK = "unlock"
40 CONF_OPEN = "open"
41 
42 DEFAULT_NAME = "Template Lock"
43 DEFAULT_OPTIMISTIC = False
44 
45 PLATFORM_SCHEMA = LOCK_PLATFORM_SCHEMA.extend(
46  {
47  vol.Optional(CONF_NAME): cv.string,
48  vol.Required(CONF_LOCK): cv.SCRIPT_SCHEMA,
49  vol.Required(CONF_UNLOCK): cv.SCRIPT_SCHEMA,
50  vol.Optional(CONF_OPEN): cv.SCRIPT_SCHEMA,
51  vol.Required(CONF_VALUE_TEMPLATE): cv.template,
52  vol.Optional(CONF_CODE_FORMAT_TEMPLATE): cv.template,
53  vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
54  vol.Optional(CONF_UNIQUE_ID): cv.string,
55  }
56 ).extend(TEMPLATE_ENTITY_AVAILABILITY_SCHEMA_LEGACY.schema)
57 
58 
60  hass: HomeAssistant, config: dict[str, Any]
61 ) -> list[TemplateLock]:
62  """Create the Template lock."""
63  config = rewrite_common_legacy_to_modern_conf(hass, config)
64  return [TemplateLock(hass, config, config.get(CONF_UNIQUE_ID))]
65 
66 
68  hass: HomeAssistant,
69  config: ConfigType,
70  async_add_entities: AddEntitiesCallback,
71  discovery_info: DiscoveryInfoType | None = None,
72 ) -> None:
73  """Set up the template lock."""
74  async_add_entities(await _async_create_entities(hass, config))
75 
76 
78  """Representation of a template lock."""
79 
80  _attr_should_poll = False
81 
82  def __init__(
83  self,
84  hass: HomeAssistant,
85  config: dict[str, Any],
86  unique_id: str | None,
87  ) -> None:
88  """Initialize the lock."""
89  super().__init__(
90  hass, config=config, fallback_name=DEFAULT_NAME, unique_id=unique_id
91  )
92  self._state_state: str | bool | LockState | None = None
93  name = self._attr_name_attr_name
94  assert name
95  self._state_template_state_template = config.get(CONF_VALUE_TEMPLATE)
96  self._command_lock_command_lock = Script(hass, config[CONF_LOCK], name, DOMAIN)
97  self._command_unlock_command_unlock = Script(hass, config[CONF_UNLOCK], name, DOMAIN)
98  if CONF_OPEN in config:
99  self._command_open_command_open = Script(hass, config[CONF_OPEN], name, DOMAIN)
100  self._attr_supported_features |= LockEntityFeature.OPEN
101  self._code_format_template_code_format_template = config.get(CONF_CODE_FORMAT_TEMPLATE)
102  self._code_format_code_format: str | None = None
103  self._code_format_template_error_code_format_template_error: TemplateError | None = None
104  self._optimistic_optimistic = config.get(CONF_OPTIMISTIC)
105  self._attr_assumed_state_attr_assumed_state = bool(self._optimistic_optimistic)
106 
107  @property
108  def is_locked(self) -> bool:
109  """Return true if lock is locked."""
110  return self._state_state in ("true", STATE_ON, LockState.LOCKED)
111 
112  @property
113  def is_jammed(self) -> bool:
114  """Return true if lock is jammed."""
115  return self._state_state == LockState.JAMMED
116 
117  @property
118  def is_unlocking(self) -> bool:
119  """Return true if lock is unlocking."""
120  return self._state_state == LockState.UNLOCKING
121 
122  @property
123  def is_locking(self) -> bool:
124  """Return true if lock is locking."""
125  return self._state_state == LockState.LOCKING
126 
127  @property
128  def is_open(self) -> bool:
129  """Return true if lock is open."""
130  return self._state_state == LockState.OPEN
131 
132  @callback
133  def _update_state(self, result):
134  """Update the state from the template."""
135  super()._update_state(result)
136  if isinstance(result, TemplateError):
137  self._state_state = None
138  return
139 
140  if isinstance(result, bool):
141  self._state_state = LockState.LOCKED if result else LockState.UNLOCKED
142  return
143 
144  if isinstance(result, str):
145  self._state_state = result.lower()
146  return
147 
148  self._state_state = None
149 
150  @property
151  def code_format(self) -> str | None:
152  """Regex for code format or None if no code is required."""
153  return self._code_format_code_format
154 
155  @callback
156  def _async_setup_templates(self) -> None:
157  """Set up templates."""
158  if TYPE_CHECKING:
159  assert self._state_template_state_template is not None
160  self.add_template_attributeadd_template_attribute(
161  "_state", self._state_template_state_template, None, self._update_state_update_state_update_state
162  )
163  if self._code_format_template_code_format_template:
164  self.add_template_attributeadd_template_attribute(
165  "_code_format_template",
166  self._code_format_template_code_format_template,
167  None,
168  self._update_code_format_update_code_format,
169  )
170  super()._async_setup_templates()
171 
172  @callback
173  def _update_code_format(self, render: str | TemplateError | None):
174  """Update code format from the template."""
175  if isinstance(render, TemplateError):
176  self._code_format_code_format = None
177  self._code_format_template_error_code_format_template_error = render
178  elif render in (None, "None", ""):
179  self._code_format_code_format = None
180  self._code_format_template_error_code_format_template_error = None
181  else:
182  self._code_format_code_format = render
183  self._code_format_template_error_code_format_template_error = None
184 
185  async def async_lock(self, **kwargs: Any) -> None:
186  """Lock the device."""
187  # Check if we need to raise for incorrect code format
188  # template before processing the action.
189  self._raise_template_error_if_available_raise_template_error_if_available()
190 
191  if self._optimistic_optimistic:
192  self._state_state = True
193  self.async_write_ha_stateasync_write_ha_state()
194 
195  tpl_vars = {ATTR_CODE: kwargs.get(ATTR_CODE) if kwargs else None}
196 
197  await self.async_run_scriptasync_run_script(
198  self._command_lock_command_lock, run_variables=tpl_vars, context=self._context_context
199  )
200 
201  async def async_unlock(self, **kwargs: Any) -> None:
202  """Unlock the device."""
203  # Check if we need to raise for incorrect code format
204  # template before processing the action.
205  self._raise_template_error_if_available_raise_template_error_if_available()
206 
207  if self._optimistic_optimistic:
208  self._state_state = False
209  self.async_write_ha_stateasync_write_ha_state()
210 
211  tpl_vars = {ATTR_CODE: kwargs.get(ATTR_CODE) if kwargs else None}
212 
213  await self.async_run_scriptasync_run_script(
214  self._command_unlock_command_unlock, run_variables=tpl_vars, context=self._context_context
215  )
216 
217  async def async_open(self, **kwargs: Any) -> None:
218  """Open the device."""
219  # Check if we need to raise for incorrect code format
220  # template before processing the action.
221  self._raise_template_error_if_available_raise_template_error_if_available()
222 
223  if self._optimistic_optimistic:
224  self._state_state = LockState.OPEN
225  self.async_write_ha_stateasync_write_ha_state()
226 
227  tpl_vars = {ATTR_CODE: kwargs.get(ATTR_CODE) if kwargs else None}
228 
229  await self.async_run_scriptasync_run_script(
230  self._command_open_command_open, run_variables=tpl_vars, context=self._context_context
231  )
232 
234  """Raise an error if the rendered code format is not valid."""
235  if self._code_format_template_error_code_format_template_error is not None:
237  translation_domain=DOMAIN,
238  translation_key="code_format_template_error",
239  translation_placeholders={
240  "entity_id": self.entity_identity_identity_id,
241  "code_format_template": self._code_format_template_code_format_template.template,
242  "cause": str(self._code_format_template_error_code_format_template_error),
243  },
244  )
None __init__(self, HomeAssistant hass, dict[str, Any] config, str|None unique_id)
Definition: lock.py:87
def _update_code_format(self, str|TemplateError|None render)
Definition: lock.py:173
None async_run_script(self, Script script, *_VarsType|None run_variables=None, Context|None context=None)
None add_template_attribute(self, str attribute, Template template, Callable[[Any], Any]|None validator=None, Callable[[Any], None]|None on_update=None, bool none_on_template_error=False)
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: lock.py:72
list[TemplateLock] _async_create_entities(HomeAssistant hass, dict[str, Any] config)
Definition: lock.py:61
dict[str, Any] rewrite_common_legacy_to_modern_conf(HomeAssistant hass, dict[str, Any] entity_cfg, dict[str, str]|None extra_legacy_fields=None)