mirror of
https://github.com/home-assistant/core.git
synced 2025-07-14 08:47:10 +00:00
Merge branch 'dev' into mysensors-component-switch
This commit is contained in:
commit
eae07c070a
@ -55,6 +55,7 @@ omit =
|
|||||||
homeassistant/components/discovery.py
|
homeassistant/components/discovery.py
|
||||||
homeassistant/components/downloader.py
|
homeassistant/components/downloader.py
|
||||||
homeassistant/components/ifttt.py
|
homeassistant/components/ifttt.py
|
||||||
|
homeassistant/components/influxdb.py
|
||||||
homeassistant/components/keyboard.py
|
homeassistant/components/keyboard.py
|
||||||
homeassistant/components/light/hue.py
|
homeassistant/components/light/hue.py
|
||||||
homeassistant/components/light/mqtt.py
|
homeassistant/components/light/mqtt.py
|
||||||
|
@ -53,9 +53,10 @@ def setup(hass, config):
|
|||||||
bootstrap.setup_component(hass, 'sun')
|
bootstrap.setup_component(hass, 'sun')
|
||||||
|
|
||||||
# Setup demo platforms
|
# Setup demo platforms
|
||||||
|
demo_config = config.copy()
|
||||||
for component in COMPONENTS_WITH_DEMO_PLATFORM:
|
for component in COMPONENTS_WITH_DEMO_PLATFORM:
|
||||||
bootstrap.setup_component(
|
demo_config[component] = {CONF_PLATFORM: 'demo'}
|
||||||
hass, component, {component: {CONF_PLATFORM: 'demo'}})
|
bootstrap.setup_component(hass, component, demo_config)
|
||||||
|
|
||||||
# Setup room groups
|
# Setup room groups
|
||||||
lights = sorted(hass.states.entity_ids('light'))
|
lights = sorted(hass.states.entity_ids('light'))
|
||||||
|
@ -44,7 +44,7 @@ HOLD_TEMP = 'hold_temp'
|
|||||||
|
|
||||||
REQUIREMENTS = [
|
REQUIREMENTS = [
|
||||||
'https://github.com/nkgilley/python-ecobee-api/archive/'
|
'https://github.com/nkgilley/python-ecobee-api/archive/'
|
||||||
'5645f843b64ac4f6e59dfb96233a07083c5e10c1.zip#python-ecobee==0.0.3']
|
'92a2f330cbaf601d0618456fdd97e5a8c42c1c47.zip#python-ecobee==0.0.4']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -22,8 +22,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
FRONTEND_URLS = [
|
FRONTEND_URLS = [
|
||||||
URL_ROOT, '/logbook', '/history', '/map', '/devService', '/devState',
|
URL_ROOT, '/logbook', '/history', '/map', '/devService', '/devState',
|
||||||
'/devEvent', '/devInfo']
|
'/devEvent', '/devInfo', '/states']
|
||||||
STATES_URL = re.compile(r'/states(/([a-zA-Z\._\-0-9/]+)|)')
|
|
||||||
|
|
||||||
_FINGERPRINT = re.compile(r'^(\w+)-[a-z0-9]{32}\.(\w+)$', re.IGNORECASE)
|
_FINGERPRINT = re.compile(r'^(\w+)-[a-z0-9]{32}\.(\w+)$', re.IGNORECASE)
|
||||||
|
|
||||||
@ -37,7 +36,8 @@ def setup(hass, config):
|
|||||||
for url in FRONTEND_URLS:
|
for url in FRONTEND_URLS:
|
||||||
hass.http.register_path('GET', url, _handle_get_root, False)
|
hass.http.register_path('GET', url, _handle_get_root, False)
|
||||||
|
|
||||||
hass.http.register_path('GET', STATES_URL, _handle_get_root, False)
|
hass.http.register_path('GET', '/service_worker.js',
|
||||||
|
_handle_get_service_worker, False)
|
||||||
|
|
||||||
# Static files
|
# Static files
|
||||||
hass.http.register_path(
|
hass.http.register_path(
|
||||||
@ -78,6 +78,17 @@ def _handle_get_root(handler, path_match, data):
|
|||||||
handler.wfile.write(template_html.encode("UTF-8"))
|
handler.wfile.write(template_html.encode("UTF-8"))
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_get_service_worker(handler, path_match, data):
|
||||||
|
""" Returns service worker for the frontend. """
|
||||||
|
if handler.server.development:
|
||||||
|
sw_path = "home-assistant-polymer/build/service_worker.js"
|
||||||
|
else:
|
||||||
|
sw_path = "service_worker.js"
|
||||||
|
|
||||||
|
handler.write_file(os.path.join(os.path.dirname(__file__), 'www_static',
|
||||||
|
sw_path))
|
||||||
|
|
||||||
|
|
||||||
def _handle_get_static(handler, path_match, data):
|
def _handle_get_static(handler, path_match, data):
|
||||||
""" Returns a static file for the frontend. """
|
""" Returns a static file for the frontend. """
|
||||||
req_file = util.sanitize_path(path_match.group('file'))
|
req_file = util.sanitize_path(path_match.group('file'))
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Home Assistant</title>
|
<title>Home Assistant</title>
|
||||||
@ -31,11 +31,21 @@
|
|||||||
margin-bottom: 123px;
|
margin-bottom: 123px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<link rel='import' href='/static/{{ app_url }}' async>
|
||||||
</head>
|
</head>
|
||||||
<body fullbleed>
|
<body fullbleed>
|
||||||
<div id='init'><img src='/static/favicon-192x192.png' height='192'></div>
|
<div id='init'><img src='/static/favicon-192x192.png' height='192'></div>
|
||||||
<script src='/static/webcomponents-lite.min.js'></script>
|
<script>
|
||||||
<link rel='import' href='/static/{{ app_url }}' />
|
var webComponentsSupported = ('registerElement' in document &&
|
||||||
|
'import' in document.createElement('link') &&
|
||||||
|
'content' in document.createElement('template'))
|
||||||
|
if (!webComponentsSupported) {
|
||||||
|
var script = document.createElement('script')
|
||||||
|
script.async = true
|
||||||
|
script.src = '/static/webcomponents-lite.min.js'
|
||||||
|
document.head.appendChild(script)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<home-assistant auth='{{ auth }}' icons='{{ icons }}'></home-assistant>
|
<home-assistant auth='{{ auth }}' icons='{{ icons }}'></home-assistant>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
||||||
VERSION = "33a9830ccda8000eb88700de9d4cd03b"
|
VERSION = "aac488c33cd4291cd0924e60a55bd309"
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
|||||||
Subproject commit 2e8ad266eeb8cd0136df498b995f584e01338000
|
Subproject commit e51b8add369f9e81d22b25b4be2400675361afdb
|
@ -0,0 +1,5 @@
|
|||||||
|
!function(e){function t(r){if(n[r])return n[r].exports;var s=n[r]={exports:{},id:r,loaded:!1};return e[r].call(s.exports,s,s.exports,t),s.loaded=!0,s.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([/*!*************************************!*\
|
||||||
|
!*** ./src/service-worker/index.js ***!
|
||||||
|
\*************************************/
|
||||||
|
function(e,t,n){"use strict";var r="0.10",s="/",c=["/","/logbook","/history","/map","/devService","/devState","/devEvent","/devInfo","/states"],i=["/static/favicon-192x192.png"];self.addEventListener("install",function(e){e.waitUntil(caches.open(r).then(function(e){return e.addAll(i.concat(s))}))}),self.addEventListener("activate",function(e){}),self.addEventListener("message",function(e){}),self.addEventListener("fetch",function(e){var t=e.request.url.substr(e.request.url.indexOf("/",8));i.includes(t)&&e.respondWith(caches.open(r).then(function(t){return t.match(e.request)})),c.includes(t)&&e.respondWith(caches.open(r).then(function(t){return t.match(s).then(function(n){var r=fetch(e.request).then(function(e){return t.put(s,e.clone()),e});return n||r})}))})}]);
|
||||||
|
//# sourceMappingURL=service_worker.js.map
|
@ -6,16 +6,17 @@ This module provides an API and a HTTP interface for debug purposes.
|
|||||||
For more details about the RESTful API, please refer to the documentation at
|
For more details about the RESTful API, please refer to the documentation at
|
||||||
https://home-assistant.io/developers/api/
|
https://home-assistant.io/developers/api/
|
||||||
"""
|
"""
|
||||||
import json
|
|
||||||
import threading
|
|
||||||
import logging
|
|
||||||
import time
|
|
||||||
import gzip
|
|
||||||
import os
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from http.server import SimpleHTTPRequestHandler, HTTPServer
|
import gzip
|
||||||
from http import cookies
|
from http import cookies
|
||||||
|
from http.server import SimpleHTTPRequestHandler, HTTPServer
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
from socketserver import ThreadingMixIn
|
from socketserver import ThreadingMixIn
|
||||||
|
import ssl
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
from urllib.parse import urlparse, parse_qs
|
from urllib.parse import urlparse, parse_qs
|
||||||
|
|
||||||
import homeassistant.core as ha
|
import homeassistant.core as ha
|
||||||
@ -36,7 +37,8 @@ CONF_API_PASSWORD = "api_password"
|
|||||||
CONF_SERVER_HOST = "server_host"
|
CONF_SERVER_HOST = "server_host"
|
||||||
CONF_SERVER_PORT = "server_port"
|
CONF_SERVER_PORT = "server_port"
|
||||||
CONF_DEVELOPMENT = "development"
|
CONF_DEVELOPMENT = "development"
|
||||||
CONF_SESSIONS_ENABLED = "sessions_enabled"
|
CONF_SSL_CERTIFICATE = 'ssl_certificate'
|
||||||
|
CONF_SSL_KEY = 'ssl_key'
|
||||||
|
|
||||||
DATA_API_PASSWORD = 'api_password'
|
DATA_API_PASSWORD = 'api_password'
|
||||||
|
|
||||||
@ -58,11 +60,13 @@ def setup(hass, config):
|
|||||||
server_host = conf.get(CONF_SERVER_HOST, '0.0.0.0')
|
server_host = conf.get(CONF_SERVER_HOST, '0.0.0.0')
|
||||||
server_port = conf.get(CONF_SERVER_PORT, SERVER_PORT)
|
server_port = conf.get(CONF_SERVER_PORT, SERVER_PORT)
|
||||||
development = str(conf.get(CONF_DEVELOPMENT, "")) == "1"
|
development = str(conf.get(CONF_DEVELOPMENT, "")) == "1"
|
||||||
|
ssl_certificate = conf.get(CONF_SSL_CERTIFICATE)
|
||||||
|
ssl_key = conf.get(CONF_SSL_KEY)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
server = HomeAssistantHTTPServer(
|
server = HomeAssistantHTTPServer(
|
||||||
(server_host, server_port), RequestHandler, hass, api_password,
|
(server_host, server_port), RequestHandler, hass, api_password,
|
||||||
development)
|
development, ssl_certificate, ssl_key)
|
||||||
except OSError:
|
except OSError:
|
||||||
# If address already in use
|
# If address already in use
|
||||||
_LOGGER.exception("Error setting up HTTP server")
|
_LOGGER.exception("Error setting up HTTP server")
|
||||||
@ -74,7 +78,8 @@ def setup(hass, config):
|
|||||||
threading.Thread(target=server.start, daemon=True).start())
|
threading.Thread(target=server.start, daemon=True).start())
|
||||||
|
|
||||||
hass.http = server
|
hass.http = server
|
||||||
hass.config.api = rem.API(util.get_local_ip(), api_password, server_port)
|
hass.config.api = rem.API(util.get_local_ip(), api_password, server_port,
|
||||||
|
ssl_certificate is not None)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -89,7 +94,7 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
|
|||||||
|
|
||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments
|
||||||
def __init__(self, server_address, request_handler_class,
|
def __init__(self, server_address, request_handler_class,
|
||||||
hass, api_password, development):
|
hass, api_password, development, ssl_certificate, ssl_key):
|
||||||
super().__init__(server_address, request_handler_class)
|
super().__init__(server_address, request_handler_class)
|
||||||
|
|
||||||
self.server_address = server_address
|
self.server_address = server_address
|
||||||
@ -98,6 +103,7 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
|
|||||||
self.development = development
|
self.development = development
|
||||||
self.paths = []
|
self.paths = []
|
||||||
self.sessions = SessionStore()
|
self.sessions = SessionStore()
|
||||||
|
self.use_ssl = ssl_certificate is not None
|
||||||
|
|
||||||
# We will lazy init this one if needed
|
# We will lazy init this one if needed
|
||||||
self.event_forwarder = None
|
self.event_forwarder = None
|
||||||
@ -105,6 +111,12 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
|
|||||||
if development:
|
if development:
|
||||||
_LOGGER.info("running http in development mode")
|
_LOGGER.info("running http in development mode")
|
||||||
|
|
||||||
|
if ssl_certificate is not None:
|
||||||
|
wrap_kwargs = {'certfile': ssl_certificate}
|
||||||
|
if ssl_key is not None:
|
||||||
|
wrap_kwargs['keyfile'] = ssl_key
|
||||||
|
self.socket = ssl.wrap_socket(self.socket, **wrap_kwargs)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
""" Starts the HTTP server. """
|
""" Starts the HTTP server. """
|
||||||
def stop_http(event):
|
def stop_http(event):
|
||||||
@ -113,8 +125,11 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
|
|||||||
|
|
||||||
self.hass.bus.listen_once(ha.EVENT_HOMEASSISTANT_STOP, stop_http)
|
self.hass.bus.listen_once(ha.EVENT_HOMEASSISTANT_STOP, stop_http)
|
||||||
|
|
||||||
|
protocol = 'https' if self.use_ssl else 'http'
|
||||||
|
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"Starting web interface at http://%s:%d", *self.server_address)
|
"Starting web interface at %s://%s:%d",
|
||||||
|
protocol, self.server_address[0], self.server_address[1])
|
||||||
|
|
||||||
# 31-1-2015: Refactored frontend/api components out of this component
|
# 31-1-2015: Refactored frontend/api components out of this component
|
||||||
# To prevent stuff from breaking, load the two extracted components
|
# To prevent stuff from breaking, load the two extracted components
|
||||||
@ -187,17 +202,12 @@ class RequestHandler(SimpleHTTPRequestHandler):
|
|||||||
"Error parsing JSON", HTTP_UNPROCESSABLE_ENTITY)
|
"Error parsing JSON", HTTP_UNPROCESSABLE_ENTITY)
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.server.api_password is None:
|
self.authenticated = (self.server.api_password is None
|
||||||
self.authenticated = True
|
or self.headers.get(HTTP_HEADER_HA_AUTH) ==
|
||||||
elif HTTP_HEADER_HA_AUTH in self.headers:
|
self.server.api_password
|
||||||
api_password = self.headers.get(HTTP_HEADER_HA_AUTH)
|
or data.get(DATA_API_PASSWORD) ==
|
||||||
|
self.server.api_password
|
||||||
if not api_password and DATA_API_PASSWORD in data:
|
or self.verify_session())
|
||||||
api_password = data[DATA_API_PASSWORD]
|
|
||||||
|
|
||||||
self.authenticated = api_password == self.server.api_password
|
|
||||||
else:
|
|
||||||
self.authenticated = self.verify_session()
|
|
||||||
|
|
||||||
if '_METHOD' in data:
|
if '_METHOD' in data:
|
||||||
method = data.pop('_METHOD')
|
method = data.pop('_METHOD')
|
||||||
|
104
homeassistant/components/influxdb.py
Normal file
104
homeassistant/components/influxdb.py
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.influxdb
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
InfluxDB component which allows you to send data to an Influx database.
|
||||||
|
|
||||||
|
For more details about this component, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/influxdb/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import homeassistant.util as util
|
||||||
|
from homeassistant.helpers import validate_config
|
||||||
|
from homeassistant.const import (EVENT_STATE_CHANGED, STATE_ON, STATE_OFF,
|
||||||
|
STATE_UNLOCKED, STATE_LOCKED, STATE_UNKNOWN)
|
||||||
|
from homeassistant.components.sun import (STATE_ABOVE_HORIZON,
|
||||||
|
STATE_BELOW_HORIZON)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DOMAIN = "influxdb"
|
||||||
|
DEPENDENCIES = []
|
||||||
|
|
||||||
|
DEFAULT_HOST = 'localhost'
|
||||||
|
DEFAULT_PORT = 8086
|
||||||
|
DEFAULT_DATABASE = 'home_assistant'
|
||||||
|
|
||||||
|
REQUIREMENTS = ['influxdb==2.10.0']
|
||||||
|
|
||||||
|
CONF_HOST = 'host'
|
||||||
|
CONF_PORT = 'port'
|
||||||
|
CONF_DB_NAME = 'database'
|
||||||
|
CONF_USERNAME = 'username'
|
||||||
|
CONF_PASSWORD = 'password'
|
||||||
|
|
||||||
|
|
||||||
|
def setup(hass, config):
|
||||||
|
""" Setup the InfluxDB component. """
|
||||||
|
|
||||||
|
from influxdb import InfluxDBClient, exceptions
|
||||||
|
|
||||||
|
if not validate_config(config, {DOMAIN: ['host']}, _LOGGER):
|
||||||
|
return False
|
||||||
|
|
||||||
|
conf = config[DOMAIN]
|
||||||
|
|
||||||
|
host = conf[CONF_HOST]
|
||||||
|
port = util.convert(conf.get(CONF_PORT), int, DEFAULT_PORT)
|
||||||
|
database = util.convert(conf.get(CONF_DB_NAME), str, DEFAULT_DATABASE)
|
||||||
|
username = util.convert(conf.get(CONF_USERNAME), str)
|
||||||
|
password = util.convert(conf.get(CONF_PASSWORD), str)
|
||||||
|
|
||||||
|
try:
|
||||||
|
influx = InfluxDBClient(host=host, port=port, username=username,
|
||||||
|
password=password, database=database)
|
||||||
|
databases = [i['name'] for i in influx.get_list_database()]
|
||||||
|
except exceptions.InfluxDBClientError:
|
||||||
|
_LOGGER.error("Database host is not accessible. "
|
||||||
|
"Please check your entries in the configuration file.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if database not in databases:
|
||||||
|
_LOGGER.error("Database %s doesn't exist", database)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def influx_event_listener(event):
|
||||||
|
""" Listen for new messages on the bus and sends them to Influx. """
|
||||||
|
|
||||||
|
state = event.data.get('new_state')
|
||||||
|
|
||||||
|
if state is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if state.state in (STATE_ON, STATE_LOCKED, STATE_ABOVE_HORIZON):
|
||||||
|
_state = 1
|
||||||
|
elif state.state in (STATE_OFF, STATE_UNLOCKED, STATE_UNKNOWN,
|
||||||
|
STATE_BELOW_HORIZON):
|
||||||
|
_state = 0
|
||||||
|
else:
|
||||||
|
_state = state.state
|
||||||
|
|
||||||
|
measurement = state.attributes.get('unit_of_measurement', state.domain)
|
||||||
|
|
||||||
|
json_body = [
|
||||||
|
{
|
||||||
|
'measurement': measurement,
|
||||||
|
'tags': {
|
||||||
|
'domain': state.domain,
|
||||||
|
'entity_id': state.object_id,
|
||||||
|
},
|
||||||
|
'time': event.time_fired,
|
||||||
|
'fields': {
|
||||||
|
'value': _state,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
influx.write_points(json_body)
|
||||||
|
except exceptions.InfluxDBClientError:
|
||||||
|
_LOGGER.exception('Error saving event to InfluxDB')
|
||||||
|
|
||||||
|
hass.bus.listen(EVENT_STATE_CHANGED, influx_event_listener)
|
||||||
|
|
||||||
|
return True
|
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
homeassistant.components.rollershutter.demo
|
homeassistant.components.rollershutter.demo
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
Demo platform for rollorshutter component.
|
Demo platform for the rollorshutter component.
|
||||||
"""
|
"""
|
||||||
from homeassistant.const import EVENT_TIME_CHANGED
|
from homeassistant.const import EVENT_TIME_CHANGED
|
||||||
from homeassistant.helpers.event import track_utc_time_change
|
from homeassistant.helpers.event import track_utc_time_change
|
||||||
@ -9,7 +9,7 @@ from homeassistant.components.rollershutter import RollershutterDevice
|
|||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
""" Sets up the Demo binary sensors. """
|
""" Sets up the Demo rollershutters. """
|
||||||
add_devices([
|
add_devices([
|
||||||
DemoRollershutter(hass, 'Kitchen Window', 0),
|
DemoRollershutter(hass, 'Kitchen Window', 0),
|
||||||
DemoRollershutter(hass, 'Living Room Window', 100),
|
DemoRollershutter(hass, 'Living Room Window', 100),
|
||||||
@ -17,7 +17,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
|
|
||||||
|
|
||||||
class DemoRollershutter(RollershutterDevice):
|
class DemoRollershutter(RollershutterDevice):
|
||||||
""" Represents a rollershutter within Home Assistant. """
|
""" Represents a rollershutter.. """
|
||||||
# pylint: disable=no-self-use
|
# pylint: disable=no-self-use
|
||||||
|
|
||||||
def __init__(self, hass, name, position):
|
def __init__(self, hass, name, position):
|
||||||
@ -29,14 +29,17 @@ class DemoRollershutter(RollershutterDevice):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
|
""" Returns the name of the rollershutter. """
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
|
""" No polling needed for a demo rollershutter. """
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_position(self):
|
def current_position(self):
|
||||||
|
""" Returns the current position of the rollershutter. """
|
||||||
return self._position
|
return self._position
|
||||||
|
|
||||||
def move_up(self, **kwargs):
|
def move_up(self, **kwargs):
|
||||||
@ -62,11 +65,13 @@ class DemoRollershutter(RollershutterDevice):
|
|||||||
self._listener = None
|
self._listener = None
|
||||||
|
|
||||||
def _listen(self):
|
def _listen(self):
|
||||||
|
""" Listens for changes. """
|
||||||
if self._listener is None:
|
if self._listener is None:
|
||||||
self._listener = track_utc_time_change(self.hass,
|
self._listener = track_utc_time_change(self.hass,
|
||||||
self._time_changed)
|
self._time_changed)
|
||||||
|
|
||||||
def _time_changed(self, now):
|
def _time_changed(self, now):
|
||||||
|
""" Track time changes. """
|
||||||
if self._moving_up:
|
if self._moving_up:
|
||||||
self._position -= 10
|
self._position -= 10
|
||||||
else:
|
else:
|
||||||
|
@ -201,7 +201,7 @@ class Script(ToggleEntity):
|
|||||||
self._last_action)
|
self._last_action)
|
||||||
domain, service = split_entity_id(conf_service)
|
domain, service = split_entity_id(conf_service)
|
||||||
data = action.get(CONF_SERVICE_DATA, {})
|
data = action.get(CONF_SERVICE_DATA, {})
|
||||||
self.hass.services.call(domain, service, data)
|
self.hass.services.call(domain, service, data, True)
|
||||||
|
|
||||||
def _fire_event(self, action):
|
def _fire_event(self, action):
|
||||||
""" Fires an event. """
|
""" Fires an event. """
|
||||||
|
@ -127,15 +127,14 @@ class ArestSensor(Entity):
|
|||||||
if 'error' in values:
|
if 'error' in values:
|
||||||
return values['error']
|
return values['error']
|
||||||
elif 'value' in values:
|
elif 'value' in values:
|
||||||
if self._corr_factor is not None \
|
value = values['value']
|
||||||
and self._decimal_places is not None:
|
if self._corr_factor is not None:
|
||||||
return round((float(values['value']) *
|
value = float(value) * float(self._corr_factor)
|
||||||
float(self._corr_factor)), self._decimal_places)
|
if self._decimal_places is not None:
|
||||||
elif self._corr_factor is not None \
|
value = round(value, self._decimal_places)
|
||||||
and self._decimal_places is None:
|
if self._decimal_places == 0:
|
||||||
return round(float(values['value']) * float(self._corr_factor))
|
value = int(value)
|
||||||
else:
|
return value
|
||||||
return values['value']
|
|
||||||
else:
|
else:
|
||||||
return values.get(self._variable, 'n/a')
|
return values.get(self._variable, 'n/a')
|
||||||
|
|
||||||
|
@ -35,8 +35,8 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
|||||||
data,
|
data,
|
||||||
config.get('name', DEFAULT_NAME),
|
config.get('name', DEFAULT_NAME),
|
||||||
config.get('unit_of_measurement'),
|
config.get('unit_of_measurement'),
|
||||||
config.get('correction_factor', 1.0),
|
config.get('correction_factor', None),
|
||||||
config.get('decimal_places', 0)
|
config.get('decimal_places', None)
|
||||||
)])
|
)])
|
||||||
|
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ class CommandSensor(Entity):
|
|||||||
self._name = name
|
self._name = name
|
||||||
self._state = False
|
self._state = False
|
||||||
self._unit_of_measurement = unit_of_measurement
|
self._unit_of_measurement = unit_of_measurement
|
||||||
self._corr_factor = float(corr_factor)
|
self._corr_factor = corr_factor
|
||||||
self._decimal_places = decimal_places
|
self._decimal_places = decimal_places
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
@ -76,9 +76,11 @@ class CommandSensor(Entity):
|
|||||||
try:
|
try:
|
||||||
if value is not None:
|
if value is not None:
|
||||||
if self._corr_factor is not None:
|
if self._corr_factor is not None:
|
||||||
self._state = round((float(value) * self._corr_factor),
|
value = float(value) * float(self._corr_factor)
|
||||||
self._decimal_places)
|
if self._decimal_places is not None:
|
||||||
else:
|
value = round(value, self._decimal_places)
|
||||||
|
if self._decimal_places == 0:
|
||||||
|
value = int(value)
|
||||||
self._state = value
|
self._state = value
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self._state = value
|
self._state = value
|
||||||
|
@ -147,16 +147,7 @@ class ForeCastSensor(Entity):
|
|||||||
self._state = data.nearestStormBearing
|
self._state = data.nearestStormBearing
|
||||||
elif self.type == 'precip_intensity':
|
elif self.type == 'precip_intensity':
|
||||||
self._state = data.precipIntensity
|
self._state = data.precipIntensity
|
||||||
if data.precipIntensity == 0:
|
|
||||||
self._state = 'None'
|
|
||||||
self._unit_of_measurement = ''
|
|
||||||
else:
|
|
||||||
self._state = data.precipIntensity
|
|
||||||
elif self.type == 'precip_type':
|
elif self.type == 'precip_type':
|
||||||
if data.precipType is None:
|
|
||||||
self._state = 'None'
|
|
||||||
self._unit_of_measurement = ''
|
|
||||||
else:
|
|
||||||
self._state = data.precipType
|
self._state = data.precipType
|
||||||
elif self.type == 'precip_probability':
|
elif self.type == 'precip_probability':
|
||||||
self._state = round(data.precipProbability * 100, 1)
|
self._state = round(data.precipProbability * 100, 1)
|
||||||
|
@ -136,17 +136,12 @@ class RestSensor(Entity):
|
|||||||
try:
|
try:
|
||||||
if value is not None:
|
if value is not None:
|
||||||
value = RestSensor.extract_value(value, self._variable)
|
value = RestSensor.extract_value(value, self._variable)
|
||||||
if self._corr_factor is not None \
|
if self._corr_factor is not None:
|
||||||
and self._decimal_places is not None:
|
value = float(value) * float(self._corr_factor)
|
||||||
self._state = round(
|
if self._decimal_places is not None:
|
||||||
(float(value) *
|
value = round(value, self._decimal_places)
|
||||||
float(self._corr_factor)),
|
if self._decimal_places == 0:
|
||||||
self._decimal_places)
|
value = int(value)
|
||||||
elif self._corr_factor is not None \
|
|
||||||
and self._decimal_places is None:
|
|
||||||
self._state = round(float(value) *
|
|
||||||
float(self._corr_factor))
|
|
||||||
else:
|
|
||||||
self._state = value
|
self._state = value
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self._state = RestSensor.extract_value(value, self._variable)
|
self._state = RestSensor.extract_value(value, self._variable)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
""" Constants used by Home Assistant components. """
|
""" Constants used by Home Assistant components. """
|
||||||
|
|
||||||
__version__ = "0.9.0.dev0"
|
__version__ = "0.10.0.dev0"
|
||||||
|
|
||||||
# Can be used to specify a catch all when registering state or event listeners.
|
# Can be used to specify a catch all when registering state or event listeners.
|
||||||
MATCH_ALL = '*'
|
MATCH_ALL = '*'
|
||||||
|
@ -25,7 +25,7 @@ from homeassistant.const import (
|
|||||||
from homeassistant.exceptions import (
|
from homeassistant.exceptions import (
|
||||||
HomeAssistantError, InvalidEntityFormatError)
|
HomeAssistantError, InvalidEntityFormatError)
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
import homeassistant.util.dt as date_util
|
import homeassistant.util.dt as dt_util
|
||||||
import homeassistant.util.location as location
|
import homeassistant.util.location as location
|
||||||
import homeassistant.helpers.temperature as temp_helper
|
import homeassistant.helpers.temperature as temp_helper
|
||||||
from homeassistant.config import get_default_config_dir
|
from homeassistant.config import get_default_config_dir
|
||||||
@ -196,8 +196,8 @@ class Event(object):
|
|||||||
self.event_type = event_type
|
self.event_type = event_type
|
||||||
self.data = data or {}
|
self.data = data or {}
|
||||||
self.origin = origin
|
self.origin = origin
|
||||||
self.time_fired = date_util.strip_microseconds(
|
self.time_fired = dt_util.strip_microseconds(
|
||||||
time_fired or date_util.utcnow())
|
time_fired or dt_util.utcnow())
|
||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
""" Returns a dict representation of this Event. """
|
""" Returns a dict representation of this Event. """
|
||||||
@ -205,7 +205,7 @@ class Event(object):
|
|||||||
'event_type': self.event_type,
|
'event_type': self.event_type,
|
||||||
'data': dict(self.data),
|
'data': dict(self.data),
|
||||||
'origin': str(self.origin),
|
'origin': str(self.origin),
|
||||||
'time_fired': date_util.datetime_to_str(self.time_fired),
|
'time_fired': dt_util.datetime_to_str(self.time_fired),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@ -351,14 +351,14 @@ class State(object):
|
|||||||
self.entity_id = entity_id.lower()
|
self.entity_id = entity_id.lower()
|
||||||
self.state = state
|
self.state = state
|
||||||
self.attributes = attributes or {}
|
self.attributes = attributes or {}
|
||||||
self.last_updated = date_util.strip_microseconds(
|
self.last_updated = dt_util.strip_microseconds(
|
||||||
last_updated or date_util.utcnow())
|
last_updated or dt_util.utcnow())
|
||||||
|
|
||||||
# Strip microsecond from last_changed else we cannot guarantee
|
# Strip microsecond from last_changed else we cannot guarantee
|
||||||
# state == State.from_dict(state.as_dict())
|
# state == State.from_dict(state.as_dict())
|
||||||
# This behavior occurs because to_dict uses datetime_to_str
|
# This behavior occurs because to_dict uses datetime_to_str
|
||||||
# which does not preserve microseconds
|
# which does not preserve microseconds
|
||||||
self.last_changed = date_util.strip_microseconds(
|
self.last_changed = dt_util.strip_microseconds(
|
||||||
last_changed or self.last_updated)
|
last_changed or self.last_updated)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -381,7 +381,8 @@ class State(object):
|
|||||||
def copy(self):
|
def copy(self):
|
||||||
""" Creates a copy of itself. """
|
""" Creates a copy of itself. """
|
||||||
return State(self.entity_id, self.state,
|
return State(self.entity_id, self.state,
|
||||||
dict(self.attributes), self.last_changed)
|
dict(self.attributes), self.last_changed,
|
||||||
|
self.last_updated)
|
||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
""" Converts State to a dict to be used within JSON.
|
""" Converts State to a dict to be used within JSON.
|
||||||
@ -390,8 +391,8 @@ class State(object):
|
|||||||
return {'entity_id': self.entity_id,
|
return {'entity_id': self.entity_id,
|
||||||
'state': self.state,
|
'state': self.state,
|
||||||
'attributes': self.attributes,
|
'attributes': self.attributes,
|
||||||
'last_changed': date_util.datetime_to_str(self.last_changed),
|
'last_changed': dt_util.datetime_to_str(self.last_changed),
|
||||||
'last_updated': date_util.datetime_to_str(self.last_updated)}
|
'last_updated': dt_util.datetime_to_str(self.last_updated)}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, json_dict):
|
def from_dict(cls, json_dict):
|
||||||
@ -406,12 +407,12 @@ class State(object):
|
|||||||
last_changed = json_dict.get('last_changed')
|
last_changed = json_dict.get('last_changed')
|
||||||
|
|
||||||
if last_changed:
|
if last_changed:
|
||||||
last_changed = date_util.str_to_datetime(last_changed)
|
last_changed = dt_util.str_to_datetime(last_changed)
|
||||||
|
|
||||||
last_updated = json_dict.get('last_updated')
|
last_updated = json_dict.get('last_updated')
|
||||||
|
|
||||||
if last_updated:
|
if last_updated:
|
||||||
last_updated = date_util.str_to_datetime(last_updated)
|
last_updated = dt_util.str_to_datetime(last_updated)
|
||||||
|
|
||||||
return cls(json_dict['entity_id'], json_dict['state'],
|
return cls(json_dict['entity_id'], json_dict['state'],
|
||||||
json_dict.get('attributes'), last_changed, last_updated)
|
json_dict.get('attributes'), last_changed, last_updated)
|
||||||
@ -428,7 +429,7 @@ class State(object):
|
|||||||
|
|
||||||
return "<state {}={}{} @ {}>".format(
|
return "<state {}={}{} @ {}>".format(
|
||||||
self.entity_id, self.state, attr,
|
self.entity_id, self.state, attr,
|
||||||
date_util.datetime_to_local_str(self.last_changed))
|
dt_util.datetime_to_local_str(self.last_changed))
|
||||||
|
|
||||||
|
|
||||||
class StateMachine(object):
|
class StateMachine(object):
|
||||||
@ -732,7 +733,7 @@ class Config(object):
|
|||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
""" Converts config to a dictionary. """
|
""" Converts config to a dictionary. """
|
||||||
time_zone = self.time_zone or date_util.UTC
|
time_zone = self.time_zone or dt_util.UTC
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'latitude': self.latitude,
|
'latitude': self.latitude,
|
||||||
@ -766,7 +767,7 @@ def create_timer(hass, interval=TIMER_INTERVAL):
|
|||||||
|
|
||||||
last_fired_on_second = -1
|
last_fired_on_second = -1
|
||||||
|
|
||||||
calc_now = date_util.utcnow
|
calc_now = dt_util.utcnow
|
||||||
|
|
||||||
while not stop_event.isSet():
|
while not stop_event.isSet():
|
||||||
now = calc_now()
|
now = calc_now()
|
||||||
@ -832,6 +833,6 @@ def create_worker_pool(worker_count=None):
|
|||||||
|
|
||||||
for start, job in current_jobs:
|
for start, job in current_jobs:
|
||||||
_LOGGER.warning("WorkerPool:Current job from %s: %s",
|
_LOGGER.warning("WorkerPool:Current job from %s: %s",
|
||||||
date_util.datetime_to_local_str(start), job)
|
dt_util.datetime_to_local_str(start), job)
|
||||||
|
|
||||||
return util.ThreadPool(job_handler, worker_count, busy_callback)
|
return util.ThreadPool(job_handler, worker_count, busy_callback)
|
||||||
|
@ -51,10 +51,13 @@ 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
|
||||||
|
|
||||||
def __init__(self, host, api_password=None, port=None):
|
def __init__(self, host, api_password=None, port=None, use_ssl=False):
|
||||||
self.host = host
|
self.host = host
|
||||||
self.port = port or SERVER_PORT
|
self.port = port or SERVER_PORT
|
||||||
self.api_password = api_password
|
self.api_password = api_password
|
||||||
|
if use_ssl:
|
||||||
|
self.base_url = "https://{}:{}".format(host, self.port)
|
||||||
|
else:
|
||||||
self.base_url = "http://{}:{}".format(host, self.port)
|
self.base_url = "http://{}:{}".format(host, self.port)
|
||||||
self.status = None
|
self.status = None
|
||||||
self._headers = {}
|
self._headers = {}
|
||||||
|
@ -21,11 +21,14 @@ pysnmp==4.2.5
|
|||||||
netdisco==0.5.2
|
netdisco==0.5.2
|
||||||
|
|
||||||
# homeassistant.components.ecobee
|
# homeassistant.components.ecobee
|
||||||
https://github.com/nkgilley/python-ecobee-api/archive/5645f843b64ac4f6e59dfb96233a07083c5e10c1.zip#python-ecobee==0.0.3
|
https://github.com/nkgilley/python-ecobee-api/archive/92a2f330cbaf601d0618456fdd97e5a8c42c1c47.zip#python-ecobee==0.0.4
|
||||||
|
|
||||||
# homeassistant.components.ifttt
|
# homeassistant.components.ifttt
|
||||||
pyfttt==0.3
|
pyfttt==0.3
|
||||||
|
|
||||||
|
# homeassistant.components.influxdb
|
||||||
|
influxdb==2.10.0
|
||||||
|
|
||||||
# homeassistant.components.isy994
|
# homeassistant.components.isy994
|
||||||
PyISY==1.0.5
|
PyISY==1.0.5
|
||||||
|
|
||||||
@ -157,7 +160,7 @@ orvibo==1.0.1
|
|||||||
pywemo==0.3.3
|
pywemo==0.3.3
|
||||||
|
|
||||||
# homeassistant.components.thermostat.honeywell
|
# homeassistant.components.thermostat.honeywell
|
||||||
evohomeclient==0.2.3
|
evohomeclient==0.2.4
|
||||||
|
|
||||||
# homeassistant.components.thermostat.nest
|
# homeassistant.components.thermostat.nest
|
||||||
python-nest==2.6.0
|
python-nest==2.6.0
|
||||||
|
@ -7,6 +7,7 @@ npm run frontend_prod
|
|||||||
|
|
||||||
cp bower_components/webcomponentsjs/webcomponents-lite.min.js ..
|
cp bower_components/webcomponentsjs/webcomponents-lite.min.js ..
|
||||||
cp build/frontend.html ..
|
cp build/frontend.html ..
|
||||||
|
cp build/service_worker.js ..
|
||||||
|
|
||||||
# Generate the MD5 hash of the new frontend
|
# Generate the MD5 hash of the new frontend
|
||||||
cd ../..
|
cd ../..
|
||||||
|
@ -66,18 +66,31 @@ class TestAPI(unittest.TestCase):
|
|||||||
|
|
||||||
# TODO move back to http component and test with use_auth.
|
# TODO move back to http component and test with use_auth.
|
||||||
def test_access_denied_without_password(self):
|
def test_access_denied_without_password(self):
|
||||||
req = requests.get(
|
req = requests.get(_url(const.URL_API))
|
||||||
_url(const.URL_API_STATES_ENTITY.format("test")))
|
|
||||||
|
|
||||||
self.assertEqual(401, req.status_code)
|
self.assertEqual(401, req.status_code)
|
||||||
|
|
||||||
def test_access_denied_with_wrong_password(self):
|
def test_access_denied_with_wrong_password(self):
|
||||||
req = requests.get(
|
req = requests.get(
|
||||||
_url(const.URL_API_STATES_ENTITY.format("test")),
|
_url(const.URL_API),
|
||||||
headers={const.HTTP_HEADER_HA_AUTH: 'wrongpassword'})
|
headers={const.HTTP_HEADER_HA_AUTH: 'wrongpassword'})
|
||||||
|
|
||||||
self.assertEqual(401, req.status_code)
|
self.assertEqual(401, req.status_code)
|
||||||
|
|
||||||
|
def test_access_with_password_in_url(self):
|
||||||
|
req = requests.get(
|
||||||
|
"{}?api_password={}".format(_url(const.URL_API), API_PASSWORD))
|
||||||
|
|
||||||
|
self.assertEqual(200, req.status_code)
|
||||||
|
|
||||||
|
def test_access_via_session(self):
|
||||||
|
session = requests.Session()
|
||||||
|
req = session.get(_url(const.URL_API), headers=HA_HEADERS)
|
||||||
|
self.assertEqual(200, req.status_code)
|
||||||
|
|
||||||
|
req = session.get(_url(const.URL_API))
|
||||||
|
self.assertEqual(200, req.status_code)
|
||||||
|
|
||||||
def test_api_list_state_entities(self):
|
def test_api_list_state_entities(self):
|
||||||
""" Test if the debug interface allows us to list state entities. """
|
""" Test if the debug interface allows us to list state entities. """
|
||||||
req = requests.get(_url(const.URL_API_STATES),
|
req = requests.get(_url(const.URL_API_STATES),
|
||||||
|
@ -268,7 +268,15 @@ class TestState(unittest.TestCase):
|
|||||||
|
|
||||||
def test_copy(self):
|
def test_copy(self):
|
||||||
state = ha.State('domain.hello', 'world', {'some': 'attr'})
|
state = ha.State('domain.hello', 'world', {'some': 'attr'})
|
||||||
self.assertEqual(state, state.copy())
|
# Patch dt_util.utcnow() so we know last_updated got copied too
|
||||||
|
with patch('homeassistant.core.dt_util.utcnow',
|
||||||
|
return_value=dt_util.utcnow() + timedelta(seconds=10)):
|
||||||
|
copy = state.copy()
|
||||||
|
self.assertEqual(state.entity_id, copy.entity_id)
|
||||||
|
self.assertEqual(state.state, copy.state)
|
||||||
|
self.assertEqual(state.attributes, copy.attributes)
|
||||||
|
self.assertEqual(state.last_changed, copy.last_changed)
|
||||||
|
self.assertEqual(state.last_updated, copy.last_updated)
|
||||||
|
|
||||||
def test_dict_conversion(self):
|
def test_dict_conversion(self):
|
||||||
state = ha.State('domain.hello', 'world', {'some': 'attr'})
|
state = ha.State('domain.hello', 'world', {'some': 'attr'})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user