python-kasa/kasa/smartstrip.py
Teemu R 72d6205ad2 Cleanup constructors by removing ioloop and protocol arguments (#23)
* Cleanup constructors by removing ioloop and protocol

* force kwarg for other arguments besides the host
2020-01-15 07:46:21 +01:00

191 lines
6.0 KiB
Python
Executable File

"""Module for multi-socket devices (HS300, HS107).
.. todo:: describe how this interfaces with single plugs.
"""
import datetime
import logging
from collections import defaultdict
from typing import Any, DefaultDict, Dict, List
from kasa.smartdevice import DeviceType, requires_update
from kasa.smartplug import SmartPlug
_LOGGER = logging.getLogger(__name__)
class SmartStrip(SmartPlug):
"""Representation of a TP-Link Smart Power Strip.
Usage example when used as library:
```python
p = SmartStrip("192.168.1.105")
# query the state of the strip
await p.update()
print(p.is_on)
# change state of all outlets
await p.turn_on()
await p.turn_off()
# individual outlets are accessible through plugs variable
for plug in p.plugs:
print(f"{p}: {p.is_on}")
# change state of a single outlet
await p.plugs[0].turn_on()
```
Errors reported by the device are raised as SmartDeviceExceptions,
and should be handled by the user of the library.
"""
def __init__(self, host: str, *, cache_ttl: int = 3) -> None:
SmartPlug.__init__(self, host=host, cache_ttl=cache_ttl)
self.emeter_type = "emeter"
self._device_type = DeviceType.Strip
self.plugs: List[SmartPlug] = []
@property # type: ignore
@requires_update
def is_on(self) -> bool:
"""Return if any of the outlets are on."""
for plug in self.plugs:
is_on = plug.is_on
if is_on:
return True
return False
async def update(self):
"""Update some of the attributes.
Needed for methods that are decorated with `requires_update`.
"""
await super().update()
# Initialize the child devices during the first update.
if not self.plugs:
children = self.sys_info["children"]
self.num_children = len(children)
for child in children:
self.plugs.append(
SmartPlug(
self.host,
child_id=child["id"],
cache_ttl=self.cache_ttl.total_seconds(),
)
)
async def turn_on(self):
"""Turn the strip on.
:raises SmartDeviceException: on error
"""
await self._query_helper("system", "set_relay_state", {"state": 1})
await self.update()
async def turn_off(self):
"""Turn the strip off.
:raises SmartDeviceException: on error
"""
await self._query_helper("system", "set_relay_state", {"state": 0})
await self.update()
@property # type: ignore
@requires_update
def on_since(self) -> datetime.datetime:
"""Return the maximum on-time of all outlets."""
return max(plug.on_since for plug in self.plugs)
@property # type: ignore
@requires_update
def state_information(self) -> Dict[str, Any]:
"""Return strip-specific state information.
:return: Strip information dict, keys in user-presentable form.
:rtype: dict
"""
state: Dict[str, Any] = {"LED state": self.led}
for plug in self.plugs:
if plug.is_on:
state["Plug %s on since" % str(plug)] = self.on_since
return state
async def current_consumption(self) -> float:
"""Get the current power consumption in watts.
:return: the current power consumption in watts.
:rtype: float
:raises SmartDeviceException: on error
"""
consumption = sum([await plug.current_consumption() for plug in self.plugs])
return consumption
async def get_icon(self) -> Dict:
"""Icon for the device.
Overriden to keep the API, as the SmartStrip and children do not
have icons, we just return dummy strings.
"""
return {"icon": "SMARTSTRIP-DUMMY", "hash": "SMARTSTRIP-DUMMY"}
async def set_alias(self, alias: str) -> None:
"""Set the alias for the strip.
:param alias: new alias
:raises SmartDeviceException: on error
"""
return await super().set_alias(alias)
@requires_update
async def get_emeter_daily(
self, year: int = None, month: int = None, kwh: bool = True
) -> Dict:
"""Retrieve daily statistics for a given month.
:param year: year for which to retrieve statistics (default: this year)
:param month: month for which to retrieve statistics (default: this
month)
:param kwh: return usage in kWh (default: True)
:return: mapping of day of month to value
:rtype: dict
:raises SmartDeviceException: on error
"""
emeter_daily: DefaultDict[int, float] = defaultdict(lambda: 0.0)
for plug in self.plugs:
plug_emeter_daily = await plug.get_emeter_daily(
year=year, month=month, kwh=kwh
)
for day, value in plug_emeter_daily.items():
emeter_daily[day] += value
return emeter_daily
@requires_update
async def get_emeter_monthly(self, year: int = None, kwh: bool = True) -> Dict:
"""Retrieve monthly statistics for a given year.
:param year: year for which to retrieve statistics (default: this year)
:param kwh: return usage in kWh (default: True)
:return: dict: mapping of month to value
:rtype: dict
:raises SmartDeviceException: on error
"""
emeter_monthly: DefaultDict[int, float] = defaultdict(lambda: 0.0)
for plug in self.plugs:
plug_emeter_monthly = await plug.get_emeter_monthly(year=year, kwh=kwh)
for month, value in plug_emeter_monthly:
emeter_monthly[month] += value
return emeter_monthly
@requires_update
async def erase_emeter_stats(self):
"""Erase energy meter statistics for all plugs.
:raises SmartDeviceException: on error
"""
for plug in self.plugs:
await plug.erase_emeter_stats()