mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-08-06 10:44:04 +00:00
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:
7
kasa/smart/modules/__init__.py
Normal file
7
kasa/smart/modules/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""Modules for SMART devices."""
|
||||
from .childdevicemodule import ChildDeviceModule
|
||||
from .devicemodule import DeviceModule
|
||||
from .energymodule import EnergyModule
|
||||
from .timemodule import TimeModule
|
||||
|
||||
__all__ = ["TimeModule", "EnergyModule", "DeviceModule", "ChildDeviceModule"]
|
9
kasa/smart/modules/childdevicemodule.py
Normal file
9
kasa/smart/modules/childdevicemodule.py
Normal file
@@ -0,0 +1,9 @@
|
||||
"""Implementation for child devices."""
|
||||
from ..smartmodule import SmartModule
|
||||
|
||||
|
||||
class ChildDeviceModule(SmartModule):
|
||||
"""Implementation for child devices."""
|
||||
|
||||
REQUIRED_COMPONENT = "child_device"
|
||||
QUERY_GETTER_NAME = "get_child_device_list"
|
21
kasa/smart/modules/devicemodule.py
Normal file
21
kasa/smart/modules/devicemodule.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""Implementation of device module."""
|
||||
from typing import Dict
|
||||
|
||||
from ..smartmodule import SmartModule
|
||||
|
||||
|
||||
class DeviceModule(SmartModule):
|
||||
"""Implementation of device module."""
|
||||
|
||||
REQUIRED_COMPONENT = "device"
|
||||
|
||||
def query(self) -> Dict:
|
||||
"""Query to execute during the update cycle."""
|
||||
query = {
|
||||
"get_device_info": None,
|
||||
}
|
||||
# Device usage is not available on older firmware versions
|
||||
if self._device._components[self.REQUIRED_COMPONENT] >= 2:
|
||||
query["get_device_usage"] = None
|
||||
|
||||
return query
|
88
kasa/smart/modules/energymodule.py
Normal file
88
kasa/smart/modules/energymodule.py
Normal file
@@ -0,0 +1,88 @@
|
||||
"""Implementation of energy monitoring module."""
|
||||
from typing import TYPE_CHECKING, Dict, Optional
|
||||
|
||||
from ...emeterstatus import EmeterStatus
|
||||
from ...feature import Feature
|
||||
from ..smartmodule import SmartModule
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..smartdevice import SmartDevice
|
||||
|
||||
|
||||
class EnergyModule(SmartModule):
|
||||
"""Implementation of energy monitoring module."""
|
||||
|
||||
REQUIRED_COMPONENT = "energy_monitoring"
|
||||
|
||||
def __init__(self, device: "SmartDevice", module: str):
|
||||
super().__init__(device, module)
|
||||
self._add_feature(
|
||||
Feature(
|
||||
device,
|
||||
name="Current consumption",
|
||||
attribute_getter="current_power",
|
||||
container=self,
|
||||
)
|
||||
) # W or mW?
|
||||
self._add_feature(
|
||||
Feature(
|
||||
device,
|
||||
name="Today's consumption",
|
||||
attribute_getter="emeter_today",
|
||||
container=self,
|
||||
)
|
||||
) # Wh or kWh?
|
||||
self._add_feature(
|
||||
Feature(
|
||||
device,
|
||||
name="This month's consumption",
|
||||
attribute_getter="emeter_this_month",
|
||||
container=self,
|
||||
)
|
||||
) # Wh or kWH?
|
||||
|
||||
def query(self) -> Dict:
|
||||
"""Query to execute during the update cycle."""
|
||||
return {
|
||||
"get_energy_usage": None,
|
||||
# The current_power in get_energy_usage is more precise (mw vs. w),
|
||||
# making this rather useless, but maybe there are version differences?
|
||||
"get_current_power": None,
|
||||
}
|
||||
|
||||
@property
|
||||
def current_power(self):
|
||||
"""Current power."""
|
||||
return self.emeter_realtime.power
|
||||
|
||||
@property
|
||||
def energy(self):
|
||||
"""Return get_energy_usage results."""
|
||||
return self.data["get_energy_usage"]
|
||||
|
||||
@property
|
||||
def emeter_realtime(self):
|
||||
"""Get the emeter status."""
|
||||
# TODO: Perhaps we should get rid of emeterstatus altogether for smartdevices
|
||||
return EmeterStatus(
|
||||
{
|
||||
"power_mw": self.energy.get("current_power"),
|
||||
"total": self._convert_energy_data(
|
||||
self.energy.get("today_energy"), 1 / 1000
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
@property
|
||||
def emeter_this_month(self) -> Optional[float]:
|
||||
"""Get the emeter value for this month."""
|
||||
return self._convert_energy_data(self.energy.get("month_energy"), 1 / 1000)
|
||||
|
||||
@property
|
||||
def emeter_today(self) -> Optional[float]:
|
||||
"""Get the emeter value for today."""
|
||||
return self._convert_energy_data(self.energy.get("today_energy"), 1 / 1000)
|
||||
|
||||
def _convert_energy_data(self, data, scale) -> Optional[float]:
|
||||
"""Return adjusted emeter information."""
|
||||
return data if not data else data * scale
|
52
kasa/smart/modules/timemodule.py
Normal file
52
kasa/smart/modules/timemodule.py
Normal file
@@ -0,0 +1,52 @@
|
||||
"""Implementation of time module."""
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from time import mktime
|
||||
from typing import TYPE_CHECKING, cast
|
||||
|
||||
from ...feature import Feature
|
||||
from ..smartmodule import SmartModule
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..smartdevice import SmartDevice
|
||||
|
||||
|
||||
class TimeModule(SmartModule):
|
||||
"""Implementation of device_local_time."""
|
||||
|
||||
REQUIRED_COMPONENT = "time"
|
||||
QUERY_GETTER_NAME = "get_device_time"
|
||||
|
||||
def __init__(self, device: "SmartDevice", module: str):
|
||||
super().__init__(device, module)
|
||||
|
||||
self._add_feature(
|
||||
Feature(
|
||||
device=device,
|
||||
name="Time",
|
||||
attribute_getter="time",
|
||||
container=self,
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def time(self) -> datetime:
|
||||
"""Return device's current datetime."""
|
||||
td = timedelta(minutes=cast(float, self.data.get("time_diff")))
|
||||
if self.data.get("region"):
|
||||
tz = timezone(td, str(self.data.get("region")))
|
||||
else:
|
||||
# in case the device returns a blank region this will result in the
|
||||
# tzname being a UTC offset
|
||||
tz = timezone(td)
|
||||
return datetime.fromtimestamp(
|
||||
cast(float, self.data.get("timestamp")),
|
||||
tz=tz,
|
||||
)
|
||||
|
||||
async def set_time(self, dt: datetime):
|
||||
"""Set device time."""
|
||||
unixtime = mktime(dt.timetuple())
|
||||
return await self.call(
|
||||
"set_device_time",
|
||||
{"timestamp": unixtime, "time_diff": dt.utcoffset(), "region": dt.tzname()},
|
||||
)
|
Reference in New Issue
Block a user