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 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.57")
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
- return self._merge_effects_by_unique_name(diy_list, fav_preset, preset_list)
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[CONF_PRESETS] = await self.async_get_all_effects(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
@@ -100,6 +100,7 @@ class FavoriteEffectPrimitive:
100
100
  icon: str = None
101
101
  libraryId: str = "aidot.preset"
102
102
  name: str = None
103
+ tag: str = None
103
104
  tags: Optional[FavoriteEffectTags] = None
104
105
  scriptSize: int = None
105
106
  secondShareFlag: bool = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-aidot
3
- Version: 0.3.58
3
+ Version: 0.3.59
4
4
  Summary: aidot control wifi lights
5
5
  Home-page: https://github.com/Aidot-Development-Team/python-aidot
6
6
  Author: aidotdev2024
@@ -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=t8wUYoStd7BxFQdFXmNGFEkLD8RGTgUQjrlY439WsP8,20476
4
- aidot/const.py,sha256=-R3eEfcM24mRr3FPgWlucutyJYR5A8N_l_81bII_YLA,12710
5
- aidot/device_client.py,sha256=rpiBjtfWYXXCoc7HKuvPiNtcrsbJlKsn89IX4-U0sQs,16807
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=vZPkylsN2cpqMZgymof055aak1AFcVqtHhOGCXMj44Q,3405
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.58.dist-info/licenses/LICENSE,sha256=5k6Ccd7T5u39Y8730J0qr1ahoaEkd1cahwEccuUZ2Dc,1079
14
- python_aidot-0.3.58.dist-info/METADATA,sha256=02W5isMXvCcgz4iR5QMiLRG8UJ_aDoGQJzVKvC_64FQ,739
15
- python_aidot-0.3.58.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
16
- python_aidot-0.3.58.dist-info/top_level.txt,sha256=_doNL2OOnXeinm1X72eH2wz26wAkZwbM47KhNB6_QzI,6
17
- python_aidot-0.3.58.dist-info/RECORD,,
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,,