core/homeassistant/components/discovery/__init__.py

233 lines
6.9 KiB
Python
Raw Normal View History

"""
Starts a service to scan in intervals for new devices.
Will emit EVENT_PLATFORM_DISCOVERED whenever a new service has been discovered.
Knows which components handle certain types, will make sure they are
loaded before the EVENT_PLATFORM_DISCOVERED is fired.
"""
from datetime import timedelta
import json
import logging
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 callback
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
import homeassistant.util.dt as dt_util
2019-07-31 19:25:30 +00:00
DOMAIN = "discovery"
SCAN_INTERVAL = timedelta(seconds=300)
2019-07-31 19:25:30 +00:00
SERVICE_APPLE_TV = "apple_tv"
SERVICE_DAIKIN = "daikin"
SERVICE_DLNA_DMR = "dlna_dmr"
SERVICE_ENIGMA2 = "enigma2"
SERVICE_FREEBOX = "freebox"
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_WINK = "wink"
SERVICE_XIAOMI_GW = "xiaomi_gw"
CONFIG_ENTRY_HANDLERS = {
2019-07-31 19:25:30 +00:00
SERVICE_DAIKIN: "daikin",
SERVICE_TELLDUSLIVE: "tellduslive",
Squeezebox config flow (#35669) * Squeezebox add config flow and player discovery * Fixes to config flow * Unavailable player detection and recovery * Improved error message for auth failure * Testing for squeezebox config flow * Import configuration.yaml * Support for discovery integration * Internal server discovery * Fix bug restoring previously detected squeezebox player * Tests for user and edit steps in config flow * Tests for import config flow * Additional config flow tests and fixes * Linter fixes * Check that players are found before iterating them * Remove noisy logger message * Update requirements_all after rebase * Use asyncio.Event in discovery task * Use common keys in strings.json * Bump pysqueezebox to v0.2.2 for fixed server discovery using python3.7 * Bump pysqueezebox version to v0.2.3 * Don't trap AbortFlow exception Co-authored-by: J. Nick Koston <nick@koston.org> * Refactor validate_input * Update squeezebox tests * Build data flow schema using function * Fix linter error * Updated en.json * Update homeassistant/components/squeezebox/media_player.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/squeezebox/media_player.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/squeezebox/media_player.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/squeezebox/media_player.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update .coveragerc for squeezebox config flow test * Mock TIMEOUT for faster testing * More schema de-duplication and testing improvements * Apply suggestions from code review Co-authored-by: J. Nick Koston <nick@koston.org> * Testing and config flow improvements * Remove unused exceptions * Remove deprecated logger message * Update homeassistant/components/squeezebox/media_player.py Co-authored-by: J. Nick Koston <nick@koston.org> * Implement suggestions from code review * Add async_unload_entry * Use MockConfigEntry in squeezebox tests * Remove unnecessary config schema * Stop server discovery task when last config entry unloaded * Improvements to async_unload_entry * Fix bug in _discovery arguments * Do not await server discovery in async_setup_entry * Do not await start server discovery in async_setup * Do not start server discovery from async_setup_entry until homeassistant running * Re-detect players when server removed and re-added without restart * Use entry.entry_id instead of unique_id * Update unittests to avoid patching homeassistant code Co-authored-by: J. Nick Koston <nick@koston.org>
2020-06-22 14:29:01 +00:00
"logitech_mediaserver": "squeezebox",
}
SERVICE_HANDLERS = {
2019-07-31 19:25:30 +00:00
SERVICE_MOBILE_APP: ("mobile_app", None),
SERVICE_HASS_IOS_APP: ("ios", None),
SERVICE_NETGEAR: ("device_tracker", None),
SERVICE_HASSIO: ("hassio", None),
SERVICE_APPLE_TV: ("apple_tv", None),
SERVICE_ENIGMA2: ("media_player", "enigma2"),
SERVICE_WINK: ("wink", None),
SERVICE_SABNZBD: ("sabnzbd", None),
SERVICE_SAMSUNG_PRINTER: ("sensor", None),
2019-07-31 19:25:30 +00:00
SERVICE_KONNECTED: ("konnected", None),
SERVICE_OCTOPRINT: ("octoprint", None),
SERVICE_FREEBOX: ("freebox", None),
"yamaha": ("media_player", "yamaha"),
"frontier_silicon": ("media_player", "frontier_silicon"),
"openhome": ("media_player", "openhome"),
"bose_soundtouch": ("media_player", "soundtouch"),
"bluesound": ("media_player", "bluesound"),
"lg_smart_device": ("media_player", "lg_soundbar"),
"nanoleaf_aurora": ("light", "nanoleaf"),
}
2019-07-31 19:25:30 +00:00
OPTIONAL_SERVICE_HANDLERS = {SERVICE_DLNA_DMR: ("media_player", "dlna_dmr")}
MIGRATED_SERVICE_HANDLERS = [
2019-07-31 19:25:30 +00:00
"axis",
"deconz",
"denonavr",
2019-07-31 19:25:30 +00:00
"esphome",
"google_cast",
SERVICE_HEOS,
"harmony",
2019-07-31 19:25:30 +00:00
"homekit",
"ikea_tradfri",
"kodi",
2019-07-31 19:25:30 +00:00
"philips_hue",
"sonos",
"songpal",
SERVICE_WEMO,
Add Xiaomi Aqara Config Flow (#35595) * Xiaomi Aqara Config Flow * Xiaomi Aqara Config Flow * Xiaomi Aqara Config Flow * Xiaomi Aqara Config Flow * Xiaomi Aqara Config Flow First tested and working version * Remove depricated discovery * Add Xiaomi Aqara Config Flow * Add Xiaomi Aqara tests * Update .coveragerc * Update requirements_test_all.txt * fix spelling mistake * fix select scheme * fix wrong conflict resolve * add IP to zeroconf discovery title * black styling * add getmac requirement Co-authored-by: Maciej Bieniek <bieniu@users.noreply.github.com> * add getmac * add getmac * Clean up * Update homeassistant/components/xiaomi_aqara/__init__.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/xiaomi_aqara/__init__.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/xiaomi_aqara/__init__.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/xiaomi_aqara/__init__.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/xiaomi_aqara/__init__.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * resolve data storage * move format_mac down * Remove discovery_retry from config flow * remove unused strings * fix styling * fix black styling * fix tests * remove mac connection This is needed to prevent a conflict with the Xiaomi Miio integration that I discovered during testing. * fix flake8 * remove getmac depandance * check for inavlid_interface + test * Validate gateway key * add invalid key tests * Fix spelling * Only set up sensors if no key Co-authored-by: Maciej Bieniek <bieniu@users.noreply.github.com> Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2020-06-22 09:54:17 +00:00
SERVICE_XIAOMI_GW,
2020-07-27 07:19:19 +00:00
"volumio",
2020-08-31 14:40:56 +00:00
SERVICE_YEELIGHT,
]
2019-07-31 19:25:30 +00:00
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, config):
2016-03-08 16:55:57 +00:00
"""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)
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:
2018-12-18 18:32:42 +00:00
logger.debug("Already discovered service %s %s.", service, info)
return
already_discovered.add(discovery_hash)
if service in CONFIG_ENTRY_HANDLERS:
await hass.config_entries.flow.async_init(
CONFIG_ENTRY_HANDLERS[service],
2019-07-31 19:25:30 +00:00
context={"source": config_entries.SOURCE_DISCOVERY},
data=info,
)
return
comp_plat = SERVICE_HANDLERS.get(service)
if not comp_plat and service in enabled_platforms:
comp_plat = OPTIONAL_SERVICE_HANDLERS[service]
# We do not know how to handle this service.
if not comp_plat:
logger.debug("Unknown service discovered: %s %s", service, info)
return
logger.info("Found new service: %s %s", service, info)
component, platform = comp_plat
if platform is None:
await async_discover(hass, service, info, component, config)
else:
2019-07-31 19:25:30 +00:00
await async_load_platform(hass, component, platform, info, config)
async def scan_devices(now):
"""Scan for devices."""
try:
results = await hass.async_add_executor_job(
_discover, netdisco, zeroconf_instance
)
for result in results:
hass.async_create_task(new_service_found(*result))
except OSError:
logger.error("Network is unreachable")
2018-12-18 18:32:42 +00:00
async_track_point_in_utc_time(
2019-07-31 19:25:30 +00:00
hass, scan_devices, dt_util.utcnow() + SCAN_INTERVAL
)
@callback
def schedule_first(event):
"""Schedule the first discovery when Home Assistant starts up."""
async_track_point_in_utc_time(hass, scan_devices, dt_util.utcnow())
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, schedule_first)
return True
def _discover(netdisco, zeroconf_instance):
"""Discover devices."""
results = []
try:
netdisco.scan(zeroconf_instance=zeroconf_instance)
for disc in netdisco.discover():
for service in netdisco.get_info(disc):
results.append((disc, service))
finally:
netdisco.stop()
return results