| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 989c0da commit 68debc2
14 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -40,7 +40,7 @@ | |||
| 40 | 40 | key from :class:`~kasa.Module`. | |
| 41 | 41 | ||
| 42 | 42 | 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. | |
| 43 | - You can check the availability using ``is_``-prefixed properties like `is_color`. | ||
| 43 | + You can check the availability using ``has_feature()`` method. | ||
| 44 | 44 | ||
| 45 | 45 | >>> from kasa import Module | |
| 46 | 46 | >>> Module.Light in dev.modules | |
@@ -52,9 +52,9 @@ | |||
| 52 | 52 | >>> await dev.update() | |
| 53 | 53 | >>> light.brightness | |
| 54 | 54 | 50 | |
| 55 | - >>> light.is_color | ||
| 55 | + >>> light.has_feature("hsv") | ||
| 56 | 56 | True | |
| 57 | - >>> if light.is_color: | ||
| 57 | + >>> if light.has_feature("hsv"): | ||
| 58 | 58 | >>> print(light.hsv) | |
| 59 | 59 | HSV(hue=0, saturation=100, value=50) | |
| 60 | 60 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -25,7 +25,9 @@ def light(dev) -> None: | |||
| 25 | 25 | @pass_dev_or_child | |
| 26 | 26 | async def brightness(dev: Device, brightness: int, transition: int): | |
| 27 | 27 | """Get or set brightness.""" | |
| 28 | - if not (light := dev.modules.get(Module.Light)) or not light.is_dimmable: | ||
| 28 | + if not (light := dev.modules.get(Module.Light)) or not light.has_feature( | ||
| 29 | + "brightness" | ||
| 30 | + ): | ||
| 29 | 31 | error("This device does not support brightness.") | |
| 30 | 32 | return | |
| 31 | 33 | ||
@@ -45,21 +47,23 @@ async def brightness(dev: Device, brightness: int, transition: int): | |||
| 45 | 47 | @pass_dev_or_child | |
| 46 | 48 | async def temperature(dev: Device, temperature: int, transition: int): | |
| 47 | 49 | """Get or set color temperature.""" | |
| 48 | - if not (light := dev.modules.get(Module.Light)) or not light.is_variable_color_temp: | ||
| 50 | + if not (light := dev.modules.get(Module.Light)) or not ( | ||
| 51 | + color_temp_feat := light.get_feature("color_temp") | ||
| 52 | + ): | ||
| 49 | 53 | error("Device does not support color temperature") | |
| 50 | 54 | return | |
| 51 | 55 | ||
| 52 | 56 | if temperature is None: | |
| 53 | 57 | echo(f"Color temperature: {light.color_temp}") | |
| 54 | - valid_temperature_range = light.valid_temperature_range | ||
| 58 | + valid_temperature_range = color_temp_feat.range | ||
| 55 | 59 | if valid_temperature_range != (0, 0): | |
| 56 | 60 | echo("(min: {}, max: {})".format(*valid_temperature_range)) | |
| 57 | 61 | else: | |
| 58 | 62 | echo( | |
| 59 | 63 | "Temperature range unknown, please open a github issue" | |
| 60 | 64 | f" or a pull request for model '{dev.model}'" | |
| 61 | 65 | ) | |
| 62 | - return light.valid_temperature_range | ||
| 66 | + return color_temp_feat.range | ||
| 63 | 67 | else: | |
| 64 | 68 | echo(f"Setting color temperature to {temperature}") | |
| 65 | 69 | return await light.set_color_temp(temperature, transition=transition) | |
@@ -99,7 +103,7 @@ async def effect(dev: Device, ctx, effect): | |||
| 99 | 103 | @pass_dev_or_child | |
| 100 | 104 | async def hsv(dev: Device, ctx, h, s, v, transition): | |
| 101 | 105 | """Get or set color in HSV.""" | |
| 102 | - if not (light := dev.modules.get(Module.Light)) or not light.is_color: | ||
| 106 | + if not (light := dev.modules.get(Module.Light)) or not light.has_feature("hsv"): | ||
| 103 | 107 | error("Device does not support colors") | |
| 104 | 108 | return | |
| 105 | 109 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -23,13 +23,13 @@ | |||
| 23 | 23 | ||
| 24 | 24 | >>> light = dev.modules[Module.Light] | |
| 25 | 25 | ||
| 26 | - You can use the ``is_``-prefixed properties to check for supported features: | ||
| 26 | + You can use the ``has_feature()`` method to check for supported features: | ||
| 27 | 27 | ||
| 28 | - >>> light.is_dimmable | ||
| 28 | + >>> light.has_feature("brightness") | ||
| 29 | 29 | True | |
| 30 | - >>> light.is_color | ||
| 30 | + >>> light.has_feature("hsv") | ||
| 31 | 31 | True | |
| 32 | - >>> light.is_variable_color_temp | ||
| 32 | + >>> light.has_feature("color_temp") | ||
| 33 | 33 | True | |
| 34 | 34 | ||
| 35 | 35 | All known bulbs support changing the brightness: | |
@@ -43,8 +43,9 @@ | |||
| 43 | 43 | ||
| 44 | 44 | Bulbs supporting color temperature can be queried for the supported range: | |
| 45 | 45 | ||
| 46 | - >>> light.valid_temperature_range | ||
| 47 | - ColorTempRange(min=2500, max=6500) | ||
| 46 | + >>> if color_temp_feature := light.get_feature("color_temp"): | ||
| 47 | + >>> print(f"{color_temp_feature.minimum_value}, {color_temp_feature.maximum_value}") | ||
| 48 | + 2500, 6500 | ||
| 48 | 49 | >>> await light.set_color_temp(3000) | |
| 49 | 50 | >>> await dev.update() | |
| 50 | 51 | >>> light.color_temp | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -13,8 +13,7 @@ | |||
| 13 | 13 | ||
| 14 | 14 | Light effects are accessed via the LightPreset module. To list available presets | |
| 15 | 15 | ||
| 16 | - >>> if dev.modules[Module.Light].has_effects: | ||
| 17 | - >>> light_effect = dev.modules[Module.LightEffect] | ||
| 16 | + >>> light_effect = dev.modules[Module.LightEffect] | ||
| 18 | 17 | >>> light_effect.effect_list | |
| 19 | 18 | ['Off', 'Party', 'Relax'] | |
| 20 | 19 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3,13 +3,14 @@ | |||
| 3 | 3 | from __future__ import annotations | |
| 4 | 4 | ||
| 5 | 5 | from dataclasses import asdict | |
| 6 | - from typing import TYPE_CHECKING, cast | ||
| 6 | + from typing import TYPE_CHECKING, Annotated, cast | ||
| 7 | 7 | ||
| 8 | 8 | from ...device_type import DeviceType | |
| 9 | 9 | from ...exceptions import KasaException | |
| 10 | 10 | from ...feature import Feature | |
| 11 | 11 | from ...interfaces.light import HSV, ColorTempRange, LightState | |
| 12 | 12 | from ...interfaces.light import Light as LightInterface | |
| 13 | + from ...module import FeatureAttribute | ||
| 13 | 14 | from ..iotmodule import IotModule | |
| 14 | 15 | ||
| 15 | 16 | if TYPE_CHECKING: | |
@@ -32,7 +33,7 @@ def _initialize_features(self) -> None: | |||
| 32 | 33 | super()._initialize_features() | |
| 33 | 34 | device = self._device | |
| 34 | 35 | ||
| 35 | - if self._device._is_dimmable: | ||
| 36 | + if device._is_dimmable: | ||
| 36 | 37 | self._add_feature( | |
| 37 | 38 | Feature( | |
| 38 | 39 | device, | |
@@ -46,7 +47,7 @@ def _initialize_features(self) -> None: | |||
| 46 | 47 | category=Feature.Category.Primary, | |
| 47 | 48 | ) | |
| 48 | 49 | ) | |
| 49 | - if self._device._is_variable_color_temp: | ||
| 50 | + if device._is_variable_color_temp: | ||
| 50 | 51 | self._add_feature( | |
| 51 | 52 | Feature( | |
| 52 | 53 | device=device, | |
@@ -60,7 +61,7 @@ def _initialize_features(self) -> None: | |||
| 60 | 61 | type=Feature.Type.Number, | |
| 61 | 62 | ) | |
| 62 | 63 | ) | |
| 63 | - if self._device._is_color: | ||
| 64 | + if device._is_color: | ||
| 64 | 65 | self._add_feature( | |
| 65 | 66 | Feature( | |
| 66 | 67 | device=device, | |
@@ -95,13 +96,13 @@ def is_dimmable(self) -> int: | |||
| 95 | 96 | return self._device._is_dimmable | |
| 96 | 97 | ||
| 97 | 98 | @property # type: ignore | |
| 98 | - def brightness(self) -> int: | ||
| 99 | + def brightness(self) -> Annotated[int, FeatureAttribute()]: | ||
| 99 | 100 | """Return the current brightness in percentage.""" | |
| 100 | 101 | return self._device._brightness | |
| 101 | 102 | ||
| 102 | 103 | async def set_brightness( | |
| 103 | 104 | self, brightness: int, *, transition: int | None = None | |
| 104 | - ) -> dict: | ||
| 105 | + ) -> Annotated[dict, FeatureAttribute()]: | ||
| 105 | 106 | """Set the brightness in percentage. A value of 0 will turn off the light. | |
| 106 | 107 | ||
| 107 | 108 | :param int brightness: brightness in percent | |
@@ -133,7 +134,7 @@ def has_effects(self) -> bool: | |||
| 133 | 134 | return bulb._has_effects | |
| 134 | 135 | ||
| 135 | 136 | @property | |
| 136 | - def hsv(self) -> HSV: | ||
| 137 | + def hsv(self) -> Annotated[HSV, FeatureAttribute()]: | ||
| 137 | 138 | """Return the current HSV state of the bulb. | |
| 138 | 139 | ||
| 139 | 140 | :return: hue, saturation and value (degrees, %, %) | |
@@ -149,7 +150,7 @@ async def set_hsv( | |||
| 149 | 150 | value: int | None = None, | |
| 150 | 151 | *, | |
| 151 | 152 | transition: int | None = None, | |
| 152 | - ) -> dict: | ||
| 153 | + ) -> Annotated[dict, FeatureAttribute()]: | ||
| 153 | 154 | """Set new HSV. | |
| 154 | 155 | ||
| 155 | 156 | Note, transition is not supported and will be ignored. | |
@@ -176,7 +177,7 @@ def valid_temperature_range(self) -> ColorTempRange: | |||
| 176 | 177 | return bulb._valid_temperature_range | |
| 177 | 178 | ||
| 178 | 179 | @property | |
| 179 | - def color_temp(self) -> int: | ||
| 180 | + def color_temp(self) -> Annotated[int, FeatureAttribute()]: | ||
| 180 | 181 | """Whether the bulb supports color temperature changes.""" | |
| 181 | 182 | if ( | |
| 182 | 183 | bulb := self._get_bulb_device() | |
@@ -186,7 +187,7 @@ def color_temp(self) -> int: | |||
| 186 | 187 | ||
| 187 | 188 | async def set_color_temp( | |
| 188 | 189 | self, temp: int, *, brightness: int | None = None, transition: int | None = None | |
| 189 | - ) -> dict: | ||
| 190 | + ) -> Annotated[dict, FeatureAttribute()]: | ||
| 190 | 191 | """Set the color temperature of the device in kelvin. | |
| 191 | 192 | ||
| 192 | 193 | Note, transition is not supported and will be ignored. | |
@@ -242,17 +243,18 @@ def state(self) -> LightState: | |||
| 242 | 243 | return self._light_state | |
| 243 | 244 | ||
| 244 | 245 | async def _post_update_hook(self) -> None: | |
| 245 | - if self._device.is_on is False: | ||
| 246 | + device = self._device | ||
| 247 | + if device.is_on is False: | ||
| 246 | 248 | state = LightState(light_on=False) | |
| 247 | 249 | else: | |
| 248 | 250 | state = LightState(light_on=True) | |
| 249 | - if self.is_dimmable: | ||
| 251 | + if device._is_dimmable: | ||
| 250 | 252 | state.brightness = self.brightness | |
| 251 | - if self.is_color: | ||
| 253 | + if device._is_color: | ||
| 252 | 254 | hsv = self.hsv | |
| 253 | 255 | state.hue = hsv.hue | |
| 254 | 256 | state.saturation = hsv.saturation | |
| 255 | - if self.is_variable_color_temp: | ||
| 257 | + if device._is_variable_color_temp: | ||
| 256 | 258 | state.color_temp = self.color_temp | |
| 257 | 259 | self._light_state = state | |
| 258 | 260 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -85,17 +85,19 @@ def preset_states_list(self) -> Sequence[IotLightPreset]: | |||
| 85 | 85 | def preset(self) -> str: | |
| 86 | 86 | """Return current preset name.""" | |
| 87 | 87 | light = self._device.modules[Module.Light] | |
| 88 | + is_color = light.has_feature("hsv") | ||
| 89 | + is_variable_color_temp = light.has_feature("color_temp") | ||
| 90 | + | ||
| 88 | 91 | brightness = light.brightness | |
| 89 | - color_temp = light.color_temp if light.is_variable_color_temp else None | ||
| 90 | - h, s = (light.hsv.hue, light.hsv.saturation) if light.is_color else (None, None) | ||
| 92 | + color_temp = light.color_temp if is_variable_color_temp else None | ||
| 93 | + | ||
| 94 | + h, s = (light.hsv.hue, light.hsv.saturation) if is_color else (None, None) | ||
| 91 | 95 | for preset_name, preset in self._presets.items(): | |
| 92 | 96 | if ( | |
| 93 | 97 | preset.brightness == brightness | |
| 94 | - and ( | ||
| 95 | - preset.color_temp == color_temp or not light.is_variable_color_temp | ||
| 96 | - ) | ||
| 97 | - and (preset.hue == h or not light.is_color) | ||
| 98 | - and (preset.saturation == s or not light.is_color) | ||
| 98 | + and (preset.color_temp == color_temp or not is_variable_color_temp) | ||
| 99 | + and (preset.hue == h or not is_color) | ||
| 100 | + and (preset.saturation == s or not is_color) | ||
| 99 | 101 | ): | |
| 100 | 102 | return preset_name | |
| 101 | 103 | return self.PRESET_NOT_SET | |
@@ -107,7 +109,7 @@ async def set_preset( | |||
| 107 | 109 | """Set a light preset for the device.""" | |
| 108 | 110 | light = self._device.modules[Module.Light] | |
| 109 | 111 | if preset_name == self.PRESET_NOT_SET: | |
| 110 | - if light.is_color: | ||
| 112 | + if light.has_feature("hsv"): | ||
| 111 | 113 | preset = LightState(hue=0, saturation=0, brightness=100) | |
| 112 | 114 | else: | |
| 113 | 115 | preset = LightState(brightness=100) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -55,7 +55,7 @@ def valid_temperature_range(self) -> ColorTempRange: | |||
| 55 | 55 | ||
| 56 | 56 | :return: White temperature range in Kelvin (minimum, maximum) | |
| 57 | 57 | """ | |
| 58 | - if not self.is_variable_color_temp: | ||
| 58 | + if Module.ColorTemperature not in self._device.modules: | ||
| 59 | 59 | raise KasaException("Color temperature not supported") | |
| 60 | 60 | ||
| 61 | 61 | return self._device.modules[Module.ColorTemperature].valid_temperature_range | |
@@ -66,23 +66,23 @@ def hsv(self) -> Annotated[HSV, FeatureAttribute()]: | |||
| 66 | 66 | ||
| 67 | 67 | :return: hue, saturation and value (degrees, %, %) | |
| 68 | 68 | """ | |
| 69 | - if not self.is_color: | ||
| 69 | + if Module.Color not in self._device.modules: | ||
| 70 | 70 | raise KasaException("Bulb does not support color.") | |
| 71 | 71 | ||
| 72 | 72 | return self._device.modules[Module.Color].hsv | |
| 73 | 73 | ||
| 74 | 74 | @property | |
| 75 | 75 | def color_temp(self) -> Annotated[int, FeatureAttribute()]: | |
| 76 | 76 | """Whether the bulb supports color temperature changes.""" | |
| 77 | - if not self.is_variable_color_temp: | ||
| 77 | + if Module.ColorTemperature not in self._device.modules: | ||
| 78 | 78 | raise KasaException("Bulb does not support colortemp.") | |
| 79 | 79 | ||
| 80 | 80 | return self._device.modules[Module.ColorTemperature].color_temp | |
| 81 | 81 | ||
| 82 | 82 | @property | |
| 83 | 83 | def brightness(self) -> Annotated[int, FeatureAttribute()]: | |
| 84 | 84 | """Return the current brightness in percentage.""" | |
| 85 | - if not self.is_dimmable: # pragma: no cover | ||
| 85 | + if Module.Brightness not in self._device.modules: | ||
| 86 | 86 | raise KasaException("Bulb is not dimmable.") | |
| 87 | 87 | ||
| 88 | 88 | return self._device.modules[Module.Brightness].brightness | |
@@ -104,7 +104,7 @@ async def set_hsv( | |||
| 104 | 104 | :param int value: value between 1 and 100 | |
| 105 | 105 | :param int transition: transition in milliseconds. | |
| 106 | 106 | """ | |
| 107 | - if not self.is_color: | ||
| 107 | + if Module.Color not in self._device.modules: | ||
| 108 | 108 | raise KasaException("Bulb does not support color.") | |
| 109 | 109 | ||
| 110 | 110 | return await self._device.modules[Module.Color].set_hsv(hue, saturation, value) | |
@@ -119,7 +119,7 @@ async def set_color_temp( | |||
| 119 | 119 | :param int temp: The new color temperature, in Kelvin | |
| 120 | 120 | :param int transition: transition in milliseconds. | |
| 121 | 121 | """ | |
| 122 | - if not self.is_variable_color_temp: | ||
| 122 | + if Module.ColorTemperature not in self._device.modules: | ||
| 123 | 123 | raise KasaException("Bulb does not support colortemp.") | |
| 124 | 124 | return await self._device.modules[Module.ColorTemperature].set_color_temp( | |
| 125 | 125 | temp, brightness=brightness | |
@@ -135,7 +135,7 @@ async def set_brightness( | |||
| 135 | 135 | :param int brightness: brightness in percent | |
| 136 | 136 | :param int transition: transition in milliseconds. | |
| 137 | 137 | """ | |
| 138 | - if not self.is_dimmable: # pragma: no cover | ||
| 138 | + if Module.Brightness not in self._device.modules: | ||
| 139 | 139 | raise KasaException("Bulb is not dimmable.") | |
| 140 | 140 | ||
| 141 | 141 | return await self._device.modules[Module.Brightness].set_brightness(brightness) | |
@@ -167,16 +167,17 @@ def state(self) -> LightState: | |||
| 167 | 167 | return self._light_state | |
| 168 | 168 | ||
| 169 | 169 | async def _post_update_hook(self) -> None: | |
| 170 | - if self._device.is_on is False: | ||
| 170 | + device = self._device | ||
| 171 | + if device.is_on is False: | ||
| 171 | 172 | state = LightState(light_on=False) | |
| 172 | 173 | else: | |
| 173 | 174 | state = LightState(light_on=True) | |
| 174 | - if self.is_dimmable: | ||
| 175 | + if Module.Brightness in device.modules: | ||
| 175 | 176 | state.brightness = self.brightness | |
| 176 | - if self.is_color: | ||
| 177 | + if Module.Color in device.modules: | ||
| 177 | 178 | hsv = self.hsv | |
| 178 | 179 | state.hue = hsv.hue | |
| 179 | 180 | state.saturation = hsv.saturation | |
| 180 | - if self.is_variable_color_temp: | ||
| 181 | + if Module.ColorTemperature in device.modules: | ||
| 181 | 182 | state.color_temp = self.color_temp | |
| 182 | 183 | self._light_state = state | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -96,13 +96,18 @@ def preset(self) -> str: | |||
| 96 | 96 | """Return current preset name.""" | |
| 97 | 97 | light = self._device.modules[SmartModule.Light] | |
| 98 | 98 | brightness = light.brightness | |
| 99 | - color_temp = light.color_temp if light.is_variable_color_temp else None | ||
| 100 | - h, s = (light.hsv.hue, light.hsv.saturation) if light.is_color else (None, None) | ||
| 99 | + color_temp = light.color_temp if light.has_feature("color_temp") else None | ||
| 100 | + h, s = ( | ||
| 101 | + (light.hsv.hue, light.hsv.saturation) | ||
| 102 | + if light.has_feature("hsv") | ||
| 103 | + else (None, None) | ||
| 104 | + ) | ||
| 101 | 105 | for preset_name, preset in self._presets.items(): | |
| 102 | 106 | if ( | |
| 103 | 107 | preset.brightness == brightness | |
| 104 | 108 | and ( | |
| 105 | - preset.color_temp == color_temp or not light.is_variable_color_temp | ||
| 109 | + preset.color_temp == color_temp | ||
| 110 | + or not light.has_feature("color_temp") | ||
| 106 | 111 | ) | |
| 107 | 112 | and preset.hue == h | |
| 108 | 113 | and preset.saturation == s | |
@@ -117,7 +122,7 @@ async def set_preset( | |||
| 117 | 122 | """Set a light preset for the device.""" | |
| 118 | 123 | light = self._device.modules[SmartModule.Light] | |
| 119 | 124 | if preset_name == self.PRESET_NOT_SET: | |
| 120 | - if light.is_color: | ||
| 125 | + if light.has_feature("hsv"): | ||
| 121 | 126 | preset = LightState(hue=0, saturation=0, brightness=100) | |
| 122 | 127 | else: | |
| 123 | 128 | preset = LightState(brightness=100) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -91,7 +91,9 @@ async def test_unknown_temp_range(dev: IotBulb, monkeypatch, caplog): | |||
| 91 | 91 | monkeypatch.setitem(dev._sys_info, "model", "unknown bulb") | |
| 92 | 92 | light = dev.modules.get(Module.Light) | |
| 93 | 93 | assert light | |
| 94 | - assert light.valid_temperature_range == (2700, 5000) | ||
| 94 | + color_temp_feat = light.get_feature("color_temp") | ||
| 95 | + assert color_temp_feat | ||
| 96 | + assert color_temp_feat.range == (2700, 5000) | ||
| 95 | 97 | assert "Unknown color temperature range, fallback to 2700-5000" in caplog.text | |
| 96 | 98 | ||
| 97 | 99 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -469,7 +469,9 @@ async def side_effect_func(*args, **kwargs): | |||
| 469 | 469 | async def test_smart_temp_range(dev: Device): | |
| 470 | 470 | light = dev.modules.get(Module.Light) | |
| 471 | 471 | assert light | |
| 472 | - assert light.valid_temperature_range | ||
| 472 | + color_temp_feat = light.get_feature("color_temp") | ||
| 473 | + assert color_temp_feat | ||
| 474 | + assert color_temp_feat.range | ||
| 473 | 475 | ||
| 474 | 476 | ||
| 475 | 477 | @device_smart | |
| Back | FazBrowse Home | New Git URL |
0 commit comments