mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-08-06 10:44:04 +00:00
Add common energy module and deprecate device emeter attributes (#976)
Consolidates logic for energy monitoring across smart and iot devices. Deprecates emeter attributes in favour of common names.
This commit is contained in:
@@ -163,12 +163,7 @@ async def _test_attribute(
|
||||
if is_expected and will_raise:
|
||||
ctx = pytest.raises(will_raise)
|
||||
elif is_expected:
|
||||
ctx = pytest.deprecated_call(
|
||||
match=(
|
||||
f"{attribute_name} is deprecated, use: Module."
|
||||
+ f"{module_name} in device.modules instead"
|
||||
)
|
||||
)
|
||||
ctx = pytest.deprecated_call(match=(f"{attribute_name} is deprecated, use:"))
|
||||
else:
|
||||
ctx = pytest.raises(
|
||||
AttributeError, match=f"Device has no attribute '{attribute_name}'"
|
||||
@@ -239,6 +234,19 @@ async def test_deprecated_other_attributes(dev: Device):
|
||||
|
||||
await _test_attribute(dev, "led", bool(led_module), "Led")
|
||||
await _test_attribute(dev, "set_led", bool(led_module), "Led", True)
|
||||
await _test_attribute(dev, "supported_modules", True, None)
|
||||
|
||||
|
||||
async def test_deprecated_emeter_attributes(dev: Device):
|
||||
energy_module = dev.modules.get(Module.Energy)
|
||||
|
||||
await _test_attribute(dev, "get_emeter_realtime", bool(energy_module), "Energy")
|
||||
await _test_attribute(dev, "emeter_realtime", bool(energy_module), "Energy")
|
||||
await _test_attribute(dev, "emeter_today", bool(energy_module), "Energy")
|
||||
await _test_attribute(dev, "emeter_this_month", bool(energy_module), "Energy")
|
||||
await _test_attribute(dev, "current_consumption", bool(energy_module), "Energy")
|
||||
await _test_attribute(dev, "get_emeter_daily", bool(energy_module), "Energy")
|
||||
await _test_attribute(dev, "get_emeter_monthly", bool(energy_module), "Energy")
|
||||
|
||||
|
||||
async def test_deprecated_light_preset_attributes(dev: Device):
|
||||
|
@@ -10,8 +10,9 @@ from voluptuous import (
|
||||
Schema,
|
||||
)
|
||||
|
||||
from kasa import EmeterStatus, KasaException
|
||||
from kasa.iot import IotDevice
|
||||
from kasa import Device, EmeterStatus, Module
|
||||
from kasa.interfaces.energy import Energy
|
||||
from kasa.iot import IotDevice, IotStrip
|
||||
from kasa.iot.modules.emeter import Emeter
|
||||
|
||||
from .conftest import has_emeter, has_emeter_iot, no_emeter
|
||||
@@ -38,16 +39,16 @@ CURRENT_CONSUMPTION_SCHEMA = Schema(
|
||||
async def test_no_emeter(dev):
|
||||
assert not dev.has_emeter
|
||||
|
||||
with pytest.raises(KasaException):
|
||||
with pytest.raises(AttributeError):
|
||||
await dev.get_emeter_realtime()
|
||||
# Only iot devices support the historical stats so other
|
||||
# devices will not implement the methods below
|
||||
if isinstance(dev, IotDevice):
|
||||
with pytest.raises(KasaException):
|
||||
with pytest.raises(AttributeError):
|
||||
await dev.get_emeter_daily()
|
||||
with pytest.raises(KasaException):
|
||||
with pytest.raises(AttributeError):
|
||||
await dev.get_emeter_monthly()
|
||||
with pytest.raises(KasaException):
|
||||
with pytest.raises(AttributeError):
|
||||
await dev.erase_emeter_stats()
|
||||
|
||||
|
||||
@@ -128,11 +129,11 @@ async def test_erase_emeter_stats(dev):
|
||||
@has_emeter_iot
|
||||
async def test_current_consumption(dev):
|
||||
if dev.has_emeter:
|
||||
x = await dev.current_consumption()
|
||||
x = dev.current_consumption
|
||||
assert isinstance(x, float)
|
||||
assert x >= 0.0
|
||||
else:
|
||||
assert await dev.current_consumption() is None
|
||||
assert dev.current_consumption is None
|
||||
|
||||
|
||||
async def test_emeterstatus_missing_current():
|
||||
@@ -173,3 +174,30 @@ async def test_emeter_daily():
|
||||
{"day": now.day, "energy_wh": 500, "month": now.month, "year": now.year}
|
||||
)
|
||||
assert emeter.emeter_today == 0.500
|
||||
|
||||
|
||||
@has_emeter
|
||||
async def test_supported(dev: Device):
|
||||
energy_module = dev.modules.get(Module.Energy)
|
||||
assert energy_module
|
||||
if isinstance(dev, IotDevice):
|
||||
info = (
|
||||
dev._last_update
|
||||
if not isinstance(dev, IotStrip)
|
||||
else dev.children[0].internal_state
|
||||
)
|
||||
emeter = info[energy_module._module]["get_realtime"]
|
||||
has_total = "total" in emeter or "total_wh" in emeter
|
||||
has_voltage_current = "voltage" in emeter or "voltage_mv" in emeter
|
||||
assert (
|
||||
energy_module.supports(Energy.ModuleFeature.CONSUMPTION_TOTAL) is has_total
|
||||
)
|
||||
assert (
|
||||
energy_module.supports(Energy.ModuleFeature.VOLTAGE_CURRENT)
|
||||
is has_voltage_current
|
||||
)
|
||||
assert energy_module.supports(Energy.ModuleFeature.PERIODIC_STATS) is True
|
||||
else:
|
||||
assert energy_module.supports(Energy.ModuleFeature.CONSUMPTION_TOTAL) is False
|
||||
assert energy_module.supports(Energy.ModuleFeature.VOLTAGE_CURRENT) is False
|
||||
assert energy_module.supports(Energy.ModuleFeature.PERIODIC_STATS) is False
|
||||
|
@@ -116,9 +116,16 @@ async def test_initial_update_no_emeter(dev, mocker):
|
||||
dev._legacy_features = set()
|
||||
spy = mocker.spy(dev.protocol, "query")
|
||||
await dev.update()
|
||||
# 2 calls are necessary as some devices crash on unexpected modules
|
||||
# child calls will happen if a child has a module with a query (e.g. schedule)
|
||||
child_calls = 0
|
||||
for child in dev.children:
|
||||
for module in child.modules.values():
|
||||
if module.query():
|
||||
child_calls += 1
|
||||
break
|
||||
# 2 parent are necessary as some devices crash on unexpected modules
|
||||
# See #105, #120, #161
|
||||
assert spy.call_count == 2
|
||||
assert spy.call_count == 2 + child_calls
|
||||
|
||||
|
||||
@device_iot
|
||||
|
Reference in New Issue
Block a user