55from typing import Any
66
77from pyschlage .code import AccessCode
8- import voluptuous as vol
98
109from homeassistant .components .lock import LockEntity
11- from homeassistant .core import (
12- HomeAssistant ,
13- ServiceCall ,
14- ServiceResponse ,
15- SupportsResponse ,
16- callback ,
17- )
10+ from homeassistant .core import HomeAssistant , ServiceResponse , callback
1811from homeassistant .exceptions import ServiceValidationError
19- from homeassistant .helpers import config_validation as cv , entity_platform
2012from homeassistant .helpers .entity_platform import AddConfigEntryEntitiesCallback
2113
22- from .const import DOMAIN , SERVICE_ADD_CODE , SERVICE_DELETE_CODE , SERVICE_GET_CODES
14+ from .const import DOMAIN
2315from .coordinator import LockData , SchlageConfigEntry , SchlageDataUpdateCoordinator
2416from .entity import SchlageEntity
2517
@@ -41,16 +33,56 @@ def _add_new_locks(locks: dict[str, LockData]) -> None:
4133 _add_new_locks (coordinator .data .locks )
4234 coordinator .new_locks_callbacks .append (_add_new_locks )
4335
44- # Custom services
45- def _validate_code_name (codes : dict [str , AccessCode ] | None , name : str ) -> None :
36+
37+ class SchlageLockEntity (SchlageEntity , LockEntity ):
38+ """Schlage lock entity."""
39+
40+ _attr_name = None
41+
42+ def __init__ (
43+ self , coordinator : SchlageDataUpdateCoordinator , device_id : str
44+ ) -> None :
45+ """Initialize a Schlage Lock."""
46+ super ().__init__ (coordinator = coordinator , device_id = device_id )
47+ self ._update_attrs ()
48+
49+ @callback
50+ def _handle_coordinator_update (self ) -> None :
51+ """Handle updated data from the coordinator."""
52+ if self .device_id in self .coordinator .data .locks :
53+ self ._update_attrs ()
54+ super ()._handle_coordinator_update ()
55+
56+ def _update_attrs (self ) -> None :
57+ """Update our internal state attributes."""
58+ self ._attr_is_locked = self ._lock .is_locked
59+ self ._attr_is_jammed = self ._lock .is_jammed
60+ self ._attr_changed_by = self ._lock .last_changed_by ()
61+
62+ async def async_lock (self , ** kwargs : Any ) -> None :
63+ """Lock the device."""
64+ await self .hass .async_add_executor_job (self ._lock .lock )
65+ await self .coordinator .async_request_refresh ()
66+
67+ async def async_unlock (self , ** kwargs : Any ) -> None :
68+ """Unlock the device."""
69+ await self .hass .async_add_executor_job (self ._lock .unlock )
70+ await self .coordinator .async_request_refresh ()
71+
72+ # Door code services
73+ def _validate_code_name (
74+ self , codes : dict [str , AccessCode ] | None , name : str
75+ ) -> None :
4676 """Validate that the code name doesn't already exist."""
4777 if codes and any (code .name .lower () == name .lower () for code in codes .values ()):
4878 raise ServiceValidationError (
4979 translation_domain = DOMAIN ,
5080 translation_key = "schlage_name_exists" ,
5181 )
5282
53- def _validate_code_value (codes : dict [str , AccessCode ] | None , code : str ) -> None :
83+ def _validate_code_value (
84+ self , codes : dict [str , AccessCode ] | None , code : str
85+ ) -> None :
5486 """Validate that the code value doesn't already exist."""
5587 if codes and any (
5688 existing_code .code == code for existing_code in codes .values ()
@@ -60,18 +92,8 @@ def _validate_code_value(codes: dict[str, AccessCode] | None, code: str) -> None
6092 translation_key = "schlage_code_exists" ,
6193 )
6294
63- async def _add_code ( entity : SchlageLockEntity , calls : ServiceCall ) -> None :
95+ async def add_code ( self , name : str , code : str ) -> None :
6496 """Add a lock code."""
65- name = calls .data .get ("name" )
66- code = calls .data .get ("code" )
67-
68- # name is required by voluptuous, the following is just to satisfy type
69- # checker, it should never be None
70- if name is None :
71- raise ServiceValidationError (
72- translation_domain = DOMAIN ,
73- translation_key = "schlage_name_required" ,
74- ) # pragma: no cover
7597
7698 # code is required by voluptuous, the following is just to satisfy type
7799 # checker, it should never be None
@@ -81,27 +103,19 @@ async def _add_code(entity: SchlageLockEntity, calls: ServiceCall) -> None:
81103 translation_key = "schlage_code_required" ,
82104 ) # pragma: no cover
83105
84- codes = entity ._lock .access_codes # noqa: SLF001
85- _validate_code_name (codes , name )
86- _validate_code_value (codes , code )
106+ codes = self ._lock .access_codes
107+ self . _validate_code_name (codes , name )
108+ self . _validate_code_value (codes , code )
87109
88110 access_code = AccessCode (name = name , code = code )
89- await hass .async_add_executor_job (entity ._lock .add_access_code , access_code ) # noqa: SLF001
90- await coordinator .async_request_refresh ()
111+ await self .coordinator .hass .async_add_executor_job (
112+ self ._lock .add_access_code , access_code
113+ )
114+ await self .coordinator .async_request_refresh ()
91115
92- async def _delete_code ( entity : SchlageLockEntity , calls : ServiceCall ) -> None :
116+ async def delete_code ( self , name : str ) -> None :
93117 """Delete a lock code."""
94- name = calls .data .get ("name" )
95-
96- # name is required by voluptuous, the following is just to satisfy type
97- # checker, it should never be None
98- if name is None :
99- raise ServiceValidationError (
100- translation_domain = DOMAIN ,
101- translation_key = "schlage_name_required" ,
102- ) # pragma: no cover
103-
104- codes = entity ._lock .access_codes # noqa: SLF001
118+ codes = self ._lock .access_codes
105119 if not codes :
106120 return
107121
@@ -117,84 +131,21 @@ async def _delete_code(entity: SchlageLockEntity, calls: ServiceCall) -> None:
117131 if not code_id_to_delete :
118132 return
119133
120- if entity ._lock .access_codes : # noqa: SLF001
121- await hass .async_add_executor_job (
122- entity ._lock .access_codes [code_id_to_delete ].delete # noqa: SLF001
134+ if self ._lock .access_codes :
135+ await self . coordinator . hass .async_add_executor_job (
136+ self ._lock .access_codes [code_id_to_delete ].delete
123137 )
124- await coordinator .async_request_refresh ()
138+ await self . coordinator .async_request_refresh ()
125139
126- async def _get_codes (
127- entity : SchlageLockEntity , calls : ServiceCall
128- ) -> ServiceResponse :
140+ async def get_codes (self ) -> ServiceResponse :
129141 """Get lock codes."""
130142
131- if entity ._lock .access_codes : # noqa: SLF001
143+ if self ._lock .access_codes :
132144 return {
133145 code : {
134- "name" : entity ._lock .access_codes [code ].name , # noqa: SLF001
135- "code" : entity ._lock .access_codes [code ].code , # noqa: SLF001
146+ "name" : self ._lock .access_codes [code ].name ,
147+ "code" : self ._lock .access_codes [code ].code ,
136148 }
137- for code in entity ._lock .access_codes # noqa: SLF001
149+ for code in self ._lock .access_codes
138150 }
139151 return {}
140-
141- platform = entity_platform .async_get_current_platform ()
142- platform .async_register_entity_service (
143- name = SERVICE_ADD_CODE ,
144- schema = {
145- vol .Required ("name" ): cv .string ,
146- vol .Required ("code" ): cv .matches_regex (r"^\d{4,8}$" ),
147- },
148- func = _add_code ,
149- )
150-
151- platform .async_register_entity_service (
152- name = SERVICE_DELETE_CODE ,
153- schema = {
154- vol .Required ("name" ): cv .string ,
155- },
156- func = _delete_code ,
157- )
158-
159- platform .async_register_entity_service (
160- name = SERVICE_GET_CODES ,
161- schema = None ,
162- func = _get_codes ,
163- supports_response = SupportsResponse .ONLY ,
164- )
165-
166-
167- class SchlageLockEntity (SchlageEntity , LockEntity ):
168- """Schlage lock entity."""
169-
170- _attr_name = None
171-
172- def __init__ (
173- self , coordinator : SchlageDataUpdateCoordinator , device_id : str
174- ) -> None :
175- """Initialize a Schlage Lock."""
176- super ().__init__ (coordinator = coordinator , device_id = device_id )
177- self ._update_attrs ()
178-
179- @callback
180- def _handle_coordinator_update (self ) -> None :
181- """Handle updated data from the coordinator."""
182- if self .device_id in self .coordinator .data .locks :
183- self ._update_attrs ()
184- super ()._handle_coordinator_update ()
185-
186- def _update_attrs (self ) -> None :
187- """Update our internal state attributes."""
188- self ._attr_is_locked = self ._lock .is_locked
189- self ._attr_is_jammed = self ._lock .is_jammed
190- self ._attr_changed_by = self ._lock .last_changed_by ()
191-
192- async def async_lock (self , ** kwargs : Any ) -> None :
193- """Lock the device."""
194- await self .hass .async_add_executor_job (self ._lock .lock )
195- await self .coordinator .async_request_refresh ()
196-
197- async def async_unlock (self , ** kwargs : Any ) -> None :
198- """Unlock the device."""
199- await self .hass .async_add_executor_job (self ._lock .unlock )
200- await self .coordinator .async_request_refresh ()
0 commit comments