Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Allow erroring modules to recover #1080

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 45 additions & 28 deletions kasa/feature.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
import logging
from dataclasses import dataclass
from enum import Enum, auto
from functools import cached_property
from typing import TYPE_CHECKING, Any, Callable

if TYPE_CHECKING:
Expand Down Expand Up @@ -142,50 +143,28 @@ class Category(Enum):
container: Any = None
#: Icon suggestion
icon: str | None = None
#: Unit, if applicable
unit: str | None = None
#: Attribute containing the name of the unit getter property.
#: If set, this property will be used to set *unit*.
unit_getter: str | None = None
#: If set, this property will be used to get the *unit*.
unit_getter: str | Callable[[], str] | None = None
#: Category hint for downstreams
category: Feature.Category = Category.Unset

# Display hints offer a way suggest how the value should be shown to users
#: Hint to help rounding the sensor values to given after-comma digits
precision_hint: int | None = None

# Number-specific attributes
#: Minimum value
minimum_value: int = 0
#: Maximum value
maximum_value: int = DEFAULT_MAX
#: Attribute containing the name of the range getter property.
#: If set, this property will be used to set *minimum_value* and *maximum_value*.
range_getter: str | None = None
range_getter: str | Callable[[], tuple[int, int]] | None = None

# Choice-specific attributes
#: List of choices as enum
choices: list[str] | None = None
#: Attribute name of the choices getter property.
#: If set, this property will be used to set *choices*.
choices_getter: str | None = None
#: If set, this property will be used to get *choices*.
choices_getter: str | Callable[[], list[str]] | None = None

def __post_init__(self):
"""Handle late-binding of members."""
# Populate minimum & maximum values, if range_getter is given
container = self.container if self.container is not None else self.device
if self.range_getter is not None:
self.minimum_value, self.maximum_value = getattr(
container, self.range_getter
)

# Populate choices, if choices_getter is given
if self.choices_getter is not None:
self.choices = getattr(container, self.choices_getter)

# Populate unit, if unit_getter is given
if self.unit_getter is not None:
self.unit = getattr(container, self.unit_getter)
self._container = self.container if self.container is not None else self.device

# Set the category, if unset
if self.category is Feature.Category.Unset:
Expand All @@ -208,6 +187,44 @@ def __post_init__(self):
f"Read-only feat defines attribute_setter: {self.name} ({self.id}):"
)

def _get_property_value(self, getter):
if getter is None:
return None
if isinstance(getter, str):
return getattr(self._container, getter)
if callable(getter):
return getter()
raise ValueError("Invalid getter: %s", getter) # pragma: no cover

@property
def choices(self) -> list[str] | None:
"""List of choices."""
return self._get_property_value(self.choices_getter)

@property
def unit(self) -> str | None:
"""Unit if applicable."""
return self._get_property_value(self.unit_getter)

@cached_property
def range(self) -> tuple[int, int] | None:
"""Range of values if applicable."""
return self._get_property_value(self.range_getter)

@cached_property
def maximum_value(self) -> int:
"""Maximum value."""
if range := self.range:
return range[1]
return self.DEFAULT_MAX

@cached_property
def minimum_value(self) -> int:
"""Minimum value."""
if range := self.range:
return range[0]
return 0

