mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +00:00
Validate API on starting remote instance
This commit is contained in:
parent
50b492c64a
commit
e9d1dfac84
25
README.md
25
README.md
@ -120,6 +120,22 @@ Because each slave maintains it's own ServiceRegistry it is possible to have mul
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
A slave instance can be started with the following code.
|
||||||
|
|
||||||
|
```python
|
||||||
|
import homeassistant.remote as remote
|
||||||
|
import homeassistant.components.http as http
|
||||||
|
|
||||||
|
remote_api = remote.API("remote_host_or_ip", "remote_api_password")
|
||||||
|
|
||||||
|
hass = remote.HomeAssistant(remote_api)
|
||||||
|
|
||||||
|
http.setup(hass, "my_local_api_password")
|
||||||
|
|
||||||
|
hass.start()
|
||||||
|
hass.block_till_stopped()
|
||||||
|
```
|
||||||
|
|
||||||
Web interface and API
|
Web interface and API
|
||||||
---------------------
|
---------------------
|
||||||
Home Assistent runs a webserver accessible on port 8123.
|
Home Assistent runs a webserver accessible on port 8123.
|
||||||
@ -143,6 +159,15 @@ Other status codes that can occur are:
|
|||||||
|
|
||||||
The api supports the following actions:
|
The api supports the following actions:
|
||||||
|
|
||||||
|
**/api - GET**<br>
|
||||||
|
Returns message if API is up and running.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "API running."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
**/api/events - GET**<br>
|
**/api/events - GET**<br>
|
||||||
Returns a dict with as keys the events and as value the number of listeners.
|
Returns a dict with as keys the events and as value the number of listeners.
|
||||||
|
|
||||||
|
@ -17,6 +17,13 @@ Other status codes that can occur are:
|
|||||||
|
|
||||||
The api supports the following actions:
|
The api supports the following actions:
|
||||||
|
|
||||||
|
/api - GET
|
||||||
|
Returns message if API is up and running.
|
||||||
|
Example result:
|
||||||
|
{
|
||||||
|
"message": "API running."
|
||||||
|
}
|
||||||
|
|
||||||
/api/states - GET
|
/api/states - GET
|
||||||
Returns a list of entities for which a state is available
|
Returns a list of entities for which a state is available
|
||||||
Example result:
|
Example result:
|
||||||
@ -112,6 +119,10 @@ def setup(hass, api_password, server_port=None, server_host=None):
|
|||||||
lambda event:
|
lambda event:
|
||||||
threading.Thread(target=server.start, daemon=True).start())
|
threading.Thread(target=server.start, daemon=True).start())
|
||||||
|
|
||||||
|
# If no local api set, set one with known information
|
||||||
|
if isinstance(hass, rem.HomeAssistant) and hass.local_api is None:
|
||||||
|
hass.local_api = rem.API(util.get_local_ip(), api_password, server_port)
|
||||||
|
|
||||||
|
|
||||||
class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
|
class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
|
||||||
""" Handle HTTP requests in a threaded fashion. """
|
""" Handle HTTP requests in a threaded fashion. """
|
||||||
@ -149,6 +160,9 @@ class RequestHandler(BaseHTTPRequestHandler):
|
|||||||
('POST', re.compile(URL_FIRE_EVENT), '_handle_fire_event'),
|
('POST', re.compile(URL_FIRE_EVENT), '_handle_fire_event'),
|
||||||
('POST', re.compile(URL_CALL_SERVICE), '_handle_call_service'),
|
('POST', re.compile(URL_CALL_SERVICE), '_handle_call_service'),
|
||||||
|
|
||||||
|
# /api - for validation purposes
|
||||||
|
('GET', rem.URL_API, '_handle_get_api'),
|
||||||
|
|
||||||
# /states
|
# /states
|
||||||
('GET', rem.URL_API_STATES, '_handle_get_api_states'),
|
('GET', rem.URL_API_STATES, '_handle_get_api_states'),
|
||||||
('GET',
|
('GET',
|
||||||
@ -619,6 +633,11 @@ class RequestHandler(BaseHTTPRequestHandler):
|
|||||||
self._message(
|
self._message(
|
||||||
"Invalid JSON for service_data", HTTP_UNPROCESSABLE_ENTITY)
|
"Invalid JSON for service_data", HTTP_UNPROCESSABLE_ENTITY)
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def _handle_get_api(self, path_match, data):
|
||||||
|
""" Renders the debug interface. """
|
||||||
|
self._message("API running.")
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def _handle_get_api_states(self, path_match, data):
|
def _handle_get_api_states(self, path_match, data):
|
||||||
""" Returns a dict containing all entity ids and their state. """
|
""" Returns a dict containing all entity ids and their state. """
|
||||||
|
@ -12,6 +12,7 @@ HomeAssistantError will be raised.
|
|||||||
import threading
|
import threading
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
|
import enum
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
@ -20,6 +21,7 @@ import homeassistant as ha
|
|||||||
|
|
||||||
SERVER_PORT = 8123
|
SERVER_PORT = 8123
|
||||||
|
|
||||||
|
URL_API = "/api/"
|
||||||
URL_API_STATES = "/api/states"
|
URL_API_STATES = "/api/states"
|
||||||
URL_API_STATES_ENTITY = "/api/states/{}"
|
URL_API_STATES_ENTITY = "/api/states/{}"
|
||||||
URL_API_EVENTS = "/api/events"
|
URL_API_EVENTS = "/api/events"
|
||||||
@ -32,6 +34,18 @@ METHOD_GET = "get"
|
|||||||
METHOD_POST = "post"
|
METHOD_POST = "post"
|
||||||
|
|
||||||
|
|
||||||
|
class APIStatus(enum.Enum):
|
||||||
|
""" Represents API status. """
|
||||||
|
|
||||||
|
OK = "ok"
|
||||||
|
INVALID_PASSWORD = "invalid_password"
|
||||||
|
CANNOT_CONNECT = "cannot_connect"
|
||||||
|
UNKNOWN = "unknown"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
class API(object):
|
class API(object):
|
||||||
""" Object to pass around Home Assistant API location and credentials. """
|
""" Object to pass around Home Assistant API location and credentials. """
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
@ -41,6 +55,13 @@ class API(object):
|
|||||||
self.port = port or SERVER_PORT
|
self.port = port or SERVER_PORT
|
||||||
self.api_password = api_password
|
self.api_password = api_password
|
||||||
self.base_url = "http://{}:{}".format(host, self.port)
|
self.base_url = "http://{}:{}".format(host, self.port)
|
||||||
|
self.status = None
|
||||||
|
|
||||||
|
def validate_api(self, force_validate=False):
|
||||||
|
if self.status is None or force_validate:
|
||||||
|
self.status = validate_api(self)
|
||||||
|
|
||||||
|
return self.status == APIStatus.OK
|
||||||
|
|
||||||
def __call__(self, method, path, data=None):
|
def __call__(self, method, path, data=None):
|
||||||
""" Makes a call to the Home Assistant api. """
|
""" Makes a call to the Home Assistant api. """
|
||||||
@ -64,9 +85,13 @@ class HomeAssistant(ha.HomeAssistant):
|
|||||||
""" Home Assistant that forwards work. """
|
""" Home Assistant that forwards work. """
|
||||||
# pylint: disable=super-init-not-called
|
# pylint: disable=super-init-not-called
|
||||||
|
|
||||||
def __init__(self, local_api, remote_api):
|
def __init__(self, remote_api, local_api=None):
|
||||||
self.local_api = local_api
|
if not remote_api.validate_api():
|
||||||
|
raise ha.HomeAssistantError(
|
||||||
|
"Remote API not valid: {}".format(remote_api.status))
|
||||||
|
|
||||||
self.remote_api = remote_api
|
self.remote_api = remote_api
|
||||||
|
self.local_api = local_api
|
||||||
|
|
||||||
self._pool = pool = ha.create_worker_pool()
|
self._pool = pool = ha.create_worker_pool()
|
||||||
|
|
||||||
@ -75,6 +100,14 @@ class HomeAssistant(ha.HomeAssistant):
|
|||||||
self.states = StateMachine(self.bus, self.remote_api)
|
self.states = StateMachine(self.bus, self.remote_api)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
|
# If there is no local API setup but we do want to connect with remote
|
||||||
|
# We create a random password and set up a local api
|
||||||
|
if self.local_api is None:
|
||||||
|
import homeassistant.components.http as http
|
||||||
|
import random
|
||||||
|
|
||||||
|
http.setup(self, '%030x'.format(random.randrange(16**30)))
|
||||||
|
|
||||||
ha.Timer(self)
|
ha.Timer(self)
|
||||||
|
|
||||||
# Setup that events from remote_api get forwarded to local_api
|
# Setup that events from remote_api get forwarded to local_api
|
||||||
@ -201,6 +234,24 @@ class JSONEncoder(json.JSONEncoder):
|
|||||||
return json.JSONEncoder.default(self, obj)
|
return json.JSONEncoder.default(self, obj)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_api(api):
|
||||||
|
""" Makes a call to validate API. """
|
||||||
|
try:
|
||||||
|
req = api(METHOD_GET, URL_API)
|
||||||
|
|
||||||
|
if req.status_code == 200:
|
||||||
|
return APIStatus.OK
|
||||||
|
|
||||||
|
elif req.status_code == 401:
|
||||||
|
return APIStatus.INVALID_PASSWORD
|
||||||
|
|
||||||
|
else:
|
||||||
|
return APIStatus.UNKNOWN
|
||||||
|
|
||||||
|
except ha.HomeAssistantError:
|
||||||
|
return APIStatus.CANNOT_CONNECT
|
||||||
|
|
||||||
|
|
||||||
def connect_remote_events(from_api, to_api):
|
def connect_remote_events(from_api, to_api):
|
||||||
""" Sets up from_api to forward all events to to_api. """
|
""" Sets up from_api to forward all events to to_api. """
|
||||||
|
|
||||||
|
@ -9,6 +9,8 @@ import queue
|
|||||||
import datetime
|
import datetime
|
||||||
import re
|
import re
|
||||||
import enum
|
import enum
|
||||||
|
import socket
|
||||||
|
import os
|
||||||
|
|
||||||
RE_SANITIZE_FILENAME = re.compile(r'(~|\.\.|/|\\)')
|
RE_SANITIZE_FILENAME = re.compile(r'(~|\.\.|/|\\)')
|
||||||
RE_SLUGIFY = re.compile(r'[^A-Za-z0-9_]+')
|
RE_SLUGIFY = re.compile(r'[^A-Za-z0-9_]+')
|
||||||
@ -124,6 +126,23 @@ def ensure_unique_string(preferred_string, current_strings):
|
|||||||
return string
|
return string
|
||||||
|
|
||||||
|
|
||||||
|
# Taken from: http://stackoverflow.com/a/11735897
|
||||||
|
def get_local_ip():
|
||||||
|
""" Tries to determine the local IP address of the machine. """
|
||||||
|
try:
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
|
||||||
|
# Use Google Public DNS server to determine own IP
|
||||||
|
sock.connect(('8.8.8.8', 80))
|
||||||
|
ip_addr = sock.getsockname()[0]
|
||||||
|
sock.close()
|
||||||
|
|
||||||
|
return ip_addr
|
||||||
|
|
||||||
|
except socket.error:
|
||||||
|
return socket.gethostbyname(socket.gethostname())
|
||||||
|
|
||||||
|
|
||||||
class OrderedEnum(enum.Enum):
|
class OrderedEnum(enum.Enum):
|
||||||
""" Taken from Python 3.4.0 docs. """
|
""" Taken from Python 3.4.0 docs. """
|
||||||
# pylint: disable=no-init
|
# pylint: disable=no-init
|
||||||
|
Loading…
x
Reference in New Issue
Block a user