Less tasks when receiving SSDP messages (#84186)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Steven Looman 2022-12-21 22:35:52 +01:00 committed by GitHub
parent 19acbf0d2a
commit 682501eb47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 103 additions and 58 deletions

View File

@ -3,7 +3,7 @@
"name": "DLNA Digital Media Renderer", "name": "DLNA Digital Media Renderer",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/dlna_dmr", "documentation": "https://www.home-assistant.io/integrations/dlna_dmr",
"requirements": ["async-upnp-client==0.32.3", "getmac==0.8.2"], "requirements": ["async-upnp-client==0.33.0", "getmac==0.8.2"],
"dependencies": ["ssdp"], "dependencies": ["ssdp"],
"after_dependencies": ["media_source"], "after_dependencies": ["media_source"],
"ssdp": [ "ssdp": [

View File

@ -3,7 +3,7 @@
"name": "DLNA Digital Media Server", "name": "DLNA Digital Media Server",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/dlna_dms", "documentation": "https://www.home-assistant.io/integrations/dlna_dms",
"requirements": ["async-upnp-client==0.32.3"], "requirements": ["async-upnp-client==0.33.0"],
"dependencies": ["ssdp"], "dependencies": ["ssdp"],
"after_dependencies": ["media_source"], "after_dependencies": ["media_source"],
"ssdp": [ "ssdp": [

View File

@ -8,7 +8,7 @@
"samsungctl[websocket]==0.7.1", "samsungctl[websocket]==0.7.1",
"samsungtvws[async,encrypted]==2.5.0", "samsungtvws[async,encrypted]==2.5.0",
"wakeonlan==2.1.0", "wakeonlan==2.1.0",
"async-upnp-client==0.32.3" "async-upnp-client==0.33.0"
], ],
"ssdp": [ "ssdp": [
{ {

View File

@ -424,7 +424,7 @@ class Scanner:
source = fix_ipv6_address_scope_id(source) or source source = fix_ipv6_address_scope_id(source) or source
self._ssdp_listeners.append( self._ssdp_listeners.append(
SsdpListener( SsdpListener(
async_callback=self._ssdp_listener_callback, callback=self._ssdp_listener_callback,
source=source, source=source,
target=target, target=target,
device_tracker=device_tracker, device_tracker=device_tracker,
@ -458,7 +458,7 @@ class Scanner:
if _async_headers_match(combined_headers, lower_match_dict) if _async_headers_match(combined_headers, lower_match_dict)
] ]
async def _ssdp_listener_callback( def _ssdp_listener_callback(
self, self,
ssdp_device: SsdpDevice, ssdp_device: SsdpDevice,
dst: DeviceOrServiceType, dst: DeviceOrServiceType,
@ -469,15 +469,49 @@ class Scanner:
"SSDP: ssdp_device: %s, dst: %s, source: %s", ssdp_device, dst, source "SSDP: ssdp_device: %s, dst: %s, source: %s", ssdp_device, dst, source
) )
assert self._description_cache
location = ssdp_device.location location = ssdp_device.location
info_desc = None _, info_desc = self._description_cache.peek_description_dict(location)
if info_desc is None:
# Fetch info desc in separate task and process from there.
self.hass.async_create_task(
self._ssdp_listener_process_with_lookup(ssdp_device, dst, source)
)
return
# Info desc known, process directly.
self._ssdp_listener_process(ssdp_device, dst, source, info_desc)
async def _ssdp_listener_process_with_lookup(
self,
ssdp_device: SsdpDevice,
dst: DeviceOrServiceType,
source: SsdpSource,
) -> None:
"""Handle a device/service change."""
location = ssdp_device.location
self._ssdp_listener_process(
ssdp_device,
dst,
source,
await self._async_get_description_dict(location),
)
def _ssdp_listener_process(
self,
ssdp_device: SsdpDevice,
dst: DeviceOrServiceType,
source: SsdpSource,
info_desc: Mapping[str, Any],
) -> None:
"""Handle a device/service change."""
matching_domains: set[str] = set()
combined_headers = ssdp_device.combined_headers(dst) combined_headers = ssdp_device.combined_headers(dst)
callbacks = self._async_get_matching_callbacks(combined_headers) callbacks = self._async_get_matching_callbacks(combined_headers)
matching_domains: set[str] = set()
# If there are no changes from a search, do not trigger a config flow # If there are no changes from a search, do not trigger a config flow
if source != SsdpSource.SEARCH_ALIVE: if source != SsdpSource.SEARCH_ALIVE:
info_desc = await self._async_get_description_dict(location) or {}
matching_domains = self.integration_matchers.async_matching_domains( matching_domains = self.integration_matchers.async_matching_domains(
CaseInsensitiveDict(combined_headers.as_dict(), **info_desc) CaseInsensitiveDict(combined_headers.as_dict(), **info_desc)
) )
@ -485,21 +519,24 @@ class Scanner:
if not callbacks and not matching_domains: if not callbacks and not matching_domains:
return return
if info_desc is None:
info_desc = await self._async_get_description_dict(location) or {}
discovery_info = discovery_info_from_headers_and_description( discovery_info = discovery_info_from_headers_and_description(
combined_headers, info_desc combined_headers, info_desc
) )
discovery_info.x_homeassistant_matching_domains = matching_domains discovery_info.x_homeassistant_matching_domains = matching_domains
if callbacks:
ssdp_change = SSDP_SOURCE_SSDP_CHANGE_MAPPING[source] ssdp_change = SSDP_SOURCE_SSDP_CHANGE_MAPPING[source]
await _async_process_callbacks(callbacks, discovery_info, ssdp_change) self.hass.async_create_task(
_async_process_callbacks(callbacks, discovery_info, ssdp_change)
)
# Config flows should only be created for alive/update messages from alive devices # Config flows should only be created for alive/update messages from alive devices
if ssdp_change == SsdpChange.BYEBYE: if source == SsdpSource.ADVERTISEMENT_BYEBYE:
return return
_LOGGER.debug("Discovery info: %s", discovery_info) _LOGGER.debug("Discovery info: %s", discovery_info)
location = ssdp_device.location
for domain in matching_domains: for domain in matching_domains:
_LOGGER.debug("Discovered %s at %s", domain, location) _LOGGER.debug("Discovered %s at %s", domain, location)
discovery_flow.async_create_flow( discovery_flow.async_create_flow(
@ -514,6 +551,13 @@ class Scanner:
) -> Mapping[str, str]: ) -> Mapping[str, str]:
"""Get description dict.""" """Get description dict."""
assert self._description_cache is not None assert self._description_cache is not None
has_description, description = self._description_cache.peek_description_dict(
location
)
if has_description:
return description or {}
return await self._description_cache.async_get_description_dict(location) or {} return await self._description_cache.async_get_description_dict(location) or {}
async def _async_headers_to_discovery_info( async def _async_headers_to_discovery_info(
@ -524,10 +568,9 @@ class Scanner:
Building this is a bit expensive so we only do it on demand. Building this is a bit expensive so we only do it on demand.
""" """
assert self._description_cache is not None assert self._description_cache is not None
location = headers["location"] location = headers["location"]
info_desc = ( info_desc = await self._async_get_description_dict(location)
await self._description_cache.async_get_description_dict(location) or {}
)
return discovery_info_from_headers_and_description(headers, info_desc) return discovery_info_from_headers_and_description(headers, info_desc)
async def async_get_discovery_info_by_udn_st( # pylint: disable=invalid-name async def async_get_discovery_info_by_udn_st( # pylint: disable=invalid-name

View File

@ -2,7 +2,7 @@
"domain": "ssdp", "domain": "ssdp",
"name": "Simple Service Discovery Protocol (SSDP)", "name": "Simple Service Discovery Protocol (SSDP)",
"documentation": "https://www.home-assistant.io/integrations/ssdp", "documentation": "https://www.home-assistant.io/integrations/ssdp",
"requirements": ["async-upnp-client==0.32.3"], "requirements": ["async-upnp-client==0.33.0"],
"dependencies": ["network"], "dependencies": ["network"],
"after_dependencies": ["zeroconf"], "after_dependencies": ["zeroconf"],
"codeowners": [], "codeowners": [],

View File

@ -3,7 +3,7 @@
"name": "UPnP/IGD", "name": "UPnP/IGD",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/upnp", "documentation": "https://www.home-assistant.io/integrations/upnp",
"requirements": ["async-upnp-client==0.32.3", "getmac==0.8.2"], "requirements": ["async-upnp-client==0.33.0", "getmac==0.8.2"],
"dependencies": ["network", "ssdp"], "dependencies": ["network", "ssdp"],
"codeowners": ["@StevenLooman"], "codeowners": ["@StevenLooman"],
"ssdp": [ "ssdp": [

View File

@ -2,7 +2,7 @@
"domain": "yeelight", "domain": "yeelight",
"name": "Yeelight", "name": "Yeelight",
"documentation": "https://www.home-assistant.io/integrations/yeelight", "documentation": "https://www.home-assistant.io/integrations/yeelight",
"requirements": ["yeelight==0.7.10", "async-upnp-client==0.32.3"], "requirements": ["yeelight==0.7.10", "async-upnp-client==0.33.0"],
"codeowners": ["@zewelor", "@shenxn", "@starkillerOG", "@alexyao2015"], "codeowners": ["@zewelor", "@shenxn", "@starkillerOG", "@alexyao2015"],
"config_flow": true, "config_flow": true,
"dependencies": ["network"], "dependencies": ["network"],

View File

@ -2,7 +2,7 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
from collections.abc import Awaitable, Callable, ValuesView from collections.abc import Callable, ValuesView
import contextlib import contextlib
from datetime import datetime from datetime import datetime
from ipaddress import IPv4Address from ipaddress import IPv4Address
@ -64,10 +64,11 @@ class YeelightScanner:
for idx, source_ip in enumerate(await self._async_build_source_set()): for idx, source_ip in enumerate(await self._async_build_source_set()):
self._connected_events.append(asyncio.Event()) self._connected_events.append(asyncio.Event())
def _wrap_async_connected_idx(idx) -> Callable[[], Awaitable[None]]: def _wrap_async_connected_idx(idx) -> Callable[[], None]:
"""Create a function to capture the idx cell variable.""" """Create a function to capture the idx cell variable."""
async def _async_connected() -> None: @callback
def _async_connected() -> None:
self._connected_events[idx].set() self._connected_events[idx].set()
return _async_connected return _async_connected
@ -75,11 +76,11 @@ class YeelightScanner:
source = (str(source_ip), 0) source = (str(source_ip), 0)
self._listeners.append( self._listeners.append(
SsdpSearchListener( SsdpSearchListener(
async_callback=self._async_process_entry, callback=self._async_process_entry,
search_target=SSDP_ST, search_target=SSDP_ST,
target=SSDP_TARGET, target=SSDP_TARGET,
source=source, source=source,
async_connect_callback=_wrap_async_connected_idx(idx), connect_callback=_wrap_async_connected_idx(idx),
) )
) )
@ -180,7 +181,8 @@ class YeelightScanner:
# of another discovery # of another discovery
async_call_later(self._hass, 1, _async_start_flow) async_call_later(self._hass, 1, _async_start_flow)
async def _async_process_entry(self, headers: CaseInsensitiveDict) -> None: @callback
def _async_process_entry(self, headers: CaseInsensitiveDict) -> None:
"""Process a discovery.""" """Process a discovery."""
_LOGGER.debug("Discovered via SSDP: %s", headers) _LOGGER.debug("Discovered via SSDP: %s", headers)
unique_id = headers["id"] unique_id = headers["id"]

View File

@ -4,7 +4,7 @@ aiodiscover==1.4.13
aiohttp==3.8.1 aiohttp==3.8.1
aiohttp_cors==0.7.0 aiohttp_cors==0.7.0
astral==2.2 astral==2.2
async-upnp-client==0.32.3 async-upnp-client==0.33.0
async_timeout==4.0.2 async_timeout==4.0.2
atomicwrites-homeassistant==1.4.1 atomicwrites-homeassistant==1.4.1
attrs==22.1.0 attrs==22.1.0

View File

@ -368,7 +368,7 @@ asterisk_mbox==0.5.0
# homeassistant.components.ssdp # homeassistant.components.ssdp
# homeassistant.components.upnp # homeassistant.components.upnp
# homeassistant.components.yeelight # homeassistant.components.yeelight
async-upnp-client==0.32.3 async-upnp-client==0.33.0
# homeassistant.components.supla # homeassistant.components.supla
asyncpysupla==0.0.5 asyncpysupla==0.0.5

View File

@ -322,7 +322,7 @@ arcam-fmj==1.0.1
# homeassistant.components.ssdp # homeassistant.components.ssdp
# homeassistant.components.upnp # homeassistant.components.upnp
# homeassistant.components.yeelight # homeassistant.components.yeelight
async-upnp-client==0.32.3 async-upnp-client==0.33.0
# homeassistant.components.sleepiq # homeassistant.components.sleepiq
asyncsleepiq==1.2.3 asyncsleepiq==1.2.3

View File

@ -11,7 +11,6 @@ from async_upnp_client.ssdp_listener import SsdpListener
from async_upnp_client.utils import CaseInsensitiveDict from async_upnp_client.utils import CaseInsensitiveDict
import pytest import pytest
import homeassistant
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components import ssdp from homeassistant.components import ssdp
from homeassistant.const import ( from homeassistant.const import (
@ -19,6 +18,7 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_STOP,
MATCH_ALL, MATCH_ALL,
) )
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
@ -31,7 +31,7 @@ def _ssdp_headers(headers):
return ssdp_headers return ssdp_headers
async def init_ssdp_component(hass: homeassistant) -> SsdpListener: async def init_ssdp_component(hass: HomeAssistant) -> SsdpListener:
"""Initialize ssdp component and get SsdpListener.""" """Initialize ssdp component and get SsdpListener."""
await async_setup_component(hass, ssdp.DOMAIN, {ssdp.DOMAIN: {}}) await async_setup_component(hass, ssdp.DOMAIN, {ssdp.DOMAIN: {}})
await hass.async_block_till_done() await hass.async_block_till_done()
@ -55,7 +55,7 @@ async def test_ssdp_flow_dispatched_on_st(mock_get_ssdp, hass, caplog, mock_flow
} }
) )
ssdp_listener = await init_ssdp_component(hass) ssdp_listener = await init_ssdp_component(hass)
await ssdp_listener._on_search(mock_ssdp_search_response) ssdp_listener._on_search(mock_ssdp_search_response)
await hass.async_block_till_done() await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -98,7 +98,7 @@ async def test_ssdp_flow_dispatched_on_manufacturer_url(
} }
) )
ssdp_listener = await init_ssdp_component(hass) ssdp_listener = await init_ssdp_component(hass)
await ssdp_listener._on_search(mock_ssdp_search_response) ssdp_listener._on_search(mock_ssdp_search_response)
await hass.async_block_till_done() await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -148,7 +148,7 @@ async def test_scan_match_upnp_devicedesc_manufacturer(
} }
) )
ssdp_listener = await init_ssdp_component(hass) ssdp_listener = await init_ssdp_component(hass)
await ssdp_listener._on_search(mock_ssdp_search_response) ssdp_listener._on_search(mock_ssdp_search_response)
await hass.async_block_till_done() await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -189,7 +189,7 @@ async def test_scan_match_upnp_devicedesc_devicetype(
} }
) )
ssdp_listener = await init_ssdp_component(hass) ssdp_listener = await init_ssdp_component(hass)
await ssdp_listener._on_search(mock_ssdp_search_response) ssdp_listener._on_search(mock_ssdp_search_response)
await hass.async_block_till_done() await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
@ -237,7 +237,7 @@ async def test_scan_not_all_present(
} }
) )
ssdp_listener = await init_ssdp_component(hass) ssdp_listener = await init_ssdp_component(hass)
await ssdp_listener._on_search(mock_ssdp_search_response) ssdp_listener._on_search(mock_ssdp_search_response)
await hass.async_block_till_done() await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -278,7 +278,7 @@ async def test_scan_not_all_match(mock_get_ssdp, hass, aioclient_mock, mock_flow
} }
) )
ssdp_listener = await init_ssdp_component(hass) ssdp_listener = await init_ssdp_component(hass)
await ssdp_listener._on_search(mock_ssdp_search_response) ssdp_listener._on_search(mock_ssdp_search_response)
await hass.async_block_till_done() await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -317,7 +317,7 @@ async def test_flow_start_only_alive(
"usn": "uuid:mock-udn::mock-st", "usn": "uuid:mock-udn::mock-st",
} }
) )
await ssdp_listener._on_search(mock_ssdp_search_response) ssdp_listener._on_search(mock_ssdp_search_response)
await hass.async_block_till_done() await hass.async_block_till_done()
mock_flow_init.assert_awaited_once_with( mock_flow_init.assert_awaited_once_with(
@ -334,7 +334,7 @@ async def test_flow_start_only_alive(
"nts": "ssdp:alive", "nts": "ssdp:alive",
} }
) )
await ssdp_listener._on_alive(mock_ssdp_advertisement) ssdp_listener._on_alive(mock_ssdp_advertisement)
await hass.async_block_till_done() await hass.async_block_till_done()
mock_flow_init.assert_awaited_once_with( mock_flow_init.assert_awaited_once_with(
"mock-domain", context={"source": config_entries.SOURCE_SSDP}, data=ANY "mock-domain", context={"source": config_entries.SOURCE_SSDP}, data=ANY
@ -343,14 +343,14 @@ async def test_flow_start_only_alive(
# ssdp:byebye advertisement should not start a flow # ssdp:byebye advertisement should not start a flow
mock_flow_init.reset_mock() mock_flow_init.reset_mock()
mock_ssdp_advertisement["nts"] = "ssdp:byebye" mock_ssdp_advertisement["nts"] = "ssdp:byebye"
await ssdp_listener._on_byebye(mock_ssdp_advertisement) ssdp_listener._on_byebye(mock_ssdp_advertisement)
await hass.async_block_till_done() await hass.async_block_till_done()
mock_flow_init.assert_not_called() mock_flow_init.assert_not_called()
# ssdp:update advertisement should start a flow # ssdp:update advertisement should start a flow
mock_flow_init.reset_mock() mock_flow_init.reset_mock()
mock_ssdp_advertisement["nts"] = "ssdp:update" mock_ssdp_advertisement["nts"] = "ssdp:update"
await ssdp_listener._on_update(mock_ssdp_advertisement) ssdp_listener._on_update(mock_ssdp_advertisement)
await hass.async_block_till_done() await hass.async_block_till_done()
mock_flow_init.assert_awaited_once_with( mock_flow_init.assert_awaited_once_with(
"mock-domain", context={"source": config_entries.SOURCE_SSDP}, data=ANY "mock-domain", context={"source": config_entries.SOURCE_SSDP}, data=ANY
@ -388,7 +388,7 @@ async def test_discovery_from_advertisement_sets_ssdp_st(
"usn": "uuid:mock-udn::mock-st", "usn": "uuid:mock-udn::mock-st",
} }
) )
await ssdp_listener._on_alive(mock_ssdp_advertisement) ssdp_listener._on_alive(mock_ssdp_advertisement)
await hass.async_block_till_done() await hass.async_block_till_done()
discovery_info = await ssdp.async_get_discovery_info_by_udn(hass, "uuid:mock-udn") discovery_info = await ssdp.async_get_discovery_info_by_udn(hass, "uuid:mock-udn")
@ -469,34 +469,35 @@ async def test_scan_with_registered_callback(
hass, async_integration_callback, {"st": "mock-st"} hass, async_integration_callback, {"st": "mock-st"}
) )
async_integration_match_all_callback1 = AsyncMock() async_integration_match_all_callback = AsyncMock()
await ssdp.async_register_callback( await ssdp.async_register_callback(
hass, async_integration_match_all_callback1, {"x-rincon-bootseq": MATCH_ALL} hass, async_integration_match_all_callback, {"x-rincon-bootseq": MATCH_ALL}
) )
async_integration_match_all_not_present_callback1 = AsyncMock() async_integration_match_all_not_present_callback = AsyncMock()
await ssdp.async_register_callback( await ssdp.async_register_callback(
hass, hass,
async_integration_match_all_not_present_callback1, async_integration_match_all_not_present_callback,
{"x-not-there": MATCH_ALL}, {"x-not-there": MATCH_ALL},
) )
async_not_matching_integration_callback1 = AsyncMock() async_not_matching_integration_callback = AsyncMock()
await ssdp.async_register_callback( await ssdp.async_register_callback(
hass, async_not_matching_integration_callback1, {"st": "not-match-mock-st"} hass, async_not_matching_integration_callback, {"st": "not-match-mock-st"}
) )
async_match_any_callback1 = AsyncMock() async_match_any_callback = AsyncMock()
await ssdp.async_register_callback(hass, async_match_any_callback1) await ssdp.async_register_callback(hass, async_match_any_callback)
await hass.async_block_till_done() await hass.async_block_till_done()
await ssdp_listener._on_search(mock_ssdp_search_response) ssdp_listener._on_search(mock_ssdp_search_response)
await hass.async_block_till_done()
assert async_integration_callback.call_count == 1 assert async_integration_callback.call_count == 1
assert async_integration_match_all_callback1.call_count == 1 assert async_integration_match_all_callback.call_count == 1
assert async_integration_match_all_not_present_callback1.call_count == 0 assert async_integration_match_all_not_present_callback.call_count == 0
assert async_match_any_callback1.call_count == 1 assert async_match_any_callback.call_count == 1
assert async_not_matching_integration_callback1.call_count == 0 assert async_not_matching_integration_callback.call_count == 0
assert async_integration_callback.call_args[0][1] == ssdp.SsdpChange.ALIVE assert async_integration_callback.call_args[0][1] == ssdp.SsdpChange.ALIVE
mock_call_data: ssdp.SsdpServiceInfo = async_integration_callback.call_args[0][0] mock_call_data: ssdp.SsdpServiceInfo = async_integration_callback.call_args[0][0]
assert mock_call_data.ssdp_ext == "" assert mock_call_data.ssdp_ext == ""
@ -552,7 +553,7 @@ async def test_getting_existing_headers(
} }
) )
ssdp_listener = await init_ssdp_component(hass) ssdp_listener = await init_ssdp_component(hass)
await ssdp_listener._on_search(mock_ssdp_search_response) ssdp_listener._on_search(mock_ssdp_search_response)
discovery_info_by_st = await ssdp.async_get_discovery_info_by_st(hass, "mock-st") discovery_info_by_st = await ssdp.async_get_discovery_info_by_st(hass, "mock-st")
discovery_info_by_st = discovery_info_by_st[0] discovery_info_by_st = discovery_info_by_st[0]

View File

@ -1,5 +1,4 @@
"""Tests for the Yeelight integration.""" """Tests for the Yeelight integration."""
import asyncio
from datetime import timedelta from datetime import timedelta
from unittest.mock import AsyncMock, MagicMock, patch from unittest.mock import AsyncMock, MagicMock, patch
@ -164,12 +163,12 @@ def _patched_ssdp_listener(info: CaseInsensitiveDict, *args, **kwargs):
async def _async_callback(*_): async def _async_callback(*_):
if kwargs["source"][0] == FAIL_TO_BIND_IP: if kwargs["source"][0] == FAIL_TO_BIND_IP:
raise OSError raise OSError
await listener.async_connect_callback() listener.connect_callback()
@callback @callback
def _async_search(*_): def _async_search(*_):
if info: if info:
asyncio.create_task(listener.async_callback(info)) listener.callback(info)
listener.async_start = _async_callback listener.async_start = _async_callback
listener.async_search = _async_search listener.async_search = _async_search