diff --git a/.strict-typing b/.strict-typing index e84e01b1803..f2c0c29ef27 100644 --- a/.strict-typing +++ b/.strict-typing @@ -203,6 +203,7 @@ homeassistant.components.webostv.* homeassistant.components.websocket_api.* homeassistant.components.wemo.* homeassistant.components.whois.* +homeassistant.components.wiz.* homeassistant.components.zodiac.* homeassistant.components.zeroconf.* homeassistant.components.zone.* diff --git a/homeassistant/components/wiz/__init__.py b/homeassistant/components/wiz/__init__.py index ec0979877cc..6e7f841ebb2 100644 --- a/homeassistant/components/wiz/__init__.py +++ b/homeassistant/components/wiz/__init__.py @@ -1,38 +1,66 @@ """WiZ Platform integration.""" -from dataclasses import dataclass +from datetime import timedelta import logging from pywizlight import wizlight -from pywizlight.exceptions import WizLightConnectionError, WizLightTimeOutError from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST +from homeassistant.const import CONF_HOST, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.debounce import Debouncer +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import DOMAIN +from .const import DOMAIN, WIZ_EXCEPTIONS +from .models import WizData _LOGGER = logging.getLogger(__name__) -PLATFORMS = ["light"] +PLATFORMS = [Platform.LIGHT] + +REQUEST_REFRESH_DELAY = 0.35 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up the wiz integration from a config entry.""" - ip_address = entry.data.get(CONF_HOST) + ip_address = entry.data[CONF_HOST] _LOGGER.debug("Get bulb with IP: %s", ip_address) + bulb = wizlight(ip_address) try: - bulb = wizlight(ip_address) - scenes = await bulb.getSupportedScenes() await bulb.getMac() - except ( - WizLightTimeOutError, - WizLightConnectionError, - ConnectionRefusedError, - ) as err: + scenes = await bulb.getSupportedScenes() + # ValueError gets thrown if the bulb type + # cannot be determined on the first try. + # This is likely because way the library + # processes responses and can be cleaned up + # in the future. + except (ValueError, *WIZ_EXCEPTIONS) as err: raise ConfigEntryNotReady from err - hass.data.setdefault(DOMAIN, {})[entry.entry_id] = WizData(bulb=bulb, scenes=scenes) + async def _async_update() -> None: + """Update the WiZ device.""" + try: + await bulb.updateState() + except WIZ_EXCEPTIONS as ex: + raise UpdateFailed(f"Failed to update device at {ip_address}: {ex}") from ex + + coordinator = DataUpdateCoordinator( + hass=hass, + logger=_LOGGER, + name=entry.title, + update_interval=timedelta(seconds=15), + update_method=_async_update, + # We don't want an immediate refresh since the device + # takes a moment to reflect the state change + request_refresh_debouncer=Debouncer( + hass, _LOGGER, cooldown=REQUEST_REFRESH_DELAY, immediate=False + ), + ) + await coordinator.async_config_entry_first_refresh() + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = WizData( + coordinator=coordinator, bulb=bulb, scenes=scenes + ) hass.config_entries.async_setup_platforms(entry, PLATFORMS) return True @@ -42,11 +70,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): hass.data[DOMAIN].pop(entry.entry_id) return unload_ok - - -@dataclass -class WizData: - """Data for the wiz integration.""" - - bulb: wizlight - scenes: list diff --git a/homeassistant/components/wiz/config_flow.py b/homeassistant/components/wiz/config_flow.py index dbe17adac00..efa9cc1443e 100644 --- a/homeassistant/components/wiz/config_flow.py +++ b/homeassistant/components/wiz/config_flow.py @@ -1,37 +1,38 @@ """Config flow for WiZ Platform.""" +from __future__ import annotations + import logging +from typing import Any from pywizlight import wizlight from pywizlight.exceptions import WizLightConnectionError, WizLightTimeOutError import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_HOST, CONF_NAME +from homeassistant.const import CONF_HOST +from homeassistant.data_entry_flow import FlowResult from .const import DEFAULT_NAME, DOMAIN +from .utils import _short_mac _LOGGER = logging.getLogger(__name__) -STEP_USER_DATA_SCHEMA = vol.Schema( - { - vol.Required(CONF_HOST): str, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): str, - } -) - class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for WiZ.""" VERSION = 1 - async def async_step_user(self, user_input=None): + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle a flow initialized by the user.""" errors = {} if user_input is not None: bulb = wizlight(user_input[CONF_HOST]) try: mac = await bulb.getMac() + bulbtype = await bulb.get_bulbtype() except WizLightTimeOutError: errors["base"] = "bulb_time_out" except ConnectionRefusedError: @@ -43,10 +44,18 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors["base"] = "unknown" else: await self.async_set_unique_id(mac) - self._abort_if_unique_id_configured() - return self.async_create_entry( - title=user_input[CONF_NAME], data=user_input + self._abort_if_unique_id_configured( + updates={CONF_HOST: user_input[CONF_HOST]} ) + bulb_type = bulbtype.bulb_type.value if bulbtype else "Unknown" + name = f"{DEFAULT_NAME} {bulb_type} {_short_mac(mac)}" + return self.async_create_entry( + title=name, + data=user_input, + ) + return self.async_show_form( - step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors + step_id="user", + data_schema=vol.Schema({vol.Required(CONF_HOST): str}), + errors=errors, ) diff --git a/homeassistant/components/wiz/const.py b/homeassistant/components/wiz/const.py index 30b3efb11d4..96a96e662f1 100644 --- a/homeassistant/components/wiz/const.py +++ b/homeassistant/components/wiz/const.py @@ -1,4 +1,13 @@ """Constants for the WiZ Platform integration.""" +from pywizlight.exceptions import WizLightConnectionError, WizLightTimeOutError DOMAIN = "wiz" DEFAULT_NAME = "WiZ" + +WIZ_EXCEPTIONS = ( + OSError, + WizLightTimeOutError, + TimeoutError, + WizLightConnectionError, + ConnectionRefusedError, +) diff --git a/homeassistant/components/wiz/light.py b/homeassistant/components/wiz/light.py index 6efacfb8b95..a8fcf1e1dae 100644 --- a/homeassistant/components/wiz/light.py +++ b/homeassistant/components/wiz/light.py @@ -1,12 +1,13 @@ """WiZ integration.""" from __future__ import annotations -from datetime import timedelta +import contextlib import logging +from typing import Any -from pywizlight import PilotBuilder, wizlight +from pywizlight import PilotBuilder from pywizlight.bulblibrary import BulbClass, BulbType -from pywizlight.exceptions import WizLightNotKnownBulb, WizLightTimeOutError +from pywizlight.exceptions import WizLightNotKnownBulb from pywizlight.rgbcw import convertHSfromRGBCW from pywizlight.scenes import get_id_from_scene_name @@ -16,83 +17,177 @@ from homeassistant.components.light import ( ATTR_EFFECT, ATTR_HS_COLOR, ATTR_RGB_COLOR, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, - SUPPORT_COLOR_TEMP, + COLOR_MODE_BRIGHTNESS, + COLOR_MODE_COLOR_TEMP, + COLOR_MODE_HS, SUPPORT_EFFECT, LightEntity, ) -from homeassistant.const import CONF_NAME +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.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import CoordinatorEntity import homeassistant.util.color as color_utils from .const import DOMAIN +from .models import WizData _LOGGER = logging.getLogger(__name__) -SUPPORT_FEATURES_RGB = ( - SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_COLOR_TEMP | SUPPORT_EFFECT -) +DEFAULT_COLOR_MODES = {COLOR_MODE_HS, COLOR_MODE_COLOR_TEMP} +DEFAULT_MIN_MIREDS = 153 +DEFAULT_MAX_MIREDS = 454 -# set poll interval to 15 sec because of changes from external to the bulb -SCAN_INTERVAL = timedelta(seconds=15) +def get_supported_color_modes(bulb_type: BulbType) -> set[str]: + """Flag supported features.""" + if not bulb_type: + # fallback + return DEFAULT_COLOR_MODES + color_modes = set() + try: + features = bulb_type.features + if features.color: + color_modes.add(COLOR_MODE_HS) + if features.color_tmp: + color_modes.add(COLOR_MODE_COLOR_TEMP) + if not color_modes and features.brightness: + color_modes.add(COLOR_MODE_BRIGHTNESS) + return color_modes + except WizLightNotKnownBulb: + _LOGGER.warning("Bulb is not present in the library. Fallback to full feature") + return DEFAULT_COLOR_MODES -async def async_setup_entry(hass, entry, async_add_entities): +def supports_effects(bulb_type: BulbType) -> bool: + """Check if a bulb supports effects.""" + with contextlib.suppress(WizLightNotKnownBulb): + return bool(bulb_type.features.effect) + return True # default is true + + +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 + try: + return color_utils.color_temperature_kelvin_to_mired( + bulb_type.kelvin_range.max + ), color_utils.color_temperature_kelvin_to_mired(bulb_type.kelvin_range.min) + except WizLightNotKnownBulb: + _LOGGER.debug("Kelvin is not present in the library. Fallback to 6500") + return DEFAULT_MIN_MIREDS, DEFAULT_MAX_MIREDS + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: """Set up the WiZ Platform from config_flow.""" - # Assign configuration variables. - wiz_data = hass.data[DOMAIN][entry.entry_id] - wizbulb = WizBulbEntity(wiz_data.bulb, entry.data.get(CONF_NAME), wiz_data.scenes) - # Add devices with defined name - async_add_entities([wizbulb], update_before_add=True) - return True + wiz_data: WizData = hass.data[DOMAIN][entry.entry_id] + async_add_entities([WizBulbEntity(wiz_data, entry.title)]) -class WizBulbEntity(LightEntity): +class WizBulbEntity(CoordinatorEntity, LightEntity): """Representation of WiZ Light bulb.""" - def __init__(self, light: wizlight, name, scenes): + def __init__(self, wiz_data: WizData, name: str) -> None: """Initialize an WiZLight.""" - self._light = light - self._state = None - self._brightness = None + 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 - self._rgb_color = None - self._temperature = None - self._hscolor = None - self._available = None - self._effect = None - self._scenes: list[str] = scenes - self._bulbtype: BulbType = light.bulbtype - self._mac = light.mac - self._attr_unique_id = light.mac - # new init states - self._attr_min_mireds = self.get_min_mireds() - self._attr_max_mireds = self.get_max_mireds() - self._attr_supported_features = self.get_supported_features() + 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): + 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 brightness(self): - """Return the brightness of the light.""" - return self._brightness - - @property - def rgb_color(self): - """Return the color property.""" - return self._rgb_color - - @property - def hs_color(self): - """Return the hs color value.""" - return self._hscolor - - @property - def is_on(self): + def is_on(self) -> bool | None: """Return true if light is on.""" - return self._state + is_on: bool | None = self._light.status + return is_on - async def async_turn_on(self, **kwargs): + @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.""" + 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 ( + COLOR_MODE_HS in color_modes + and (rgb := self._light.state.get_rgb()) is not None + and rgb[0] is not None + ): + return COLOR_MODE_HS + return COLOR_MODE_BRIGHTNESS + + @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 @@ -150,199 +245,9 @@ class WizBulbEntity(LightEntity): ) await self._light.turn_on(pilot) + await self.coordinator.async_request_refresh() - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Instruct the light to turn off.""" await self._light.turn_off() - - @property - def color_temp(self): - """Return the CT color value in mireds.""" - return self._temperature - - def get_min_mireds(self) -> int: - """Return the coldest color_temp that this light supports.""" - if self._bulbtype is None: - return color_utils.color_temperature_kelvin_to_mired(6500) - # DW bulbs have no kelvin - if self._bulbtype.bulb_type == BulbClass.DW: - return 0 - # If bulbtype is TW or RGB then return the kelvin value - try: - return color_utils.color_temperature_kelvin_to_mired( - self._bulbtype.kelvin_range.max - ) - except WizLightNotKnownBulb: - _LOGGER.debug("Kelvin is not present in the library. Fallback to 6500") - return color_utils.color_temperature_kelvin_to_mired(6500) - - def get_max_mireds(self) -> int: - """Return the warmest color_temp that this light supports.""" - if self._bulbtype is None: - return color_utils.color_temperature_kelvin_to_mired(2200) - # DW bulbs have no kelvin - if self._bulbtype.bulb_type == BulbClass.DW: - return 0 - # If bulbtype is TW or RGB then return the kelvin value - try: - return color_utils.color_temperature_kelvin_to_mired( - self._bulbtype.kelvin_range.min - ) - except WizLightNotKnownBulb: - _LOGGER.debug("Kelvin is not present in the library. Fallback to 2200") - return color_utils.color_temperature_kelvin_to_mired(2200) - - def get_supported_features(self) -> int: - """Flag supported features.""" - if not self._bulbtype: - # fallback - return SUPPORT_FEATURES_RGB - features = 0 - try: - # Map features for better reading - if self._bulbtype.features.brightness: - features = features | SUPPORT_BRIGHTNESS - if self._bulbtype.features.color: - features = features | SUPPORT_COLOR - if self._bulbtype.features.effect: - features = features | SUPPORT_EFFECT - if self._bulbtype.features.color_tmp: - features = features | SUPPORT_COLOR_TEMP - return features - except WizLightNotKnownBulb: - _LOGGER.warning( - "Bulb is not present in the library. Fallback to full feature" - ) - return SUPPORT_FEATURES_RGB - - @property - def effect(self): - """Return the current effect.""" - return self._effect - - @property - def effect_list(self): - """Return the list of supported effects. - - URL: https://docs.pro.wizconnected.com/#light-modes - """ - return self._scenes - - @property - def available(self): - """Return if light is available.""" - return self._available - - async def async_update(self): - """Fetch new state data for this light.""" - await self.update_state() - - if self._state is not None and self._state is not False: - self.update_brightness() - self.update_temperature() - self.update_color() - self.update_effect() - - @property - def device_info(self): - """Get device specific attributes.""" - return { - "connections": {(CONNECTION_NETWORK_MAC, self._mac)}, - "name": self._attr_name, - "manufacturer": "WiZ Light Platform", - "model": self._bulbtype.name, - } - - def update_state_available(self): - """Update the state if bulb is available.""" - self._state = self._light.status - self._available = True - - def update_state_unavailable(self): - """Update the state if bulb is unavailable.""" - self._state = False - self._available = False - - async def update_state(self): - """Update the state.""" - try: - await self._light.updateState() - except (ConnectionRefusedError, TimeoutError, WizLightTimeOutError) as ex: - _LOGGER.debug(ex) - self.update_state_unavailable() - else: - if self._light.state is None: - self.update_state_unavailable() - else: - self.update_state_available() - _LOGGER.debug( - "[wizlight %s] updated state: %s and available: %s", - self._light.ip, - self._state, - self._available, - ) - - def update_brightness(self): - """Update the brightness.""" - if self._light.state.get_brightness() is None: - return - brightness = self._light.state.get_brightness() - if 0 <= int(brightness) <= 255: - self._brightness = int(brightness) - else: - _LOGGER.error( - "Received invalid brightness : %s. Expected: 0-255", brightness - ) - self._brightness = None - - def update_temperature(self): - """Update the temperature.""" - colortemp = self._light.state.get_colortemp() - if colortemp is None or colortemp == 0: - self._temperature = None - return - - _LOGGER.debug( - "[wizlight %s] kelvin from the bulb: %s", self._light.ip, colortemp - ) - temperature = color_utils.color_temperature_kelvin_to_mired(colortemp) - self._temperature = temperature - - def update_color(self): - """Update the hs color.""" - colortemp = self._light.state.get_colortemp() - if colortemp is not None and colortemp != 0: - self._hscolor = None - return - if self._light.state.get_rgb() is None: - return - - rgb = self._light.state.get_rgb() - if rgb[0] is None: - # this is the case if the temperature was changed - # do nothing until the RGB color was changed - return - warmwhite = self._light.state.get_warm_white() - if warmwhite is None: - return - self._hscolor = convertHSfromRGBCW(rgb, warmwhite) - - def update_effect(self): - """Update the bulb scene.""" - self._effect = self._light.state.get_scene() - - async def get_bulb_type(self): - """Get the bulb type.""" - if self._bulbtype is not None: - return self._bulbtype - try: - self._bulbtype = await self._light.get_bulbtype() - _LOGGER.info( - "[wizlight %s] Initiate the WiZ bulb as %s", - self._light.ip, - self._bulbtype.name, - ) - except WizLightTimeOutError: - _LOGGER.debug( - "[wizlight %s] Bulbtype update failed - Timeout", self._light.ip - ) + await self.coordinator.async_request_refresh() diff --git a/homeassistant/components/wiz/manifest.json b/homeassistant/components/wiz/manifest.json index ca50cd8c7e2..8b366e7f506 100644 --- a/homeassistant/components/wiz/manifest.json +++ b/homeassistant/components/wiz/manifest.json @@ -3,11 +3,7 @@ "name": "WiZ", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/wiz", - "requirements": [ - "pywizlight==0.4.15" - ], + "requirements": ["pywizlight==0.4.16"], "iot_class": "local_polling", - "codeowners": [ - "@sbidy" - ] + "codeowners": ["@sbidy"] } \ No newline at end of file diff --git a/homeassistant/components/wiz/models.py b/homeassistant/components/wiz/models.py new file mode 100644 index 00000000000..efbb2a664b1 --- /dev/null +++ b/homeassistant/components/wiz/models.py @@ -0,0 +1,15 @@ +"""WiZ integration models.""" +from dataclasses import dataclass + +from pywizlight import wizlight + +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator + + +@dataclass +class WizData: + """Data for the wiz integration.""" + + coordinator: DataUpdateCoordinator + bulb: wizlight + scenes: list diff --git a/homeassistant/components/wiz/strings.json b/homeassistant/components/wiz/strings.json index 59e6d18c179..3088a7f098d 100644 --- a/homeassistant/components/wiz/strings.json +++ b/homeassistant/components/wiz/strings.json @@ -3,13 +3,9 @@ "step": { "user": { "data": { - "host": "[%key:common::config_flow::data::host%]", - "name": "[%key:common::config_flow::data::name%]" + "host": "[%key:common::config_flow::data::host%]" }, - "description": "Please enter a hostname or IP address and name to add a new bulb:" - }, - "confirm": { - "description": "[%key:common::config_flow::description::confirm_setup%]" + "description": "Enter the IP address of the device." } }, "error": { diff --git a/homeassistant/components/wiz/translations/en.json b/homeassistant/components/wiz/translations/en.json index 7d95281e14a..24f34bd0f5b 100644 --- a/homeassistant/components/wiz/translations/en.json +++ b/homeassistant/components/wiz/translations/en.json @@ -11,15 +11,11 @@ "unknown": "Unexpected error" }, "step": { - "confirm": { - "description": "Do you want to add a new Bulb?" - }, "user": { "data": { - "host": "Hostname or IP", - "name": "Name" + "host": "Host" }, - "description": "Please enter a hostname or IP address and name to add a new bulb:" + "description": "Enter the IP address of the device." } } } diff --git a/homeassistant/components/wiz/utils.py b/homeassistant/components/wiz/utils.py new file mode 100644 index 00000000000..edce7d47fea --- /dev/null +++ b/homeassistant/components/wiz/utils.py @@ -0,0 +1,7 @@ +"""WiZ utils.""" +from __future__ import annotations + + +def _short_mac(mac: str) -> str: + """Get the short mac address from the full mac.""" + return mac.replace(":", "").upper()[-6:] diff --git a/mypy.ini b/mypy.ini index 32ee9352326..524ef2c5420 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2050,6 +2050,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.wiz.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.zodiac.*] check_untyped_defs = true disallow_incomplete_defs = true diff --git a/requirements_all.txt b/requirements_all.txt index a886a0f98b3..513a69e59b4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2051,7 +2051,7 @@ pywemo==0.7.0 pywilight==0.0.70 # homeassistant.components.wiz -pywizlight==0.4.15 +pywizlight==0.4.16 # homeassistant.components.xeoma pyxeoma==1.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 74b3e53e8c8..f098beb6261 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1276,7 +1276,7 @@ pywemo==0.7.0 pywilight==0.0.70 # homeassistant.components.wiz -pywizlight==0.4.15 +pywizlight==0.4.16 # homeassistant.components.zerproc pyzerproc==0.4.8 diff --git a/tests/components/wiz/test_config_flow.py b/tests/components/wiz/test_config_flow.py index e08ca87a389..20afd994046 100644 --- a/tests/components/wiz/test_config_flow.py +++ b/tests/components/wiz/test_config_flow.py @@ -1,4 +1,5 @@ """Test the WiZ Platform config flow.""" +from contextlib import contextmanager from unittest.mock import patch import pytest @@ -9,27 +10,49 @@ from homeassistant.components.wiz.config_flow import ( WizLightTimeOutError, ) from homeassistant.components.wiz.const import DOMAIN -from homeassistant.const import CONF_HOST, CONF_NAME +from homeassistant.const import CONF_HOST from tests.common import MockConfigEntry -FAKE_BULB_CONFIG = '{"method":"getSystemConfig","env":"pro","result":\ - {"mac":"ABCABCABCABC",\ - "homeId":653906,\ - "roomId":989983,\ - "moduleName":"ESP_0711_STR",\ - "fwVersion":"1.21.0",\ - "groupId":0,"drvConf":[20,2],\ - "ewf":[255,0,255,255,0,0,0],\ - "ewfHex":"ff00ffff000000",\ - "ping":0}}' - -TEST_SYSTEM_INFO = {"id": "ABCABCABCABC", "name": "Test Bulb"} +FAKE_MAC = "ABCABCABCABC" +FAKE_BULB_CONFIG = { + "method": "getSystemConfig", + "env": "pro", + "result": { + "mac": FAKE_MAC, + "homeId": 653906, + "roomId": 989983, + "moduleName": "ESP_0711_STR", + "fwVersion": "1.21.0", + "groupId": 0, + "drvConf": [20, 2], + "ewf": [255, 0, 255, 255, 0, 0, 0], + "ewfHex": "ff00ffff000000", + "ping": 0, + }, +} +FAKE_EXTENDED_WHITE_RANGE = [2200, 2700, 6500, 6500] +TEST_SYSTEM_INFO = {"id": FAKE_MAC, "name": "Test Bulb"} +TEST_CONNECTION = {CONF_HOST: "1.1.1.1"} +TEST_NO_IP = {CONF_HOST: "this is no IP input"} -TEST_CONNECTION = {CONF_HOST: "1.1.1.1", CONF_NAME: "Test Bulb"} +def _patch_wizlight(): + @contextmanager + def _patcher(): + with patch( + "homeassistant.components.wiz.wizlight.getBulbConfig", + return_value=FAKE_BULB_CONFIG, + ), patch( + "homeassistant.components.wiz.wizlight.getExtendedWhiteRange", + return_value=FAKE_EXTENDED_WHITE_RANGE, + ), patch( + "homeassistant.components.wiz.wizlight.getMac", + return_value=FAKE_MAC, + ): + yield -TEST_NO_IP = {CONF_HOST: "this is no IP input", CONF_NAME: "Test Bulb"} + return _patcher() async def test_form(hass): @@ -40,13 +63,7 @@ async def test_form(hass): assert result["type"] == "form" assert result["errors"] == {} # Patch functions - with patch( - "homeassistant.components.wiz.wizlight.getBulbConfig", - return_value=FAKE_BULB_CONFIG, - ), patch( - "homeassistant.components.wiz.wizlight.getMac", - return_value="ABCABCABCABC", - ) as mock_setup, patch( + with _patch_wizlight(), patch( "homeassistant.components.wiz.async_setup_entry", return_value=True, ) as mock_setup_entry: @@ -57,9 +74,10 @@ async def test_form(hass): await hass.async_block_till_done() assert result2["type"] == "create_entry" - assert result2["title"] == "Test Bulb" - assert result2["data"] == TEST_CONNECTION - assert len(mock_setup.mock_calls) == 1 + assert result2["title"] == "WiZ Dimmable White ABCABC" + assert result2["data"] == { + CONF_HOST: "1.1.1.1", + } assert len(mock_setup_entry.mock_calls) == 1 @@ -98,8 +116,6 @@ async def test_form_updates_unique_id(hass): unique_id=TEST_SYSTEM_INFO["id"], data={ CONF_HOST: "dummy", - CONF_NAME: TEST_SYSTEM_INFO["name"], - "id": TEST_SYSTEM_INFO["id"], }, ) @@ -108,13 +124,7 @@ async def test_form_updates_unique_id(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "homeassistant.components.wiz.wizlight.getBulbConfig", - return_value=FAKE_BULB_CONFIG, - ), patch( - "homeassistant.components.wiz.wizlight.getMac", - return_value="ABCABCABCABC", - ), patch( + with _patch_wizlight(), patch( "homeassistant.components.wiz.async_setup_entry", return_value=True, ):