2
2
3
3
from __future__ import annotations
4
4
5
- import asyncio
6
5
import logging
7
6
import time
8
7
from typing import Any
9
8
10
- import aiohttp
11
9
from bleak .backends .device import BLEDevice
12
10
from cryptography .hazmat .primitives .ciphers import Cipher , algorithms , modes
13
11
14
- from ..api_config import SWITCHBOT_APP_API_BASE_URL , SWITCHBOT_APP_CLIENT_ID
15
- from ..const import (
16
- LockStatus ,
17
- SwitchbotAccountConnectionError ,
18
- SwitchbotApiError ,
19
- SwitchbotAuthenticationError ,
20
- SwitchbotModel ,
21
- )
22
- from .device import SwitchbotDevice , SwitchbotOperationError
12
+ from ..const import LockStatus , SwitchbotModel
13
+ from .device import SwitchbotEncryptedDevice
23
14
24
15
COMMAND_HEADER = "57"
25
16
COMMAND_GET_CK_IV = f"{ COMMAND_HEADER } 0f2103"
54
45
# The return value of the command is 6 when the command is successful but the battery is low.
55
46
56
47
57
- class SwitchbotLock (SwitchbotDevice ):
48
+ class SwitchbotLock (SwitchbotEncryptedDevice ):
58
49
"""Representation of a Switchbot Lock."""
59
50
60
51
def __init__ (
@@ -66,141 +57,23 @@ def __init__(
66
57
model : SwitchbotModel = SwitchbotModel .LOCK ,
67
58
** kwargs : Any ,
68
59
) -> None :
69
- if len (key_id ) == 0 :
70
- raise ValueError ("key_id is missing" )
71
- elif len (key_id ) != 2 :
72
- raise ValueError ("key_id is invalid" )
73
- if len (encryption_key ) == 0 :
74
- raise ValueError ("encryption_key is missing" )
75
- elif len (encryption_key ) != 32 :
76
- raise ValueError ("encryption_key is invalid" )
77
60
if model not in (SwitchbotModel .LOCK , SwitchbotModel .LOCK_PRO ):
78
61
raise ValueError ("initializing SwitchbotLock with a non-lock model" )
79
- self ._iv = None
80
- self ._cipher = None
81
- self ._key_id = key_id
82
- self ._encryption_key = bytearray .fromhex (encryption_key )
83
62
self ._notifications_enabled : bool = False
84
- self ._model : SwitchbotModel = model
85
- super ().__init__ (device , None , interface , ** kwargs )
63
+ super ().__init__ (device , key_id , encryption_key , model , interface , ** kwargs )
86
64
87
- @staticmethod
65
+ @classmethod
88
66
async def verify_encryption_key (
67
+ cls ,
89
68
device : BLEDevice ,
90
69
key_id : str ,
91
70
encryption_key : str ,
92
71
model : SwitchbotModel = SwitchbotModel .LOCK ,
93
72
** kwargs : Any ,
94
73
) -> bool :
95
- try :
96
- lock = SwitchbotLock (
97
- device , key_id = key_id , encryption_key = encryption_key , model = model
98
- )
99
- except ValueError :
100
- return False
101
- try :
102
- lock_info = await lock .get_basic_info ()
103
- except SwitchbotOperationError :
104
- return False
105
-
106
- return lock_info is not None
107
-
108
- @staticmethod
109
- async def api_request (
110
- session : aiohttp .ClientSession ,
111
- subdomain : str ,
112
- path : str ,
113
- data : dict = None ,
114
- headers : dict = None ,
115
- ) -> dict :
116
- url = f"https://{ subdomain } .{ SWITCHBOT_APP_API_BASE_URL } /{ path } "
117
- async with session .post (
118
- url ,
119
- json = data ,
120
- headers = headers ,
121
- timeout = aiohttp .ClientTimeout (total = 10 ),
122
- ) as result :
123
- if result .status > 299 :
124
- raise SwitchbotApiError (
125
- f"Unexpected status code returned by SwitchBot API: { result .status } "
126
- )
127
-
128
- response = await result .json ()
129
- if response ["statusCode" ] != 100 :
130
- raise SwitchbotApiError (
131
- f"{ response ['message' ]} , status code: { response ['statusCode' ]} "
132
- )
133
-
134
- return response ["body" ]
135
-
136
- # Old non-async method preserved for backwards compatibility
137
- @staticmethod
138
- def retrieve_encryption_key (device_mac : str , username : str , password : str ):
139
- async def async_fn ():
140
- async with aiohttp .ClientSession () as session :
141
- return await SwitchbotLock .async_retrieve_encryption_key (
142
- session , device_mac , username , password
143
- )
144
-
145
- return asyncio .run (async_fn ())
146
-
147
- @staticmethod
148
- async def async_retrieve_encryption_key (
149
- session : aiohttp .ClientSession , device_mac : str , username : str , password : str
150
- ) -> dict :
151
- """Retrieve lock key from internal SwitchBot API."""
152
- device_mac = device_mac .replace (":" , "" ).replace ("-" , "" ).upper ()
153
-
154
- try :
155
- auth_result = await SwitchbotLock .api_request (
156
- session ,
157
- "account" ,
158
- "account/api/v1/user/login" ,
159
- {
160
- "clientId" : SWITCHBOT_APP_CLIENT_ID ,
161
- "username" : username ,
162
- "password" : password ,
163
- "grantType" : "password" ,
164
- "verifyCode" : "" ,
165
- },
166
- )
167
- auth_headers = {"authorization" : auth_result ["access_token" ]}
168
- except Exception as err :
169
- raise SwitchbotAuthenticationError (f"Authentication failed: { err } " ) from err
170
-
171
- try :
172
- userinfo = await SwitchbotLock .api_request (
173
- session , "account" , "account/api/v1/user/userinfo" , {}, auth_headers
174
- )
175
- if "botRegion" in userinfo and userinfo ["botRegion" ] != "" :
176
- region = userinfo ["botRegion" ]
177
- else :
178
- region = "us"
179
- except Exception as err :
180
- raise SwitchbotAccountConnectionError (
181
- f"Failed to retrieve SwitchBot Account user details: { err } "
182
- ) from err
183
-
184
- try :
185
- device_info = await SwitchbotLock .api_request (
186
- session ,
187
- f"wonderlabs.{ region } " ,
188
- "wonder/keys/v1/communicate" ,
189
- {
190
- "device_mac" : device_mac ,
191
- "keyType" : "user" ,
192
- },
193
- auth_headers ,
194
- )
195
-
196
- return {
197
- "key_id" : device_info ["communicationKey" ]["keyId" ],
198
- "encryption_key" : device_info ["communicationKey" ]["key" ],
199
- }
200
- except Exception as err :
201
- raise SwitchbotAccountConnectionError (
202
- f"Failed to retrieve encryption key from SwitchBot Account: { err } "
203
- ) from err
74
+ return super ().verify_encryption_key (
75
+ device , key_id , encryption_key , model , ** kwargs
76
+ )
204
77
205
78
async def lock (self ) -> bool :
206
79
"""Send lock command."""
0 commit comments