diff --git a/home-assistant.conf.default b/home-assistant.conf.default index 0d3cd27b442..21090ddf77d 100644 --- a/home-assistant.conf.default +++ b/home-assistant.conf.default @@ -1,6 +1,7 @@ [common] latitude=32.87336 longitude=-117.22743 +api_password=mypass [tomato] host=192.168.1.1 diff --git a/homeassistant/__init__.py b/homeassistant/__init__.py index 141f08d2be9..28a1e22a6c7 100644 --- a/homeassistant/__init__.py +++ b/homeassistant/__init__.py @@ -40,11 +40,11 @@ class HomeAssistant(object): LightTrigger(self.eventbus, self.statemachine, self._setup_weather_watcher(), devicetracker, light_control) - def setup_http_interface(self): + def setup_http_interface(self, api_password): """ Sets up the HTTP interface. """ if self.httpinterface is None: self.logger.info("Setting up HTTP interface") - self.httpinterface = HTTPInterface(self.eventbus, self.statemachine) + self.httpinterface = HTTPInterface(self.eventbus, self.statemachine, api_password) return self.httpinterface diff --git a/homeassistant/core.py b/homeassistant/core.py index 6c77994b802..03110c6ebec 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -121,9 +121,9 @@ class StateMachine(object): def set_state(self, category, new_state): """ Set the state of a category. """ - self.lock.acquire() + self._validate_category(category) - assert category in self.states, "Category does not exist: {}".format(category) + self.lock.acquire() old_state = self.states[category] @@ -136,16 +136,27 @@ class StateMachine(object): def is_state(self, category, state): """ Returns True if category is specified state. """ - assert category in self.states, "Category does not exist: {}".format(category) + self._validate_category(category) return self.get_state(category).state == state def get_state(self, category): """ Returns a tuple (state,last_changed) describing the state of the specified category. """ - assert category in self.states, "Category does not exist: {}".format(category) + self._validate_category(category) return self.states[category] def get_states(self): """ Returns a list of tuples (category, state, last_changed) sorted by category. """ return [(category, self.states[category].state, self.states[category].last_changed) for category in sorted(self.states.keys())] + + def _validate_category(self, category): + if category not in self.states: + raise CategoryDoesNotExistException("Category {} does not exist.".format(category)) + + +class HomeAssistantException(Exception): + """ General Home Assistant exception occured. """ + +class CategoryDoesNotExistException(HomeAssistantException): + """ Specified category does not exist within the state machine. """ diff --git a/homeassistant/httpinterface.py b/homeassistant/httpinterface.py index 6394b4d670c..dc486190383 100644 --- a/homeassistant/httpinterface.py +++ b/homeassistant/httpinterface.py @@ -2,32 +2,59 @@ homeassistant.httpinterface ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This module provides an HTTP interface for debug purposes. +This module provides an API and a HTTP interface for debug purposes. + +By default it will run on port 8080. + +All API calls have to be accompanied by an 'api_password' parameter. + +The api supports the following actions: + +/api/state/change - POST +parameter: category - string +parameter: new_state - string +Changes category 'category' to 'new_state' + +/api/event/fire - POST +parameter: event_name - string +parameter: event_data - JSON-string (optional) +Fires an 'event_name' event containing data from 'event_data' """ +import json import threading -import urlparse import logging from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +from urlparse import urlparse, parse_qs import requests -from .core import EVENT_START, EVENT_SHUTDOWN +from .core import EVENT_START, EVENT_SHUTDOWN, Event, CategoryDoesNotExistException -SERVER_HOST = '127.0.0.1' SERVER_PORT = 8080 +MESSAGE_STATUS_OK = "OK" +MESSAGE_STATUS_ERROR = "ERROR" +MESSAGE_STATUS_UNAUTHORIZED = "UNAUTHORIZED" + class HTTPInterface(threading.Thread): """ Provides an HTTP interface for Home Assistant. """ - def __init__(self, eventbus, statemachine): + def __init__(self, eventbus, statemachine, api_password, server_port=SERVER_PORT, server_host=None): threading.Thread.__init__(self) - self.server = HTTPServer((SERVER_HOST, SERVER_PORT), RequestHandler) + # If no server host is given, accept all incoming requests + if server_host is None: + server_host = '0.0.0.0' + self.server = HTTPServer((server_host, server_port), RequestHandler) + + self.server.flash_message = None + self.server.logger = logging.getLogger(__name__) self.server.eventbus = eventbus self.server.statemachine = statemachine + self.server.api_password = api_password self._stop = threading.Event() @@ -36,7 +63,7 @@ class HTTPInterface(threading.Thread): def run(self): """ Start the HTTP interface. """ - logging.getLogger(__name__).info("Starting") + self.server.logger.info("Starting") while not self._stop.is_set(): self.server.handle_request() @@ -47,7 +74,7 @@ class HTTPInterface(threading.Thread): self._stop.set() # Trigger a fake request to get the server to quit - requests.get("http://{}:{}".format(SERVER_HOST, SERVER_PORT)) + requests.get("http://127.0.0.1:{}".format(SERVER_PORT)) class RequestHandler(BaseHTTPRequestHandler): """ Handles incoming HTTP requests """ @@ -55,13 +82,40 @@ class RequestHandler(BaseHTTPRequestHandler): #Handler for the GET requests def do_GET(self): """ Handle incoming GET requests. """ + write = lambda txt: self.wfile.write(txt+"\n") - if self.path == "/": + url = urlparse(self.path) + + get_data = parse_qs(url.query) + + # Verify API password + if get_data.get('api_password', [''])[0] != self.server.api_password: self.send_response(200) self.send_header('Content-type','text/html') self.end_headers() - write = self.wfile.write + write("") + write("
") + write("") + + + # Serve debug URL + elif url.path == "/": + self.send_response(200) + self.send_header('Content-type','text/html') + self.end_headers() + + + write("") + + # Flash message support + if self.server.flash_message is not None: + write("