python-aidot 0.3.55__py3-none-any.whl → 0.3.56__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aidot/aes_utils.py +50 -0
- aidot/client.py +212 -107
- aidot/const.py +0 -14
- aidot/device_client.py +304 -363
- aidot/discover.py +68 -68
- aidot/login_const.py +16 -0
- aidot/models/__init__.py +36 -0
- aidot/models/device_client_model.py +86 -83
- aidot/models/device_model.py +43 -130
- {python_aidot-0.3.55.dist-info → python_aidot-0.3.56.dist-info}/METADATA +4 -2
- python_aidot-0.3.56.dist-info/RECORD +17 -0
- aidot/api/__init__.py +0 -0
- aidot/api/cloud_api.py +0 -151
- aidot/models/auth_model.py +0 -170
- aidot/models/base_model.py +0 -22
- aidot/utils/__init__.py +0 -12
- aidot/utils/async_timer.py +0 -142
- aidot/utils/crypto.py +0 -91
- python_aidot-0.3.55.dist-info/RECORD +0 -22
- {python_aidot-0.3.55.dist-info → python_aidot-0.3.56.dist-info}/WHEEL +0 -0
- {python_aidot-0.3.55.dist-info → python_aidot-0.3.56.dist-info}/licenses/LICENSE +0 -0
- {python_aidot-0.3.55.dist-info → python_aidot-0.3.56.dist-info}/top_level.txt +0 -0
aidot/aes_utils.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|
2
|
+
from cryptography.hazmat.backends import default_backend
|
|
3
|
+
from cryptography.hazmat.primitives import padding
|
|
4
|
+
import json
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def aes_encrypt(plaintext, key):
|
|
9
|
+
padder = padding.PKCS7(algorithms.AES.block_size).padder()
|
|
10
|
+
padded_data = padder.update(plaintext) + padder.finalize()
|
|
11
|
+
|
|
12
|
+
cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=default_backend())
|
|
13
|
+
encryptor = cipher.encryptor()
|
|
14
|
+
|
|
15
|
+
ciphertext = encryptor.update(padded_data) + encryptor.finalize()
|
|
16
|
+
|
|
17
|
+
return ciphertext
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def aes_decrypt(ciphertext, key):
|
|
21
|
+
cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=default_backend())
|
|
22
|
+
decryptor = cipher.decryptor()
|
|
23
|
+
|
|
24
|
+
decrypted_data = decryptor.update(ciphertext) + decryptor.finalize()
|
|
25
|
+
|
|
26
|
+
unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
|
|
27
|
+
plaintext = unpadder.update(decrypted_data) + unpadder.finalize()
|
|
28
|
+
|
|
29
|
+
return plaintext.decode()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def aes_decrypt_to_json(
|
|
33
|
+
ciphertext: bytes, key: Optional[bytes] = None
|
|
34
|
+
) -> dict[str, Any]:
|
|
35
|
+
"""Decrypt AES encrypted data and parse to JSON.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
ciphertext: AES encrypted data
|
|
39
|
+
key: AES key (optional, if None, assumes data is already decrypted)
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Parsed JSON dict
|
|
43
|
+
"""
|
|
44
|
+
if key:
|
|
45
|
+
decrypted_data = aes_decrypt(ciphertext, key)
|
|
46
|
+
else:
|
|
47
|
+
decrypted_data = (
|
|
48
|
+
ciphertext.decode() if isinstance(ciphertext, bytes) else ciphertext
|
|
49
|
+
)
|
|
50
|
+
return json.loads(decrypted_data)
|
aidot/client.py
CHANGED
|
@@ -2,43 +2,63 @@
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
|
+
import base64
|
|
6
|
+
import aiohttp
|
|
5
7
|
from aiohttp import ClientSession
|
|
6
8
|
from typing import Any, Optional
|
|
9
|
+
from cryptography.hazmat.backends import default_backend
|
|
10
|
+
from cryptography.hazmat.primitives import serialization
|
|
11
|
+
from cryptography.hazmat.primitives.asymmetric import padding
|
|
7
12
|
import uuid
|
|
8
13
|
from pathlib import Path
|
|
9
14
|
import hashlib
|
|
10
|
-
|
|
11
|
-
from .utils import rsa_encrypt
|
|
15
|
+
from .exceptions import AidotAuthFailed, AidotUserOrPassIncorrect
|
|
12
16
|
from .device_client import DeviceClient
|
|
13
|
-
from .models.auth_model import UserInformation, LoginRequest
|
|
14
|
-
from .models.device_model import DeviceModel
|
|
15
|
-
from .api.cloud_api import CloudApi
|
|
16
17
|
from .discover import Discover
|
|
17
|
-
from .
|
|
18
|
+
from .login_const import APP_ID, PUBLIC_KEY_PEM, API_URL_TEMPLATE, DEFAULT_REGION
|
|
18
19
|
from .const import (
|
|
20
|
+
CONF_ACCESS_TOKEN,
|
|
21
|
+
CONF_APP_ID,
|
|
22
|
+
CONF_CODE,
|
|
23
|
+
CONF_COUNTRY,
|
|
24
|
+
CONF_DEVICE_LIST,
|
|
19
25
|
CONF_ID,
|
|
20
26
|
CONF_IPADDRESS,
|
|
21
|
-
|
|
22
|
-
CONF_DEVICE_LIST,
|
|
27
|
+
CONF_PASSWORD,
|
|
23
28
|
CONF_PRODUCT,
|
|
24
29
|
CONF_PRODUCT_ID,
|
|
25
|
-
|
|
30
|
+
CONF_REFRESH_TOKEN,
|
|
31
|
+
CONF_REGION,
|
|
32
|
+
CONF_TERMINAL,
|
|
33
|
+
CONF_TOKEN,
|
|
34
|
+
CONF_USERNAME,
|
|
35
|
+
DEFAULT_COUNTRY_NAME,
|
|
26
36
|
SUPPORTED_COUNTRYS,
|
|
27
|
-
|
|
28
|
-
|
|
37
|
+
DEFAULT_COUNTRY_CODE,
|
|
38
|
+
CONF_IS_OWNER,
|
|
39
|
+
CONF_LOGIN_INFO,
|
|
40
|
+
ServerErrorCode,
|
|
29
41
|
)
|
|
30
42
|
|
|
31
43
|
_LOGGER = logging.getLogger(__name__)
|
|
32
44
|
|
|
33
45
|
|
|
34
|
-
|
|
35
|
-
"""
|
|
46
|
+
def rsa_password_encrypt(message: str) -> str:
|
|
47
|
+
"""Get password rsa encrypt."""
|
|
48
|
+
public_key = serialization.load_pem_public_key(
|
|
49
|
+
PUBLIC_KEY_PEM, backend=default_backend()
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
encrypted = public_key.encrypt(
|
|
53
|
+
message.encode("utf-8"),
|
|
54
|
+
padding.PKCS1v15(),
|
|
55
|
+
)
|
|
36
56
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
_token_fresh_cb: Optional[callable] = None
|
|
40
|
-
_products: dict[str, dict[str, Any]] = {}
|
|
57
|
+
encrypted_base64 = base64.b64encode(encrypted).decode("utf-8")
|
|
58
|
+
return encrypted_base64
|
|
41
59
|
|
|
60
|
+
|
|
61
|
+
class AidotClient:
|
|
42
62
|
def __init__(
|
|
43
63
|
self,
|
|
44
64
|
session: Optional[ClientSession],
|
|
@@ -47,55 +67,49 @@ class AidotClient:
|
|
|
47
67
|
password: str | None = None,
|
|
48
68
|
token: dict | None = None,
|
|
49
69
|
) -> None:
|
|
50
|
-
_LOGGER.info("Client Version: v0.3.
|
|
51
|
-
self.
|
|
70
|
+
_LOGGER.info("Client Version: v0.3.56")
|
|
71
|
+
self.session = session
|
|
72
|
+
self.username = username
|
|
73
|
+
self.password = password
|
|
74
|
+
self.country_code = country_code or DEFAULT_COUNTRY_CODE
|
|
75
|
+
self.country_name = DEFAULT_COUNTRY_NAME
|
|
76
|
+
self._region = DEFAULT_REGION
|
|
77
|
+
self._base_url = API_URL_TEMPLATE.format(region=self._region)
|
|
78
|
+
self.login_info: dict[str, Any] = {}
|
|
52
79
|
self._device_clients = {}
|
|
53
|
-
self.
|
|
54
|
-
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
# Set region and country from country_code
|
|
80
|
+
self._discover: Discover | None = None
|
|
81
|
+
self._token_fresh_cb = None
|
|
58
82
|
for item in SUPPORTED_COUNTRYS:
|
|
59
|
-
if item[
|
|
60
|
-
self.
|
|
61
|
-
self.
|
|
83
|
+
if item["id"] == self.country_code:
|
|
84
|
+
self.country_name = item["name"]
|
|
85
|
+
self._region = item["region"].lower()
|
|
86
|
+
self._base_url = API_URL_TEMPLATE.format(region=self._region)
|
|
62
87
|
break
|
|
63
|
-
|
|
64
|
-
# Handle token (existing login info)
|
|
65
88
|
if token is not None:
|
|
89
|
+
# ✅ 兼容性处理: v1.0.8 数据结构迁移到 v1.1.3
|
|
90
|
+
# 旧版本: config_entry.data[CONF_LOGIN_INFO]
|
|
91
|
+
# 新版本: config_entry.data
|
|
66
92
|
if token.get(CONF_ID) is None and token.get(CONF_LOGIN_INFO) is not None:
|
|
67
93
|
token = token.get(CONF_LOGIN_INFO)
|
|
68
|
-
self.user_info.update_from_json(token)
|
|
69
|
-
|
|
70
|
-
self._cloud_api = CloudApi(
|
|
71
|
-
session=session,
|
|
72
|
-
user_info=self.user_info,
|
|
73
|
-
auth_failed_callback=self._on_auth_failed,
|
|
74
|
-
token_refreshed_callback=self._on_token_refreshed,
|
|
75
|
-
)
|
|
76
|
-
self.setup_discover()
|
|
77
94
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
self.user_info.accessToken = ""
|
|
86
|
-
|
|
87
|
-
def _on_token_refreshed(self) -> None:
|
|
88
|
-
"""Handle token refreshed event from CloudApi."""
|
|
89
|
-
_LOGGER.debug("Token refreshed successfully")
|
|
90
|
-
if self._token_fresh_cb:
|
|
91
|
-
self._token_fresh_cb()
|
|
95
|
+
self.login_info = token.copy()
|
|
96
|
+
self.username = token[CONF_USERNAME]
|
|
97
|
+
self.password = token[CONF_PASSWORD]
|
|
98
|
+
self._region = token[CONF_REGION]
|
|
99
|
+
self.country_name = token[CONF_COUNTRY]
|
|
100
|
+
self._base_url = API_URL_TEMPLATE.format(region=self._region)
|
|
101
|
+
self.setup_discover()
|
|
92
102
|
|
|
93
103
|
def set_token_fresh_cb(self, callback) -> None:
|
|
94
|
-
"""Set callback for token refresh events."""
|
|
95
104
|
self._token_fresh_cb = callback
|
|
96
105
|
|
|
106
|
+
def get_identifier(self) -> str:
|
|
107
|
+
return f"{self._region}-{self.username}"
|
|
108
|
+
|
|
109
|
+
def update_password(self, password: str) -> None:
|
|
110
|
+
self.password = password
|
|
111
|
+
|
|
97
112
|
async def get_terminal_id(self) -> str:
|
|
98
|
-
"""Get or create terminal ID for device identification."""
|
|
99
113
|
file_path = Path.home() / ".aidot_terminal_id"
|
|
100
114
|
|
|
101
115
|
def _read_or_create() -> str:
|
|
@@ -115,95 +129,186 @@ class AidotClient:
|
|
|
115
129
|
|
|
116
130
|
async def async_post_login(self) -> dict[str, Any]:
|
|
117
131
|
"""Login the user input allows us to connect."""
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
132
|
+
url = f"{self._base_url}/users/loginWithFreeVerification"
|
|
133
|
+
headers = {CONF_APP_ID: APP_ID, CONF_TERMINAL: "app"}
|
|
134
|
+
# f"{region}:{self.country_name.strip()}",
|
|
135
|
+
terminalId = await self.get_terminal_id()
|
|
136
|
+
if terminalId is None:
|
|
137
|
+
terminalId = "gvz3gjae10l4zii00t7y0"
|
|
138
|
+
data = {
|
|
139
|
+
"countryKey": f"region:{self.country_name.strip()}",
|
|
140
|
+
"username": self.username,
|
|
141
|
+
"password": rsa_password_encrypt(self.password),
|
|
142
|
+
"terminalId": terminalId,
|
|
143
|
+
"webVersion": "0.5.0",
|
|
144
|
+
"area": "Asia/Shanghai",
|
|
145
|
+
"UTC": "UTC+8",
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
response_data: dict[str, Any] = {}
|
|
149
|
+
try:
|
|
150
|
+
response = await self.session.post(url, headers=headers, json=data)
|
|
151
|
+
response_data = await response.json()
|
|
152
|
+
response.raise_for_status()
|
|
153
|
+
self.login_info = response_data
|
|
154
|
+
self.login_info[CONF_PASSWORD] = self.password
|
|
155
|
+
self.login_info[CONF_REGION] = self._region
|
|
156
|
+
self.login_info[CONF_COUNTRY] = self.country_name
|
|
157
|
+
self.setup_discover()
|
|
158
|
+
return self.login_info
|
|
159
|
+
except aiohttp.ClientError as err:
|
|
160
|
+
_LOGGER.error("async_post_login ClientError: %s", err)
|
|
161
|
+
if response_data.get(CONF_CODE) == ServerErrorCode.USER_PWD_INCORRECT:
|
|
162
|
+
raise AidotUserOrPassIncorrect from err
|
|
163
|
+
raise
|
|
164
|
+
|
|
165
|
+
async def async_refresh_token(self) -> dict[str, Any]:
|
|
166
|
+
url = f"{self._base_url}/users/refreshToken"
|
|
167
|
+
headers = {CONF_APP_ID: APP_ID, CONF_TERMINAL: "app"}
|
|
168
|
+
data = {
|
|
169
|
+
CONF_REFRESH_TOKEN: self.login_info[CONF_REFRESH_TOKEN],
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
response_data: dict[str, Any] = {}
|
|
173
|
+
try:
|
|
174
|
+
response = await self.session.post(url, headers=headers, json=data)
|
|
175
|
+
response_data = await response.json()
|
|
176
|
+
response.raise_for_status()
|
|
177
|
+
self.login_info[CONF_ACCESS_TOKEN] = response_data[CONF_ACCESS_TOKEN]
|
|
178
|
+
if response_data[CONF_REFRESH_TOKEN] is not None:
|
|
179
|
+
self.login_info[CONF_REFRESH_TOKEN] = response_data[CONF_REFRESH_TOKEN]
|
|
180
|
+
_LOGGER.debug(f"refresh token: {response_data}")
|
|
181
|
+
if self._token_fresh_cb:
|
|
182
|
+
self._token_fresh_cb()
|
|
183
|
+
return response_data
|
|
184
|
+
except aiohttp.ClientError as err:
|
|
185
|
+
_LOGGER.error("async_refresh_token ClientError: %s %s", err, response_data)
|
|
186
|
+
if response_data.get(CONF_CODE) == ServerErrorCode.LOGIN_INVALID:
|
|
187
|
+
raise AidotAuthFailed from err
|
|
188
|
+
raise
|
|
189
|
+
|
|
190
|
+
async def async_session_get(
|
|
191
|
+
self, params: str, headers: str | None = None
|
|
192
|
+
) -> dict[str, Any]:
|
|
193
|
+
url = f"{self._base_url}{params}"
|
|
194
|
+
token = self.login_info[CONF_ACCESS_TOKEN]
|
|
195
|
+
if token is None:
|
|
196
|
+
raise AidotAuthFailed()
|
|
197
|
+
if headers is None:
|
|
198
|
+
headers = {
|
|
199
|
+
CONF_TERMINAL: "app",
|
|
200
|
+
CONF_TOKEN: token,
|
|
201
|
+
CONF_APP_ID: APP_ID,
|
|
202
|
+
}
|
|
203
|
+
response_data = {}
|
|
204
|
+
try:
|
|
205
|
+
response = await self.session.get(url, headers=headers)
|
|
206
|
+
response_data = await response.json()
|
|
207
|
+
response.raise_for_status()
|
|
208
|
+
return response_data
|
|
209
|
+
except aiohttp.ClientError as err:
|
|
210
|
+
_LOGGER.error("async_get ClientError: %s %s", err, response_data)
|
|
211
|
+
code = response_data.get(CONF_CODE)
|
|
212
|
+
if code == ServerErrorCode.TOKEN_EXPIRED:
|
|
213
|
+
try:
|
|
214
|
+
await self.async_refresh_token()
|
|
215
|
+
return await self.async_session_get(params)
|
|
216
|
+
except AidotAuthFailed as auth_err:
|
|
217
|
+
raise AidotAuthFailed from auth_err
|
|
218
|
+
elif (
|
|
219
|
+
code == ServerErrorCode.LOGIN_INVALID or code == 21027 or code == 21041
|
|
220
|
+
):
|
|
221
|
+
self.login_info[CONF_ACCESS_TOKEN] = None
|
|
222
|
+
raise AidotAuthFailed from err
|
|
223
|
+
raise
|
|
224
|
+
|
|
225
|
+
async def async_get_products(self, product_ids: str) -> list[dict[str, Any]]:
|
|
226
|
+
"""Get device list."""
|
|
227
|
+
params = f"/products/{product_ids}"
|
|
228
|
+
return await self.async_session_get(params)
|
|
229
|
+
|
|
230
|
+
async def async_get_devices(self, house_id: str) -> list[dict[str, Any]]:
|
|
231
|
+
"""Get device list."""
|
|
232
|
+
params = f"/devices?houseId={house_id}"
|
|
233
|
+
return await self.async_session_get(params)
|
|
234
|
+
|
|
235
|
+
async def async_get_houses(self) -> list[dict[str, Any]]:
|
|
236
|
+
"""Get house list."""
|
|
237
|
+
params = "/houses"
|
|
238
|
+
return await self.async_session_get(params)
|
|
131
239
|
|
|
132
240
|
async def async_get_all_device(self) -> dict[str, Any]:
|
|
133
|
-
|
|
134
|
-
filter_devices: dict[str, Any] = {}
|
|
135
|
-
filter_product_ids: set[str] = set()
|
|
241
|
+
final_device_list: list[dict[str, Any]] = []
|
|
136
242
|
try:
|
|
137
|
-
houses = await self.
|
|
243
|
+
houses = await self.async_get_houses()
|
|
138
244
|
for house in houses:
|
|
139
245
|
if house.get(CONF_IS_OWNER) is False:
|
|
140
246
|
continue
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
product_list = await self._cloud_api.get_products(product_ids) or []
|
|
157
|
-
product_map = {p[CONF_ID]: p for p in product_list}
|
|
158
|
-
for device in filter_devices.values():
|
|
159
|
-
device[CONF_PRODUCT] = product_map.get(device[CONF_PRODUCT_ID])
|
|
247
|
+
# get device_list
|
|
248
|
+
device_list = await self.async_get_devices(house[CONF_ID])
|
|
249
|
+
if device_list:
|
|
250
|
+
final_device_list.extend(device_list)
|
|
251
|
+
|
|
252
|
+
# get product_list
|
|
253
|
+
if not final_device_list:
|
|
254
|
+
return {CONF_DEVICE_LIST: []}
|
|
255
|
+
productIds = ",".join([item[CONF_PRODUCT_ID] for item in final_device_list])
|
|
256
|
+
product_list = await self.async_get_products(productIds)
|
|
257
|
+
|
|
258
|
+
for product in product_list:
|
|
259
|
+
for device in final_device_list:
|
|
260
|
+
if device[CONF_PRODUCT_ID] == product[CONF_ID]:
|
|
261
|
+
device[CONF_PRODUCT] = product
|
|
160
262
|
|
|
161
263
|
except Exception as e:
|
|
162
264
|
raise e
|
|
163
|
-
return
|
|
265
|
+
return {CONF_DEVICE_LIST: final_device_list}
|
|
164
266
|
|
|
165
267
|
def get_device_client(self, device: dict[str, Any]) -> DeviceClient:
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
device_client: DeviceClient = self._device_clients.get(_device.id)
|
|
268
|
+
device_id = device.get(CONF_ID)
|
|
269
|
+
device_client: DeviceClient = self._device_clients.get(device_id)
|
|
169
270
|
if device_client is None:
|
|
170
|
-
device_client = DeviceClient(
|
|
171
|
-
self._device_clients[
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
271
|
+
device_client = DeviceClient(device, self.login_info)
|
|
272
|
+
self._device_clients[device_id] = device_client
|
|
273
|
+
if self._discover is not None:
|
|
274
|
+
ip = self._discover.discovered_device.get(device_id)
|
|
275
|
+
device_client.update_ip_address(ip)
|
|
175
276
|
return device_client
|
|
176
277
|
|
|
177
278
|
async def remove_device_client(self, dev_id: str) -> None:
|
|
178
|
-
"""Remove and close device client."""
|
|
179
279
|
device_client: DeviceClient = self._device_clients.get(dev_id)
|
|
180
280
|
if device_client is not None:
|
|
181
281
|
await device_client.close()
|
|
182
282
|
del self._device_clients[dev_id]
|
|
183
283
|
|
|
184
284
|
def setup_discover(self) -> None:
|
|
185
|
-
"""
|
|
186
|
-
if
|
|
285
|
+
"""初始化完成后调用,启动设备发现"""
|
|
286
|
+
if self.login_info.get(CONF_ID) is None:
|
|
287
|
+
return
|
|
288
|
+
if self._discover is not None:
|
|
187
289
|
return
|
|
188
290
|
|
|
189
291
|
_LOGGER.warning("setup_discover")
|
|
190
292
|
|
|
191
|
-
def _discover_callback(dev_id
|
|
293
|
+
def _discover_callback(dev_id, event: dict[str, str]) -> None:
|
|
192
294
|
device_ip = event[CONF_IPADDRESS]
|
|
193
295
|
device_client: DeviceClient = self._device_clients.get(dev_id)
|
|
194
296
|
if device_client is not None:
|
|
195
297
|
device_client.update_ip_address(device_ip)
|
|
196
298
|
|
|
197
|
-
Discover.
|
|
198
|
-
|
|
299
|
+
self._discover = Discover(self.login_info, _discover_callback)
|
|
300
|
+
self._discover.start_repeat_broadcast()
|
|
199
301
|
|
|
200
302
|
async def async_close(self) -> None:
|
|
201
|
-
"""
|
|
303
|
+
"""关闭客户端,清理资源"""
|
|
304
|
+
if self._discover is not None:
|
|
305
|
+
self._discover.close()
|
|
306
|
+
self._discover = None
|
|
202
307
|
for client in self._device_clients.values():
|
|
203
308
|
await client.close()
|
|
204
309
|
self._device_clients.clear()
|
|
205
310
|
|
|
206
311
|
async def async_cleanup(self) -> None:
|
|
207
|
-
"""
|
|
312
|
+
"""清理所有资源"""
|
|
208
313
|
_LOGGER.debug("async_cleanup")
|
|
209
314
|
await self.async_close()
|
aidot/const.py
CHANGED
|
@@ -230,17 +230,3 @@ class ServerErrorCode(IntEnum):
|
|
|
230
230
|
TOKEN_EXPIRED = 21026
|
|
231
231
|
LOGIN_INVALID = 21025
|
|
232
232
|
USER_PWD_INCORRECT = 560080
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
# Login / API constants
|
|
236
|
-
APP_ID = "1383974540041977857"
|
|
237
|
-
API_URL_TEMPLATE = "https://prod-{region}-api.arnoo.com/v17"
|
|
238
|
-
DEFAULT_REGION = "us"
|
|
239
|
-
PUBLIC_KEY_PEM = b"""
|
|
240
|
-
-----BEGIN PUBLIC KEY-----
|
|
241
|
-
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCtQAnPCi8ksPnS1Du6z96PsKfN
|
|
242
|
-
p2Gp/f/bHwlrAdplbX3p7/TnGpnbJGkLq8uRxf6cw+vOthTsZjkPCF7CatRvRnTj
|
|
243
|
-
c9fcy7yE0oXa5TloYyXD6GkxgftBbN/movkJJGQCc7gFavuYoAdTRBOyQoXBtm0m
|
|
244
|
-
kXMSjXOldI/290b9BQIDAQAB
|
|
245
|
-
-----END PUBLIC KEY-----
|
|
246
|
-
"""
|