diff --git a/kasa/cli.py b/kasa/cli.py index 5d56e19b4..e225cb3c2 100755 --- a/kasa/cli.py +++ b/kasa/cli.py @@ -295,7 +295,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"]) @@ -313,6 +312,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) diff --git a/kasa/modules/emeter.py b/kasa/modules/emeter.py index f8144e39e..bb161ce61 100644 --- a/kasa/modules/emeter.py +++ b/kasa/modules/emeter.py @@ -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 @@ 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 diff --git a/kasa/modules/usage.py b/kasa/modules/usage.py index 2a5b6dc0b..5aecb9a75 100644 --- a/kasa/modules/usage.py +++ b/kasa/modules/usage.py @@ -17,22 +17,53 @@ def query(self): 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): diff --git a/kasa/smartdevice.py b/kasa/smartdevice.py index c9b9171f5..da2ebd99d 100755 --- a/kasa/smartdevice.py +++ b/kasa/smartdevice.py @@ -476,6 +476,7 @@ async def get_emeter_realtime(self) -> EmeterStatus: 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: @@ -500,28 +501,14 @@ def _create_emeter_request(self, year: int = None, month: int = None): 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..""" @@ -554,16 +541,7 @@ async def get_emeter_daily( :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: @@ -574,14 +552,7 @@ async def get_emeter_monthly(self, year: int = None, kwh: bool = True) -> Dict: :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: @@ -593,7 +564,7 @@ async def erase_emeter_stats(self) -> Dict: 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: