mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-04-26 16:46:23 +00:00
Add support for bulb presets (#379)
* Add support for bulb presets * Update docs
This commit is contained in:
parent
b386485ab0
commit
f32f7f3925
@ -11,6 +11,7 @@ Supported features
|
|||||||
* Setting brightness, color temperature, and color (in HSV)
|
* Setting brightness, color temperature, and color (in HSV)
|
||||||
* Querying emeter information
|
* Querying emeter information
|
||||||
* Transitions
|
* Transitions
|
||||||
|
* Presets
|
||||||
|
|
||||||
Currently unsupported
|
Currently unsupported
|
||||||
*********************
|
*********************
|
||||||
@ -61,3 +62,7 @@ API documentation
|
|||||||
:members:
|
:members:
|
||||||
:inherited-members:
|
:inherited-members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
|
|
||||||
|
.. autoclass:: kasa.SmartBulbPreset
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
@ -17,7 +17,7 @@ from kasa.discover import Discover
|
|||||||
from kasa.emeterstatus import EmeterStatus
|
from kasa.emeterstatus import EmeterStatus
|
||||||
from kasa.exceptions import SmartDeviceException
|
from kasa.exceptions import SmartDeviceException
|
||||||
from kasa.protocol import TPLinkSmartHomeProtocol
|
from kasa.protocol import TPLinkSmartHomeProtocol
|
||||||
from kasa.smartbulb import SmartBulb
|
from kasa.smartbulb import SmartBulb, SmartBulbPreset
|
||||||
from kasa.smartdevice import DeviceType, SmartDevice
|
from kasa.smartdevice import DeviceType, SmartDevice
|
||||||
from kasa.smartdimmer import SmartDimmer
|
from kasa.smartdimmer import SmartDimmer
|
||||||
from kasa.smartlightstrip import SmartLightStrip
|
from kasa.smartlightstrip import SmartLightStrip
|
||||||
@ -31,6 +31,7 @@ __all__ = [
|
|||||||
"Discover",
|
"Discover",
|
||||||
"TPLinkSmartHomeProtocol",
|
"TPLinkSmartHomeProtocol",
|
||||||
"SmartBulb",
|
"SmartBulb",
|
||||||
|
"SmartBulbPreset",
|
||||||
"DeviceType",
|
"DeviceType",
|
||||||
"EmeterStatus",
|
"EmeterStatus",
|
||||||
"SmartDevice",
|
"SmartDevice",
|
||||||
|
61
kasa/cli.py
61
kasa/cli.py
@ -216,8 +216,13 @@ async def state(dev: SmartDevice):
|
|||||||
click.echo(f"\tLocation: {dev.location}")
|
click.echo(f"\tLocation: {dev.location}")
|
||||||
|
|
||||||
click.echo(click.style("\n\t== Device specific information ==", bold=True))
|
click.echo(click.style("\n\t== Device specific information ==", bold=True))
|
||||||
for k, v in dev.state_information.items():
|
for info_name, info_data in dev.state_information.items():
|
||||||
click.echo(f"\t{k}: {v}")
|
if isinstance(info_data, list):
|
||||||
|
click.echo(f"\t{info_name}:")
|
||||||
|
for item in info_data:
|
||||||
|
click.echo(f"\t\t{item}")
|
||||||
|
else:
|
||||||
|
click.echo(f"\t{info_name}: {info_data}")
|
||||||
|
|
||||||
if dev.has_emeter:
|
if dev.has_emeter:
|
||||||
click.echo(click.style("\n\t== Current State ==", bold=True))
|
click.echo(click.style("\n\t== Current State ==", bold=True))
|
||||||
@ -538,5 +543,57 @@ def _schedule_list(dev, type):
|
|||||||
click.echo(f"No rules of type {type}")
|
click.echo(f"No rules of type {type}")
|
||||||
|
|
||||||
|
|
||||||
|
@cli.group(invoke_without_command=True)
|
||||||
|
@click.pass_context
|
||||||
|
async def presets(ctx):
|
||||||
|
"""List and modify bulb setting presets."""
|
||||||
|
if ctx.invoked_subcommand is None:
|
||||||
|
return await ctx.invoke(presets_list)
|
||||||
|
|
||||||
|
|
||||||
|
@presets.command(name="list")
|
||||||
|
@pass_dev
|
||||||
|
def presets_list(dev: SmartBulb):
|
||||||
|
"""List presets."""
|
||||||
|
if not dev.is_bulb:
|
||||||
|
click.echo("Presets only supported on bulbs")
|
||||||
|
return
|
||||||
|
|
||||||
|
for preset in dev.presets:
|
||||||
|
print(preset)
|
||||||
|
|
||||||
|
|
||||||
|
@presets.command(name="modify")
|
||||||
|
@click.argument("index", type=int)
|
||||||
|
@click.option("--brightness", type=int)
|
||||||
|
@click.option("--hue", type=int)
|
||||||
|
@click.option("--saturation", type=int)
|
||||||
|
@click.option("--temperature", type=int)
|
||||||
|
@pass_dev
|
||||||
|
async def presets_modify(
|
||||||
|
dev: SmartBulb, index, brightness, hue, saturation, temperature
|
||||||
|
):
|
||||||
|
"""Modify a preset."""
|
||||||
|
for preset in dev.presets:
|
||||||
|
if preset.index == index:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
click.echo(f"No preset found for index {index}")
|
||||||
|
return
|
||||||
|
|
||||||
|
if brightness is not None:
|
||||||
|
preset.brightness = brightness
|
||||||
|
if hue is not None:
|
||||||
|
preset.hue = hue
|
||||||
|
if saturation is not None:
|
||||||
|
preset.saturation = saturation
|
||||||
|
if temperature is not None:
|
||||||
|
preset.color_temp = temperature
|
||||||
|
|
||||||
|
click.echo(f"Going to save preset: {preset}")
|
||||||
|
|
||||||
|
await dev.save_preset(preset)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
cli()
|
cli()
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
"""Module for bulbs (LB*, KL*, KB*)."""
|
"""Module for bulbs (LB*, KL*, KB*)."""
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from typing import Any, Dict, NamedTuple, cast
|
from typing import Any, Dict, List, NamedTuple, cast
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from .modules import Antitheft, Cloud, Countdown, Emeter, Schedule, Time, Usage
|
from .modules import Antitheft, Cloud, Countdown, Emeter, Schedule, Time, Usage
|
||||||
from .smartdevice import DeviceType, SmartDevice, SmartDeviceException, requires_update
|
from .smartdevice import DeviceType, SmartDevice, SmartDeviceException, requires_update
|
||||||
@ -22,6 +24,16 @@ class HSV(NamedTuple):
|
|||||||
value: int
|
value: int
|
||||||
|
|
||||||
|
|
||||||
|
class SmartBulbPreset(BaseModel):
|
||||||
|
"""Bulb configuration preset."""
|
||||||
|
|
||||||
|
index: int
|
||||||
|
brightness: int
|
||||||
|
hue: int
|
||||||
|
saturation: int
|
||||||
|
color_temp: int
|
||||||
|
|
||||||
|
|
||||||
TPLINK_KELVIN = {
|
TPLINK_KELVIN = {
|
||||||
"LB130": ColorTempRange(2500, 9000),
|
"LB130": ColorTempRange(2500, 9000),
|
||||||
"LB120": ColorTempRange(2700, 6500),
|
"LB120": ColorTempRange(2700, 6500),
|
||||||
@ -50,7 +62,7 @@ class SmartBulb(SmartDevice):
|
|||||||
All changes to the device are done using awaitable methods,
|
All changes to the device are done using awaitable methods,
|
||||||
which will not change the cached values, but you must await :func:`update()` separately.
|
which will not change the cached values, but you must await :func:`update()` separately.
|
||||||
|
|
||||||
Errors reported by the device are raised as :class:`SmartDeviceException`\s,
|
Errors reported by the device are raised as :class:`SmartDeviceExceptions <kasa.exceptions.SmartDeviceException>`,
|
||||||
and should be handled by the user of the library.
|
and should be handled by the user of the library.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
@ -68,7 +80,8 @@ class SmartBulb(SmartDevice):
|
|||||||
>>> print(bulb.is_on)
|
>>> print(bulb.is_on)
|
||||||
True
|
True
|
||||||
|
|
||||||
You can use the ``is_``-prefixed properties to check for supported features
|
You can use the ``is_``-prefixed properties to check for supported features:
|
||||||
|
|
||||||
>>> bulb.is_dimmable
|
>>> bulb.is_dimmable
|
||||||
True
|
True
|
||||||
>>> bulb.is_color
|
>>> bulb.is_color
|
||||||
@ -102,11 +115,26 @@ class SmartBulb(SmartDevice):
|
|||||||
HSV(hue=180, saturation=100, value=80)
|
HSV(hue=180, saturation=100, value=80)
|
||||||
|
|
||||||
If you don't want to use the default transitions, you can pass `transition` in milliseconds.
|
If you don't want to use the default transitions, you can pass `transition` in milliseconds.
|
||||||
This applies to all transitions (turn_on, turn_off, set_hsv, set_color_temp, set_brightness).
|
This applies to all transitions (:func:`turn_on`, :func:`turn_off`, :func:`set_hsv`, :func:`set_color_temp`, :func:`set_brightness`).
|
||||||
The following changes the brightness over a period of 10 seconds:
|
The following changes the brightness over a period of 10 seconds:
|
||||||
|
|
||||||
>>> asyncio.run(bulb.set_brightness(100, transition=10_000))
|
>>> asyncio.run(bulb.set_brightness(100, transition=10_000))
|
||||||
|
|
||||||
|
Bulb configuration presets can be accessed using the :func:`presets` property:
|
||||||
|
|
||||||
|
>>> bulb.presets
|
||||||
|
[SmartBulbPreset(index=0, brightness=50, hue=0, saturation=0, color_temp=2700), SmartBulbPreset(index=1, brightness=100, hue=0, saturation=75, color_temp=0), SmartBulbPreset(index=2, brightness=100, hue=120, saturation=75, color_temp=0), SmartBulbPreset(index=3, brightness=100, hue=240, saturation=75, color_temp=0)]
|
||||||
|
|
||||||
|
To modify an existing preset, pass :class:`~kasa.smartbulb.SmartBulbPreset` instance to :func:`save_preset` method:
|
||||||
|
|
||||||
|
>>> preset = bulb.presets[0]
|
||||||
|
>>> preset.brightness
|
||||||
|
50
|
||||||
|
>>> preset.brightness = 100
|
||||||
|
>>> asyncio.run(bulb.save_preset(preset))
|
||||||
|
>>> bulb.presets[0].brightness
|
||||||
|
100
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
LIGHT_SERVICE = "smartlife.iot.smartbulb.lightingservice"
|
LIGHT_SERVICE = "smartlife.iot.smartbulb.lightingservice"
|
||||||
@ -167,7 +195,7 @@ class SmartBulb(SmartDevice):
|
|||||||
@requires_update
|
@requires_update
|
||||||
def light_state(self) -> Dict[str, str]:
|
def light_state(self) -> Dict[str, str]:
|
||||||
"""Query the light state."""
|
"""Query the light state."""
|
||||||
light_state = self._last_update["system"]["get_sysinfo"]["light_state"]
|
light_state = self.sys_info["light_state"]
|
||||||
if light_state is None:
|
if light_state is None:
|
||||||
raise SmartDeviceException(
|
raise SmartDeviceException(
|
||||||
"The device has no light_state or you have not called update()"
|
"The device has no light_state or you have not called update()"
|
||||||
@ -369,6 +397,7 @@ class SmartBulb(SmartDevice):
|
|||||||
info["Valid temperature range"] = self.valid_temperature_range
|
info["Valid temperature range"] = self.valid_temperature_range
|
||||||
if self.is_color:
|
if self.is_color:
|
||||||
info["HSV"] = self.hsv
|
info["HSV"] = self.hsv
|
||||||
|
info["Presets"] = self.presets
|
||||||
|
|
||||||
return info
|
return info
|
||||||
|
|
||||||
@ -407,3 +436,25 @@ class SmartBulb(SmartDevice):
|
|||||||
return await self._query_helper(
|
return await self._query_helper(
|
||||||
"smartlife.iot.common.system", "set_dev_alias", {"alias": alias}
|
"smartlife.iot.common.system", "set_dev_alias", {"alias": alias}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property # type: ignore
|
||||||
|
@requires_update
|
||||||
|
def presets(self) -> List[SmartBulbPreset]:
|
||||||
|
"""Return a list of available bulb setting presets."""
|
||||||
|
return [SmartBulbPreset(**vals) for vals in self.sys_info["preferred_state"]]
|
||||||
|
|
||||||
|
async def save_preset(self, preset: SmartBulbPreset):
|
||||||
|
"""Save a setting preset.
|
||||||
|
|
||||||
|
You can either construct a preset object manually, or pass an existing one obtained
|
||||||
|
obtained using :func:`presets`.
|
||||||
|
"""
|
||||||
|
if len(self.presets) == 0:
|
||||||
|
raise SmartDeviceException("Device does not supported saving presets")
|
||||||
|
|
||||||
|
if preset.index >= len(self.presets):
|
||||||
|
raise SmartDeviceException("Invalid preset index")
|
||||||
|
|
||||||
|
return await self._query_helper(
|
||||||
|
self.LIGHT_SERVICE, "set_preferred_state", preset.dict()
|
||||||
|
)
|
||||||
|
@ -391,6 +391,12 @@ class FakeTransportProtocol(TPLinkSmartHomeProtocol):
|
|||||||
_LOGGER.debug("New light state: %s", new_state)
|
_LOGGER.debug("New light state: %s", new_state)
|
||||||
self.proto["system"]["get_sysinfo"]["light_state"] = new_state
|
self.proto["system"]["get_sysinfo"]["light_state"] = new_state
|
||||||
|
|
||||||
|
def set_preferred_state(self, new_state, *args):
|
||||||
|
"""Implementation of set_preferred_state."""
|
||||||
|
self.proto["system"]["get_sysinfo"]["preferred_state"][
|
||||||
|
new_state["index"]
|
||||||
|
] = new_state
|
||||||
|
|
||||||
def light_state(self, x, *args):
|
def light_state(self, x, *args):
|
||||||
light_state = self.proto["system"]["get_sysinfo"]["light_state"]
|
light_state = self.proto["system"]["get_sysinfo"]["light_state"]
|
||||||
# Our tests have light state off, so we simply return the dft_on_state when device is on.
|
# Our tests have light state off, so we simply return the dft_on_state when device is on.
|
||||||
@ -425,6 +431,7 @@ class FakeTransportProtocol(TPLinkSmartHomeProtocol):
|
|||||||
"smartlife.iot.smartbulb.lightingservice": {
|
"smartlife.iot.smartbulb.lightingservice": {
|
||||||
"get_light_state": light_state,
|
"get_light_state": light_state,
|
||||||
"transition_light_state": transition_light_state,
|
"transition_light_state": transition_light_state,
|
||||||
|
"set_preferred_state": set_preferred_state,
|
||||||
},
|
},
|
||||||
"smartlife.iot.lighting_effect": {
|
"smartlife.iot.lighting_effect": {
|
||||||
"set_lighting_effect": set_lighting_effect,
|
"set_lighting_effect": set_lighting_effect,
|
||||||
@ -433,6 +440,7 @@ class FakeTransportProtocol(TPLinkSmartHomeProtocol):
|
|||||||
"smartlife.iot.lightStrip": {
|
"smartlife.iot.lightStrip": {
|
||||||
"set_light_state": transition_light_state,
|
"set_light_state": transition_light_state,
|
||||||
"get_light_state": light_state,
|
"get_light_state": light_state,
|
||||||
|
"set_preferred_state": set_preferred_state,
|
||||||
},
|
},
|
||||||
"smartlife.iot.common.system": {
|
"smartlife.iot.common.system": {
|
||||||
"set_dev_alias": set_alias,
|
"set_dev_alias": set_alias,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from kasa import DeviceType, SmartDeviceException
|
from kasa import DeviceType, SmartBulb, SmartBulbPreset, SmartDeviceException
|
||||||
|
|
||||||
from .conftest import (
|
from .conftest import (
|
||||||
bulb,
|
bulb,
|
||||||
@ -18,7 +18,7 @@ from .newfakes import BULB_SCHEMA, LIGHT_STATE_SCHEMA
|
|||||||
|
|
||||||
|
|
||||||
@bulb
|
@bulb
|
||||||
async def test_bulb_sysinfo(dev):
|
async def test_bulb_sysinfo(dev: SmartBulb):
|
||||||
assert dev.sys_info is not None
|
assert dev.sys_info is not None
|
||||||
BULB_SCHEMA(dev.sys_info)
|
BULB_SCHEMA(dev.sys_info)
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ async def test_bulb_sysinfo(dev):
|
|||||||
|
|
||||||
|
|
||||||
@bulb
|
@bulb
|
||||||
async def test_state_attributes(dev):
|
async def test_state_attributes(dev: SmartBulb):
|
||||||
assert "Brightness" in dev.state_information
|
assert "Brightness" in dev.state_information
|
||||||
assert dev.state_information["Brightness"] == dev.brightness
|
assert dev.state_information["Brightness"] == dev.brightness
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ async def test_state_attributes(dev):
|
|||||||
|
|
||||||
|
|
||||||
@bulb
|
@bulb
|
||||||
async def test_light_state_without_update(dev, monkeypatch):
|
async def test_light_state_without_update(dev: SmartBulb, monkeypatch):
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(SmartDeviceException):
|
||||||
monkeypatch.setitem(
|
monkeypatch.setitem(
|
||||||
dev._last_update["system"]["get_sysinfo"], "light_state", None
|
dev._last_update["system"]["get_sysinfo"], "light_state", None
|
||||||
@ -49,13 +49,13 @@ async def test_light_state_without_update(dev, monkeypatch):
|
|||||||
|
|
||||||
|
|
||||||
@bulb
|
@bulb
|
||||||
async def test_get_light_state(dev):
|
async def test_get_light_state(dev: SmartBulb):
|
||||||
LIGHT_STATE_SCHEMA(await dev.get_light_state())
|
LIGHT_STATE_SCHEMA(await dev.get_light_state())
|
||||||
|
|
||||||
|
|
||||||
@color_bulb
|
@color_bulb
|
||||||
@turn_on
|
@turn_on
|
||||||
async def test_hsv(dev, turn_on):
|
async def test_hsv(dev: SmartBulb, turn_on):
|
||||||
await handle_turn_on(dev, turn_on)
|
await handle_turn_on(dev, turn_on)
|
||||||
assert dev.is_color
|
assert dev.is_color
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ async def test_hsv(dev, turn_on):
|
|||||||
|
|
||||||
|
|
||||||
@color_bulb
|
@color_bulb
|
||||||
async def test_set_hsv_transition(dev, mocker):
|
async def test_set_hsv_transition(dev: SmartBulb, mocker):
|
||||||
set_light_state = mocker.patch("kasa.SmartBulb.set_light_state")
|
set_light_state = mocker.patch("kasa.SmartBulb.set_light_state")
|
||||||
await dev.set_hsv(10, 10, 100, transition=1000)
|
await dev.set_hsv(10, 10, 100, transition=1000)
|
||||||
|
|
||||||
@ -86,7 +86,7 @@ async def test_set_hsv_transition(dev, mocker):
|
|||||||
|
|
||||||
@color_bulb
|
@color_bulb
|
||||||
@turn_on
|
@turn_on
|
||||||
async def test_invalid_hsv(dev, turn_on):
|
async def test_invalid_hsv(dev: SmartBulb, turn_on):
|
||||||
await handle_turn_on(dev, turn_on)
|
await handle_turn_on(dev, turn_on)
|
||||||
assert dev.is_color
|
assert dev.is_color
|
||||||
|
|
||||||
@ -104,13 +104,13 @@ async def test_invalid_hsv(dev, turn_on):
|
|||||||
|
|
||||||
|
|
||||||
@color_bulb
|
@color_bulb
|
||||||
async def test_color_state_information(dev):
|
async def test_color_state_information(dev: SmartBulb):
|
||||||
assert "HSV" in dev.state_information
|
assert "HSV" in dev.state_information
|
||||||
assert dev.state_information["HSV"] == dev.hsv
|
assert dev.state_information["HSV"] == dev.hsv
|
||||||
|
|
||||||
|
|
||||||
@non_color_bulb
|
@non_color_bulb
|
||||||
async def test_hsv_on_non_color(dev):
|
async def test_hsv_on_non_color(dev: SmartBulb):
|
||||||
assert not dev.is_color
|
assert not dev.is_color
|
||||||
|
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(SmartDeviceException):
|
||||||
@ -120,7 +120,7 @@ async def test_hsv_on_non_color(dev):
|
|||||||
|
|
||||||
|
|
||||||
@variable_temp
|
@variable_temp
|
||||||
async def test_variable_temp_state_information(dev):
|
async def test_variable_temp_state_information(dev: SmartBulb):
|
||||||
assert "Color temperature" in dev.state_information
|
assert "Color temperature" in dev.state_information
|
||||||
assert dev.state_information["Color temperature"] == dev.color_temp
|
assert dev.state_information["Color temperature"] == dev.color_temp
|
||||||
|
|
||||||
@ -132,7 +132,7 @@ async def test_variable_temp_state_information(dev):
|
|||||||
|
|
||||||
@variable_temp
|
@variable_temp
|
||||||
@turn_on
|
@turn_on
|
||||||
async def test_try_set_colortemp(dev, turn_on):
|
async def test_try_set_colortemp(dev: SmartBulb, turn_on):
|
||||||
await handle_turn_on(dev, turn_on)
|
await handle_turn_on(dev, turn_on)
|
||||||
await dev.set_color_temp(2700)
|
await dev.set_color_temp(2700)
|
||||||
await dev.update()
|
await dev.update()
|
||||||
@ -140,7 +140,7 @@ async def test_try_set_colortemp(dev, turn_on):
|
|||||||
|
|
||||||
|
|
||||||
@variable_temp
|
@variable_temp
|
||||||
async def test_set_color_temp_transition(dev, mocker):
|
async def test_set_color_temp_transition(dev: SmartBulb, mocker):
|
||||||
set_light_state = mocker.patch("kasa.SmartBulb.set_light_state")
|
set_light_state = mocker.patch("kasa.SmartBulb.set_light_state")
|
||||||
await dev.set_color_temp(2700, transition=100)
|
await dev.set_color_temp(2700, transition=100)
|
||||||
|
|
||||||
@ -148,7 +148,7 @@ async def test_set_color_temp_transition(dev, mocker):
|
|||||||
|
|
||||||
|
|
||||||
@variable_temp
|
@variable_temp
|
||||||
async def test_unknown_temp_range(dev, monkeypatch, caplog):
|
async def test_unknown_temp_range(dev: SmartBulb, monkeypatch, caplog):
|
||||||
monkeypatch.setitem(dev._sys_info, "model", "unknown bulb")
|
monkeypatch.setitem(dev._sys_info, "model", "unknown bulb")
|
||||||
|
|
||||||
assert dev.valid_temperature_range == (2700, 5000)
|
assert dev.valid_temperature_range == (2700, 5000)
|
||||||
@ -156,7 +156,7 @@ async def test_unknown_temp_range(dev, monkeypatch, caplog):
|
|||||||
|
|
||||||
|
|
||||||
@variable_temp
|
@variable_temp
|
||||||
async def test_out_of_range_temperature(dev):
|
async def test_out_of_range_temperature(dev: SmartBulb):
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
await dev.set_color_temp(1000)
|
await dev.set_color_temp(1000)
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
@ -164,7 +164,7 @@ async def test_out_of_range_temperature(dev):
|
|||||||
|
|
||||||
|
|
||||||
@non_variable_temp
|
@non_variable_temp
|
||||||
async def test_non_variable_temp(dev):
|
async def test_non_variable_temp(dev: SmartBulb):
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(SmartDeviceException):
|
||||||
await dev.set_color_temp(2700)
|
await dev.set_color_temp(2700)
|
||||||
|
|
||||||
@ -177,7 +177,7 @@ async def test_non_variable_temp(dev):
|
|||||||
|
|
||||||
@dimmable
|
@dimmable
|
||||||
@turn_on
|
@turn_on
|
||||||
async def test_dimmable_brightness(dev, turn_on):
|
async def test_dimmable_brightness(dev: SmartBulb, turn_on):
|
||||||
await handle_turn_on(dev, turn_on)
|
await handle_turn_on(dev, turn_on)
|
||||||
assert dev.is_dimmable
|
assert dev.is_dimmable
|
||||||
|
|
||||||
@ -194,7 +194,7 @@ async def test_dimmable_brightness(dev, turn_on):
|
|||||||
|
|
||||||
|
|
||||||
@bulb
|
@bulb
|
||||||
async def test_turn_on_transition(dev, mocker):
|
async def test_turn_on_transition(dev: SmartBulb, mocker):
|
||||||
set_light_state = mocker.patch("kasa.SmartBulb.set_light_state")
|
set_light_state = mocker.patch("kasa.SmartBulb.set_light_state")
|
||||||
await dev.turn_on(transition=1000)
|
await dev.turn_on(transition=1000)
|
||||||
|
|
||||||
@ -206,7 +206,7 @@ async def test_turn_on_transition(dev, mocker):
|
|||||||
|
|
||||||
|
|
||||||
@bulb
|
@bulb
|
||||||
async def test_dimmable_brightness_transition(dev, mocker):
|
async def test_dimmable_brightness_transition(dev: SmartBulb, mocker):
|
||||||
set_light_state = mocker.patch("kasa.SmartBulb.set_light_state")
|
set_light_state = mocker.patch("kasa.SmartBulb.set_light_state")
|
||||||
await dev.set_brightness(10, transition=1000)
|
await dev.set_brightness(10, transition=1000)
|
||||||
|
|
||||||
@ -214,7 +214,7 @@ async def test_dimmable_brightness_transition(dev, mocker):
|
|||||||
|
|
||||||
|
|
||||||
@dimmable
|
@dimmable
|
||||||
async def test_invalid_brightness(dev):
|
async def test_invalid_brightness(dev: SmartBulb):
|
||||||
assert dev.is_dimmable
|
assert dev.is_dimmable
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
@ -225,7 +225,7 @@ async def test_invalid_brightness(dev):
|
|||||||
|
|
||||||
|
|
||||||
@non_dimmable
|
@non_dimmable
|
||||||
async def test_non_dimmable(dev):
|
async def test_non_dimmable(dev: SmartBulb):
|
||||||
assert not dev.is_dimmable
|
assert not dev.is_dimmable
|
||||||
|
|
||||||
with pytest.raises(SmartDeviceException):
|
with pytest.raises(SmartDeviceException):
|
||||||
@ -235,7 +235,9 @@ async def test_non_dimmable(dev):
|
|||||||
|
|
||||||
|
|
||||||
@bulb
|
@bulb
|
||||||
async def test_ignore_default_not_set_without_color_mode_change_turn_on(dev, mocker):
|
async def test_ignore_default_not_set_without_color_mode_change_turn_on(
|
||||||
|
dev: SmartBulb, mocker
|
||||||
|
):
|
||||||
query_helper = mocker.patch("kasa.SmartBulb._query_helper")
|
query_helper = mocker.patch("kasa.SmartBulb._query_helper")
|
||||||
# When turning back without settings, ignore default to restore the state
|
# When turning back without settings, ignore default to restore the state
|
||||||
await dev.turn_on()
|
await dev.turn_on()
|
||||||
@ -245,3 +247,46 @@ async def test_ignore_default_not_set_without_color_mode_change_turn_on(dev, moc
|
|||||||
await dev.turn_off()
|
await dev.turn_off()
|
||||||
args, kwargs = query_helper.call_args_list[1]
|
args, kwargs = query_helper.call_args_list[1]
|
||||||
assert args[2] == {"on_off": 0, "ignore_default": 1}
|
assert args[2] == {"on_off": 0, "ignore_default": 1}
|
||||||
|
|
||||||
|
|
||||||
|
@bulb
|
||||||
|
async def test_list_presets(dev: SmartBulb):
|
||||||
|
presets = dev.presets
|
||||||
|
assert len(presets) == len(dev.sys_info["preferred_state"])
|
||||||
|
|
||||||
|
for preset, raw in zip(presets, dev.sys_info["preferred_state"]):
|
||||||
|
assert preset.index == raw["index"]
|
||||||
|
assert preset.hue == raw["hue"]
|
||||||
|
assert preset.brightness == raw["brightness"]
|
||||||
|
assert preset.saturation == raw["saturation"]
|
||||||
|
assert preset.color_temp == raw["color_temp"]
|
||||||
|
|
||||||
|
|
||||||
|
@bulb
|
||||||
|
async def test_modify_preset(dev: SmartBulb, mocker):
|
||||||
|
"""Verify that modifying preset calls the and exceptions are raised properly."""
|
||||||
|
if not dev.presets:
|
||||||
|
pytest.skip("Some strips do not support presets")
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"index": 0,
|
||||||
|
"brightness": 10,
|
||||||
|
"hue": 0,
|
||||||
|
"saturation": 0,
|
||||||
|
"color_temp": 0,
|
||||||
|
}
|
||||||
|
preset = SmartBulbPreset(**data)
|
||||||
|
|
||||||
|
assert preset.index == 0
|
||||||
|
assert preset.brightness == 10
|
||||||
|
assert preset.hue == 0
|
||||||
|
assert preset.saturation == 0
|
||||||
|
assert preset.color_temp == 0
|
||||||
|
|
||||||
|
await dev.save_preset(preset)
|
||||||
|
assert dev.presets[0].brightness == 10
|
||||||
|
|
||||||
|
with pytest.raises(SmartDeviceException):
|
||||||
|
await dev.save_preset(
|
||||||
|
SmartBulbPreset(index=5, hue=0, brightness=0, saturation=0, color_temp=0)
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user