@property
def value(self):
"""Return the current value."""
Expand Down
12 changes: 6 additions & 6 deletions kasa/interfaces/energy.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def _initialize_features(self):
name="Current consumption",
attribute_getter="current_consumption",
container=self,
unit="W",
unit_getter=lambda: "W",
id="current_consumption",
precision_hint=1,
category=Feature.Category.Primary,
Expand All @@ -53,7 +53,7 @@ def _initialize_features(self):
name="Today's consumption",
attribute_getter="consumption_today",
container=self,
unit="kWh",
unit_getter=lambda: "kWh",
id="consumption_today",
precision_hint=3,
category=Feature.Category.Info,
Expand All @@ -67,7 +67,7 @@ def _initialize_features(self):
name="This month's consumption",
attribute_getter="consumption_this_month",
container=self,
unit="kWh",
unit_getter=lambda: "kWh",
precision_hint=3,
category=Feature.Category.Info,
type=Feature.Type.Sensor,
Expand All @@ -80,7 +80,7 @@ def _initialize_features(self):
name="Total consumption since reboot",
attribute_getter="consumption_total",
container=self,
unit="kWh",
unit_getter=lambda: "kWh",
id="consumption_total",
precision_hint=3,
category=Feature.Category.Info,
Expand All @@ -94,7 +94,7 @@ def _initialize_features(self):
name="Voltage",
attribute_getter="voltage",
container=self,
unit="V",
unit_getter=lambda: "V",
id="voltage",
precision_hint=1,
category=Feature.Category.Primary,
Expand All @@ -107,7 +107,7 @@ def _initialize_features(self):
name="Current",
attribute_getter="current",
container=self,
unit="A",
unit_getter=lambda: "A",
id="current",
precision_hint=2,
category=Feature.Category.Primary,
Expand Down
2 changes: 1 addition & 1 deletion kasa/iot/iotdevice.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ async def _initialize_features(self):
name="RSSI",
attribute_getter="rssi",
icon="mdi:signal",
unit="dBm",
unit_getter=lambda: "dBm",
category=Feature.Category.Debug,
type=Feature.Type.Sensor,
)
Expand Down
2 changes: 1 addition & 1 deletion kasa/iot/modules/ambientlight.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def __init__(self, device, module):
attribute_getter="ambientlight_brightness",
type=Feature.Type.Sensor,
category=Feature.Category.Primary,
unit="%",
unit_getter=lambda: "%",
)
)

Expand Down
3 changes: 1 addition & 2 deletions kasa/iot/modules/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ def _initialize_features(self):
container=self,
attribute_getter="brightness",
attribute_setter="set_brightness",
minimum_value=BRIGHTNESS_MIN,
maximum_value=BRIGHTNESS_MAX,
range_getter=lambda: (BRIGHTNESS_MIN, BRIGHTNESS_MAX),
type=Feature.Type.Number,
category=Feature.Category.Primary,
)
Expand Down
2 changes: 1 addition & 1 deletion kasa/smart/modules/alarm.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def _initialize_features(self):
attribute_setter="set_alarm_volume",
category=Feature.Category.Config,
type=Feature.Type.Choice,
choices=["low", "normal", "high"],
choices_getter=lambda: ["low", "normal", "high"],
)
)
self._add_feature(
Expand Down
2 changes: 1 addition & 1 deletion kasa/smart/modules/autooff.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def _initialize_features(self):
attribute_getter="delay",
attribute_setter="set_delay",
type=Feature.Type.Number,
unit="min", # ha-friendly unit, see UnitOfTime.MINUTES
unit_getter=lambda: "min", # ha-friendly unit, see UnitOfTime.MINUTES
)
)
self._add_feature(
Expand Down
2 changes: 1 addition & 1 deletion kasa/smart/modules/batterysensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def _initialize_features(self):
container=self,
attribute_getter="battery",
icon="mdi:battery",
unit="%",
unit_getter=lambda: "%",
category=Feature.Category.Info,
type=Feature.Type.Sensor,
)
Expand Down
3 changes: 1 addition & 2 deletions kasa/smart/modules/brightness.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ def _initialize_features(self):
container=self,
attribute_getter="brightness",
attribute_setter="set_brightness",
minimum_value=BRIGHTNESS_MIN,
maximum_value=BRIGHTNESS_MAX,
range_getter=lambda: (BRIGHTNESS_MIN, BRIGHTNESS_MAX),
type=Feature.Type.Number,
category=Feature.Category.Primary,
)
Expand Down
7 changes: 0 additions & 7 deletions kasa/smart/modules/cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,6 @@ class Cloud(SmartModule):
REQUIRED_COMPONENT = "cloud_connect"
MINIMUM_UPDATE_INTERVAL_SECS = 60

def _post_update_hook(self):
"""Perform actions after a device update.

Overrides the default behaviour to disable a module if the query returns
an error because the logic here is to treat that as not connected.
"""

def __init__(self, device: SmartDevice, module: str):
super().__init__(device, module)

Expand Down
10 changes: 9 additions & 1 deletion kasa/smart/modules/energy.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from ...emeterstatus import EmeterStatus
from ...exceptions import KasaException
from ...interfaces.energy import Energy as EnergyInterface
from ..smartmodule import SmartModule
from ..smartmodule import SmartModule, raise_if_update_error


