core/homeassistant/components/hue/bridge.py

144 lines
4.9 KiB
Python
Raw Normal View History

"""Code to handle a Hue bridge."""
import asyncio
import async_timeout
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.helpers import aiohttp_client, config_validation as cv
from .const import DOMAIN, LOGGER
from .errors import AuthenticationRequired, CannotConnect
SERVICE_HUE_SCENE = "hue_activate_scene"
ATTR_GROUP_NAME = "group_name"
ATTR_SCENE_NAME = "scene_name"
SCENE_SCHEMA = vol.Schema({
vol.Required(ATTR_GROUP_NAME): cv.string,
vol.Required(ATTR_SCENE_NAME): cv.string,
})
class HueBridge(object):
"""Manages a single Hue bridge."""
def __init__(self, hass, config_entry, allow_unreachable, allow_groups):
"""Initialize the system."""
self.config_entry = config_entry
self.hass = hass
self.allow_unreachable = allow_unreachable
self.allow_groups = allow_groups
self.available = True
self.api = None
async def async_setup(self, tries=0):
"""Set up a phue bridge based on host parameter."""
host = self.config_entry.data['host']
try:
self.api = await get_bridge(
self.hass, host,
self.config_entry.data['username']
)
except AuthenticationRequired:
# usernames can become invalid if hub is reset or user removed.
# We are going to fail the config entry setup and initiate a new
# linking procedure. When linking succeeds, it will remove the
# old config entry.
self.hass.async_add_job(self.hass.config_entries.flow.async_init(
DOMAIN, source='import', data={
'host': host,
}
))
return False
except CannotConnect:
retry_delay = 2 ** (tries + 1)
LOGGER.error("Error connecting to the Hue bridge at %s. Retrying "
"in %d seconds", host, retry_delay)
async def retry_setup(_now):
"""Retry setup."""
if await self.async_setup(tries + 1):
# This feels hacky, we should find a better way to do this
self.config_entry.state = config_entries.ENTRY_STATE_LOADED
# Unhandled edge case: cancel this if we discover bridge on new IP
self.hass.helpers.event.async_call_later(retry_delay, retry_setup)
return False
except Exception: # pylint: disable=broad-except
LOGGER.exception('Unknown error connecting with Hue bridge at %s',
host)
return False
self.hass.async_add_job(
self.hass.helpers.discovery.async_load_platform(
'light', DOMAIN, {'host': host}))
self.hass.services.async_register(
DOMAIN, SERVICE_HUE_SCENE, self.hue_activate_scene,
schema=SCENE_SCHEMA)
return True
async def hue_activate_scene(self, call, updated=False):
"""Service to call directly into bridge to set scenes."""
group_name = call.data[ATTR_GROUP_NAME]
scene_name = call.data[ATTR_SCENE_NAME]
group = next(
(group for group in self.api.groups.values()
if group.name == group_name), None)
scene_id = next(
(scene.id for scene in self.api.scenes.values()
if scene.name == scene_name), None)
# If we can't find it, fetch latest info.
if not updated and (group is None or scene_id is None):
await self.api.groups.update()
await self.api.scenes.update()
await self.hue_activate_scene(call, updated=True)
return
if group is None:
LOGGER.warning('Unable to find group %s', group_name)
return
if scene_id is None:
LOGGER.warning('Unable to find scene %s', scene_name)
return
await group.set_action(scene=scene_id)
async def get_bridge(hass, host, username=None):
"""Create a bridge object and verify authentication."""
import aiohue
bridge = aiohue.Bridge(
host, username=username,
websession=aiohttp_client.async_get_clientsession(hass)
)
try:
with async_timeout.timeout(5):
# Create username if we don't have one
if not username:
await bridge.create_user('home-assistant')
# Initialize bridge (and validate our username)
await bridge.initialize()
return bridge
except (aiohue.LinkButtonNotPressed, aiohue.Unauthorized):
LOGGER.warning("Connected to Hue at %s but not registered.", host)
raise AuthenticationRequired
except (asyncio.TimeoutError, aiohue.RequestError):
LOGGER.error("Error connecting to the Hue bridge at %s", host)
raise CannotConnect
except aiohue.AiohueException:
LOGGER.exception('Unknown Hue linking error occurred')
raise AuthenticationRequired