mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-04-26 16:46:23 +00:00
Embed FeatureType inside Feature (#860)
Moves `FeatureType` into `Feature` to make it easier to use the API. This also enforces that no invalid types are accepted (i.e., `Category.Config` cannot be a `Sensor`) If `--verbose` is used with the cli tool, some extra information is displayed for features when in the state command.
This commit is contained in:
parent
e410e4f3f3
commit
65874c0365
@ -35,7 +35,7 @@ from kasa.exceptions import (
|
|||||||
TimeoutError,
|
TimeoutError,
|
||||||
UnsupportedDeviceError,
|
UnsupportedDeviceError,
|
||||||
)
|
)
|
||||||
from kasa.feature import Feature, FeatureType
|
from kasa.feature import Feature
|
||||||
from kasa.iot.iotbulb import BulbPreset, TurnOnBehavior, TurnOnBehaviors
|
from kasa.iot.iotbulb import BulbPreset, TurnOnBehavior, TurnOnBehaviors
|
||||||
from kasa.iotprotocol import (
|
from kasa.iotprotocol import (
|
||||||
IotProtocol,
|
IotProtocol,
|
||||||
@ -58,7 +58,6 @@ __all__ = [
|
|||||||
"TurnOnBehavior",
|
"TurnOnBehavior",
|
||||||
"DeviceType",
|
"DeviceType",
|
||||||
"Feature",
|
"Feature",
|
||||||
"FeatureType",
|
|
||||||
"EmeterStatus",
|
"EmeterStatus",
|
||||||
"Device",
|
"Device",
|
||||||
"Bulb",
|
"Bulb",
|
||||||
|
36
kasa/cli.py
36
kasa/cli.py
@ -585,7 +585,10 @@ async def sysinfo(dev):
|
|||||||
|
|
||||||
|
|
||||||
def _echo_features(
|
def _echo_features(
|
||||||
features: dict[str, Feature], title: str, category: Feature.Category | None = None
|
features: dict[str, Feature],
|
||||||
|
title: str,
|
||||||
|
category: Feature.Category | None = None,
|
||||||
|
verbose: bool = False,
|
||||||
):
|
):
|
||||||
"""Print out a listing of features and their values."""
|
"""Print out a listing of features and their values."""
|
||||||
if category is not None:
|
if category is not None:
|
||||||
@ -599,24 +602,42 @@ def _echo_features(
|
|||||||
for _, feat in features.items():
|
for _, feat in features.items():
|
||||||
try:
|
try:
|
||||||
echo(f"\t{feat}")
|
echo(f"\t{feat}")
|
||||||
|
if verbose:
|
||||||
|
echo(f"\t\tType: {feat.type}")
|
||||||
|
echo(f"\t\tCategory: {feat.category}")
|
||||||
|
echo(f"\t\tIcon: {feat.icon}")
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
echo(f"\t{feat.name} ({feat.id}): got exception (%s)" % ex)
|
echo(f"\t{feat.name} ({feat.id}): got exception (%s)" % ex)
|
||||||
|
|
||||||
|
|
||||||
def _echo_all_features(features, title_prefix=None):
|
def _echo_all_features(features, *, verbose=False, title_prefix=None):
|
||||||
"""Print out all features by category."""
|
"""Print out all features by category."""
|
||||||
if title_prefix is not None:
|
if title_prefix is not None:
|
||||||
echo(f"[bold]\n\t == {title_prefix} ==[/bold]")
|
echo(f"[bold]\n\t == {title_prefix} ==[/bold]")
|
||||||
_echo_features(
|
_echo_features(
|
||||||
features, title="\n\t== Primary features ==", category=Feature.Category.Primary
|
features,
|
||||||
|
title="\n\t== Primary features ==",
|
||||||
|
category=Feature.Category.Primary,
|
||||||
|
verbose=verbose,
|
||||||
)
|
)
|
||||||
_echo_features(
|
_echo_features(
|
||||||
features, title="\n\t== Information ==", category=Feature.Category.Info
|
features,
|
||||||
|
title="\n\t== Information ==",
|
||||||
|
category=Feature.Category.Info,
|
||||||
|
verbose=verbose,
|
||||||
)
|
)
|
||||||
_echo_features(
|
_echo_features(
|
||||||
features, title="\n\t== Configuration ==", category=Feature.Category.Config
|
features,
|
||||||
|
title="\n\t== Configuration ==",
|
||||||
|
category=Feature.Category.Config,
|
||||||
|
verbose=verbose,
|
||||||
|
)
|
||||||
|
_echo_features(
|
||||||
|
features,
|
||||||
|
title="\n\t== Debug ==",
|
||||||
|
category=Feature.Category.Debug,
|
||||||
|
verbose=verbose,
|
||||||
)
|
)
|
||||||
_echo_features(features, title="\n\t== Debug ==", category=Feature.Category.Debug)
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@ -636,6 +657,7 @@ async def state(ctx, dev: Device):
|
|||||||
_echo_all_features(
|
_echo_all_features(
|
||||||
child.features,
|
child.features,
|
||||||
title_prefix=f"{child.alias} ({child.model}, {child.device_type})",
|
title_prefix=f"{child.alias} ({child.model}, {child.device_type})",
|
||||||
|
verbose=verbose,
|
||||||
)
|
)
|
||||||
|
|
||||||
echo()
|
echo()
|
||||||
@ -647,7 +669,7 @@ async def state(ctx, dev: Device):
|
|||||||
echo(f"\tMAC (rssi): {dev.mac} ({dev.rssi})")
|
echo(f"\tMAC (rssi): {dev.mac} ({dev.rssi})")
|
||||||
echo(f"\tLocation: {dev.location}")
|
echo(f"\tLocation: {dev.location}")
|
||||||
|
|
||||||
_echo_all_features(dev.features)
|
_echo_all_features(dev.features, verbose=verbose)
|
||||||
|
|
||||||
echo("\n\t[bold]== Modules ==[/bold]")
|
echo("\n\t[bold]== Modules ==[/bold]")
|
||||||
for module in dev.modules.values():
|
for module in dev.modules.values():
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
from typing import TYPE_CHECKING, Any, Callable
|
from typing import TYPE_CHECKING, Any, Callable
|
||||||
@ -10,26 +11,44 @@ if TYPE_CHECKING:
|
|||||||
from .device import Device
|
from .device import Device
|
||||||
|
|
||||||
|
|
||||||
# TODO: This is only useful for Feature, so maybe move to Feature.Type?
|
_LOGGER = logging.getLogger(__name__)
|
||||||
class FeatureType(Enum):
|
|
||||||
"""Type to help decide how to present the feature."""
|
|
||||||
|
|
||||||
Sensor = auto()
|
|
||||||
BinarySensor = auto()
|
|
||||||
Switch = auto()
|
|
||||||
Action = auto()
|
|
||||||
Number = auto()
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Feature:
|
class Feature:
|
||||||
"""Feature defines a generic interface for device features."""
|
"""Feature defines a generic interface for device features."""
|
||||||
|
|
||||||
|
class Type(Enum):
|
||||||
|
"""Type to help decide how to present the feature."""
|
||||||
|
|
||||||
|
#: Sensor is an informative read-only value
|
||||||
|
Sensor = auto()
|
||||||
|
#: BinarySensor is a read-only boolean
|
||||||
|
BinarySensor = auto()
|
||||||
|
#: Switch is a boolean setting
|
||||||
|
Switch = auto()
|
||||||
|
#: Action triggers some action on device
|
||||||
|
Action = auto()
|
||||||
|
#: Number defines a numeric setting
|
||||||
|
#: See :ref:`range_getter`, :ref:`minimum_value`, and :ref:`maximum_value`
|
||||||
|
Number = auto()
|
||||||
|
#: Choice defines a setting with pre-defined values
|
||||||
|
Choice = auto()
|
||||||
|
Unknown = -1
|
||||||
|
|
||||||
|
# TODO: unsure if this is a great idea..
|
||||||
|
Sensor = Type.Sensor
|
||||||
|
BinarySensor = Type.BinarySensor
|
||||||
|
Switch = Type.Switch
|
||||||
|
Action = Type.Action
|
||||||
|
Number = Type.Number
|
||||||
|
Choice = Type.Choice
|
||||||
|
|
||||||
class Category(Enum):
|
class Category(Enum):
|
||||||
"""Category hint for downstreams."""
|
"""Category hint to allow feature grouping."""
|
||||||
|
|
||||||
#: Primary features control the device state directly.
|
#: Primary features control the device state directly.
|
||||||
#: Examples including turning the device on, or adjust its brightness.
|
#: Examples include turning the device on/off, or adjusting its brightness.
|
||||||
Primary = auto()
|
Primary = auto()
|
||||||
#: Config features change device behavior without immediate state changes.
|
#: Config features change device behavior without immediate state changes.
|
||||||
Config = auto()
|
Config = auto()
|
||||||
@ -58,7 +77,7 @@ class Feature:
|
|||||||
#: Category hint for downstreams
|
#: Category hint for downstreams
|
||||||
category: Feature.Category = Category.Unset
|
category: Feature.Category = Category.Unset
|
||||||
#: Type of the feature
|
#: Type of the feature
|
||||||
type: FeatureType = FeatureType.Sensor
|
type: Feature.Type = Type.Sensor
|
||||||
|
|
||||||
# Number-specific attributes
|
# Number-specific attributes
|
||||||
#: Minimum value
|
#: Minimum value
|
||||||
@ -92,10 +111,19 @@ class Feature:
|
|||||||
else:
|
else:
|
||||||
self.category = Feature.Category.Info
|
self.category = Feature.Category.Info
|
||||||
|
|
||||||
|
if self.category == Feature.Category.Config and self.type in [
|
||||||
|
Feature.Type.Sensor,
|
||||||
|
Feature.Type.BinarySensor,
|
||||||
|
]:
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid type for configurable feature: {self.name} ({self.id}):"
|
||||||
|
f" {self.type}"
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self):
|
def value(self):
|
||||||
"""Return the current value."""
|
"""Return the current value."""
|
||||||
if self.type == FeatureType.Action:
|
if self.type == Feature.Type.Action:
|
||||||
return "<Action>"
|
return "<Action>"
|
||||||
if self.attribute_getter is None:
|
if self.attribute_getter is None:
|
||||||
raise ValueError("Not an action and no attribute_getter set")
|
raise ValueError("Not an action and no attribute_getter set")
|
||||||
@ -109,7 +137,7 @@ class Feature:
|
|||||||
"""Set the value."""
|
"""Set the value."""
|
||||||
if self.attribute_setter is None:
|
if self.attribute_setter is None:
|
||||||
raise ValueError("Tried to set read-only feature.")
|
raise ValueError("Tried to set read-only feature.")
|
||||||
if self.type == FeatureType.Number: # noqa: SIM102
|
if self.type == Feature.Type.Number: # noqa: SIM102
|
||||||
if value < self.minimum_value or value > self.maximum_value:
|
if value < self.minimum_value or value > self.maximum_value:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Value {value} out of range "
|
f"Value {value} out of range "
|
||||||
@ -117,7 +145,7 @@ class Feature:
|
|||||||
)
|
)
|
||||||
|
|
||||||
container = self.container if self.container is not None else self.device
|
container = self.container if self.container is not None else self.device
|
||||||
if self.type == FeatureType.Action:
|
if self.type == Feature.Type.Action:
|
||||||
return await getattr(container, self.attribute_setter)()
|
return await getattr(container, self.attribute_setter)()
|
||||||
|
|
||||||
return await getattr(container, self.attribute_setter)(value)
|
return await getattr(container, self.attribute_setter)(value)
|
||||||
@ -127,7 +155,7 @@ class Feature:
|
|||||||
if self.unit is not None:
|
if self.unit is not None:
|
||||||
s += f" {self.unit}"
|
s += f" {self.unit}"
|
||||||
|
|
||||||
if self.type == FeatureType.Number:
|
if self.type == Feature.Type.Number:
|
||||||
s += f" (range: {self.minimum_value}-{self.maximum_value})"
|
s += f" (range: {self.minimum_value}-{self.maximum_value})"
|
||||||
|
|
||||||
return s
|
return s
|
||||||
|
@ -15,7 +15,7 @@ except ImportError:
|
|||||||
from ..bulb import HSV, Bulb, BulbPreset, ColorTempRange
|
from ..bulb import HSV, Bulb, BulbPreset, ColorTempRange
|
||||||
from ..device_type import DeviceType
|
from ..device_type import DeviceType
|
||||||
from ..deviceconfig import DeviceConfig
|
from ..deviceconfig import DeviceConfig
|
||||||
from ..feature import Feature, FeatureType
|
from ..feature import Feature
|
||||||
from ..protocol import BaseProtocol
|
from ..protocol import BaseProtocol
|
||||||
from .iotdevice import IotDevice, KasaException, requires_update
|
from .iotdevice import IotDevice, KasaException, requires_update
|
||||||
from .modules import Antitheft, Cloud, Countdown, Emeter, Schedule, Time, Usage
|
from .modules import Antitheft, Cloud, Countdown, Emeter, Schedule, Time, Usage
|
||||||
@ -221,7 +221,7 @@ class IotBulb(IotDevice, Bulb):
|
|||||||
attribute_setter="set_brightness",
|
attribute_setter="set_brightness",
|
||||||
minimum_value=1,
|
minimum_value=1,
|
||||||
maximum_value=100,
|
maximum_value=100,
|
||||||
type=FeatureType.Number,
|
type=Feature.Type.Number,
|
||||||
category=Feature.Category.Primary,
|
category=Feature.Category.Primary,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -7,7 +7,7 @@ from typing import Any
|
|||||||
|
|
||||||
from ..device_type import DeviceType
|
from ..device_type import DeviceType
|
||||||
from ..deviceconfig import DeviceConfig
|
from ..deviceconfig import DeviceConfig
|
||||||
from ..feature import Feature, FeatureType
|
from ..feature import Feature
|
||||||
from ..protocol import BaseProtocol
|
from ..protocol import BaseProtocol
|
||||||
from .iotdevice import KasaException, requires_update
|
from .iotdevice import KasaException, requires_update
|
||||||
from .iotplug import IotPlug
|
from .iotplug import IotPlug
|
||||||
@ -96,7 +96,7 @@ class IotDimmer(IotPlug):
|
|||||||
attribute_setter="set_brightness",
|
attribute_setter="set_brightness",
|
||||||
minimum_value=1,
|
minimum_value=1,
|
||||||
maximum_value=100,
|
maximum_value=100,
|
||||||
type=FeatureType.Number,
|
type=Feature.Type.Number,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import logging
|
|||||||
|
|
||||||
from ..device_type import DeviceType
|
from ..device_type import DeviceType
|
||||||
from ..deviceconfig import DeviceConfig
|
from ..deviceconfig import DeviceConfig
|
||||||
from ..feature import Feature, FeatureType
|
from ..feature import Feature
|
||||||
from ..protocol import BaseProtocol
|
from ..protocol import BaseProtocol
|
||||||
from .iotdevice import IotDevice, requires_update
|
from .iotdevice import IotDevice, requires_update
|
||||||
from .modules import Antitheft, Cloud, Schedule, Time, Usage
|
from .modules import Antitheft, Cloud, Schedule, Time, Usage
|
||||||
@ -69,7 +69,7 @@ class IotPlug(IotDevice):
|
|||||||
icon="mdi:led-{state}",
|
icon="mdi:led-{state}",
|
||||||
attribute_getter="led",
|
attribute_getter="led",
|
||||||
attribute_setter="set_led",
|
attribute_setter="set_led",
|
||||||
type=FeatureType.Switch,
|
type=Feature.Type.Switch,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""Implementation of the ambient light (LAS) module found in some dimmers."""
|
"""Implementation of the ambient light (LAS) module found in some dimmers."""
|
||||||
|
|
||||||
from ...feature import Feature, FeatureType
|
from ...feature import Feature
|
||||||
from ..iotmodule import IotModule, merge
|
from ..iotmodule import IotModule, merge
|
||||||
|
|
||||||
# TODO create tests and use the config reply there
|
# TODO create tests and use the config reply there
|
||||||
@ -25,7 +25,7 @@ class AmbientLight(IotModule):
|
|||||||
name="Ambient Light",
|
name="Ambient Light",
|
||||||
icon="mdi:brightness-percent",
|
icon="mdi:brightness-percent",
|
||||||
attribute_getter="ambientlight_brightness",
|
attribute_getter="ambientlight_brightness",
|
||||||
type=FeatureType.Sensor,
|
type=Feature.Type.Sensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from ...feature import Feature, FeatureType
|
from ...feature import Feature
|
||||||
from ..iotmodule import IotModule
|
from ..iotmodule import IotModule
|
||||||
|
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ class Cloud(IotModule):
|
|||||||
name="Cloud connection",
|
name="Cloud connection",
|
||||||
icon="mdi:cloud",
|
icon="mdi:cloud",
|
||||||
attribute_getter="is_connected",
|
attribute_getter="is_connected",
|
||||||
type=FeatureType.BinarySensor,
|
type=Feature.Type.BinarySensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from ...feature import Feature, FeatureType
|
from ...feature import Feature
|
||||||
from ..smartmodule import SmartModule
|
from ..smartmodule import SmartModule
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -32,7 +32,7 @@ class AlarmModule(SmartModule):
|
|||||||
container=self,
|
container=self,
|
||||||
attribute_getter="active",
|
attribute_getter="active",
|
||||||
icon="mdi:bell",
|
icon="mdi:bell",
|
||||||
type=FeatureType.BinarySensor,
|
type=Feature.Type.BinarySensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self._add_feature(
|
self._add_feature(
|
||||||
@ -60,7 +60,7 @@ class AlarmModule(SmartModule):
|
|||||||
"Test alarm",
|
"Test alarm",
|
||||||
container=self,
|
container=self,
|
||||||
attribute_setter="play",
|
attribute_setter="play",
|
||||||
type=FeatureType.Action,
|
type=Feature.Type.Action,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self._add_feature(
|
self._add_feature(
|
||||||
@ -69,7 +69,7 @@ class AlarmModule(SmartModule):
|
|||||||
"Stop alarm",
|
"Stop alarm",
|
||||||
container=self,
|
container=self,
|
||||||
attribute_setter="stop",
|
attribute_setter="stop",
|
||||||
type=FeatureType.Action,
|
type=Feature.Type.Action,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ class AutoOffModule(SmartModule):
|
|||||||
container=self,
|
container=self,
|
||||||
attribute_getter="enabled",
|
attribute_getter="enabled",
|
||||||
attribute_setter="set_enabled",
|
attribute_setter="set_enabled",
|
||||||
|
type=Feature.Type.Switch,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self._add_feature(
|
self._add_feature(
|
||||||
@ -36,6 +37,7 @@ class AutoOffModule(SmartModule):
|
|||||||
container=self,
|
container=self,
|
||||||
attribute_getter="delay",
|
attribute_getter="delay",
|
||||||
attribute_setter="set_delay",
|
attribute_setter="set_delay",
|
||||||
|
type=Feature.Type.Number,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self._add_feature(
|
self._add_feature(
|
||||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from ...feature import Feature, FeatureType
|
from ...feature import Feature
|
||||||
from ..smartmodule import SmartModule
|
from ..smartmodule import SmartModule
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -35,7 +35,7 @@ class BatterySensor(SmartModule):
|
|||||||
container=self,
|
container=self,
|
||||||
attribute_getter="battery_low",
|
attribute_getter="battery_low",
|
||||||
icon="mdi:alert",
|
icon="mdi:alert",
|
||||||
type=FeatureType.BinarySensor,
|
type=Feature.Type.BinarySensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from ...feature import Feature, FeatureType
|
from ...feature import Feature
|
||||||
from ..smartmodule import SmartModule
|
from ..smartmodule import SmartModule
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -31,7 +31,7 @@ class Brightness(SmartModule):
|
|||||||
attribute_setter="set_brightness",
|
attribute_setter="set_brightness",
|
||||||
minimum_value=BRIGHTNESS_MIN,
|
minimum_value=BRIGHTNESS_MIN,
|
||||||
maximum_value=BRIGHTNESS_MAX,
|
maximum_value=BRIGHTNESS_MAX,
|
||||||
type=FeatureType.Number,
|
type=Feature.Type.Number,
|
||||||
category=Feature.Category.Primary,
|
category=Feature.Category.Primary,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -5,7 +5,7 @@ from __future__ import annotations
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from ...exceptions import SmartErrorCode
|
from ...exceptions import SmartErrorCode
|
||||||
from ...feature import Feature, FeatureType
|
from ...feature import Feature
|
||||||
from ..smartmodule import SmartModule
|
from ..smartmodule import SmartModule
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -28,7 +28,7 @@ class CloudModule(SmartModule):
|
|||||||
container=self,
|
container=self,
|
||||||
attribute_getter="is_connected",
|
attribute_getter="is_connected",
|
||||||
icon="mdi:cloud",
|
icon="mdi:cloud",
|
||||||
type=FeatureType.BinarySensor,
|
type=Feature.Type.BinarySensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -25,8 +25,9 @@ class ColorModule(SmartModule):
|
|||||||
"HSV",
|
"HSV",
|
||||||
container=self,
|
container=self,
|
||||||
attribute_getter="hsv",
|
attribute_getter="hsv",
|
||||||
# TODO proper type for setting hsv
|
|
||||||
attribute_setter="set_hsv",
|
attribute_setter="set_hsv",
|
||||||
|
# TODO proper type for setting hsv
|
||||||
|
type=Feature.Type.Unknown,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from ...feature import Feature, FeatureType
|
from ...feature import Feature
|
||||||
from ..smartmodule import SmartModule
|
from ..smartmodule import SmartModule
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -27,7 +27,7 @@ class FanModule(SmartModule):
|
|||||||
attribute_getter="fan_speed_level",
|
attribute_getter="fan_speed_level",
|
||||||
attribute_setter="set_fan_speed_level",
|
attribute_setter="set_fan_speed_level",
|
||||||
icon="mdi:fan",
|
icon="mdi:fan",
|
||||||
type=FeatureType.Number,
|
type=Feature.Type.Number,
|
||||||
minimum_value=1,
|
minimum_value=1,
|
||||||
maximum_value=4,
|
maximum_value=4,
|
||||||
category=Feature.Category.Primary,
|
category=Feature.Category.Primary,
|
||||||
@ -41,7 +41,7 @@ class FanModule(SmartModule):
|
|||||||
attribute_getter="sleep_mode",
|
attribute_getter="sleep_mode",
|
||||||
attribute_setter="set_sleep_mode",
|
attribute_setter="set_sleep_mode",
|
||||||
icon="mdi:sleep",
|
icon="mdi:sleep",
|
||||||
type=FeatureType.Switch,
|
type=Feature.Type.Switch,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ from __future__ import annotations
|
|||||||
from typing import TYPE_CHECKING, Any, Optional
|
from typing import TYPE_CHECKING, Any, Optional
|
||||||
|
|
||||||
from ...exceptions import SmartErrorCode
|
from ...exceptions import SmartErrorCode
|
||||||
from ...feature import Feature, FeatureType
|
from ...feature import Feature
|
||||||
from ..smartmodule import SmartModule
|
from ..smartmodule import SmartModule
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -59,7 +59,7 @@ class Firmware(SmartModule):
|
|||||||
container=self,
|
container=self,
|
||||||
attribute_getter="auto_update_enabled",
|
attribute_getter="auto_update_enabled",
|
||||||
attribute_setter="set_auto_update_enabled",
|
attribute_setter="set_auto_update_enabled",
|
||||||
type=FeatureType.Switch,
|
type=Feature.Type.Switch,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self._add_feature(
|
self._add_feature(
|
||||||
@ -68,7 +68,7 @@ class Firmware(SmartModule):
|
|||||||
"Update available",
|
"Update available",
|
||||||
container=self,
|
container=self,
|
||||||
attribute_getter="update_available",
|
attribute_getter="update_available",
|
||||||
type=FeatureType.BinarySensor,
|
type=Feature.Type.BinarySensor,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from ...feature import Feature, FeatureType
|
from ...feature import Feature
|
||||||
from ..smartmodule import SmartModule
|
from ..smartmodule import SmartModule
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -34,7 +34,7 @@ class HumiditySensor(SmartModule):
|
|||||||
"Humidity warning",
|
"Humidity warning",
|
||||||
container=self,
|
container=self,
|
||||||
attribute_getter="humidity_warning",
|
attribute_getter="humidity_warning",
|
||||||
type=FeatureType.BinarySensor,
|
type=Feature.Type.BinarySensor,
|
||||||
icon="mdi:alert",
|
icon="mdi:alert",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from ...feature import Feature, FeatureType
|
from ...feature import Feature
|
||||||
from ..smartmodule import SmartModule
|
from ..smartmodule import SmartModule
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -27,7 +27,7 @@ class LedModule(SmartModule):
|
|||||||
icon="mdi:led-{state}",
|
icon="mdi:led-{state}",
|
||||||
attribute_getter="led",
|
attribute_getter="led",
|
||||||
attribute_setter="set_led",
|
attribute_setter="set_led",
|
||||||
type=FeatureType.Switch,
|
type=Feature.Type.Switch,
|
||||||
category=Feature.Category.Config,
|
category=Feature.Category.Config,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -5,7 +5,7 @@ from __future__ import annotations
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from ...exceptions import KasaException
|
from ...exceptions import KasaException
|
||||||
from ...feature import Feature, FeatureType
|
from ...feature import Feature
|
||||||
from ..smartmodule import SmartModule
|
from ..smartmodule import SmartModule
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -35,7 +35,7 @@ class LightTransitionModule(SmartModule):
|
|||||||
icon=icon,
|
icon=icon,
|
||||||
attribute_getter="enabled_v1",
|
attribute_getter="enabled_v1",
|
||||||
attribute_setter="set_enabled_v1",
|
attribute_setter="set_enabled_v1",
|
||||||
type=FeatureType.Switch,
|
type=Feature.Type.Switch,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
elif self.supported_version >= 2:
|
elif self.supported_version >= 2:
|
||||||
@ -51,7 +51,7 @@ class LightTransitionModule(SmartModule):
|
|||||||
attribute_getter="turn_on_transition",
|
attribute_getter="turn_on_transition",
|
||||||
attribute_setter="set_turn_on_transition",
|
attribute_setter="set_turn_on_transition",
|
||||||
icon=icon,
|
icon=icon,
|
||||||
type=FeatureType.Number,
|
type=Feature.Type.Number,
|
||||||
maximum_value=self.MAXIMUM_DURATION,
|
maximum_value=self.MAXIMUM_DURATION,
|
||||||
)
|
)
|
||||||
) # self._turn_on_transition_max
|
) # self._turn_on_transition_max
|
||||||
@ -63,7 +63,7 @@ class LightTransitionModule(SmartModule):
|
|||||||
attribute_getter="turn_off_transition",
|
attribute_getter="turn_off_transition",
|
||||||
attribute_setter="set_turn_off_transition",
|
attribute_setter="set_turn_off_transition",
|
||||||
icon=icon,
|
icon=icon,
|
||||||
type=FeatureType.Number,
|
type=Feature.Type.Number,
|
||||||
maximum_value=self.MAXIMUM_DURATION,
|
maximum_value=self.MAXIMUM_DURATION,
|
||||||
)
|
)
|
||||||
) # self._turn_off_transition_max
|
) # self._turn_off_transition_max
|
||||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import TYPE_CHECKING, Literal
|
from typing import TYPE_CHECKING, Literal
|
||||||
|
|
||||||
from ...feature import Feature, FeatureType
|
from ...feature import Feature
|
||||||
from ..smartmodule import SmartModule
|
from ..smartmodule import SmartModule
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -35,7 +35,7 @@ class TemperatureSensor(SmartModule):
|
|||||||
"Temperature warning",
|
"Temperature warning",
|
||||||
container=self,
|
container=self,
|
||||||
attribute_getter="temperature_warning",
|
attribute_getter="temperature_warning",
|
||||||
type=FeatureType.BinarySensor,
|
type=Feature.Type.BinarySensor,
|
||||||
icon="mdi:alert",
|
icon="mdi:alert",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -46,6 +46,7 @@ class TemperatureSensor(SmartModule):
|
|||||||
container=self,
|
container=self,
|
||||||
attribute_getter="temperature_unit",
|
attribute_getter="temperature_unit",
|
||||||
attribute_setter="set_temperature_unit",
|
attribute_setter="set_temperature_unit",
|
||||||
|
type=Feature.Type.Choice,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
# TODO: use temperature_unit for feature creation
|
# TODO: use temperature_unit for feature creation
|
||||||
|
@ -26,6 +26,7 @@ class TemperatureControl(SmartModule):
|
|||||||
attribute_getter="target_temperature",
|
attribute_getter="target_temperature",
|
||||||
attribute_setter="set_target_temperature",
|
attribute_setter="set_target_temperature",
|
||||||
icon="mdi:thermometer",
|
icon="mdi:thermometer",
|
||||||
|
type=Feature.Type.Number,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
# TODO: this might belong into its own module, temperature_correction?
|
# TODO: this might belong into its own module, temperature_correction?
|
||||||
@ -38,6 +39,7 @@ class TemperatureControl(SmartModule):
|
|||||||
attribute_setter="set_temperature_offset",
|
attribute_setter="set_temperature_offset",
|
||||||
minimum_value=-10,
|
minimum_value=-10,
|
||||||
maximum_value=10,
|
maximum_value=10,
|
||||||
|
type=Feature.Type.Number,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ from ..device_type import DeviceType
|
|||||||
from ..deviceconfig import DeviceConfig
|
from ..deviceconfig import DeviceConfig
|
||||||
from ..emeterstatus import EmeterStatus
|
from ..emeterstatus import EmeterStatus
|
||||||
from ..exceptions import AuthenticationError, DeviceError, KasaException, SmartErrorCode
|
from ..exceptions import AuthenticationError, DeviceError, KasaException, SmartErrorCode
|
||||||
from ..feature import Feature, FeatureType
|
from ..feature import Feature
|
||||||
from ..smartprotocol import SmartProtocol
|
from ..smartprotocol import SmartProtocol
|
||||||
from .modules import * # noqa: F403
|
from .modules import * # noqa: F403
|
||||||
|
|
||||||
@ -191,7 +191,7 @@ class SmartDevice(Device):
|
|||||||
"State",
|
"State",
|
||||||
attribute_getter="is_on",
|
attribute_getter="is_on",
|
||||||
attribute_setter="set_state",
|
attribute_setter="set_state",
|
||||||
type=FeatureType.Switch,
|
type=Feature.Type.Switch,
|
||||||
category=Feature.Category.Primary,
|
category=Feature.Category.Primary,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -236,7 +236,7 @@ class SmartDevice(Device):
|
|||||||
"Overheated",
|
"Overheated",
|
||||||
attribute_getter=lambda x: x._info["overheated"],
|
attribute_getter=lambda x: x._info["overheated"],
|
||||||
icon="mdi:heat-wave",
|
icon="mdi:heat-wave",
|
||||||
type=FeatureType.BinarySensor,
|
type=Feature.Type.BinarySensor,
|
||||||
category=Feature.Category.Debug,
|
category=Feature.Category.Debug,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from kasa import Feature, FeatureType
|
from kasa import Feature
|
||||||
|
|
||||||
|
|
||||||
class DummyDevice:
|
class DummyDevice:
|
||||||
@ -18,7 +18,7 @@ def dummy_feature() -> Feature:
|
|||||||
attribute_setter="dummysetter",
|
attribute_setter="dummysetter",
|
||||||
container=None,
|
container=None,
|
||||||
icon="mdi:dummy",
|
icon="mdi:dummy",
|
||||||
type=FeatureType.BinarySensor,
|
type=Feature.Type.Switch,
|
||||||
unit="dummyunit",
|
unit="dummyunit",
|
||||||
)
|
)
|
||||||
return feat
|
return feat
|
||||||
@ -32,10 +32,21 @@ def test_feature_api(dummy_feature: Feature):
|
|||||||
assert dummy_feature.attribute_setter == "dummysetter"
|
assert dummy_feature.attribute_setter == "dummysetter"
|
||||||
assert dummy_feature.container is None
|
assert dummy_feature.container is None
|
||||||
assert dummy_feature.icon == "mdi:dummy"
|
assert dummy_feature.icon == "mdi:dummy"
|
||||||
assert dummy_feature.type == FeatureType.BinarySensor
|
assert dummy_feature.type == Feature.Type.Switch
|
||||||
assert dummy_feature.unit == "dummyunit"
|
assert dummy_feature.unit == "dummyunit"
|
||||||
|
|
||||||
|
|
||||||
|
def test_feature_missing_type():
|
||||||
|
"""Test that creating a feature with a setter but without type causes an error."""
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
Feature(
|
||||||
|
device=DummyDevice(), # type: ignore[arg-type]
|
||||||
|
name="dummy error",
|
||||||
|
attribute_getter="dummygetter",
|
||||||
|
attribute_setter="dummysetter",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_feature_value(dummy_feature: Feature):
|
def test_feature_value(dummy_feature: Feature):
|
||||||
"""Verify that property gets accessed on *value* access."""
|
"""Verify that property gets accessed on *value* access."""
|
||||||
dummy_feature.attribute_getter = "test_prop"
|
dummy_feature.attribute_getter = "test_prop"
|
||||||
@ -91,7 +102,7 @@ async def test_feature_action(mocker):
|
|||||||
attribute_setter="call_action",
|
attribute_setter="call_action",
|
||||||
container=None,
|
container=None,
|
||||||
icon="mdi:dummy",
|
icon="mdi:dummy",
|
||||||
type=FeatureType.Action,
|
type=Feature.Type.Action,
|
||||||
)
|
)
|
||||||
mock_call_action = mocker.patch.object(feat.device, "call_action", create=True)
|
mock_call_action = mocker.patch.object(feat.device, "call_action", create=True)
|
||||||
assert feat.value == "<Action>"
|
assert feat.value == "<Action>"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user