mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-11-24 02:08:37 +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:
@@ -220,7 +220,7 @@ class IotBulb(IotDevice):
|
||||
Module.IotAntitheft, Antitheft(self, "smartlife.iot.common.anti_theft")
|
||||
)
|
||||
self.add_module(Module.IotTime, Time(self, "smartlife.iot.common.timesetting"))
|
||||
self.add_module(Module.IotEmeter, Emeter(self, self.emeter_type))
|
||||
self.add_module(Module.Energy, Emeter(self, self.emeter_type))
|
||||
self.add_module(Module.IotCountdown, Countdown(self, "countdown"))
|
||||
self.add_module(Module.IotCloud, Cloud(self, "smartlife.iot.common.cloud"))
|
||||
self.add_module(Module.Light, Light(self, self.LIGHT_SERVICE))
|
||||
|
||||
@@ -23,7 +23,6 @@ from typing import TYPE_CHECKING, Any, Mapping, Sequence, cast
|
||||
|
||||
from ..device import Device, WifiNetwork
|
||||
from ..deviceconfig import DeviceConfig
|
||||
from ..emeterstatus import EmeterStatus
|
||||
from ..exceptions import KasaException
|
||||
from ..feature import Feature
|
||||
from ..module import Module
|
||||
@@ -188,7 +187,7 @@ class IotDevice(Device):
|
||||
super().__init__(host=host, config=config, protocol=protocol)
|
||||
|
||||
self._sys_info: Any = None # TODO: this is here to avoid changing tests
|
||||
self._supported_modules: dict[str, IotModule] | None = None
|
||||
self._supported_modules: dict[str | ModuleName[Module], IotModule] | None = None
|
||||
self._legacy_features: set[str] = set()
|
||||
self._children: Mapping[str, IotDevice] = {}
|
||||
self._modules: dict[str | ModuleName[Module], IotModule] = {}
|
||||
@@ -199,15 +198,16 @@ class IotDevice(Device):
|
||||
return list(self._children.values())
|
||||
|
||||
@property
|
||||
@requires_update
|
||||
def modules(self) -> ModuleMapping[IotModule]:
|
||||
"""Return the device modules."""
|
||||
if TYPE_CHECKING:
|
||||
return cast(ModuleMapping[IotModule], self._modules)
|
||||
return self._modules
|
||||
return cast(ModuleMapping[IotModule], self._supported_modules)
|
||||
return self._supported_modules
|
||||
|
||||
def add_module(self, name: str | ModuleName[Module], module: IotModule):
|
||||
"""Register a module."""
|
||||
if name in self.modules:
|
||||
if name in self._modules:
|
||||
_LOGGER.debug("Module %s already registered, ignoring..." % name)
|
||||
return
|
||||
|
||||
@@ -272,14 +272,6 @@ class IotDevice(Device):
|
||||
"""Return a set of features that the device supports."""
|
||||
return self._features
|
||||
|
||||
@property # type: ignore
|
||||
@requires_update
|
||||
def supported_modules(self) -> list[str | ModuleName[Module]]:
|
||||
"""Return a set of modules supported by the device."""
|
||||
# TODO: this should rather be called `features`, but we don't want to break
|
||||
# the API now. Maybe just deprecate it and point the users to use this?
|
||||
return list(self._modules.keys())
|
||||
|
||||
@property # type: ignore
|
||||
@requires_update
|
||||
def has_emeter(self) -> bool:
|
||||
@@ -321,6 +313,11 @@ class IotDevice(Device):
|
||||
|
||||
async def _initialize_modules(self):
|
||||
"""Initialize modules not added in init."""
|
||||
if self.has_emeter:
|
||||
_LOGGER.debug(
|
||||
"The device has emeter, querying its information along sysinfo"
|
||||
)
|
||||
self.add_module(Module.Energy, Emeter(self, self.emeter_type))
|
||||
|
||||
async def _initialize_features(self):
|
||||
"""Initialize common features."""
|
||||
@@ -357,29 +354,13 @@ class IotDevice(Device):
|
||||
)
|
||||
)
|
||||
|
||||
for module in self._modules.values():
|
||||
for module in self._supported_modules.values():
|
||||
module._initialize_features()
|
||||
for module_feat in module._module_features.values():
|
||||
self._add_feature(module_feat)
|
||||
|
||||
async def _modular_update(self, req: dict) -> None:
|
||||
"""Execute an update query."""
|
||||
if self.has_emeter:
|
||||
_LOGGER.debug(
|
||||
"The device has emeter, querying its information along sysinfo"
|
||||
)
|
||||
self.add_module(Module.IotEmeter, Emeter(self, self.emeter_type))
|
||||
|
||||
# TODO: perhaps modules should not have unsupported modules,
|
||||
# making separate handling for this unnecessary
|
||||
if self._supported_modules is None:
|
||||
supported = {}
|
||||
for module in self._modules.values():
|
||||
if module.is_supported:
|
||||
supported[module._module] = module
|
||||
|
||||
self._supported_modules = supported
|
||||
|
||||
request_list = []
|
||||
est_response_size = 1024 if "system" in req else 0
|
||||
for module in self._modules.values():
|
||||
@@ -411,6 +392,15 @@ class IotDevice(Device):
|
||||
update = {**update, **response}
|
||||
self._last_update = update
|
||||
|
||||
# IOT modules are added as default but could be unsupported post first update
|
||||
if self._supported_modules is None:
|
||||
supported = {}
|
||||
for module_name, module in self._modules.items():
|
||||
if module.is_supported:
|
||||
supported[module_name] = module
|
||||
|
||||
self._supported_modules = supported
|
||||
|
||||
def update_from_discover_info(self, info: dict[str, Any]) -> None:
|
||||
"""Update state from info from the discover call."""
|
||||
self._discovery_info = info
|
||||
@@ -557,74 +547,6 @@ class IotDevice(Device):
|
||||
"""
|
||||
return await self._query_helper("system", "set_mac_addr", {"mac": mac})
|
||||
|
||||
@property
|
||||
@requires_update
|
||||
def emeter_realtime(self) -> EmeterStatus:
|
||||
"""Return current energy readings."""
|
||||
self._verify_emeter()
|
||||
return EmeterStatus(self.modules[Module.IotEmeter].realtime)
|
||||
|
||||
async def get_emeter_realtime(self) -> EmeterStatus:
|
||||
"""Retrieve current energy readings."""
|
||||
self._verify_emeter()
|
||||
return EmeterStatus(await self.modules[Module.IotEmeter].get_realtime())
|
||||
|
||||
@property
|
||||
@requires_update
|
||||
def emeter_today(self) -> float | None:
|
||||
"""Return today's energy consumption in kWh."""
|
||||
self._verify_emeter()
|
||||
return self.modules[Module.IotEmeter].emeter_today
|
||||
|
||||
@property
|
||||
@requires_update
|
||||
def emeter_this_month(self) -> float | None:
|
||||
"""Return this month's energy consumption in kWh."""
|
||||
self._verify_emeter()
|
||||
return self.modules[Module.IotEmeter].emeter_this_month
|
||||
|
||||
async def get_emeter_daily(
|
||||
self, year: int | None = None, month: int | None = None, kwh: bool = True
|
||||
) -> dict:
|
||||
"""Retrieve daily statistics for a given month.
|
||||
|
||||
:param year: year for which to retrieve statistics (default: this year)
|
||||
:param month: month for which to retrieve statistics (default: this
|
||||
month)
|
||||
:param kwh: return usage in kWh (default: True)
|
||||
:return: mapping of day of month to value
|
||||
"""
|
||||
self._verify_emeter()
|
||||
return await self.modules[Module.IotEmeter].get_daystat(
|
||||
year=year, month=month, kwh=kwh
|
||||
)
|
||||
|
||||
@requires_update
|
||||
async def get_emeter_monthly(
|
||||
self, year: int | None = None, kwh: bool = True
|
||||
) -> dict:
|
||||
"""Retrieve monthly statistics for a given year.
|
||||
|
||||
:param year: year for which to retrieve statistics (default: this year)
|
||||
:param kwh: return usage in kWh (default: True)
|
||||
:return: dict: mapping of month to value
|
||||
"""
|
||||
self._verify_emeter()
|
||||
return await self.modules[Module.IotEmeter].get_monthstat(year=year, kwh=kwh)
|
||||
|
||||
@requires_update
|
||||
async def erase_emeter_stats(self) -> dict:
|
||||
"""Erase energy meter statistics."""
|
||||
self._verify_emeter()
|
||||
return await self.modules[Module.IotEmeter].erase_stats()
|
||||
|
||||
@requires_update
|
||||
async def current_consumption(self) -> float:
|
||||
"""Get the current power consumption in Watt."""
|
||||
self._verify_emeter()
|
||||
response = self.emeter_realtime
|
||||
return float(response["power"])
|
||||
|
||||
async def reboot(self, delay: int = 1) -> None:
|
||||
"""Reboot the device.
|
||||
|
||||
|
||||
@@ -9,16 +9,17 @@ from typing import Any
|
||||
|
||||
from ..device_type import DeviceType
|
||||
from ..deviceconfig import DeviceConfig
|
||||
from ..emeterstatus import EmeterStatus
|
||||
from ..exceptions import KasaException
|
||||
from ..feature import Feature
|
||||
from ..interfaces import Energy
|
||||
from ..module import Module
|
||||
from ..protocol import BaseProtocol
|
||||
from .iotdevice import (
|
||||
EmeterStatus,
|
||||
IotDevice,
|
||||
merge,
|
||||
requires_update,
|
||||
)
|
||||
from .iotmodule import IotModule
|
||||
from .iotplug import IotPlug
|
||||
from .modules import Antitheft, Countdown, Schedule, Time, Usage
|
||||
|
||||
@@ -97,11 +98,20 @@ class IotStrip(IotDevice):
|
||||
super().__init__(host=host, config=config, protocol=protocol)
|
||||
self.emeter_type = "emeter"
|
||||
self._device_type = DeviceType.Strip
|
||||
|
||||
async def _initialize_modules(self):
|
||||
"""Initialize modules."""
|
||||
# Strip has different modules to plug so do not call super
|
||||
self.add_module(Module.IotAntitheft, Antitheft(self, "anti_theft"))
|
||||
self.add_module(Module.IotSchedule, Schedule(self, "schedule"))
|
||||
self.add_module(Module.IotUsage, Usage(self, "schedule"))
|
||||
self.add_module(Module.IotTime, Time(self, "time"))
|
||||
self.add_module(Module.IotCountdown, Countdown(self, "countdown"))
|
||||
if self.has_emeter:
|
||||
_LOGGER.debug(
|
||||
"The device has emeter, querying its information along sysinfo"
|
||||
)
|
||||
self.add_module(Module.Energy, StripEmeter(self, self.emeter_type))
|
||||
|
||||
@property # type: ignore
|
||||
@requires_update
|
||||
@@ -114,10 +124,12 @@ class IotStrip(IotDevice):
|
||||
|
||||
Needed for methods that are decorated with `requires_update`.
|
||||
"""
|
||||
# Super initializes modules and features
|
||||
await super().update(update_children)
|
||||
|
||||
initialize_children = not self.children
|
||||
# Initialize the child devices during the first update.
|
||||
if not self.children:
|
||||
if initialize_children:
|
||||
children = self.sys_info["children"]
|
||||
_LOGGER.debug("Initializing %s child sockets", len(children))
|
||||
self._children = {
|
||||
@@ -127,12 +139,22 @@ class IotStrip(IotDevice):
|
||||
for child in children
|
||||
}
|
||||
for child in self._children.values():
|
||||
await child._initialize_features()
|
||||
await child._initialize_modules()
|
||||
|
||||
if update_children and self.has_emeter:
|
||||
if update_children:
|
||||
for plug in self.children:
|
||||
await plug.update()
|
||||
|
||||
if not self.features:
|
||||
await self._initialize_features()
|
||||
|
||||
async def _initialize_features(self):
|
||||
"""Initialize common features."""
|
||||
# Do not initialize features until children are created
|
||||
if not self.children:
|
||||
return
|
||||
await super()._initialize_features()
|
||||
|
||||
async def turn_on(self, **kwargs):
|
||||
"""Turn the strip on."""
|
||||
await self._query_helper("system", "set_relay_state", {"state": 1})
|
||||
@@ -150,21 +172,43 @@ class IotStrip(IotDevice):
|
||||
|
||||
return min(plug.on_since for plug in self.children if plug.on_since is not None)
|
||||
|
||||
async def current_consumption(self) -> float:
|
||||
"""Get the current power consumption in watts."""
|
||||
return sum([await plug.current_consumption() for plug in self.children])
|
||||
|
||||
@requires_update
|
||||
async def get_emeter_realtime(self) -> EmeterStatus:
|
||||
class StripEmeter(IotModule, Energy):
|
||||
"""Energy module implementation to aggregate child modules."""
|
||||
|
||||
_supported = (
|
||||
Energy.ModuleFeature.CONSUMPTION_TOTAL
|
||||
| Energy.ModuleFeature.PERIODIC_STATS
|
||||
| Energy.ModuleFeature.VOLTAGE_CURRENT
|
||||
)
|
||||
|
||||
def supports(self, module_feature: Energy.ModuleFeature) -> bool:
|
||||
"""Return True if module supports the feature."""
|
||||
return module_feature in self._supported
|
||||
|
||||
def query(self):
|
||||
"""Return the base query."""
|
||||
return {}
|
||||
|
||||
@property
|
||||
def current_consumption(self) -> float | None:
|
||||
"""Get the current power consumption in watts."""
|
||||
return sum(
|
||||
v if (v := plug.modules[Module.Energy].current_consumption) else 0.0
|
||||
for plug in self._device.children
|
||||
)
|
||||
|
||||
async def get_status(self) -> EmeterStatus:
|
||||
"""Retrieve current energy readings."""
|
||||
emeter_rt = await self._async_get_emeter_sum("get_emeter_realtime", {})
|
||||
emeter_rt = await self._async_get_emeter_sum("get_status", {})
|
||||
# Voltage is averaged since each read will result
|
||||
# in a slightly different voltage since they are not atomic
|
||||
emeter_rt["voltage_mv"] = int(emeter_rt["voltage_mv"] / len(self.children))
|
||||
emeter_rt["voltage_mv"] = int(
|
||||
emeter_rt["voltage_mv"] / len(self._device.children)
|
||||
)
|
||||
return EmeterStatus(emeter_rt)
|
||||
|
||||
@requires_update
|
||||
async def get_emeter_daily(
|
||||
async def get_daily_stats(
|
||||
self, year: int | None = None, month: int | None = None, kwh: bool = True
|
||||
) -> dict:
|
||||
"""Retrieve daily statistics for a given month.
|
||||
@@ -176,11 +220,10 @@ class IotStrip(IotDevice):
|
||||
:return: mapping of day of month to value
|
||||
"""
|
||||
return await self._async_get_emeter_sum(
|
||||
"get_emeter_daily", {"year": year, "month": month, "kwh": kwh}
|
||||
"get_daily_stats", {"year": year, "month": month, "kwh": kwh}
|
||||
)
|
||||
|
||||
@requires_update
|
||||
async def get_emeter_monthly(
|
||||
async def get_monthly_stats(
|
||||
self, year: int | None = None, kwh: bool = True
|
||||
) -> dict:
|
||||
"""Retrieve monthly statistics for a given year.
|
||||
@@ -189,44 +232,68 @@ class IotStrip(IotDevice):
|
||||
:param kwh: return usage in kWh (default: True)
|
||||
"""
|
||||
return await self._async_get_emeter_sum(
|
||||
"get_emeter_monthly", {"year": year, "kwh": kwh}
|
||||
"get_monthly_stats", {"year": year, "kwh": kwh}
|
||||
)
|
||||
|
||||
async def _async_get_emeter_sum(self, func: str, kwargs: dict[str, Any]) -> dict:
|
||||
"""Retreive emeter stats for a time period from children."""
|
||||
self._verify_emeter()
|
||||
"""Retrieve emeter stats for a time period from children."""
|
||||
return merge_sums(
|
||||
[await getattr(plug, func)(**kwargs) for plug in self.children]
|
||||
[
|
||||
await getattr(plug.modules[Module.Energy], func)(**kwargs)
|
||||
for plug in self._device.children
|
||||
]
|
||||
)
|
||||
|
||||
@requires_update
|
||||
async def erase_emeter_stats(self):
|
||||
async def erase_stats(self):
|
||||
"""Erase energy meter statistics for all plugs."""
|
||||
for plug in self.children:
|
||||
await plug.erase_emeter_stats()
|
||||
for plug in self._device.children:
|
||||
await plug.modules[Module.Energy].erase_stats()
|
||||
|
||||
@property # type: ignore
|
||||
@requires_update
|
||||
def emeter_this_month(self) -> float | None:
|
||||
def consumption_this_month(self) -> float | None:
|
||||
"""Return this month's energy consumption in kWh."""
|
||||
return sum(v if (v := plug.emeter_this_month) else 0 for plug in self.children)
|
||||
return sum(
|
||||
v if (v := plug.modules[Module.Energy].consumption_this_month) else 0.0
|
||||
for plug in self._device.children
|
||||
)
|
||||
|
||||
@property # type: ignore
|
||||
@requires_update
|
||||
def emeter_today(self) -> float | None:
|
||||
def consumption_today(self) -> float | None:
|
||||
"""Return this month's energy consumption in kWh."""
|
||||
return sum(v if (v := plug.emeter_today) else 0 for plug in self.children)
|
||||
return sum(
|
||||
v if (v := plug.modules[Module.Energy].consumption_today) else 0.0
|
||||
for plug in self._device.children
|
||||
)
|
||||
|
||||
@property # type: ignore
|
||||
@requires_update
|
||||
def emeter_realtime(self) -> EmeterStatus:
|
||||
def consumption_total(self) -> float | None:
|
||||
"""Return total energy consumption since reboot in kWh."""
|
||||
return sum(
|
||||
v if (v := plug.modules[Module.Energy].consumption_total) else 0.0
|
||||
for plug in self._device.children
|
||||
)
|
||||
|
||||
@property # type: ignore
|
||||
def status(self) -> EmeterStatus:
|
||||
"""Return current energy readings."""
|
||||
emeter = merge_sums([plug.emeter_realtime for plug in self.children])
|
||||
emeter = merge_sums(
|
||||
[plug.modules[Module.Energy].status for plug in self._device.children]
|
||||
)
|
||||
# Voltage is averaged since each read will result
|
||||
# in a slightly different voltage since they are not atomic
|
||||
emeter["voltage_mv"] = int(emeter["voltage_mv"] / len(self.children))
|
||||
emeter["voltage_mv"] = int(emeter["voltage_mv"] / len(self._device.children))
|
||||
return EmeterStatus(emeter)
|
||||
|
||||
@property
|
||||
def current(self) -> float | None:
|
||||
"""Return the current in A."""
|
||||
return self.status.current
|
||||
|
||||
@property
|
||||
def voltage(self) -> float | None:
|
||||
"""Get the current voltage in V."""
|
||||
return self.status.voltage
|
||||
|
||||
|
||||
class IotStripPlug(IotPlug):
|
||||
"""Representation of a single socket in a power strip.
|
||||
@@ -275,9 +342,10 @@ class IotStripPlug(IotPlug):
|
||||
icon="mdi:clock",
|
||||
)
|
||||
)
|
||||
# If the strip plug has it's own modules we should call initialize
|
||||
# features for the modules here. However the _initialize_modules function
|
||||
# above does not seem to be called.
|
||||
for module in self._supported_modules.values():
|
||||
module._initialize_features()
|
||||
for module_feat in module._module_features.values():
|
||||
self._add_feature(module_feat)
|
||||
|
||||
async def update(self, update_children: bool = True):
|
||||
"""Query the device to update the data.
|
||||
@@ -285,26 +353,8 @@ class IotStripPlug(IotPlug):
|
||||
Needed for properties that are decorated with `requires_update`.
|
||||
"""
|
||||
await self._modular_update({})
|
||||
|
||||
def _create_emeter_request(self, year: int | None = None, month: int | None = None):
|
||||
"""Create a request for requesting all emeter statistics at once."""
|
||||
if year is None:
|
||||
year = datetime.now().year
|
||||
if month is None:
|
||||
month = datetime.now().month
|
||||
|
||||
req: dict[str, Any] = {}
|
||||
|
||||
merge(req, self._create_request("emeter", "get_realtime"))
|
||||
merge(req, self._create_request("emeter", "get_monthstat", {"year": year}))
|
||||
merge(
|
||||
req,
|
||||
self._create_request(
|
||||
"emeter", "get_daystat", {"month": month, "year": year}
|
||||
),
|
||||
)
|
||||
|
||||
return req
|
||||
if not self._features:
|
||||
await self._initialize_features()
|
||||
|
||||
def _create_request(
|
||||
self, target: str, cmd: str, arg: dict | None = None, child_ids=None
|
||||
|
||||
@@ -4,130 +4,71 @@ from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from ... import Device
|
||||
from ...emeterstatus import EmeterStatus
|
||||
from ...feature import Feature
|
||||
from ...interfaces.energy import Energy as EnergyInterface
|
||||
from .usage import Usage
|
||||
|
||||
|
||||
class Emeter(Usage):
|
||||
class Emeter(Usage, EnergyInterface):
|
||||
"""Emeter module."""
|
||||
|
||||
def __init__(self, device: Device, module: str):
|
||||
super().__init__(device, module)
|
||||
self._add_feature(
|
||||
Feature(
|
||||
device,
|
||||
name="Current consumption",
|
||||
attribute_getter="current_consumption",
|
||||
container=self,
|
||||
unit="W",
|
||||
id="current_power_w", # for homeassistant backwards compat
|
||||
precision_hint=1,
|
||||
category=Feature.Category.Primary,
|
||||
def _post_update_hook(self) -> None:
|
||||
self._supported = EnergyInterface.ModuleFeature.PERIODIC_STATS
|
||||
if (
|
||||
"voltage_mv" in self.data["get_realtime"]
|
||||
or "voltage" in self.data["get_realtime"]
|
||||
):
|
||||
self._supported = (
|
||||
self._supported | EnergyInterface.ModuleFeature.VOLTAGE_CURRENT
|
||||
)
|
||||
)
|
||||
self._add_feature(
|
||||
Feature(
|
||||
device,
|
||||
name="Today's consumption",
|
||||
attribute_getter="emeter_today",
|
||||
container=self,
|
||||
unit="kWh",
|
||||
id="today_energy_kwh", # for homeassistant backwards compat
|
||||
precision_hint=3,
|
||||
category=Feature.Category.Info,
|
||||
if (
|
||||
"total_wh" in self.data["get_realtime"]
|
||||
or "total" in self.data["get_realtime"]
|
||||
):
|
||||
self._supported = (
|
||||
self._supported | EnergyInterface.ModuleFeature.CONSUMPTION_TOTAL
|
||||
)
|
||||
)
|
||||
self._add_feature(
|
||||
Feature(
|
||||
device,
|
||||
id="consumption_this_month",
|
||||
name="This month's consumption",
|
||||
attribute_getter="emeter_this_month",
|
||||
container=self,
|
||||
unit="kWh",
|
||||
precision_hint=3,
|
||||
category=Feature.Category.Info,
|
||||
)
|
||||
)
|
||||
self._add_feature(
|
||||
Feature(
|
||||
device,
|
||||
name="Total consumption since reboot",
|
||||
attribute_getter="emeter_total",
|
||||
container=self,
|
||||
unit="kWh",
|
||||
id="total_energy_kwh", # for homeassistant backwards compat
|
||||
precision_hint=3,
|
||||
category=Feature.Category.Info,
|
||||
)
|
||||
)
|
||||
self._add_feature(
|
||||
Feature(
|
||||
device,
|
||||
name="Voltage",
|
||||
attribute_getter="voltage",
|
||||
container=self,
|
||||
unit="V",
|
||||
id="voltage", # for homeassistant backwards compat
|
||||
precision_hint=1,
|
||||
category=Feature.Category.Primary,
|
||||
)
|
||||
)
|
||||
self._add_feature(
|
||||
Feature(
|
||||
device,
|
||||
name="Current",
|
||||
attribute_getter="current",
|
||||
container=self,
|
||||
unit="A",
|
||||
id="current_a", # for homeassistant backwards compat
|
||||
precision_hint=2,
|
||||
category=Feature.Category.Primary,
|
||||
)
|
||||
)
|
||||
|
||||
@property # type: ignore
|
||||
def realtime(self) -> EmeterStatus:
|
||||
def status(self) -> EmeterStatus:
|
||||
"""Return current energy readings."""
|
||||
return EmeterStatus(self.data["get_realtime"])
|
||||
|
||||
@property
|
||||
def emeter_today(self) -> float | None:
|
||||
def consumption_today(self) -> float | None:
|
||||
"""Return today's energy consumption in kWh."""
|
||||
raw_data = self.daily_data
|
||||
today = datetime.now().day
|
||||
data = self._convert_stat_data(raw_data, entry_key="day", key=today)
|
||||
return data.get(today)
|
||||
return data.get(today, 0.0)
|
||||
|
||||
@property
|
||||
def emeter_this_month(self) -> float | None:
|
||||
def consumption_this_month(self) -> float | None:
|
||||
"""Return this month's energy consumption in kWh."""
|
||||
raw_data = self.monthly_data
|
||||
current_month = datetime.now().month
|
||||
data = self._convert_stat_data(raw_data, entry_key="month", key=current_month)
|
||||
return data.get(current_month)
|
||||
return data.get(current_month, 0.0)
|
||||
|
||||
@property
|
||||
def current_consumption(self) -> float | None:
|
||||
"""Get the current power consumption in Watt."""
|
||||
return self.realtime.power
|
||||
return self.status.power
|
||||
|
||||
@property
|
||||
def emeter_total(self) -> float | None:
|
||||
def consumption_total(self) -> float | None:
|
||||
"""Return total consumption since last reboot in kWh."""
|
||||
return self.realtime.total
|
||||
return self.status.total
|
||||
|
||||
@property
|
||||
def current(self) -> float | None:
|
||||
"""Return the current in A."""
|
||||
return self.realtime.current
|
||||
return self.status.current
|
||||
|
||||
@property
|
||||
def voltage(self) -> float | None:
|
||||
"""Get the current voltage in V."""
|
||||
return self.realtime.voltage
|
||||
return self.status.voltage
|
||||
|
||||
async def erase_stats(self):
|
||||
"""Erase all stats.
|
||||
@@ -136,11 +77,11 @@ class Emeter(Usage):
|
||||
"""
|
||||
return await self.call("erase_emeter_stat")
|
||||
|
||||
async def get_realtime(self):
|
||||
async def get_status(self) -> EmeterStatus:
|
||||
"""Return real-time statistics."""
|
||||
return await self.call("get_realtime")
|
||||
return EmeterStatus(await self.call("get_realtime"))
|
||||
|
||||
async def get_daystat(self, *, year=None, month=None, kwh=True) -> dict:
|
||||
async def get_daily_stats(self, *, year=None, month=None, kwh=True) -> dict:
|
||||
"""Return daily stats for the given year & month.
|
||||
|
||||
The return value is a dictionary of {day: energy, ...}.
|
||||
@@ -149,7 +90,7 @@ class Emeter(Usage):
|
||||
data = self._convert_stat_data(data["day_list"], entry_key="day", kwh=kwh)
|
||||
return data
|
||||
|
||||
async def get_monthstat(self, *, year=None, kwh=True) -> dict:
|
||||
async def get_monthly_stats(self, *, year=None, kwh=True) -> dict:
|
||||
"""Return monthly stats for the given year.
|
||||
|
||||
The return value is a dictionary of {month: energy, ...}.
|
||||
|
||||
@@ -30,3 +30,8 @@ class Led(IotModule, LedInterface):
|
||||
async def set_led(self, state: bool):
|
||||
"""Set the state of the led (night mode)."""
|
||||
return await self.call("set_led_off", {"off": int(not state)})
|
||||
|
||||
@property
|
||||
def is_supported(self) -> bool:
|
||||
"""Return whether the module is supported by the device."""
|
||||
return "led_off" in self.data
|
||||
|
||||
Reference in New Issue
Block a user