diff --git a/kasa/iot/iotbulb.py b/kasa/iot/iotbulb.py index 14c71103..cb2e858c 100644 --- a/kasa/iot/iotbulb.py +++ b/kasa/iot/iotbulb.py @@ -4,10 +4,13 @@ from __future__ import annotations import logging import re +from dataclasses import dataclass from enum import Enum -from typing import cast +from typing import Annotated, cast -from pydantic.v1 import BaseModel, Field, root_validator +from mashumaro import DataClassDictMixin +from mashumaro.config import BaseConfig +from mashumaro.types import Alias from ..device_type import DeviceType from ..deviceconfig import DeviceConfig @@ -35,9 +38,12 @@ class BehaviorMode(str, Enum): Last = "last_status" #: Use chosen preset. Preset = "customize_preset" + #: Circadian + Circadian = "circadian" -class TurnOnBehavior(BaseModel): +@dataclass +class TurnOnBehavior(DataClassDictMixin): """Model to present a single turn on behavior. :param int preset: the index number of wanted preset. @@ -48,34 +54,30 @@ class TurnOnBehavior(BaseModel): to contain either the preset index, or ``None`` for the last known state. """ - #: Index of preset to use, or ``None`` for the last known state. - preset: int | None = Field(alias="index", default=None) + class Config(BaseConfig): + """Serialization config.""" + + omit_none = True + serialize_by_alias = True + #: Wanted behavior mode: BehaviorMode - - @root_validator - def _mode_based_on_preset(cls, values: dict) -> dict: - """Set the mode based on the preset value.""" - if values["preset"] is not None: - values["mode"] = BehaviorMode.Preset - else: - values["mode"] = BehaviorMode.Last - - return values - - class Config: - """Configuration to make the validator run when changing the values.""" - - validate_assignment = True + #: Index of preset to use, or ``None`` for the last known state. + preset: Annotated[int | None, Alias("index")] = None + brightness: int | None = None + color_temp: int | None = None + hue: int | None = None + saturation: int | None = None -class TurnOnBehaviors(BaseModel): +@dataclass +class TurnOnBehaviors(DataClassDictMixin): """Model to contain turn on behaviors.""" #: The behavior when the bulb is turned on programmatically. - soft: TurnOnBehavior = Field(alias="soft_on") + soft: Annotated[TurnOnBehavior, Alias("soft_on")] #: The behavior when the bulb has been off from mains power. - hard: TurnOnBehavior = Field(alias="hard_on") + hard: Annotated[TurnOnBehavior, Alias("hard_on")] TPLINK_KELVIN = { @@ -303,7 +305,7 @@ class IotBulb(IotDevice): async def get_turn_on_behavior(self) -> TurnOnBehaviors: """Return the behavior for turning the bulb on.""" - return TurnOnBehaviors.parse_obj( + return TurnOnBehaviors.from_dict( await self._query_helper(self.LIGHT_SERVICE, "get_default_behavior") ) @@ -314,7 +316,7 @@ class IotBulb(IotDevice): you should use :func:`get_turn_on_behavior` to get the current settings. """ return await self._query_helper( - self.LIGHT_SERVICE, "set_default_behavior", behavior.dict(by_alias=True) + self.LIGHT_SERVICE, "set_default_behavior", behavior.to_dict() ) async def get_light_state(self) -> dict[str, dict]: diff --git a/tests/fakeprotocol_iot.py b/tests/fakeprotocol_iot.py index b4ce4b94..8a9667d5 100644 --- a/tests/fakeprotocol_iot.py +++ b/tests/fakeprotocol_iot.py @@ -176,6 +176,23 @@ MOTION_MODULE = { } } +LIGHT_DETAILS = { + "color_rendering_index": 80, + "err_code": 0, + "incandescent_equivalent": 60, + "lamp_beam_angle": 150, + "max_lumens": 800, + "max_voltage": 120, + "min_voltage": 110, + "wattage": 10, +} + +DEFAULT_BEHAVIOR = { + "err_code": 0, + "hard_on": {"mode": "circadian"}, + "soft_on": {"mode": "last_status"}, +} + class FakeIotProtocol(IotProtocol): def __init__(self, info, fixture_name=None, *, verbatim=False): @@ -399,6 +416,8 @@ class FakeIotTransport(BaseTransport): }, "smartlife.iot.smartbulb.lightingservice": { "get_light_state": light_state, + "get_light_details": LIGHT_DETAILS, + "get_default_behavior": DEFAULT_BEHAVIOR, "transition_light_state": transition_light_state, "set_preferred_state": set_preferred_state, }, @@ -409,6 +428,8 @@ class FakeIotTransport(BaseTransport): "smartlife.iot.lightStrip": { "set_light_state": transition_light_state, "get_light_state": light_state, + "get_light_details": LIGHT_DETAILS, + "get_default_behavior": DEFAULT_BEHAVIOR, "set_preferred_state": set_preferred_state, }, "smartlife.iot.common.system": { diff --git a/tests/test_bulb.py b/tests/test_bulb.py index 1589e919..4a547522 100644 --- a/tests/test_bulb.py +++ b/tests/test_bulb.py @@ -497,3 +497,9 @@ SYSINFO_SCHEMA_BULB = SYSINFO_SCHEMA.extend( @bulb def test_device_type_bulb(dev: Device): assert dev.device_type in {DeviceType.Bulb, DeviceType.LightStrip} + + +@bulb_iot +async def test_turn_on_behaviours(dev: IotBulb): + behavior = await dev.get_turn_on_behavior() + assert behavior