Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Retry yeelight setup later if the wrong device is found #98884

Merged
merged 3 commits into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 14 additions & 0 deletions homeassistant/components/yeelight/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ async def _async_initialize(
entry: ConfigEntry,
device: YeelightDevice,
) -> None:
"""Initialize a Yeelight device."""
entry_data = hass.data[DOMAIN][DATA_CONFIG_ENTRIES][entry.entry_id] = {}
await device.async_setup()
entry_data[DATA_DEVICE] = device
Expand Down Expand Up @@ -216,6 +217,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
except (asyncio.TimeoutError, OSError, BulbException) as ex:
raise ConfigEntryNotReady from ex

found_unique_id = device.unique_id
expected_unique_id = entry.unique_id
if expected_unique_id and found_unique_id and found_unique_id != expected_unique_id:
# If the id of the device does not match the unique_id
# of the config entry, it likely means the DHCP lease has expired
# and the device has been assigned a new IP address. We need to
# wait for the next discovery to find the device at its new address
# and update the config entry so we do not mix up devices.
raise ConfigEntryNotReady(
f"Unexpected device found at {device.host}; "
f"expected {expected_unique_id}, found {found_unique_id}"
)

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

# Wait to install the reload listener until everything was successfully initialized
Expand Down
20 changes: 14 additions & 6 deletions homeassistant/components/yeelight/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@

import asyncio
import logging
from typing import Any

from yeelight import BulbException
from yeelight.aio import KEY_CONNECTED
from yeelight.aio import KEY_CONNECTED, AsyncBulb

from homeassistant.const import CONF_ID, CONF_NAME
from homeassistant.core import callback
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.event import async_call_later

Expand Down Expand Up @@ -63,17 +64,19 @@ def update_needs_bg_power_workaround(data):
class YeelightDevice:
"""Represents single Yeelight device."""

def __init__(self, hass, host, config, bulb):
def __init__(
self, hass: HomeAssistant, host: str, config: dict[str, Any], bulb: AsyncBulb
) -> None:
"""Initialize device."""
self._hass = hass
self._config = config
self._host = host
self._bulb_device = bulb
self.capabilities = {}
self._device_type = None
self.capabilities: dict[str, Any] = {}
self._device_type: str | None = None
self._available = True
self._initialized = False
self._name = None
self._name: str | None = None

@property
def bulb(self):
Expand Down Expand Up @@ -115,6 +118,11 @@ def fw_version(self):
"""Return the firmware version."""
return self.capabilities.get("fw_ver")

@property
def unique_id(self) -> str | None:
"""Return the unique ID of the device."""
return self.capabilities.get("id")

@property
def is_nightlight_supported(self) -> bool:
"""Return true / false if nightlight is supported.
Expand Down
25 changes: 25 additions & 0 deletions tests/components/yeelight/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -618,3 +618,28 @@ async def test_async_setup_with_discovery_not_working(hass: HomeAssistant) -> No
assert config_entry.state is ConfigEntryState.LOADED

assert hass.states.get("light.yeelight_color_0x15243f").state == STATE_ON


async def test_async_setup_retries_with_wrong_device(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test the config entry enters a retry state with the wrong device."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data={CONF_HOST: IP_ADDRESS, CONF_ID: "0x0000000000999999"},
options={},
unique_id="0x0000000000999999",
)
config_entry.add_to_hass(hass)

with _patch_discovery(), _patch_discovery_timeout(), _patch_discovery_interval(), 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
assert (
"Unexpected device found at 192.168.1.239; expected 0x0000000000999999, "
"found 0x000000000015243f; Retrying in background"
) in caplog.text