Initial implementation for modularized smartdevice (#757)

The initial steps to modularize the smartdevice. Modules are initialized based on the component negotiation, and each module can indicate which features it supports and which queries should be run during the update cycle.
This commit is contained in:
Teemu R
2024-02-19 18:01:31 +01:00
committed by GitHub
parent e86dcb6bf5
commit 11719991c0
21 changed files with 408 additions and 156 deletions

View File

@@ -4,7 +4,6 @@ from .antitheft import Antitheft
from .cloud import Cloud
from .countdown import Countdown
from .emeter import Emeter
from .module import IotModule
from .motion import Motion
from .rulemodule import Rule, RuleModule
from .schedule import Schedule
@@ -17,7 +16,6 @@ __all__ = [
"Cloud",
"Countdown",
"Emeter",
"IotModule",
"Motion",
"Rule",
"RuleModule",

View File

@@ -1,5 +1,5 @@
"""Implementation of the ambient light (LAS) module found in some dimmers."""
from .module import IotModule
from ..iotmodule import IotModule
# TODO create tests and use the config reply there
# [{"hw_id":0,"enable":0,"dark_index":1,"min_adc":0,"max_adc":2450,

View File

@@ -5,7 +5,7 @@ except ImportError:
from pydantic import BaseModel
from ...feature import Feature, FeatureType
from .module import IotModule
from ..iotmodule import IotModule
class CloudInfo(BaseModel):

View File

@@ -1,96 +0,0 @@
"""Base class for all module implementations."""
import collections
import logging
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Dict
from ...exceptions import SmartDeviceException
from ...feature import Feature
if TYPE_CHECKING:
from kasa.iot import IotDevice
_LOGGER = logging.getLogger(__name__)
# TODO: This is used for query construcing
def merge(d, u):
"""Update dict recursively."""
for k, v in u.items():
if isinstance(v, collections.abc.Mapping):
d[k] = merge(d.get(k, {}), v)
else:
d[k] = v
return d
class IotModule(ABC):
"""Base class implemention for all modules.
The base classes should implement `query` to return the query they want to be
executed during the regular update cycle.
"""
def __init__(self, device: "IotDevice", module: str):
self._device = device
self._module = module
self._module_features: Dict[str, Feature] = {}
def _add_feature(self, feature: Feature):
"""Add module feature."""
feature_name = f"{self._module}_{feature.name}"
if feature_name in self._module_features:
raise SmartDeviceException("Duplicate name detected %s" % feature_name)
self._module_features[feature_name] = feature
@abstractmethod
def query(self):
"""Query to execute during the update cycle.
The inheriting modules implement this to include their wanted
queries to the query that gets executed when Device.update() gets called.
"""
@property
def estimated_query_response_size(self):
"""Estimated maximum size of query response.
The inheriting modules implement this to estimate how large a query response
will be so that queries can be split should an estimated response be too large
"""
return 256 # Estimate for modules that don't specify
@property
def data(self):
"""Return the module specific raw data from the last update."""
if self._module not in self._device._last_update:
raise SmartDeviceException(
f"You need to call update() prior accessing module data"
f" for '{self._module}'"
)
return self._device._last_update[self._module]
@property
def is_supported(self) -> bool:
"""Return whether the module is supported by the device."""
if self._module not in self._device._last_update:
_LOGGER.debug("Initial update, so consider supported: %s", self._module)
return True
return "err_code" not in self.data
def call(self, method, params=None):
"""Call the given method with the given parameters."""
return self._device._query_helper(self._module, method, params)
def query_for_command(self, query, params=None):
"""Create a request object for the given parameters."""
return self._device._create_request(self._module, query, params)
def __repr__(self) -> str:
return (
f"<Module {self.__class__.__name__} ({self._module})"
f" for {self._device.host}>"
)

View File

@@ -3,7 +3,7 @@ from enum import Enum
from typing import Optional
from ...exceptions import SmartDeviceException
from .module import IotModule
from ..iotmodule import IotModule
class Range(Enum):

View File

@@ -9,7 +9,7 @@ except ImportError:
from pydantic import BaseModel
from .module import IotModule, merge
from ..iotmodule import IotModule, merge
class Action(Enum):

View File

@@ -2,7 +2,7 @@
from datetime import datetime
from ...exceptions import SmartDeviceException
from .module import IotModule, merge
from ..iotmodule import IotModule, merge
class Time(IotModule):

View File

@@ -2,7 +2,7 @@
from datetime import datetime
from typing import Dict
from .module import IotModule, merge
from ..iotmodule import IotModule, merge
class Usage(IotModule):