Retry yeelight setup later if first update fails (#58446)
This commit is contained in:
parent
58b6b5854d
commit
4653d0b079
4 changed files with 49 additions and 10 deletions
|
@ -80,7 +80,7 @@ SSDP_TARGET = ("239.255.255.250", 1982)
|
||||||
SSDP_ST = "wifi_bulb"
|
SSDP_ST = "wifi_bulb"
|
||||||
DISCOVERY_ATTEMPTS = 3
|
DISCOVERY_ATTEMPTS = 3
|
||||||
DISCOVERY_SEARCH_INTERVAL = timedelta(seconds=2)
|
DISCOVERY_SEARCH_INTERVAL = timedelta(seconds=2)
|
||||||
DISCOVERY_TIMEOUT = 2
|
DISCOVERY_TIMEOUT = 8
|
||||||
|
|
||||||
|
|
||||||
YEELIGHT_RGB_TRANSITION = "RGBTransition"
|
YEELIGHT_RGB_TRANSITION = "RGBTransition"
|
||||||
|
@ -211,9 +211,6 @@ async def _async_initialize(
|
||||||
data={**entry.data, CONF_DETECTED_MODEL: device.capabilities["model"]},
|
data={**entry.data, CONF_DETECTED_MODEL: device.capabilities["model"]},
|
||||||
)
|
)
|
||||||
|
|
||||||
# fetch initial state
|
|
||||||
await device.async_update()
|
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_normalize_config_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
def _async_normalize_config_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||||
|
@ -500,7 +497,6 @@ class YeelightDevice:
|
||||||
self._device_type = None
|
self._device_type = None
|
||||||
self._available = True
|
self._available = True
|
||||||
self._initialized = False
|
self._initialized = False
|
||||||
self._did_first_update = False
|
|
||||||
self._name = None
|
self._name = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -568,7 +564,7 @@ class YeelightDevice:
|
||||||
@property
|
@property
|
||||||
def is_color_flow_enabled(self) -> bool:
|
def is_color_flow_enabled(self) -> bool:
|
||||||
"""Return true / false if color flow is currently running."""
|
"""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
|
@property
|
||||||
def _active_mode(self):
|
def _active_mode(self):
|
||||||
|
@ -632,7 +628,6 @@ class YeelightDevice:
|
||||||
|
|
||||||
async def async_update(self, force=False):
|
async def async_update(self, force=False):
|
||||||
"""Update device properties and send data updated signal."""
|
"""Update device properties and send data updated signal."""
|
||||||
self._did_first_update = True
|
|
||||||
if not force and self._initialized and self._available:
|
if not force and self._initialized and self._available:
|
||||||
# No need to poll unless force, already connected
|
# No need to poll unless force, already connected
|
||||||
return
|
return
|
||||||
|
@ -649,7 +644,7 @@ class YeelightDevice:
|
||||||
was_available = self._available
|
was_available = self._available
|
||||||
self._available = data.get(KEY_CONNECTED, True)
|
self._available = data.get(KEY_CONNECTED, True)
|
||||||
if update_needs_bg_power_workaround(data) or (
|
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
|
# 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)
|
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
|
return device
|
||||||
|
|
|
@ -488,7 +488,7 @@ class YeelightGenericLight(YeelightEntity, LightEntity):
|
||||||
brightness_property = (
|
brightness_property = (
|
||||||
"bright" if self._bulb.music_mode else self._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))
|
return round(255 * (int(brightness) / 100))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -380,6 +380,34 @@ async def test_async_listen_error_late_discovery(hass, caplog):
|
||||||
assert config_entry.data[CONF_DETECTED_MODEL] == MODEL
|
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):
|
async def test_unload_before_discovery(hass, caplog):
|
||||||
"""Test unloading before discovery."""
|
"""Test unloading before discovery."""
|
||||||
config_entry = MockConfigEntry(domain=DOMAIN, data=CONFIG_ENTRY_DATA)
|
config_entry = MockConfigEntry(domain=DOMAIN, data=CONFIG_ENTRY_DATA)
|
||||||
|
|
|
@ -751,7 +751,7 @@ async def test_device_types(hass: HomeAssistant, caplog):
|
||||||
mocked_bulb.last_properties = properties
|
mocked_bulb.last_properties = properties
|
||||||
|
|
||||||
async def _async_setup(config_entry):
|
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)
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
# We use asyncio.create_task now to avoid
|
# We use asyncio.create_task now to avoid
|
||||||
|
|
Loading…
Add table
Reference in a new issue