From 4653d0b0795d8b271ff1b6ce57df1d384558ffcb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 27 Oct 2021 03:09:45 -0500 Subject: [PATCH] Retry yeelight setup later if first update fails (#58446) --- homeassistant/components/yeelight/__init__.py | 27 ++++++++++++------ homeassistant/components/yeelight/light.py | 2 +- tests/components/yeelight/test_init.py | 28 +++++++++++++++++++ tests/components/yeelight/test_light.py | 2 +- 4 files changed, 49 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/yeelight/__init__.py b/homeassistant/components/yeelight/__init__.py index 04b3bc4d0a8..e57979a7ea2 100644 --- a/homeassistant/components/yeelight/__init__.py +++ b/homeassistant/components/yeelight/__init__.py @@ -80,7 +80,7 @@ SSDP_TARGET = ("239.255.255.250", 1982) SSDP_ST = "wifi_bulb" DISCOVERY_ATTEMPTS = 3 DISCOVERY_SEARCH_INTERVAL = timedelta(seconds=2) -DISCOVERY_TIMEOUT = 2 +DISCOVERY_TIMEOUT = 8 YEELIGHT_RGB_TRANSITION = "RGBTransition" @@ -211,9 +211,6 @@ async def _async_initialize( data={**entry.data, CONF_DETECTED_MODEL: device.capabilities["model"]}, ) - # fetch initial state - await device.async_update() - @callback def _async_normalize_config_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: @@ -500,7 +497,6 @@ class YeelightDevice: self._device_type = None self._available = True self._initialized = False - self._did_first_update = False self._name = None @property @@ -568,7 +564,7 @@ class YeelightDevice: @property def is_color_flow_enabled(self) -> bool: """Return true / false if color flow is currently running.""" - return int(self._color_flow) == ACTIVE_COLOR_FLOWING + return self._color_flow and int(self._color_flow) == ACTIVE_COLOR_FLOWING @property def _active_mode(self): @@ -632,7 +628,6 @@ class YeelightDevice: async def async_update(self, force=False): """Update device properties and send data updated signal.""" - self._did_first_update = True if not force and self._initialized and self._available: # No need to poll unless force, already connected return @@ -649,7 +644,7 @@ class YeelightDevice: was_available = self._available self._available = data.get(KEY_CONNECTED, True) if update_needs_bg_power_workaround(data) or ( - self._did_first_update and not was_available and self._available + not was_available and self._available ): # On reconnect the properties may be out of sync # @@ -724,4 +719,20 @@ async def _async_get_device( ) entry.async_on_unload(_async_stop_listen_on_unload) + # fetch initial state + await device.async_update() + + if ( + # Must have last_properties + not device.bulb.last_properties + # Must have at least a power property + or ( + "main_power" not in device.bulb.last_properties + and "power" not in device.bulb.last_properties + ) + ): + raise ConfigEntryNotReady( + "Could not fetch initial state; try power cycling the device" + ) + return device diff --git a/homeassistant/components/yeelight/light.py b/homeassistant/components/yeelight/light.py index 67f55c7f737..d70845ae86d 100644 --- a/homeassistant/components/yeelight/light.py +++ b/homeassistant/components/yeelight/light.py @@ -488,7 +488,7 @@ class YeelightGenericLight(YeelightEntity, LightEntity): brightness_property = ( "bright" if self._bulb.music_mode else self._brightness_property ) - brightness = self._get_property(brightness_property) + brightness = self._get_property(brightness_property) or 0 return round(255 * (int(brightness) / 100)) @property diff --git a/tests/components/yeelight/test_init.py b/tests/components/yeelight/test_init.py index 16d8d7c8a90..7e9958a09d2 100644 --- a/tests/components/yeelight/test_init.py +++ b/tests/components/yeelight/test_init.py @@ -380,6 +380,34 @@ async def test_async_listen_error_late_discovery(hass, caplog): assert config_entry.data[CONF_DETECTED_MODEL] == MODEL +async def test_fail_to_fetch_initial_state(hass, caplog): + """Test failing to fetch initial state results in a retry.""" + config_entry = MockConfigEntry( + domain=DOMAIN, data={CONF_HOST: IP_ADDRESS, **CONFIG_ENTRY_DATA} + ) + config_entry.add_to_hass(hass) + + mocked_bulb = _mocked_bulb() + del mocked_bulb.last_properties["power"] + del mocked_bulb.last_properties["main_power"] + + with _patch_discovery(), patch(f"{MODULE}.AsyncBulb", return_value=mocked_bulb): + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state is ConfigEntryState.SETUP_RETRY + await hass.async_block_till_done() + assert "Could not fetch initial state; try power cycling the device" in caplog.text + + with _patch_discovery(), patch(f"{MODULE}.AsyncBulb", return_value=_mocked_bulb()): + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5)) + await hass.async_block_till_done() + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=10)) + await hass.async_block_till_done() + + assert config_entry.state is ConfigEntryState.LOADED + + async def test_unload_before_discovery(hass, caplog): """Test unloading before discovery.""" config_entry = MockConfigEntry(domain=DOMAIN, data=CONFIG_ENTRY_DATA) diff --git a/tests/components/yeelight/test_light.py b/tests/components/yeelight/test_light.py index 42ac3675548..a4e9e2d9746 100644 --- a/tests/components/yeelight/test_light.py +++ b/tests/components/yeelight/test_light.py @@ -751,7 +751,7 @@ async def test_device_types(hass: HomeAssistant, caplog): mocked_bulb.last_properties = properties async def _async_setup(config_entry): - with patch(f"{MODULE}.AsyncBulb", return_value=mocked_bulb): + with _patch_discovery(), patch(f"{MODULE}.AsyncBulb", return_value=mocked_bulb): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() # We use asyncio.create_task now to avoid