Add power sensor to WiZ (#73260)

This commit is contained in:
J. Nick Koston 2022-06-09 18:10:15 -10:00 committed by GitHub
parent 5863d57e73
commit a9ab98fb45
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 95 additions and 11 deletions

View file

@ -1,4 +1,6 @@
"""WiZ Platform integration.""" """WiZ Platform integration."""
from __future__ import annotations
import asyncio import asyncio
from datetime import timedelta from datetime import timedelta
import logging import logging
@ -80,10 +82,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"Found bulb {bulb.mac} at {ip_address}, expected {entry.unique_id}" "Found bulb {bulb.mac} at {ip_address}, expected {entry.unique_id}"
) )
async def _async_update() -> None: async def _async_update() -> float | None:
"""Update the WiZ device.""" """Update the WiZ device."""
try: try:
await bulb.updateState() await bulb.updateState()
if bulb.power_monitoring is not False:
power: float | None = await bulb.get_power()
return power
return None
except WIZ_EXCEPTIONS as ex: except WIZ_EXCEPTIONS as ex:
raise UpdateFailed(f"Failed to update device at {ip_address}: {ex}") from ex raise UpdateFailed(f"Failed to update device at {ip_address}: {ex}") from ex
@ -117,7 +123,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
def _async_push_update(state: PilotParser) -> None: def _async_push_update(state: PilotParser) -> None:
"""Receive a push update.""" """Receive a push update."""
_LOGGER.debug("%s: Got push update: %s", bulb.mac, state.pilotResult) _LOGGER.debug("%s: Got push update: %s", bulb.mac, state.pilotResult)
coordinator.async_set_updated_data(None) coordinator.async_set_updated_data(coordinator.data)
if state.get_source() == PIR_SOURCE: if state.get_source() == PIR_SOURCE:
async_dispatcher_send(hass, SIGNAL_WIZ_PIR.format(bulb.mac)) async_dispatcher_send(hass, SIGNAL_WIZ_PIR.format(bulb.mac))

View file

@ -2,7 +2,7 @@
from __future__ import annotations from __future__ import annotations
from abc import abstractmethod from abc import abstractmethod
from typing import Any from typing import Any, Optional
from pywizlight.bulblibrary import BulbType from pywizlight.bulblibrary import BulbType
@ -10,12 +10,15 @@ from homeassistant.const import ATTR_HW_VERSION, ATTR_MODEL
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.entity import DeviceInfo, Entity, ToggleEntity from homeassistant.helpers.entity import DeviceInfo, Entity, ToggleEntity
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
)
from .models import WizData from .models import WizData
class WizEntity(CoordinatorEntity, Entity): class WizEntity(CoordinatorEntity[DataUpdateCoordinator[Optional[float]]], Entity):
"""Representation of WiZ entity.""" """Representation of WiZ entity."""
def __init__(self, wiz_data: WizData, name: str) -> None: def __init__(self, wiz_data: WizData, name: str) -> None:

View file

@ -13,7 +13,7 @@
"dependencies": ["network"], "dependencies": ["network"],
"quality_scale": "platinum", "quality_scale": "platinum",
"documentation": "https://www.home-assistant.io/integrations/wiz", "documentation": "https://www.home-assistant.io/integrations/wiz",
"requirements": ["pywizlight==0.5.13"], "requirements": ["pywizlight==0.5.14"],
"iot_class": "local_push", "iot_class": "local_push",
"codeowners": ["@sbidy"] "codeowners": ["@sbidy"]
} }

View file

@ -8,7 +8,7 @@ from homeassistant.components.sensor import (
SensorStateClass, SensorStateClass,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import SIGNAL_STRENGTH_DECIBELS_MILLIWATT from homeassistant.const import POWER_WATT, SIGNAL_STRENGTH_DECIBELS_MILLIWATT
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -30,6 +30,17 @@ SENSORS: tuple[SensorEntityDescription, ...] = (
) )
POWER_SENSORS: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
key="power",
name="Current Power",
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.POWER,
native_unit_of_measurement=POWER_WATT,
),
)
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
entry: ConfigEntry, entry: ConfigEntry,
@ -37,9 +48,17 @@ async def async_setup_entry(
) -> None: ) -> None:
"""Set up the wiz sensor.""" """Set up the wiz sensor."""
wiz_data: WizData = hass.data[DOMAIN][entry.entry_id] wiz_data: WizData = hass.data[DOMAIN][entry.entry_id]
async_add_entities( entities = [
WizSensor(wiz_data, entry.title, description) for description in SENSORS WizSensor(wiz_data, entry.title, description) for description in SENSORS
) ]
if wiz_data.coordinator.data is not None:
entities.extend(
[
WizPowerSensor(wiz_data, entry.title, description)
for description in POWER_SENSORS
]
)
async_add_entities(entities)
class WizSensor(WizEntity, SensorEntity): class WizSensor(WizEntity, SensorEntity):
@ -63,3 +82,16 @@ class WizSensor(WizEntity, SensorEntity):
self._attr_native_value = self._device.state.pilotResult.get( self._attr_native_value = self._device.state.pilotResult.get(
self.entity_description.key self.entity_description.key
) )
class WizPowerSensor(WizSensor):
"""Defines a WiZ power sensor."""
@callback
def _async_update_attrs(self) -> None:
"""Handle updating _attr values."""
# Newer firmwares will have the power in their state
watts_push = self._device.state.get_power()
# Older firmwares will be polled and in the coordinator data
watts_poll = self.coordinator.data
self._attr_native_value = watts_poll if watts_push is None else watts_push

