Improve usage module, consolidate API with emeter (#249)

* Consolidate API for both emeter&usage modules
* Add new cli command 'usage' to query usage
This commit is contained in:
Teemu R 2021-11-19 16:41:49 +01:00
parent 3926f3224f
commit e3588047fc
4 changed files with 134 additions and 46 deletions

View File

@ -316,7 +316,6 @@ async def emeter(dev: SmartDevice, year, month, erase):
usage_data = await dev.get_emeter_daily(year=month.year, month=month.month)
else:
# Call with no argument outputs summary data and returns
usage_data = {}
emeter_status = dev.emeter_realtime
click.echo("Current: %s A" % emeter_status["current"])
@ -334,6 +333,44 @@ async def emeter(dev: SmartDevice, year, month, erase):
click.echo(f"{index}, {usage}")
@cli.command()
@pass_dev
@click.option("--year", type=click.DateTime(["%Y"]), default=None, required=False)
@click.option("--month", type=click.DateTime(["%Y-%m"]), default=None, required=False)
@click.option("--erase", is_flag=True)
async def usage(dev: SmartDevice, year, month, erase):
"""Query usage for historical consumption.
Daily and monthly data provided in CSV format.
"""
click.echo(click.style("== Usage ==", bold=True))
usage = dev.modules["usage"]
if erase:
click.echo("Erasing usage statistics..")
click.echo(await usage.erase_stats())
return
if year:
click.echo(f"== For year {year.year} ==")
click.echo("Month, usage (minutes)")
usage_data = await usage.get_monthstat(year.year)
elif month:
click.echo(f"== For month {month.month} of {month.year} ==")
click.echo("Day, usage (minutes)")
usage_data = await usage.get_daystat(year=month.year, month=month.month)
else:
# Call with no argument outputs summary data and returns
click.echo("Today: %s minutes" % usage.usage_today)
click.echo("This month: %s minutes" % usage.usage_this_month)
return
# output any detailed usage data
for index, usage in usage_data.items():
click.echo(f"{index}, {usage}")
@cli.command()
@click.argument("brightness", type=click.IntRange(0, 100), default=None, required=False)
@click.option("--transition", type=int, required=False)

View File

@ -1,4 +1,7 @@
"""Implementation of the emeter module."""
from datetime import datetime
from typing import Dict, Optional
from ..emeterstatus import EmeterStatus
from .usage import Usage
@ -6,15 +9,61 @@ from .usage import Usage
class Emeter(Usage):
"""Emeter module."""
def query(self):
"""Prepare query for emeter data."""
return self._device._create_emeter_request()
@property # type: ignore
def realtime(self) -> EmeterStatus:
"""Return current energy readings."""
return EmeterStatus(self.data["get_realtime"])
@property
def emeter_today(self) -> Optional[float]:
"""Return today's energy consumption in kWh."""
raw_data = self.daily_data
today = datetime.now().day
data = self._emeter_convert_emeter_data(raw_data)
return data.get(today)
@property
def emeter_this_month(self) -> Optional[float]:
"""Return this month's energy consumption in kWh."""
raw_data = self.monthly_data
current_month = datetime.now().month
data = self._emeter_convert_emeter_data(raw_data)
return data.get(current_month)
async def erase_stats(self):
"""Erase all stats."""
"""Erase all stats.
Uses different query than usage meter.
"""
return await self.call("erase_emeter_stat")
async def get_daystat(self, *, year, month, kwh=True):
"""Return daily stats for the given year & month."""
raw_data = await super().get_daystat(year=year, month=month)
return self._emeter_convert_emeter_data(raw_data["day_list"], kwh)
async def get_monthstat(self, *, year, kwh=True):
"""Return monthly stats for the given year."""
raw_data = await super().get_monthstat(year=year)
return self._emeter_convert_emeter_data(raw_data["month_list"], kwh)
def _emeter_convert_emeter_data(self, data, kwh=True) -> Dict:
"""Return emeter information keyed with the day/month.."""
response = [EmeterStatus(**x) for x in data]
if not response:
return {}
energy_key = "energy_wh"
if kwh:
energy_key = "energy"
entry_key = "month"
if "day" in response[0]:
entry_key = "day"
data = {entry[entry_key]: entry[energy_key] for entry in response}
return data

View File

@ -17,22 +17,53 @@ class Usage(Module):
req, self.query_for_command("get_daystat", {"year": year, "month": month})
)
req = merge(req, self.query_for_command("get_monthstat", {"year": year}))
req = merge(req, self.query_for_command("get_next_action"))
return req
async def get_daystat(self, year, month):
"""Return stats for the current day."""
@property
def daily_data(self):
"""Return statistics on daily basis."""
return self.data["get_daystat"]["day_list"]
@property
def monthly_data(self):
"""Return statistics on monthly basis."""
return self.data["get_monthstat"]["month_list"]
@property
def usage_today(self):
"""Return today's usage in minutes."""
today = datetime.now().day
converted = [x["time"] for x in self.daily_data if x["day"] == today]
if not converted:
return None
return converted.pop()
@property
def usage_this_month(self):
"""Return usage in this month in minutes."""
this_month = datetime.now().month
converted = [x["time"] for x in self.monthly_data if x["month"] == this_month]
if not converted:
return None
return converted.pop()
async def get_daystat(self, *, year=None, month=None):
"""Return daily stats for the given year & month."""
if year is None:
year = datetime.now().year
if month is None:
month = datetime.now().month
return await self.call("get_daystat", {"year": year, "month": month})
async def get_monthstat(self, year):
"""Return stats for the current month."""
async def get_monthstat(self, *, year=None):
"""Return monthly stats for the given year."""
if year is None:
year = datetime.now().year
return await self.call("get_monthstat", {"year": year})
async def erase_stats(self):

