Skip to content

Commit

Permalink
Split googlehome to a component with device tracker platform (home-as…
Browse files Browse the repository at this point in the history
…sistant#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
  • Loading branch information
ludeeus authored and MartinHjelmare committed Jan 31, 2019
1 parent e20c2aa commit 632b204
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 79 deletions.
4 changes: 3 additions & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
139 changes: 65 additions & 74 deletions homeassistant/components/device_tracker/googlehome.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,91 +5,82 @@
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.last_results = {}

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):
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

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

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)
86 changes: 86 additions & 0 deletions homeassistant/components/googlehome.py
Original file line number Diff line number Diff line change
@@ -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
6 changes: 3 additions & 3 deletions requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down

0 comments on commit 632b204

Please sign in to comment.