mirror of
https://github.com/python-kasa/python-kasa.git
synced 2024-12-22 11:13:34 +00:00
Update cli, light modules, and docs to use FeatureAttributes (#1364)
Some checks are pending
CI / Perform linting checks (3.13) (push) Waiting to run
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, macos-latest, 3.11) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, macos-latest, 3.12) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, macos-latest, 3.13) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, ubuntu-latest, 3.11) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, ubuntu-latest, 3.12) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, ubuntu-latest, 3.13) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, windows-latest, 3.11) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, windows-latest, 3.12) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, windows-latest, 3.13) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (true, ubuntu-latest, 3.11) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (true, ubuntu-latest, 3.12) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (true, ubuntu-latest, 3.13) (push) Blocked by required conditions
CodeQL checks / Analyze (python) (push) Waiting to run
Some checks are pending
CI / Perform linting checks (3.13) (push) Waiting to run
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, macos-latest, 3.11) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, macos-latest, 3.12) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, macos-latest, 3.13) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, ubuntu-latest, 3.11) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, ubuntu-latest, 3.12) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, ubuntu-latest, 3.13) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, windows-latest, 3.11) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, windows-latest, 3.12) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, windows-latest, 3.13) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (true, ubuntu-latest, 3.11) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (true, ubuntu-latest, 3.12) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (true, ubuntu-latest, 3.13) (push) Blocked by required conditions
CodeQL checks / Analyze (python) (push) Waiting to run
This commit is contained in:
parent
f8a46f74cd
commit
7709bb967f
@ -40,7 +40,7 @@ Different groups of functionality are supported by modules which you can access
|
|||||||
key from :class:`~kasa.Module`.
|
key from :class:`~kasa.Module`.
|
||||||
|
|
||||||
Modules will only be available on the device if they are supported but some individual features of a module may not be available for your device.
|
Modules will only be available on the device if they are supported but some individual features of a module may not be available for your device.
|
||||||
You can check the availability using ``is_``-prefixed properties like `is_color`.
|
You can check the availability using ``has_feature()`` method.
|
||||||
|
|
||||||
>>> from kasa import Module
|
>>> from kasa import Module
|
||||||
>>> Module.Light in dev.modules
|
>>> Module.Light in dev.modules
|
||||||
@ -52,9 +52,9 @@ True
|
|||||||
>>> await dev.update()
|
>>> await dev.update()
|
||||||
>>> light.brightness
|
>>> light.brightness
|
||||||
50
|
50
|
||||||
>>> light.is_color
|
>>> light.has_feature("hsv")
|
||||||
True
|
True
|
||||||
>>> if light.is_color:
|
>>> if light.has_feature("hsv"):
|
||||||
>>> print(light.hsv)
|
>>> print(light.hsv)
|
||||||
HSV(hue=0, saturation=100, value=50)
|
HSV(hue=0, saturation=100, value=50)
|
||||||
|
|
||||||
|
@ -25,7 +25,9 @@ def light(dev) -> None:
|
|||||||
@pass_dev_or_child
|
@pass_dev_or_child
|
||||||
async def brightness(dev: Device, brightness: int, transition: int):
|
async def brightness(dev: Device, brightness: int, transition: int):
|
||||||
"""Get or set brightness."""
|
"""Get or set brightness."""
|
||||||
if not (light := dev.modules.get(Module.Light)) or not light.is_dimmable:
|
if not (light := dev.modules.get(Module.Light)) or not light.has_feature(
|
||||||
|
"brightness"
|
||||||
|
):
|
||||||
error("This device does not support brightness.")
|
error("This device does not support brightness.")
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -45,13 +47,15 @@ async def brightness(dev: Device, brightness: int, transition: int):
|
|||||||
@pass_dev_or_child
|
@pass_dev_or_child
|
||||||
async def temperature(dev: Device, temperature: int, transition: int):
|
async def temperature(dev: Device, temperature: int, transition: int):
|
||||||
"""Get or set color temperature."""
|
"""Get or set color temperature."""
|
||||||
if not (light := dev.modules.get(Module.Light)) or not light.is_variable_color_temp:
|
if not (light := dev.modules.get(Module.Light)) or not (
|
||||||
|
color_temp_feat := light.get_feature("color_temp")
|
||||||
|
):
|
||||||
error("Device does not support color temperature")
|
error("Device does not support color temperature")
|
||||||
return
|
return
|
||||||
|
|
||||||
if temperature is None:
|
if temperature is None:
|
||||||
echo(f"Color temperature: {light.color_temp}")
|
echo(f"Color temperature: {light.color_temp}")
|
||||||
valid_temperature_range = light.valid_temperature_range
|
valid_temperature_range = color_temp_feat.range
|
||||||
if valid_temperature_range != (0, 0):
|
if valid_temperature_range != (0, 0):
|
||||||
echo("(min: {}, max: {})".format(*valid_temperature_range))
|
echo("(min: {}, max: {})".format(*valid_temperature_range))
|
||||||
else:
|
else:
|
||||||
@ -59,7 +63,7 @@ async def temperature(dev: Device, temperature: int, transition: int):
|
|||||||
"Temperature range unknown, please open a github issue"
|
"Temperature range unknown, please open a github issue"
|
||||||
f" or a pull request for model '{dev.model}'"
|
f" or a pull request for model '{dev.model}'"
|
||||||
)
|
)
|
||||||
return light.valid_temperature_range
|
return color_temp_feat.range
|
||||||
else:
|
else:
|
||||||
echo(f"Setting color temperature to {temperature}")
|
echo(f"Setting color temperature to {temperature}")
|
||||||
return await light.set_color_temp(temperature, transition=transition)
|
return await light.set_color_temp(temperature, transition=transition)
|
||||||
@ -99,7 +103,7 @@ async def effect(dev: Device, ctx, effect):
|
|||||||
@pass_dev_or_child
|
@pass_dev_or_child
|
||||||
async def hsv(dev: Device, ctx, h, s, v, transition):
|
async def hsv(dev: Device, ctx, h, s, v, transition):
|
||||||
"""Get or set color in HSV."""
|
"""Get or set color in HSV."""
|
||||||
if not (light := dev.modules.get(Module.Light)) or not light.is_color:
|
if not (light := dev.modules.get(Module.Light)) or not light.has_feature("hsv"):
|
||||||
error("Device does not support colors")
|
error("Device does not support colors")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -23,13 +23,13 @@ Get the light module to interact:
|
|||||||
|
|
||||||
>>> light = dev.modules[Module.Light]
|
>>> light = dev.modules[Module.Light]
|
||||||
|
|
||||||
You can use the ``is_``-prefixed properties to check for supported features:
|
You can use the ``has_feature()`` method to check for supported features:
|
||||||
|
|
||||||
>>> light.is_dimmable
|
>>> light.has_feature("brightness")
|
||||||
True
|
True
|
||||||
>>> light.is_color
|
>>> light.has_feature("hsv")
|
||||||
True
|
True
|
||||||
>>> light.is_variable_color_temp
|
>>> light.has_feature("color_temp")
|
||||||
True
|
True
|
||||||
|
|
||||||
All known bulbs support changing the brightness:
|
All known bulbs support changing the brightness:
|
||||||
@ -43,8 +43,9 @@ All known bulbs support changing the brightness:
|
|||||||
|
|
||||||
Bulbs supporting color temperature can be queried for the supported range:
|
Bulbs supporting color temperature can be queried for the supported range:
|
||||||
|
|
||||||
>>> light.valid_temperature_range
|
>>> if color_temp_feature := light.get_feature("color_temp"):
|
||||||
ColorTempRange(min=2500, max=6500)
|
>>> print(f"{color_temp_feature.minimum_value}, {color_temp_feature.maximum_value}")
|
||||||
|
2500, 6500
|
||||||
>>> await light.set_color_temp(3000)
|
>>> await light.set_color_temp(3000)
|
||||||
>>> await dev.update()
|
>>> await dev.update()
|
||||||
>>> light.color_temp
|
>>> light.color_temp
|
||||||
|
@ -13,8 +13,7 @@ Living Room Bulb
|
|||||||
|
|
||||||
Light effects are accessed via the LightPreset module. To list available presets
|
Light effects are accessed via the LightPreset module. To list available presets
|
||||||
|
|
||||||
>>> if dev.modules[Module.Light].has_effects:
|
>>> light_effect = dev.modules[Module.LightEffect]
|
||||||
>>> light_effect = dev.modules[Module.LightEffect]
|
|
||||||
>>> light_effect.effect_list
|
>>> light_effect.effect_list
|
||||||
['Off', 'Party', 'Relax']
|
['Off', 'Party', 'Relax']
|
||||||
|
|
||||||
|
@ -3,13 +3,14 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import asdict
|
from dataclasses import asdict
|
||||||
from typing import TYPE_CHECKING, cast
|
from typing import TYPE_CHECKING, Annotated, cast
|
||||||
|
|
||||||
from ...device_type import DeviceType
|
from ...device_type import DeviceType
|
||||||
from ...exceptions import KasaException
|
from ...exceptions import KasaException
|
||||||
from ...feature import Feature
|
from ...feature import Feature
|
||||||
from ...interfaces.light import HSV, ColorTempRange, LightState
|
from ...interfaces.light import HSV, ColorTempRange, LightState
|
||||||
from ...interfaces.light import Light as LightInterface
|
from ...interfaces.light import Light as LightInterface
|
||||||
|
from ...module import FeatureAttribute
|
||||||
from ..iotmodule import IotModule
|
from ..iotmodule import IotModule
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -32,7 +33,7 @@ class Light(IotModule, LightInterface):
|
|||||||
super()._initialize_features()
|
super()._initialize_features()
|
||||||
device = self._device
|
device = self._device
|
||||||
|
|
||||||
if self._device._is_dimmable:
|
if device._is_dimmable:
|
||||||
self._add_feature(
|
self._add_feature(
|
||||||
Feature(
|
Feature(
|
||||||
device,
|
device,
|
||||||
@ -46,7 +47,7 @@ class Light(IotModule, LightInterface):
|
|||||||
category=Feature.Category.Primary,
|
category=Feature.Category.Primary,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if self._device._is_variable_color_temp:
|
if device._is_variable_color_temp:
|
||||||
self._add_feature(
|
self._add_feature(
|
||||||
Feature(
|
Feature(
|
||||||
device=device,
|
device=device,
|
||||||
@ -60,7 +61,7 @@ class Light(IotModule, LightInterface):
|
|||||||
type=Feature.Type.Number,
|
type=Feature.Type.Number,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if self._device._is_color:
|
if device._is_color:
|
||||||
self._add_feature(
|
self._add_feature(
|
||||||
Feature(
|
Feature(
|
||||||
device=device,
|
device=device,
|
||||||
@ -95,13 +96,13 @@ class Light(IotModule, LightInterface):
|
|||||||
return self._device._is_dimmable
|
return self._device._is_dimmable
|
||||||
|
|
||||||
@property # type: ignore
|
@property # type: ignore
|
||||||
def brightness(self) -> int:
|
def brightness(self) -> Annotated[int, FeatureAttribute()]:
|
||||||
"""Return the current brightness in percentage."""
|
"""Return the current brightness in percentage."""
|
||||||
return self._device._brightness
|
return self._device._brightness
|
||||||
|
|
||||||
async def set_brightness(
|
async def set_brightness(
|
||||||
self, brightness: int, *, transition: int | None = None
|
self, brightness: int, *, transition: int | None = None
|
||||||
) -> dict:
|
) -> Annotated[dict, FeatureAttribute()]:
|
||||||
"""Set the brightness in percentage. A value of 0 will turn off the light.
|
"""Set the brightness in percentage. A value of 0 will turn off the light.
|
||||||
|
|
||||||
:param int brightness: brightness in percent
|
:param int brightness: brightness in percent
|
||||||
@ -133,7 +134,7 @@ class Light(IotModule, LightInterface):
|
|||||||
return bulb._has_effects
|
return bulb._has_effects
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hsv(self) -> HSV:
|
def hsv(self) -> Annotated[HSV, FeatureAttribute()]:
|
||||||
"""Return the current HSV state of the bulb.
|
"""Return the current HSV state of the bulb.
|
||||||
|
|
||||||
:return: hue, saturation and value (degrees, %, %)
|
:return: hue, saturation and value (degrees, %, %)
|
||||||
@ -149,7 +150,7 @@ class Light(IotModule, LightInterface):
|
|||||||
value: int | None = None,
|
value: int | None = None,
|
||||||
*,
|
*,
|
||||||
transition: int | None = None,
|
transition: int | None = None,
|
||||||
) -> dict:
|
) -> Annotated[dict, FeatureAttribute()]:
|
||||||
"""Set new HSV.
|
"""Set new HSV.
|
||||||
|
|
||||||
Note, transition is not supported and will be ignored.
|
Note, transition is not supported and will be ignored.
|
||||||
@ -176,7 +177,7 @@ class Light(IotModule, LightInterface):
|
|||||||
return bulb._valid_temperature_range
|
return bulb._valid_temperature_range
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def color_temp(self) -> int:
|
def color_temp(self) -> Annotated[int, FeatureAttribute()]:
|
||||||
"""Whether the bulb supports color temperature changes."""
|
"""Whether the bulb supports color temperature changes."""
|
||||||
if (
|
if (
|
||||||
bulb := self._get_bulb_device()
|
bulb := self._get_bulb_device()
|
||||||
@ -186,7 +187,7 @@ class Light(IotModule, LightInterface):
|
|||||||
|
|
||||||
async def set_color_temp(
|
async def set_color_temp(
|
||||||
self, temp: int, *, brightness: int | None = None, transition: int | None = None
|
self, temp: int, *, brightness: int | None = None, transition: int | None = None
|
||||||
) -> dict:
|
) -> Annotated[dict, FeatureAttribute()]:
|
||||||
"""Set the color temperature of the device in kelvin.
|
"""Set the color temperature of the device in kelvin.
|
||||||
|
|
||||||
Note, transition is not supported and will be ignored.
|
Note, transition is not supported and will be ignored.
|
||||||
@ -242,17 +243,18 @@ class Light(IotModule, LightInterface):
|
|||||||
return self._light_state
|
return self._light_state
|
||||||
|
|
||||||
async def _post_update_hook(self) -> None:
|
async def _post_update_hook(self) -> None:
|
||||||
if self._device.is_on is False:
|
device = self._device
|
||||||
|
if device.is_on is False:
|
||||||
state = LightState(light_on=False)
|
state = LightState(light_on=False)
|
||||||
else:
|
else:
|
||||||
state = LightState(light_on=True)
|
state = LightState(light_on=True)
|
||||||
if self.is_dimmable:
|
if device._is_dimmable:
|
||||||
state.brightness = self.brightness
|
state.brightness = self.brightness
|
||||||
if self.is_color:
|
if device._is_color:
|
||||||
hsv = self.hsv
|
hsv = self.hsv
|
||||||
state.hue = hsv.hue
|
state.hue = hsv.hue
|
||||||
state.saturation = hsv.saturation
|
state.saturation = hsv.saturation
|
||||||
if self.is_variable_color_temp:
|
if device._is_variable_color_temp:
|
||||||
state.color_temp = self.color_temp
|
state.color_temp = self.color_temp
|
||||||
self._light_state = state
|
self._light_state = state
|
||||||
|
|
||||||
|
@ -85,17 +85,19 @@ class LightPreset(IotModule, LightPresetInterface):
|
|||||||
def preset(self) -> str:
|
def preset(self) -> str:
|
||||||
"""Return current preset name."""
|
"""Return current preset name."""
|
||||||
light = self._device.modules[Module.Light]
|
light = self._device.modules[Module.Light]
|
||||||
|
is_color = light.has_feature("hsv")
|
||||||
|
is_variable_color_temp = light.has_feature("color_temp")
|
||||||
|
|
||||||
brightness = light.brightness
|
brightness = light.brightness
|
||||||
color_temp = light.color_temp if light.is_variable_color_temp else None
|
color_temp = light.color_temp if is_variable_color_temp else None
|
||||||
h, s = (light.hsv.hue, light.hsv.saturation) if light.is_color else (None, None)
|
|
||||||
|
h, s = (light.hsv.hue, light.hsv.saturation) if is_color else (None, None)
|
||||||
for preset_name, preset in self._presets.items():
|
for preset_name, preset in self._presets.items():
|
||||||
if (
|
if (
|
||||||
preset.brightness == brightness
|
preset.brightness == brightness
|
||||||
and (
|
and (preset.color_temp == color_temp or not is_variable_color_temp)
|
||||||
preset.color_temp == color_temp or not light.is_variable_color_temp
|
and (preset.hue == h or not is_color)
|
||||||
)
|
and (preset.saturation == s or not is_color)
|
||||||
and (preset.hue == h or not light.is_color)
|
|
||||||
and (preset.saturation == s or not light.is_color)
|
|
||||||
):
|
):
|
||||||
return preset_name
|
return preset_name
|
||||||
return self.PRESET_NOT_SET
|
return self.PRESET_NOT_SET
|
||||||
@ -107,7 +109,7 @@ class LightPreset(IotModule, LightPresetInterface):
|
|||||||
"""Set a light preset for the device."""
|
"""Set a light preset for the device."""
|
||||||
light = self._device.modules[Module.Light]
|
light = self._device.modules[Module.Light]
|
||||||
if preset_name == self.PRESET_NOT_SET:
|
if preset_name == self.PRESET_NOT_SET:
|
||||||
if light.is_color:
|
if light.has_feature("hsv"):
|
||||||
preset = LightState(hue=0, saturation=0, brightness=100)
|
preset = LightState(hue=0, saturation=0, brightness=100)
|
||||||
else:
|
else:
|
||||||
preset = LightState(brightness=100)
|
preset = LightState(brightness=100)
|
||||||
|
@ -55,7 +55,7 @@ class Light(SmartModule, LightInterface):
|
|||||||
|
|
||||||
:return: White temperature range in Kelvin (minimum, maximum)
|
:return: White temperature range in Kelvin (minimum, maximum)
|
||||||
"""
|
"""
|
||||||
if not self.is_variable_color_temp:
|
if Module.ColorTemperature not in self._device.modules:
|
||||||
raise KasaException("Color temperature not supported")
|
raise KasaException("Color temperature not supported")
|
||||||
|
|
||||||
return self._device.modules[Module.ColorTemperature].valid_temperature_range
|
return self._device.modules[Module.ColorTemperature].valid_temperature_range
|
||||||
@ -66,7 +66,7 @@ class Light(SmartModule, LightInterface):
|
|||||||
|
|
||||||
:return: hue, saturation and value (degrees, %, %)
|
:return: hue, saturation and value (degrees, %, %)
|
||||||
"""
|
"""
|
||||||
if not self.is_color:
|
if Module.Color not in self._device.modules:
|
||||||
raise KasaException("Bulb does not support color.")
|
raise KasaException("Bulb does not support color.")
|
||||||
|
|
||||||
return self._device.modules[Module.Color].hsv
|
return self._device.modules[Module.Color].hsv
|
||||||
@ -74,7 +74,7 @@ class Light(SmartModule, LightInterface):
|
|||||||
@property
|
@property
|
||||||
def color_temp(self) -> Annotated[int, FeatureAttribute()]:
|
def color_temp(self) -> Annotated[int, FeatureAttribute()]:
|
||||||
"""Whether the bulb supports color temperature changes."""
|
"""Whether the bulb supports color temperature changes."""
|
||||||
if not self.is_variable_color_temp:
|
if Module.ColorTemperature not in self._device.modules:
|
||||||
raise KasaException("Bulb does not support colortemp.")
|
raise KasaException("Bulb does not support colortemp.")
|
||||||
|
|
||||||
return self._device.modules[Module.ColorTemperature].color_temp
|
return self._device.modules[Module.ColorTemperature].color_temp
|
||||||
@ -82,7 +82,7 @@ class Light(SmartModule, LightInterface):
|
|||||||
@property
|
@property
|
||||||
def brightness(self) -> Annotated[int, FeatureAttribute()]:
|
def brightness(self) -> Annotated[int, FeatureAttribute()]:
|
||||||
"""Return the current brightness in percentage."""
|
"""Return the current brightness in percentage."""
|
||||||
if not self.is_dimmable: # pragma: no cover
|
if Module.Brightness not in self._device.modules:
|
||||||
raise KasaException("Bulb is not dimmable.")
|
raise KasaException("Bulb is not dimmable.")
|
||||||
|
|
||||||
return self._device.modules[Module.Brightness].brightness
|
return self._device.modules[Module.Brightness].brightness
|
||||||
@ -104,7 +104,7 @@ class Light(SmartModule, LightInterface):
|
|||||||
:param int value: value between 1 and 100
|
:param int value: value between 1 and 100
|
||||||
:param int transition: transition in milliseconds.
|
:param int transition: transition in milliseconds.
|
||||||
"""
|
"""
|
||||||
if not self.is_color:
|
if Module.Color not in self._device.modules:
|
||||||
raise KasaException("Bulb does not support color.")
|
raise KasaException("Bulb does not support color.")
|
||||||
|
|
||||||
return await self._device.modules[Module.Color].set_hsv(hue, saturation, value)
|
return await self._device.modules[Module.Color].set_hsv(hue, saturation, value)
|
||||||
@ -119,7 +119,7 @@ class Light(SmartModule, LightInterface):
|
|||||||
:param int temp: The new color temperature, in Kelvin
|
:param int temp: The new color temperature, in Kelvin
|
||||||
:param int transition: transition in milliseconds.
|
:param int transition: transition in milliseconds.
|
||||||
"""
|
"""
|
||||||
if not self.is_variable_color_temp:
|
if Module.ColorTemperature not in self._device.modules:
|
||||||
raise KasaException("Bulb does not support colortemp.")
|
raise KasaException("Bulb does not support colortemp.")
|
||||||
return await self._device.modules[Module.ColorTemperature].set_color_temp(
|
return await self._device.modules[Module.ColorTemperature].set_color_temp(
|
||||||
temp, brightness=brightness
|
temp, brightness=brightness
|
||||||
@ -135,7 +135,7 @@ class Light(SmartModule, LightInterface):
|
|||||||
:param int brightness: brightness in percent
|
:param int brightness: brightness in percent
|
||||||
:param int transition: transition in milliseconds.
|
:param int transition: transition in milliseconds.
|
||||||
"""
|
"""
|
||||||
if not self.is_dimmable: # pragma: no cover
|
if Module.Brightness not in self._device.modules:
|
||||||
raise KasaException("Bulb is not dimmable.")
|
raise KasaException("Bulb is not dimmable.")
|
||||||
|
|
||||||
return await self._device.modules[Module.Brightness].set_brightness(brightness)
|
return await self._device.modules[Module.Brightness].set_brightness(brightness)
|
||||||
@ -167,16 +167,17 @@ class Light(SmartModule, LightInterface):
|
|||||||
return self._light_state
|
return self._light_state
|
||||||
|
|
||||||
async def _post_update_hook(self) -> None:
|
async def _post_update_hook(self) -> None:
|
||||||
if self._device.is_on is False:
|
device = self._device
|
||||||
|
if device.is_on is False:
|
||||||
state = LightState(light_on=False)
|
state = LightState(light_on=False)
|
||||||
else:
|
else:
|
||||||
state = LightState(light_on=True)
|
state = LightState(light_on=True)
|
||||||
if self.is_dimmable:
|
if Module.Brightness in device.modules:
|
||||||
state.brightness = self.brightness
|
state.brightness = self.brightness
|
||||||
if self.is_color:
|
if Module.Color in device.modules:
|
||||||
hsv = self.hsv
|
hsv = self.hsv
|
||||||
state.hue = hsv.hue
|
state.hue = hsv.hue
|
||||||
state.saturation = hsv.saturation
|
state.saturation = hsv.saturation
|
||||||
if self.is_variable_color_temp:
|
if Module.ColorTemperature in device.modules:
|
||||||
state.color_temp = self.color_temp
|
state.color_temp = self.color_temp
|
||||||
self._light_state = state
|
self._light_state = state
|
||||||
|
@ -96,13 +96,18 @@ class LightPreset(SmartModule, LightPresetInterface):
|
|||||||
"""Return current preset name."""
|
"""Return current preset name."""
|
||||||
light = self._device.modules[SmartModule.Light]
|
light = self._device.modules[SmartModule.Light]
|
||||||
brightness = light.brightness
|
brightness = light.brightness
|
||||||
color_temp = light.color_temp if light.is_variable_color_temp else None
|
color_temp = light.color_temp if light.has_feature("color_temp") else None
|
||||||
h, s = (light.hsv.hue, light.hsv.saturation) if light.is_color else (None, None)
|
h, s = (
|
||||||
|
(light.hsv.hue, light.hsv.saturation)
|
||||||
|
if light.has_feature("hsv")
|
||||||
|
else (None, None)
|
||||||
|
)
|
||||||
for preset_name, preset in self._presets.items():
|
for preset_name, preset in self._presets.items():
|
||||||
if (
|
if (
|
||||||
preset.brightness == brightness
|
preset.brightness == brightness
|
||||||
and (
|
and (
|
||||||
preset.color_temp == color_temp or not light.is_variable_color_temp
|
preset.color_temp == color_temp
|
||||||
|
or not light.has_feature("color_temp")
|
||||||
)
|
)
|
||||||
and preset.hue == h
|
and preset.hue == h
|
||||||
and preset.saturation == s
|
and preset.saturation == s
|
||||||
@ -117,7 +122,7 @@ class LightPreset(SmartModule, LightPresetInterface):
|
|||||||
"""Set a light preset for the device."""
|
"""Set a light preset for the device."""
|
||||||
light = self._device.modules[SmartModule.Light]
|
light = self._device.modules[SmartModule.Light]
|
||||||
if preset_name == self.PRESET_NOT_SET:
|
if preset_name == self.PRESET_NOT_SET:
|
||||||
if light.is_color:
|
if light.has_feature("hsv"):
|
||||||
preset = LightState(hue=0, saturation=0, brightness=100)
|
preset = LightState(hue=0, saturation=0, brightness=100)
|
||||||
else:
|
else:
|
||||||
preset = LightState(brightness=100)
|
preset = LightState(brightness=100)
|
||||||
|
@ -91,7 +91,9 @@ async def test_unknown_temp_range(dev: IotBulb, monkeypatch, caplog):
|
|||||||
monkeypatch.setitem(dev._sys_info, "model", "unknown bulb")
|
monkeypatch.setitem(dev._sys_info, "model", "unknown bulb")
|
||||||
light = dev.modules.get(Module.Light)
|
light = dev.modules.get(Module.Light)
|
||||||
assert light
|
assert light
|
||||||
assert light.valid_temperature_range == (2700, 5000)
|
color_temp_feat = light.get_feature("color_temp")
|
||||||
|
assert color_temp_feat
|
||||||
|
assert color_temp_feat.range == (2700, 5000)
|
||||||
assert "Unknown color temperature range, fallback to 2700-5000" in caplog.text
|
assert "Unknown color temperature range, fallback to 2700-5000" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
@ -469,7 +469,9 @@ async def test_smartdevice_cloud_connection(dev: SmartDevice, mocker: MockerFixt
|
|||||||
async def test_smart_temp_range(dev: Device):
|
async def test_smart_temp_range(dev: Device):
|
||||||
light = dev.modules.get(Module.Light)
|
light = dev.modules.get(Module.Light)
|
||||||
assert light
|
assert light
|
||||||
assert light.valid_temperature_range
|
color_temp_feat = light.get_feature("color_temp")
|
||||||
|
assert color_temp_feat
|
||||||
|
assert color_temp_feat.range
|
||||||
|
|
||||||
|
|
||||||
@device_smart
|
@device_smart
|
||||||
|
@ -25,7 +25,7 @@ async def test_hsv(dev: Device, turn_on):
|
|||||||
light = dev.modules.get(Module.Light)
|
light = dev.modules.get(Module.Light)
|
||||||
assert light
|
assert light
|
||||||
await handle_turn_on(dev, turn_on)
|
await handle_turn_on(dev, turn_on)
|
||||||
assert light.is_color
|
assert light.has_feature("hsv")
|
||||||
|
|
||||||
hue, saturation, brightness = light.hsv
|
hue, saturation, brightness = light.hsv
|
||||||
assert 0 <= hue <= 360
|
assert 0 <= hue <= 360
|
||||||
@ -106,7 +106,7 @@ async def test_invalid_hsv(
|
|||||||
light = dev.modules.get(Module.Light)
|
light = dev.modules.get(Module.Light)
|
||||||
assert light
|
assert light
|
||||||
await handle_turn_on(dev, turn_on)
|
await handle_turn_on(dev, turn_on)
|
||||||
assert light.is_color
|
assert light.has_feature("hsv")
|
||||||
with pytest.raises(exception_cls, match=error):
|
with pytest.raises(exception_cls, match=error):
|
||||||
await light.set_hsv(hue, sat, brightness)
|
await light.set_hsv(hue, sat, brightness)
|
||||||
|
|
||||||
@ -124,7 +124,7 @@ async def test_color_state_information(dev: Device):
|
|||||||
async def test_hsv_on_non_color(dev: Device):
|
async def test_hsv_on_non_color(dev: Device):
|
||||||
light = dev.modules.get(Module.Light)
|
light = dev.modules.get(Module.Light)
|
||||||
assert light
|
assert light
|
||||||
assert not light.is_color
|
assert not light.has_feature("hsv")
|
||||||
|
|
||||||
with pytest.raises(KasaException):
|
with pytest.raises(KasaException):
|
||||||
await light.set_hsv(0, 0, 0)
|
await light.set_hsv(0, 0, 0)
|
||||||
@ -173,9 +173,6 @@ async def test_non_variable_temp(dev: Device):
|
|||||||
with pytest.raises(KasaException):
|
with pytest.raises(KasaException):
|
||||||
await light.set_color_temp(2700)
|
await light.set_color_temp(2700)
|
||||||
|
|
||||||
with pytest.raises(KasaException):
|
|
||||||
print(light.valid_temperature_range)
|
|
||||||
|
|
||||||
with pytest.raises(KasaException):
|
with pytest.raises(KasaException):
|
||||||
print(light.color_temp)
|
print(light.color_temp)
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ from pytest_mock import MockerFixture
|
|||||||
|
|
||||||
from kasa import (
|
from kasa import (
|
||||||
AuthenticationError,
|
AuthenticationError,
|
||||||
|
ColorTempRange,
|
||||||
Credentials,
|
Credentials,
|
||||||
Device,
|
Device,
|
||||||
DeviceError,
|
DeviceError,
|
||||||
@ -523,7 +524,9 @@ async def test_emeter(dev: Device, mocker, runner):
|
|||||||
|
|
||||||
async def test_brightness(dev: Device, runner):
|
async def test_brightness(dev: Device, runner):
|
||||||
res = await runner.invoke(brightness, obj=dev)
|
res = await runner.invoke(brightness, obj=dev)
|
||||||
if not (light := dev.modules.get(Module.Light)) or not light.is_dimmable:
|
if not (light := dev.modules.get(Module.Light)) or not light.has_feature(
|
||||||
|
"brightness"
|
||||||
|
):
|
||||||
assert "This device does not support brightness." in res.output
|
assert "This device does not support brightness." in res.output
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -540,13 +543,16 @@ async def test_brightness(dev: Device, runner):
|
|||||||
|
|
||||||
async def test_color_temperature(dev: Device, runner):
|
async def test_color_temperature(dev: Device, runner):
|
||||||
res = await runner.invoke(temperature, obj=dev)
|
res = await runner.invoke(temperature, obj=dev)
|
||||||
if not (light := dev.modules.get(Module.Light)) or not light.is_variable_color_temp:
|
if not (light := dev.modules.get(Module.Light)) or not (
|
||||||
|
color_temp_feat := light.get_feature("color_temp")
|
||||||
|
):
|
||||||
assert "Device does not support color temperature" in res.output
|
assert "Device does not support color temperature" in res.output
|
||||||
return
|
return
|
||||||
|
|
||||||
res = await runner.invoke(temperature, obj=dev)
|
res = await runner.invoke(temperature, obj=dev)
|
||||||
assert f"Color temperature: {light.color_temp}" in res.output
|
assert f"Color temperature: {light.color_temp}" in res.output
|
||||||
valid_range = light.valid_temperature_range
|
valid_range = color_temp_feat.range
|
||||||
|
assert isinstance(valid_range, ColorTempRange)
|
||||||
assert f"(min: {valid_range.min}, max: {valid_range.max})" in res.output
|
assert f"(min: {valid_range.min}, max: {valid_range.max})" in res.output
|
||||||
|
|
||||||
val = int((valid_range.min + valid_range.max) / 2)
|
val = int((valid_range.min + valid_range.max) / 2)
|
||||||
@ -572,7 +578,7 @@ async def test_color_temperature(dev: Device, runner):
|
|||||||
|
|
||||||
async def test_color_hsv(dev: Device, runner: CliRunner):
|
async def test_color_hsv(dev: Device, runner: CliRunner):
|
||||||
res = await runner.invoke(hsv, obj=dev)
|
res = await runner.invoke(hsv, obj=dev)
|
||||||
if not (light := dev.modules.get(Module.Light)) or not light.is_color:
|
if not (light := dev.modules.get(Module.Light)) or not light.has_feature("hsv"):
|
||||||
assert "Device does not support colors" in res.output
|
assert "Device does not support colors" in res.output
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -198,7 +198,7 @@ async def test_light_color_temp(dev: Device):
|
|||||||
|
|
||||||
light = next(get_parent_and_child_modules(dev, Module.Light))
|
light = next(get_parent_and_child_modules(dev, Module.Light))
|
||||||
assert light
|
assert light
|
||||||
if not light.is_variable_color_temp:
|
if not light.has_feature("color_temp"):
|
||||||
pytest.skip(
|
pytest.skip(
|
||||||
"Some smart light strips have color_temperature"
|
"Some smart light strips have color_temperature"
|
||||||
" component but min and max are the same"
|
" component but min and max are the same"
|
||||||
|
@ -280,19 +280,19 @@ async def test_deprecated_light_attributes(dev: Device):
|
|||||||
await _test_attribute(dev, "is_color", bool(light), "Light")
|
await _test_attribute(dev, "is_color", bool(light), "Light")
|
||||||
await _test_attribute(dev, "is_variable_color_temp", bool(light), "Light")
|
await _test_attribute(dev, "is_variable_color_temp", bool(light), "Light")
|
||||||
|
|
||||||
exc = KasaException if light and not light.is_dimmable else None
|
exc = KasaException if light and not light.has_feature("brightness") else None
|
||||||
await _test_attribute(dev, "brightness", bool(light), "Light", will_raise=exc)
|
await _test_attribute(dev, "brightness", bool(light), "Light", will_raise=exc)
|
||||||
await _test_attribute(
|
await _test_attribute(
|
||||||
dev, "set_brightness", bool(light), "Light", 50, will_raise=exc
|
dev, "set_brightness", bool(light), "Light", 50, will_raise=exc
|
||||||
)
|
)
|
||||||
|
|
||||||
exc = KasaException if light and not light.is_color else None
|
exc = KasaException if light and not light.has_feature("hsv") else None
|
||||||
await _test_attribute(dev, "hsv", bool(light), "Light", will_raise=exc)
|
await _test_attribute(dev, "hsv", bool(light), "Light", will_raise=exc)
|
||||||
await _test_attribute(
|
await _test_attribute(
|
||||||
dev, "set_hsv", bool(light), "Light", 50, 50, 50, will_raise=exc
|
dev, "set_hsv", bool(light), "Light", 50, 50, 50, will_raise=exc
|
||||||
)
|
)
|
||||||
|
|
||||||
exc = KasaException if light and not light.is_variable_color_temp else None
|
exc = KasaException if light and not light.has_feature("color_temp") else None
|
||||||
await _test_attribute(dev, "color_temp", bool(light), "Light", will_raise=exc)
|
await _test_attribute(dev, "color_temp", bool(light), "Light", will_raise=exc)
|
||||||
await _test_attribute(
|
await _test_attribute(
|
||||||
dev, "set_color_temp", bool(light), "Light", 2700, will_raise=exc
|
dev, "set_color_temp", bool(light), "Light", 2700, will_raise=exc
|
||||||
|
Loading…
Reference in New Issue
Block a user