Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions kasa/smart/modules/energy.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,30 @@ class Energy(SmartModule, EnergyInterface):

REQUIRED_COMPONENT = "energy_monitoring"

async def _post_update_hook(self) -> None:
if "voltage_mv" in self.data.get("get_emeter_data", {}):
self._supported = (
self._supported | EnergyInterface.ModuleFeature.VOLTAGE_CURRENT
)

def query(self) -> dict:
"""Query to execute during the update cycle."""
req = {
"get_energy_usage": None,
}
if self.supported_version > 1:
req["get_current_power"] = None
req["get_emeter_data"] = None
req["get_emeter_vgain_igain"] = None
return req

@property
@raise_if_update_error
def current_consumption(self) -> float | None:
"""Current power in watts."""
if (power := self.energy.get("current_power")) is not None:
if (power := self.energy.get("current_power")) is not None or (
power := self.data.get("get_emeter_data", {}).get("power_mw")
) is not None:
return power / 1_000
# Fallback if get_energy_usage does not provide current_power,
# which can happen on some newer devices (e.g. P304M).
Expand Down Expand Up @@ -58,7 +68,10 @@ def _get_status_from_energy(self, energy: dict) -> EmeterStatus:
@raise_if_update_error
def status(self) -> EmeterStatus:
"""Get the emeter status."""
return self._get_status_from_energy(self.energy)
if "get_emeter_data" in self.data:
return EmeterStatus(self.data["get_emeter_data"])
else:
return self._get_status_from_energy(self.energy)

async def get_status(self) -> EmeterStatus:
"""Return real-time statistics."""
Expand Down Expand Up @@ -87,13 +100,15 @@ def consumption_total(self) -> float | None:
@raise_if_update_error
def current(self) -> float | None:
"""Return the current in A."""
return None
ma = self.data.get("get_emeter_data", {}).get("current_ma")
return ma / 1000 if ma else None

@property
@raise_if_update_error
def voltage(self) -> float | None:
"""Get the current voltage in V."""
return None
mv = self.data.get("get_emeter_data", {}).get("voltage_mv")
return mv / 1000 if mv else None

async def _deprecated_get_realtime(self) -> EmeterStatus:
"""Retrieve current energy readings."""
Expand Down
13 changes: 13 additions & 0 deletions tests/fakeprotocol_smart.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,19 @@ def credentials_hash(self):
),
"get_device_usage": ("device", {}),
"get_connect_cloud_state": ("cloud_connect", {"status": 0}),
"get_emeter_data": (
"energy_monitoring",
{
"current_ma": 33,
"energy_wh": 971,
"power_mw": 1003,
"voltage_mv": 121215,
},
),
"get_emeter_vgain_igain": (
"energy_monitoring",
{"igain": 10861, "vgain": 118657},
),
}

async def send(self, request: str):
Expand Down
5 changes: 4 additions & 1 deletion tests/test_emeter.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,5 +211,8 @@ async def test_supported(dev: Device):
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
if energy_module.supported_version < 2:
assert energy_module.supports(Energy.ModuleFeature.VOLTAGE_CURRENT) is False
else:
assert energy_module.supports(Energy.ModuleFeature.VOLTAGE_CURRENT) is True
Loading