Add common Thermostat module (#977)

This commit is contained in:
Steven B.
2024-11-26 09:37:15 +00:00
committed by GitHub
parent cb4e28394d
commit 3dfada7575
10 changed files with 208 additions and 14 deletions

View File

@@ -36,6 +36,7 @@ from kasa.exceptions import (
)
from kasa.feature import Feature
from kasa.interfaces.light import HSV, ColorTempRange, Light, LightState
from kasa.interfaces.thermostat import Thermostat, ThermostatState
from kasa.module import Module
from kasa.protocols import BaseProtocol, IotProtocol, SmartProtocol
from kasa.protocols.iotprotocol import _deprecated_TPLinkSmartHomeProtocol # noqa: F401
@@ -72,6 +73,8 @@ __all__ = [
"DeviceConnectionParameters",
"DeviceEncryptionType",
"DeviceFamily",
"ThermostatState",
"Thermostat",
]
from . import iot

View File

@@ -6,6 +6,7 @@ from .led import Led
from .light import Light, LightState
from .lighteffect import LightEffect
from .lightpreset import LightPreset
from .thermostat import Thermostat, ThermostatState
from .time import Time
__all__ = [
@@ -16,5 +17,7 @@ __all__ = [
"LightEffect",
"LightState",
"LightPreset",
"Thermostat",
"ThermostatState",
"Time",
]

View File

@@ -0,0 +1,65 @@
"""Interact with a TPLink Thermostat."""
from __future__ import annotations
from abc import ABC, abstractmethod
from enum import Enum
from typing import Annotated, Literal
from ..module import FeatureAttribute, Module
class ThermostatState(Enum):
"""Thermostat state."""
Heating = "heating"
Calibrating = "progress_calibration"
Idle = "idle"
Off = "off"
Unknown = "unknown"
class Thermostat(Module, ABC):
"""Base class for TP-Link Thermostat."""
@property
@abstractmethod
def state(self) -> bool:
"""Return thermostat state."""
@abstractmethod
async def set_state(self, enabled: bool) -> dict:
"""Set thermostat state."""
@property
@abstractmethod
def mode(self) -> ThermostatState:
"""Return thermostat state."""
@property
@abstractmethod
def target_temperature(self) -> Annotated[float, FeatureAttribute()]:
"""Return target temperature."""
@abstractmethod
async def set_target_temperature(
self, target: float
) -> Annotated[dict, FeatureAttribute()]:
"""Set target temperature."""
@property
@abstractmethod
def temperature(self) -> Annotated[float, FeatureAttribute()]:
"""Return current humidity in percentage."""
return self._device.sys_info["current_temp"]
@property
@abstractmethod
def temperature_unit(self) -> Literal["celsius", "fahrenheit"]:
"""Return current temperature unit."""
@abstractmethod
async def set_temperature_unit(
self, unit: Literal["celsius", "fahrenheit"]
) -> dict:
"""Set the device temperature unit."""

View File

@@ -96,6 +96,7 @@ class Module(ABC):
Led: Final[ModuleName[interfaces.Led]] = ModuleName("Led")
Light: Final[ModuleName[interfaces.Light]] = ModuleName("Light")
LightPreset: Final[ModuleName[interfaces.LightPreset]] = ModuleName("LightPreset")
Thermostat: Final[ModuleName[interfaces.Thermostat]] = ModuleName("Thermostat")
Time: Final[ModuleName[interfaces.Time]] = ModuleName("Time")
# IOT only Modules

View File

@@ -27,6 +27,7 @@ from .motionsensor import MotionSensor
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 +62,6 @@ __all__ = [
"MotionSensor",
"TriggerLogs",
"FrostProtection",
"Thermostat",
"SmartLightEffect",
]

View File

@@ -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,

View 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)

View File

@@ -24,6 +24,7 @@ from .modules import (
DeviceModule,
Firmware,
Light,
Thermostat,
Time,
)
from .smartmodule import SmartModule
@@ -361,6 +362,11 @@ class SmartDevice(Device):
or Module.ColorTemperature in self._modules
):
self._modules[Light.__name__] = Light(self, "light")
if (
Module.TemperatureControl in self._modules
and Module.TemperatureSensor in self._modules
):
self._modules[Thermostat.__name__] = Thermostat(self, "thermostat")
async def _initialize_features(self) -> None:
"""Initialize device features."""