Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion homeassistant/components/config/config_entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ def delete(self, request, flow_id):
hass = request.app['hass']

try:
hass.config_entries.async_abort(flow_id)
hass.config_entries.flow.async_abort(flow_id)
except config_entries.UnknownFlow:
return self.json_message('Invalid flow specified', 404)

Expand Down
138 changes: 129 additions & 9 deletions homeassistant/components/hue.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,24 @@
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/hue/
"""
import asyncio
import json
from functools import partial
import logging
import os
import socket

import async_timeout
import requests
import voluptuous as vol

from homeassistant.components.discovery import SERVICE_HUE
from homeassistant.const import CONF_FILENAME, CONF_HOST
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.helpers import discovery, aiohttp_client
from homeassistant import config_entries

REQUIREMENTS = ['phue==1.0']
REQUIREMENTS = ['phue==1.0', 'aiohue==0.3.0']

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -133,13 +137,14 @@ def bridge_discovered(hass, service, discovery_info):


def setup_bridge(host, hass, filename=None, allow_unreachable=False,
allow_in_emulated_hue=True, allow_hue_groups=True):
allow_in_emulated_hue=True, allow_hue_groups=True,
username=None):
"""Set up a given Hue bridge."""
# Only register a device once
if socket.gethostbyname(host) in hass.data[DOMAIN]:
return

bridge = HueBridge(host, hass, filename, allow_unreachable,
bridge = HueBridge(host, hass, filename, username, allow_unreachable,
allow_in_emulated_hue, allow_hue_groups)
bridge.setup()

Expand All @@ -164,13 +169,14 @@ def _find_host_from_config(hass, filename=PHUE_CONFIG_FILE):
class HueBridge(object):
"""Manages a single Hue bridge."""

def __init__(self, host, hass, filename, allow_unreachable=False,
def __init__(self, host, hass, filename, username, allow_unreachable=False,
allow_in_emulated_hue=True, allow_hue_groups=True):
"""Initialize the system."""
self.host = host
self.bridge_id = socket.gethostbyname(host)
self.hass = hass
self.filename = filename
self.username = username
self.allow_unreachable = allow_unreachable
self.allow_in_emulated_hue = allow_in_emulated_hue
self.allow_hue_groups = allow_hue_groups
Expand All @@ -189,10 +195,14 @@ def setup(self):
import phue

try:
self.bridge = phue.Bridge(
self.host,
config_file_path=self.hass.config.path(self.filename))
except (ConnectionRefusedError, OSError): # Wrong host was given
kwargs = {}
if self.username is not None:
kwargs['username'] = self.username
if self.filename is not None:
kwargs['config_file_path'] = \
self.hass.config.path(self.filename)
self.bridge = phue.Bridge(self.host, **kwargs)
except OSError: # Wrong host was given
_LOGGER.error("Error connecting to the Hue bridge at %s",
self.host)
return
Expand All @@ -204,6 +214,7 @@ def setup(self):
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unknown error connecting with Hue bridge at %s",
self.host)
return

# If we came here and configuring this host, mark as done
if self.config_request_id:
Expand Down Expand Up @@ -260,3 +271,112 @@ def set_light(self, light_id, command):
def set_group(self, light_id, command):
"""Change light settings for a group. See phue for detail."""
return self.bridge.set_group(light_id, command)


@config_entries.HANDLERS.register(DOMAIN)
class HueFlowHandler(config_entries.ConfigFlowHandler):
"""Handle a Hue config flow."""

VERSION = 1

def __init__(self):
"""Initialize the Hue flow."""
self.host = None

@property
def _websession(self):
"""Return a websession.

Cannot assign in init because hass variable is not set yet.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not having access to hass and not passing it explicitly will bite us continuously in code review and as bugs, I think. But maybe there's no way around it?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we can make a default constructor

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the reasons I don't like constructors is that if people override it, we can never change that part anymore.

