8000 Expose energy command to cli (#1307) · MAXIGAMESSUPPER/python-kasa@69e08c2 · GitHub
[go: up one dir, main page]

Skip to content

Commit 69e08c2

Browse files
rytilahtisdb9696
andauthored
Expose energy command to cli (python-kasa#1307)
Co-authored-by: Steven B <51370195+sdb9696@users.noreply.github.com>
1 parent 3dfada7 commit 69e08c2

File tree

5 files changed

+44
-42
lines changed

5 files changed

+44
-42
lines changed

kasa/cli/main.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ def _legacy_type_to_class(_type: str) -> Any:
7575
"time": None,
7676
"schedule": None,
7777
"usage": None,
78+
"energy": "usage",
7879
# device commands runnnable at top level
7980
"state": "device",
8081
"on": "device",

kasa/cli/usage.py

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from __future__ import annotations
44

5-
import logging
65
from typing import cast
76

87
import asyncclick as click
@@ -21,21 +20,6 @@
2120
)
2221

2322

24-
@click.command()
25-
@click.option("--index", type=int, required=False)
26-
@click.option("--name", type=str, required=False)
27-
@click.option("--year", type=click.DateTime(["%Y"]), default=None, required=False)
28-
@click.option("--month", type=click.DateTime(["%Y-%m"]), default=None, required=False)
29-
@click.option("--erase", is_flag=True)
30-
@click.pass_context
31-
async def emeter(ctx: click.Context, index, name, year, month, erase):
32-
"""Query emeter for historical consumption."""
33-
logging.warning("Deprecated, use 'kasa energy'")
34-
return await ctx.invoke(
35-
energy, child_index=index, child=name, year=year, month=month, erase=erase
36-
)
37-
38-
3923
@click.command()
4024
@click.option("--year", type=click.DateTime(["%Y"]), default=None, required=False)
4125
@click.option("--month", type=click.DateTime(["%Y-%m"]), default=None, required=False)
@@ -46,7 +30,7 @@ async def energy(dev: Device, year, month, erase):
4630
4731
Daily and monthly data provided in CSV format.
4832
"""
49-
echo("[bold]== Emeter ==[/bold]")
33+
echo("[bold]== Energy ==[/bold]")
5034
if not (energy := dev.modules.get(Module.Energy)):
5135
error("Device has no energy module.")
5236
return
@@ -71,7 +55,7 @@ async def energy(dev: Device, year, month, erase):
7155
usage_data = await energy.get_daily_stats(year=month.year, month=month.month)
7256
else:
7357
# Call with no argument outputs summary data and returns
74-
emeter_status = await energy.get_status()
58+
emeter_status = energy.status
7559

7660
echo("Current: {} A".format(emeter_status["current"]))
7761
echo("Voltage: {} V".format(emeter_status["voltage"]))

kasa/smart/modules/energy.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,12 @@ def status(self) -> EmeterStatus:
7575

7676
async def get_status(self) -> EmeterStatus:
7777
"""Return real-time statistics."""
78-
res = await self.call("get_energy_usage")
79-
return self._get_status_from_energy(res["get_energy_usage"])
78+
if "get_emeter_data" in self.data:
79+
res = await self.call("get_emeter_data")
80+
return EmeterStatus(res["get_emeter_data"])
81+
else:
82+
res = await self.call("get_energy_usage")
83+
return self._get_status_from_energy(res["get_energy_usage"])
8084

8185
@property
8286
@raise_if_update_error

tests/test_cli.py

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import os
33
import re
44
from datetime import datetime
5-
from unittest.mock import ANY
5+
from unittest.mock import ANY, PropertyMock, patch
66
from zoneinfo import ZoneInfo
77

88
import asyncclick as click
@@ -40,7 +40,7 @@
4040
)
4141
from kasa.cli.main import TYPES, _legacy_type_to_class, cli, cmd_command, raw_command
4242
from kasa.cli.time import time
43-
from kasa.cli.usage import emeter, energy
43+
from kasa.cli.usage import energy
4444
from kasa.cli.wifi import wifi
4545
from kasa.discover import Discover, DiscoveryResult
4646
from kasa.iot import IotDevice
@@ -432,38 +432,45 @@ async def test_time_set(dev: Device, mocker, runner):
432432

433433

434434
async def test_emeter(dev: Device, mocker, runner):
435-
res = await runner.invoke(emeter, obj=dev)
435+
mocker.patch("kasa.Discover.discover_single", return_value=dev)
436+
base_cmd = ["--host", "dummy", "energy"]
437+
res = await runner.invoke(cli, base_cmd, obj=dev)
436438
if not (energy := dev.modules.get(Module.Energy)):
437439
assert "Device has no energy module." in res.output
438440
return
439441

440-
assert "== Emeter ==" in res.output
442+
assert "== Energy ==" in res.output
441443

442444
if dev.device_type is not DeviceType.Strip:
443-
res = await runner.invoke(emeter, ["--index", "0"], obj=dev)
445+
res = await runner.invoke(cli, [*base_cmd, "--index", "0"], obj=dev)
444446
assert f"Device: {dev.host} does not have children" in res.output
445-
res = await runner.invoke(emeter, ["--name", "mock"], obj=dev)
447+
res = await runner.invoke(cli, [*base_cmd, "--name", "mock"], obj=dev)
446448
assert f"Device: {dev.host} does not have children" in res.output
447449

448450
if dev.device_type is DeviceType.Strip and len(dev.children) > 0:
449451
child_energy = dev.children[0].modules.get(Module.Energy)
450452
assert child_energy
451-
realtime_emeter = mocker.patch.object(child_energy, "get_status")
452-
realtime_emeter.return_value = EmeterStatus({"voltage_mv": 122066})
453453

454-
res = await runner.invoke(emeter, ["--index", "0"], obj=dev)
455-
assert "Voltage: 122.066 V" in res.output
456-
realtime_emeter.assert_called()
457-
assert realtime_emeter.call_count == 1
454+
with patch.object(
455+
type(child_energy), "status", new_callable=PropertyMock
456+
) as child_status:
457+
child_status.return_value = EmeterStatus({"voltage_mv": 122066})
458+
459+
res = await runner.invoke(cli, [*base_cmd, "--index", "0"], obj=dev)
460+
assert "Voltage: 122.066 V" in res.output
461+
child_status.assert_called()
462+
assert child_status.call_count == 1
458463

459-
res = await runner.invoke(emeter, ["--name", dev.children[0].alias], obj=dev)
460-
assert "Voltage: 122.066 V" in res.output
461-
assert realtime_emeter.call_count == 2
464+
res = await runner.invoke(
465+
cli, [*base_cmd, "--name", dev.children[0].alias], obj=dev
466+
)
467+
assert "Voltage: 122.066 V" in res.output
468+
assert child_status.call_count == 2
462469

463470
if isinstance(dev, IotDevice):
464471
monthly = mocker.patch.object(energy, "get_monthly_stats")
465472
monthly.return_value = {1: 1234}
466-
res = await runner.invoke(emeter, ["--year", "1900"], obj=dev)
473+
res = await runner.invoke(cli, [*base_cmd, "--year", "1900"], obj=dev)
467474
if not isinstance(dev, IotDevice):
468475
assert "Device does not support historical statistics" in res.output
469476
return
@@ -474,7 +481,7 @@ async def test_emeter(dev: Device, mocker, runner):
474481
if isinstance(dev, IotDevice):
475482
daily = mocker.patch.object(energy, "get_daily_stats")
476483
daily.return_value = {1: 1234}
477-
res = await runner.invoke(emeter, ["--month", "1900-12"], obj=dev)
484+
res = await runner.invoke(cli, [*base_cmd, "--month", "1900-12"], obj=dev)
478485
if not isinstance(dev, IotDevice):
479486
assert "Device has no historical statistics" in res.output
480487
return

tests/test_emeter.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,16 @@
2323
CURRENT_CONSUMPTION_SCHEMA = Schema(
2424
Any(
2525
{
26-
"voltage": Any(All(float, Range(min=0, max=300)), None),
27-
"power": Any(Coerce(float), None),
28-
"total": Any(Coerce(float), None),
29-
"current": Any(All(float), None),
3026
"voltage_mv": Any(All(float, Range(min=0, max=300000)), int, None),
3127
"power_mw": Any(Coerce(float), None),
32-
"total_wh": Any(Coerce(float), None),
3328
"current_ma": Any(All(float), int, None),
29+
"energy_wh": Any(Coerce(float), None),
30+
"total_wh": Any(Coerce(float), None),
31+
"voltage": Any(All(float, Range(min=0, max=300)), None),
32+
"power": Any(Coerce(float), None),
33+
"current": Any(All(float), None),
34+
"total": Any(Coerce(float), None),
35+
"energy": Any(Coerce(float), None),
3436
"slot_id": Any(Coerce(int), None),
3537
},
3638
None,
@@ -65,6 +67,10 @@ async def test_get_emeter_realtime(dev):
6567
emeter = dev.modules[Module.Energy]
6668

6769
current_emeter = await emeter.get_status()
70+
# Check realtime query gets the same value as status property
71+
# iot _query_helper strips out the error code from module responses.
72+
# but it's not stripped out of the _modular_update queries.
73+
assert current_emeter == {k: v for k, v in emeter.status.items() if k != "err_code"}
6874
CURRENT_CONSUMPTION_SCHEMA(current_emeter)
6975

7076

0 commit comments

Comments
 (0)
0