From 0f38b22982264c033c15050e65adcabbbe2921f3 Mon Sep 17 00:00:00 2001 From: Teemu Rytilahti Date: Thu, 23 Sep 2021 17:19:34 +0200 Subject: [PATCH] Improve emeterstatus API, move into own module Adds the following properties to EmeterStatus for saner API: * voltage (in V) * power (in W) * current (in A) * total (in kWh) --- kasa/__init__.py | 3 +- kasa/emeterstatus.py | 82 +++++++++++++++++++++++++++++++++++++++ kasa/smartbulb.py | 7 +--- kasa/smartdevice.py | 45 +-------------------- kasa/tests/test_emeter.py | 4 +- 5 files changed, 88 insertions(+), 53 deletions(-) create mode 100644 kasa/emeterstatus.py diff --git a/kasa/__init__.py b/kasa/__init__.py index 51b5291b4..fc798fb37 100755 --- a/kasa/__init__.py +++ b/kasa/__init__.py @@ -14,10 +14,11 @@ from importlib_metadata import version # type: ignore from kasa.discover import Discover +from kasa.emeterstatus import EmeterStatus from kasa.exceptions import SmartDeviceException from kasa.protocol import TPLinkSmartHomeProtocol from kasa.smartbulb import SmartBulb -from kasa.smartdevice import DeviceType, EmeterStatus, SmartDevice +from kasa.smartdevice import DeviceType, SmartDevice from kasa.smartdimmer import SmartDimmer from kasa.smartlightstrip import SmartLightStrip from kasa.smartplug import SmartPlug diff --git a/kasa/emeterstatus.py b/kasa/emeterstatus.py new file mode 100644 index 000000000..da636551d --- /dev/null +++ b/kasa/emeterstatus.py @@ -0,0 +1,82 @@ +"""Module for emeter container.""" +import logging +from typing import Optional + +_LOGGER = logging.getLogger(__name__) + + +class EmeterStatus(dict): + """Container for converting different representations of emeter data. + + Newer FW/HW versions postfix the variable names with the used units, + where-as the olders do not have this feature. + + This class automatically converts between these two to allow + backwards and forwards compatibility. + """ + + @property + def voltage(self) -> Optional[float]: + """Return voltage in V.""" + try: + return self["voltage"] + except ValueError: + return None + + @property + def power(self) -> Optional[float]: + """Return power in W.""" + try: + return self["power"] + except ValueError: + return None + + @property + def current(self) -> Optional[float]: + """Return current in A.""" + try: + return self["current"] + except ValueError: + return None + + @property + def total(self) -> Optional[float]: + """Return total in kWh.""" + try: + return self["total"] + except ValueError: + return None + + def __repr__(self): + return f"" + + def __getitem__(self, item): + valid_keys = [ + "voltage_mv", + "power_mw", + "current_ma", + "energy_wh", + "total_wh", + "voltage", + "power", + "current", + "total", + "energy", + ] + + # 1. if requested data is available, return it + if item in super().keys(): + return super().__getitem__(item) + # otherwise decide how to convert it + else: + if item not in valid_keys: + raise KeyError(item) + if "_" in item: # upscale + return super().__getitem__(item[: item.find("_")]) * 1000 + else: # downscale + for i in super().keys(): + if i.startswith(item): + return self.__getitem__(i) / 1000 + + _LOGGER.debug(f"Unable to find value for '{item}'") + return None diff --git a/kasa/smartbulb.py b/kasa/smartbulb.py index 74d5c1cf9..aad2ce8ce 100644 --- a/kasa/smartbulb.py +++ b/kasa/smartbulb.py @@ -3,12 +3,7 @@ import re from typing import Any, Dict, NamedTuple, cast -from kasa.smartdevice import ( - DeviceType, - SmartDevice, - SmartDeviceException, - requires_update, -) +from .smartdevice import DeviceType, SmartDevice, SmartDeviceException, requires_update class ColorTempRange(NamedTuple): diff --git a/kasa/smartdevice.py b/kasa/smartdevice.py index fa2273581..1efc0773e 100755 --- a/kasa/smartdevice.py +++ b/kasa/smartdevice.py @@ -19,6 +19,7 @@ from enum import Enum, auto from typing import Any, Dict, List, Optional +from .emeterstatus import EmeterStatus from .exceptions import SmartDeviceException from .protocol import TPLinkSmartHomeProtocol @@ -50,48 +51,6 @@ class WifiNetwork: rssi: Optional[int] = None -class EmeterStatus(dict): - """Container for converting different representations of emeter data. - - Newer FW/HW versions postfix the variable names with the used units, - where-as the olders do not have this feature. - - This class automatically converts between these two to allow - backwards and forwards compatibility. - """ - - def __getitem__(self, item): - valid_keys = [ - "voltage_mv", - "power_mw", - "current_ma", - "energy_wh", - "total_wh", - "voltage", - "power", - "current", - "total", - "energy", - ] - - # 1. if requested data is available, return it - if item in super().keys(): - return super().__getitem__(item) - # otherwise decide how to convert it - else: - if item not in valid_keys: - raise KeyError(item) - if "_" in item: # upscale - return super().__getitem__(item[: item.find("_")]) * 1000 - else: # downscale - for i in super().keys(): - if i.startswith(item): - return self.__getitem__(i) / 1000 - - _LOGGER.debug(f"Unable to find value for '{item}'") - return None - - def requires_update(f): """Indicate that `update` should be called before accessing this method.""" # noqa: D202 if inspect.iscoroutinefunction(f): @@ -202,7 +161,7 @@ class SmartDevice: >>> dev.has_emeter True >>> dev.emeter_realtime - {'current': 0.015342, 'err_code': 0, 'power': 0.983971, 'total': 32.448, 'voltage': 235.595234} + >>> dev.emeter_today >>> dev.emeter_this_month diff --git a/kasa/tests/test_emeter.py b/kasa/tests/test_emeter.py index 388a42d40..7f0f95aca 100644 --- a/kasa/tests/test_emeter.py +++ b/kasa/tests/test_emeter.py @@ -1,6 +1,6 @@ import pytest -from kasa import SmartDeviceException +from kasa import EmeterStatus, SmartDeviceException from .conftest import has_emeter, no_emeter, pytestmark from .newfakes import CURRENT_CONSUMPTION_SCHEMA @@ -121,8 +121,6 @@ async def test_current_consumption(dev): async def test_emeterstatus_missing_current(): """KL125 does not report 'current' for emeter.""" - from kasa import EmeterStatus - regular = EmeterStatus( {"err_code": 0, "power_mw": 0, "total_wh": 13, "current_ma": 123} )