mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 22:27:07 +00:00
Remove legacy discovery integration (#96856)
This commit is contained in:
parent
b45369bb35
commit
22d0f4ff0a
@ -277,8 +277,6 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/discord/ @tkdrob
|
/tests/components/discord/ @tkdrob
|
||||||
/homeassistant/components/discovergy/ @jpbede
|
/homeassistant/components/discovergy/ @jpbede
|
||||||
/tests/components/discovergy/ @jpbede
|
/tests/components/discovergy/ @jpbede
|
||||||
/homeassistant/components/discovery/ @home-assistant/core
|
|
||||||
/tests/components/discovery/ @home-assistant/core
|
|
||||||
/homeassistant/components/dlink/ @tkdrob
|
/homeassistant/components/dlink/ @tkdrob
|
||||||
/tests/components/dlink/ @tkdrob
|
/tests/components/dlink/ @tkdrob
|
||||||
/homeassistant/components/dlna_dmr/ @StevenLooman @chishm
|
/homeassistant/components/dlna_dmr/ @StevenLooman @chishm
|
||||||
|
@ -1,248 +0,0 @@
|
|||||||
"""Starts a service to scan in intervals for new devices."""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
from typing import NamedTuple
|
|
||||||
|
|
||||||
from netdisco.discovery import NetworkDiscovery
|
|
||||||
import voluptuous as vol
|
|
||||||
|
|
||||||
from homeassistant import config_entries
|
|
||||||
from homeassistant.components import zeroconf
|
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED
|
|
||||||
from homeassistant.core import Event, HassJob, HomeAssistant, callback
|
|
||||||
from homeassistant.helpers import discovery_flow
|
|
||||||
import homeassistant.helpers.config_validation as cv
|
|
||||||
from homeassistant.helpers.discovery import async_discover, async_load_platform
|
|
||||||
from homeassistant.helpers.event import async_track_point_in_utc_time
|
|
||||||
from homeassistant.helpers.typing import ConfigType
|
|
||||||
from homeassistant.loader import async_get_zeroconf
|
|
||||||
import homeassistant.util.dt as dt_util
|
|
||||||
|
|
||||||
DOMAIN = "discovery"
|
|
||||||
|
|
||||||
SCAN_INTERVAL = timedelta(seconds=300)
|
|
||||||
SERVICE_APPLE_TV = "apple_tv"
|
|
||||||
SERVICE_DAIKIN = "daikin"
|
|
||||||
SERVICE_DLNA_DMR = "dlna_dmr"
|
|
||||||
SERVICE_ENIGMA2 = "enigma2"
|
|
||||||
SERVICE_HASS_IOS_APP = "hass_ios"
|
|
||||||
SERVICE_HASSIO = "hassio"
|
|
||||||
SERVICE_HEOS = "heos"
|
|
||||||
SERVICE_KONNECTED = "konnected"
|
|
||||||
SERVICE_MOBILE_APP = "hass_mobile_app"
|
|
||||||
SERVICE_NETGEAR = "netgear_router"
|
|
||||||
SERVICE_OCTOPRINT = "octoprint"
|
|
||||||
SERVICE_SABNZBD = "sabnzbd"
|
|
||||||
SERVICE_SAMSUNG_PRINTER = "samsung_printer"
|
|
||||||
SERVICE_TELLDUSLIVE = "tellstick"
|
|
||||||
SERVICE_YEELIGHT = "yeelight"
|
|
||||||
SERVICE_WEMO = "belkin_wemo"
|
|
||||||
SERVICE_XIAOMI_GW = "xiaomi_gw"
|
|
||||||
|
|
||||||
# These have custom protocols
|
|
||||||
CONFIG_ENTRY_HANDLERS = {
|
|
||||||
SERVICE_TELLDUSLIVE: "tellduslive",
|
|
||||||
"logitech_mediaserver": "squeezebox",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class ServiceDetails(NamedTuple):
|
|
||||||
"""Store service details."""
|
|
||||||
|
|
||||||
component: str
|
|
||||||
platform: str | None
|
|
||||||
|
|
||||||
|
|
||||||
# These have no config flows
|
|
||||||
SERVICE_HANDLERS = {
|
|
||||||
SERVICE_ENIGMA2: ServiceDetails("media_player", "enigma2"),
|
|
||||||
"yamaha": ServiceDetails("media_player", "yamaha"),
|
|
||||||
"bluesound": ServiceDetails("media_player", "bluesound"),
|
|
||||||
}
|
|
||||||
|
|
||||||
OPTIONAL_SERVICE_HANDLERS: dict[str, tuple[str, str | None]] = {}
|
|
||||||
|
|
||||||
MIGRATED_SERVICE_HANDLERS = [
|
|
||||||
SERVICE_APPLE_TV,
|
|
||||||
"axis",
|
|
||||||
"bose_soundtouch",
|
|
||||||
"deconz",
|
|
||||||
SERVICE_DAIKIN,
|
|
||||||
"denonavr",
|
|
||||||
SERVICE_DLNA_DMR,
|
|
||||||
"esphome",
|
|
||||||
"google_cast",
|
|
||||||
SERVICE_HASS_IOS_APP,
|
|
||||||
SERVICE_HASSIO,
|
|
||||||
SERVICE_HEOS,
|
|
||||||
"harmony",
|
|
||||||
"homekit",
|
|
||||||
"ikea_tradfri",
|
|
||||||
"kodi",
|
|
||||||
SERVICE_KONNECTED,
|
|
||||||
SERVICE_MOBILE_APP,
|
|
||||||
SERVICE_NETGEAR,
|
|
||||||
SERVICE_OCTOPRINT,
|
|
||||||
"openhome",
|
|
||||||
"philips_hue",
|
|
||||||
SERVICE_SAMSUNG_PRINTER,
|
|
||||||
"sonos",
|
|
||||||
"songpal",
|
|
||||||
SERVICE_WEMO,
|
|
||||||
SERVICE_XIAOMI_GW,
|
|
||||||
"volumio",
|
|
||||||
SERVICE_YEELIGHT,
|
|
||||||
SERVICE_SABNZBD,
|
|
||||||
"nanoleaf_aurora",
|
|
||||||
"lg_smart_device",
|
|
||||||
]
|
|
||||||
|
|
||||||
DEFAULT_ENABLED = (
|
|
||||||
list(CONFIG_ENTRY_HANDLERS) + list(SERVICE_HANDLERS) + MIGRATED_SERVICE_HANDLERS
|
|
||||||
)
|
|
||||||
DEFAULT_DISABLED = list(OPTIONAL_SERVICE_HANDLERS) + MIGRATED_SERVICE_HANDLERS
|
|
||||||
|
|
||||||
CONF_IGNORE = "ignore"
|
|
||||||
CONF_ENABLE = "enable"
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
|
||||||
{
|
|
||||||
vol.Optional(DOMAIN): vol.Schema(
|
|
||||||
{
|
|
||||||
vol.Optional(CONF_IGNORE, default=[]): vol.All(
|
|
||||||
cv.ensure_list, [vol.In(DEFAULT_ENABLED)]
|
|
||||||
),
|
|
||||||
vol.Optional(CONF_ENABLE, default=[]): vol.All(
|
|
||||||
cv.ensure_list, [vol.In(DEFAULT_DISABLED + DEFAULT_ENABLED)]
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
extra=vol.ALLOW_EXTRA,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|
||||||
"""Start a discovery service."""
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
netdisco = NetworkDiscovery()
|
|
||||||
already_discovered = set()
|
|
||||||
|
|
||||||
if DOMAIN in config:
|
|
||||||
# Platforms ignore by config
|
|
||||||
ignored_platforms = config[DOMAIN][CONF_IGNORE]
|
|
||||||
|
|
||||||
# Optional platforms enabled by config
|
|
||||||
enabled_platforms = config[DOMAIN][CONF_ENABLE]
|
|
||||||
else:
|
|
||||||
ignored_platforms = []
|
|
||||||
enabled_platforms = []
|
|
||||||
|
|
||||||
for platform in enabled_platforms:
|
|
||||||
if platform in DEFAULT_ENABLED:
|
|
||||||
logger.warning(
|
|
||||||
(
|
|
||||||
"Please remove %s from your discovery.enable configuration "
|
|
||||||
"as it is now enabled by default"
|
|
||||||
),
|
|
||||||
platform,
|
|
||||||
)
|
|
||||||
|
|
||||||
zeroconf_instance = await zeroconf.async_get_instance(hass)
|
|
||||||
# Do not scan for types that have already been converted
|
|
||||||
# as it will generate excess network traffic for questions
|
|
||||||
# the zeroconf instance already knows the answers
|
|
||||||
zeroconf_types = list(await async_get_zeroconf(hass))
|
|
||||||
|
|
||||||
async def new_service_found(service, info):
|
|
||||||
"""Handle a new service if one is found."""
|
|
||||||
if service in MIGRATED_SERVICE_HANDLERS:
|
|
||||||
return
|
|
||||||
|
|
||||||
if service in ignored_platforms:
|
|
||||||
logger.info("Ignoring service: %s %s", service, info)
|
|
||||||
return
|
|
||||||
|
|
||||||
discovery_hash = json.dumps([service, info], sort_keys=True)
|
|
||||||
if discovery_hash in already_discovered:
|
|
||||||
logger.debug("Already discovered service %s %s.", service, info)
|
|
||||||
return
|
|
||||||
|
|
||||||
already_discovered.add(discovery_hash)
|
|
||||||
|
|
||||||
if service in CONFIG_ENTRY_HANDLERS:
|
|
||||||
discovery_flow.async_create_flow(
|
|
||||||
hass,
|
|
||||||
CONFIG_ENTRY_HANDLERS[service],
|
|
||||||
context={"source": config_entries.SOURCE_DISCOVERY},
|
|
||||||
data=info,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
service_details = SERVICE_HANDLERS.get(service)
|
|
||||||
|
|
||||||
if not service_details and service in enabled_platforms:
|
|
||||||
service_details = OPTIONAL_SERVICE_HANDLERS[service]
|
|
||||||
|
|
||||||
# We do not know how to handle this service.
|
|
||||||
if not service_details:
|
|
||||||
logger.debug("Unknown service discovered: %s %s", service, info)
|
|
||||||
return
|
|
||||||
|
|
||||||
logger.info("Found new service: %s %s", service, info)
|
|
||||||
|
|
||||||
if service_details.platform is None:
|
|
||||||
await async_discover(hass, service, info, service_details.component, config)
|
|
||||||
else:
|
|
||||||
await async_load_platform(
|
|
||||||
hass, service_details.component, service_details.platform, info, config
|
|
||||||
)
|
|
||||||
|
|
||||||
async def scan_devices(now: datetime) -> None:
|
|
||||||
"""Scan for devices."""
|
|
||||||
try:
|
|
||||||
results = await hass.async_add_executor_job(
|
|
||||||
_discover, netdisco, zeroconf_instance, zeroconf_types
|
|
||||||
)
|
|
||||||
|
|
||||||
for result in results:
|
|
||||||
hass.async_create_task(new_service_found(*result))
|
|
||||||
except OSError:
|
|
||||||
logger.error("Network is unreachable")
|
|
||||||
|
|
||||||
async_track_point_in_utc_time(
|
|
||||||
hass, scan_devices_job, dt_util.utcnow() + SCAN_INTERVAL
|
|
||||||
)
|
|
||||||
|
|
||||||
@callback
|
|
||||||
def schedule_first(event: Event) -> None:
|
|
||||||
"""Schedule the first discovery when Home Assistant starts up."""
|
|
||||||
async_track_point_in_utc_time(hass, scan_devices_job, dt_util.utcnow())
|
|
||||||
|
|
||||||
scan_devices_job = HassJob(scan_devices, cancel_on_shutdown=True)
|
|
||||||
|
|
||||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, schedule_first)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def _discover(netdisco, zeroconf_instance, zeroconf_types):
|
|
||||||
"""Discover devices."""
|
|
||||||
results = []
|
|
||||||
try:
|
|
||||||
netdisco.scan(
|
|
||||||
zeroconf_instance=zeroconf_instance, suppress_mdns_types=zeroconf_types
|
|
||||||
)
|
|
||||||
|
|
||||||
for disc in netdisco.discover():
|
|
||||||
for service in netdisco.get_info(disc):
|
|
||||||
results.append((disc, service))
|
|
||||||
|
|
||||||
finally:
|
|
||||||
netdisco.stop()
|
|
||||||
|
|
||||||
return results
|
|
@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"domain": "discovery",
|
|
||||||
"name": "Discovery",
|
|
||||||
"after_dependencies": ["zeroconf"],
|
|
||||||
"codeowners": ["@home-assistant/core"],
|
|
||||||
"documentation": "https://www.home-assistant.io/integrations/discovery",
|
|
||||||
"integration_type": "system",
|
|
||||||
"loggers": ["netdisco"],
|
|
||||||
"quality_scale": "internal",
|
|
||||||
"requirements": ["netdisco==3.0.0"]
|
|
||||||
}
|
|
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"domain": "xiaomi_aqara",
|
"domain": "xiaomi_aqara",
|
||||||
"name": "Xiaomi Gateway (Aqara)",
|
"name": "Xiaomi Gateway (Aqara)",
|
||||||
"after_dependencies": ["discovery"],
|
|
||||||
"codeowners": ["@danielhiversen", "@syssi"],
|
"codeowners": ["@danielhiversen", "@syssi"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/xiaomi_aqara",
|
"documentation": "https://www.home-assistant.io/integrations/xiaomi_aqara",
|
||||||
|
@ -1240,9 +1240,6 @@ nessclient==0.10.0
|
|||||||
# homeassistant.components.netdata
|
# homeassistant.components.netdata
|
||||||
netdata==1.1.0
|
netdata==1.1.0
|
||||||
|
|
||||||
# homeassistant.components.discovery
|
|
||||||
netdisco==3.0.0
|
|
||||||
|
|
||||||
# homeassistant.components.nmap_tracker
|
# homeassistant.components.nmap_tracker
|
||||||
netmap==0.7.0.2
|
netmap==0.7.0.2
|
||||||
|
|
||||||
|
@ -951,9 +951,6 @@ ndms2-client==0.1.2
|
|||||||
# homeassistant.components.ness_alarm
|
# homeassistant.components.ness_alarm
|
||||||
nessclient==0.10.0
|
nessclient==0.10.0
|
||||||
|
|
||||||
# homeassistant.components.discovery
|
|
||||||
netdisco==3.0.0
|
|
||||||
|
|
||||||
# homeassistant.components.nmap_tracker
|
# homeassistant.components.nmap_tracker
|
||||||
netmap==0.7.0.2
|
netmap==0.7.0.2
|
||||||
|
|
||||||
|
@ -62,7 +62,6 @@ NO_IOT_CLASS = [
|
|||||||
"device_automation",
|
"device_automation",
|
||||||
"device_tracker",
|
"device_tracker",
|
||||||
"diagnostics",
|
"diagnostics",
|
||||||
"discovery",
|
|
||||||
"downloader",
|
"downloader",
|
||||||
"ffmpeg",
|
"ffmpeg",
|
||||||
"file_upload",
|
"file_upload",
|
||||||
|
@ -1 +0,0 @@
|
|||||||
"""Tests for the discovery component."""
|
|
@ -1,105 +0,0 @@
|
|||||||
"""The tests for the discovery component."""
|
|
||||||
from unittest.mock import MagicMock, patch
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from homeassistant import config_entries
|
|
||||||
from homeassistant.bootstrap import async_setup_component
|
|
||||||
from homeassistant.components import discovery
|
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED
|
|
||||||
from homeassistant.core import HomeAssistant
|
|
||||||
from homeassistant.util.dt import utcnow
|
|
||||||
|
|
||||||
from tests.common import async_fire_time_changed, mock_coro
|
|
||||||
|
|
||||||
# One might consider to "mock" services, but it's easy enough to just use
|
|
||||||
# what is already available.
|
|
||||||
SERVICE = "yamaha"
|
|
||||||
SERVICE_COMPONENT = "media_player"
|
|
||||||
|
|
||||||
SERVICE_INFO = {"key": "value"} # Can be anything
|
|
||||||
|
|
||||||
UNKNOWN_SERVICE = "this_service_will_never_be_supported"
|
|
||||||
|
|
||||||
BASE_CONFIG = {discovery.DOMAIN: {"ignore": [], "enable": []}}
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
|
||||||
def netdisco_mock():
|
|
||||||
"""Mock netdisco."""
|
|
||||||
with patch.dict("sys.modules", {"netdisco.discovery": MagicMock()}):
|
|
||||||
yield
|
|
||||||
|
|
||||||
|
|
||||||
async def mock_discovery(hass, discoveries, config=BASE_CONFIG):
|
|
||||||
"""Mock discoveries."""
|
|
||||||
with patch("homeassistant.components.zeroconf.async_get_instance"), patch(
|
|
||||||
"homeassistant.components.zeroconf.async_setup", return_value=True
|
|
||||||
), patch.object(discovery, "_discover", discoveries), patch(
|
|
||||||
"homeassistant.components.discovery.async_discover"
|
|
||||||
) as mock_discover, patch(
|
|
||||||
"homeassistant.components.discovery.async_load_platform",
|
|
||||||
return_value=mock_coro(),
|
|
||||||
) as mock_platform:
|
|
||||||
assert await async_setup_component(hass, "discovery", config)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
await hass.async_start()
|
|
||||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
async_fire_time_changed(hass, utcnow())
|
|
||||||
# Work around an issue where our loop.call_soon not get caught
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
return mock_discover, mock_platform
|
|
||||||
|
|
||||||
|
|
||||||
async def test_unknown_service(hass: HomeAssistant) -> None:
|
|
||||||
"""Test that unknown service is ignored."""
|
|
||||||
|
|
||||||
def discover(netdisco, zeroconf_instance, suppress_mdns_types):
|
|
||||||
"""Fake discovery."""
|
|
||||||
return [("this_service_will_never_be_supported", {"info": "some"})]
|
|
||||||
|
|
||||||
mock_discover, mock_platform = await mock_discovery(hass, discover)
|
|
||||||
|
|
||||||
assert not mock_discover.called
|
|
||||||
assert not mock_platform.called
|
|
||||||
|
|
||||||
|
|
||||||
async def test_load_platform(hass: HomeAssistant) -> None:
|
|
||||||
"""Test load a platform."""
|
|
||||||
|
|
||||||
def discover(netdisco, zeroconf_instance, suppress_mdns_types):
|
|
||||||
"""Fake discovery."""
|
|
||||||
return [(SERVICE, SERVICE_INFO)]
|
|
||||||
|
|
||||||
mock_discover, mock_platform = await mock_discovery(hass, discover)
|
|
||||||
|
|
||||||
assert not mock_discover.called
|
|
||||||
assert mock_platform.called
|
|
||||||
mock_platform.assert_called_with(
|
|
||||||
hass, SERVICE_COMPONENT, SERVICE, SERVICE_INFO, BASE_CONFIG
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def test_discover_config_flow(hass: HomeAssistant) -> None:
|
|
||||||
"""Test discovery triggering a config flow."""
|
|
||||||
discovery_info = {"hello": "world"}
|
|
||||||
|
|
||||||
def discover(netdisco, zeroconf_instance, suppress_mdns_types):
|
|
||||||
"""Fake discovery."""
|
|
||||||
return [("mock-service", discovery_info)]
|
|
||||||
|
|
||||||
with patch.dict(
|
|
||||||
discovery.CONFIG_ENTRY_HANDLERS, {"mock-service": "mock-component"}
|
|
||||||
), patch(
|
|
||||||
"homeassistant.config_entries.ConfigEntriesFlowManager.async_init"
|
|
||||||
) as m_init:
|
|
||||||
await mock_discovery(hass, discover)
|
|
||||||
|
|
||||||
assert len(m_init.mock_calls) == 1
|
|
||||||
args, kwargs = m_init.mock_calls[0][1:]
|
|
||||||
assert args == ("mock-component",)
|
|
||||||
assert kwargs["context"]["source"] == config_entries.SOURCE_DISCOVERY
|
|
||||||
assert kwargs["data"] == discovery_info
|
|
Loading…
x
Reference in New Issue
Block a user