mirror of
https://github.com/python-kasa/python-kasa.git
synced 2024-12-23 11:43:34 +00:00
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:
parent
3926f3224f
commit
e3588047fc
39
kasa/cli.py
39
kasa/cli.py
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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:
|
||||||
|
Loading…
Reference in New Issue
Block a user