General cleanups all around (janitoring) (#63)

* Move tests to device-type specific test files to make improvements more approachable

* protocol: remove the port parameter from query, as there are no other known ports, fix docstrings

* Revise docstrings, remove superfluous information and remove unused methods ({set,get_icon} and set_time)

* cli: indent device output to make it more easily readable when having multiple devices

* remove adjust flake8 ignores (we have no setup.py anymore)

* pyproject: include cli tool to coverage, add config for interrogate (docstring coverage)

* bulb: raise exception on color_temp error cases instead of returning zero values

* improve bulb tests, simplify conftest

* strip: rename plugs property to children and move it to smartdevice
This commit is contained in:
Teemu R
2020-05-27 16:55:18 +02:00
committed by GitHub
parent 836f1701b9
commit 644a10a0d1
19 changed files with 762 additions and 969 deletions

View File

@@ -1,11 +1,8 @@
"""Module for multi-socket devices (HS300, HS107).
.. todo:: describe how this interfaces with single plugs.
"""
"""Module for multi-socket devices (HS300, HS107, KP303, ..)."""
import logging
from collections import defaultdict
from datetime import datetime, timedelta
from typing import Any, DefaultDict, Dict, List, Optional
from typing import Any, DefaultDict, Dict, Optional
from kasa.smartdevice import (
DeviceType,
@@ -49,13 +46,12 @@ class SmartStrip(SmartDevice):
super().__init__(host=host)
self.emeter_type = "emeter"
self._device_type = DeviceType.Strip
self.plugs: List[SmartStripPlug] = []
@property # type: ignore
@requires_update
def is_on(self) -> bool:
"""Return if any of the outlets are on."""
for plug in self.plugs:
for plug in self.children:
is_on = plug.is_on
if is_on:
return True
@@ -69,46 +65,24 @@ class SmartStrip(SmartDevice):
await super().update()
# Initialize the child devices during the first update.
if not self.plugs:
if not self.children:
children = self.sys_info["children"]
_LOGGER.debug("Initializing %s child sockets", len(children))
for child in children:
self.plugs.append(
self.children.append(
SmartStripPlug(self.host, parent=self, child_id=child["id"])
)
async def turn_on(self):
"""Turn the strip on.
:raises SmartDeviceException: on error
"""
"""Turn the strip on."""
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
"""
"""Turn the strip off."""
await self._query_helper("system", "set_relay_state", {"state": 0})
await self.update()
def get_plug_by_name(self, name: str) -> "SmartStripPlug":
"""Return child plug for given name."""
for p in self.plugs:
if p.alias == name:
return p
raise SmartDeviceException(f"Device has no child with {name}")
def get_plug_by_index(self, index: int) -> "SmartStripPlug":
"""Return child plug for given index."""
if index + 1 > len(self.plugs) or index < 0:
raise SmartDeviceException(
f"Invalid index {index}, device has {len(self.plugs)} plugs"
)
return self.plugs[index]
@property # type: ignore
@requires_update
def on_since(self) -> Optional[datetime]:
@@ -116,25 +90,17 @@ class SmartStrip(SmartDevice):
if self.is_off:
return None
return max(plug.on_since for plug in self.plugs if plug.on_since is not None)
return max(plug.on_since for plug in self.children if plug.on_since is not None)
@property # type: ignore
@requires_update
def led(self) -> bool:
"""Return the state of the led.
:return: True if led is on, False otherwise
:rtype: bool
"""
"""Return the state of the led."""
sys_info = self.sys_info
return bool(1 - sys_info["led_off"])
async def set_led(self, state: bool):
"""Set the state of the led (night mode).
:param bool state: True to set led on, False to set led off
:raises SmartDeviceException: on error
"""
"""Set the state of the led (night mode)."""
await self._query_helper("system", "set_led_off", {"off": int(not state)})
await self.update()
@@ -144,38 +110,23 @@ class SmartStrip(SmartDevice):
"""Return strip-specific state information.
:return: Strip information dict, keys in user-presentable form.
:rtype: dict
"""
return {
"LED state": self.led,
"Childs count": len(self.plugs),
"Childs count": len(self.children),
"On since": self.on_since,
}
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])
"""Get the current power consumption in watts."""
consumption = sum([await plug.current_consumption() for plug in self.children])
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)
@@ -190,11 +141,9 @@ class SmartStrip(SmartDevice):
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:
for plug in self.children:
plug_emeter_daily = await plug.get_emeter_daily(
year=year, month=month, kwh=kwh
)
@@ -208,12 +157,9 @@ class SmartStrip(SmartDevice):
: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:
for plug in self.children:
plug_emeter_monthly = await plug.get_emeter_monthly(year=year, kwh=kwh)
for month, value in plug_emeter_monthly:
emeter_monthly[month] += value
@@ -222,11 +168,8 @@ class SmartStrip(SmartDevice):
@requires_update
async def erase_emeter_stats(self):
"""Erase energy meter statistics for all plugs.
:raises SmartDeviceException: on error
"""
for plug in self.plugs:
"""Erase energy meter statistics for all plugs."""
for plug in self.children:
await plug.erase_emeter_stats()
@@ -267,10 +210,7 @@ class SmartStripPlug(SmartPlug):
@property # type: ignore
@requires_update
def is_on(self) -> bool:
"""Return whether device is on.
:return: True if device is on, False otherwise
"""
"""Return whether device is on."""
info = self._get_child_info()
return info["state"]
@@ -280,9 +220,6 @@ class SmartStripPlug(SmartPlug):
"""Return the state of the led.
This is always false for subdevices.
:return: True if led is on, False otherwise
:rtype: bool
"""
return False
@@ -304,11 +241,7 @@ class SmartStripPlug(SmartPlug):
@property # type: ignore
@requires_update
def alias(self) -> str:
"""Return device name (alias).
:return: Device name aka alias.
:rtype: str
"""
"""Return device name (alias)."""
info = self._get_child_info()
return info["alias"]
@@ -322,11 +255,7 @@ class SmartStripPlug(SmartPlug):
@property # type: ignore
@requires_update
def on_since(self) -> Optional[datetime]:
"""Return pretty-printed on-time.
:return: datetime for on since
:rtype: datetime
"""
"""Return on-time, if available."""
if self.is_off:
return None
@@ -338,21 +267,14 @@ class SmartStripPlug(SmartPlug):
@property # type: ignore
@requires_update
def model(self) -> str:
"""Return device model for a child socket.
:return: device model
:rtype: str
:raises SmartDeviceException: on error
"""
"""Return device model for a child socket."""
sys_info = self.parent.sys_info
return f"Socket for {sys_info['model']}"
def _get_child_info(self) -> Dict:
"""Return the subdevice information for this device.
:raises SmartDeviceException: if the information is not found.
"""
"""Return the subdevice information for this device."""
for plug in self.parent.sys_info["children"]:
if plug["id"] == self.child_id:
return plug
raise SmartDeviceException(f"Unable to find children {self.child_id}")