mirror of
https://github.com/python-kasa/python-kasa.git
synced 2024-12-22 11:13:34 +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,
|
||||
UnsupportedDeviceError,
|
||||
)
|
||||
from kasa.feature import Feature, FeatureType
|
||||
from kasa.feature import Feature
|
||||
from kasa.iot.iotbulb import BulbPreset, TurnOnBehavior, TurnOnBehaviors
|
||||
from kasa.iotprotocol import (
|
||||
IotProtocol,
|
||||
@ -58,7 +58,6 @@ __all__ = [
|
||||
"TurnOnBehavior",
|
||||
"DeviceType",
|
||||
"Feature",
|
||||
"FeatureType",
|
||||
"EmeterStatus",
|
||||
"Device",
|
||||
"Bulb",
|
||||
|
36
kasa/cli.py
36
kasa/cli.py
@ -585,7 +585,10 @@ async def sysinfo(dev):
|
||||
|
||||
|
||||
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."""
|
||||
if category is not None:
|
||||
@ -599,24 +602,42 @@ def _echo_features(
|
||||
for _, feat in features.items():
|
||||
try:
|
||||
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:
|
||||
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."""
|
||||
if title_prefix is not None:
|
||||
echo(f"[bold]\n\t == {title_prefix} ==[/bold]")
|
||||
_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(
|
||||
features, title="\n\t== Information ==", category=Feature.Category.Info
|
||||
features,
|
||||
title="\n\t== Information ==",
|
||||
category=Feature.Category.Info,
|
||||
verbose=verbose,
|
||||
)
|
||||
_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()
|
||||
@ -636,6 +657,7 @@ async def state(ctx, dev: Device):
|
||||
_echo_all_features(
|
||||
child.features,
|
||||
title_prefix=f"{child.alias} ({child.model}, {child.device_type})",
|
||||
verbose=verbose,
|
||||
)
|
||||
|
||||
echo()
|
||||
@ -647,7 +669,7 @@ async def state(ctx, dev: Device):
|
||||
echo(f"\tMAC (rssi): {dev.mac} ({dev.rssi})")
|
||||
echo(f"\tLocation: {dev.location}")
|
||||
|
||||
_echo_all_features(dev.features)
|
||||
_echo_all_features(dev.features, verbose=verbose)
|
||||
|
||||
echo("\n\t[bold]== Modules ==[/bold]")
|
||||
for module in dev.modules.values():
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum, auto
|
||||
from typing import TYPE_CHECKING, Any, Callable
|
||||
@ -10,26 +11,44 @@ if TYPE_CHECKING:
|
||||
from .device import Device
|
||||
|
||||
|
||||
# TODO: This is only useful for Feature, so maybe move to Feature.Type?
|
||||
class FeatureType(Enum):
|
||||
"""Type to help decide how to present the feature."""
|
||||
|
||||
Sensor = auto()
|
||||
BinarySensor = auto()
|
||||
Switch = auto()
|
||||
Action = auto()
|
||||
Number = auto()
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Feature:
|
||||
"""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):
|
||||
"""Category hint for downstreams."""
|
||||
"""Category hint to allow feature grouping."""
|
||||
|
||||
#: 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()
|
||||
#: Config features change device behavior without immediate state changes.
|
||||
Config = auto()
|
||||
@ -58,7 +77,7 @@ class Feature:
|
||||
#: Category hint for downstreams
|
||||
category: Feature.Category = Category.Unset
|
||||
#: Type of the feature
|
||||
type: FeatureType = FeatureType.Sensor
|
||||
type: Feature.Type = Type.Sensor
|
||||
|
||||
# Number-specific attributes
|
||||
#: Minimum value
|
||||
@ -92,10 +111,19 @@ class Feature:
|
||||
else:
|
||||
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
|
||||
def value(self):
|
||||
"""Return the current value."""
|
||||
if self.type == FeatureType.Action:
|
||||
if self.type == Feature.Type.Action:
|
||||
return "<Action>"
|
||||
if self.attribute_getter is None:
|
||||
raise ValueError("Not an action and no attribute_getter set")
|
||||
@ -109,7 +137,7 @@ class Feature:
|
||||
"""Set the value."""
|
||||
if self.attribute_setter is None:
|
||||
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:
|
||||
raise ValueError(
|
||||
f"Value {value} out of range "
|
||||
@ -117,7 +145,7 @@ class Feature:
|
||||
)
|
||||
|
||||
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)(value)
|
||||
@ -127,7 +155,7 @@ class Feature:
|
||||
if self.unit is not None:
|
||||
s += f" {self.unit}"
|
||||
|
||||
if self.type == FeatureType.Number:
|
||||
if self.type == Feature.Type.Number:
|
||||
s += f" (range: {self.minimum_value}-{self.maximum_value})"
|
||||
|
||||
return s
|
||||
|
@ -15,7 +15,7 @@ except ImportError:
|
||||
from ..bulb import HSV, Bulb, BulbPreset, ColorTempRange
|
||||
from ..device_type import DeviceType
|
||||
from ..deviceconfig import DeviceConfig
|
||||
from ..feature import Feature, FeatureType
|
||||
from ..feature import Feature
|
||||
from ..protocol import BaseProtocol
|
||||
from .iotdevice import IotDevice, KasaException, requires_update
|
||||
from .modules import Antitheft, Cloud, Countdown, Emeter, Schedule, Time, Usage
|
||||
@ -221,7 +221,7 @@ class IotBulb(IotDevice, Bulb):
|
||||
attribute_setter="set_brightness",
|
||||
minimum_value=1,
|
||||
maximum_value=100,
|
||||
type=FeatureType.Number,
|
||||
type=Feature.Type.Number,
|
||||
category=Feature.Category.Primary,
|
||||
)
|
||||
)
|
||||
|
@ -7,7 +7,7 @@ from typing import Any
|
||||
|
||||
from ..device_type import DeviceType
|
||||
from ..deviceconfig import DeviceConfig
|
||||
from ..feature import Feature, FeatureType
|
||||
from ..feature import Feature
|
||||
from ..protocol import BaseProtocol
|
||||
from .iotdevice import KasaException, requires_update
|
||||
from .iotplug import IotPlug
|
||||
@ -96,7 +96,7 @@ class IotDimmer(IotPlug):
|
||||
attribute_setter="set_brightness",
|
||||
minimum_value=1,
|
||||
maximum_value=100,
|
||||
type=FeatureType.Number,
|
||||
type=Feature.Type.Number,
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -6,7 +6,7 @@ import logging
|
||||
|
||||
from ..device_type import DeviceType
|
||||
from ..deviceconfig import DeviceConfig
|
||||
from ..feature import Feature, FeatureType
|
||||
from ..feature import Feature
|
||||
from ..protocol import BaseProtocol
|
||||
from .iotdevice import IotDevice, requires_update
|
||||
from .modules import Antitheft, Cloud, Schedule, Time, Usage
|
||||
@ -69,7 +69,7 @@ class IotPlug(IotDevice):
|
||||
icon="mdi:led-{state}",
|
||||
attribute_getter="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."""
|
||||
|
||||
from ...feature import Feature, FeatureType
|
||||
from ...feature import Feature
|
||||
from ..iotmodule import IotModule, merge
|
||||
|
||||
# TODO create tests and use the config reply there
|
||||
@ -25,7 +25,7 @@ class AmbientLight(IotModule):
|
||||
name="Ambient Light",
|
||||
icon="mdi:brightness-percent",
|
||||
attribute_getter="ambientlight_brightness",
|
||||
type=FeatureType.Sensor,
|
||||
type=Feature.Type.Sensor,
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -5,7 +5,7 @@ try:
|
||||
except ImportError:
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ...feature import Feature, FeatureType
|
||||
from ...feature import Feature
|
||||
from ..iotmodule import IotModule
|
||||
|
||||
|
||||
@ -36,7 +36,7 @@ class Cloud(IotModule):
|
||||
name="Cloud connection",
|
||||
icon="mdi:cloud",
|
||||
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 ...feature import Feature, FeatureType
|
||||
from ...feature import Feature
|
||||
from ..smartmodule import SmartModule
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -32,7 +32,7 @@ class AlarmModule(SmartModule):
|
||||
container=self,
|
||||
attribute_getter="active",
|
||||
icon="mdi:bell",
|
||||
type=FeatureType.BinarySensor,
|
||||
type=Feature.Type.BinarySensor,
|
||||
)
|
||||
)
|
||||
self._add_feature(
|
||||
@ -60,7 +60,7 @@ class AlarmModule(SmartModule):
|
||||
"Test alarm",
|
||||
container=self,
|
||||
attribute_setter="play",
|
||||
type=FeatureType.Action,
|
||||
type=Feature.Type.Action,
|
||||
)
|
||||
)
|
||||
self._add_feature(
|
||||
@ -69,7 +69,7 @@ class AlarmModule(SmartModule):
|
||||
"Stop alarm",
|
||||
container=self,
|
||||
attribute_setter="stop",
|
||||
type=FeatureType.Action,
|
||||
type=Feature.Type.Action,
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -27,6 +27,7 @@ class AutoOffModule(SmartModule):
|
||||
container=self,
|
||||
attribute_getter="enabled",
|
||||
attribute_setter="set_enabled",
|
||||
type=Feature.Type.Switch,
|
||||
)
|
||||
)
|
||||
self._add_feature(
|
||||
@ -36,6 +37,7 @@ class AutoOffModule(SmartModule):
|
||||
container=self,
|
||||
attribute_getter="delay",
|
||||
attribute_setter="set_delay",
|
||||
type=Feature.Type.Number,
|
||||
)
|
||||
)
|
||||
self._add_feature(
|
||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ...feature import Feature, FeatureType
|
||||
from ...feature import Feature
|
||||
from ..smartmodule import SmartModule
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -35,7 +35,7 @@ class BatterySensor(SmartModule):
|
||||
container=self,
|
||||
attribute_getter="battery_low",
|
||||
icon="mdi:alert",
|
||||
type=FeatureType.BinarySensor,
|
||||
type=Feature.Type.BinarySensor,
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ...feature import Feature, FeatureType
|
||||
from ...feature import Feature
|
||||
from ..smartmodule import SmartModule
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -31,7 +31,7 @@ class Brightness(SmartModule):
|
||||
attribute_setter="set_brightness",
|
||||
minimum_value=BRIGHTNESS_MIN,
|
||||
maximum_value=BRIGHTNESS_MAX,
|
||||
type=FeatureType.Number,
|
||||
type=Feature.Type.Number,
|
||||
category=Feature.Category.Primary,
|
||||
)
|
||||
)
|
||||
|
@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ...exceptions import SmartErrorCode
|
||||
from ...feature import Feature, FeatureType
|
||||
from ...feature import Feature
|
||||
from ..smartmodule import SmartModule
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -28,7 +28,7 @@ class CloudModule(SmartModule):
|
||||
container=self,
|
||||
attribute_getter="is_connected",
|
||||
icon="mdi:cloud",
|
||||
type=FeatureType.BinarySensor,
|
||||
type=Feature.Type.BinarySensor,
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -25,8 +25,9 @@ class ColorModule(SmartModule):
|
||||
"HSV",
|
||||
container=self,
|
||||
attribute_getter="hsv",
|
||||
# TODO proper type for setting 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 ...feature import Feature, FeatureType
|
||||
from ...feature import Feature
|
||||
from ..smartmodule import SmartModule
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -27,7 +27,7 @@ class FanModule(SmartModule):
|
||||
attribute_getter="fan_speed_level",
|
||||
attribute_setter="set_fan_speed_level",
|
||||
icon="mdi:fan",
|
||||
type=FeatureType.Number,
|
||||
type=Feature.Type.Number,
|
||||
minimum_value=1,
|
||||
maximum_value=4,
|
||||
category=Feature.Category.Primary,
|
||||
@ -41,7 +41,7 @@ class FanModule(SmartModule):
|
||||
attribute_getter="sleep_mode",
|
||||
attribute_setter="set_sleep_mode",
|
||||
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 ...exceptions import SmartErrorCode
|
||||
from ...feature import Feature, FeatureType
|
||||
from ...feature import Feature
|
||||
from ..smartmodule import SmartModule
|
||||
|
||||
try:
|
||||
@ -59,7 +59,7 @@ class Firmware(SmartModule):
|
||||
container=self,
|
||||
attribute_getter="auto_update_enabled",
|
||||
attribute_setter="set_auto_update_enabled",
|
||||
type=FeatureType.Switch,
|
||||
type=Feature.Type.Switch,
|
||||
)
|
||||
)
|
||||
self._add_feature(
|
||||
@ -68,7 +68,7 @@ class Firmware(SmartModule):
|
||||
"Update available",
|
||||
container=self,
|
||||
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 ...feature import Feature, FeatureType
|
||||
from ...feature import Feature
|
||||
from ..smartmodule import SmartModule
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -34,7 +34,7 @@ class HumiditySensor(SmartModule):
|
||||
"Humidity warning",
|
||||
container=self,
|
||||
attribute_getter="humidity_warning",
|
||||
type=FeatureType.BinarySensor,
|
||||
type=Feature.Type.BinarySensor,
|
||||
icon="mdi:alert",
|
||||
)
|
||||
)
|
||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ...feature import Feature, FeatureType
|
||||
from ...feature import Feature
|
||||
from ..smartmodule import SmartModule
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -27,7 +27,7 @@ class LedModule(SmartModule):
|
||||
icon="mdi:led-{state}",
|
||||
attribute_getter="led",
|
||||
attribute_setter="set_led",
|
||||
type=FeatureType.Switch,
|
||||
type=Feature.Type.Switch,
|
||||
category=Feature.Category.Config,
|
||||
)
|
||||
)
|
||||
|
@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ...exceptions import KasaException
|
||||
from ...feature import Feature, FeatureType
|
||||
from ...feature import Feature
|
||||
from ..smartmodule import SmartModule
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -35,7 +35,7 @@ class LightTransitionModule(SmartModule):
|
||||
icon=icon,
|
||||
attribute_getter="enabled_v1",
|
||||
attribute_setter="set_enabled_v1",
|
||||
type=FeatureType.Switch,
|
||||
type=Feature.Type.Switch,
|
||||
)
|
||||
)
|
||||
elif self.supported_version >= 2:
|
||||
@ -51,7 +51,7 @@ class LightTransitionModule(SmartModule):
|
||||
attribute_getter="turn_on_transition",
|
||||
attribute_setter="set_turn_on_transition",
|
||||
icon=icon,
|
||||
type=FeatureType.Number,
|
||||
type=Feature.Type.Number,
|
||||
maximum_value=self.MAXIMUM_DURATION,
|
||||
)
|
||||
) # self._turn_on_transition_max
|
||||
@ -63,7 +63,7 @@ class LightTransitionModule(SmartModule):
|
||||
attribute_getter="turn_off_transition",
|
||||
attribute_setter="set_turn_off_transition",
|
||||
icon=icon,
|
||||
type=FeatureType.Number,
|
||||
type=Feature.Type.Number,
|
||||
maximum_value=self.MAXIMUM_DURATION,
|
||||
)
|
||||
) # self._turn_off_transition_max
|
||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
|
||||
from ...feature import Feature, FeatureType
|
||||
from ...feature import Feature
|
||||
from ..smartmodule import SmartModule
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -35,7 +35,7 @@ class TemperatureSensor(SmartModule):
|
||||
"Temperature warning",
|
||||
container=self,
|
||||
attribute_getter="temperature_warning",
|
||||
type=FeatureType.BinarySensor,
|
||||
type=Feature.Type.BinarySensor,
|
||||
icon="mdi:alert",
|
||||
)
|
||||
)
|
||||
@ -46,6 +46,7 @@ class TemperatureSensor(SmartModule):
|
||||
container=self,
|
||||
attribute_getter="temperature_unit",
|
||||
attribute_setter="set_temperature_unit",
|
||||
type=Feature.Type.Choice,
|
||||
)
|
||||
)
|
||||
# TODO: use temperature_unit for feature creation
|
||||
|
@ -26,6 +26,7 @@ class TemperatureControl(SmartModule):
|
||||
attribute_getter="target_temperature",
|
||||
attribute_setter="set_target_temperature",
|
||||
icon="mdi:thermometer",
|
||||
type=Feature.Type.Number,
|
||||
)
|
||||
)
|
||||
# TODO: this might belong into its own module, temperature_correction?
|
||||
@ -38,6 +39,7 @@ class TemperatureControl(SmartModule):
|
||||
attribute_setter="set_temperature_offset",
|
||||
minimum_value=-10,
|
||||
maximum_value=10,
|
||||
type=Feature.Type.Number,
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -13,7 +13,7 @@ from ..device_type import DeviceType
|
||||
from ..deviceconfig import DeviceConfig
|
||||
from ..emeterstatus import EmeterStatus
|
||||
from ..exceptions import AuthenticationError, DeviceError, KasaException, SmartErrorCode
|
||||
from ..feature import Feature, FeatureType
|
||||
from ..feature import Feature
|
||||
from ..smartprotocol import SmartProtocol
|
||||
from .modules import * # noqa: F403
|
||||
|
||||
@ -191,7 +191,7 @@ class SmartDevice(Device):
|
||||
"State",
|
||||
attribute_getter="is_on",
|
||||
attribute_setter="set_state",
|
||||
type=FeatureType.Switch,
|
||||
type=Feature.Type.Switch,
|
||||
category=Feature.Category.Primary,
|
||||
)
|
||||
)
|
||||
@ -236,7 +236,7 @@ class SmartDevice(Device):
|
||||
"Overheated",
|
||||
attribute_getter=lambda x: x._info["overheated"],
|
||||
icon="mdi:heat-wave",
|
||||
type=FeatureType.BinarySensor,
|
||||
type=Feature.Type.BinarySensor,
|
||||
category=Feature.Category.Debug,
|
||||
)
|
||||
)
|
||||
|
@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from kasa import Feature, FeatureType
|
||||
from kasa import Feature
|
||||
|
||||
|
||||
class DummyDevice:
|
||||
@ -18,7 +18,7 @@ def dummy_feature() -> Feature:
|
||||
attribute_setter="dummysetter",
|
||||
container=None,
|
||||
icon="mdi:dummy",
|
||||
type=FeatureType.BinarySensor,
|
||||
type=Feature.Type.Switch,
|
||||
unit="dummyunit",
|
||||
)
|
||||
return feat
|
||||
@ -32,10 +32,21 @@ def test_feature_api(dummy_feature: Feature):
|
||||
assert dummy_feature.attribute_setter == "dummysetter"
|
||||
assert dummy_feature.container is None
|
||||
assert dummy_feature.icon == "mdi:dummy"
|
||||
assert dummy_feature.type == FeatureType.BinarySensor
|
||||
assert dummy_feature.type == Feature.Type.Switch
|
||||
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):
|
||||
"""Verify that property gets accessed on *value* access."""
|
||||
dummy_feature.attribute_getter = "test_prop"
|
||||
@ -91,7 +102,7 @@ async def test_feature_action(mocker):
|
||||
attribute_setter="call_action",
|
||||
container=None,
|
||||
icon="mdi:dummy",
|
||||
type=FeatureType.Action,
|
||||
type=Feature.Type.Action,
|
||||
)
|
||||
mock_call_action = mocker.patch.object(feat.device, "call_action", create=True)
|
||||
assert feat.value == "<Action>"
|
||||
|
Loading…
Reference in New Issue
Block a user