class Energy(SmartModule, EnergyInterface):
Expand All @@ -23,13 +23,15 @@ def query(self) -> dict:
return req

@property
@raise_if_update_error
def current_consumption(self) -> float | None:
"""Current power in watts."""
if (power := self.energy.get("current_power")) is not None:
return power / 1_000
return None

@property
@raise_if_update_error
def energy(self):
"""Return get_energy_usage results."""
if en := self.data.get("get_energy_usage"):
Expand All @@ -45,6 +47,7 @@ def _get_status_from_energy(self, energy) -> EmeterStatus:
)

@property
@raise_if_update_error
def status(self):
"""Get the emeter status."""
return self._get_status_from_energy(self.energy)
Expand All @@ -55,26 +58,31 @@ async def get_status(self):
return self._get_status_from_energy(res["get_energy_usage"])

@property
@raise_if_update_error
def consumption_this_month(self) -> float | None:
"""Get the emeter value for this month in kWh."""
return self.energy.get("month_energy") / 1_000

@property
@raise_if_update_error
def consumption_today(self) -> float | None:
"""Get the emeter value for today in kWh."""
return self.energy.get("today_energy") / 1_000

@property
@raise_if_update_error
def consumption_total(self) -> float | None:
"""Return total consumption since last reboot in kWh."""
return None

@property
@raise_if_update_error
def current(self) -> float | None:
"""Return the current in A."""
return None

@property
@raise_if_update_error
def voltage(self) -> float | None:
"""Get the current voltage in V."""
return None
Expand Down
3 changes: 1 addition & 2 deletions kasa/smart/modules/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ def __init__(self, device: SmartDevice, module: str):
attribute_setter="set_fan_speed_level",
icon="mdi:fan",
type=Feature.Type.Number,
minimum_value=0,
maximum_value=4,
range_getter=lambda: (0, 4),
category=Feature.Category.Primary,
)
)
Expand Down
2 changes: 1 addition & 1 deletion kasa/smart/modules/humiditysensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def __init__(self, device: SmartDevice, module: str):
container=self,
attribute_getter="humidity",
icon="mdi:water-percent",
unit="%",
unit_getter=lambda: "%",
category=Feature.Category.Primary,
type=Feature.Type.Sensor,
)
Expand Down
4 changes: 2 additions & 2 deletions kasa/smart/modules/lighttransition.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def _initialize_features(self):
attribute_setter="set_turn_on_transition",
icon=icon,
type=Feature.Type.Number,
maximum_value=self._turn_on_transition_max,
range_getter=lambda: (0, self._turn_on_transition_max),
)
)
self._add_feature(
Expand All @@ -86,7 +86,7 @@ def _initialize_features(self):
attribute_setter="set_turn_off_transition",
icon=icon,
type=Feature.Type.Number,
maximum_value=self._turn_off_transition_max,
range_getter=lambda: (0, self._turn_off_transition_max),
)
)

Expand Down
2 changes: 1 addition & 1 deletion kasa/smart/modules/reportmode.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def __init__(self, device: SmartDevice, module: str):
name="Report interval",
container=self,
attribute_getter="report_interval",
unit="s",
unit_getter=lambda: "s",
category=Feature.Category.Debug,
type=Feature.Type.Sensor,
)
Expand Down
3 changes: 1 addition & 2 deletions kasa/smart/modules/temperaturecontrol.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ def _initialize_features(self):
container=self,
attribute_getter="temperature_offset",
attribute_setter="set_temperature_offset",
minimum_value=-10,
maximum_value=10,
range_getter=lambda: (-10, 10),
type=Feature.Type.Number,
category=Feature.Category.Config,
)
Expand Down
2 changes: 1 addition & 1 deletion kasa/smart/modules/temperaturesensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def __init__(self, device: SmartDevice, module: str):
attribute_getter="temperature_unit",
attribute_setter="set_temperature_unit",
type=Feature.Type.Choice,
choices=["celsius", "fahrenheit"],
choices_getter=lambda: ["celsius", "fahrenheit"],
)
)

Expand Down
Loading
Loading