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:
Steven B
2024-06-17 11:22:05 +01:00
committed by GitHub
parent 51a972542f
commit b4a6df2b5c
14 changed files with 487 additions and 382 deletions

View File

@@ -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):

View File

@@ -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

View File

@@ -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