View File

@ -482,6 +482,7 @@ class SmartDevice:
def _create_emeter_request(self, year: int = None, month: int = None):
"""Create a Internal method for building a request for all emeter statistics at once."""
# TODO: this is currently only here for smartstrip plug support, move it there?
if year is None:
year = datetime.now().year
if month is None:
@ -506,28 +507,14 @@ class SmartDevice:
def emeter_today(self) -> Optional[float]:
"""Return today's energy consumption in kWh."""
self._verify_emeter()
raw_data = self._last_update[self.emeter_type]["get_daystat"]["day_list"]
data = self._emeter_convert_emeter_data(raw_data)
today = datetime.now().day
if today in data:
return data[today]
return None
return self.modules["emeter"].emeter_today
@property # type: ignore
@requires_update
def emeter_this_month(self) -> Optional[float]:
"""Return this month's energy consumption in kWh."""
self._verify_emeter()
raw_data = self._last_update[self.emeter_type]["get_monthstat"]["month_list"]
data = self._emeter_convert_emeter_data(raw_data)
current_month = datetime.now().month
if current_month in data:
return data[current_month]
return None
return self.modules["emeter"].emeter_this_month
def _emeter_convert_emeter_data(self, data, kwh=True) -> Dict:
"""Return emeter information keyed with the day/month.."""
@ -560,16 +547,7 @@ class SmartDevice:
:return: mapping of day of month to value
"""
self._verify_emeter()
if year is None:
year = datetime.now().year
if month is None:
month = datetime.now().month
response = await self._query_helper(
self.emeter_type, "get_daystat", {"month": month, "year": year}
)
return self._emeter_convert_emeter_data(response["day_list"], kwh)
return await self.modules["emeter"].get_daystat(year=year, month=month, kwh=kwh)
@requires_update
async def get_emeter_monthly(self, year: int = None, kwh: bool = True) -> Dict:
@ -580,14 +558,7 @@ class SmartDevice:
:return: dict: mapping of month to value
"""
self._verify_emeter()
if year is None:
year = datetime.now().year
response = await self._query_helper(
self.emeter_type, "get_monthstat", {"year": year}
)
return self._emeter_convert_emeter_data(response["month_list"], kwh)
return await self.modules["emeter"].get_monthstat(year=year, kwh=kwh)
@requires_update
async def erase_emeter_stats(self) -> Dict:
@ -599,7 +570,7 @@ class SmartDevice:
async def current_consumption(self) -> float:
"""Get the current power consumption in Watt."""
self._verify_emeter()
response = EmeterStatus(await self.get_emeter_realtime())
response = self.emeter_realtime
return float(response["power"])
async def reboot(self, delay: int = 1) -> None: