python-aidot 0.3.45__tar.gz → 0.3.46__tar.gz
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.
- {python_aidot-0.3.45 → python_aidot-0.3.46}/PKG-INFO +3 -2
- {python_aidot-0.3.45 → python_aidot-0.3.46}/aidot/client.py +40 -13
- {python_aidot-0.3.45 → python_aidot-0.3.46}/aidot/device_client.py +83 -73
- {python_aidot-0.3.45 → python_aidot-0.3.46}/aidot/discover.py +45 -35
- {python_aidot-0.3.45 → python_aidot-0.3.46}/python_aidot.egg-info/PKG-INFO +3 -2
- {python_aidot-0.3.45 → python_aidot-0.3.46}/setup.py +1 -1
- {python_aidot-0.3.45 → python_aidot-0.3.46}/LICENSE +0 -0
- {python_aidot-0.3.45 → python_aidot-0.3.46}/README.md +0 -0
- {python_aidot-0.3.45 → python_aidot-0.3.46}/aidot/__init__.py +0 -0
- {python_aidot-0.3.45 → python_aidot-0.3.46}/aidot/aes_utils.py +0 -0
- {python_aidot-0.3.45 → python_aidot-0.3.46}/aidot/const.py +0 -0
- {python_aidot-0.3.45 → python_aidot-0.3.46}/aidot/exceptions.py +0 -0
- {python_aidot-0.3.45 → python_aidot-0.3.46}/aidot/login_const.py +0 -0
- {python_aidot-0.3.45 → python_aidot-0.3.46}/python_aidot.egg-info/SOURCES.txt +0 -0
- {python_aidot-0.3.45 → python_aidot-0.3.46}/python_aidot.egg-info/dependency_links.txt +0 -0
- {python_aidot-0.3.45 → python_aidot-0.3.46}/python_aidot.egg-info/requires.txt +0 -0
- {python_aidot-0.3.45 → python_aidot-0.3.46}/python_aidot.egg-info/top_level.txt +0 -0
- {python_aidot-0.3.45 → python_aidot-0.3.46}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: python-aidot
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.46
|
|
4
4
|
Summary: aidot control wifi lights
|
|
5
5
|
Home-page: https://github.com/Aidot-Development-Team/python-aidot
|
|
6
6
|
Author: aidotdev2024
|
|
@@ -16,6 +16,7 @@ Dynamic: classifier
|
|
|
16
16
|
Dynamic: description
|
|
17
17
|
Dynamic: description-content-type
|
|
18
18
|
Dynamic: home-page
|
|
19
|
+
Dynamic: license-file
|
|
19
20
|
Dynamic: requires-dist
|
|
20
21
|
Dynamic: summary
|
|
21
22
|
|
|
@@ -9,7 +9,9 @@ from typing import Any, Optional
|
|
|
9
9
|
from cryptography.hazmat.backends import default_backend
|
|
10
10
|
from cryptography.hazmat.primitives import serialization
|
|
11
11
|
from cryptography.hazmat.primitives.asymmetric import padding
|
|
12
|
-
|
|
12
|
+
import uuid
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
import hashlib
|
|
13
15
|
from .exceptions import AidotAuthFailed, AidotUserOrPassIncorrect
|
|
14
16
|
from .device_client import DeviceClient
|
|
15
17
|
from .discover import Discover
|
|
@@ -93,7 +95,7 @@ class AidotClient:
|
|
|
93
95
|
self._region = token[CONF_REGION]
|
|
94
96
|
self.country_name = token[CONF_COUNTRY]
|
|
95
97
|
self._base_url = f"https://prod-{self._region}-api.arnoo.com/v17"
|
|
96
|
-
|
|
98
|
+
self.setup_discover()
|
|
97
99
|
def set_token_fresh_cb(self, callback) -> None:
|
|
98
100
|
self._token_fresh_cb = callback
|
|
99
101
|
|
|
@@ -102,17 +104,35 @@ class AidotClient:
|
|
|
102
104
|
|
|
103
105
|
def update_password(self, password: str) -> None:
|
|
104
106
|
self.password = password
|
|
107
|
+
|
|
108
|
+
def get_terminal_id(self) -> str:
|
|
109
|
+
file_path = Path.home() / ".aidot_terminal_id"
|
|
110
|
+
if file_path.exists():
|
|
111
|
+
raw_id = file_path.read_text().strip()
|
|
112
|
+
else:
|
|
113
|
+
node = uuid.getnode()
|
|
114
|
+
is_random = (node >> 40) & 1
|
|
115
|
+
|
|
116
|
+
if is_random:
|
|
117
|
+
raw_id = str(uuid.uuid4())
|
|
118
|
+
else:
|
|
119
|
+
raw_id = format(node, 'x')
|
|
120
|
+
file_path.write_text(raw_id)
|
|
121
|
+
return hashlib.md5(raw_id.encode()).hexdigest()
|
|
105
122
|
|
|
106
123
|
async def async_post_login(self) -> dict[str, Any]:
|
|
107
124
|
"""Login the user input allows us to connect."""
|
|
108
125
|
url = f"{self._base_url}/users/loginWithFreeVerification"
|
|
109
126
|
headers = {CONF_APP_ID: APP_ID, CONF_TERMINAL: "app"}
|
|
110
127
|
# f"{region}:{self.country_name.strip()}",
|
|
128
|
+
terminalId = self.get_terminal_id()
|
|
129
|
+
if terminalId is None:
|
|
130
|
+
terminalId = "gvz3gjae10l4zii00t7y0"
|
|
111
131
|
data = {
|
|
112
132
|
"countryKey": f"region:{self.country_name.strip()}",
|
|
113
133
|
"username": self.username,
|
|
114
134
|
"password": rsa_password_encrypt(self.password),
|
|
115
|
-
"terminalId":
|
|
135
|
+
"terminalId": terminalId,
|
|
116
136
|
"webVersion": "0.5.0",
|
|
117
137
|
"area": "Asia/Shanghai",
|
|
118
138
|
"UTC": "UTC+8",
|
|
@@ -126,6 +146,7 @@ class AidotClient:
|
|
|
126
146
|
self.login_info[CONF_PASSWORD] = self.password
|
|
127
147
|
self.login_info[CONF_REGION] = self._region
|
|
128
148
|
self.login_info[CONF_COUNTRY] = self.country_name
|
|
149
|
+
self.setup_discover()
|
|
129
150
|
return self.login_info
|
|
130
151
|
except aiohttp.ClientError as e:
|
|
131
152
|
_LOGGER.info(f"async_post_login ClientError {e}")
|
|
@@ -237,7 +258,6 @@ class AidotClient:
|
|
|
237
258
|
if device_client is None:
|
|
238
259
|
device_client = DeviceClient(device, self.login_info)
|
|
239
260
|
self._device_clients[device_id] = device_client
|
|
240
|
-
asyncio.get_running_loop().create_task(device_client.ping_task())
|
|
241
261
|
if self._discover is not None:
|
|
242
262
|
ip = self._discover.discovered_device.get(device_id)
|
|
243
263
|
device_client.update_ip_address(ip)
|
|
@@ -249,7 +269,10 @@ class AidotClient:
|
|
|
249
269
|
await device_client.close()
|
|
250
270
|
del self._device_clients[dev_id]
|
|
251
271
|
|
|
252
|
-
def
|
|
272
|
+
def setup_discover(self) -> None:
|
|
273
|
+
"""初始化完成后调用,启动设备发现"""
|
|
274
|
+
if self.login_info.get(CONF_ID) is None:
|
|
275
|
+
return
|
|
253
276
|
if self._discover is not None:
|
|
254
277
|
return
|
|
255
278
|
|
|
@@ -260,14 +283,18 @@ class AidotClient:
|
|
|
260
283
|
device_client.update_ip_address(device_ip)
|
|
261
284
|
|
|
262
285
|
self._discover = Discover(self.login_info, _discover_callback)
|
|
263
|
-
|
|
286
|
+
self._discover.start_repeat_broadcast()
|
|
264
287
|
|
|
265
|
-
def
|
|
266
|
-
|
|
267
|
-
self._discover
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
self.stop_discover()
|
|
288
|
+
async def async_close(self) -> None:
|
|
289
|
+
"""关闭客户端,清理资源"""
|
|
290
|
+
if self._discover is not None:
|
|
291
|
+
self._discover.close()
|
|
292
|
+
self._discover = None
|
|
271
293
|
for client in self._device_clients.values():
|
|
272
|
-
|
|
294
|
+
await client.close()
|
|
273
295
|
self._device_clients.clear()
|
|
296
|
+
|
|
297
|
+
async def async_cleanup(self) -> None:
|
|
298
|
+
"""清理所有资源"""
|
|
299
|
+
_LOGGER.info(f"async_cleanup")
|
|
300
|
+
await self.async_close()
|
|
@@ -107,7 +107,13 @@ class DeviceClient(object):
|
|
|
107
107
|
_ip_address: str = None
|
|
108
108
|
device_id: str
|
|
109
109
|
_is_close: bool = False
|
|
110
|
-
|
|
110
|
+
on_status_update: Any = None
|
|
111
|
+
_receive_task: Any = None
|
|
112
|
+
_login_task: Any = None
|
|
113
|
+
_reconnect_handle: Any = None
|
|
114
|
+
_ping_timer: Any = None
|
|
115
|
+
writer: Any = None
|
|
116
|
+
reader: Any = None
|
|
111
117
|
@property
|
|
112
118
|
def connect_and_login(self) -> bool:
|
|
113
119
|
return self._connect_and_login
|
|
@@ -154,7 +160,8 @@ class DeviceClient(object):
|
|
|
154
160
|
return
|
|
155
161
|
self._ip_address = ip
|
|
156
162
|
if self._connecting is not True and self._connect_and_login is not True:
|
|
157
|
-
asyncio.
|
|
163
|
+
self._login_task = asyncio.create_task(self.async_login())
|
|
164
|
+
|
|
158
165
|
|
|
159
166
|
async def async_login(self) -> None:
|
|
160
167
|
if self._ip_address is None:
|
|
@@ -175,6 +182,11 @@ class DeviceClient(object):
|
|
|
175
182
|
packet = magic + _msgtype + bodysize + send_data
|
|
176
183
|
|
|
177
184
|
return packet
|
|
185
|
+
|
|
186
|
+
def _notify_status_update(self) -> None:
|
|
187
|
+
if self.on_status_update:
|
|
188
|
+
self.on_status_update(self.status)
|
|
189
|
+
|
|
178
190
|
|
|
179
191
|
async def login(self) -> None:
|
|
180
192
|
login_seq = str(int(time.time() * 1000) + self._login_uuid)[-9:]
|
|
@@ -225,39 +237,48 @@ class DeviceClient(object):
|
|
|
225
237
|
self.ascNumber = json_data[CONF_PAYLOAD][CONF_ASCNUMBER]
|
|
226
238
|
self.ascNumber += 1
|
|
227
239
|
self.status.online = True
|
|
228
|
-
asyncio.
|
|
240
|
+
self._receive_task = asyncio.create_task(
|
|
241
|
+
self.receive_data(),
|
|
242
|
+
name=f"aidot_receive_{self.device_id}"
|
|
243
|
+
)
|
|
244
|
+
if self._ping_timer:
|
|
245
|
+
self._ping_timer.cancel()
|
|
246
|
+
if self._reconnect_handle:
|
|
247
|
+
self._reconnect_handle.cancel()
|
|
248
|
+
self._reconnect_handle = None
|
|
249
|
+
self._schedule_ping()
|
|
229
250
|
_LOGGER.info(f"connect device success: {self._ip_address}")
|
|
230
251
|
await self.send_action({}, "getDevAttrReq")
|
|
231
252
|
except Exception as e:
|
|
232
253
|
_LOGGER.error(f"connect device error : {e}")
|
|
233
254
|
return
|
|
234
255
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
async def reveive_data(self) -> None:
|
|
256
|
+
# TCP容易拼包,需要谨慎处理
|
|
257
|
+
async def receive_data(self) -> None:
|
|
238
258
|
while True:
|
|
239
259
|
try:
|
|
240
|
-
|
|
241
|
-
|
|
260
|
+
# 先读取 8 字节头
|
|
261
|
+
header = await self.reader.readexactly(8)
|
|
262
|
+
magic, msgtype, bodysize = struct.unpack(">HHI", header)
|
|
263
|
+
|
|
264
|
+
# 再读取 body
|
|
265
|
+
body = await self.reader.readexactly(bodysize)
|
|
266
|
+
|
|
267
|
+
# 解密
|
|
268
|
+
decrypted_data = aes_decrypt(body, self.aes_key)
|
|
269
|
+
json_data = json.loads(decrypted_data)
|
|
270
|
+
# _LOGGER.info(f"reveive_data : {json_data}")
|
|
271
|
+
except asyncio.CancelledError:
|
|
272
|
+
_LOGGER.debug("Receive task cancelled")
|
|
273
|
+
raise
|
|
274
|
+
except (BrokenPipeError, ConnectionResetError, asyncio.IncompleteReadError) as e:
|
|
242
275
|
_LOGGER.error(f"{self.device_id} read status error {e}")
|
|
243
276
|
await self.reset()
|
|
244
277
|
self.status.online = False
|
|
278
|
+
self._notify_status_update()
|
|
245
279
|
return
|
|
246
280
|
except Exception as e:
|
|
247
|
-
_LOGGER.error(f"recv
|
|
248
|
-
return
|
|
249
|
-
data_len = len(data)
|
|
250
|
-
if data_len <= 0:
|
|
251
|
-
_LOGGER.error("recv data error len, exit socket")
|
|
252
|
-
await self.reset()
|
|
253
|
-
self.status.online = False
|
|
254
|
-
return
|
|
255
|
-
try:
|
|
256
|
-
magic, msgtype, bodysize = struct.unpack(">HHI", data[:8])
|
|
257
|
-
decrypted_data = aes_decrypt(data[8:], self.aes_key)
|
|
258
|
-
json_data = json.loads(decrypted_data)
|
|
259
|
-
except Exception as e:
|
|
260
|
-
_LOGGER.error(f"recv json error : {e}")
|
|
281
|
+
_LOGGER.error(f"recv error: {e}")
|
|
261
282
|
continue
|
|
262
283
|
|
|
263
284
|
if "service" in json_data:
|
|
@@ -269,56 +290,12 @@ class DeviceClient(object):
|
|
|
269
290
|
if payload is not None:
|
|
270
291
|
self.ascNumber = payload.get(CONF_ASCNUMBER)
|
|
271
292
|
self.status.update(payload.get(CONF_ATTR))
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
self.
|
|
277
|
-
|
|
278
|
-
# if self._connect_and_login is False:
|
|
279
|
-
# await asyncio.sleep(2)
|
|
280
|
-
# raise AidotNotLogin
|
|
281
|
-
# try:
|
|
282
|
-
# data = await self.reader.read(1024)
|
|
283
|
-
# except (BrokenPipeError, ConnectionResetError) as e:
|
|
284
|
-
# _LOGGER.error(f"{self.device_id} read status error {e}")
|
|
285
|
-
# await self.reset()
|
|
286
|
-
# self.status.online = False
|
|
287
|
-
# return self.status
|
|
288
|
-
# except Exception as e:
|
|
289
|
-
# _LOGGER.error(f"recv data error {e}")
|
|
290
|
-
# return self.status
|
|
291
|
-
# data_len = len(data)
|
|
292
|
-
# if data_len <= 0:
|
|
293
|
-
# _LOGGER.error("recv data error len")
|
|
294
|
-
# await self.reset()
|
|
295
|
-
# self.status.online = False
|
|
296
|
-
# return self.status
|
|
297
|
-
# try:
|
|
298
|
-
# magic, msgtype, bodysize = struct.unpack(">HHI", data[:8])
|
|
299
|
-
# decrypted_data = aes_decrypt(data[8:], self.aes_key)
|
|
300
|
-
# json_data = json.loads(decrypted_data)
|
|
301
|
-
# except Exception as e:
|
|
302
|
-
# _LOGGER.error(f"recv json error : {e}")
|
|
303
|
-
# return await self.read_status()
|
|
304
|
-
|
|
305
|
-
# if "service" in json_data:
|
|
306
|
-
# if "test" == json_data["service"]:
|
|
307
|
-
# self.ping_count = 0
|
|
308
|
-
# return await self.read_status()
|
|
309
|
-
# payload = json_data.get(CONF_PAYLOAD)
|
|
310
|
-
# if payload is not None:
|
|
311
|
-
# self.ascNumber = payload.get(CONF_ASCNUMBER)
|
|
312
|
-
# self.status.update(payload.get(CONF_ATTR))
|
|
313
|
-
return self.status
|
|
314
|
-
|
|
315
|
-
async def ping_task(self) -> None:
|
|
316
|
-
while True:
|
|
317
|
-
if self._is_close:
|
|
318
|
-
return
|
|
319
|
-
await asyncio.sleep(5)
|
|
320
|
-
await self.send_ping_action()
|
|
321
|
-
await asyncio.sleep(5)
|
|
293
|
+
self._notify_status_update()
|
|
294
|
+
|
|
295
|
+
def _schedule_ping(self):
|
|
296
|
+
loop = asyncio.get_running_loop()
|
|
297
|
+
loop.create_task(self.send_ping_action())
|
|
298
|
+
self._ping_timer = loop.call_later(10, self._schedule_ping)
|
|
322
299
|
|
|
323
300
|
async def send_dev_attr(self, dev_attr) -> None:
|
|
324
301
|
if not self._connect_and_login:
|
|
@@ -393,6 +370,8 @@ class DeviceClient(object):
|
|
|
393
370
|
_LOGGER.error(f"{self.device_id} send action error {e}")
|
|
394
371
|
|
|
395
372
|
async def send_ping_action(self) -> int:
|
|
373
|
+
if self._is_close:
|
|
374
|
+
return -1
|
|
396
375
|
ping = {
|
|
397
376
|
"service": "test",
|
|
398
377
|
"method": "pingreq",
|
|
@@ -400,6 +379,7 @@ class DeviceClient(object):
|
|
|
400
379
|
"srcAddr": "x.xxxxxxx",
|
|
401
380
|
CONF_PAYLOAD: {},
|
|
402
381
|
}
|
|
382
|
+
_LOGGER.info(f"{self.device_id} send_ping_action {ping}")
|
|
403
383
|
try:
|
|
404
384
|
if self.ping_count >= 2:
|
|
405
385
|
_LOGGER.error(
|
|
@@ -419,17 +399,47 @@ class DeviceClient(object):
|
|
|
419
399
|
return -1
|
|
420
400
|
|
|
421
401
|
async def reset(self) -> None:
|
|
402
|
+
if self._ping_timer:
|
|
403
|
+
self._ping_timer.cancel()
|
|
404
|
+
if self._reconnect_handle:
|
|
405
|
+
self._reconnect_handle.cancel()
|
|
406
|
+
self._reconnect_handle = None
|
|
407
|
+
|
|
408
|
+
if self._receive_task and not self._receive_task.done():
|
|
409
|
+
self._receive_task.cancel()
|
|
410
|
+
try:
|
|
411
|
+
await self._receive_task
|
|
412
|
+
except asyncio.CancelledError as e:
|
|
413
|
+
_LOGGER.error(f"{self.device_id} writer close error {e}")
|
|
414
|
+
pass
|
|
422
415
|
try:
|
|
423
416
|
if self.writer:
|
|
424
417
|
self.writer.close()
|
|
425
418
|
await self.writer.wait_closed()
|
|
426
419
|
except Exception as e:
|
|
427
|
-
_LOGGER.error(f"{self.device_id} writer close error {e}")
|
|
420
|
+
_LOGGER.error(f"{self.device_id} writer/reader close error {e}")
|
|
421
|
+
self.writer = self.reader = None;
|
|
422
|
+
|
|
428
423
|
self._connect_and_login = False
|
|
429
424
|
self.status.online = False
|
|
430
425
|
self.ping_count = 0
|
|
426
|
+
self._notify_status_update()
|
|
427
|
+
# 自动重连(如果没有主动关闭)
|
|
428
|
+
if not self._is_close and self._ip_address:
|
|
429
|
+
self._schedule_reconnect()
|
|
431
430
|
|
|
432
431
|
async def close(self) -> None:
|
|
433
432
|
self._is_close = True
|
|
434
433
|
await self.reset()
|
|
435
434
|
_LOGGER.info(f"{self.device_id} connect close by user")
|
|
435
|
+
|
|
436
|
+
def _schedule_reconnect(self) -> None:
|
|
437
|
+
"""延迟重连"""
|
|
438
|
+
_LOGGER.info(f"{self.device_id} _schedule_reconnect")
|
|
439
|
+
loop = asyncio.get_running_loop()
|
|
440
|
+
# self._reconnect_handle = loop.call_later(
|
|
441
|
+
# 10, # 10秒后重连
|
|
442
|
+
# lambda: asyncio.create_task(self.async_login())
|
|
443
|
+
# )
|
|
444
|
+
self._reconnect_handle = loop.call_later(15, self._schedule_reconnect)
|
|
445
|
+
self._login_task = asyncio.create_task(self.async_login())
|
|
@@ -10,8 +10,10 @@ from .const import CONF_ID, CONF_IPADDRESS
|
|
|
10
10
|
from .exceptions import AidotOSError
|
|
11
11
|
|
|
12
12
|
_LOGGER = logging.getLogger(__name__)
|
|
13
|
-
_DISCOVER_TIME =
|
|
13
|
+
# _DISCOVER_TIME = 15
|
|
14
14
|
|
|
15
|
+
_DISCOVER_FAST = 5 # 启动时快速发现
|
|
16
|
+
_DISCOVER_SLOW = 120 # 稳定后慢速维持
|
|
15
17
|
|
|
16
18
|
class BroadcastProtocol:
|
|
17
19
|
_is_closed = False
|
|
@@ -49,6 +51,7 @@ class BroadcastProtocol:
|
|
|
49
51
|
"timestamp": str(current_timestamp_milliseconds),
|
|
50
52
|
},
|
|
51
53
|
}
|
|
54
|
+
_LOGGER.info(f"send_broadcast {message}")
|
|
52
55
|
send_data = aes_encrypt(json.dumps(message).encode(), self.aes_key)
|
|
53
56
|
try:
|
|
54
57
|
self.transport.sendto(send_data, ("255.255.255.255", 6666))
|
|
@@ -58,6 +61,7 @@ class BroadcastProtocol:
|
|
|
58
61
|
def datagram_received(self, data, addr) -> None:
|
|
59
62
|
data_str = aes_decrypt(data, self.aes_key)
|
|
60
63
|
data_json = json.loads(data_str)
|
|
64
|
+
_LOGGER.info(f"datagram_received {data_json}")
|
|
61
65
|
if "payload" in data_json:
|
|
62
66
|
if "mac" in data_json["payload"]:
|
|
63
67
|
devId = data_json["payload"]["devId"]
|
|
@@ -85,47 +89,51 @@ class Discover:
|
|
|
85
89
|
_login_info: dict[str, Any] = None
|
|
86
90
|
_broadcast_protocol: BroadcastProtocol = None
|
|
87
91
|
discovered_device: dict[str, str]
|
|
88
|
-
|
|
92
|
+
_timer_handle: asyncio.TimerHandle | None = None
|
|
89
93
|
|
|
90
94
|
def __init__(self, login_info, callback):
|
|
91
95
|
self.discovered_device = {}
|
|
92
96
|
self._login_info = login_info
|
|
93
97
|
self._callback = callback
|
|
94
|
-
|
|
98
|
+
|
|
95
99
|
async def try_create_broadcast(self) -> None:
|
|
96
|
-
if self._broadcast_protocol is None:
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
if self._broadcast_protocol is not None:
|
|
101
|
+
return
|
|
102
|
+
try:
|
|
103
|
+
protocol = BroadcastProtocol(self._discover_callback, self._login_info[CONF_ID])
|
|
104
|
+
self._transport, _ = await asyncio.get_running_loop().create_datagram_endpoint(
|
|
105
|
+
lambda: protocol,
|
|
106
|
+
local_addr=("0.0.0.0", 0),
|
|
99
107
|
)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
lambda: self._broadcast_protocol,
|
|
106
|
-
local_addr=("0.0.0.0", 0),
|
|
107
|
-
)
|
|
108
|
-
except OSError:
|
|
109
|
-
raise AidotOSError
|
|
110
|
-
|
|
111
|
-
async def send_broadcast(self) -> None:
|
|
112
|
-
await self.try_create_broadcast()
|
|
113
|
-
self._broadcast_protocol.send_broadcast()
|
|
114
|
-
|
|
115
|
-
async def repeat_broadcast(self) -> None:
|
|
108
|
+
self._broadcast_protocol = protocol # 成功后再赋值
|
|
109
|
+
except OSError:
|
|
110
|
+
raise AidotOSError
|
|
111
|
+
|
|
112
|
+
def start_repeat_broadcast(self) -> None:
|
|
116
113
|
self._is_close = False
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
114
|
+
self._fast_discover_count = 6 # 前6次快速(30秒内)
|
|
115
|
+
self._schedule_broadcast()
|
|
116
|
+
|
|
117
|
+
def _schedule_broadcast(self) -> None:
|
|
118
|
+
_LOGGER.debug(f"_schedule_broadcast")
|
|
119
|
+
# 前几次快速发现,之后慢速
|
|
120
|
+
if self._fast_discover_count > 0:
|
|
121
|
+
interval = _DISCOVER_FAST
|
|
122
|
+
self._fast_discover_count -= 1
|
|
123
|
+
else:
|
|
124
|
+
interval = _DISCOVER_SLOW
|
|
125
|
+
|
|
126
|
+
loop = asyncio.get_running_loop()
|
|
127
|
+
asyncio.create_task(self._do_broadcast())
|
|
128
|
+
self._timer_handle = loop.call_later(interval, self._schedule_broadcast)
|
|
129
|
+
|
|
130
|
+
async def _do_broadcast(self) -> None:
|
|
131
|
+
"""执行广播"""
|
|
132
|
+
try:
|
|
133
|
+
await self.try_create_broadcast()
|
|
134
|
+
self._broadcast_protocol.send_broadcast()
|
|
135
|
+
except Exception as e:
|
|
136
|
+
_LOGGER.error(f"Broadcast failed: {e}")
|
|
129
137
|
|
|
130
138
|
def _discover_callback(self, dev_id, event: dict[str, str]) -> None:
|
|
131
139
|
self.discovered_device[dev_id] = event[CONF_IPADDRESS]
|
|
@@ -133,7 +141,9 @@ class Discover:
|
|
|
133
141
|
self._callback(dev_id, event)
|
|
134
142
|
|
|
135
143
|
def close(self) -> None:
|
|
136
|
-
self.
|
|
144
|
+
if self._timer_handle is not None:
|
|
145
|
+
self._timer_handle.cancel()
|
|
146
|
+
self._timer_handle = None
|
|
137
147
|
if self._broadcast_protocol is not None:
|
|
138
148
|
self._broadcast_protocol.close()
|
|
139
149
|
self._broadcast_protocol = None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: python-aidot
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.46
|
|
4
4
|
Summary: aidot control wifi lights
|
|
5
5
|
Home-page: https://github.com/Aidot-Development-Team/python-aidot
|
|
6
6
|
Author: aidotdev2024
|
|
@@ -16,6 +16,7 @@ Dynamic: classifier
|
|
|
16
16
|
Dynamic: description
|
|
17
17
|
Dynamic: description-content-type
|
|
18
18
|
Dynamic: home-page
|
|
19
|
+
Dynamic: license-file
|
|
19
20
|
Dynamic: requires-dist
|
|
20
21
|
Dynamic: summary
|
|
21
22
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|