Use device properties for WeMo Insight sensors (#63525)

pull/68821/head
Eric Severance 2022-03-28 17:47:18 -07:00 committed by GitHub
parent 349060be2f
commit c6ba987995
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 79 additions and 79 deletions

View File

@ -1,5 +1,10 @@
"""Support for power sensors in WeMo Insight devices."""
from __future__ import annotations
import asyncio
from collections.abc import Callable
from dataclasses import dataclass
from typing import cast
from homeassistant.components.sensor import (
SensorDeviceClass,
@ -13,13 +18,42 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.util import convert
from .const import DOMAIN as WEMO_DOMAIN
from .entity import WemoEntity
from .wemo_device import DeviceCoordinator
@dataclass
class AttributeSensorDescription(SensorEntityDescription):
"""SensorEntityDescription for WeMo AttributeSensor entities."""
state_conversion: Callable[[StateType], StateType] | None = None
unique_id_suffix: str | None = None
ATTRIBUTE_SENSORS = (
AttributeSensorDescription(
name="Current Power",
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=POWER_WATT,
key="current_power_watts",
unique_id_suffix="currentpower",
state_conversion=lambda state: round(cast(float, state), 2),
),
AttributeSensorDescription(
name="Today Energy",
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
key="today_kwh",
unique_id_suffix="todaymw",
state_conversion=lambda state: round(cast(float, state), 2),
),
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
@ -30,7 +64,9 @@ async def async_setup_entry(
async def _discovered_wemo(coordinator: DeviceCoordinator) -> None:
"""Handle a discovered Wemo device."""
async_add_entities(
[InsightCurrentPower(coordinator), InsightTodayEnergy(coordinator)]
AttributeSensor(coordinator, description)
for description in ATTRIBUTE_SENSORS
if hasattr(coordinator.wemo, description.key)
)
async_dispatcher_connect(hass, f"{WEMO_DOMAIN}.sensor", _discovered_wemo)
@ -43,66 +79,35 @@ async def async_setup_entry(
)
class InsightSensor(WemoEntity, SensorEntity):
"""Common base for WeMo Insight power sensors."""
class AttributeSensor(WemoEntity, SensorEntity):
"""Sensor that reads attributes of a wemo device."""
entity_description: AttributeSensorDescription
def __init__(
self, coordinator: DeviceCoordinator, description: AttributeSensorDescription
) -> None:
"""Init AttributeSensor."""
super().__init__(coordinator)
self.entity_description = description
@property
def name_suffix(self) -> str:
"""Return the name of the entity if any."""
assert self.entity_description.name
def name_suffix(self) -> str | None:
"""Return the name of the entity."""
return self.entity_description.name
@property
def unique_id_suffix(self) -> str:
"""Return the id of this entity."""
return self.entity_description.key
def unique_id_suffix(self) -> str | None:
"""Suffix to append to the WeMo device's unique ID."""
return self.entity_description.unique_id_suffix
@property
def available(self) -> bool:
"""Return true if sensor is available."""
return (
self.entity_description.key in self.wemo.insight_params
and super().available
)
class InsightCurrentPower(InsightSensor):
"""Current instantaineous power consumption."""
entity_description = SensorEntityDescription(
key="currentpower",
name="Current Power",
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=POWER_WATT,
)
def convert_state(self, value: StateType) -> StateType:
"""Convert native state to a value appropriate for the sensor."""
if (convert := self.entity_description.state_conversion) is None:
return None
return convert(value)
@property
def native_value(self) -> StateType:
"""Return the current power consumption."""
milliwatts = convert(
self.wemo.insight_params.get(self.entity_description.key), float, 0.0
)
assert isinstance(milliwatts, float)
return milliwatts / 1000.0
class InsightTodayEnergy(InsightSensor):
"""Energy used today."""
entity_description = SensorEntityDescription(
key="todaymw",
name="Today Energy",
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
)
@property
def native_value(self) -> StateType:
"""Return the current energy use today."""
milliwatt_seconds = convert(
self.wemo.insight_params.get(self.entity_description.key), float, 0.0
)
assert isinstance(milliwatt_seconds, float)
return round(milliwatt_seconds / (1000.0 * 1000.0 * 60), 2)
"""Return the value of the device attribute."""
return self.convert_state(getattr(self.wemo, self.entity_description.key))

View File

@ -15,6 +15,9 @@ MOCK_PORT = 50000
MOCK_NAME = "WemoDeviceName"
MOCK_SERIAL_NUMBER = "WemoSerialNumber"
MOCK_FIRMWARE_VERSION = "WeMo_WW_2.00.XXXXX.PVT-OWRT"
MOCK_INSIGHT_CURRENT_WATTS = 0.01
MOCK_INSIGHT_TODAY_KWH = 3.33
MOCK_INSIGHT_STATE_THRESHOLD_POWER = 8.0
@pytest.fixture(name="pywemo_model")
@ -64,6 +67,15 @@ def pywemo_device_fixture(pywemo_registry, pywemo_model):
device.get_state.return_value = 0 # Default to Off
device.supports_long_press.return_value = cls.supports_long_press()
if issubclass(cls, pywemo.Insight):
device.get_standby_state = pywemo.StandbyState.OFF
device.current_power_watts = MOCK_INSIGHT_CURRENT_WATTS
device.today_kwh = MOCK_INSIGHT_TODAY_KWH
device.threshold_power_watts = MOCK_INSIGHT_STATE_THRESHOLD_POWER
device.on_for = 1234
device.today_on_time = 5678
device.total_on_time = 9012
url = f"http://{MOCK_HOST}:{MOCK_PORT}/setup.xml"
with patch("pywemo.setup_url_for_address", return_value=url), patch(
"pywemo.discovery.device_from_description", return_value=device

View File

@ -2,13 +2,7 @@
import pytest
from homeassistant.components.homeassistant import (
DOMAIN as HA_DOMAIN,
SERVICE_UPDATE_ENTITY,
)
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE
from homeassistant.setup import async_setup_component
from .conftest import MOCK_INSIGHT_CURRENT_WATTS, MOCK_INSIGHT_TODAY_KWH
from .entity_test_helpers import EntityTestHelpers
@ -38,7 +32,6 @@ class InsightTestTemplate(EntityTestHelpers):
ENTITY_ID_SUFFIX: str
EXPECTED_STATE_VALUE: str
INSIGHT_PARAM_NAME: str
@pytest.fixture(name="wemo_entity_suffix")
@classmethod
@ -46,30 +39,20 @@ class InsightTestTemplate(EntityTestHelpers):
"""Select the appropriate entity for the test."""
return cls.ENTITY_ID_SUFFIX
async def test_state_unavailable(self, hass, wemo_entity, pywemo_device):
"""Test that there is no failure if the insight_params is not populated."""
del pywemo_device.insight_params[self.INSIGHT_PARAM_NAME]
await async_setup_component(hass, HA_DOMAIN, {})
await hass.services.async_call(
HA_DOMAIN,
SERVICE_UPDATE_ENTITY,
{ATTR_ENTITY_ID: [wemo_entity.entity_id]},
blocking=True,
)
assert hass.states.get(wemo_entity.entity_id).state == STATE_UNAVAILABLE
def test_state(self, hass, wemo_entity):
"""Test the sensor state."""
assert hass.states.get(wemo_entity.entity_id).state == self.EXPECTED_STATE_VALUE
class TestInsightCurrentPower(InsightTestTemplate):
"""Test the InsightCurrentPower class."""
ENTITY_ID_SUFFIX = "_current_power"
EXPECTED_STATE_VALUE = "0.001"
INSIGHT_PARAM_NAME = "currentpower"
EXPECTED_STATE_VALUE = str(MOCK_INSIGHT_CURRENT_WATTS)
class TestInsightTodayEnergy(InsightTestTemplate):
"""Test the InsightTodayEnergy class."""
ENTITY_ID_SUFFIX = "_today_energy"
EXPECTED_STATE_VALUE = "3.33"
INSIGHT_PARAM_NAME = "todaymw"
EXPECTED_STATE_VALUE = str(MOCK_INSIGHT_TODAY_KWH)