diff --git a/wyze_sdk/api/devices/bulbs.py b/wyze_sdk/api/devices/bulbs.py index d543270..e2347bf 100644 --- a/wyze_sdk/api/devices/bulbs.py +++ b/wyze_sdk/api/devices/bulbs.py @@ -1,12 +1,12 @@ from datetime import timedelta -from typing import Optional, Sequence +from typing import Optional, Sequence, Tuple, Union from wyze_sdk.api.base import BaseClient -from wyze_sdk.errors import WyzeFeatureNotSupportedError +from wyze_sdk.errors import WyzeFeatureNotSupportedError, WyzeRequestError from wyze_sdk.models.devices import (Bulb, BulbProps, DeviceModels, DeviceProp, DeviceProps, MeshBulb, PropDef) from wyze_sdk.models.devices.bulbs import BaseBulb -from wyze_sdk.models.devices.lights import LightProps +from wyze_sdk.models.devices.lights import LightControlMode, LightProps, LightVisualEffect from wyze_sdk.service import WyzeResponse, api_service @@ -14,6 +14,8 @@ class BulbsClient(BaseClient): """A Client that services Wyze bulbs/lights. """ + LIGHT_STRIP_PRO_SUBSECTION_COUNT = 16 + def _list_bulbs(self) -> Sequence[dict]: return [device for device in super()._list_devices( ) if device['product_model'] in DeviceModels.BULB] @@ -149,13 +151,18 @@ def set_color_temp(self, *, device_mac: str, device_model: str, color_temp: int, """ if device_model in DeviceModels.MESH_BULB: - prop_def = BulbProps.color_temp_mesh() - prop_def.validate(color_temp) + _prop_def = BulbProps.color_temp_mesh() + _prop_def.validate(color_temp) + + _prop = DeviceProp(definition=PropDef(_prop_def.pid, str), value=str(color_temp)) + if device_model in DeviceModels.LIGHT_STRIP: + _prop = [_prop] + _prop.append(DeviceProp(definition=LightProps.control_light(), value=LightControlMode.TEMPERATURE.code)) return super()._api_client().run_action_list( actions={ "key": "set_mesh_property", - "prop": DeviceProp(definition=PropDef(prop_def.pid, str), value=str(color_temp)), + "prop": _prop, "device_mac": device_mac, "provider_key": device_model, } @@ -167,28 +174,66 @@ def set_color_temp(self, *, device_mac: str, device_model: str, color_temp: int, return super()._api_client().set_device_property_list( mac=device_mac, model=device_model, props=DeviceProp(definition=PropDef(prop_def.pid, str), value=str(color_temp))) - def set_color(self, *, device_mac: str, device_model: str, color: str, **kwargs) -> WyzeResponse: + def set_color(self, *, device_mac: str, device_model: str, color: Union[str, Sequence[str]], **kwargs) -> WyzeResponse: """Sets the color of a bulb. + For Light Strip Pro devices, this color can be a list of 16 colors that will + be used to set the value for each subsection of the light strip. The list is + ordered like: + `` + 15 14 13 12 + 8 9 10 11 + 7 6 5 4 + 0 1 2 3 + `` + Args: :param str device_mac: The device mac. e.g. ``ABCDEF1234567890`` :param str device_model: The device model. e.g. ``WLPA19`` - :param str color: The new color temperature. e.g. ``ff0000`` + :param color: The new color(s). e.g. ``ff0000`` or ``['ff0000', '00ff00', ...]`` + :type color: Union[str, Sequence[str]] :rtype: WyzeResponse :raises WyzeFeatureNotSupportedError: If the bulb doesn't support color + or color is a list and the bulb doesn't support color sections """ if device_model not in DeviceModels.MESH_BULB: raise WyzeFeatureNotSupportedError("set_color") - prop_def = BulbProps.color() - prop_def.validate(color) + _color_prop_def = BulbProps.color() + _color_prop = None + + if isinstance(color, (list, Tuple)): + if device_model not in DeviceModels.LIGHT_STRIP_PRO: + raise WyzeFeatureNotSupportedError("The target device type does not support color sections.") + if len(color) != self.LIGHT_STRIP_PRO_SUBSECTION_COUNT: + raise WyzeRequestError(f"Color must specify values for all {self.LIGHT_STRIP_PRO_SUBSECTION_COUNT} subsections.") + + color = list(map(lambda _color: _color.upper(), color)) + for _color in color: + _color_prop_def.validate(_color) + else: + color = color.upper() + _color_prop_def.validate(color) + _color_prop = DeviceProp(definition=PropDef(_color_prop_def.pid, str), value=str(color)) + + _prop = _color_prop + + # if we're dealing with a light strip, we need to set the color control mode + if device_model in DeviceModels.LIGHT_STRIP: + _prop = [DeviceProp(definition=LightProps.control_light(), value=LightControlMode.COLOR.code)] + # Pro light strips also need their subsection property updated + if device_model in DeviceModels.LIGHT_STRIP_PRO: + if isinstance(color, str): + # turn this into a list of subsections for joining + color = [color] * self.LIGHT_STRIP_PRO_SUBSECTION_COUNT + _prop.append(DeviceProp(definition=LightProps.subsection(), value="00" + "#00".join(color))) return super()._api_client().run_action_list( actions={ "key": "set_mesh_property", - "prop": DeviceProp(definition=prop_def, value=color), + "prop": _prop, "device_mac": device_mac, "provider_key": device_model, } @@ -258,3 +303,30 @@ def _set_away_mode_disbled(self, *, device_mac: str, device_model: str, **kwargs ) return super()._api_client().set_device_property( mac=device_mac, model=device_model, pid=prop_def.pid, value="0") + + def set_effect(self, *, device_mac: str, device_model: str, effect: LightVisualEffect, **kwargs) -> WyzeResponse: + """Sets the visual/sound effect for a light. + + Args: + :param str device_mac: The device mac. e.g. ``ABCDEF1234567890`` + :param str device_model: The device model. e.g. ``WLPA19`` + :param str LightVisualEffect: The new visual effect definition. + + :rtype: WyzeResponse + + :raises WyzeFeatureNotSupportedError: If the light doesn't support effects + """ + if device_model not in DeviceModels.LIGHT_STRIP: + raise WyzeFeatureNotSupportedError("set_effect") + + _prop = effect.to_plist() + _prop.append(DeviceProp(definition=LightProps.control_light(), value=LightControlMode.FRAGMENTED.code)) + + return super()._api_client().run_action_list( + actions={ + "key": "set_mesh_property", + "prop": _prop, + "device_mac": device_mac, + "provider_key": device_model, + } + ) diff --git a/wyze_sdk/models/devices/__init__.py b/wyze_sdk/models/devices/__init__.py index 5e21f8b..df4100c 100644 --- a/wyze_sdk/models/devices/__init__.py +++ b/wyze_sdk/models/devices/__init__.py @@ -6,7 +6,7 @@ DeviceProp, DeviceProps, LockableMixin, MotionMixin, PropDef, SwitchableMixin, VoltageMixin) from .lights import LightProps, Light # noqa -from .bulbs import BulbProps, Bulb, MeshBulb, WhiteBulb # noqa +from .bulbs import BulbProps, BaseBulb, Bulb, MeshBulb, WhiteBulb # noqa from .cameras import BaseStation, Camera, CameraProps # noqa from .locks import (Lock, LockEventType, LockGateway, LockProps, # noqa LockRecord, LockRecordDetail) diff --git a/wyze_sdk/models/devices/base.py b/wyze_sdk/models/devices/base.py index 032849c..f80374f 100644 --- a/wyze_sdk/models/devices/base.py +++ b/wyze_sdk/models/devices/base.py @@ -1,6 +1,7 @@ from __future__ import annotations import distutils.util +import json import logging from abc import ABCMeta from datetime import datetime @@ -39,7 +40,9 @@ class DeviceModels(object): BULB_WHITE = ['WLPA19'] BULB_WHITE_V2 = ['HL_HWB2'] - MESH_BULB = ['WLPA19C'] + LIGHT_STRIP_PRO = ['HL_LSLP'] + LIGHT_STRIP = ['HL_LSL'] + LIGHT_STRIP_PRO + MESH_BULB = ['WLPA19C'] + LIGHT_STRIP BULB = BULB_WHITE + BULB_WHITE_V2 + MESH_BULB @@ -174,10 +177,10 @@ def api_value(self) -> Any: if self.definition.type == bool: if self.definition.api_type == int: self.logger.debug(f"returning boolean value {self.value} as int") - return int(self.value) + return int(self.value if self.value else False) elif self.definition.api_type == str: self.logger.debug(f"returning boolean value {self.value} as str") - return str(int(self.value)) + return str(int(self.value if self.value else False)) if isinstance(self.value, self.definition.api_type): self.logger.debug(f"value {self.value} is already of configured api type {self.definition.api_type}, returning unchanged") return self.value @@ -187,6 +190,18 @@ def api_value(self) -> Any: except ValueError: self.logger.warning(f"could not cast value `{self.value}` into expected api type {self.definition.api_type}") + def __str__(self): + return f"Property {self.definition.pid}: {self.value} [API value: {self.api_value}]" + + def to_json(self): + to_return = { + 'pid': self.definition.pid, + 'pvalue': json.dumps(self.api_value), + } + if self.ts is not None: + to_return['ts'] = str(int(self.ts.replace(microsecond=0).timestamp())) + return to_return + class DeviceProps(object): diff --git a/wyze_sdk/models/devices/bulbs.py b/wyze_sdk/models/devices/bulbs.py index e94a79e..d4a199a 100644 --- a/wyze_sdk/models/devices/bulbs.py +++ b/wyze_sdk/models/devices/bulbs.py @@ -1,11 +1,11 @@ from __future__ import annotations -from typing import Optional, Set, Union +from typing import Optional, Sequence, Set, Union from wyze_sdk.models import PropDef, show_unknown_key_warning from wyze_sdk.models.devices import (DeviceProp, DeviceModels, LightProps, Light) -from wyze_sdk.models.devices.lights import LightControlMode +from wyze_sdk.models.devices.lights import LightControlMode, LightVisualEffectModel, LightVisualEffectRunType class BulbProps(object): @@ -73,6 +73,8 @@ def parse(cls, device: Union[dict, "BaseBulb"]) -> Optional["BaseBulb"]: return WhiteBulb(**device) if device["product_model"] in DeviceModels.BULB_WHITE_V2 else Bulb(**device) elif type == MeshBulb.type: return MeshBulb(**device) + elif type == LightStrip.type: + return LightStrip(**device) else: cls.logger.warning(f"Unknown bulb type detected ({device})") return Bulb(**device) @@ -131,3 +133,168 @@ def color(self, value: Union[str, DeviceProp]): if isinstance(value, str): value = DeviceProp(definition=LightProps.color(), value=value) self._color = value + + +class LightStrip(BaseBulb): + + type = "LightStrip" + + @property + def attributes(self) -> Set[str]: + return super().attributes.union({ + "color", + "subsection", + "supports_music", + "lamp_with_music_rhythm", + "effect_model", + "music_mode", + "sensitivity", + "speed", + "auto_color", + "color_palette", + "effect_run_type", + "music_port", + "music_aes_key", + }) + + def __init__( + self, + **others: dict, + ): + super().__init__(type=self.type, **others) + self.color = super()._extract_property(LightProps.color(), others) + self.subsection = super()._extract_property(LightProps.subsection(), others) + self.supports_music = super()._extract_property(LightProps.supports_music(), others) + self.lamp_with_music_rhythm = super()._extract_property(LightProps.lamp_with_music_rhythm(), others) + self.effect_model = super()._extract_property(LightProps.lamp_with_music_mode(), others) + self.music_mode = super()._extract_property(LightProps.music_mode(), others) + self.sensitivity = super()._extract_property(LightProps.lamp_with_music_music(), others) + self.speed = super()._extract_property(LightProps.light_strip_speed(), others) + self.auto_color = super()._extract_property(LightProps.lamp_with_music_auto_color(), others) + self.color_palette = super()._extract_property(LightProps.lamp_with_music_color(), others) + self.effect_run_type = super()._extract_property(LightProps.lamp_with_music_type(), others) + self.music_port = super()._extract_property(LightProps.music_port(), others) + self.music_aes_key = super()._extract_property(LightProps.music_aes_key(), others) + show_unknown_key_warning(self, others) + + @property + def color(self) -> str: + return None if self._color is None else self._color.value + + @color.setter + def color(self, value: Union[str, DeviceProp]): + if isinstance(value, str): + value = DeviceProp(definition=LightProps.color(), value=value) + self._color = value + + @property + def subsection(self) -> Optional[Sequence[str]]: + if self._subsection is None or self._subsection.value.strip() == '': + return None + return list(map(lambda color: color[-6:], self._subsection.value.strip().split('#'))) + + @subsection.setter + def subsection(self, value: Union[str, DeviceProp]): + if isinstance(value, LightProps.subsection().type): + value = DeviceProp(definition=LightProps.subsection(), value=value) + self._subsection = value + + @property + def sensitivity(self) -> int: + return None if self._sensitivity is None else self._sensitivity.value + + @sensitivity.setter + def sensitivity(self, value: Union[int, DeviceProp]): + if isinstance(value, LightProps.lamp_with_music_music().type): + value = DeviceProp(definition=LightProps.lamp_with_music_music(), value=value) + self._sensitivity = value + + @property + def speed(self) -> int: + return None if self._speed is None else self._speed.value + + @speed.setter + def speed(self, value: Union[int, DeviceProp]): + if isinstance(value, LightProps.light_strip_speed().type): + value = DeviceProp(definition=LightProps.light_strip_speed(), value=value) + self._speed = value + + @property + def supports_music(self) -> bool: + return None if self._supports_music is None else self._supports_music.value + + @supports_music.setter + def supports_music(self, value: Union[bool, DeviceProp]): + if isinstance(value, LightProps.supports_music().type): + value = DeviceProp(definition=LightProps.supports_music(), value=value) + self._supports_music = value + + @property + def effect_model(self) -> LightVisualEffectModel: + return None if self._effect_model is None else LightVisualEffectModel.parse(str(self._effect_model.value)) + + @effect_model.setter + def effect_model(self, value: Union[LightVisualEffectModel, DeviceProp]): + if isinstance(value, LightVisualEffectModel): + value = DeviceProp(definition=LightProps.lamp_with_music_mode(), value=value.id) + self._effect_model = value + + @property + def effect_run_type(self) -> LightVisualEffectRunType: + return None if self._effect_run_type is None else LightVisualEffectRunType.parse(str(self._effect_run_type.value)) + + @effect_run_type.setter + def effect_run_type(self, value: Union[LightVisualEffectRunType, DeviceProp]): + if isinstance(value, LightVisualEffectRunType): + value = DeviceProp(definition=LightProps.lamp_with_music_type(), value=value.id) + self._effect_run_type = value + + @property + def music_mode(self) -> bool: + return None if self._music_mode is None else self._music_mode.value + + @music_mode.setter + def music_mode(self, value: Union[bool, DeviceProp]): + if isinstance(value, LightProps.music_mode().type): + value = DeviceProp(definition=LightProps.music_mode(), value=value) + self._music_mode = value + + @property + def auto_color(self) -> bool: + return None if self._auto_color is None else self._auto_color.value + + @auto_color.setter + def auto_color(self, value: Union[bool, DeviceProp]): + if isinstance(value, LightProps.lamp_with_music_auto_color().type): + value = DeviceProp(definition=LightProps.lamp_with_music_auto_color(), value=value) + self._auto_color = value + + @property + def color_palette(self) -> str: + return None if self._color_palette is None else self._color_palette.value + + @color_palette.setter + def color_palette(self, value: Union[str, DeviceProp]): + if isinstance(value, LightProps.lamp_with_music_color().type): + value = DeviceProp(definition=LightProps.lamp_with_music_color(), value=value) + self._color_palette = value + + @property + def music_port(self) -> str: + return None if self._music_port is None else self._music_port.value + + @music_port.setter + def music_port(self, value: Union[str, DeviceProp]): + if isinstance(value, LightProps.music_port().type): + value = DeviceProp(definition=LightProps.music_port(), value=value) + self._music_port = value + + @property + def music_aes_key(self) -> str: + return None if self._music_aes_key is None else self._music_aes_key.value + + @music_aes_key.setter + def music_aes_key(self, value: Union[str, DeviceProp]): + if isinstance(value, LightProps.music_aes_key().type): + value = DeviceProp(definition=LightProps.music_aes_key(), value=value) + self._music_aes_key = value diff --git a/wyze_sdk/models/devices/lights.py b/wyze_sdk/models/devices/lights.py index df69be1..0c16d08 100644 --- a/wyze_sdk/models/devices/lights.py +++ b/wyze_sdk/models/devices/lights.py @@ -1,9 +1,9 @@ from __future__ import annotations from enum import Enum -from typing import Set, Union, Optional +from typing import Sequence, Set, Tuple, Union, Optional -from wyze_sdk.models import PropDef +from wyze_sdk.models import JsonObject, PropDef from wyze_sdk.models.devices import (AbstractWirelessNetworkedDevice, DeviceProp, DeviceProps, SwitchableMixin) @@ -15,7 +15,7 @@ class LightControlMode(Enum): COLOR = ('Color', 1) TEMPERATURE = ('Temperature', 2) - # if the bulb is light strip in fragmented control mode, value is 3 + FRAGMENTED = ('Fragmented', 3) def __init__(self, description: str, code: int): self.description = description @@ -26,9 +26,9 @@ def describe(self): @classmethod def parse(cls, code: int) -> Optional["LightControlMode"]: - for mode in list(LightControlMode): - if code == mode.code: - return mode + for item in list(LightControlMode): + if code == item.code: + return item class LightPowerLossRecoveryMode(Enum): @@ -45,9 +45,91 @@ def describe(self): @classmethod def parse(cls, code: int) -> Optional["LightPowerLossRecoveryMode"]: - for mode in list(LightPowerLossRecoveryMode): - if code == mode.code: - return mode + for item in list(LightPowerLossRecoveryMode): + if code == item.code: + return item + + +class LightVisualEffectRunType(Enum): + """ + Additional visual effect run instructions for lights. + + See: com.wyze.commonlight.strip.model.DynamicTypeBean + """ + + DIRECTION_LEFT = ('0', 'Left [ -> ]') + DIRECTION_DISPERSIVE = ('1', 'Dispersive [<-->]') + DIRECTION_GATHERED = ('2', 'Gathered [-><-]') + + def __init__( + self, + id: str, + description: str, + ): + self.id = id + self.description = description + + def describe(self): + return self.description + + def to_json(self): + return self.id + + @classmethod + def parse(cls, id: str) -> Optional[LightVisualEffectRunType]: + for item in list(LightVisualEffectRunType): + if id == item.id: + return item + + @classmethod + def directions(cls) -> Sequence[LightVisualEffectRunType]: + return [ + LightVisualEffectRunType.DIRECTION_LEFT, + LightVisualEffectRunType.DIRECTION_DISPERSIVE, + LightVisualEffectRunType.DIRECTION_GATHERED, + ] + + +class LightVisualEffectModel(Enum): + """ + A preset light/sound effect model for lights. + + See: com.wyze.commonlight.strip.model.DynamicModelBean + """ + + GRADUAL_CHANGE = ('1', 'Shadow') + JUMP = ('2', 'Leap') + TWINKLE = ('3', 'Flicker') + MARQUEE = ('4', 'Marquee', LightVisualEffectRunType.directions()) + COLORFUL = ('5', 'Color Focus', LightVisualEffectRunType.directions()) + RUNNING_WATER = ('6', 'Water', LightVisualEffectRunType.directions()) + SEA_WAVE = ('7', 'Sea Wave', LightVisualEffectRunType.directions()) + METEOR = ('8', 'Shooting Star', LightVisualEffectRunType.directions()) + STARSHINE = ('9', 'Starlight', LightVisualEffectRunType.directions()) + + def __init__( + self, + id: str, + description: str, + run_types: Union[LightVisualEffectRunType, Sequence[LightVisualEffectRunType]] = None + ): + self.id = id + self.description = description + if run_types is None and not isinstance(run_types, (list, Tuple)): + run_types = [run_types] + self.run_types = run_types + + def describe(self): + return self.description + + def to_json(self): + return self.id + + @classmethod + def parse(cls, id: str) -> Optional[LightVisualEffectModel]: + for item in list(LightVisualEffectModel): + if id == item.id: + return item class LightProps(object): @@ -89,7 +171,7 @@ def color(cls) -> PropDef: @classmethod def control_light(cls) -> PropDef: - return PropDef("P1508", int, acceptable_values=[1, 2]) + return PropDef("P1508", int, acceptable_values=[1, 2, 3]) @classmethod def power_loss_recovery(cls) -> PropDef: @@ -115,6 +197,138 @@ def supports_sun_match(cls) -> PropDef: def supports_timer(cls) -> PropDef: return PropDef("P1531", bool, int, [0, 1]) + # @classmethod + # def something1(cls) -> PropDef: + # return PropDef("P1511", str) # UNUSED? + + @classmethod + def subsection(cls) -> PropDef: + # 15 14 13 12 + # 8 9 10 11 + # 7 6 5 4 + # 0 1 2 3 + return PropDef("P1515", str) + + @classmethod + def lamp_with_music_rhythm(cls) -> PropDef: + # appears to be 0 if not in group, and group id if in group + # and this seems to set ipPort/aes key + # see: com.hualai.wyze.lslight.device.f.L + return PropDef("P1516", str) + + @classmethod + def lamp_with_music_mode(cls) -> PropDef: + # sceneRunModelId + return PropDef("P1522", int, str) + + @classmethod + def lamp_with_music_type(cls) -> PropDef: + # sceneRunTypeId + return PropDef("P1523", int, str) + + @classmethod + def lamp_with_music_music(cls) -> PropDef: + # light strip sensitivity (0-100) + return PropDef("P1524", int, str, acceptable_values=range(0, 101)) + + @classmethod + def lamp_with_music_auto_color(cls) -> PropDef: + # lampWithMusicAutoColor + return PropDef("P1525", bool, str, ['0', '1']) + + @classmethod + def lamp_with_music_color(cls) -> PropDef: + # this is the color palette under music -> auto-color + return PropDef("P1526", str) + + # @classmethod + # def color_palette(cls) -> PropDef: + # return PropDef("P1527", bool, int, [0, 1]) # UNUSED? + + @classmethod + def supports_music(cls) -> PropDef: + return PropDef("P1532", bool, int, [0, 1]) + + @classmethod + def music_port(cls) -> PropDef: + return PropDef("P1533", str) + + @classmethod + def music_aes_key(cls) -> PropDef: + return PropDef("P1534", str) + + @classmethod + def music_mode(cls) -> PropDef: + # musicMode + return PropDef("P1535", bool, str, ['0', '1']) + + @classmethod + def light_strip_speed(cls) -> PropDef: + # (1-10) + return PropDef("P1536", str, acceptable_values=["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]) + + +class LightVisualEffect(JsonObject): + """ + A customizable visual/sound effect for lights. + + Visual effects comprise the pre-defined scene model, optional scene run + instructions, and additional configurable properties. + + An example of this is the water effect, which has a fixed model, a single + scene run type for the "direction" of the effect, and options like auto- + color, music mode, etc. + """ + + attributes = { + "model", + "rhythm", + "sensitivity", + "auto_color", + "color_palette", + "mode", + "speed", + "run_type", + } + + def __init__( + self, + *, + model: LightVisualEffectModel, + rhythm: str = '0', + music_mode: bool = False, + sensitivity: int = 100, + speed: int = 8, + auto_color: bool = False, + color_palette: str = '2961AF,B5267A,91FF6A', + run_type: LightVisualEffectRunType = None, + ): + self.model = model + self.rhythm = rhythm + self.music_mode = music_mode + self.sensitivity = sensitivity + self.speed = speed + self.auto_color = auto_color + self.color_palette = color_palette + self.run_type = run_type + + def to_json(self): + return map(lambda prop: prop.to_json(), self.to_plist()) + + def to_plist(self) -> Sequence[DeviceProp]: + to_return = [ + DeviceProp(definition=LightProps.lamp_with_music_mode(), value=self.model.id), + DeviceProp(definition=LightProps.music_mode(), value=self.music_mode), + DeviceProp(definition=LightProps.light_strip_speed(), value=self.speed), + DeviceProp(definition=LightProps.lamp_with_music_music(), value=self.sensitivity), + DeviceProp(definition=LightProps.lamp_with_music_rhythm(), value=self.rhythm), + DeviceProp(definition=LightProps.lamp_with_music_auto_color(), value=self.auto_color), + DeviceProp(definition=LightProps.lamp_with_music_color(), value=self.color_palette), + ] + if self.run_type is not None: + to_return.append(DeviceProp(definition=LightProps.lamp_with_music_type(), value=self.run_type.id)) + return to_return + class Light(SwitchableMixin, AbstractWirelessNetworkedDevice): diff --git a/wyze_sdk/service/api_service.py b/wyze_sdk/service/api_service.py index fa81323..d3abdc9 100644 --- a/wyze_sdk/service/api_service.py +++ b/wyze_sdk/service/api_service.py @@ -272,24 +272,28 @@ def run_action_list(self, *, actions: Union[dict[str, dict[str, DeviceProp]], Se if not isinstance(actions, (list, Tuple)): actions = [actions] for action in actions: - kwargs["action_list"].append({ + _action = { "action_key": action["key"], "action_params": { "list": [ { "mac": action["device_mac"], - "plist": [ - { - "pid": action["prop"].definition.pid, - "pvalue": str(action["prop"].api_value), - } - ] + "plist": [] } ] }, "instance_id": action["device_mac"], "provider_key": action["provider_key"], - }) + } + if 'prop' in action: + if not isinstance(action['prop'], (list, Tuple)): + action['prop'] = [action['prop']] + for prop in action['prop']: + _action["action_params"]["list"][0]["plist"].append({ + "pid": prop.definition.pid, + "pvalue": str(prop.api_value), + }) + kwargs["action_list"].append(_action) if custom_string is not None: kwargs.update({"custom_string": custom_string}) return self.api_call('/app/v2/auto/run_action_list', json=kwargs)