Merge remote-tracking branch 'upstream/master' into feat/light_module_feats

This commit is contained in:
Steven B
2024-12-11 13:21:46 +00:00
78 changed files with 5065 additions and 854 deletions

View File

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

View File

@@ -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."""

View File

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

View File

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

View 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 {}

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)