2019-02-13 20:21:14 +00:00
|
|
|
"""Support for exposing Home Assistant via Zeroconf."""
|
2016-04-10 22:34:04 +00:00
|
|
|
import logging
|
|
|
|
|
2019-05-21 22:36:26 +00:00
|
|
|
import ipaddress
|
2016-09-30 02:02:22 +00:00
|
|
|
import voluptuous as vol
|
|
|
|
|
2019-05-21 22:36:26 +00:00
|
|
|
from aiozeroconf import (
|
|
|
|
ServiceBrowser, ServiceInfo, ServiceStateChange, Zeroconf)
|
|
|
|
|
2016-04-10 23:02:07 +00:00
|
|
|
from homeassistant.const import (EVENT_HOMEASSISTANT_STOP, __version__)
|
2019-05-21 22:36:26 +00:00
|
|
|
from homeassistant.generated import zeroconf as zeroconf_manifest
|
2016-04-10 22:34:04 +00:00
|
|
|
|
2016-09-30 02:02:22 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
2016-04-10 22:34:04 +00:00
|
|
|
|
2016-09-30 02:02:22 +00:00
|
|
|
DOMAIN = 'zeroconf'
|
2016-04-10 23:09:52 +00:00
|
|
|
|
2019-05-21 22:36:26 +00:00
|
|
|
ATTR_HOST = 'host'
|
|
|
|
ATTR_PORT = 'port'
|
|
|
|
ATTR_HOSTNAME = 'hostname'
|
|
|
|
ATTR_TYPE = 'type'
|
|
|
|
ATTR_NAME = 'name'
|
|
|
|
ATTR_PROPERTIES = 'properties'
|
2016-04-10 22:34:04 +00:00
|
|
|
|
2016-09-30 02:02:22 +00:00
|
|
|
ZEROCONF_TYPE = '_home-assistant._tcp.local.'
|
2016-04-10 22:34:04 +00:00
|
|
|
|
2016-09-30 02:02:22 +00:00
|
|
|
CONFIG_SCHEMA = vol.Schema({
|
|
|
|
DOMAIN: vol.Schema({}),
|
|
|
|
}, extra=vol.ALLOW_EXTRA)
|
2016-04-10 22:34:04 +00:00
|
|
|
|
|
|
|
|
2019-05-14 03:58:13 +00:00
|
|
|
async def async_setup(hass, config):
|
2016-04-10 23:02:07 +00:00
|
|
|
"""Set up Zeroconf and make Home Assistant discoverable."""
|
2016-09-30 02:02:22 +00:00
|
|
|
zeroconf_name = '{}.{}'.format(hass.config.location_name, ZEROCONF_TYPE)
|
2016-04-10 22:34:04 +00:00
|
|
|
|
2016-09-30 02:02:22 +00:00
|
|
|
params = {
|
|
|
|
'version': __version__,
|
|
|
|
'base_url': hass.config.api.base_url,
|
2019-03-11 02:55:36 +00:00
|
|
|
# always needs authentication
|
|
|
|
'requires_api_password': True,
|
2016-09-30 02:02:22 +00:00
|
|
|
}
|
2016-04-10 22:34:04 +00:00
|
|
|
|
2019-05-16 19:04:20 +00:00
|
|
|
info = ServiceInfo(ZEROCONF_TYPE, zeroconf_name,
|
|
|
|
port=hass.http.server_port, properties=params)
|
|
|
|
|
|
|
|
zeroconf = Zeroconf(hass.loop)
|
2017-01-03 20:39:42 +00:00
|
|
|
|
2019-05-14 03:58:13 +00:00
|
|
|
await zeroconf.register_service(info)
|
2016-04-10 22:34:04 +00:00
|
|
|
|
2019-05-21 22:36:26 +00:00
|
|
|
async def new_service(service_type, name):
|
|
|
|
"""Signal new service discovered."""
|
|
|
|
service_info = await zeroconf.get_service_info(service_type, name)
|
|
|
|
info = info_from_service(service_info)
|
|
|
|
_LOGGER.debug("Discovered new device %s %s", name, info)
|
|
|
|
|
|
|
|
for domain in zeroconf_manifest.SERVICE_TYPES[service_type]:
|
2019-05-27 02:48:27 +00:00
|
|
|
await hass.config_entries.flow.async_init(
|
|
|
|
domain, context={'source': DOMAIN}, data=info
|
2019-05-21 22:36:26 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
def service_update(_, service_type, name, state_change):
|
|
|
|
"""Service state changed."""
|
|
|
|
if state_change is ServiceStateChange.Added:
|
|
|
|
hass.async_create_task(new_service(service_type, name))
|
|
|
|
|
|
|
|
for service in zeroconf_manifest.SERVICE_TYPES:
|
|
|
|
ServiceBrowser(zeroconf, service, handlers=[service_update])
|
|
|
|
|
|
|
|
async def stop_zeroconf(_):
|
2016-04-10 23:02:07 +00:00
|
|
|
"""Stop Zeroconf."""
|
2019-05-14 03:58:13 +00:00
|
|
|
await zeroconf.unregister_service(info)
|
|
|
|
await zeroconf.close()
|
2016-04-10 22:34:04 +00:00
|
|
|
|
2019-05-14 03:58:13 +00:00
|
|
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_zeroconf)
|
2016-04-10 22:34:04 +00:00
|
|
|
|
2016-04-10 23:02:07 +00:00
|
|
|
return True
|
2019-05-21 22:36:26 +00:00
|
|
|
|
|
|
|
|
|
|
|
def info_from_service(service):
|
|
|
|
"""Return prepared info from mDNS entries."""
|
|
|
|
properties = {}
|
|
|
|
|
|
|
|
for key, value in service.properties.items():
|
|
|
|
try:
|
|
|
|
if isinstance(value, bytes):
|
|
|
|
value = value.decode('utf-8')
|
|
|
|
properties[key.decode('utf-8')] = value
|
|
|
|
except UnicodeDecodeError:
|
|
|
|
_LOGGER.warning("Unicode decode error on %s: %s", key, value)
|
|
|
|
|
|
|
|
address = service.address or service.address6
|
|
|
|
|
|
|
|
info = {
|
|
|
|
ATTR_HOST: str(ipaddress.ip_address(address)),
|
|
|
|
ATTR_PORT: service.port,
|
|
|
|
ATTR_HOSTNAME: service.server,
|
|
|
|
ATTR_TYPE: service.type,
|
|
|
|
ATTR_NAME: service.name,
|
|
|
|
ATTR_PROPERTIES: properties,
|
|
|
|
}
|
|
|
|
|
|
|
|
return info
|