Merge branch 'dev' of https://github.com/balloob/home-assistant into limitlessled

This commit is contained in:
happyleaves 2015-11-29 12:44:44 -05:00
commit 52b39efc51
86 changed files with 1835 additions and 872 deletions

View file

@ -17,6 +17,9 @@ omit =
homeassistant/components/*/tellstick.py
homeassistant/components/*/vera.py
homeassistant/components/ecobee.py
homeassistant/components/*/ecobee.py
homeassistant/components/verisure.py
homeassistant/components/*/verisure.py

View file

@ -82,7 +82,7 @@ def _setup_component(hass, domain, config):
return True
component = loader.get_component(domain)
missing_deps = [dep for dep in component.DEPENDENCIES
missing_deps = [dep for dep in getattr(component, 'DEPENDENCIES', [])
if dep not in hass.config.components]
if missing_deps:
@ -106,7 +106,7 @@ def _setup_component(hass, domain, config):
# Assumption: if a component does not depend on groups
# it communicates with devices
if group.DOMAIN not in component.DEPENDENCIES:
if group.DOMAIN not in getattr(component, 'DEPENDENCIES', []):
hass.pool.add_worker()
hass.bus.fire(
@ -133,14 +133,13 @@ def prepare_setup_platform(hass, config, domain, platform_name):
return platform
# Load dependencies
if hasattr(platform, 'DEPENDENCIES'):
for component in platform.DEPENDENCIES:
if not setup_component(hass, component, config):
_LOGGER.error(
'Unable to prepare setup for platform %s because '
'dependency %s could not be initialized', platform_path,
component)
return None
for component in getattr(platform, 'DEPENDENCIES', []):
if not setup_component(hass, component, config):
_LOGGER.error(
'Unable to prepare setup for platform %s because '
'dependency %s could not be initialized', platform_path,
component)
return None
if not _handle_requirements(hass, platform, platform_path):
return None

View file

@ -15,7 +15,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
DOMAIN = 'alarm_control_panel'
DEPENDENCIES = []
SCAN_INTERVAL = 30
ENTITY_ID_FORMAT = DOMAIN + '.{}'

View file

@ -18,8 +18,6 @@ from homeassistant.const import (
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = []
DEFAULT_ALARM_NAME = 'HA Alarm'
DEFAULT_PENDING_TIME = 60
DEFAULT_TRIGGER_TIME = 120

View file

@ -18,10 +18,10 @@ from homeassistant.bootstrap import ERROR_LOG_FILENAME
from homeassistant.const import (
URL_API, URL_API_STATES, URL_API_EVENTS, URL_API_SERVICES, URL_API_STREAM,
URL_API_EVENT_FORWARD, URL_API_STATES_ENTITY, URL_API_COMPONENTS,
URL_API_CONFIG, URL_API_BOOTSTRAP, URL_API_ERROR_LOG,
URL_API_CONFIG, URL_API_BOOTSTRAP, URL_API_ERROR_LOG, URL_API_LOG_OUT,
EVENT_TIME_CHANGED, EVENT_HOMEASSISTANT_STOP, MATCH_ALL,
HTTP_OK, HTTP_CREATED, HTTP_BAD_REQUEST, HTTP_NOT_FOUND,
HTTP_UNPROCESSABLE_ENTITY, CONTENT_TYPE_TEXT_PLAIN)
HTTP_UNPROCESSABLE_ENTITY)
DOMAIN = 'api'
@ -36,10 +36,6 @@ _LOGGER = logging.getLogger(__name__)
def setup(hass, config):
""" Register the API with the HTTP interface. """
if 'http' not in hass.config.components:
_LOGGER.error('Dependency http is not loaded')
return False
# /api - for validation purposes
hass.http.register_path('GET', URL_API, _handle_get_api)
@ -93,6 +89,8 @@ def setup(hass, config):
hass.http.register_path('GET', URL_API_ERROR_LOG,
_handle_get_api_error_log)
hass.http.register_path('POST', URL_API_LOG_OUT, _handle_post_api_log_out)
return True
@ -108,6 +106,7 @@ def _handle_get_api_stream(handler, path_match, data):
wfile = handler.wfile
write_lock = threading.Lock()
block = threading.Event()
session_id = None
restrict = data.get('restrict')
if restrict:
@ -121,6 +120,7 @@ def _handle_get_api_stream(handler, path_match, data):
try:
wfile.write(msg.encode("UTF-8"))
wfile.flush()
handler.server.sessions.extend_validation(session_id)
except IOError:
block.set()
@ -140,6 +140,7 @@ def _handle_get_api_stream(handler, path_match, data):
handler.send_response(HTTP_OK)
handler.send_header('Content-type', 'text/event-stream')
session_id = handler.set_session_cookie_header()
handler.end_headers()
hass.bus.listen(MATCH_ALL, forward_events)
@ -347,9 +348,15 @@ def _handle_get_api_components(handler, path_match, data):
def _handle_get_api_error_log(handler, path_match, data):
""" Returns the logged errors for this session. """
error_path = handler.server.hass.config.path(ERROR_LOG_FILENAME)
with open(error_path, 'rb') as error_log:
handler.write_file_pointer(CONTENT_TYPE_TEXT_PLAIN, error_log)
handler.write_file(handler.server.hass.config.path(ERROR_LOG_FILENAME),
False)
def _handle_post_api_log_out(handler, path_match, data):
""" Log user out. """
handler.send_response(HTTP_OK)
handler.destroy_session()
handler.end_headers()
def _services_json(hass):

View file

@ -19,7 +19,6 @@ from homeassistant.const import (EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STOP)
DOMAIN = "arduino"
DEPENDENCIES = []
REQUIREMENTS = ['PyMata==2.07a']
BOARD = None
_LOGGER = logging.getLogger(__name__)

View file

@ -14,7 +14,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.const import (STATE_ON, STATE_OFF)
DOMAIN = 'binary_sensor'
DEPENDENCIES = []
SCAN_INTERVAL = 30
ENTITY_ID_FORMAT = DOMAIN + '.{}'

View file

@ -8,7 +8,6 @@ https://home-assistant.io/components/browser/
"""
DOMAIN = "browser"
DEPENDENCIES = []
SERVICE_BROWSE_URL = "browse_url"

View file

@ -80,19 +80,21 @@ def setup(hass, config):
def _proxy_camera_image(handler, path_match, data):
""" Proxies the camera image via the HA server. """
entity_id = path_match.group(ATTR_ENTITY_ID)
camera = component.entities.get(entity_id)
camera = None
if entity_id in component.entities.keys():
camera = component.entities[entity_id]
if camera:
response = camera.camera_image()
if response is not None:
handler.wfile.write(response)
else:
handler.send_response(HTTP_NOT_FOUND)
else:
if camera is None:
handler.send_response(HTTP_NOT_FOUND)
handler.end_headers()
return
response = camera.camera_image()
if response is None:
handler.send_response(HTTP_NOT_FOUND)
handler.end_headers()
return
handler.wfile.write(response)
hass.http.register_path(
'GET',
@ -108,12 +110,9 @@ def setup(hass, config):
stream even with only a still image URL available.
"""
entity_id = path_match.group(ATTR_ENTITY_ID)
camera = component.entities.get(entity_id)
camera = None
if entity_id in component.entities.keys():
camera = component.entities[entity_id]
if not camera:
if camera is None:
handler.send_response(HTTP_NOT_FOUND)
handler.end_headers()
return
@ -131,7 +130,6 @@ def setup(hass, config):
# MJPEG_START_HEADER.format()
while True:
img_bytes = camera.camera_image()
if img_bytes is None:
continue
@ -148,12 +146,12 @@ def setup(hass, config):
handler.request.sendall(
bytes('--jpgboundary\r\n', 'utf-8'))
time.sleep(0.5)
except (requests.RequestException, IOError):
camera.is_streaming = False
camera.update_ha_state()
camera.is_streaming = False
hass.http.register_path(
'GET',
re.compile(

View file

@ -4,8 +4,8 @@ homeassistant.components.camera.demo
Demo platform that has a fake camera.
"""
import os
from random import randint
from homeassistant.components.camera import Camera
import homeassistant.util.dt as dt_util
def setup_platform(hass, config, add_devices, discovery_info=None):
@ -24,12 +24,12 @@ class DemoCamera(Camera):
def camera_image(self):
""" Return a faked still image response. """
now = dt_util.utcnow()
image_path = os.path.join(os.path.dirname(__file__),
'demo_{}.png'.format(randint(1, 5)))
'demo_{}.jpg'.format(now.second % 4))
with open(image_path, 'rb') as file:
output = file.read()
return output
return file.read()
@property
def name(self):

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

View file

@ -15,7 +15,6 @@ from homeassistant.helpers import generate_entity_id
from homeassistant.const import EVENT_TIME_CHANGED
DOMAIN = "configurator"
DEPENDENCIES = []
ENTITY_ID_FORMAT = DOMAIN + ".{}"
SERVICE_CONFIGURE = "configure"

View file

@ -14,7 +14,6 @@ from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF)
DOMAIN = "conversation"
DEPENDENCIES = []
SERVICE_PROCESS = "process"

View file

@ -98,7 +98,7 @@ class NmapDeviceScanner(object):
from nmap import PortScanner, PortScannerError
scanner = PortScanner()
options = "-F --host-timeout 5"
options = "-F --host-timeout 5s"
if self.home_interval:
boundary = dt_util.now() - self.home_interval

View file

@ -17,8 +17,7 @@ from homeassistant.const import (
ATTR_SERVICE, ATTR_DISCOVERED)
DOMAIN = "discovery"
DEPENDENCIES = []
REQUIREMENTS = ['netdisco==0.5.1']
REQUIREMENTS = ['netdisco==0.5.2']
SCAN_INTERVAL = 300 # seconds

View file

@ -15,7 +15,6 @@ from homeassistant.helpers import validate_config
from homeassistant.util import sanitize_filename
DOMAIN = "downloader"
DEPENDENCIES = []
SERVICE_DOWNLOAD_FILE = "download_file"

View file

@ -0,0 +1,154 @@
"""
homeassistant.components.ecobee
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Ecobee Component
This component adds support for Ecobee3 Wireless Thermostats.
You will need to setup developer access to your thermostat,
and create and API key on the ecobee website.
The first time you run this component you will see a configuration
component card in Home Assistant. This card will contain a PIN code
that you will need to use to authorize access to your thermostat. You
can do this at https://www.ecobee.com/consumerportal/index.html
Click My Apps, Add application, Enter Pin and click Authorize.
After authorizing the application click the button in the configuration
card. Now your thermostat and sensors should shown in home-assistant.
You can use the optional hold_temp parameter to set whether or not holds
are set indefintely or until the next scheduled event.
ecobee:
api_key: asdfasdfasdfasdfasdfaasdfasdfasdfasdf
hold_temp: True
"""
from homeassistant.loader import get_component
from homeassistant import bootstrap
from homeassistant.util import Throttle
from homeassistant.const import (
EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, ATTR_DISCOVERED, CONF_API_KEY)
from datetime import timedelta
import logging
import os
DOMAIN = "ecobee"
DISCOVER_THERMOSTAT = "ecobee.thermostat"
DISCOVER_SENSORS = "ecobee.sensor"
NETWORK = None
HOLD_TEMP = 'hold_temp'
REQUIREMENTS = [
'https://github.com/nkgilley/python-ecobee-api/archive/'
'd35596b67c75451fa47001c493a15eebee195e93.zip#python-ecobee==0.0.1']
_LOGGER = logging.getLogger(__name__)
ECOBEE_CONFIG_FILE = 'ecobee.conf'
_CONFIGURING = {}
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=180)
def request_configuration(network, hass, config):
""" Request configuration steps from the user. """
configurator = get_component('configurator')
if 'ecobee' in _CONFIGURING:
configurator.notify_errors(
_CONFIGURING['ecobee'], "Failed to register, please try again.")
return
# pylint: disable=unused-argument
def ecobee_configuration_callback(callback_data):
""" Actions to do when our configuration callback is called. """
network.request_tokens()
network.update()
setup_ecobee(hass, network, config)
_CONFIGURING['ecobee'] = configurator.request_config(
hass, "Ecobee", ecobee_configuration_callback,
description=(
'Please authorize this app at https://www.ecobee.com/consumer'
'portal/index.html with pin code: ' + network.pin),
description_image="/static/images/config_ecobee_thermostat.png",
submit_caption="I have authorized the app."
)
def setup_ecobee(hass, network, config):
""" Setup ecobee thermostat """
# If ecobee has a PIN then it needs to be configured.
if network.pin is not None:
request_configuration(network, hass, config)
return
if 'ecobee' in _CONFIGURING:
configurator = get_component('configurator')
configurator.request_done(_CONFIGURING.pop('ecobee'))
# Ensure component is loaded
bootstrap.setup_component(hass, 'thermostat', config)
bootstrap.setup_component(hass, 'sensor', config)
hold_temp = config[DOMAIN].get(HOLD_TEMP, False)
# Fire thermostat discovery event
hass.bus.fire(EVENT_PLATFORM_DISCOVERED, {
ATTR_SERVICE: DISCOVER_THERMOSTAT,
ATTR_DISCOVERED: {'hold_temp': hold_temp}
})
# Fire sensor discovery event
hass.bus.fire(EVENT_PLATFORM_DISCOVERED, {
ATTR_SERVICE: DISCOVER_SENSORS,
ATTR_DISCOVERED: {}
})
# pylint: disable=too-few-public-methods
class EcobeeData(object):
""" Gets the latest data and update the states. """
def __init__(self, config_file):
from pyecobee import Ecobee
self.ecobee = Ecobee(config_file)
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
""" Get the latest data from pyecobee. """
self.ecobee.update()
_LOGGER.info("ecobee data updated successfully.")
def setup(hass, config):
"""
Setup Ecobee.
Will automatically load thermostat and sensor components to support
devices discovered on the network.
"""
# pylint: disable=global-statement, import-error
global NETWORK
if 'ecobee' in _CONFIGURING:
return
from pyecobee import config_from_file
# Create ecobee.conf if it doesn't exist
if not os.path.isfile(hass.config.path(ECOBEE_CONFIG_FILE)):
if config[DOMAIN].get(CONF_API_KEY) is None:
_LOGGER.error("No ecobee api_key found in config.")
return
jsonconfig = {"API_KEY": config[DOMAIN].get(CONF_API_KEY)}
config_from_file(hass.config.path(ECOBEE_CONFIG_FILE), jsonconfig)
NETWORK = EcobeeData(hass.config.path(ECOBEE_CONFIG_FILE))
setup_ecobee(hass, NETWORK.ecobee, config)
return True

View file

@ -54,8 +54,7 @@ def setup(hass, config):
def _handle_get_root(handler, path_match, data):
""" Renders the debug interface. """
""" Renders the frontend. """
handler.send_response(HTTP_OK)
handler.send_header('Content-type', 'text/html; charset=utf-8')
handler.end_headers()
@ -66,7 +65,7 @@ def _handle_get_root(handler, path_match, data):
app_url = "frontend-{}.html".format(version.VERSION)
# auto login if no password was set, else check api_password param
auth = ('no_password_set' if handler.server.no_password_set
auth = ('no_password_set' if handler.server.api_password is None
else data.get('api_password', ''))
with open(INDEX_PATH) as template_file:

View file

@ -4,16 +4,13 @@
<meta charset="utf-8">
<title>Home Assistant</title>
<link rel='manifest' href='/static/manifest.json' />
<link rel='shortcut icon' href='/static/favicon.ico' />
<link rel='icon' type='image/png'
href='/static/favicon-192x192.png' sizes='192x192'>
<link rel='manifest' href='/static/manifest.json'>
<link rel='icon' href='/static/favicon.ico'>
<link rel='apple-touch-icon' sizes='180x180'
href='/static/favicon-apple-180x180.png'>
href='/static/favicon-apple-180x180.png'>
<meta name='apple-mobile-web-app-capable' content='yes'>
<meta name='mobile-web-app-capable' content='yes'>
<meta name='viewport' content='width=device-width,
user-scalable=no' />
<meta name='viewport' content='width=device-width, user-scalable=no'>
<meta name='theme-color' content='#03a9f4'>
<style>
#init {
@ -26,24 +23,17 @@
justify-content: center;
align-items: center;
text-align: center;
font-family: 'Roboto', 'Noto', sans-serif;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
#init div {
line-height: 34px;
margin-bottom: 89px;
margin-bottom: 123px;
}
</style>
</head>
<body fullbleed>
<div id='init'>
<img src='/static/splash.png' height='230' />
<div>Initializing</div>
</div>
<div id='init'><img src='/static/favicon-192x192.png' height='192'></div>
<script src='/static/webcomponents-lite.min.js'></script>
<link rel='import' href='/static/{{ app_url }}' />
<home-assistant auth='{{ auth }}' icons='{{ icons }}'></home-assistant>

View file

@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """
VERSION = "dff74f773ea8b0356b0bd8130ed6f0cf"
VERSION = "36df87bb6c219a2ee59adf416e3abdfa"

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit 39e09d85b74afb332ad2872b5aa556c9c9d113c3
Subproject commit 33124030f6d119ad3a58cb520062f2aa58022c6d

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View file

@ -3,12 +3,17 @@
"short_name": "Assistant",
"start_url": "/",
"display": "standalone",
"theme_color": "#03A9F4",
"icons": [
{
"src": "\/static\/favicon-192x192.png",
"src": "/static/favicon-192x192.png",
"sizes": "192x192",
"type": "image\/png",
"density": "4.0"
"type": "image/png",
},
{
"src": "/static/favicon-384x384.png",
"sizes": "384x384",
"type": "image/png",
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

File diff suppressed because one or more lines are too long

View file

@ -17,7 +17,6 @@ from homeassistant.const import (
STATE_UNKNOWN)
DOMAIN = "group"
DEPENDENCIES = []
ENTITY_ID_FORMAT = DOMAIN + ".{}"

View file

@ -12,10 +12,7 @@ import logging
import time
import gzip
import os
import random
import string
from datetime import timedelta
from homeassistant.util import Throttle
from http.server import SimpleHTTPRequestHandler, HTTPServer
from http import cookies
from socketserver import ThreadingMixIn
@ -34,7 +31,6 @@ import homeassistant.util.dt as date_util
import homeassistant.bootstrap as bootstrap
DOMAIN = "http"
DEPENDENCIES = []
CONF_API_PASSWORD = "api_password"
CONF_SERVER_HOST = "server_host"
@ -45,40 +41,30 @@ CONF_SESSIONS_ENABLED = "sessions_enabled"
DATA_API_PASSWORD = 'api_password'
# Throttling time in seconds for expired sessions check
MIN_SEC_SESSION_CLEARING = timedelta(seconds=20)
SESSION_CLEAR_INTERVAL = timedelta(seconds=20)
SESSION_TIMEOUT_SECONDS = 1800
SESSION_KEY = 'sessionId'
_LOGGER = logging.getLogger(__name__)
def setup(hass, config=None):
def setup(hass, config):
""" Sets up the HTTP API and debug interface. """
if config is None or DOMAIN not in config:
config = {DOMAIN: {}}
conf = config[DOMAIN]
api_password = util.convert(config[DOMAIN].get(CONF_API_PASSWORD), str)
no_password_set = api_password is None
if no_password_set:
api_password = util.get_random_string()
api_password = util.convert(conf.get(CONF_API_PASSWORD), str)
# If no server host is given, accept all incoming requests
server_host = config[DOMAIN].get(CONF_SERVER_HOST, '0.0.0.0')
server_port = config[DOMAIN].get(CONF_SERVER_PORT, SERVER_PORT)
development = str(config[DOMAIN].get(CONF_DEVELOPMENT, "")) == "1"
sessions_enabled = config[DOMAIN].get(CONF_SESSIONS_ENABLED, True)
server_host = conf.get(CONF_SERVER_HOST, '0.0.0.0')
server_port = conf.get(CONF_SERVER_PORT, SERVER_PORT)
development = str(conf.get(CONF_DEVELOPMENT, "")) == "1"
try:
server = HomeAssistantHTTPServer(
(server_host, server_port), RequestHandler, hass, api_password,
development, no_password_set, sessions_enabled)
development)
except OSError:
# Happens if address already in use
# If address already in use
_LOGGER.exception("Error setting up HTTP server")
return False
@ -103,17 +89,15 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
# pylint: disable=too-many-arguments
def __init__(self, server_address, request_handler_class,
hass, api_password, development, no_password_set,
sessions_enabled):
hass, api_password, development):
super().__init__(server_address, request_handler_class)
self.server_address = server_address
self.hass = hass
self.api_password = api_password
self.development = development
self.no_password_set = no_password_set
self.paths = []
self.sessions = SessionStore(sessions_enabled)
self.sessions = SessionStore()
# We will lazy init this one if needed
self.event_forwarder = None
@ -162,12 +146,13 @@ class RequestHandler(SimpleHTTPRequestHandler):
def __init__(self, req, client_addr, server):
""" Contructor, call the base constructor and set up session """
self._session = None
# Track if this was an authenticated request
self.authenticated = False
SimpleHTTPRequestHandler.__init__(self, req, client_addr, server)
def log_message(self, fmt, *arguments):
""" Redirect built-in log to HA logging """
if self.server.no_password_set:
if self.server.api_password is None:
_LOGGER.info(fmt, *arguments)
else:
_LOGGER.info(
@ -202,18 +187,17 @@ class RequestHandler(SimpleHTTPRequestHandler):
"Error parsing JSON", HTTP_UNPROCESSABLE_ENTITY)
return
self._session = self.get_session()
if self.server.no_password_set:
api_password = self.server.api_password
else:
if self.server.api_password is None:
self.authenticated = True
elif HTTP_HEADER_HA_AUTH in self.headers:
api_password = self.headers.get(HTTP_HEADER_HA_AUTH)
if not api_password and DATA_API_PASSWORD in data:
api_password = data[DATA_API_PASSWORD]
if not api_password and self._session is not None:
api_password = self._session.cookie_values.get(
CONF_API_PASSWORD)
self.authenticated = api_password == self.server.api_password
else:
self.authenticated = self.verify_session()
if '_METHOD' in data:
method = data.pop('_METHOD')
@ -246,18 +230,13 @@ class RequestHandler(SimpleHTTPRequestHandler):
# Did we find a handler for the incoming request?
if handle_request_method:
# For some calls we need a valid password
if require_auth and api_password != self.server.api_password:
if require_auth and not self.authenticated:
self.write_json_message(
"API password missing or incorrect.", HTTP_UNAUTHORIZED)
return
else:
if self._session is None and require_auth:
self._session = self.server.sessions.create(
api_password)
handle_request_method(self, path_match, data)
handle_request_method(self, path_match, data)
elif path_matched_but_not_method:
self.send_response(HTTP_METHOD_NOT_ALLOWED)
@ -308,18 +287,19 @@ class RequestHandler(SimpleHTTPRequestHandler):
json.dumps(data, indent=4, sort_keys=True,
cls=rem.JSONEncoder).encode("UTF-8"))
def write_file(self, path):
def write_file(self, path, cache_headers=True):
""" Returns a file to the user. """
try:
with open(path, 'rb') as inp:
self.write_file_pointer(self.guess_type(path), inp)
self.write_file_pointer(self.guess_type(path), inp,
cache_headers)
except IOError:
self.send_response(HTTP_NOT_FOUND)
self.end_headers()
_LOGGER.exception("Unable to serve %s", path)
def write_file_pointer(self, content_type, inp):
def write_file_pointer(self, content_type, inp, cache_headers=True):
"""
Helper function to write a file pointer to the user.
Does not do error handling.
@ -329,7 +309,8 @@ class RequestHandler(SimpleHTTPRequestHandler):
self.send_response(HTTP_OK)
self.send_header(HTTP_HEADER_CONTENT_TYPE, content_type)
self.set_cache_header()
if cache_headers:
self.set_cache_header()
self.set_session_cookie_header()
if do_gzip:
@ -356,75 +337,81 @@ class RequestHandler(SimpleHTTPRequestHandler):
def set_cache_header(self):
""" Add cache headers if not in development """
if not self.server.development:
# 1 year in seconds
cache_time = 365 * 86400
if self.server.development:
return
self.send_header(
HTTP_HEADER_CACHE_CONTROL,
"public, max-age={}".format(cache_time))
self.send_header(
HTTP_HEADER_EXPIRES,
self.date_time_string(time.time()+cache_time))
# 1 year in seconds
cache_time = 365 * 86400
self.send_header(
HTTP_HEADER_CACHE_CONTROL,
"public, max-age={}".format(cache_time))
self.send_header(
HTTP_HEADER_EXPIRES,
self.date_time_string(time.time()+cache_time))
def set_session_cookie_header(self):
""" Add the header for the session cookie """
if self.server.sessions.enabled and self._session is not None:
existing_sess_id = self.get_current_session_id()
""" Add the header for the session cookie and return session id. """
if not self.authenticated:
return
if existing_sess_id != self._session.session_id:
self.send_header(
'Set-Cookie',
SESSION_KEY+'='+self._session.session_id)
session_id = self.get_cookie_session_id()
def get_session(self):
""" Get the requested session object from cookie value """
if self.server.sessions.enabled is not True:
return None
session_id = self.get_current_session_id()
if session_id is not None:
session = self.server.sessions.get(session_id)
if session is not None:
session.reset_expiry()
return session
self.server.sessions.extend_validation(session_id)
return
return None
self.send_header(
'Set-Cookie',
'{}={}'.format(SESSION_KEY, self.server.sessions.create())
)
def get_current_session_id(self):
return session_id
def verify_session(self):
""" Verify that we are in a valid session. """
return self.get_cookie_session_id() is not None
def get_cookie_session_id(self):
"""
Extracts the current session id from the
cookie or returns None if not set
cookie or returns None if not set or invalid
"""
if 'Cookie' not in self.headers:
return None
cookie = cookies.SimpleCookie()
try:
cookie.load(self.headers["Cookie"])
except cookies.CookieError:
return None
if self.headers.get('Cookie', None) is not None:
cookie.load(self.headers.get("Cookie"))
morsel = cookie.get(SESSION_KEY)
if cookie.get(SESSION_KEY, False):
return cookie[SESSION_KEY].value
if morsel is None:
return None
session_id = cookie[SESSION_KEY].value
if self.server.sessions.is_valid(session_id):
return session_id
return None
def destroy_session(self):
""" Destroys session. """
session_id = self.get_cookie_session_id()
class ServerSession:
""" A very simple session class """
def __init__(self, session_id):
""" Set up the expiry time on creation """
self._expiry = 0
self.reset_expiry()
self.cookie_values = {}
self.session_id = session_id
if session_id is None:
return
def reset_expiry(self):
""" Resets the expiry based on current time """
self._expiry = date_util.utcnow() + timedelta(
seconds=SESSION_TIMEOUT_SECONDS)
self.send_header('Set-Cookie', '')
self.server.sessions.destroy(session_id)
@property
def is_expired(self):
""" Return true if the session is expired based on the expiry time """
return self._expiry < date_util.utcnow()
def session_valid_time():
""" Time till when a session will be valid. """
return date_util.utcnow() + timedelta(seconds=SESSION_TIMEOUT_SECONDS)
class SessionStore(object):
@ -432,47 +419,42 @@ class SessionStore(object):
def __init__(self, enabled=True):
""" Set up the session store """
self._sessions = {}
self.enabled = enabled
self.session_lock = threading.RLock()
self.lock = threading.RLock()
@Throttle(MIN_SEC_SESSION_CLEARING)
def remove_expired(self):
@util.Throttle(SESSION_CLEAR_INTERVAL)
def _remove_expired(self):
""" Remove any expired sessions. """
if self.session_lock.acquire(False):
try:
keys = []
for key in self._sessions.keys():
keys.append(key)
now = date_util.utcnow()
for key in [key for key, valid_time in self._sessions.items()
if valid_time < now]:
self._sessions.pop(key)
for key in keys:
if self._sessions[key].is_expired:
del self._sessions[key]
_LOGGER.info("Cleared expired session %s", key)
finally:
self.session_lock.release()
def is_valid(self, key):
""" Return True if a valid session is given. """
with self.lock:
self._remove_expired()
def add(self, key, session):
""" Add a new session to the list of tracked sessions """
self.remove_expired()
with self.session_lock:
self._sessions[key] = session
return (key in self._sessions and
self._sessions[key] > date_util.utcnow())
def get(self, key):
""" get a session by key """
self.remove_expired()
session = self._sessions.get(key, None)
if session is not None and session.is_expired:
return None
return session
def extend_validation(self, key):
""" Extend a session validation time. """
with self.lock:
self._sessions[key] = session_valid_time()
def create(self, api_password):
""" Creates a new session and adds it to the sessions """
if self.enabled is not True:
return None
def destroy(self, key):
""" Destroy a session by key. """
with self.lock:
self._sessions.pop(key, None)
chars = string.ascii_letters + string.digits
session_id = ''.join([random.choice(chars) for i in range(20)])
session = ServerSession(session_id)
session.cookie_values[CONF_API_PASSWORD] = api_password
self.add(session_id, session)
return session
def create(self):
""" Creates a new session. """
with self.lock:
session_id = util.get_random_string(20)
while session_id in self._sessions:
session_id = util.get_random_string(20)
self._sessions[session_id] = session_valid_time()
return session_id

View file

@ -22,8 +22,6 @@ ATTR_VALUE1 = 'value1'
ATTR_VALUE2 = 'value2'
ATTR_VALUE3 = 'value3'
DEPENDENCIES = []
REQUIREMENTS = ['pyfttt==0.3']

View file

@ -9,7 +9,6 @@ https://home-assistant.io/components/introduction/
import logging
DOMAIN = 'introduction'
DEPENDENCIES = []
def setup(hass, config=None):

View file

@ -20,7 +20,6 @@ from homeassistant.const import (
ATTR_FRIENDLY_NAME)
DOMAIN = "isy994"
DEPENDENCIES = []
REQUIREMENTS = ['PyISY==1.0.5']
DISCOVER_LIGHTS = "isy994.lights"
DISCOVER_SWITCHES = "isy994.switches"

View file

@ -15,7 +15,6 @@ from homeassistant.const import (
DOMAIN = "keyboard"
DEPENDENCIES = []
REQUIREMENTS = ['pyuserinput==0.1.9']

View file

@ -21,7 +21,6 @@ import homeassistant.util.color as color_util
DOMAIN = "light"
DEPENDENCIES = []
SCAN_INTERVAL = 30
GROUP_NAME_ALL_LIGHTS = 'all lights'

View file

@ -14,7 +14,6 @@ _LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ["blinkstick==1.1.7"]
DEPENDENCIES = []
# pylint: disable=unused-argument

View file

@ -8,7 +8,6 @@ https://home-assistant.io/components/light.mqtt/
"""
import logging
import homeassistant.util.color as color_util
import homeassistant.components.mqtt as mqtt
from homeassistant.components.light import (Light,
ATTR_BRIGHTNESS, ATTR_RGB_COLOR)
@ -37,45 +36,40 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
add_devices_callback([MqttLight(
hass,
config.get('name', DEFAULT_NAME),
{"state_topic": config.get('state_topic'),
"command_topic": config.get('command_topic'),
"brightness_state_topic": config.get('brightness_state_topic'),
"brightness_command_topic":
config.get('brightness_command_topic'),
"rgb_state_topic": config.get('rgb_state_topic'),
"rgb_command_topic": config.get('rgb_command_topic')},
config.get('rgb', None),
{
"state_topic": config.get('state_topic'),
"command_topic": config.get('command_topic'),
"brightness_state_topic": config.get('brightness_state_topic'),
"brightness_command_topic": config.get('brightness_command_topic'),
"rgb_state_topic": config.get('rgb_state_topic'),
"rgb_command_topic": config.get('rgb_command_topic')
},
config.get('qos', DEFAULT_QOS),
{"on": config.get('payload_on', DEFAULT_PAYLOAD_ON),
"off": config.get('payload_off', DEFAULT_PAYLOAD_OFF)},
config.get('brightness'),
{
"on": config.get('payload_on', DEFAULT_PAYLOAD_ON),
"off": config.get('payload_off', DEFAULT_PAYLOAD_OFF)
},
config.get('optimistic', DEFAULT_OPTIMISTIC))])
# pylint: disable=too-many-instance-attributes
class MqttLight(Light):
""" Provides a MQTT light. """
# pylint: disable=too-many-arguments
def __init__(self, hass, name,
topic,
rgb, qos,
payload,
brightness, optimistic):
# pylint: disable=too-many-arguments,too-many-instance-attributes
def __init__(self, hass, name, topic, qos, payload, optimistic):
self._hass = hass
self._name = name
self._topic = topic
self._rgb = rgb
self._qos = qos
self._payload = payload
self._brightness = brightness
self._optimistic = optimistic
self._optimistic = optimistic or topic["state_topic"] is None
self._optimistic_rgb = optimistic or topic["rgb_state_topic"] is None
self._optimistic_brightness = (optimistic or
topic["brightness_state_topic"] is None)
self._state = False
self._xy = None
def message_received(topic, payload, qos):
def state_received(topic, payload, qos):
""" A new MQTT message has been received. """
if payload == self._payload["on"]:
self._state = True
@ -84,27 +78,15 @@ class MqttLight(Light):
self.update_ha_state()
if self._topic["state_topic"] is None:
# force optimistic mode
self._optimistic = True
else:
# Subscribe the state_topic
if self._topic["state_topic"] is not None:
mqtt.subscribe(self._hass, self._topic["state_topic"],
message_received, self._qos)
state_received, self._qos)
def brightness_received(topic, payload, qos):
""" A new MQTT message for the brightness has been received. """
self._brightness = int(payload)
self.update_ha_state()
def rgb_received(topic, payload, qos):
""" A new MQTT message has been received. """
self._rgb = [int(val) for val in payload.split(',')]
self._xy = color_util.color_RGB_to_xy(int(self._rgb[0]),
int(self._rgb[1]),
int(self._rgb[2]))
self.update_ha_state()
if self._topic["brightness_state_topic"] is not None:
mqtt.subscribe(self._hass, self._topic["brightness_state_topic"],
brightness_received, self._qos)
@ -112,12 +94,17 @@ class MqttLight(Light):
else:
self._brightness = None
def rgb_received(topic, payload, qos):
""" A new MQTT message has been received. """
self._rgb = [int(val) for val in payload.split(',')]
self.update_ha_state()
if self._topic["rgb_state_topic"] is not None:
mqtt.subscribe(self._hass, self._topic["rgb_state_topic"],
rgb_received, self._qos)
self._xy = [0, 0]
self._rgb = [255, 255, 255]
else:
self._xy = None
self._rgb = None
@property
def brightness(self):
@ -129,11 +116,6 @@ class MqttLight(Light):
""" RGB color value. """
return self._rgb
@property
def color_xy(self):
""" RGB color value. """
return self._xy
@property
def should_poll(self):
""" No polling needed for a MQTT light. """
@ -151,19 +133,25 @@ class MqttLight(Light):
def turn_on(self, **kwargs):
""" Turn the device on. """
should_update = False
if ATTR_RGB_COLOR in kwargs and \
self._topic["rgb_command_topic"] is not None:
self._rgb = kwargs[ATTR_RGB_COLOR]
rgb = DEFAULT_RGB_PATTERN % tuple(self._rgb)
mqtt.publish(self._hass, self._topic["rgb_command_topic"],
rgb, self._qos)
if self._optimistic_rgb:
self._rgb = kwargs[ATTR_RGB_COLOR]
should_update = True
if ATTR_BRIGHTNESS in kwargs and \
self._topic["brightness_command_topic"] is not None:
self._brightness = kwargs[ATTR_BRIGHTNESS]
mqtt.publish(self._hass, self._topic["brightness_command_topic"],
self._brightness, self._qos)
if self._optimistic_brightness:
self._brightness = kwargs[ATTR_BRIGHTNESS]
should_update = True
mqtt.publish(self._hass, self._topic["command_topic"],
self._payload["on"], self._qos)
@ -171,6 +159,9 @@ class MqttLight(Light):
if self._optimistic:
# optimistically assume that switch has changed state
self._state = True
should_update = True
if should_update:
self.update_ha_state()
def turn_off(self, **kwargs):

View file

@ -10,6 +10,7 @@ from homeassistant.components.light import Light, ATTR_BRIGHTNESS
from homeassistant.const import (EVENT_HOMEASSISTANT_STOP,
ATTR_FRIENDLY_NAME)
REQUIREMENTS = ['tellcore-py==1.1.2']
SIGNAL_REPETITIONS = 1
# pylint: disable=unused-argument
@ -21,13 +22,14 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
import tellcore.constants as tellcore_constants
core = telldus.TelldusCore(callback_dispatcher=DirectCallbackDispatcher())
signal_repetitions = config.get('signal_repetitions', SIGNAL_REPETITIONS)
switches_and_lights = core.devices()
lights = []
for switch in switches_and_lights:
if switch.methods(tellcore_constants.TELLSTICK_DIM):
lights.append(TellstickLight(switch))
lights.append(TellstickLight(switch, signal_repetitions))
def _device_event_callback(id_, method, data, cid):
""" Called from the TelldusCore library to update one device """
@ -52,11 +54,12 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
class TellstickLight(Light):
""" Represents a Tellstick light. """
def __init__(self, tellstick_device):
def __init__(self, tellstick_device, signal_repetitions):
import tellcore.constants as tellcore_constants
self.tellstick_device = tellstick_device
self.state_attr = {ATTR_FRIENDLY_NAME: tellstick_device.name}
self.signal_repetitions = signal_repetitions
self._brightness = 0
self.last_sent_command_mask = (tellcore_constants.TELLSTICK_TURNON |
@ -64,6 +67,7 @@ class TellstickLight(Light):
tellcore_constants.TELLSTICK_DIM |
tellcore_constants.TELLSTICK_UP |
tellcore_constants.TELLSTICK_DOWN)
self.update()
@property
def name(self):
@ -82,7 +86,8 @@ class TellstickLight(Light):
def turn_off(self, **kwargs):
""" Turns the switch off. """
self.tellstick_device.turn_off()
for _ in range(self.signal_repetitions):
self.tellstick_device.turn_off()
self._brightness = 0
self.update_ha_state()
@ -95,7 +100,8 @@ class TellstickLight(Light):
else:
self._brightness = brightness
self.tellstick_device.dim(self._brightness)
for _ in range(self.signal_repetitions):
self.tellstick_device.dim(self._brightness)
self.update_ha_state()
def update(self):

View file

@ -20,7 +20,6 @@ from homeassistant.const import (
from homeassistant.components import (group, wink)
DOMAIN = 'lock'
DEPENDENCIES = []
SCAN_INTERVAL = 30
GROUP_NAME_ALL_LOCKS = 'all locks'

View file

@ -10,7 +10,6 @@ import logging
from collections import OrderedDict
DOMAIN = 'logger'
DEPENDENCIES = []
LOGSEVERITY = {
'CRITICAL': 50,

View file

@ -22,7 +22,6 @@ from homeassistant.const import (
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK)
DOMAIN = 'media_player'
DEPENDENCIES = []
SCAN_INTERVAL = 10
ENTITY_ID_FORMAT = DOMAIN + '.{}'

View file

@ -13,7 +13,6 @@ from homeassistant.const import (EVENT_HOMEASSISTANT_START,
DOMAIN = "modbus"
DEPENDENCIES = []
REQUIREMENTS = ['https://github.com/bashwork/pymodbus/archive/'
'd7fc4f1cc975631e0a9011390e8017f64b612661.zip#pymodbus==1.2.0']

View file

@ -0,0 +1,143 @@
"""
homeassistant.components.motor
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Motor component.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/motor/
"""
import os
import logging
from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity
from homeassistant.components import group
from homeassistant.const import (
SERVICE_OPEN, SERVICE_CLOSE, SERVICE_STOP,
STATE_OPEN, STATE_CLOSED, STATE_UNKNOWN, ATTR_ENTITY_ID)
DOMAIN = 'motor'
SCAN_INTERVAL = 15
GROUP_NAME_ALL_MOTORS = 'all motors'
ENTITY_ID_ALL_MOTORS = group.ENTITY_ID_FORMAT.format('all_motors')
ENTITY_ID_FORMAT = DOMAIN + '.{}'
# Maps discovered services to their platforms
DISCOVERY_PLATFORMS = {}
_LOGGER = logging.getLogger(__name__)
ATTR_CURRENT_POSITION = 'current_position'
def is_open(hass, entity_id=None):
""" Returns if the motor is open based on the statemachine. """
entity_id = entity_id or ENTITY_ID_ALL_MOTORS
return hass.states.is_state(entity_id, STATE_OPEN)
def call_open(hass, entity_id=None):
""" Open all or specified motor. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_OPEN, data)
def call_close(hass, entity_id=None):
""" Close all or specified motor. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_CLOSE, data)
def call_stop(hass, entity_id=None):
""" Stops all or specified motor. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_STOP, data)
def setup(hass, config):
""" Track states and offer events for motors. """
component = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, DISCOVERY_PLATFORMS,
GROUP_NAME_ALL_MOTORS)
component.setup(config)
def handle_motor_service(service):
""" Handles calls to the motor services. """
target_motors = component.extract_from_service(service)
for motor in target_motors:
if service.service == SERVICE_OPEN:
motor.open()
elif service.service == SERVICE_CLOSE:
motor.close()
elif service.service == SERVICE_STOP:
motor.stop()
if motor.should_poll:
motor.update_ha_state(True)
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
hass.services.register(DOMAIN, SERVICE_OPEN,
handle_motor_service,
descriptions.get(SERVICE_OPEN))
hass.services.register(DOMAIN, SERVICE_CLOSE,
handle_motor_service,
descriptions.get(SERVICE_CLOSE))
hass.services.register(DOMAIN, SERVICE_STOP,
handle_motor_service,
descriptions.get(SERVICE_STOP))
return True
class MotorDevice(Entity):
""" Represents a motor within Home Assistant. """
# pylint: disable=no-self-use
@property
def current_position(self):
"""
Return current position of motor.
None is unknown, 0 is closed, 100 is fully open.
"""
raise NotImplementedError()
@property
def state(self):
""" Returns the state of the motor. """
current = self.current_position
if current is None:
return STATE_UNKNOWN
return STATE_CLOSED if current == 0 else STATE_OPEN
@property
def state_attributes(self):
""" Return the state attributes. """
current = self.current_position
if current is None:
return None
return {
ATTR_CURRENT_POSITION: current
}
def open(self, **kwargs):
""" Open the motor. """
raise NotImplementedError()
def close(self, **kwargs):
""" Close the motor. """
raise NotImplementedError()
def stop(self, **kwargs):
""" Stop the motor. """
raise NotImplementedError()

View file

@ -0,0 +1,104 @@
"""
homeassistant.components.motor.mqtt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Allows to configure a MQTT motor.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/motor.mqtt/
"""
import logging
import homeassistant.components.mqtt as mqtt
from homeassistant.components.motor import MotorDevice
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['mqtt']
DEFAULT_NAME = "MQTT Motor"
DEFAULT_QOS = 0
DEFAULT_PAYLOAD_OPEN = "OPEN"
DEFAULT_PAYLOAD_CLOSE = "CLOSE"
DEFAULT_PAYLOAD_STOP = "STOP"
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Add MQTT Motor """
if config.get('command_topic') is None:
_LOGGER.error("Missing required variable: command_topic")
return False
add_devices_callback([MqttMotor(
hass,
config.get('name', DEFAULT_NAME),
config.get('state_topic'),
config.get('command_topic'),
config.get('qos', DEFAULT_QOS),
config.get('payload_open', DEFAULT_PAYLOAD_OPEN),
config.get('payload_close', DEFAULT_PAYLOAD_CLOSE),
config.get('payload_stop', DEFAULT_PAYLOAD_STOP),
config.get('state_format'))])
# pylint: disable=too-many-arguments, too-many-instance-attributes
class MqttMotor(MotorDevice):
""" Represents a motor that can be controlled using MQTT. """
def __init__(self, hass, name, state_topic, command_topic, qos,
payload_open, payload_close, payload_stop, state_format):
self._state = None
self._hass = hass
self._name = name
self._state_topic = state_topic
self._command_topic = command_topic
self._qos = qos
self._payload_open = payload_open
self._payload_close = payload_close
self._payload_stop = payload_stop
self._parse = mqtt.FmtParser(state_format)
if self._state_topic is None:
return
def message_received(topic, payload, qos):
""" A new MQTT message has been received. """
value = self._parse(payload)
if value.isnumeric() and 0 <= int(value) <= 100:
self._state = int(value)
self.update_ha_state()
else:
_LOGGER.warning(
"Payload is expected to be an integer between 0 and 100")
mqtt.subscribe(hass, self._state_topic, message_received, self._qos)
@property
def should_poll(self):
""" No polling needed """
return False
@property
def name(self):
""" The name of the motor. """
return self._name
@property
def current_position(self):
"""
Return current position of motor.
None is unknown, 0 is closed, 100 is fully open.
"""
return self._state
def open(self, **kwargs):
""" Open the device. """
mqtt.publish(self.hass, self._command_topic, self._payload_open,
self._qos)
def close(self, **kwargs):
""" Close the device. """
mqtt.publish(self.hass, self._command_topic, self._payload_close,
self._qos)
def stop(self, **kwargs):
""" Stop the device. """
mqtt.publish(self.hass, self._command_topic, self._payload_stop,
self._qos)

View file

@ -28,11 +28,11 @@ MQTT_CLIENT = None
DEFAULT_PORT = 1883
DEFAULT_KEEPALIVE = 60
DEFAULT_QOS = 0
DEFAULT_RETAIN = False
SERVICE_PUBLISH = 'publish'
EVENT_MQTT_MESSAGE_RECEIVED = 'MQTT_MESSAGE_RECEIVED'
DEPENDENCIES = []
REQUIREMENTS = ['paho-mqtt==1.1', 'jsonpath-rw==1.4.0']
CONF_BROKER = 'broker'
@ -46,11 +46,12 @@ CONF_CERTIFICATE = 'certificate'
ATTR_TOPIC = 'topic'
ATTR_PAYLOAD = 'payload'
ATTR_QOS = 'qos'
ATTR_RETAIN = 'retain'
MAX_RECONNECT_WAIT = 300 # seconds
def publish(hass, topic, payload, qos=None):
def publish(hass, topic, payload, qos=None, retain=None):
""" Send an MQTT message. """
data = {
ATTR_TOPIC: topic,
@ -58,6 +59,10 @@ def publish(hass, topic, payload, qos=None):
}
if qos is not None:
data[ATTR_QOS] = qos
if retain is not None:
data[ATTR_RETAIN] = retain
hass.services.call(DOMAIN, SERVICE_PUBLISH, data)
@ -119,9 +124,10 @@ def setup(hass, config):
msg_topic = call.data.get(ATTR_TOPIC)
payload = call.data.get(ATTR_PAYLOAD)
qos = call.data.get(ATTR_QOS, DEFAULT_QOS)
retain = call.data.get(ATTR_RETAIN, DEFAULT_RETAIN)
if msg_topic is None or payload is None:
return
MQTT_CLIENT.publish(msg_topic, payload, qos)
MQTT_CLIENT.publish(msg_topic, payload, qos, retain)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_mqtt)
@ -132,7 +138,7 @@ def setup(hass, config):
# pylint: disable=too-few-public-methods
class _JsonFmtParser(object):
""" Implements a json parser on xpath. """
""" Implements a JSON parser on xpath. """
def __init__(self, jsonpath):
import jsonpath_rw
self._expr = jsonpath_rw.parse(jsonpath)
@ -190,9 +196,9 @@ class MQTT(object):
self._mqttc.connect(broker, port, keepalive)
def publish(self, topic, payload, qos):
def publish(self, topic, payload, qos, retain):
""" Publish a MQTT message. """
self._mqttc.publish(topic, payload, qos)
self._mqttc.publish(topic, payload, qos, retain)
def start(self):
""" Run the MQTT client. """

View file

@ -17,7 +17,6 @@ from homeassistant.helpers import config_per_platform
from homeassistant.const import CONF_NAME
DOMAIN = "notify"
DEPENDENCIES = []
# Title of notification
ATTR_TITLE = "title"

View file

@ -100,7 +100,7 @@ class PushBulletNotificationService(BaseNotificationService):
# This also seems works to send to all devices in own account
if ttype == 'email':
self.pushbullet.push_note(title, message, email=tname)
_LOGGER.info('Sent notification to self')
_LOGGER.info('Sent notification to email %s', tname)
continue
# Refresh if name not found. While awaiting periodic refresh
@ -108,18 +108,21 @@ class PushBulletNotificationService(BaseNotificationService):
if ttype not in self.pbtargets:
_LOGGER.error('Invalid target syntax: %s', target)
continue
if tname.lower() not in self.pbtargets[ttype] and not refreshed:
tname = tname.lower()
if tname not in self.pbtargets[ttype] and not refreshed:
self.refresh()
refreshed = True
# Attempt push_note on a dict value. Keys are types & target
# name. Dict pbtargets has all *actual* targets.
try:
self.pbtargets[ttype][tname.lower()].push_note(title, message)
self.pbtargets[ttype][tname].push_note(title, message)
_LOGGER.info('Sent notification to %s/%s', ttype, tname)
except KeyError:
_LOGGER.error('No such target: %s/%s', ttype, tname)
continue
except self.pushbullet.errors.PushError:
_LOGGER.error('Notify failed to: %s/%s', ttype, tname)
continue
_LOGGER.info('Sent notification to %s/%s', ttype, tname)

View file

@ -23,7 +23,6 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
DOMAIN = "recorder"
DEPENDENCIES = []
DB_FILE = 'home-assistant.db'

View file

@ -9,7 +9,6 @@ https://home-assistant.io/components/rfxtrx/
import logging
from homeassistant.util import slugify
DEPENDENCIES = []
REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/archive/0.2.zip' +
'#RFXtrx==0.2']

View file

@ -76,8 +76,9 @@ def setup(hass, config):
_LOGGER.warn("Found invalid key for script: %s. Use %s instead.",
object_id, slugify(object_id))
continue
if not cfg.get(CONF_SEQUENCE):
_LOGGER.warn("Missing key 'sequence' for script %s", object_id)
if not isinstance(cfg.get(CONF_SEQUENCE), list):
_LOGGER.warn("Key 'sequence' for script %s should be a list",
object_id)
continue
alias = cfg.get(CONF_ALIAS, object_id)
script = Script(hass, object_id, alias, cfg[CONF_SEQUENCE])

View file

@ -9,10 +9,9 @@ https://home-assistant.io/components/sensor/
import logging
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.components import wink, zwave, isy994, verisure
from homeassistant.components import wink, zwave, isy994, verisure, ecobee
DOMAIN = 'sensor'
DEPENDENCIES = []
SCAN_INTERVAL = 30
ENTITY_ID_FORMAT = DOMAIN + '.{}'
@ -22,7 +21,8 @@ DISCOVERY_PLATFORMS = {
wink.DISCOVER_SENSORS: 'wink',
zwave.DISCOVER_SENSORS: 'zwave',
isy994.DISCOVER_SENSORS: 'isy994',
verisure.DISCOVER_SENSORS: 'verisure'
verisure.DISCOVER_SENSORS: 'verisure',
ecobee.DISCOVER_SENSORS: 'ecobee'
}

View file

@ -0,0 +1,94 @@
"""
homeassistant.components.sensor.ecobee
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Ecobee Thermostat Component
This component adds support for Ecobee3 Wireless Thermostats.
You will need to setup developer access to your thermostat,
and create and API key on the ecobee website.
The first time you run this component you will see a configuration
component card in Home Assistant. This card will contain a PIN code
that you will need to use to authorize access to your thermostat. You
can do this at https://www.ecobee.com/consumerportal/index.html
Click My Apps, Add application, Enter Pin and click Authorize.
After authorizing the application click the button in the configuration
card. Now your thermostat and sensors should shown in home-assistant.
You can use the optional hold_temp parameter to set whether or not holds
are set indefintely or until the next scheduled event.
ecobee:
api_key: asdfasdfasdfasdfasdfaasdfasdfasdfasdf
hold_temp: True
"""
from homeassistant.helpers.entity import Entity
from homeassistant.components import ecobee
from homeassistant.const import TEMP_FAHRENHEIT
import logging
DEPENDENCIES = ['ecobee']
SENSOR_TYPES = {
'temperature': ['Temperature', TEMP_FAHRENHEIT],
'humidity': ['Humidity', '%'],
'occupancy': ['Occupancy', '']
}
_LOGGER = logging.getLogger(__name__)
ECOBEE_CONFIG_FILE = 'ecobee.conf'
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the sensors. """
if discovery_info is None:
return
dev = list()
for name, data in ecobee.NETWORK.ecobee.sensors.items():
if 'temp' in data:
dev.append(EcobeeSensor(name, 'temperature'))
if 'humidity' in data:
dev.append(EcobeeSensor(name, 'humidity'))
if 'occupancy' in data:
dev.append(EcobeeSensor(name, 'occupancy'))
add_devices(dev)
class EcobeeSensor(Entity):
""" An ecobee sensor. """
def __init__(self, sensor_name, sensor_type):
self._name = sensor_name + ' ' + SENSOR_TYPES[sensor_type][0]
self.sensor_name = sensor_name
self.type = sensor_type
self._state = None
self._unit_of_measurement = SENSOR_TYPES[sensor_type][1]
self.update()
@property
def name(self):
return self._name.rstrip()
@property
def state(self):
""" Returns the state of the device. """
return self._state
@property
def unit_of_measurement(self):
return self._unit_of_measurement
def update(self):
ecobee.NETWORK.update()
data = ecobee.NETWORK.ecobee.sensors[self.sensor_name]
if self.type == 'temperature':
self._state = data['temp']
elif self.type == 'humidity':
self._state = data['humidity']
elif self.type == 'occupancy':
self._state = data['occupancy']

View file

@ -97,5 +97,5 @@ class EfergySensor(Entity):
self._state = response.json()['sum']
else:
self._state = 'Unknown'
except RequestException:
except (RequestException, ValueError):
_LOGGER.warning('Could not update status for %s', self.name)

View file

@ -12,7 +12,6 @@ import subprocess
from homeassistant.util import slugify
DOMAIN = 'shell_command'
DEPENDENCIES = []
_LOGGER = logging.getLogger(__name__)

View file

@ -15,7 +15,6 @@ import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant.helpers.entity import Entity
DEPENDENCIES = []
REQUIREMENTS = ['astral==0.8.1']
DOMAIN = "sun"
ENTITY_ID = "sun.sun"

View file

@ -20,7 +20,6 @@ from homeassistant.components import (
group, discovery, wink, isy994, verisure, zwave)
DOMAIN = 'switch'
DEPENDENCIES = []
SCAN_INTERVAL = 30
GROUP_NAME_ALL_SWITCHES = 'all switches'

View file

@ -34,30 +34,33 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return False
dev = []
pins = config.get('pins')
pins = config.get('pins', {})
for pinnum, pin in pins.items():
dev.append(ArestSwitch(resource,
config.get('name', response.json()['name']),
pin.get('name'),
pinnum))
dev.append(ArestSwitchPin(resource,
config.get('name', response.json()['name']),
pin.get('name'),
pinnum))
functions = config.get('functions', {})
for funcname, func in functions.items():
dev.append(ArestSwitchFunction(resource,
config.get('name',
response.json()['name']),
func.get('name'),
funcname))
add_devices(dev)
class ArestSwitch(SwitchDevice):
class ArestSwitchBase(SwitchDevice):
""" Implements an aREST switch. """
def __init__(self, resource, location, name, pin):
def __init__(self, resource, location, name):
self._resource = resource
self._name = '{} {}'.format(location.title(), name.title()) \
or DEVICE_DEFAULT_NAME
self._pin = pin
self._state = None
request = requests.get('{}/mode/{}/o'.format(self._resource,
self._pin), timeout=10)
if request.status_code is not 200:
_LOGGER.error("Can't set mode. Is device offline?")
@property
def name(self):
""" The name of the switch. """
@ -68,6 +71,72 @@ class ArestSwitch(SwitchDevice):
""" True if device is on. """
return self._state
class ArestSwitchFunction(ArestSwitchBase):
""" Implements an aREST switch. Based on functions. """
def __init__(self, resource, location, name, func):
super().__init__(resource, location, name)
self._func = func
request = requests.get('{}/{}'.format(self._resource, self._func),
timeout=10)
if request.status_code is not 200:
_LOGGER.error("Can't find function. Is device offline?")
return
try:
request.json()['return_value']
except KeyError:
_LOGGER.error("No return_value received. "
"Is the function name correct.")
except ValueError:
_LOGGER.error("Response invalid. Is the function name correct.")
def turn_on(self, **kwargs):
""" Turn the device on. """
request = requests.get('{}/{}'.format(self._resource, self._func),
timeout=10, params={"params": "1"})
if request.status_code == 200:
self._state = True
else:
_LOGGER.error("Can't turn on function %s at %s. "
"Is device offline?",
self._func, self._resource)
def turn_off(self, **kwargs):
""" Turn the device off. """
request = requests.get('{}/{}'.format(self._resource, self._func),
timeout=10, params={"params": "0"})
if request.status_code == 200:
self._state = False
else:
_LOGGER.error("Can't turn off function %s at %s. "
"Is device offline?",
self._func, self._resource)
def update(self):
""" Gets the latest data from aREST API and updates the state. """
request = requests.get('{}/{}'.format(self._resource,
self._func), timeout=10)
self._state = request.json()['return_value'] != 0
class ArestSwitchPin(ArestSwitchBase):
""" Implements an aREST switch. Based on digital I/O """
def __init__(self, resource, location, name, pin):
super().__init__(resource, location, name)
self._pin = pin
request = requests.get('{}/mode/{}/o'.format(self._resource,
self._pin), timeout=10)
if request.status_code is not 200:
_LOGGER.error("Can't set mode. Is device offline?")
def turn_on(self, **kwargs):
""" Turn the device on. """
request = requests.get('{}/digital/{}/1'.format(self._resource,

View file

@ -17,6 +17,7 @@ DEFAULT_QOS = 0
DEFAULT_PAYLOAD_ON = "ON"
DEFAULT_PAYLOAD_OFF = "OFF"
DEFAULT_OPTIMISTIC = False
DEFAULT_RETAIN = False
DEPENDENCIES = ['mqtt']
@ -35,6 +36,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
config.get('state_topic'),
config.get('command_topic'),
config.get('qos', DEFAULT_QOS),
config.get('retain', DEFAULT_RETAIN),
config.get('payload_on', DEFAULT_PAYLOAD_ON),
config.get('payload_off', DEFAULT_PAYLOAD_OFF),
config.get('optimistic', DEFAULT_OPTIMISTIC),
@ -44,7 +46,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
# pylint: disable=too-many-arguments, too-many-instance-attributes
class MqttSwitch(SwitchDevice):
""" Represents a switch that can be toggled using MQTT. """
def __init__(self, hass, name, state_topic, command_topic, qos,
def __init__(self, hass, name, state_topic, command_topic, qos, retain,
payload_on, payload_off, optimistic, state_format):
self._state = False
self._hass = hass
@ -52,6 +54,7 @@ class MqttSwitch(SwitchDevice):
self._state_topic = state_topic
self._command_topic = command_topic
self._qos = qos
self._retain = retain
self._payload_on = payload_on
self._payload_off = payload_off
self._optimistic = optimistic
@ -93,7 +96,7 @@ class MqttSwitch(SwitchDevice):
def turn_on(self, **kwargs):
""" Turn the device on. """
mqtt.publish(self.hass, self._command_topic, self._payload_on,
self._qos)
self._qos, self._retain)
if self._optimistic:
# optimistically assume that switch has changed state
self._state = True
@ -102,7 +105,7 @@ class MqttSwitch(SwitchDevice):
def turn_off(self, **kwargs):
""" Turn the device off. """
mqtt.publish(self.hass, self._command_topic, self._payload_off,
self._qos)
self._qos, self._retain)
if self._optimistic:
# optimistically assume that switch has changed state
self._state = False

View file

@ -126,5 +126,8 @@ class VeraSwitch(ToggleEntity):
def update(self):
# We need to debounce the status call after turning switch on or off
# because the vera has some lag in updating the device status
if (self.last_command_send + 5) < time.time():
self.is_on_status = self.vera_device.is_switched_on()
try:
if (self.last_command_send + 5) < time.time():
self.is_on_status = self.vera_device.is_switched_on()
except RequestException:
_LOGGER.warning('Could not update status for %s', self.name)

View file

@ -11,7 +11,7 @@ import logging
from homeassistant.components.switch import SwitchDevice
from homeassistant.const import STATE_ON, STATE_OFF, STATE_STANDBY
REQUIREMENTS = ['pywemo==0.3.2']
REQUIREMENTS = ['pywemo==0.3.3']
_LOGGER = logging.getLogger(__name__)

View file

@ -15,12 +15,12 @@ from homeassistant.config import load_yaml_config_file
import homeassistant.util as util
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.temperature import convert
from homeassistant.components import ecobee
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_ON, STATE_OFF, STATE_UNKNOWN,
TEMP_CELCIUS)
DOMAIN = "thermostat"
DEPENDENCIES = []
ENTITY_ID_FORMAT = DOMAIN + ".{}"
SCAN_INTERVAL = 60
@ -42,6 +42,10 @@ ATTR_OPERATION = "current_operation"
_LOGGER = logging.getLogger(__name__)
DISCOVERY_PLATFORMS = {
ecobee.DISCOVER_THERMOSTAT: 'ecobee',
}
def set_away_mode(hass, away_mode, entity_id=None):
""" Turn all or specified thermostat away mode on. """
@ -67,7 +71,8 @@ def set_temperature(hass, temperature, entity_id=None):
def setup(hass, config):
""" Setup thermostats. """
component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
component = EntityComponent(_LOGGER, DOMAIN, hass,
SCAN_INTERVAL, DISCOVERY_PLATFORMS)
component.setup(config)
def thermostat_service(service):
@ -142,13 +147,13 @@ class ThermostatDevice(Entity):
data = {
ATTR_CURRENT_TEMPERATURE:
self._convert(self.current_temperature, 1),
ATTR_MIN_TEMP: self._convert(self.min_temp, 0),
ATTR_MAX_TEMP: self._convert(self.max_temp, 0),
ATTR_TEMPERATURE: self._convert(self.target_temperature, 0),
ATTR_MIN_TEMP: self._convert(self.min_temp, 1),
ATTR_MAX_TEMP: self._convert(self.max_temp, 1),
ATTR_TEMPERATURE: self._convert(self.target_temperature, 1),
ATTR_TEMPERATURE_LOW:
self._convert(self.target_temperature_low, 0),
self._convert(self.target_temperature_low, 1),
ATTR_TEMPERATURE_HIGH:
self._convert(self.target_temperature_high, 0),
self._convert(self.target_temperature_high, 1),
}
operation = self.operation

View file

@ -0,0 +1,209 @@
"""
homeassistant.components.thermostat.ecobee
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Ecobee Thermostat Component
This component adds support for Ecobee3 Wireless Thermostats.
You will need to setup developer access to your thermostat,
and create and API key on the ecobee website.
The first time you run this component you will see a configuration
component card in Home Assistant. This card will contain a PIN code
that you will need to use to authorize access to your thermostat. You
can do this at https://www.ecobee.com/consumerportal/index.html
Click My Apps, Add application, Enter Pin and click Authorize.
After authorizing the application click the button in the configuration
card. Now your thermostat and sensors should shown in home-assistant.
You can use the optional hold_temp parameter to set whether or not holds
are set indefintely or until the next scheduled event.
ecobee:
api_key: asdfasdfasdfasdfasdfaasdfasdfasdfasdf
hold_temp: True
"""
from homeassistant.components.thermostat import (ThermostatDevice, STATE_COOL,
STATE_IDLE, STATE_HEAT)
from homeassistant.const import (TEMP_FAHRENHEIT, STATE_ON, STATE_OFF)
from homeassistant.components import ecobee
import logging
DEPENDENCIES = ['ecobee']
_LOGGER = logging.getLogger(__name__)
ECOBEE_CONFIG_FILE = 'ecobee.conf'
_CONFIGURING = {}
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Setup Platform """
if discovery_info is None:
return
data = ecobee.NETWORK
hold_temp = discovery_info['hold_temp']
_LOGGER.info("Loading ecobee thermostat component with hold_temp set to "
+ str(hold_temp))
add_devices(Thermostat(data, index, hold_temp)
for index in range(len(data.ecobee.thermostats)))
class Thermostat(ThermostatDevice):
""" Thermostat class for Ecobee """
def __init__(self, data, thermostat_index, hold_temp):
self.data = data
self.thermostat_index = thermostat_index
self.thermostat = self.data.ecobee.get_thermostat(
self.thermostat_index)
self._name = self.thermostat['name']
self._away = 'away' in self.thermostat['program']['currentClimateRef']
self.hold_temp = hold_temp
def update(self):
self.data.update()
self.thermostat = self.data.ecobee.get_thermostat(
self.thermostat_index)
@property
def name(self):
""" Returns the name of the Ecobee Thermostat. """
return self.thermostat['name']
@property
def unit_of_measurement(self):
""" Unit of measurement this thermostat expresses itself in. """
return TEMP_FAHRENHEIT
@property
def current_temperature(self):
""" Returns the current temperature. """
return self.thermostat['runtime']['actualTemperature'] / 10
@property
def target_temperature(self):
""" Returns the temperature we try to reach. """
return (self.target_temperature_low + self.target_temperature_high) / 2
@property
def target_temperature_low(self):
""" Returns the lower bound temperature we try to reach. """
return int(self.thermostat['runtime']['desiredHeat'] / 10)
@property
def target_temperature_high(self):
""" Returns the upper bound temperature we try to reach. """
return int(self.thermostat['runtime']['desiredCool'] / 10)
@property
def humidity(self):
""" Returns the current humidity. """
return self.thermostat['runtime']['actualHumidity']
@property
def desired_fan_mode(self):
""" Returns the desired fan mode of operation. """
return self.thermostat['runtime']['desiredFanMode']
@property
def fan(self):
""" Returns the current fan state. """
if 'fan' in self.thermostat['equipmentStatus']:
return STATE_ON
else:
return STATE_OFF
@property
def operation(self):
""" Returns current operation ie. heat, cool, idle """
status = self.thermostat['equipmentStatus']
if status == '':
return STATE_IDLE
elif 'Cool' in status:
return STATE_COOL
elif 'auxHeat' in status:
return STATE_HEAT
elif 'heatPump' in status:
return STATE_HEAT
else:
return status
@property
def mode(self):
""" Returns current mode ie. home, away, sleep """
mode = self.thermostat['program']['currentClimateRef']
self._away = 'away' in mode
return mode
@property
def hvac_mode(self):
""" Return current hvac mode ie. auto, auxHeatOnly, cool, heat, off """
return self.thermostat['settings']['hvacMode']
@property
def device_state_attributes(self):
""" Returns device specific state attributes. """
# Move these to Thermostat Device and make them global
return {
"humidity": self.humidity,
"fan": self.fan,
"mode": self.mode,
"hvac_mode": self.hvac_mode
}
@property
def is_away_mode_on(self):
""" Returns if away mode is on. """
return self._away
def turn_away_mode_on(self):
""" Turns away on. """
self._away = True
if self.hold_temp:
self.data.ecobee.set_climate_hold("away", "indefinite")
else:
self.data.ecobee.set_climate_hold("away")
def turn_away_mode_off(self):
""" Turns away off. """
self._away = False
self.data.ecobee.resume_program()
def set_temperature(self, temperature):
""" Set new target temperature """
temperature = int(temperature)
low_temp = temperature - 1
high_temp = temperature + 1
if self.hold_temp:
self.data.ecobee.set_hold_temp(low_temp, high_temp, "indefinite")
else:
self.data.ecobee.set_hold_temp(low_temp, high_temp)
def set_hvac_mode(self, mode):
""" Set HVAC mode (auto, auxHeatOnly, cool, heat, off) """
self.data.ecobee.set_hvac_mode(mode)
# Home and Sleep mode aren't used in UI yet:
# def turn_home_mode_on(self):
# """ Turns home mode on. """
# self._away = False
# self.data.ecobee.set_climate_hold("home")
# def turn_home_mode_off(self):
# """ Turns home mode off. """
# self._away = False
# self.data.ecobee.resume_program()
# def turn_sleep_mode_on(self):
# """ Turns sleep mode on. """
# self._away = False
# self.data.ecobee.set_climate_hold("sleep")
# def turn_sleep_mode_off(self):
# """ Turns sleep mode off. """
# self._away = False
# self.data.ecobee.resume_program()

View file

@ -16,7 +16,6 @@ from homeassistant.helpers import event
_LOGGER = logging.getLogger(__name__)
PYPI_URL = 'https://pypi.python.org/pypi/homeassistant/json'
DEPENDENCIES = []
DOMAIN = 'updater'
ENTITY_ID = 'updater.updater'

View file

@ -17,7 +17,6 @@ from homeassistant.const import (
ATTR_SERVICE, ATTR_DISCOVERED, ATTR_FRIENDLY_NAME)
DOMAIN = "wink"
DEPENDENCIES = []
REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/'
'42fdcfa721b1bc583688e3592d8427f4c13ba6d9.zip'
'#python-wink==0.2']

View file

@ -15,7 +15,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.util.location import distance
DOMAIN = "zone"
DEPENDENCIES = []
ENTITY_ID_FORMAT = 'zone.{}'
ENTITY_ID_HOME = ENTITY_ID_FORMAT.format('home')
STATE = 'zoning'

View file

@ -17,7 +17,6 @@ from homeassistant.const import (
EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, ATTR_DISCOVERED)
DOMAIN = "zwave"
DEPENDENCIES = []
REQUIREMENTS = ['pydispatcher==2.0.5']
CONF_USB_STICK_PATH = "usb_path"

View file

@ -143,6 +143,10 @@ SERVICE_ALARM_TRIGGER = "alarm_trigger"
SERVICE_LOCK = "lock"
SERVICE_UNLOCK = "unlock"
SERVICE_OPEN = 'open'
SERVICE_CLOSE = 'close'
SERVICE_STOP = 'stop'
# #### API / REMOTE ####
SERVER_PORT = 8123
@ -160,6 +164,7 @@ URL_API_EVENT_FORWARD = "/api/event_forwarding"
URL_API_COMPONENTS = "/api/components"
URL_API_BOOTSTRAP = "/api/bootstrap"
URL_API_ERROR_LOG = "/api/error_log"
URL_API_LOG_OUT = "/api/log_out"
HTTP_OK = 200
HTTP_CREATED = 201

View file

@ -4,6 +4,8 @@ homeassistant.helpers.entity_component
Provides helpers for components that manage entities.
"""
from threading import Lock
from homeassistant.bootstrap import prepare_setup_platform
from homeassistant.helpers import (
generate_entity_id, config_per_platform, extract_entity_ids)
@ -37,6 +39,7 @@ class EntityComponent(object):
self.is_polling = False
self.config = None
self.lock = Lock()
def setup(self, config):
"""
@ -61,8 +64,11 @@ class EntityComponent(object):
Takes in a list of new entities. For each entity will see if it already
exists. If not, will add it, set it up and push the first state.
"""
for entity in new_entities:
if entity is not None and entity not in self.entities.values():
with self.lock:
for entity in new_entities:
if entity is None or entity in self.entities.values():
continue
entity.hass = self.hass
if getattr(entity, 'entity_id', None) is None:
@ -74,23 +80,33 @@ class EntityComponent(object):
entity.update_ha_state()
if self.group is None and self.group_name is not None:
self.group = group.Group(self.hass, self.group_name,
user_defined=False)
if self.group is None and self.group_name is not None:
self.group = group.Group(self.hass, self.group_name,
user_defined=False)
if self.group is not None:
self.group.update_tracked_entity_ids(self.entities.keys())
if self.group is not None:
self.group.update_tracked_entity_ids(self.entities.keys())
self._start_polling()
if self.is_polling or \
not any(entity.should_poll for entity
in self.entities.values()):
return
self.is_polling = True
track_utc_time_change(
self.hass, self._update_entity_states,
second=range(0, 60, self.scan_interval))
def extract_from_service(self, service):
"""
Takes a service and extracts all known entities.
Will return all if no entity IDs given in service.
"""
if ATTR_ENTITY_ID not in service.data:
return self.entities.values()
else:
with self.lock:
if ATTR_ENTITY_ID not in service.data:
return list(self.entities.values())
return [self.entities[entity_id] for entity_id
in extract_entity_ids(self.hass, service)
if entity_id in self.entities]
@ -99,9 +115,10 @@ class EntityComponent(object):
""" Update the states of all the entities. """
self.logger.info("Updating %s entities", self.domain)
for entity in self.entities.values():
if entity.should_poll:
entity.update_ha_state(True)
with self.lock:
for entity in self.entities.values():
if entity.should_poll:
entity.update_ha_state(True)
def _entity_discovered(self, service, info):
""" Called when a entity is discovered. """
@ -110,18 +127,6 @@ class EntityComponent(object):
self._setup_platform(self.discovery_platforms[service], {}, info)
def _start_polling(self):
""" Start polling entities if necessary. """
if self.is_polling or \
not any(entity.should_poll for entity in self.entities.values()):
return
self.is_polling = True
track_utc_time_change(
self.hass, self._update_entity_states,
second=range(0, 60, self.scan_interval))
def _setup_platform(self, platform_type, platform_config,
discovery_info=None):
""" Tries to setup a platform for this component. """

View file

@ -193,7 +193,7 @@ def _load_order_component(comp_name, load_order, loading):
loading.add(comp_name)
for dependency in component.DEPENDENCIES:
for dependency in getattr(component, 'DEPENDENCIES', []):
# Check not already loaded
if dependency in load_order:
continue

View file

@ -18,7 +18,10 @@ python-nmap==0.4.3
pysnmp==4.2.5
# homeassistant.components.discovery
netdisco==0.5.1
netdisco==0.5.2
# homeassistant.components.ecobee
https://github.com/nkgilley/python-ecobee-api/archive/d35596b67c75451fa47001c493a15eebee195e93.zip#python-ecobee==0.0.1
# homeassistant.components.ifttt
pyfttt==0.3
@ -75,6 +78,8 @@ https://github.com/bashwork/pymodbus/archive/d7fc4f1cc975631e0a9011390e8017f64b6
# homeassistant.components.mqtt
paho-mqtt==1.1
# homeassistant.components.mqtt
jsonpath-rw==1.4.0
# homeassistant.components.notify.pushbullet
@ -149,7 +154,7 @@ hikvision==0.4
orvibo==1.0.0
# homeassistant.components.switch.wemo
pywemo==0.3.2
pywemo==0.3.3
# homeassistant.components.thermostat.honeywell
evohomeclient==0.2.3

View file

@ -101,7 +101,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase):
alarm_control_panel.alarm_arm_home(self.hass)
self.hass.pool.block_till_done()
self.assertEqual(('alarm/command', 'ARM_HOME', 0),
self.assertEqual(('alarm/command', 'ARM_HOME', 0, False),
self.mock_publish.mock_calls[-1][1])
def test_arm_home_not_publishes_mqtt_with_invalid_code(self):
@ -130,7 +130,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase):
alarm_control_panel.alarm_arm_away(self.hass)
self.hass.pool.block_till_done()
self.assertEqual(('alarm/command', 'ARM_AWAY', 0),
self.assertEqual(('alarm/command', 'ARM_AWAY', 0, False),
self.mock_publish.mock_calls[-1][1])
def test_arm_away_not_publishes_mqtt_with_invalid_code(self):
@ -159,7 +159,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase):
alarm_control_panel.alarm_disarm(self.hass)
self.hass.pool.block_till_done()
self.assertEqual(('alarm/command', 'DISARM', 0),
self.assertEqual(('alarm/command', 'DISARM', 0, False),
self.mock_publish.mock_calls[-1][1])
def test_disarm_not_publishes_mqtt_with_invalid_code(self):

View file

@ -44,7 +44,6 @@ light:
payload_off: "off"
"""
import unittest
import homeassistant.util.color as color_util
from homeassistant.const import STATE_ON, STATE_OFF
import homeassistant.core as ha
@ -63,6 +62,29 @@ class TestLightMQTT(unittest.TestCase):
""" Stop down stuff we started. """
self.hass.stop()
def test_no_color_or_brightness_if_no_topics(self):
self.assertTrue(light.setup(self.hass, {
'light': {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'test_light_rgb/status',
'command_topic': 'test_light_rgb/set',
}
}))
state = self.hass.states.get('light.test')
self.assertEqual(STATE_OFF, state.state)
self.assertIsNone(state.attributes.get('rgb_color'))
self.assertIsNone(state.attributes.get('brightness'))
fire_mqtt_message(self.hass, 'test_light_rgb/status', 'on')
self.hass.pool.block_till_done()
state = self.hass.states.get('light.test')
self.assertEqual(STATE_ON, state.state)
self.assertIsNone(state.attributes.get('rgb_color'))
self.assertIsNone(state.attributes.get('brightness'))
def test_controlling_state_via_topic(self):
self.assertTrue(light.setup(self.hass, {
'light': {
@ -82,12 +104,16 @@ class TestLightMQTT(unittest.TestCase):
state = self.hass.states.get('light.test')
self.assertEqual(STATE_OFF, state.state)
self.assertIsNone(state.attributes.get('rgb_color'))
self.assertIsNone(state.attributes.get('brightness'))
fire_mqtt_message(self.hass, 'test_light_rgb/status', 'on')
self.hass.pool.block_till_done()
state = self.hass.states.get('light.test')
self.assertEqual(STATE_ON, state.state)
self.assertEqual([255, 255, 255], state.attributes.get('rgb_color'))
self.assertEqual(255, state.attributes.get('brightness'))
fire_mqtt_message(self.hass, 'test_light_rgb/status', 'off')
self.hass.pool.block_till_done()
@ -139,7 +165,7 @@ class TestLightMQTT(unittest.TestCase):
light.turn_on(self.hass, 'light.test')
self.hass.pool.block_till_done()
self.assertEqual(('test_light_rgb/set', 'on', 2),
self.assertEqual(('test_light_rgb/set', 'on', 2, False),
self.mock_publish.mock_calls[-1][1])
state = self.hass.states.get('light.test')
self.assertEqual(STATE_ON, state.state)
@ -147,7 +173,7 @@ class TestLightMQTT(unittest.TestCase):
light.turn_off(self.hass, 'light.test')
self.hass.pool.block_till_done()
self.assertEqual(('test_light_rgb/set', 'off', 2),
self.assertEqual(('test_light_rgb/set', 'off', 2, False),
self.mock_publish.mock_calls[-1][1])
state = self.hass.states.get('light.test')
self.assertEqual(STATE_OFF, state.state)

View file

View file

@ -0,0 +1,166 @@
"""
tests.components.motor.test_mqtt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tests mqtt motor.
"""
import unittest
from homeassistant.const import STATE_OPEN, STATE_CLOSED, STATE_UNKNOWN
import homeassistant.core as ha
import homeassistant.components.motor as motor
from tests.common import mock_mqtt_component, fire_mqtt_message
class TestMotorMQTT(unittest.TestCase):
""" Test the MQTT motor. """
def setUp(self): # pylint: disable=invalid-name
self.hass = ha.HomeAssistant()
self.mock_publish = mock_mqtt_component(self.hass)
def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """
self.hass.stop()
def test_controlling_state_via_topic(self):
self.assertTrue(motor.setup(self.hass, {
'motor': {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'state-topic',
'command_topic': 'command-topic',
'qos': 0,
'payload_open': 'OPEN',
'payload_close': 'CLOSE',
'payload_stop': 'STOP'
}
}))
state = self.hass.states.get('motor.test')
self.assertEqual(STATE_UNKNOWN, state.state)
fire_mqtt_message(self.hass, 'state-topic', '0')
self.hass.pool.block_till_done()
state = self.hass.states.get('motor.test')
self.assertEqual(STATE_CLOSED, state.state)
fire_mqtt_message(self.hass, 'state-topic', '50')
self.hass.pool.block_till_done()
state = self.hass.states.get('motor.test')
self.assertEqual(STATE_OPEN, state.state)
fire_mqtt_message(self.hass, 'state-topic', '100')
self.hass.pool.block_till_done()
state = self.hass.states.get('motor.test')
self.assertEqual(STATE_OPEN, state.state)
def test_send_open_command(self):
self.assertTrue(motor.setup(self.hass, {
'motor': {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'state-topic',
'command_topic': 'command-topic',
'qos': 2
}
}))
state = self.hass.states.get('motor.test')
self.assertEqual(STATE_UNKNOWN, state.state)
motor.call_open(self.hass, 'motor.test')
self.hass.pool.block_till_done()
self.assertEqual(('command-topic', 'OPEN', 2, False),
self.mock_publish.mock_calls[-1][1])
state = self.hass.states.get('motor.test')
self.assertEqual(STATE_UNKNOWN, state.state)
def test_send_close_command(self):
self.assertTrue(motor.setup(self.hass, {
'motor': {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'state-topic',
'command_topic': 'command-topic',
'qos': 2
}
}))
state = self.hass.states.get('motor.test')
self.assertEqual(STATE_UNKNOWN, state.state)
motor.call_close(self.hass, 'motor.test')
self.hass.pool.block_till_done()
self.assertEqual(('command-topic', 'CLOSE', 2, False),
self.mock_publish.mock_calls[-1][1])
state = self.hass.states.get('motor.test')
self.assertEqual(STATE_UNKNOWN, state.state)
def test_send_stop_command(self):
self.assertTrue(motor.setup(self.hass, {
'motor': {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'state-topic',
'command_topic': 'command-topic',
'qos': 2
}
}))
state = self.hass.states.get('motor.test')
self.assertEqual(STATE_UNKNOWN, state.state)
motor.call_stop(self.hass, 'motor.test')
self.hass.pool.block_till_done()
self.assertEqual(('command-topic', 'STOP', 2, False),
self.mock_publish.mock_calls[-1][1])
state = self.hass.states.get('motor.test')
self.assertEqual(STATE_UNKNOWN, state.state)
def test_state_attributes_current_position(self):
self.assertTrue(motor.setup(self.hass, {
'motor': {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'state-topic',
'command_topic': 'command-topic',
'payload_open': 'OPEN',
'payload_close': 'CLOSE',
'payload_stop': 'STOP'
}
}))
state_attributes_dict = self.hass.states.get(
'motor.test').attributes
self.assertFalse('current_position' in state_attributes_dict)
fire_mqtt_message(self.hass, 'state-topic', '0')
self.hass.pool.block_till_done()
current_position = self.hass.states.get(
'motor.test').attributes['current_position']
self.assertEqual(0, current_position)
fire_mqtt_message(self.hass, 'state-topic', '50')
self.hass.pool.block_till_done()
current_position = self.hass.states.get(
'motor.test').attributes['current_position']
self.assertEqual(50, current_position)
fire_mqtt_message(self.hass, 'state-topic', '101')
self.hass.pool.block_till_done()
current_position = self.hass.states.get(
'motor.test').attributes['current_position']
self.assertEqual(50, current_position)
fire_mqtt_message(self.hass, 'state-topic', 'non-numeric')
self.hass.pool.block_till_done()
current_position = self.hass.states.get(
'motor.test').attributes['current_position']
self.assertEqual(50, current_position)

View file

@ -68,7 +68,7 @@ class TestSensorMQTT(unittest.TestCase):
switch.turn_on(self.hass, 'switch.test')
self.hass.pool.block_till_done()
self.assertEqual(('command-topic', 'beer on', 2),
self.assertEqual(('command-topic', 'beer on', 2, False),
self.mock_publish.mock_calls[-1][1])
state = self.hass.states.get('switch.test')
self.assertEqual(STATE_ON, state.state)
@ -76,7 +76,7 @@ class TestSensorMQTT(unittest.TestCase):
switch.turn_off(self.hass, 'switch.test')
self.hass.pool.block_till_done()
self.assertEqual(('command-topic', 'beer off', 2),
self.assertEqual(('command-topic', 'beer off', 2, False),
self.mock_publish.mock_calls[-1][1])
state = self.hass.states.get('switch.test')
self.assertEqual(STATE_OFF, state.state)

View file

@ -8,14 +8,13 @@ Tests Home Assistant HTTP component does what it should do.
import unittest
import json
from unittest.mock import patch
import tempfile
import requests
from homeassistant import bootstrap, const
import homeassistant.core as ha
import homeassistant.bootstrap as bootstrap
import homeassistant.remote as remote
import homeassistant.components.http as http
from homeassistant.const import HTTP_HEADER_HA_AUTH
API_PASSWORD = "test1234"
@ -26,7 +25,7 @@ SERVER_PORT = 8120
HTTP_BASE_URL = "http://127.0.0.1:{}".format(SERVER_PORT)
HA_HEADERS = {HTTP_HEADER_HA_AUTH: API_PASSWORD}
HA_HEADERS = {const.HTTP_HEADER_HA_AUTH: API_PASSWORD}
hass = None
@ -68,20 +67,20 @@ class TestAPI(unittest.TestCase):
# TODO move back to http component and test with use_auth.
def test_access_denied_without_password(self):
req = requests.get(
_url(remote.URL_API_STATES_ENTITY.format("test")))
_url(const.URL_API_STATES_ENTITY.format("test")))
self.assertEqual(401, req.status_code)
def test_access_denied_with_wrong_password(self):
req = requests.get(
_url(remote.URL_API_STATES_ENTITY.format("test")),
headers={HTTP_HEADER_HA_AUTH: 'wrongpassword'})
_url(const.URL_API_STATES_ENTITY.format("test")),
headers={const.HTTP_HEADER_HA_AUTH: 'wrongpassword'})
self.assertEqual(401, req.status_code)
def test_api_list_state_entities(self):
""" Test if the debug interface allows us to list state entities. """
req = requests.get(_url(remote.URL_API_STATES),
req = requests.get(_url(const.URL_API_STATES),
headers=HA_HEADERS)
remote_data = [ha.State.from_dict(item) for item in req.json()]
@ -91,7 +90,7 @@ class TestAPI(unittest.TestCase):
def test_api_get_state(self):
""" Test if the debug interface allows us to get a state. """
req = requests.get(
_url(remote.URL_API_STATES_ENTITY.format("test.test")),
_url(const.URL_API_STATES_ENTITY.format("test.test")),
headers=HA_HEADERS)
data = ha.State.from_dict(req.json())
@ -105,7 +104,7 @@ class TestAPI(unittest.TestCase):
def test_api_get_non_existing_state(self):
""" Test if the debug interface allows us to get a state. """
req = requests.get(
_url(remote.URL_API_STATES_ENTITY.format("does_not_exist")),
_url(const.URL_API_STATES_ENTITY.format("does_not_exist")),
headers=HA_HEADERS)
self.assertEqual(404, req.status_code)
@ -115,7 +114,7 @@ class TestAPI(unittest.TestCase):
hass.states.set("test.test", "not_to_be_set")
requests.post(_url(remote.URL_API_STATES_ENTITY.format("test.test")),
requests.post(_url(const.URL_API_STATES_ENTITY.format("test.test")),
data=json.dumps({"state": "debug_state_change2"}),
headers=HA_HEADERS)
@ -130,7 +129,7 @@ class TestAPI(unittest.TestCase):
new_state = "debug_state_change"
req = requests.post(
_url(remote.URL_API_STATES_ENTITY.format(
_url(const.URL_API_STATES_ENTITY.format(
"test_entity.that_does_not_exist")),
data=json.dumps({'state': new_state}),
headers=HA_HEADERS)
@ -146,7 +145,7 @@ class TestAPI(unittest.TestCase):
""" Test if API sends appropriate error if we omit state. """
req = requests.post(
_url(remote.URL_API_STATES_ENTITY.format(
_url(const.URL_API_STATES_ENTITY.format(
"test_entity.that_does_not_exist")),
data=json.dumps({}),
headers=HA_HEADERS)
@ -165,7 +164,7 @@ class TestAPI(unittest.TestCase):
hass.bus.listen_once("test.event_no_data", listener)
requests.post(
_url(remote.URL_API_EVENTS_EVENT.format("test.event_no_data")),
_url(const.URL_API_EVENTS_EVENT.format("test.event_no_data")),
headers=HA_HEADERS)
hass.pool.block_till_done()
@ -186,7 +185,7 @@ class TestAPI(unittest.TestCase):
hass.bus.listen_once("test_event_with_data", listener)
requests.post(
_url(remote.URL_API_EVENTS_EVENT.format("test_event_with_data")),
_url(const.URL_API_EVENTS_EVENT.format("test_event_with_data")),
data=json.dumps({"test": 1}),
headers=HA_HEADERS)
@ -206,7 +205,7 @@ class TestAPI(unittest.TestCase):
hass.bus.listen_once("test_event_bad_data", listener)
req = requests.post(
_url(remote.URL_API_EVENTS_EVENT.format("test_event_bad_data")),
_url(const.URL_API_EVENTS_EVENT.format("test_event_bad_data")),
data=json.dumps('not an object'),
headers=HA_HEADERS)
@ -217,7 +216,7 @@ class TestAPI(unittest.TestCase):
# Try now with valid but unusable JSON
req = requests.post(
_url(remote.URL_API_EVENTS_EVENT.format("test_event_bad_data")),
_url(const.URL_API_EVENTS_EVENT.format("test_event_bad_data")),
data=json.dumps([1, 2, 3]),
headers=HA_HEADERS)
@ -226,9 +225,31 @@ class TestAPI(unittest.TestCase):
self.assertEqual(422, req.status_code)
self.assertEqual(0, len(test_value))
def test_api_get_config(self):
req = requests.get(_url(const.URL_API_CONFIG),
headers=HA_HEADERS)
self.assertEqual(hass.config.as_dict(), req.json())
def test_api_get_components(self):
req = requests.get(_url(const.URL_API_COMPONENTS),
headers=HA_HEADERS)
self.assertEqual(hass.config.components, req.json())
def test_api_get_error_log(self):
test_content = 'Test String'
with tempfile.NamedTemporaryFile() as log:
log.write(test_content.encode('utf-8'))
log.flush()
with patch.object(hass.config, 'path', return_value=log.name):
req = requests.get(_url(const.URL_API_ERROR_LOG),
headers=HA_HEADERS)
self.assertEqual(test_content, req.text)
self.assertIsNone(req.headers.get('expires'))
def test_api_get_event_listeners(self):
""" Test if we can get the list of events being listened for. """
req = requests.get(_url(remote.URL_API_EVENTS),
req = requests.get(_url(const.URL_API_EVENTS),
headers=HA_HEADERS)
local = hass.bus.listeners
@ -241,7 +262,7 @@ class TestAPI(unittest.TestCase):
def test_api_get_services(self):
""" Test if we can get a dict describing current services. """
req = requests.get(_url(remote.URL_API_SERVICES),
req = requests.get(_url(const.URL_API_SERVICES),
headers=HA_HEADERS)
local_services = hass.services.services
@ -262,7 +283,7 @@ class TestAPI(unittest.TestCase):
hass.services.register("test_domain", "test_service", listener)
requests.post(
_url(remote.URL_API_SERVICES_SERVICE.format(
_url(const.URL_API_SERVICES_SERVICE.format(
"test_domain", "test_service")),
headers=HA_HEADERS)
@ -283,7 +304,7 @@ class TestAPI(unittest.TestCase):
hass.services.register("test_domain", "test_service", listener)
requests.post(
_url(remote.URL_API_SERVICES_SERVICE.format(
_url(const.URL_API_SERVICES_SERVICE.format(
"test_domain", "test_service")),
data=json.dumps({"test": 1}),
headers=HA_HEADERS)
@ -296,24 +317,24 @@ class TestAPI(unittest.TestCase):
""" Test setting up event forwarding. """
req = requests.post(
_url(remote.URL_API_EVENT_FORWARD),
_url(const.URL_API_EVENT_FORWARD),
headers=HA_HEADERS)
self.assertEqual(400, req.status_code)
req = requests.post(
_url(remote.URL_API_EVENT_FORWARD),
_url(const.URL_API_EVENT_FORWARD),
data=json.dumps({'host': '127.0.0.1'}),
headers=HA_HEADERS)
self.assertEqual(400, req.status_code)
req = requests.post(
_url(remote.URL_API_EVENT_FORWARD),
_url(const.URL_API_EVENT_FORWARD),
data=json.dumps({'api_password': 'bla-di-bla'}),
headers=HA_HEADERS)
self.assertEqual(400, req.status_code)
req = requests.post(
_url(remote.URL_API_EVENT_FORWARD),
_url(const.URL_API_EVENT_FORWARD),
data=json.dumps({
'api_password': 'bla-di-bla',
'host': '127.0.0.1',
@ -323,7 +344,7 @@ class TestAPI(unittest.TestCase):
self.assertEqual(422, req.status_code)
req = requests.post(
_url(remote.URL_API_EVENT_FORWARD),
_url(const.URL_API_EVENT_FORWARD),
data=json.dumps({
'api_password': 'bla-di-bla',
'host': '127.0.0.1',
@ -334,7 +355,7 @@ class TestAPI(unittest.TestCase):
# Setup a real one
req = requests.post(
_url(remote.URL_API_EVENT_FORWARD),
_url(const.URL_API_EVENT_FORWARD),
data=json.dumps({
'api_password': API_PASSWORD,
'host': '127.0.0.1',
@ -345,13 +366,13 @@ class TestAPI(unittest.TestCase):
# Delete it again..
req = requests.delete(
_url(remote.URL_API_EVENT_FORWARD),
_url(const.URL_API_EVENT_FORWARD),
data=json.dumps({}),
headers=HA_HEADERS)
self.assertEqual(400, req.status_code)
req = requests.delete(
_url(remote.URL_API_EVENT_FORWARD),
_url(const.URL_API_EVENT_FORWARD),
data=json.dumps({
'host': '127.0.0.1',
'port': 'abcd'
@ -360,7 +381,7 @@ class TestAPI(unittest.TestCase):
self.assertEqual(422, req.status_code)
req = requests.delete(
_url(remote.URL_API_EVENT_FORWARD),
_url(const.URL_API_EVENT_FORWARD),
data=json.dumps({
'host': '127.0.0.1',
'port': SERVER_PORT

View file

@ -27,23 +27,10 @@ class TestScript(unittest.TestCase):
""" Stop down stuff we started. """
self.hass.stop()
def test_setup_with_empty_sequence(self):
self.assertTrue(script.setup(self.hass, {
'script': {
'test': {
'sequence': []
}
}
}))
self.assertIsNone(self.hass.states.get(ENTITY_ID))
def test_setup_with_missing_sequence(self):
self.assertTrue(script.setup(self.hass, {
'script': {
'test': {
'sequence': []
}
'test': {}
}
}))
@ -60,6 +47,19 @@ class TestScript(unittest.TestCase):
self.assertEqual(0, len(self.hass.states.entity_ids('script')))
def test_setup_with_dict_as_sequence(self):
self.assertTrue(script.setup(self.hass, {
'script': {
'test': {
'sequence': {
'event': 'test_event'
}
}
}
}))
self.assertEqual(0, len(self.hass.states.entity_ids('script')))
def test_firing_event(self):
event = 'test_event'
calls = []