Fix emeter support for newer HS110 firmwares (#107)

* Add support for new-style emeter

This commit adds a straightforward dict-extending container,
which converts between the old and new keys of the get_emeter_realtime()
Furthermore the unit tests are converted to base on HS100
instead of HS110.

This is the first step to fix #103, other emeter-using functionality
has not yet been converted, only getting the current consumption.

* fix a couple of linting issues

* Convert new-style emeter values also for get_emeter_daily() and get_emeter_monthly()

* Adds a new 'kwh' parameter for those calls, which defaults to True
* This changes the behavior of bulbs emeter reporting, use False if you prefer the preciser values
This commit is contained in:
Teemu R
2018-06-16 21:16:35 +02:00
committed by GitHub
parent 11a7042a04
commit ef2e21ff69
6 changed files with 162 additions and 39 deletions

View File

@@ -15,7 +15,6 @@ http://www.apache.org/licenses/LICENSE-2.0
"""
import datetime
import logging
import socket
import warnings
from collections import defaultdict
from typing import Any, Dict, List, Tuple, Optional
@@ -32,6 +31,40 @@ class SmartDeviceException(Exception):
pass
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('_')]) * 10**3
else: # downscale
for i in super().keys():
if i.startswith(item):
return self.__getitem__(i) / 10**3
raise SmartDeviceException("Unable to find a value for '%s'" %
item)
class SmartDevice(object):
# possible device features
FEATURE_ENERGY_METER = 'ENE'
@@ -52,7 +85,6 @@ class SmartDevice(object):
protocol = TPLinkSmartHomeProtocol()
self.protocol = protocol
self.emeter_type = "emeter" # type: str
self.emeter_units = False
def _query_helper(self,
target: str,
@@ -380,17 +412,20 @@ class SmartDevice(object):
if not self.has_emeter:
return None
return self._query_helper(self.emeter_type, "get_realtime")
return EmeterStatus(self._query_helper(self.emeter_type,
"get_realtime"))
def get_emeter_daily(self,
year: int = None,
month: int = None) -> Optional[Dict]:
month: int = None,
kwh: bool = True) -> Optional[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 statistcs (default: this
month)
:param kwh: return usage in kWh (default: True)
:return: mapping of day of month to value
None if device has no energy meter or error occured
:rtype: dict
@@ -406,20 +441,24 @@ class SmartDevice(object):
response = self._query_helper(self.emeter_type, "get_daystat",
{'month': month, 'year': year})
response = [EmeterStatus(**x) for x in response["day_list"]]
if self.emeter_units:
key = 'energy_wh'
else:
key = 'energy_wh'
if kwh:
key = 'energy'
return {entry['day']: entry[key]
for entry in response['day_list']}
data = {entry['day']: entry[key]
for entry in response}
def get_emeter_monthly(self, year=None) -> Optional[Dict]:
return data
def get_emeter_monthly(self, year: int = None,
kwh: bool = True) -> Optional[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
None if device has no energy meter
:rtype: dict
@@ -433,14 +472,14 @@ class SmartDevice(object):
response = self._query_helper(self.emeter_type, "get_monthstat",
{'year': year})
response = [EmeterStatus(**x) for x in response["month_list"]]
if self.emeter_units:
key = 'energy_wh'
else:
key = 'energy_wh'
if kwh:
key = 'energy'
return {entry['month']: entry[key]
for entry in response['month_list']}
for entry in response}
def erase_emeter_stats(self) -> bool:
"""
@@ -471,11 +510,8 @@ class SmartDevice(object):
if not self.has_emeter:
return None
response = self.get_emeter_realtime()
if self.emeter_units:
return float(response['power_mw'])
else:
return float(response['power'])
response = EmeterStatus(self.get_emeter_realtime())
return response['power']
def turn_off(self) -> None:
"""