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) usage_data = await dev.get_emeter_daily(year=month.year, month=month.month)
else: else:
# Call with no argument outputs summary data and returns # Call with no argument outputs summary data and returns
usage_data = {}
emeter_status = dev.emeter_realtime emeter_status = dev.emeter_realtime
click.echo("Current: %s A" % emeter_status["current"]) 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}") 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() @cli.command()
@click.argument("brightness", type=click.IntRange(0, 100), default=None, required=False) @click.argument("brightness", type=click.IntRange(0, 100), default=None, required=False)
@click.option("--transition", type=int, required=False) @click.option("--transition", type=int, required=False)

View File

@ -1,4 +1,7 @@
"""Implementation of the emeter module.""" """Implementation of the emeter module."""
from datetime import datetime
from typing import Dict, Optional
from ..emeterstatus import EmeterStatus from ..emeterstatus import EmeterStatus
from .usage import Usage from .usage import Usage
@ -6,15 +9,61 @@ from .usage import Usage
class Emeter(Usage): class Emeter(Usage):
"""Emeter module.""" """Emeter module."""
def query(self):
"""Prepare query for emeter data."""
return self._device._create_emeter_request()
@property # type: ignore @property # type: ignore
def realtime(self) -> EmeterStatus: def realtime(self) -> EmeterStatus:
"""Return current energy readings.""" """Return current energy readings."""
return EmeterStatus(self.data["get_realtime"]) 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): async def erase_stats(self):
"""Erase all stats.""" """Erase all stats.
Uses different query than usage meter.
"""
return await self.call("erase_emeter_stat") 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, 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_monthstat", {"year": year}))
req = merge(req, self.query_for_command("get_next_action"))
return req return req
async def get_daystat(self, year, month): @property
"""Return stats for the current day.""" 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: if year is None:
year = datetime.now().year year = datetime.now().year
if month is None: if month is None:
month = datetime.now().month month = datetime.now().month
return await self.call("get_daystat", {"year": year, "month": month}) return await self.call("get_daystat", {"year": year, "month": month})
async def get_monthstat(self, year): async def get_monthstat(self, *, year=None):
"""Return stats for the current month.""" """Return monthly stats for the given year."""
if year is None: if year is None:
year = datetime.now().year year = datetime.now().year
return await self.call("get_monthstat", {"year": year}) return await self.call("get_monthstat", {"year": year})
async def erase_stats(self): async def erase_stats(self):

View File

@ -482,6 +482,7 @@ class SmartDevice:
def _create_emeter_request(self, year: int = None, month: int = None): 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.""" """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: if year is None:
year = datetime.now().year year = datetime.now().year
if month is None: if month is None:
@ -506,28 +507,14 @@ class SmartDevice:
def emeter_today(self) -> Optional[float]: def emeter_today(self) -> Optional[float]:
"""Return today's energy consumption in kWh.""" """Return today's energy consumption in kWh."""
self._verify_emeter() self._verify_emeter()
raw_data = self._last_update[self.emeter_type]["get_daystat"]["day_list"] return self.modules["emeter"].emeter_today
data = self._emeter_convert_emeter_data(raw_data)
today = datetime.now().day
if today in data:
return data[today]
return None
@property # type: ignore @property # type: ignore
@requires_update @requires_update
def emeter_this_month(self) -> Optional[float]: def emeter_this_month(self) -> Optional[float]:
"""Return this month's energy consumption in kWh.""" """Return this month's energy consumption in kWh."""
self._verify_emeter() self._verify_emeter()
raw_data = self._last_update[self.emeter_type]["get_monthstat"]["month_list"] return self.modules["emeter"].emeter_this_month
data = self._emeter_convert_emeter_data(raw_data)
current_month = datetime.now().month
if current_month in data:
return data[current_month]
return None
def _emeter_convert_emeter_data(self, data, kwh=True) -> Dict: def _emeter_convert_emeter_data(self, data, kwh=True) -> Dict:
"""Return emeter information keyed with the day/month..""" """Return emeter information keyed with the day/month.."""
@ -560,16 +547,7 @@ class SmartDevice:
:return: mapping of day of month to value :return: mapping of day of month to value
""" """
self._verify_emeter() self._verify_emeter()
if year is None: return await self.modules["emeter"].get_daystat(year=year, month=month, kwh=kwh)
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)
@requires_update @requires_update
async def get_emeter_monthly(self, year: int = None, kwh: bool = True) -> Dict: 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 :return: dict: mapping of month to value
""" """
self._verify_emeter() self._verify_emeter()
if year is None: return await self.modules["emeter"].get_monthstat(year=year, kwh=kwh)
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)
@requires_update @requires_update
async def erase_emeter_stats(self) -> Dict: async def erase_emeter_stats(self) -> Dict:
@ -599,7 +570,7 @@ class SmartDevice:
async def current_consumption(self) -> float: async def current_consumption(self) -> float:
"""Get the current power consumption in Watt.""" """Get the current power consumption in Watt."""
self._verify_emeter() self._verify_emeter()
response = EmeterStatus(await self.get_emeter_realtime()) response = self.emeter_realtime
return float(response["power"]) return float(response["power"])
async def reboot(self, delay: int = 1) -> None: async def reboot(self, delay: int = 1) -> None: