Skip to content

Commit

Permalink
Fix more things
Browse files Browse the repository at this point in the history
  • Loading branch information
balloob committed Oct 18, 2016
1 parent 4615816 commit 8f26311
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 152 deletions.
2 changes: 1 addition & 1 deletion homeassistant/components/camera/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def get(self, request, entity_id):
return self.Response(status=404)

authenticated = (request.authenticated or
request.args.get('token') == camera.access_token)
request.GET.get('token') == camera.access_token)

if not authenticated:
return self.Response(status=401)
Expand Down
99 changes: 55 additions & 44 deletions homeassistant/components/emulated_hue.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/emulated_hue/
"""
import asyncio
import threading
import socket
import logging
import json
import os
import select

from aiohttp import web
import voluptuous as vol

from homeassistant import util, core
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, SERVICE_TURN_OFF, SERVICE_TURN_ON,
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
STATE_ON, HTTP_BAD_REQUEST
STATE_ON, HTTP_BAD_REQUEST, HTTP_NOT_FOUND,
)
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_SUPPORTED_FEATURES, SUPPORT_BRIGHTNESS
Expand Down Expand Up @@ -85,17 +87,28 @@ def setup(hass, yaml_config):
upnp_listener = UPNPResponderThread(
config.host_ip_addr, config.listen_port)

# @core.callback
def start_emulated_hue_bridge(event):
"""Start the emulated hue bridge."""
server.start()
# hass.loop.create_task(server.start())
# Temp, while fixing listen_once
from homeassistant.util.async import run_coroutine_threadsafe

run_coroutine_threadsafe(server.start(), hass.loop).result()

upnp_listener.start()

hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_emulated_hue_bridge)

# @core.callback
def stop_emulated_hue_bridge(event):
"""Stop the emulated hue bridge."""
upnp_listener.stop()
server.stop()
# hass.loop.create_task(server.stop())
# Temp, while fixing listen_once
from homeassistant.util.async import run_coroutine_threadsafe

run_coroutine_threadsafe(server.stop(), hass.loop).result()

hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_emulated_hue_bridge)

Expand Down Expand Up @@ -156,6 +169,7 @@ def __init__(self, hass, config):
super().__init__(hass)
self.config = config

@core.callback
def get(self, request):
"""Handle a GET request."""
xml_template = """<?xml version="1.0" encoding="UTF-8" ?>
Expand Down Expand Up @@ -183,7 +197,7 @@ def get(self, request):
resp_text = xml_template.format(
self.config.host_ip_addr, self.config.listen_port)

return self.Response(resp_text, mimetype='text/xml')
return web.Response(text=resp_text, content_type='text/xml')


class HueUsernameView(HomeAssistantView):
Expand All @@ -198,9 +212,13 @@ def __init__(self, hass):
"""Initialize the instance of the view."""
super().__init__(hass)

@asyncio.coroutine
def post(self, request):
"""Handle a POST request."""
data = request.json
try:
data = yield from request.json()
except json.decoder.JSONDecodeError:
return self.json_message('Invalid JSON', HTTP_BAD_REQUEST)

if 'devicetype' not in data:
return self.json_message('devicetype not specified',
Expand All @@ -212,10 +230,10 @@ def post(self, request):
class HueLightsView(HomeAssistantView):
"""Handle requests for getting and setting info about entities."""

url = '/api/<username>/lights'
url = '/api/{username}/lights'
name = 'api:username:lights'
extra_urls = ['/api/<username>/lights/<entity_id>',
'/api/<username>/lights/<entity_id>/state']
extra_urls = ['/api/{username}/lights/{entity_id}',
'/api/{username}/lights/{entity_id}/state']
requires_auth = False

def __init__(self, hass, config):
Expand All @@ -224,58 +242,49 @@ def __init__(self, hass, config):
self.config = config
self.cached_states = {}

@core.callback
def get(self, request, username, entity_id=None):
"""Handle a GET request."""
if entity_id is None:
return self.get_lights_list()
return self.async_get_lights_list()

if not request.base_url.endswith('state'):
return self.get_light_state(entity_id)
if not request.path.endswith('state'):
return self.async_get_light_state(entity_id)

return self.Response("Method not allowed", status=405)
return web.Response(text="Method not allowed", status=405)

@asyncio.coroutine
def put(self, request, username, entity_id=None):
"""Handle a PUT request."""
if not request.base_url.endswith('state'):
return self.Response("Method not allowed", status=405)

content_type = request.environ.get('CONTENT_TYPE', '')
if content_type == 'application/x-www-form-urlencoded':
# Alexa sends JSON data with a form data content type, for
# whatever reason, and Werkzeug parses form data automatically,
# so we need to do some gymnastics to get the data we need
json_data = None

for key in request.form:
try:
json_data = json.loads(key)
break
except ValueError:
# Try the next key?
pass

if json_data is None:
return self.Response("Bad request", status=400)
else:
json_data = request.json
if not request.path.endswith('state'):
return web.Response(text="Method not allowed", status=405)

if entity_id and self.hass.states.get(entity_id) is None:
return self.json_message('Entity not found', HTTP_NOT_FOUND)

try:
json_data = yield from request.json()
except json.decoder.JSONDecodeError:
return self.json_message('Invalid JSON', HTTP_BAD_REQUEST)

return self.put_light_state(json_data, entity_id)
result = yield from self.async_put_light_state(json_data, entity_id)
return result

def get_lights_list(self):
def async_get_lights_list(self):
"""Process a request to get the list of available lights."""
json_response = {}

for entity in self.hass.states.all():
for entity in self.hass.states.async_all():
if self.is_entity_exposed(entity):
json_response[entity.entity_id] = entity_to_json(entity)

return self.json(json_response)

def get_light_state(self, entity_id):
def async_get_light_state(self, entity_id):
"""Process a request to get the state of an individual light."""
entity = self.hass.states.get(entity_id)
if entity is None or not self.is_entity_exposed(entity):
return self.Response("Entity not found", status=404)
return web.Response(text="Entity not found", status=404)

cached_state = self.cached_states.get(entity_id, None)

Expand All @@ -290,23 +299,24 @@ def get_light_state(self, entity_id):

return self.json(json_response)

def put_light_state(self, request_json, entity_id):
@asyncio.coroutine
def async_put_light_state(self, request_json, entity_id):
"""Process a request to set the state of an individual light."""
config = self.config

# Retrieve the entity from the state machine
entity = self.hass.states.get(entity_id)
if entity is None:
return self.Response("Entity not found", status=404)
return web.Response(text="Entity not found", status=404)

if not self.is_entity_exposed(entity):
return self.Response("Entity not found", status=404)
return web.Response(text="Entity not found", status=404)

# Parse the request into requested "on" status and brightness
parsed = parse_hue_api_put_light_body(request_json, entity)

if parsed is None:
return self.Response("Bad request", status=400)
return web.Response(text="Bad request", status=400)

result, brightness = parsed

Expand All @@ -331,7 +341,8 @@ def put_light_state(self, request_json, entity_id):
self.cached_states[entity_id] = (result, brightness)

# Perform the requested action
self.hass.services.call(core.DOMAIN, service, data, blocking=True)
yield from self.hass.services.async_call(core.DOMAIN, service, data,
blocking=True)

json_response = \
[create_hue_success_response(entity_id, HUE_API_STATE_ON, result)]
Expand Down
29 changes: 23 additions & 6 deletions homeassistant/components/history.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/history/
"""
import asyncio
from collections import defaultdict
from datetime import timedelta
from itertools import groupby
import voluptuous as vol

from aiohttp import web

from homeassistant.const import HTTP_BAD_REQUEST
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
from homeassistant.components import recorder, script
Expand Down Expand Up @@ -192,32 +196,42 @@ def setup(hass, config):
class Last5StatesView(HomeAssistantView):
"""Handle last 5 state view requests."""

url = '/api/history/entity/<entity:entity_id>/recent_states'
url = '/api/history/entity/{entity_id}/recent_states'
name = 'api:history:entity-recent-states'

def __init__(self, hass):
"""Initilalize the history last 5 states view."""
super().__init__(hass)

@asyncio.coroutine
def get(self, request, entity_id):
"""Retrieve last 5 states of entity."""
return self.json(last_5_states(entity_id))
result = yield from self.hass.loop.run_in_executor(
last_5_states, entity_id)
return self.json(result)


class HistoryPeriodView(HomeAssistantView):
"""Handle history period requests."""

url = '/api/history/period'
name = 'api:history:view-period'
extra_urls = ['/api/history/period/<datetime:datetime>']
extra_urls = ['/api/history/period/{datetime}']

def __init__(self, hass, filters):
"""Initilalize the history period view."""
super().__init__(hass)
self.filters = filters

@asyncio.coroutine
def get(self, request, datetime=None):
"""Return history over a period of time."""
if datetime:
datetime = dt_util.parse_datetime(datetime)

if datetime is None:
return web.Response('Invalid datetime', HTTP_BAD_REQUEST)

one_day = timedelta(days=1)

if datetime:
Expand All @@ -226,10 +240,13 @@ def get(self, request, datetime=None):
start_time = dt_util.utcnow() - one_day

end_time = start_time + one_day
entity_id = request.args.get('filter_entity_id')
entity_id = request.GET.get('filter_entity_id')

result = yield from self.hass.loop.run_in_executor(
get_significant_states, start_time, end_time, entity_id,
self.filters)

return self.json(get_significant_states(
start_time, end_time, entity_id, self.filters).values())
return self.json(result.values())


# pylint: disable=too-few-public-methods
Expand Down
Loading

0 comments on commit 8f26311

Please sign in to comment.