Home Assistant Unofficial Reference 2024.12.1
lock.py
Go to the documentation of this file.
1 """Representation of Z-Wave locks."""
2 
3 from __future__ import annotations
4 
5 from typing import Any
6 
7 import voluptuous as vol
8 from zwave_js_server.client import Client as ZwaveClient
9 from zwave_js_server.const import CommandClass
10 from zwave_js_server.const.command_class.lock import (
11  ATTR_CODE_SLOT,
12  ATTR_USERCODE,
13  LOCK_CMD_CLASS_TO_LOCKED_STATE_MAP,
14  LOCK_CMD_CLASS_TO_PROPERTY_MAP,
15  DoorLockCCConfigurationSetOptions,
16  DoorLockMode,
17  OperationType,
18 )
19 from zwave_js_server.exceptions import BaseZwaveJSServerError
20 from zwave_js_server.util.lock import clear_usercode, set_configuration, set_usercode
21 
22 from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN, LockEntity, LockState
23 from homeassistant.config_entries import ConfigEntry
24 from homeassistant.core import HomeAssistant, callback
25 from homeassistant.exceptions import HomeAssistantError
26 from homeassistant.helpers import config_validation as cv, entity_platform
27 from homeassistant.helpers.dispatcher import async_dispatcher_connect
28 from homeassistant.helpers.entity_platform import AddEntitiesCallback
29 
30 from .const import (
31  ATTR_AUTO_RELOCK_TIME,
32  ATTR_BLOCK_TO_BLOCK,
33  ATTR_HOLD_AND_RELEASE_TIME,
34  ATTR_LOCK_TIMEOUT,
35  ATTR_OPERATION_TYPE,
36  ATTR_TWIST_ASSIST,
37  DATA_CLIENT,
38  DOMAIN,
39  LOGGER,
40  SERVICE_CLEAR_LOCK_USERCODE,
41  SERVICE_SET_LOCK_CONFIGURATION,
42  SERVICE_SET_LOCK_USERCODE,
43 )
44 from .discovery import ZwaveDiscoveryInfo
45 from .entity import ZWaveBaseEntity
46 
47 PARALLEL_UPDATES = 0
48 
49 STATE_TO_ZWAVE_MAP: dict[int, dict[str, int | bool]] = {
50  CommandClass.DOOR_LOCK: {
51  LockState.UNLOCKED: DoorLockMode.UNSECURED,
52  LockState.LOCKED: DoorLockMode.SECURED,
53  },
54  CommandClass.LOCK: {
55  LockState.UNLOCKED: False,
56  LockState.LOCKED: True,
57  },
58 }
59 UNIT16_SCHEMA = vol.All(vol.Coerce(int), vol.Range(min=0, max=65535))
60 
61 
63  hass: HomeAssistant,
64  config_entry: ConfigEntry,
65  async_add_entities: AddEntitiesCallback,
66 ) -> None:
67  """Set up Z-Wave lock from config entry."""
68  client: ZwaveClient = config_entry.runtime_data[DATA_CLIENT]
69 
70  @callback
71  def async_add_lock(info: ZwaveDiscoveryInfo) -> None:
72  """Add Z-Wave Lock."""
73  driver = client.driver
74  assert driver is not None # Driver is ready before platforms are loaded.
75  entities: list[ZWaveBaseEntity] = []
76  entities.append(ZWaveLock(config_entry, driver, info))
77 
78  async_add_entities(entities)
79 
80  config_entry.async_on_unload(
82  hass, f"{DOMAIN}_{config_entry.entry_id}_add_{LOCK_DOMAIN}", async_add_lock
83  )
84  )
85 
86  platform = entity_platform.async_get_current_platform()
87 
88  platform.async_register_entity_service(
89  SERVICE_SET_LOCK_USERCODE,
90  {
91  vol.Required(ATTR_CODE_SLOT): vol.Coerce(int),
92  vol.Required(ATTR_USERCODE): cv.string,
93  },
94  "async_set_lock_usercode",
95  )
96 
97  platform.async_register_entity_service(
98  SERVICE_CLEAR_LOCK_USERCODE,
99  {
100  vol.Required(ATTR_CODE_SLOT): vol.Coerce(int),
101  },
102  "async_clear_lock_usercode",
103  )
104 
105  platform.async_register_entity_service(
106  SERVICE_SET_LOCK_CONFIGURATION,
107  {
108  vol.Required(ATTR_OPERATION_TYPE): vol.All(
109  cv.string,
110  vol.Upper,
111  vol.In(["TIMED", "CONSTANT"]),
112  lambda x: OperationType[x],
113  ),
114  vol.Optional(ATTR_LOCK_TIMEOUT): UNIT16_SCHEMA,
115  vol.Optional(ATTR_AUTO_RELOCK_TIME): UNIT16_SCHEMA,
116  vol.Optional(ATTR_HOLD_AND_RELEASE_TIME): UNIT16_SCHEMA,
117  vol.Optional(ATTR_TWIST_ASSIST): vol.Coerce(bool),
118  vol.Optional(ATTR_BLOCK_TO_BLOCK): vol.Coerce(bool),
119  },
120  "async_set_lock_configuration",
121  )
122 
123 
124 class ZWaveLock(ZWaveBaseEntity, LockEntity):
125  """Representation of a Z-Wave lock."""
126 
127  @property
128  def is_locked(self) -> bool | None:
129  """Return true if the lock is locked."""
130  value = self.infoinfo.primary_value
131  if value.value is None or (
132  value.command_class == CommandClass.DOOR_LOCK
133  and value.value == DoorLockMode.UNKNOWN
134  ):
135  # guard missing value
136  return None
137  return (
138  LOCK_CMD_CLASS_TO_LOCKED_STATE_MAP[CommandClass(value.command_class)]
139  == self.infoinfo.primary_value.value
140  )
141 
142  async def _set_lock_state(self, target_state: LockState, **kwargs: Any) -> None:
143  """Set the lock state."""
144  target_value = self.get_zwave_valueget_zwave_value(
145  LOCK_CMD_CLASS_TO_PROPERTY_MAP[
146  CommandClass(self.infoinfo.primary_value.command_class)
147  ]
148  )
149  if target_value is not None:
150  await self._async_set_value_async_set_value(
151  target_value,
152  STATE_TO_ZWAVE_MAP[self.infoinfo.primary_value.command_class][target_state],
153  )
154 
155  async def async_lock(self, **kwargs: Any) -> None:
156  """Lock the lock."""
157  await self._set_lock_state_set_lock_state(LockState.LOCKED)
158 
159  async def async_unlock(self, **kwargs: Any) -> None:
160  """Unlock the lock."""
161  await self._set_lock_state_set_lock_state(LockState.UNLOCKED)
162 
163  async def async_set_lock_usercode(self, code_slot: int, usercode: str) -> None:
164  """Set the usercode to index X on the lock."""
165  try:
166  await set_usercode(self.infoinfo.node, code_slot, usercode)
167  except BaseZwaveJSServerError as err:
168  raise HomeAssistantError(
169  f"Unable to set lock usercode on lock {self.entity_id} code_slot "
170  f"{code_slot}: {err}"
171  ) from err
172  LOGGER.debug("User code at slot %s on lock %s set", code_slot, self.entity_identity_id)
173 
174  async def async_clear_lock_usercode(self, code_slot: int) -> None:
175  """Clear the usercode at index X on the lock."""
176  try:
177  await clear_usercode(self.infoinfo.node, code_slot)
178  except BaseZwaveJSServerError as err:
179  raise HomeAssistantError(
180  f"Unable to clear lock usercode on lock {self.entity_id} code_slot "
181  f"{code_slot}: {err}"
182  ) from err
183  LOGGER.debug(
184  "User code at slot %s on lock %s cleared", code_slot, self.entity_identity_id
185  )
186 
188  self,
189  operation_type: OperationType,
190  lock_timeout: int | None = None,
191  auto_relock_time: int | None = None,
192  hold_and_release_time: int | None = None,
193  twist_assist: bool | None = None,
194  block_to_block: bool | None = None,
195  ) -> None:
196  """Set the lock configuration."""
197  params: dict[str, Any] = {"operation_type": operation_type}
198  params.update(
199  {
200  attr: val
201  for attr, val in (
202  ("lock_timeout_configuration", lock_timeout),
203  ("auto_relock_time", auto_relock_time),
204  ("hold_and_release_time", hold_and_release_time),
205  ("twist_assist", twist_assist),
206  ("block_to_block", block_to_block),
207  )
208  if val is not None
209  }
210  )
211  configuration = DoorLockCCConfigurationSetOptions(**params)
212  result = await set_configuration(
213  self.infoinfo.node.endpoints[self.infoinfo.primary_value.endpoint or 0],
214  configuration,
215  )
216  if result is None:
217  return
218  msg = f"Result status is {result.status}"
219  if result.remaining_duration is not None:
220  msg += f" and remaining duration is {result.remaining_duration!s}"
221  LOGGER.info("%s after setting lock configuration for %s", msg, self.entity_identity_id)
SetValueResult|None _async_set_value(self, ZwaveValue value, Any new_value, dict|None options=None, bool|None wait_for_result=None)
Definition: entity.py:330
ZwaveValue|None get_zwave_value(self, str|int value_property, int|None command_class=None, int|None endpoint=None, int|str|None value_property_key=None, bool add_to_watched_value_ids=True, bool check_all_endpoints=False)
Definition: entity.py:280
None _set_lock_state(self, LockState target_state, **Any kwargs)
Definition: lock.py:142
None async_set_lock_configuration(self, OperationType operation_type, int|None lock_timeout=None, int|None auto_relock_time=None, int|None hold_and_release_time=None, bool|None twist_assist=None, bool|None block_to_block=None)
Definition: lock.py:195
None async_unlock(self, **Any kwargs)
Definition: lock.py:159
None async_set_lock_usercode(self, int code_slot, str usercode)
Definition: lock.py:163
None async_clear_lock_usercode(self, int code_slot)
Definition: lock.py:174
None async_lock(self, **Any kwargs)
Definition: lock.py:155
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: lock.py:66
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103