Skip to content

Migrate TurnOnBehaviours to mashumaro #1285

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 27 additions & 25 deletions kasa/iot/iotbulb.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@

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
Expand Down Expand Up @@ -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.
Expand All @@ -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)
#: 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
class Config(BaseConfig):
"""Serialization config."""

return values
omit_none = True
serialize_by_alias = True

class Config:
"""Configuration to make the validator run when changing the values."""

validate_assignment = True
#: Wanted behavior
mode: BehaviorMode
#: 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 = {
Expand Down Expand Up @@ -303,7 +305,7 @@ async def get_light_details(self) -> dict[str, int]:

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")
)

Expand All @@ -314,7 +316,7 @@ async def set_turn_on_behavior(self, behavior: TurnOnBehaviors) -> dict:
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]:
Expand Down
21 changes: 21 additions & 0 deletions tests/fakeprotocol_iot.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,23 @@ def success(res):
}
}

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):
Expand Down Expand Up @@ -398,6 +415,8 @@ def set_time(self, new_state: dict, *args):
},
"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,
},
Expand All @@ -408,6 +427,8 @@ def set_time(self, new_state: dict, *args):
"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": {
Expand Down
6 changes: 6 additions & 0 deletions tests/test_bulb.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,3 +494,9 @@ async def test_modify_preset_payloads(dev: IotBulb, preset, payload, mocker):
@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
Loading