mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-11-06 07:31:55 +00:00
Merge remote-tracking branch 'upstream/master' into feat/light_module_feats
This commit is contained in:
@@ -24,9 +24,11 @@ from .lightpreset import LightPreset
|
||||
from .lightstripeffect import LightStripEffect
|
||||
from .lighttransition import LightTransition
|
||||
from .motionsensor import MotionSensor
|
||||
from .overheatprotection import OverheatProtection
|
||||
from .reportmode import ReportMode
|
||||
from .temperaturecontrol import TemperatureControl
|
||||
from .temperaturesensor import TemperatureSensor
|
||||
from .thermostat import Thermostat
|
||||
from .time import Time
|
||||
from .triggerlogs import TriggerLogs
|
||||
from .waterleaksensor import WaterleakSensor
|
||||
@@ -61,5 +63,7 @@ __all__ = [
|
||||
"MotionSensor",
|
||||
"TriggerLogs",
|
||||
"FrostProtection",
|
||||
"Thermostat",
|
||||
"SmartLightEffect",
|
||||
"OverheatProtection",
|
||||
]
|
||||
|
||||
@@ -10,7 +10,7 @@ class ContactSensor(SmartModule):
|
||||
"""Implementation of contact sensor module."""
|
||||
|
||||
REQUIRED_COMPONENT = None # we depend on availability of key
|
||||
REQUIRED_KEY_ON_PARENT = "open"
|
||||
SYSINFO_LOOKUP_KEYS = ["open"]
|
||||
|
||||
def _initialize_features(self) -> None:
|
||||
"""Initialize features after the initial update."""
|
||||
|
||||
@@ -75,8 +75,12 @@ class Energy(SmartModule, EnergyInterface):
|
||||
|
||||
async def get_status(self) -> EmeterStatus:
|
||||
"""Return real-time statistics."""
|
||||
res = await self.call("get_energy_usage")
|
||||
return self._get_status_from_energy(res["get_energy_usage"])
|
||||
if "get_emeter_data" in self.data:
|
||||
res = await self.call("get_emeter_data")
|
||||
return EmeterStatus(res["get_emeter_data"])
|
||||
else:
|
||||
res = await self.call("get_energy_usage")
|
||||
return self._get_status_from_energy(res["get_energy_usage"])
|
||||
|
||||
@property
|
||||
@raise_if_update_error
|
||||
|
||||
@@ -24,6 +24,7 @@ class LightTransition(SmartModule):
|
||||
REQUIRED_COMPONENT = "on_off_gradually"
|
||||
QUERY_GETTER_NAME = "get_on_off_gradually_info"
|
||||
MINIMUM_UPDATE_INTERVAL_SECS = 60
|
||||
# v3 added max_duration, we default to 60 when it's not available
|
||||
MAXIMUM_DURATION = 60
|
||||
|
||||
# Key in sysinfo that indicates state can be retrieved from there.
|
||||
@@ -144,10 +145,22 @@ class LightTransition(SmartModule):
|
||||
return await self.call("set_on_off_gradually_info", {"enable": enable})
|
||||
else:
|
||||
on = await self.call(
|
||||
"set_on_off_gradually_info", {"on_state": {"enable": enable}}
|
||||
"set_on_off_gradually_info",
|
||||
{
|
||||
"on_state": {
|
||||
"enable": enable,
|
||||
"duration": self._on_state["duration"],
|
||||
}
|
||||
},
|
||||
)
|
||||
off = await self.call(
|
||||
"set_on_off_gradually_info", {"off_state": {"enable": enable}}
|
||||
"set_on_off_gradually_info",
|
||||
{
|
||||
"off_state": {
|
||||
"enable": enable,
|
||||
"duration": self._off_state["duration"],
|
||||
}
|
||||
},
|
||||
)
|
||||
return {**on, **off}
|
||||
|
||||
@@ -167,7 +180,6 @@ class LightTransition(SmartModule):
|
||||
@property
|
||||
def _turn_on_transition_max(self) -> int:
|
||||
"""Maximum turn on duration."""
|
||||
# v3 added max_duration, we default to 60 when it's not available
|
||||
return self._on_state["max_duration"]
|
||||
|
||||
@allow_update_after
|
||||
@@ -184,7 +196,7 @@ class LightTransition(SmartModule):
|
||||
if seconds <= 0:
|
||||
return await self.call(
|
||||
"set_on_off_gradually_info",
|
||||
{"on_state": {"enable": False}},
|
||||
{"on_state": {"enable": False, "duration": self._on_state["duration"]}},
|
||||
)
|
||||
|
||||
return await self.call(
|
||||
@@ -220,7 +232,12 @@ class LightTransition(SmartModule):
|
||||
if seconds <= 0:
|
||||
return await self.call(
|
||||
"set_on_off_gradually_info",
|
||||
{"off_state": {"enable": False}},
|
||||
{
|
||||
"off_state": {
|
||||
"enable": False,
|
||||
"duration": self._off_state["duration"],
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
return await self.call(
|
||||
|
||||
41
kasa/smart/modules/overheatprotection.py
Normal file
41
kasa/smart/modules/overheatprotection.py
Normal file
@@ -0,0 +1,41 @@
|
||||
"""Overheat module."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from ...feature import Feature
|
||||
from ..smartmodule import SmartModule
|
||||
|
||||
|
||||
class OverheatProtection(SmartModule):
|
||||
"""Implementation for overheat_protection."""
|
||||
|
||||
SYSINFO_LOOKUP_KEYS = ["overheated", "overheat_status"]
|
||||
|
||||
def _initialize_features(self) -> None:
|
||||
"""Initialize features after the initial update."""
|
||||
self._add_feature(
|
||||
Feature(
|
||||
self._device,
|
||||
container=self,
|
||||
id="overheated",
|
||||
name="Overheated",
|
||||
attribute_getter="overheated",
|
||||
icon="mdi:heat-wave",
|
||||
type=Feature.Type.BinarySensor,
|
||||
category=Feature.Category.Info,
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def overheated(self) -> bool:
|
||||
"""Return True if device reports overheating."""
|
||||
if (value := self._device.sys_info.get("overheat_status")) is not None:
|
||||
# Value can be normal, cooldown, or overheated.
|
||||
# We report all but normal as overheated.
|
||||
return value != "normal"
|
||||
|
||||
return self._device.sys_info["overheated"]
|
||||
|
||||
def query(self) -> dict:
|
||||
"""Query to execute during the update cycle."""
|
||||
return {}
|
||||
@@ -3,24 +3,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from enum import Enum
|
||||
|
||||
from ...feature import Feature
|
||||
from ...interfaces.thermostat import ThermostatState
|
||||
from ..smartmodule import SmartModule
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ThermostatState(Enum):
|
||||
"""Thermostat state."""
|
||||
|
||||
Heating = "heating"
|
||||
Calibrating = "progress_calibration"
|
||||
Idle = "idle"
|
||||
Off = "off"
|
||||
Unknown = "unknown"
|
||||
|
||||
|
||||
class TemperatureControl(SmartModule):
|
||||
"""Implementation of temperature module."""
|
||||
|
||||
@@ -56,7 +46,6 @@ class TemperatureControl(SmartModule):
|
||||
category=Feature.Category.Config,
|
||||
)
|
||||
)
|
||||
|
||||
self._add_feature(
|
||||
Feature(
|
||||
self._device,
|
||||
@@ -69,7 +58,6 @@ class TemperatureControl(SmartModule):
|
||||
type=Feature.Type.Switch,
|
||||
)
|
||||
)
|
||||
|
||||
self._add_feature(
|
||||
Feature(
|
||||
self._device,
|
||||
|
||||
74
kasa/smart/modules/thermostat.py
Normal file
74
kasa/smart/modules/thermostat.py
Normal file
@@ -0,0 +1,74 @@
|
||||
"""Module for a Thermostat."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Annotated, Literal
|
||||
|
||||
from ...feature import Feature
|
||||
from ...interfaces.thermostat import Thermostat as ThermostatInterface
|
||||
from ...interfaces.thermostat import ThermostatState
|
||||
from ...module import FeatureAttribute, Module
|
||||
from ..smartmodule import SmartModule
|
||||
|
||||
|
||||
class Thermostat(SmartModule, ThermostatInterface):
|
||||
"""Implementation of a Thermostat."""
|
||||
|
||||
@property
|
||||
def _all_features(self) -> dict[str, Feature]:
|
||||
"""Get the features for this module and any sub modules."""
|
||||
ret: dict[str, Feature] = {}
|
||||
if temp_control := self._device.modules.get(Module.TemperatureControl):
|
||||
ret.update(**temp_control._module_features)
|
||||
if temp_sensor := self._device.modules.get(Module.TemperatureSensor):
|
||||
ret.update(**temp_sensor._module_features)
|
||||
return ret
|
||||
|
||||
def query(self) -> dict:
|
||||
"""Query to execute during the update cycle."""
|
||||
return {}
|
||||
|
||||
@property
|
||||
def state(self) -> bool:
|
||||
"""Return thermostat state."""
|
||||
return self._device.modules[Module.TemperatureControl].state
|
||||
|
||||
async def set_state(self, enabled: bool) -> dict:
|
||||
"""Set thermostat state."""
|
||||
return await self._device.modules[Module.TemperatureControl].set_state(enabled)
|
||||
|
||||
@property
|
||||
def mode(self) -> ThermostatState:
|
||||
"""Return thermostat state."""
|
||||
return self._device.modules[Module.TemperatureControl].mode
|
||||
|
||||
@property
|
||||
def target_temperature(self) -> Annotated[float, FeatureAttribute()]:
|
||||
"""Return target temperature."""
|
||||
return self._device.modules[Module.TemperatureControl].target_temperature
|
||||
|
||||
async def set_target_temperature(
|
||||
self, target: float
|
||||
) -> Annotated[dict, FeatureAttribute()]:
|
||||
"""Set target temperature."""
|
||||
return await self._device.modules[
|
||||
Module.TemperatureControl
|
||||
].set_target_temperature(target)
|
||||
|
||||
@property
|
||||
def temperature(self) -> Annotated[float, FeatureAttribute()]:
|
||||
"""Return current humidity in percentage."""
|
||||
return self._device.modules[Module.TemperatureSensor].temperature
|
||||
|
||||
@property
|
||||
def temperature_unit(self) -> Literal["celsius", "fahrenheit"]:
|
||||
"""Return current temperature unit."""
|
||||
return self._device.modules[Module.TemperatureSensor].temperature_unit
|
||||
|
||||
async def set_temperature_unit(
|
||||
self, unit: Literal["celsius", "fahrenheit"]
|
||||
) -> dict:
|
||||
"""Set the device temperature unit."""
|
||||
return await self._device.modules[
|
||||
Module.TemperatureSensor
|
||||
].set_temperature_unit(unit)
|
||||
Reference in New Issue
Block a user