View file

@ -2029,7 +2029,7 @@ pywemo==0.9.1
pywilight==0.0.70 pywilight==0.0.70
# homeassistant.components.wiz # homeassistant.components.wiz
pywizlight==0.5.13 pywizlight==0.5.14
# homeassistant.components.ws66i # homeassistant.components.ws66i
pyws66i==1.1 pyws66i==1.1

View file

@ -1349,7 +1349,7 @@ pywemo==0.9.1
pywilight==0.0.70 pywilight==0.0.70
# homeassistant.components.wiz # homeassistant.components.wiz
pywizlight==0.5.13 pywizlight==0.5.14
# homeassistant.components.ws66i # homeassistant.components.ws66i
pyws66i==1.1 pyws66i==1.1

View file

@ -150,6 +150,17 @@ FAKE_SOCKET = BulbType(
white_channels=2, white_channels=2,
white_to_color_ratio=80, white_to_color_ratio=80,
) )
FAKE_SOCKET_WITH_POWER_MONITORING = BulbType(
bulb_type=BulbClass.SOCKET,
name="ESP25_SOCKET_01",
features=Features(
color=False, color_tmp=False, effect=False, brightness=False, dual_head=False
),
kelvin_range=KelvinRange(2700, 6500),
fw_version="1.26.2",
white_channels=2,
white_to_color_ratio=80,
)
FAKE_OLD_FIRMWARE_DIMMABLE_BULB = BulbType( FAKE_OLD_FIRMWARE_DIMMABLE_BULB = BulbType(
bulb_type=BulbClass.DW, bulb_type=BulbClass.DW,
name=None, name=None,
@ -197,7 +208,9 @@ def _mocked_wizlight(device, extended_white_range, bulb_type) -> wizlight:
) )
bulb.getMac = AsyncMock(return_value=FAKE_MAC) bulb.getMac = AsyncMock(return_value=FAKE_MAC)
bulb.turn_on = AsyncMock() bulb.turn_on = AsyncMock()
bulb.get_power = AsyncMock(return_value=None)
bulb.turn_off = AsyncMock() bulb.turn_off = AsyncMock()
bulb.power_monitoring = False
bulb.updateState = AsyncMock(return_value=FAKE_STATE) bulb.updateState = AsyncMock(return_value=FAKE_STATE)
bulb.getSupportedScenes = AsyncMock(return_value=list(SCENES.values())) bulb.getSupportedScenes = AsyncMock(return_value=list(SCENES.values()))
bulb.start_push = AsyncMock(side_effect=_save_setup_callback) bulb.start_push = AsyncMock(side_effect=_save_setup_callback)

View file

@ -1,11 +1,15 @@
"""Tests for the sensor platform.""" """Tests for the sensor platform."""
from unittest.mock import AsyncMock
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
from . import ( from . import (
FAKE_DUAL_HEAD_RGBWW_BULB, FAKE_DUAL_HEAD_RGBWW_BULB,
FAKE_MAC, FAKE_MAC,
FAKE_SOCKET_WITH_POWER_MONITORING,
_mocked_wizlight,
_patch_discovery, _patch_discovery,
_patch_wizlight, _patch_wizlight,
async_push_update, async_push_update,
@ -35,3 +39,29 @@ async def test_signal_strength(hass: HomeAssistant) -> None:
await async_push_update(hass, bulb, {"mac": FAKE_MAC, "rssi": -50}) await async_push_update(hass, bulb, {"mac": FAKE_MAC, "rssi": -50})
assert hass.states.get(entity_id).state == "-50" assert hass.states.get(entity_id).state == "-50"
async def test_power_monitoring(hass: HomeAssistant) -> None:
"""Test power monitoring."""
socket = _mocked_wizlight(None, None, FAKE_SOCKET_WITH_POWER_MONITORING)
socket.power_monitoring = None
socket.get_power = AsyncMock(return_value=5.123)
_, entry = await async_setup_integration(
hass, wizlight=socket, bulb_type=FAKE_SOCKET_WITH_POWER_MONITORING
)
entity_id = "sensor.mock_title_current_power"
entity_registry = er.async_get(hass)
reg_entry = entity_registry.async_get(entity_id)
assert reg_entry.unique_id == f"{FAKE_MAC}_power"
updated_entity = entity_registry.async_update_entity(
entity_id=entity_id, disabled_by=None
)
assert not updated_entity.disabled
with _patch_discovery(), _patch_wizlight(device=socket):
await hass.config_entries.async_reload(entry.entry_id)
await hass.async_block_till_done()
assert hass.states.get(entity_id).state == "5.123"
await async_push_update(hass, socket, {"mac": FAKE_MAC, "pc": 800})
assert hass.states.get(entity_id).state == "0.8"