From 632b2042e4e21bfefd1bcdfc092367ab866ea64c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Thu, 31 Jan 2019 21:16:31 +0100 Subject: [PATCH] Split googlehome to a component with device tracker platform (#19971) * Add component for googlehome * Add missing name in CODEOWNERS * Linting issues * googledevices version bump * Use NAME from component instead of DOMAIN * Cleaner handling of accepted devices in for loop * Fixes one linting issue * Validate device_types * Fixes one linting issue * Fixes linting issue * Revert 0abb642 and import DOMAIN as GOOGLEHOME_DOMAIN * Return false if discovery_info is None * Combine if's in for loop * Use async_load_platfrom * Fix line length * Add error message to user * Shorter log message * error -> warning, remove period * Update .coveragerc * Move to correct place --- .coveragerc | 4 +- CODEOWNERS | 5 +- .../components/device_tracker/googlehome.py | 135 ++++++++---------- homeassistant/components/googlehome.py | 86 +++++++++++ requirements_all.txt | 6 +- 5 files changed, 159 insertions(+), 77 deletions(-) create mode 100644 homeassistant/components/googlehome.py diff --git a/.coveragerc b/.coveragerc index f1ff7715580..722f74a0b6a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -151,6 +151,9 @@ omit = homeassistant/components/google.py homeassistant/components/*/google.py + homeassistant/components/googlehome.py + homeassistant/components/*/googlehome.py + homeassistant/components/greeneye_monitor.py homeassistant/components/sensor/greeneye_monitor.py @@ -553,7 +556,6 @@ omit = homeassistant/components/device_tracker/ddwrt.py homeassistant/components/device_tracker/fritz.py homeassistant/components/device_tracker/google_maps.py - homeassistant/components/device_tracker/googlehome.py homeassistant/components/device_tracker/hitron_coda.py homeassistant/components/device_tracker/huawei_router.py homeassistant/components/device_tracker/icloud.py diff --git a/CODEOWNERS b/CODEOWNERS index 98eaca90076..a0d67c6191d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -62,7 +62,6 @@ homeassistant/components/cover/group.py @cdce8p homeassistant/components/cover/template.py @PhracturedBlue homeassistant/components/device_tracker/asuswrt.py @kennedyshead homeassistant/components/device_tracker/automatic.py @armills -homeassistant/components/device_tracker/googlehome.py @ludeeus homeassistant/components/device_tracker/huawei_router.py @abmantis homeassistant/components/device_tracker/quantum_gateway.py @cisasteelersfan homeassistant/components/device_tracker/tile.py @bachya @@ -188,6 +187,10 @@ homeassistant/components/eight_sleep.py @mezz64 homeassistant/components/*/eight_sleep.py @mezz64 homeassistant/components/esphome/*.py @OttoWinter +# G +homeassistant/components/googlehome.py @ludeeus +homeassistant/components/*/googlehome.py @ludeeus + # H homeassistant/components/hive.py @Rendili @KJonline homeassistant/components/*/hive.py @Rendili @KJonline diff --git a/homeassistant/components/device_tracker/googlehome.py b/homeassistant/components/device_tracker/googlehome.py index daa36d1d2c7..ba6d708295a 100644 --- a/homeassistant/components/device_tracker/googlehome.py +++ b/homeassistant/components/device_tracker/googlehome.py @@ -5,91 +5,82 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.googlehome/ """ import logging +from datetime import timedelta -import voluptuous as vol +from homeassistant.components.device_tracker import DeviceScanner +from homeassistant.components.googlehome import ( + CLIENT, DOMAIN as GOOGLEHOME_DOMAIN, NAME) +from homeassistant.helpers.event import async_track_time_interval +from homeassistant.util import slugify -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.components.device_tracker import ( - DOMAIN, PLATFORM_SCHEMA, DeviceScanner) -from homeassistant.const import CONF_HOST +DEPENDENCIES = ['googlehome'] -REQUIREMENTS = ['ghlocalapi==0.3.5'] +DEFAULT_SCAN_INTERVAL = timedelta(seconds=10) _LOGGER = logging.getLogger(__name__) -CONF_RSSI_THRESHOLD = 'rssi_threshold' -PLATFORM_SCHEMA = vol.All( - PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_RSSI_THRESHOLD, default=-70): vol.Coerce(int), - })) - - -async def async_get_scanner(hass, config): - """Validate the configuration and return an Google Home scanner.""" - scanner = GoogleHomeDeviceScanner(hass, config[DOMAIN]) - await scanner.async_connect() - return scanner if scanner.success_init else None +async def async_setup_scanner(hass, config, async_see, discovery_info=None): + """Validate the configuration and return a Google Home scanner.""" + if discovery_info is None: + _LOGGER.warning( + "To use this you need to configure the 'googlehome' component") + return False + scanner = GoogleHomeDeviceScanner(hass, hass.data[CLIENT], + discovery_info, async_see) + return await scanner.async_init() class GoogleHomeDeviceScanner(DeviceScanner): """This class queries a Google Home unit.""" - def __init__(self, hass, config): + def __init__(self, hass, client, config, async_see): """Initialize the scanner.""" - from ghlocalapi.device_info import DeviceInfo - from ghlocalapi.bluetooth import Bluetooth + self.async_see = async_see + self.hass = hass + self.rssi = config['rssi_threshold'] + self.device_types = config['device_types'] + self.host = config['host'] + self.client = client - self.last_results = {} + async def async_init(self): + """Further initialize connection to Google Home.""" + await self.client.update_data(self.host) + data = self.hass.data[GOOGLEHOME_DOMAIN][self.host] + info = data.get('info', {}) + connected = bool(info) + if connected: + await self.async_update() + async_track_time_interval(self.hass, + self.async_update, + DEFAULT_SCAN_INTERVAL) + return connected - self.success_init = False - self._host = config[CONF_HOST] - self.rssi_threshold = config[CONF_RSSI_THRESHOLD] - - session = async_get_clientsession(hass) - self.deviceinfo = DeviceInfo(hass.loop, session, self._host) - self.scanner = Bluetooth(hass.loop, session, self._host) - - async def async_connect(self): - """Initialize connection to Google Home.""" - await self.deviceinfo.get_device_info() - data = self.deviceinfo.device_info - self.success_init = data is not None - - async def async_scan_devices(self): - """Scan for new devices and return a list with found device IDs.""" - await self.async_update_info() - return list(self.last_results.keys()) - - async def async_get_device_name(self, device): - """Return the name of the given device or None if we don't know.""" - if device not in self.last_results: - return None - return '{}_{}'.format(self._host, - self.last_results[device]['btle_mac_address']) - - async def get_extra_attributes(self, device): - """Return the extra attributes of the device.""" - return self.last_results[device] - - async def async_update_info(self): + async def async_update(self, now=None): """Ensure the information from Google Home is up to date.""" - _LOGGER.debug('Checking Devices...') - await self.scanner.scan_for_devices() - await self.scanner.get_scan_result() - ghname = self.deviceinfo.device_info['name'] - devices = {} - for device in self.scanner.devices: - if device['rssi'] > self.rssi_threshold: - uuid = '{}_{}'.format(self._host, device['mac_address']) - devices[uuid] = {} - devices[uuid]['rssi'] = device['rssi'] - devices[uuid]['btle_mac_address'] = device['mac_address'] - devices[uuid]['ghname'] = ghname - devices[uuid]['source_type'] = 'bluetooth' - if device['name']: - devices[uuid]['btle_name'] = device['name'] - await self.scanner.clear_scan_result() - self.last_results = devices + _LOGGER.debug('Checking Devices on %s', self.host) + await self.client.update_data(self.host) + data = self.hass.data[GOOGLEHOME_DOMAIN][self.host] + info = data.get('info') + bluetooth = data.get('bluetooth') + if info is None or bluetooth is None: + return + google_home_name = info.get('name', NAME) + + for device in bluetooth: + if (device['device_type'] not in + self.device_types or device['rssi'] < self.rssi): + continue + + name = "{} {}".format(self.host, device['mac_address']) + + attributes = {} + attributes['btle_mac_address'] = device['mac_address'] + attributes['ghname'] = google_home_name + attributes['rssi'] = device['rssi'] + attributes['source_type'] = 'bluetooth' + if device['name']: + attributes['name'] = device['name'] + + await self.async_see(dev_id=slugify(name), + attributes=attributes) diff --git a/homeassistant/components/googlehome.py b/homeassistant/components/googlehome.py new file mode 100644 index 00000000000..78bd2d7df3f --- /dev/null +++ b/homeassistant/components/googlehome.py @@ -0,0 +1,86 @@ +""" +Support Google Home units. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/googlehome/ +""" +import logging + +import asyncio +import voluptuous as vol +from homeassistant.const import CONF_DEVICES, CONF_HOST +from homeassistant.helpers import discovery +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +_LOGGER = logging.getLogger(__name__) + +REQUIREMENTS = ['googledevices==1.0.2'] + +DOMAIN = 'googlehome' +CLIENT = 'googlehome_client' + +NAME = 'GoogleHome' + +CONF_DEVICE_TYPES = 'device_types' +CONF_RSSI_THRESHOLD = 'rssi_threshold' + +DEVICE_TYPES = [1, 2, 3] +DEFAULT_RSSI_THRESHOLD = -70 + +DEVICE_CONFIG = vol.Schema({ + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_DEVICE_TYPES, + default=DEVICE_TYPES): vol.All(cv.ensure_list, + [vol.In(DEVICE_TYPES)]), + vol.Optional(CONF_RSSI_THRESHOLD, + default=DEFAULT_RSSI_THRESHOLD): vol.Coerce(int), +}) + + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_DEVICES): vol.All(cv.ensure_list, [DEVICE_CONFIG]), + }), +}, extra=vol.ALLOW_EXTRA) + + +async def async_setup(hass, config): + """Set up the Google Home component.""" + hass.data[DOMAIN] = {} + hass.data[CLIENT] = GoogleHomeClient(hass) + + for device in config[DOMAIN][CONF_DEVICES]: + hass.data[DOMAIN][device['host']] = {} + hass.async_create_task( + discovery.async_load_platform( + hass, 'device_tracker', DOMAIN, device, config)) + + return True + + +class GoogleHomeClient: + """Handle all communication with the Google Home unit.""" + + def __init__(self, hass): + """Initialize the Google Home Client.""" + self.hass = hass + self._connected = None + + async def update_data(self, host): + """Update data from Google Home.""" + from googledevices.api.connect import Cast + _LOGGER.debug("Updating Google Home data for %s", host) + session = async_get_clientsession(self.hass) + + device_info = await Cast(host, self.hass.loop, session).info() + device_info_data = await device_info.get_device_info() + self._connected = bool(device_info_data) + + bluetooth = await Cast(host, self.hass.loop, session).bluetooth() + await bluetooth.scan_for_devices() + await asyncio.sleep(5) + bluetooth_data = await bluetooth.get_scan_result() + + self.hass.data[DOMAIN][host]['info'] = device_info_data + self.hass.data[DOMAIN][host]['bluetooth'] = bluetooth_data diff --git a/requirements_all.txt b/requirements_all.txt index 2031eeaec19..5faf6f2c6d0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -456,9 +456,6 @@ geojson_client==0.3 # homeassistant.components.sensor.geo_rss_events georss_client==0.5 -# homeassistant.components.device_tracker.googlehome -ghlocalapi==0.3.5 - # homeassistant.components.sensor.gitter gitterpy==0.1.7 @@ -471,6 +468,9 @@ gntp==1.0.3 # homeassistant.components.google google-api-python-client==1.6.4 +# homeassistant.components.googlehome +googledevices==1.0.2 + # homeassistant.components.sensor.google_travel_time googlemaps==2.5.1