python-aidot 0.3.58__py3-none-any.whl → 0.3.59__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/client.py +96 -4
- aidot/const.py +7 -0
- aidot/device_client.py +19 -2
- aidot/models/device_model.py +1 -0
- {python_aidot-0.3.58.dist-info → python_aidot-0.3.59.dist-info}/METADATA +1 -1
- {python_aidot-0.3.58.dist-info → python_aidot-0.3.59.dist-info}/RECORD +9 -9
- {python_aidot-0.3.58.dist-info → python_aidot-0.3.59.dist-info}/WHEEL +0 -0
- {python_aidot-0.3.58.dist-info → python_aidot-0.3.59.dist-info}/licenses/LICENSE +0 -0
- {python_aidot-0.3.58.dist-info → python_aidot-0.3.59.dist-info}/top_level.txt +0 -0
aidot/client.py
CHANGED
|
@@ -24,6 +24,11 @@ from .const import (
|
|
|
24
24
|
CONF_CODE,
|
|
25
25
|
CONF_COUNTRY,
|
|
26
26
|
CONF_DEVICE_LIST,
|
|
27
|
+
CONF_DIYS,
|
|
28
|
+
CONF_EFFECTS,
|
|
29
|
+
CONF_EFFECT_SELECTION,
|
|
30
|
+
CONF_EFFECT_SOURCE,
|
|
31
|
+
CONF_FAV_PRESETS,
|
|
27
32
|
CONF_ID,
|
|
28
33
|
CONF_IPADDRESS,
|
|
29
34
|
CONF_PASSWORD,
|
|
@@ -48,6 +53,9 @@ from .const import (
|
|
|
48
53
|
CONF_AES_KEY,
|
|
49
54
|
CONF_FIRMWARE_VERSION,
|
|
50
55
|
CONF_PRIMITIVE,
|
|
56
|
+
DEFAULT_EFFECT_SOURCE,
|
|
57
|
+
EFFECT_SOURCE_MANUAL,
|
|
58
|
+
EFFECT_SOURCE_RECOMMENDED,
|
|
51
59
|
)
|
|
52
60
|
|
|
53
61
|
_LOGGER = logging.getLogger(__name__)
|
|
@@ -76,9 +84,13 @@ class AidotClient:
|
|
|
76
84
|
username: str | None = None,
|
|
77
85
|
password: str | None = None,
|
|
78
86
|
token: dict | None = None,
|
|
87
|
+
options: dict[str, Any] | None = None,
|
|
88
|
+
effect_source: str = DEFAULT_EFFECT_SOURCE,
|
|
79
89
|
) -> None:
|
|
80
|
-
_LOGGER.info("Client Version: v0.3.
|
|
90
|
+
_LOGGER.info("Client Version: v0.3.59")
|
|
81
91
|
self.session = session
|
|
92
|
+
self.options = options.copy() if options is not None else {}
|
|
93
|
+
self.effect_source = self.options.get(CONF_EFFECT_SOURCE, effect_source)
|
|
82
94
|
self.username = username
|
|
83
95
|
self.password = password
|
|
84
96
|
self.country_code = country_code or DEFAULT_COUNTRY_CODE
|
|
@@ -394,7 +406,7 @@ class AidotClient:
|
|
|
394
406
|
async def async_get_all_effects(
|
|
395
407
|
self, device: dict[str, Any]
|
|
396
408
|
) -> dict[str, FavoriteEffectPrimitive]:
|
|
397
|
-
"""Get all effects list."""
|
|
409
|
+
"""Get all effects list and store raw effect sources on the device."""
|
|
398
410
|
diy_list: list[FavoriteEffectPrimitive] = []
|
|
399
411
|
fav_preset: list[FavoriteEffectPrimitive] = []
|
|
400
412
|
preset_list: list[FavoriteEffectPrimitive] = []
|
|
@@ -430,7 +442,87 @@ class AidotClient:
|
|
|
430
442
|
preset_list = [
|
|
431
443
|
item for item in preset_list if item.primitiveEffectId not in fav_preset_ids
|
|
432
444
|
]
|
|
433
|
-
|
|
445
|
+
|
|
446
|
+
device[CONF_DIYS] = diy_list
|
|
447
|
+
device[CONF_FAV_PRESETS] = fav_preset
|
|
448
|
+
device[CONF_PRESETS] = preset_list
|
|
449
|
+
return self.get_filtered_effects(device)
|
|
450
|
+
|
|
451
|
+
def get_filtered_effects(
|
|
452
|
+
self, device: dict[str, Any]
|
|
453
|
+
) -> dict[str, FavoriteEffectPrimitive]:
|
|
454
|
+
"""Get final effects for Home Assistant from raw device effect sources."""
|
|
455
|
+
diy_list = device.get(CONF_DIYS, [])
|
|
456
|
+
fav_preset = device.get(CONF_FAV_PRESETS, [])
|
|
457
|
+
preset_list = device.get(CONF_PRESETS, [])
|
|
458
|
+
|
|
459
|
+
if self.effect_source == EFFECT_SOURCE_MANUAL:
|
|
460
|
+
return self._merge_effects_by_unique_name(
|
|
461
|
+
self._filter_manual_effects(device, diy_list),
|
|
462
|
+
self._filter_manual_effects(device, fav_preset),
|
|
463
|
+
self._filter_manual_effects(device, preset_list),
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
return self._merge_effects_by_unique_name(
|
|
467
|
+
diy_list,
|
|
468
|
+
fav_preset,
|
|
469
|
+
self._filter_preset_list(preset_list),
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
def get_all_cached_effects(
|
|
473
|
+
self, device: dict[str, Any]
|
|
474
|
+
) -> dict[str, FavoriteEffectPrimitive]:
|
|
475
|
+
"""Get all effects from cached raw device effect sources."""
|
|
476
|
+
return self._merge_effects_by_unique_name(
|
|
477
|
+
device.get(CONF_DIYS, []),
|
|
478
|
+
device.get(CONF_FAV_PRESETS, []),
|
|
479
|
+
device.get(CONF_PRESETS, []),
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
def _filter_manual_effects(
|
|
483
|
+
self, device: dict[str, Any], effect_list: list[FavoriteEffectPrimitive]
|
|
484
|
+
) -> list[FavoriteEffectPrimitive]:
|
|
485
|
+
"""Filter effect list by manual device selection."""
|
|
486
|
+
selected_ids = self._get_manual_effect_ids(device)
|
|
487
|
+
return [
|
|
488
|
+
effect
|
|
489
|
+
for effect in effect_list
|
|
490
|
+
if self._get_effect_ids(effect) & selected_ids
|
|
491
|
+
]
|
|
492
|
+
|
|
493
|
+
def _get_manual_effect_ids(self, device: dict[str, Any]) -> set[str]:
|
|
494
|
+
"""Get manually selected effect ids for a device."""
|
|
495
|
+
effect_selection = self.options.get(CONF_EFFECT_SELECTION, {})
|
|
496
|
+
selected_ids = effect_selection.get(device.get(CONF_ID), [])
|
|
497
|
+
return {effect_id for effect_id in selected_ids if effect_id}
|
|
498
|
+
|
|
499
|
+
@staticmethod
|
|
500
|
+
def _get_effect_ids(effect: FavoriteEffectPrimitive) -> set[str]:
|
|
501
|
+
"""Get selectable ids for an effect."""
|
|
502
|
+
return {
|
|
503
|
+
effect_id
|
|
504
|
+
for effect_id in (effect.primitiveEffectId, effect.favoriteId)
|
|
505
|
+
if effect_id
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
def _filter_preset_list(
|
|
509
|
+
self, preset_list: list[FavoriteEffectPrimitive]
|
|
510
|
+
) -> list[FavoriteEffectPrimitive]:
|
|
511
|
+
"""Filter preset effects by configured source."""
|
|
512
|
+
if self.effect_source != EFFECT_SOURCE_RECOMMENDED:
|
|
513
|
+
return preset_list
|
|
514
|
+
|
|
515
|
+
recommended = [
|
|
516
|
+
effect for effect in preset_list if self._is_recommended_effect(effect)
|
|
517
|
+
]
|
|
518
|
+
if recommended:
|
|
519
|
+
return recommended
|
|
520
|
+
return preset_list[:10]
|
|
521
|
+
|
|
522
|
+
@staticmethod
|
|
523
|
+
def _is_recommended_effect(effect: FavoriteEffectPrimitive) -> bool:
|
|
524
|
+
"""Return whether an effect should be included in recommended presets."""
|
|
525
|
+
return effect.tag not in (None, "") and effect.tag.strip() != "New"
|
|
434
526
|
|
|
435
527
|
@staticmethod
|
|
436
528
|
def _merge_effects_by_unique_name(
|
|
@@ -485,7 +577,7 @@ class AidotClient:
|
|
|
485
577
|
and CONF_AES_KEY in device
|
|
486
578
|
and device[CONF_AES_KEY][0] is not None
|
|
487
579
|
):
|
|
488
|
-
device[
|
|
580
|
+
device[CONF_EFFECTS] = await self.async_get_all_effects(device)
|
|
489
581
|
|
|
490
582
|
except Exception as e:
|
|
491
583
|
raise e
|
aidot/const.py
CHANGED
|
@@ -215,6 +215,13 @@ CONF_SET_DEV_ATTR_REQ = "setDevAttrReq"
|
|
|
215
215
|
CONF_DIYS = "diys"
|
|
216
216
|
CONF_PRESETS = "presets"
|
|
217
217
|
CONF_FAV_PRESETS = "favoritePresets"
|
|
218
|
+
CONF_EFFECTS = "effects"
|
|
219
|
+
CONF_EFFECT_SOURCE = "effect_source"
|
|
220
|
+
CONF_EFFECT_SELECTION = "effect_selection"
|
|
221
|
+
EFFECT_SOURCE_ALL = "all"
|
|
222
|
+
EFFECT_SOURCE_RECOMMENDED = "recommended"
|
|
223
|
+
EFFECT_SOURCE_MANUAL = "manual"
|
|
224
|
+
DEFAULT_EFFECT_SOURCE = EFFECT_SOURCE_ALL
|
|
218
225
|
CONF_VERSION = "version"
|
|
219
226
|
CONF_LIGHT_SCRIPT_FLAGS = "lightScriptFlags"
|
|
220
227
|
CONF_PRIMITIVE = "primitive"
|
aidot/device_client.py
CHANGED
|
@@ -7,6 +7,7 @@ import time
|
|
|
7
7
|
import json
|
|
8
8
|
import asyncio
|
|
9
9
|
import logging
|
|
10
|
+
from ipaddress import ip_address
|
|
10
11
|
from typing import TYPE_CHECKING, Any
|
|
11
12
|
|
|
12
13
|
from .aes_utils import aes_encrypt, aes_decrypt_to_json
|
|
@@ -21,6 +22,7 @@ from .models.device_client_model import (
|
|
|
21
22
|
from .const import (
|
|
22
23
|
CONF_AES_KEY,
|
|
23
24
|
CONF_CCT,
|
|
25
|
+
CONF_EFFECTS,
|
|
24
26
|
CONF_HARDWARE_VERSION,
|
|
25
27
|
CONF_ID,
|
|
26
28
|
CONF_IDENTITY,
|
|
@@ -106,7 +108,7 @@ class DeviceInformation:
|
|
|
106
108
|
self.model_id = device.get(CONF_MODEL_ID)
|
|
107
109
|
self.name = device.get(CONF_NAME)
|
|
108
110
|
self.hw_version = device.get(CONF_HARDWARE_VERSION)
|
|
109
|
-
self.presets = device.get(CONF_PRESETS, {})
|
|
111
|
+
self.presets = device.get(CONF_EFFECTS, device.get(CONF_PRESETS, {}))
|
|
110
112
|
self.preset_names = list(self.presets)
|
|
111
113
|
if CONF_PRODUCT in device and CONF_SERVICE_MODULES in device[CONF_PRODUCT]:
|
|
112
114
|
for service in device[CONF_PRODUCT][CONF_SERVICE_MODULES]:
|
|
@@ -174,13 +176,28 @@ class DeviceClient(object):
|
|
|
174
176
|
self._simpleVersion = device.get("simpleVersion")
|
|
175
177
|
self._TAG = f"{self.device_id}"
|
|
176
178
|
self.syncProperties = []
|
|
177
|
-
if self.info.model_id == "lk.WIFI-RGBWLight-D0006":
|
|
179
|
+
if self.info.model_id == "lk.WIFI-RGBWLight-D0006": # 一代灯
|
|
178
180
|
self.syncProperties = [CONF_ON_OFF, CONF_DIMMING, CONF_RGBW, CONF_CCT]
|
|
179
181
|
self.ping_data = None
|
|
180
182
|
self.heart_time = 10
|
|
183
|
+
else:
|
|
184
|
+
properties = device.get(CONF_PROPERTIES)
|
|
185
|
+
if properties is not None:
|
|
186
|
+
ip_address = self._get_valid_ip(properties.get("ipAddress"))
|
|
187
|
+
self.update_ip_address(ip_address)
|
|
181
188
|
|
|
182
189
|
# _LOGGER.warning(f"{self._TAG}:{device}")
|
|
183
190
|
|
|
191
|
+
@staticmethod
|
|
192
|
+
def _get_valid_ip(ip: str | None) -> str | None:
|
|
193
|
+
"""Return a valid IP address string."""
|
|
194
|
+
if not ip:
|
|
195
|
+
return None
|
|
196
|
+
try:
|
|
197
|
+
return str(ip_address(ip))
|
|
198
|
+
except ValueError:
|
|
199
|
+
return None
|
|
200
|
+
|
|
184
201
|
async def connect(self, ip_address) -> None:
|
|
185
202
|
_LOGGER.warning(f"{self._TAG}:connect device: {ip_address}")
|
|
186
203
|
self.reader = self.writer = None
|
aidot/models/device_model.py
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
aidot/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
aidot/aes_utils.py,sha256=bGKuC8B6-d2rrNt5Mj7M9gQsaK8C2BDtTisHSnxjgA4,1554
|
|
3
|
-
aidot/client.py,sha256=
|
|
4
|
-
aidot/const.py,sha256
|
|
5
|
-
aidot/device_client.py,sha256=
|
|
3
|
+
aidot/client.py,sha256=evj6AXCcduUStAk-jlMdotY3_h6B-I-cSyddoH0VjO8,24026
|
|
4
|
+
aidot/const.py,sha256=Ly-OGX7Nth4ydy8Lq4JYSUg_RJ-NIoTxNkSmJnnVWP8,12957
|
|
5
|
+
aidot/device_client.py,sha256=SnDcRaptYznxHR86PAJfz-iiCVp3wwygo7HwSU_eCgk,17397
|
|
6
6
|
aidot/discover.py,sha256=1hGBPnPcYRvy72yty5ZMTth_mdZuAYgcSaXf6Z68jow,4944
|
|
7
7
|
aidot/exceptions.py,sha256=VJl_HOxpa9B_5vEA4ORNllDPSo28BBcMlV-mF8NiAk4,663
|
|
8
8
|
aidot/login_const.py,sha256=pjXF9ORsY18m_N8oR4WMfaCDBDvLzDXmmKaEmmup_BE,511
|
|
9
9
|
aidot/models/__init__.py,sha256=vdiTDsMQh7TAODvDS7IMAQp1CS7fK2C_f1CKyjLycqU,849
|
|
10
10
|
aidot/models/device_client_model.py,sha256=4Tjeq0L1NUp2Rf4mbGPkvMWE-lhLU8oHDVHkLxn-FRg,5116
|
|
11
|
-
aidot/models/device_model.py,sha256=
|
|
11
|
+
aidot/models/device_model.py,sha256=630L_eezZWfDujpPQvseRpDC5csmKuOdcymrj8ao7HU,3425
|
|
12
12
|
aidot/models/discover_model.py,sha256=fQ77TbubypYFN12myHgXyXGF0yHEffHdD1HIpEwY4r8,2480
|
|
13
|
-
python_aidot-0.3.
|
|
14
|
-
python_aidot-0.3.
|
|
15
|
-
python_aidot-0.3.
|
|
16
|
-
python_aidot-0.3.
|
|
17
|
-
python_aidot-0.3.
|
|
13
|
+
python_aidot-0.3.59.dist-info/licenses/LICENSE,sha256=5k6Ccd7T5u39Y8730J0qr1ahoaEkd1cahwEccuUZ2Dc,1079
|
|
14
|
+
python_aidot-0.3.59.dist-info/METADATA,sha256=M4HvkuUb8JH4enM8cpwuJnTAA8ICV53Xh9s5a8vGjxY,739
|
|
15
|
+
python_aidot-0.3.59.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
16
|
+
python_aidot-0.3.59.dist-info/top_level.txt,sha256=_doNL2OOnXeinm1X72eH2wz26wAkZwbM47KhNB6_QzI,6
|
|
17
|
+
python_aidot-0.3.59.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|