mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 08:47:57 +00:00
Switch ssdp to be async by using async_upnp_client for scanning (#46554)
SSDP scans no longer runs in the executor This is an interim step that converts the async_upnp_client response to netdisco's object to ensure fully backwards compatibility
This commit is contained in:
parent
e9334347eb
commit
39785c5cef
@ -1,14 +1,16 @@
|
||||
"""The SSDP integration."""
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
import itertools
|
||||
import logging
|
||||
from typing import Any, Mapping
|
||||
|
||||
import aiohttp
|
||||
from async_upnp_client.search import async_search
|
||||
from defusedxml import ElementTree
|
||||
from netdisco import ssdp, util
|
||||
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.event import async_track_time_interval
|
||||
from homeassistant.loader import async_get_ssdp
|
||||
|
||||
@ -51,12 +53,6 @@ async def async_setup(hass, config):
|
||||
return True
|
||||
|
||||
|
||||
def _run_ssdp_scans():
|
||||
_LOGGER.debug("Scanning")
|
||||
# Run 3 times as packets can get lost
|
||||
return itertools.chain.from_iterable([ssdp.scan() for _ in range(3)])
|
||||
|
||||
|
||||
class Scanner:
|
||||
"""Class to manage SSDP scanning."""
|
||||
|
||||
@ -64,25 +60,38 @@ class Scanner:
|
||||
"""Initialize class."""
|
||||
self.hass = hass
|
||||
self.seen = set()
|
||||
self._entries = []
|
||||
self._integration_matchers = integration_matchers
|
||||
self._description_cache = {}
|
||||
|
||||
async def _on_ssdp_response(self, data: Mapping[str, Any]) -> None:
|
||||
"""Process an ssdp response."""
|
||||
self.async_store_entry(
|
||||
ssdp.UPNPEntry({key.lower(): item for key, item in data.items()})
|
||||
)
|
||||
|
||||
@callback
|
||||
def async_store_entry(self, entry):
|
||||
"""Save an entry for later processing."""
|
||||
self._entries.append(entry)
|
||||
|
||||
async def async_scan(self, _):
|
||||
"""Scan for new entries."""
|
||||
entries = await self.hass.async_add_executor_job(_run_ssdp_scans)
|
||||
|
||||
await self._process_entries(entries)
|
||||
await async_search(async_callback=self._on_ssdp_response)
|
||||
await self._process_entries()
|
||||
|
||||
# We clear the cache after each run. We track discovered entries
|
||||
# so will never need a description twice.
|
||||
self._description_cache.clear()
|
||||
self._entries.clear()
|
||||
|
||||
async def _process_entries(self, entries):
|
||||
async def _process_entries(self):
|
||||
"""Process SSDP entries."""
|
||||
entries_to_process = []
|
||||
unseen_locations = set()
|
||||
|
||||
for entry in entries:
|
||||
for entry in self._entries:
|
||||
key = (entry.st, entry.location)
|
||||
|
||||
if key in self.seen:
|
||||
|
@ -2,7 +2,7 @@
|
||||
"domain": "ssdp",
|
||||
"name": "Simple Service Discovery Protocol (SSDP)",
|
||||
"documentation": "https://www.home-assistant.io/integrations/ssdp",
|
||||
"requirements": ["defusedxml==0.6.0", "netdisco==2.8.2"],
|
||||
"requirements": ["defusedxml==0.6.0", "netdisco==2.8.2", "async-upnp-client==0.14.13"],
|
||||
"after_dependencies": ["zeroconf"],
|
||||
"codeowners": []
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ PyNaCl==1.3.0
|
||||
aiohttp==3.7.3
|
||||
aiohttp_cors==0.7.0
|
||||
astral==1.10.1
|
||||
async-upnp-client==0.14.13
|
||||
async_timeout==3.0.1
|
||||
attrs==19.3.0
|
||||
awesomeversion==21.2.2
|
||||
|
@ -284,6 +284,7 @@ asmog==0.0.6
|
||||
asterisk_mbox==0.5.0
|
||||
|
||||
# homeassistant.components.dlna_dmr
|
||||
# homeassistant.components.ssdp
|
||||
# homeassistant.components.upnp
|
||||
async-upnp-client==0.14.13
|
||||
|
||||
|
@ -173,6 +173,7 @@ aprslib==0.6.46
|
||||
arcam-fmj==0.5.3
|
||||
|
||||
# homeassistant.components.dlna_dmr
|
||||
# homeassistant.components.ssdp
|
||||
# homeassistant.components.upnp
|
||||
async-upnp-client==0.14.13
|
||||
|
||||
|
@ -14,15 +14,18 @@ async def test_scan_match_st(hass, caplog):
|
||||
"""Test matching based on ST."""
|
||||
scanner = ssdp.Scanner(hass, {"mock-domain": [{"st": "mock-st"}]})
|
||||
|
||||
with patch(
|
||||
"netdisco.ssdp.scan",
|
||||
return_value=[
|
||||
async def _inject_entry(*args, **kwargs):
|
||||
scanner.async_store_entry(
|
||||
Mock(
|
||||
st="mock-st",
|
||||
location=None,
|
||||
values={"usn": "mock-usn", "server": "mock-server", "ext": ""},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.ssdp.async_search",
|
||||
side_effect=_inject_entry,
|
||||
), patch.object(
|
||||
hass.config_entries.flow, "async_init", return_value=mock_coro()
|
||||
) as mock_init:
|
||||
@ -58,9 +61,14 @@ async def test_scan_match_upnp_devicedesc(hass, aioclient_mock, key):
|
||||
)
|
||||
scanner = ssdp.Scanner(hass, {"mock-domain": [{key: "Paulus"}]})
|
||||
|
||||
async def _inject_entry(*args, **kwargs):
|
||||
scanner.async_store_entry(
|
||||
Mock(st="mock-st", location="http://1.1.1.1", values={})
|
||||
)
|
||||
|
||||
with patch(
|
||||
"netdisco.ssdp.scan",
|
||||
return_value=[Mock(st="mock-st", location="http://1.1.1.1", values={})],
|
||||
"homeassistant.components.ssdp.async_search",
|
||||
side_effect=_inject_entry,
|
||||
), patch.object(
|
||||
hass.config_entries.flow, "async_init", return_value=mock_coro()
|
||||
) as mock_init:
|
||||
@ -95,9 +103,14 @@ async def test_scan_not_all_present(hass, aioclient_mock):
|
||||
},
|
||||
)
|
||||
|
||||
async def _inject_entry(*args, **kwargs):
|
||||
scanner.async_store_entry(
|
||||
Mock(st="mock-st", location="http://1.1.1.1", values={})
|
||||
)
|
||||
|
||||
with patch(
|
||||
"netdisco.ssdp.scan",
|
||||
return_value=[Mock(st="mock-st", location="http://1.1.1.1", values={})],
|
||||
"homeassistant.components.ssdp.async_search",
|
||||
side_effect=_inject_entry,
|
||||
), patch.object(
|
||||
hass.config_entries.flow, "async_init", return_value=mock_coro()
|
||||
) as mock_init:
|
||||
@ -131,9 +144,14 @@ async def test_scan_not_all_match(hass, aioclient_mock):
|
||||
},
|
||||
)
|
||||
|
||||
async def _inject_entry(*args, **kwargs):
|
||||
scanner.async_store_entry(
|
||||
Mock(st="mock-st", location="http://1.1.1.1", values={})
|
||||
)
|
||||
|
||||
with patch(
|
||||
"netdisco.ssdp.scan",
|
||||
return_value=[Mock(st="mock-st", location="http://1.1.1.1", values={})],
|
||||
"homeassistant.components.ssdp.async_search",
|
||||
side_effect=_inject_entry,
|
||||
), patch.object(
|
||||
hass.config_entries.flow, "async_init", return_value=mock_coro()
|
||||
) as mock_init:
|
||||
@ -148,9 +166,14 @@ async def test_scan_description_fetch_fail(hass, aioclient_mock, exc):
|
||||
aioclient_mock.get("http://1.1.1.1", exc=exc)
|
||||
scanner = ssdp.Scanner(hass, {})
|
||||
|
||||
async def _inject_entry(*args, **kwargs):
|
||||
scanner.async_store_entry(
|
||||
Mock(st="mock-st", location="http://1.1.1.1", values={})
|
||||
)
|
||||
|
||||
with patch(
|
||||
"netdisco.ssdp.scan",
|
||||
return_value=[Mock(st="mock-st", location="http://1.1.1.1", values={})],
|
||||
"homeassistant.components.ssdp.async_search",
|
||||
side_effect=_inject_entry,
|
||||
):
|
||||
await scanner.async_scan(None)
|
||||
|
||||
@ -165,9 +188,14 @@ async def test_scan_description_parse_fail(hass, aioclient_mock):
|
||||
)
|
||||
scanner = ssdp.Scanner(hass, {})
|
||||
|
||||
async def _inject_entry(*args, **kwargs):
|
||||
scanner.async_store_entry(
|
||||
Mock(st="mock-st", location="http://1.1.1.1", values={})
|
||||
)
|
||||
|
||||
with patch(
|
||||
"netdisco.ssdp.scan",
|
||||
return_value=[Mock(st="mock-st", location="http://1.1.1.1", values={})],
|
||||
"homeassistant.components.ssdp.async_search",
|
||||
side_effect=_inject_entry,
|
||||
):
|
||||
await scanner.async_scan(None)
|
||||
|
||||
@ -196,9 +224,14 @@ async def test_invalid_characters(hass, aioclient_mock):
|
||||
},
|
||||
)
|
||||
|
||||
async def _inject_entry(*args, **kwargs):
|
||||
scanner.async_store_entry(
|
||||
Mock(st="mock-st", location="http://1.1.1.1", values={})
|
||||
)
|
||||
|
||||
with patch(
|
||||
"netdisco.ssdp.scan",
|
||||
return_value=[Mock(st="mock-st", location="http://1.1.1.1", values={})],
|
||||
"homeassistant.components.ssdp.async_search",
|
||||
side_effect=_inject_entry,
|
||||
), patch.object(
|
||||
hass.config_entries.flow, "async_init", return_value=mock_coro()
|
||||
) as mock_init:
|
||||
|
Loading…
x
Reference in New Issue
Block a user