"""
return aiohttp_client.async_get_clientsession(self.hass)

async def async_step_init(self, user_input=None):
"""Handle a flow start."""
from aiohue.discovery import discover_nupnp

if user_input is not None:
self.host = user_input['host']
return await self.async_step_link()

try:
with async_timeout.timeout(5):
bridges = await discover_nupnp(websession=self._websession)
except asyncio.TimeoutError:
return self.async_abort(
reason='Unable to discover Hue bridges.'
)

if not bridges:
return self.async_abort(
reason='No Philips Hue bridges discovered.'
)

# Find already configured hosts
configured_hosts = set(
entry.data['host'] for entry
in self.hass.config_entries.async_entries(DOMAIN))

hosts = [bridge.host for bridge in bridges
if bridge.host not in configured_hosts]

if not hosts:
return self.async_abort(
reason='All Philips Hue bridges are already configured.'
)

elif len(hosts) == 1:
self.host = hosts[0]
return await self.async_step_link()

return self.async_show_form(
step_id='init',
title='Pick Hue Bridge',
data_schema=vol.Schema({
vol.Required('host'): vol.In(hosts)
})
)

async def async_step_link(self, user_input=None):
"""Attempt to link with the Hue bridge."""
import aiohue
errors = {}

if user_input is not None:
bridge = aiohue.Bridge(self.host, websession=self._websession)
try:
with async_timeout.timeout(5):
# Create auth token
await bridge.create_user('home-assistant')
# Fetches name and id
await bridge.initialize()
except (asyncio.TimeoutError, aiohue.RequestError,
aiohue.LinkButtonNotPressed):
errors['base'] = 'Failed to register, please try again.'
except aiohue.AiohueException:
errors['base'] = 'Unknown linking error occurred.'
_LOGGER.exception('Uknown Hue linking error occurred')
else:
return self.async_create_entry(
title=bridge.config.name,
data={
'host': bridge.host,
'bridge_id': bridge.config.bridgeid,
'username': bridge.username,
}
)

return self.async_show_form(
step_id='link',
title='Link Hub',
description=CONFIG_INSTRUCTIONS,
errors=errors,
)


async def async_setup_entry(hass, entry):
"""Set up a bridge for a config entry."""
await hass.async_add_job(partial(
setup_bridge, entry.data['host'], hass,
username=entry.data['username']))
return True
3 changes: 2 additions & 1 deletion homeassistant/components/spc.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,8 @@ def _call_web_gateway(self, resource, use_get=True):
url = self._build_url(resource)
try:
_LOGGER.debug("Attempting to retrieve SPC data from %s", url)
session = aiohttp.ClientSession()
session = \
self._hass.helpers.aiohttp_client.async_get_clientsession()
with async_timeout.timeout(10, loop=self._hass.loop):
action = session.get if use_get else session.put
response = yield from action(url)
Expand Down
3 changes: 2 additions & 1 deletion homeassistant/config_entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ async def async_step_discovery(info):
HANDLERS = Registry()
# Components that have config flows. In future we will auto-generate this list.
FLOWS = [
'config_entry_example'
'config_entry_example',
'hue',
]

SOURCE_USER = 'user'
Expand Down
9 changes: 1 addition & 8 deletions homeassistant/helpers/aiohttp_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,7 @@ def async_get_clientsession(hass, verify_ssl=True):
key = DATA_CLIENTSESSION_NOTVERIFY

if key not in hass.data:
connector = _async_get_connector(hass, verify_ssl)
clientsession = aiohttp.ClientSession(
loop=hass.loop,
connector=connector,
headers={USER_AGENT: SERVER_SOFTWARE}
)
_async_register_clientsession_shutdown(hass, clientsession)
hass.data[key] = clientsession
hass.data[key] = async_create_clientsession(hass, verify_ssl)

return hass.data[key]

Expand Down
3 changes: 3 additions & 0 deletions requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ aiodns==1.1.1
# homeassistant.components.http
aiohttp_cors==0.6.0

# homeassistant.components.hue
aiohue==0.3.0

# homeassistant.components.sensor.imap
aioimaplib==0.7.13

Expand Down
3 changes: 3 additions & 0 deletions requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ aioautomatic==0.6.5
# homeassistant.components.http
aiohttp_cors==0.6.0

# homeassistant.components.hue
aiohue==0.3.0

# homeassistant.components.notify.apns
apns2==0.3.0

Expand Down
1 change: 1 addition & 0 deletions script/gen_requirements_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
TEST_REQUIREMENTS = (
'aioautomatic',
'aiohttp_cors',
'aiohue',
'apns2',
'caldav',
'coinmarketcap',
Expand Down
Loading