Fix yeelight connection when bulb stops responding to SSDP (#57138)
parent
e22407ba16
commit
eba7cad33f
|
@ -181,6 +181,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
DATA_CUSTOM_EFFECTS: conf.get(CONF_CUSTOM_EFFECTS, {}),
|
DATA_CUSTOM_EFFECTS: conf.get(CONF_CUSTOM_EFFECTS, {}),
|
||||||
DATA_CONFIG_ENTRIES: {},
|
DATA_CONFIG_ENTRIES: {},
|
||||||
}
|
}
|
||||||
|
# Make sure the scanner is always started in case we are
|
||||||
|
# going to retry via ConfigEntryNotReady and the bulb has changed
|
||||||
|
# ip
|
||||||
|
scanner = YeelightScanner.async_get(hass)
|
||||||
|
await scanner.async_setup()
|
||||||
|
|
||||||
# Import manually configured devices
|
# Import manually configured devices
|
||||||
for host, device_config in config.get(DOMAIN, {}).get(CONF_DEVICES, {}).items():
|
for host, device_config in config.get(DOMAIN, {}).get(CONF_DEVICES, {}).items():
|
||||||
|
@ -281,11 +286,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
try:
|
try:
|
||||||
device = await _async_get_device(hass, entry.data[CONF_HOST], entry)
|
device = await _async_get_device(hass, entry.data[CONF_HOST], entry)
|
||||||
except BULB_EXCEPTIONS as ex:
|
except BULB_EXCEPTIONS as ex:
|
||||||
# If CONF_ID is not valid we cannot fallback to discovery
|
# Always retry later since bulbs can stop responding to SSDP
|
||||||
# so we must retry by raising ConfigEntryNotReady
|
# sometimes even though they are online. If it has changed
|
||||||
if not entry.data.get(CONF_ID):
|
# IP we will update it via discovery to the config flow
|
||||||
raise ConfigEntryNotReady from ex
|
raise ConfigEntryNotReady from ex
|
||||||
# Otherwise fall through to discovery
|
|
||||||
else:
|
else:
|
||||||
# Since device is passed this cannot throw an exception anymore
|
# Since device is passed this cannot throw an exception anymore
|
||||||
await _async_initialize(hass, entry, entry.data[CONF_HOST], device=device)
|
await _async_initialize(hass, entry, entry.data[CONF_HOST], device=device)
|
||||||
|
@ -298,7 +302,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
except BULB_EXCEPTIONS:
|
except BULB_EXCEPTIONS:
|
||||||
_LOGGER.exception("Failed to connect to bulb at %s", host)
|
_LOGGER.exception("Failed to connect to bulb at %s", host)
|
||||||
|
|
||||||
# discovery
|
|
||||||
scanner = YeelightScanner.async_get(hass)
|
scanner = YeelightScanner.async_get(hass)
|
||||||
await scanner.async_register_callback(entry.data[CONF_ID], _async_from_discovery)
|
await scanner.async_register_callback(entry.data[CONF_ID], _async_from_discovery)
|
||||||
return True
|
return True
|
||||||
|
@ -501,7 +504,9 @@ class YeelightScanner:
|
||||||
_LOGGER.debug("Discovered via SSDP: %s", response)
|
_LOGGER.debug("Discovered via SSDP: %s", response)
|
||||||
unique_id = response["id"]
|
unique_id = response["id"]
|
||||||
host = urlparse(response["location"]).hostname
|
host = urlparse(response["location"]).hostname
|
||||||
if unique_id not in self._unique_id_capabilities:
|
current_entry = self._unique_id_capabilities.get(unique_id)
|
||||||
|
# Make sure we handle ip changes
|
||||||
|
if not current_entry or host != urlparse(current_entry["location"]).hostname:
|
||||||
_LOGGER.debug("Yeelight discovered with %s", response)
|
_LOGGER.debug("Yeelight discovered with %s", response)
|
||||||
self._async_discovered_by_ssdp(response)
|
self._async_discovered_by_ssdp(response)
|
||||||
self._host_capabilities[host] = response
|
self._host_capabilities[host] = response
|
||||||
|
@ -571,7 +576,7 @@ class YeelightDevice:
|
||||||
self._bulb_device = bulb
|
self._bulb_device = bulb
|
||||||
self.capabilities = {}
|
self.capabilities = {}
|
||||||
self._device_type = None
|
self._device_type = None
|
||||||
self._available = False
|
self._available = True
|
||||||
self._initialized = False
|
self._initialized = False
|
||||||
self._did_first_update = False
|
self._did_first_update = False
|
||||||
self._name = None
|
self._name = None
|
||||||
|
|
|
@ -9,8 +9,6 @@ from homeassistant.components.yeelight import (
|
||||||
CONF_MODEL,
|
CONF_MODEL,
|
||||||
CONF_NIGHTLIGHT_SWITCH,
|
CONF_NIGHTLIGHT_SWITCH,
|
||||||
CONF_NIGHTLIGHT_SWITCH_TYPE,
|
CONF_NIGHTLIGHT_SWITCH_TYPE,
|
||||||
DATA_CONFIG_ENTRIES,
|
|
||||||
DATA_DEVICE,
|
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
NIGHTLIGHT_SWITCH_TYPE_LIGHT,
|
NIGHTLIGHT_SWITCH_TYPE_LIGHT,
|
||||||
STATE_CHANGE_TIME,
|
STATE_CHANGE_TIME,
|
||||||
|
@ -57,41 +55,41 @@ async def test_ip_changes_fallback_discovery(hass: HomeAssistant):
|
||||||
)
|
)
|
||||||
config_entry.add_to_hass(hass)
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
mocked_bulb = _mocked_bulb(True)
|
mocked_fail_bulb = _mocked_bulb(cannot_connect=True)
|
||||||
mocked_bulb.bulb_type = BulbType.WhiteTempMood
|
mocked_fail_bulb.bulb_type = BulbType.WhiteTempMood
|
||||||
mocked_bulb.async_listen = AsyncMock(side_effect=[BulbException, None, None, None])
|
with patch(
|
||||||
|
f"{MODULE}.AsyncBulb", return_value=mocked_fail_bulb
|
||||||
|
), _patch_discovery():
|
||||||
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||||
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=2))
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# The discovery should update the ip address
|
||||||
|
assert config_entry.data[CONF_HOST] == IP_ADDRESS
|
||||||
|
assert config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||||
|
mocked_bulb = _mocked_bulb()
|
||||||
|
|
||||||
with patch(f"{MODULE}.AsyncBulb", return_value=mocked_bulb), _patch_discovery():
|
with patch(f"{MODULE}.AsyncBulb", return_value=mocked_bulb), _patch_discovery():
|
||||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=10))
|
||||||
await hass.async_block_till_done()
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
assert config_entry.state is ConfigEntryState.LOADED
|
||||||
|
|
||||||
binary_sensor_entity_id = ENTITY_BINARY_SENSOR_TEMPLATE.format(
|
binary_sensor_entity_id = ENTITY_BINARY_SENSOR_TEMPLATE.format(
|
||||||
f"yeelight_color_{SHORT_ID}"
|
f"yeelight_color_{SHORT_ID}"
|
||||||
)
|
)
|
||||||
|
|
||||||
type(mocked_bulb).async_get_properties = AsyncMock(None)
|
|
||||||
|
|
||||||
await hass.data[DOMAIN][DATA_CONFIG_ENTRIES][config_entry.entry_id][
|
|
||||||
DATA_DEVICE
|
|
||||||
].async_update()
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
entity_registry = er.async_get(hass)
|
entity_registry = er.async_get(hass)
|
||||||
assert entity_registry.async_get(binary_sensor_entity_id) is not None
|
assert entity_registry.async_get(binary_sensor_entity_id) is not None
|
||||||
|
|
||||||
with patch(f"{MODULE}.AsyncBulb", return_value=mocked_bulb), _patch_discovery():
|
|
||||||
# The discovery should update the ip address
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=2))
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert config_entry.data[CONF_HOST] == IP_ADDRESS
|
|
||||||
|
|
||||||
# Make sure we can still reload with the new ip right after we change it
|
# Make sure we can still reload with the new ip right after we change it
|
||||||
with patch(f"{MODULE}.AsyncBulb", return_value=mocked_bulb), _patch_discovery():
|
with patch(f"{MODULE}.AsyncBulb", return_value=mocked_bulb), _patch_discovery():
|
||||||
await hass.config_entries.async_reload(config_entry.entry_id)
|
await hass.config_entries.async_reload(config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert config_entry.state is ConfigEntryState.LOADED
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
assert entity_registry.async_get(binary_sensor_entity_id) is not None
|
assert entity_registry.async_get(binary_sensor_entity_id) is not None
|
||||||
|
|
||||||
|
|
||||||
|
@ -328,13 +326,21 @@ async def test_bulb_off_while_adding_in_ha(hass: HomeAssistant):
|
||||||
)
|
)
|
||||||
config_entry.add_to_hass(hass)
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
mocked_bulb = _mocked_bulb(True)
|
mocked_bulb = _mocked_bulb(cannot_connect=True)
|
||||||
mocked_bulb.bulb_type = BulbType.WhiteTempMood
|
mocked_bulb.bulb_type = BulbType.WhiteTempMood
|
||||||
|
|
||||||
with patch(f"{MODULE}.AsyncBulb", return_value=mocked_bulb), _patch_discovery(
|
with patch(f"{MODULE}.AsyncBulb", return_value=mocked_bulb), _patch_discovery(
|
||||||
no_device=True
|
no_device=True
|
||||||
), _patch_discovery_timeout(), _patch_discovery_interval():
|
), _patch_discovery_timeout(), _patch_discovery_interval():
|
||||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||||
|
|
||||||
|
with patch(f"{MODULE}.AsyncBulb", return_value=_mocked_bulb()), _patch_discovery(
|
||||||
|
no_device=True
|
||||||
|
), _patch_discovery_timeout(), _patch_discovery_interval():
|
||||||
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=2))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert config_entry.state is ConfigEntryState.LOADED
|
assert config_entry.state is ConfigEntryState.LOADED
|
||||||
|
@ -401,7 +407,7 @@ async def test_async_listen_error_has_host_with_id(hass: HomeAssistant):
|
||||||
):
|
):
|
||||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
|
||||||
assert config_entry.state is ConfigEntryState.LOADED
|
assert config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||||
|
|
||||||
|
|
||||||
async def test_async_listen_error_has_host_without_id(hass: HomeAssistant):
|
async def test_async_listen_error_has_host_without_id(hass: HomeAssistant):
|
||||||
|
@ -433,9 +439,16 @@ async def test_async_setup_with_missing_id(hass: HomeAssistant):
|
||||||
f"{MODULE}.AsyncBulb", return_value=_mocked_bulb(cannot_connect=True)
|
f"{MODULE}.AsyncBulb", return_value=_mocked_bulb(cannot_connect=True)
|
||||||
):
|
):
|
||||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
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 config_entry.data[CONF_ID] == ID
|
||||||
|
|
||||||
assert config_entry.state is ConfigEntryState.LOADED
|
with _patch_discovery(), _patch_discovery_timeout(), _patch_discovery_interval(), patch(
|
||||||
assert config_entry.data[CONF_ID] == ID
|
f"{MODULE}.AsyncBulb", return_value=_mocked_bulb()
|
||||||
|
):
|
||||||
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=2))
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert config_entry.state is ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
|
||||||
async def test_connection_dropped_resyncs_properties(hass: HomeAssistant):
|
async def test_connection_dropped_resyncs_properties(hass: HomeAssistant):
|
||||||
|
|
Loading…
Reference in New Issue