mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-08-06 10:44:04 +00:00
Support for new ks240 fan/light wall switch (#839)
In order to support the ks240 which has children for the fan and light components, this PR adds those modules at the parent level and hides the children so it looks like a single device to consumers. It also decides which modules not to take from the child because the child does not support them even though it say it does. It does this for now via a fixed list, e.g. `Time`, `Firmware` etc. Also adds fixtures from two versions and corresponding tests.
This commit is contained in:
@@ -57,3 +57,7 @@ class Brightness(SmartModule):
|
||||
)
|
||||
|
||||
return await self.call("set_device_info", {"brightness": brightness})
|
||||
|
||||
async def _check_supported(self):
|
||||
"""Additional check to see if the module is supported by the device."""
|
||||
return "brightness" in self.data
|
||||
|
@@ -68,3 +68,7 @@ class FanModule(SmartModule):
|
||||
async def set_sleep_mode(self, on: bool):
|
||||
"""Set sleep mode."""
|
||||
return await self.call("set_device_info", {"fan_sleep_mode_on": on})
|
||||
|
||||
async def _check_supported(self):
|
||||
"""Is the module available on this device."""
|
||||
return "fan_speed_level" in self.data
|
||||
|
@@ -103,6 +103,8 @@ class LightTransitionModule(SmartModule):
|
||||
|
||||
Available only from v2.
|
||||
"""
|
||||
if "fade_on_time" in self._device.sys_info:
|
||||
return self._device.sys_info["fade_on_time"]
|
||||
return self._turn_on["duration"]
|
||||
|
||||
@property
|
||||
@@ -138,6 +140,8 @@ class LightTransitionModule(SmartModule):
|
||||
|
||||
Available only from v2.
|
||||
"""
|
||||
if "fade_off_time" in self._device.sys_info:
|
||||
return self._device.sys_info["fade_off_time"]
|
||||
return self._turn_off["duration"]
|
||||
|
||||
@property
|
||||
@@ -166,3 +170,11 @@ class LightTransitionModule(SmartModule):
|
||||
"set_on_off_gradually_info",
|
||||
{"off_state": {**self._turn_on, "duration": seconds}},
|
||||
)
|
||||
|
||||
def query(self) -> dict:
|
||||
"""Query to execute during the update cycle."""
|
||||
# Some devices have the required info in the device info.
|
||||
if "gradually_on_mode" in self._device.sys_info:
|
||||
return {}
|
||||
else:
|
||||
return {self.QUERY_GETTER_NAME: None}
|
||||
|
@@ -22,6 +22,12 @@ _LOGGER = logging.getLogger(__name__)
|
||||
if TYPE_CHECKING:
|
||||
from .smartmodule import SmartModule
|
||||
|
||||
# List of modules that wall switches with children, i.e. ks240 report on
|
||||
# the child but only work on the parent. See longer note below in _initialize_modules.
|
||||
# This list should be updated when creating new modules that could have the
|
||||
# same issue, homekit perhaps?
|
||||
WALL_SWITCH_PARENT_ONLY_MODULES = [DeviceModule, TimeModule, Firmware, CloudModule] # noqa: F405
|
||||
|
||||
|
||||
class SmartDevice(Device):
|
||||
"""Base class to represent a SMART protocol based device."""
|
||||
@@ -78,6 +84,9 @@ class SmartDevice(Device):
|
||||
@property
|
||||
def children(self) -> Sequence[SmartDevice]:
|
||||
"""Return list of children."""
|
||||
# Wall switches with children report all modules on the parent only
|
||||
if self.device_type == DeviceType.WallSwitch:
|
||||
return []
|
||||
return list(self._children.values())
|
||||
|
||||
def _try_get_response(self, responses: dict, request: str, default=None) -> dict:
|
||||
@@ -162,8 +171,23 @@ class SmartDevice(Device):
|
||||
"""Initialize modules based on component negotiation response."""
|
||||
from .smartmodule import SmartModule
|
||||
|
||||
# Some wall switches (like ks240) are internally presented as having child
|
||||
# devices which report the child's components on the parent's sysinfo, even
|
||||
# when they need to be accessed through the children.
|
||||
# The logic below ensures that such devices report all but whitelisted, the
|
||||
# child modules at the parent level to create an illusion of a single device.
|
||||
if self._parent and self._parent.device_type == DeviceType.WallSwitch:
|
||||
modules = self._parent.modules
|
||||
skip_parent_only_modules = True
|
||||
else:
|
||||
modules = self.modules
|
||||
skip_parent_only_modules = False
|
||||
|
||||
for mod in SmartModule.REGISTERED_MODULES.values():
|
||||
_LOGGER.debug("%s requires %s", mod, mod.REQUIRED_COMPONENT)
|
||||
|
||||
if skip_parent_only_modules and mod in WALL_SWITCH_PARENT_ONLY_MODULES:
|
||||
continue
|
||||
if mod.REQUIRED_COMPONENT in self._components:
|
||||
_LOGGER.debug(
|
||||
"Found required %s, adding %s to modules.",
|
||||
@@ -171,8 +195,8 @@ class SmartDevice(Device):
|
||||
mod.__name__,
|
||||
)
|
||||
module = mod(self, mod.REQUIRED_COMPONENT)
|
||||
if await module._check_supported():
|
||||
self.modules[module.name] = module
|
||||
if module.name not in modules and await module._check_supported():
|
||||
modules[module.name] = module
|
||||
|
||||
async def _initialize_features(self):
|
||||
"""Initialize device features."""
|
||||
@@ -568,6 +592,8 @@ class SmartDevice(Device):
|
||||
return DeviceType.Plug
|
||||
if "light_strip" in components:
|
||||
return DeviceType.LightStrip
|
||||
if "SWITCH" in device_type and "child_device" in components:
|
||||
return DeviceType.WallSwitch
|
||||
if "dimmer_calibration" in components:
|
||||
return DeviceType.Dimmer
|
||||
if "brightness" in components:
|
||||
|
Reference in New Issue
Block a user