From 5621e209632577f29c91579e460511840d742b63 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 5 Feb 2022 11:56:17 -0600 Subject: [PATCH] WiZ Cleanups part 3 (#65819) * WiZ Cleanups part 3 - Sockets are now using the switch platform * tweaks * remove rgb colorcheck * tweaks * tweaks * cover * cover --- .coveragerc | 2 + homeassistant/components/wiz/__init__.py | 2 +- homeassistant/components/wiz/entity.py | 42 ++++ homeassistant/components/wiz/light.py | 208 +++++------------- homeassistant/components/wiz/strings.json | 1 - homeassistant/components/wiz/switch.py | 35 +++ .../components/wiz/translations/en.json | 3 +- tests/components/wiz/test_config_flow.py | 1 + 8 files changed, 141 insertions(+), 153 deletions(-) create mode 100644 homeassistant/components/wiz/entity.py create mode 100644 homeassistant/components/wiz/switch.py diff --git a/.coveragerc b/.coveragerc index 6b0de56b35d..8babaab4e25 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1317,7 +1317,9 @@ omit = homeassistant/components/wiz/__init__.py homeassistant/components/wiz/const.py homeassistant/components/wiz/discovery.py + homeassistant/components/wiz/entity.py homeassistant/components/wiz/light.py + homeassistant/components/wiz/switch.py homeassistant/components/wolflink/__init__.py homeassistant/components/wolflink/sensor.py homeassistant/components/wolflink/const.py diff --git a/homeassistant/components/wiz/__init__.py b/homeassistant/components/wiz/__init__.py index c45925adec7..ddf324278cf 100644 --- a/homeassistant/components/wiz/__init__.py +++ b/homeassistant/components/wiz/__init__.py @@ -22,7 +22,7 @@ from .models import WizData _LOGGER = logging.getLogger(__name__) -PLATFORMS = [Platform.LIGHT] +PLATFORMS = [Platform.LIGHT, Platform.SWITCH] REQUEST_REFRESH_DELAY = 0.35 diff --git a/homeassistant/components/wiz/entity.py b/homeassistant/components/wiz/entity.py new file mode 100644 index 00000000000..0c4829383d3 --- /dev/null +++ b/homeassistant/components/wiz/entity.py @@ -0,0 +1,42 @@ +"""WiZ integration entities.""" +from __future__ import annotations + +from typing import Any + +from pywizlight.bulblibrary import BulbType + +from homeassistant.core import callback +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from homeassistant.helpers.entity import DeviceInfo, ToggleEntity +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .models import WizData + + +class WizToggleEntity(CoordinatorEntity, ToggleEntity): + """Representation of WiZ toggle entity.""" + + def __init__(self, wiz_data: WizData, name: str) -> None: + """Initialize an WiZ device.""" + super().__init__(wiz_data.coordinator) + self._device = wiz_data.bulb + bulb_type: BulbType = self._device.bulbtype + self._attr_unique_id = self._device.mac + self._attr_name = name + self._attr_device_info = DeviceInfo( + connections={(CONNECTION_NETWORK_MAC, self._device.mac)}, + name=name, + manufacturer="WiZ", + model=bulb_type.name, + ) + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + self._attr_is_on = self._device.status + super()._handle_coordinator_update() + + async def async_turn_off(self, **kwargs: Any) -> None: + """Instruct the device to turn off.""" + await self._device.turn_off() + await self.coordinator.async_request_refresh() diff --git a/homeassistant/components/wiz/light.py b/homeassistant/components/wiz/light.py index 09e17976eb7..ee586c8939d 100644 --- a/homeassistant/components/wiz/light.py +++ b/homeassistant/components/wiz/light.py @@ -1,4 +1,4 @@ -"""WiZ integration.""" +"""WiZ integration light platform.""" from __future__ import annotations import logging @@ -14,7 +14,6 @@ from homeassistant.components.light import ( ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_HS_COLOR, - ATTR_RGB_COLOR, COLOR_MODE_BRIGHTNESS, COLOR_MODE_COLOR_TEMP, COLOR_MODE_HS, @@ -22,14 +21,15 @@ from homeassistant.components.light import ( LightEntity, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant -from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC -from homeassistant.helpers.entity import DeviceInfo +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import CoordinatorEntity -import homeassistant.util.color as color_utils +from homeassistant.util.color import ( + color_temperature_kelvin_to_mired, + color_temperature_mired_to_kelvin, +) -from .const import DOMAIN +from .const import DOMAIN, SOCKET_DEVICE_STR +from .entity import WizToggleEntity from .models import WizData _LOGGER = logging.getLogger(__name__) @@ -52,22 +52,15 @@ def get_supported_color_modes(bulb_type: BulbType) -> set[str]: return color_modes -def supports_effects(bulb_type: BulbType) -> bool: - """Check if a bulb supports effects.""" - return bool(bulb_type.features.effect) - - def get_min_max_mireds(bulb_type: BulbType) -> tuple[int, int]: """Return the coldest and warmest color_temp that this light supports.""" - if bulb_type is None: - return DEFAULT_MIN_MIREDS, DEFAULT_MAX_MIREDS # DW bulbs have no kelvin if bulb_type.bulb_type == BulbClass.DW: return 0, 0 # If bulbtype is TW or RGB then return the kelvin value - return color_utils.color_temperature_kelvin_to_mired( + return color_temperature_kelvin_to_mired( bulb_type.kelvin_range.max - ), color_utils.color_temperature_kelvin_to_mired(bulb_type.kelvin_range.min) + ), color_temperature_kelvin_to_mired(bulb_type.kelvin_range.min) async def async_setup_entry( @@ -77,162 +70,79 @@ async def async_setup_entry( ) -> None: """Set up the WiZ Platform from config_flow.""" wiz_data: WizData = hass.data[DOMAIN][entry.entry_id] - async_add_entities([WizBulbEntity(wiz_data, entry.title)]) + if SOCKET_DEVICE_STR not in wiz_data.bulb.bulbtype.name: + async_add_entities([WizBulbEntity(wiz_data, entry.title)]) -class WizBulbEntity(CoordinatorEntity, LightEntity): +class WizBulbEntity(WizToggleEntity, LightEntity): """Representation of WiZ Light bulb.""" def __init__(self, wiz_data: WizData, name: str) -> None: """Initialize an WiZLight.""" - super().__init__(wiz_data.coordinator) - self._light = wiz_data.bulb - bulb_type: BulbType = self._light.bulbtype - self._attr_unique_id = self._light.mac - self._attr_name = name + super().__init__(wiz_data, name) + bulb_type: BulbType = self._device.bulbtype self._attr_effect_list = wiz_data.scenes self._attr_min_mireds, self._attr_max_mireds = get_min_max_mireds(bulb_type) self._attr_supported_color_modes = get_supported_color_modes(bulb_type) - if supports_effects(bulb_type): + if bulb_type.features.effect: self._attr_supported_features = SUPPORT_EFFECT - self._attr_device_info = DeviceInfo( - connections={(CONNECTION_NETWORK_MAC, self._light.mac)}, - name=name, - manufacturer="WiZ", - model=bulb_type.name, - ) - @property - def is_on(self) -> bool | None: - """Return true if light is on.""" - is_on: bool | None = self._light.status - return is_on - - @property - def brightness(self) -> int | None: - """Return the brightness of the light.""" - if (brightness := self._light.state.get_brightness()) is None: - return None - if 0 <= int(brightness) <= 255: - return int(brightness) - _LOGGER.error("Received invalid brightness : %s. Expected: 0-255", brightness) - return None - - @property - def color_mode(self) -> str: - """Return the current color mode.""" + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + state = self._device.state + if (brightness := state.get_brightness()) is not None: + self._attr_brightness = max(0, min(255, brightness)) color_modes = self.supported_color_modes assert color_modes is not None - if ( - COLOR_MODE_COLOR_TEMP in color_modes - and self._light.state.get_colortemp() is not None - ): - return COLOR_MODE_COLOR_TEMP - if ( + if COLOR_MODE_COLOR_TEMP in color_modes and state.get_colortemp() is not None: + self._attr_color_mode = COLOR_MODE_COLOR_TEMP + if color_temp := state.get_colortemp(): + self._attr_color_temp = color_temperature_kelvin_to_mired(color_temp) + elif ( COLOR_MODE_HS in color_modes - and (rgb := self._light.state.get_rgb()) is not None + and (rgb := state.get_rgb()) is not None and rgb[0] is not None ): - return COLOR_MODE_HS - return COLOR_MODE_BRIGHTNESS + if (warm_white := state.get_warm_white()) is not None: + self._attr_hs_color = convertHSfromRGBCW(rgb, warm_white) + self._attr_color_mode = COLOR_MODE_HS + else: + self._attr_color_mode = COLOR_MODE_BRIGHTNESS + self._attr_effect = state.get_scene() + super()._handle_coordinator_update() - @property - def hs_color(self) -> tuple[float, float] | None: - """Return the hs color value.""" - colortemp = self._light.state.get_colortemp() - if colortemp is not None and colortemp != 0: - return None - if (rgb := self._light.state.get_rgb()) is None: - return None - if rgb[0] is None: - # this is the case if the temperature was changed - # do nothing until the RGB color was changed - return None - if (warmwhite := self._light.state.get_warm_white()) is None: - return None - hue_sat = convertHSfromRGBCW(rgb, warmwhite) - hue: float = hue_sat[0] - sat: float = hue_sat[1] - return hue, sat - - @property - def color_temp(self) -> int | None: - """Return the CT color value in mireds.""" - colortemp = self._light.state.get_colortemp() - if colortemp is None or colortemp == 0: - return None - _LOGGER.debug( - "[wizlight %s] kelvin from the bulb: %s", self._light.ip, colortemp - ) - return color_utils.color_temperature_kelvin_to_mired(colortemp) - - @property - def effect(self) -> str | None: - """Return the current effect.""" - effect: str | None = self._light.state.get_scene() - return effect - - async def async_turn_on(self, **kwargs: Any) -> None: - """Instruct the light to turn on.""" - brightness = None - - if ATTR_BRIGHTNESS in kwargs: - brightness = kwargs.get(ATTR_BRIGHTNESS) - - if ATTR_RGB_COLOR in kwargs: - pilot = PilotBuilder(rgb=kwargs.get(ATTR_RGB_COLOR), brightness=brightness) + @callback + def _async_pilot_builder(self, **kwargs: Any) -> PilotBuilder: + """Create the PilotBuilder for turn on.""" + brightness = kwargs.get(ATTR_BRIGHTNESS) if ATTR_HS_COLOR in kwargs: - pilot = PilotBuilder( + return PilotBuilder( hucolor=(kwargs[ATTR_HS_COLOR][0], kwargs[ATTR_HS_COLOR][1]), brightness=brightness, ) - else: - colortemp = None - if ATTR_COLOR_TEMP in kwargs: - kelvin = color_utils.color_temperature_mired_to_kelvin( - kwargs[ATTR_COLOR_TEMP] - ) - colortemp = kelvin - _LOGGER.debug( - "[wizlight %s] kelvin changed and send to bulb: %s", - self._light.ip, - colortemp, - ) - sceneid = None - if ATTR_EFFECT in kwargs: - sceneid = get_id_from_scene_name(kwargs[ATTR_EFFECT]) + color_temp = None + if ATTR_COLOR_TEMP in kwargs: + color_temp = color_temperature_mired_to_kelvin(kwargs[ATTR_COLOR_TEMP]) - if sceneid == 1000: # rhythm - pilot = PilotBuilder() - else: - pilot = PilotBuilder( - brightness=brightness, colortemp=colortemp, scene=sceneid - ) - _LOGGER.debug( - "[wizlight %s] Pilot will be send with brightness=%s, colortemp=%s, scene=%s", - self._light.ip, - brightness, - colortemp, - sceneid, - ) + scene_id = None + if ATTR_EFFECT in kwargs: + scene_id = get_id_from_scene_name(kwargs[ATTR_EFFECT]) + if scene_id == 1000: # rhythm + return PilotBuilder() - sceneid = None - if ATTR_EFFECT in kwargs: - sceneid = get_id_from_scene_name(kwargs[ATTR_EFFECT]) + _LOGGER.debug( + "[wizlight %s] Pilot will be sent with brightness=%s, color_temp=%s, scene_id=%s", + self._device.ip, + brightness, + color_temp, + scene_id, + ) + return PilotBuilder(brightness=brightness, colortemp=color_temp, scene=scene_id) - if sceneid == 1000: # rhythm - pilot = PilotBuilder() - else: - pilot = PilotBuilder( - brightness=brightness, colortemp=colortemp, scene=sceneid - ) - - await self._light.turn_on(pilot) - await self.coordinator.async_request_refresh() - - async def async_turn_off(self, **kwargs: Any) -> None: - """Instruct the light to turn off.""" - await self._light.turn_off() + async def async_turn_on(self, **kwargs: Any) -> None: + """Instruct the light to turn on.""" + await self._device.turn_on(self._async_pilot_builder(**kwargs)) await self.coordinator.async_request_refresh() diff --git a/homeassistant/components/wiz/strings.json b/homeassistant/components/wiz/strings.json index 99f491e7360..2195bb09a03 100644 --- a/homeassistant/components/wiz/strings.json +++ b/homeassistant/components/wiz/strings.json @@ -19,7 +19,6 @@ "no_wiz_light": "The bulb can not be connected via WiZ Platform integration." }, "abort": { - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } } diff --git a/homeassistant/components/wiz/switch.py b/homeassistant/components/wiz/switch.py new file mode 100644 index 00000000000..eae7e3d47a8 --- /dev/null +++ b/homeassistant/components/wiz/switch.py @@ -0,0 +1,35 @@ +"""WiZ integration switch platform.""" +from __future__ import annotations + +from typing import Any + +from pywizlight import PilotBuilder + +from homeassistant.components.switch import SwitchEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN, SOCKET_DEVICE_STR +from .entity import WizToggleEntity +from .models import WizData + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the WiZ switch platform.""" + wiz_data: WizData = hass.data[DOMAIN][entry.entry_id] + if SOCKET_DEVICE_STR in wiz_data.bulb.bulbtype.name: + async_add_entities([WizSocketEntity(wiz_data, entry.title)]) + + +class WizSocketEntity(WizToggleEntity, SwitchEntity): + """Representation of a WiZ socket.""" + + async def async_turn_on(self, **kwargs: Any) -> None: + """Instruct the socket to turn on.""" + await self._device.turn_on(PilotBuilder()) + await self.coordinator.async_request_refresh() diff --git a/homeassistant/components/wiz/translations/en.json b/homeassistant/components/wiz/translations/en.json index b0d65e9f957..192d1137c2f 100644 --- a/homeassistant/components/wiz/translations/en.json +++ b/homeassistant/components/wiz/translations/en.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "Device is already configured", - "no_devices_found": "No devices found on the network" + "already_configured": "Device is already configured" }, "error": { "bulb_time_out": "Can not connect to the bulb. Maybe the bulb is offline or a wrong IP/host was entered. Please turn on the light and try again!", diff --git a/tests/components/wiz/test_config_flow.py b/tests/components/wiz/test_config_flow.py index 18c28f50c0e..a043d4a654e 100644 --- a/tests/components/wiz/test_config_flow.py +++ b/tests/components/wiz/test_config_flow.py @@ -152,6 +152,7 @@ async def test_form_updates_unique_id(hass): assert result2["type"] == "abort" assert result2["reason"] == "already_configured" + assert entry.data[CONF_HOST] == FAKE_IP @pytest.mark.parametrize(