1
mirror of https://github.com/home-assistant/core synced 2024-08-02 23:40:32 +02:00

Refactor: code moved to new helper and constants file. Also adds support for multiple types for switch/light components.

This commit is contained in:
Paulus Schoutsen 2014-12-06 23:57:02 -08:00
parent 513a03fb46
commit 0527760e9b
41 changed files with 603 additions and 442 deletions

View File

@ -55,6 +55,8 @@ After you got the demo mode running it is time to enable some real components an
*Note:* you can append `?api_password=YOUR_PASSWORD` to the url of the web interface to log in automatically.
*Note:* for the light and switch component, you can specify multiple types by using sequential sections: [switch], [switch 2], [switch 3] etc
### Philips Hue
To get Philips Hue working you will have to connect Home Assistant to the Hue bridge.
@ -68,7 +70,7 @@ After that add the following lines to your `home-assistant.conf`:
```
[light]
type=hue
platform=hue
```
### Wireless router
@ -77,7 +79,7 @@ Your wireless router is used to track which devices are connected. Three differe
```
[device_tracker]
type=netgear
platform=netgear
host=192.168.1.1
username=admin
password=MY_PASSWORD

View File

@ -9,11 +9,11 @@ api_password=mypass
# development=1
[light]
type=hue
platform=hue
[device_tracker]
# The following types are available: netgear, tomato, luci
type=netgear
platform=netgear
host=192.168.1.1
username=admin
password=PASSWORD
@ -26,7 +26,7 @@ password=PASSWORD
# hosts=192.168.1.9,192.168.1.12
[switch]
type=wemo
platform=wemo
# Optional: hard code the hosts (comma seperated) to avoid scanning the network
# hosts=192.168.1.9,192.168.1.12

View File

@ -6,8 +6,8 @@ Provides a mock switch platform.
Call init before using it in your tests to ensure clean test data.
"""
import homeassistant.components as components
from ha_test.helper import MockToggleDevice
from homeassistant.const import STATE_ON, STATE_OFF
from ha_test.helpers import MockToggleDevice
DEVICES = []
@ -18,9 +18,9 @@ def init(empty=False):
global DEVICES
DEVICES = [] if empty else [
MockToggleDevice('Ceiling', components.STATE_ON),
MockToggleDevice('Ceiling', components.STATE_OFF),
MockToggleDevice(None, components.STATE_OFF)
MockToggleDevice('Ceiling', STATE_ON),
MockToggleDevice('Ceiling', STATE_OFF),
MockToggleDevice(None, STATE_OFF)
]

View File

@ -6,8 +6,8 @@ Provides a mock switch platform.
Call init before using it in your tests to ensure clean test data.
"""
import homeassistant.components as components
from ha_test.helper import MockToggleDevice
from homeassistant.const import STATE_ON, STATE_OFF
from ha_test.helpers import MockToggleDevice
DEVICES = []
@ -18,9 +18,9 @@ def init(empty=False):
global DEVICES
DEVICES = [] if empty else [
MockToggleDevice('AC', components.STATE_ON),
MockToggleDevice('AC', components.STATE_OFF),
MockToggleDevice(None, components.STATE_OFF)
MockToggleDevice('AC', STATE_ON),
MockToggleDevice('AC', STATE_OFF),
MockToggleDevice(None, STATE_OFF)
]

View File

@ -7,7 +7,8 @@ Helper method for writing tests.
import os
import homeassistant as ha
import homeassistant.components as components
from homeassistant.helpers import ToggleDevice
from homeassistant.const import STATE_ON, STATE_OFF
def get_test_home_assistant():
@ -41,7 +42,7 @@ class MockModule(object):
self.setup = lambda hass, config: False if setup is None else setup
class MockToggleDevice(components.ToggleDevice):
class MockToggleDevice(ToggleDevice):
""" Provides a mock toggle device. """
def __init__(self, name, state):
self.name = name
@ -56,17 +57,17 @@ class MockToggleDevice(components.ToggleDevice):
def turn_on(self, **kwargs):
""" Turn the device on. """
self.calls.append(('turn_on', kwargs))
self.state = components.STATE_ON
self.state = STATE_ON
def turn_off(self, **kwargs):
""" Turn the device off. """
self.calls.append(('turn_off', kwargs))
self.state = components.STATE_OFF
self.state = STATE_OFF
def is_on(self):
""" True if device is on. """
self.calls.append(('is_on', {}))
return self.state == components.STATE_ON
return self.state == STATE_ON
def last_call(self, method=None):
if method is None:

View File

@ -9,9 +9,13 @@ import logging
import unittest
import homeassistant as ha
import homeassistant.components as components
from homeassistant.const import (
SERVICE_TURN_OFF, SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN,
SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE,
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREV_TRACK, ATTR_ENTITY_ID,
CONF_HOSTS)
import homeassistant.components.chromecast as chromecast
from helper import mock_service
from helpers import mock_service
def setUpModule(): # pylint: disable=invalid-name
@ -45,14 +49,14 @@ class TestChromecast(unittest.TestCase):
Test if the call service methods conver to correct service calls.
"""
services = {
components.SERVICE_TURN_OFF: chromecast.turn_off,
components.SERVICE_VOLUME_UP: chromecast.volume_up,
components.SERVICE_VOLUME_DOWN: chromecast.volume_down,
components.SERVICE_MEDIA_PLAY_PAUSE: chromecast.media_play_pause,
components.SERVICE_MEDIA_PLAY: chromecast.media_play,
components.SERVICE_MEDIA_PAUSE: chromecast.media_pause,
components.SERVICE_MEDIA_NEXT_TRACK: chromecast.media_next_track,
components.SERVICE_MEDIA_PREV_TRACK: chromecast.media_prev_track
SERVICE_TURN_OFF: chromecast.turn_off,
SERVICE_VOLUME_UP: chromecast.volume_up,
SERVICE_VOLUME_DOWN: chromecast.volume_down,
SERVICE_MEDIA_PLAY_PAUSE: chromecast.media_play_pause,
SERVICE_MEDIA_PLAY: chromecast.media_play,
SERVICE_MEDIA_PAUSE: chromecast.media_pause,
SERVICE_MEDIA_NEXT_TRACK: chromecast.media_next_track,
SERVICE_MEDIA_PREV_TRACK: chromecast.media_prev_track
}
for service_name, service_method in services.items():
@ -75,7 +79,7 @@ class TestChromecast(unittest.TestCase):
self.assertEqual(call.domain, chromecast.DOMAIN)
self.assertEqual(call.service, service_name)
self.assertEqual(call.data,
{components.ATTR_ENTITY_ID: self.test_entity})
{ATTR_ENTITY_ID: self.test_entity})
def test_setup(self):
"""
@ -84,4 +88,4 @@ class TestChromecast(unittest.TestCase):
In an ideal world we would create a mock pychromecast API..
"""
self.assertFalse(chromecast.setup(
self.hass, {chromecast.DOMAIN: {ha.CONF_HOSTS: '127.0.0.1'}}))
self.hass, {chromecast.DOMAIN: {CONF_HOSTS: '127.0.0.1'}}))

View File

@ -9,6 +9,8 @@ import unittest
import homeassistant as ha
import homeassistant.loader as loader
from homeassistant.const import (
STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF)
import homeassistant.components as comps
@ -21,8 +23,8 @@ class TestComponentsCore(unittest.TestCase):
loader.prepare(self.hass)
self.assertTrue(comps.setup(self.hass, {}))
self.hass.states.set('light.Bowl', comps.STATE_ON)
self.hass.states.set('light.Ceiling', comps.STATE_OFF)
self.hass.states.set('light.Bowl', STATE_ON)
self.hass.states.set('light.Ceiling', STATE_OFF)
def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """
@ -38,7 +40,7 @@ class TestComponentsCore(unittest.TestCase):
""" Test turn_on method. """
runs = []
self.hass.services.register(
'light', comps.SERVICE_TURN_ON, lambda x: runs.append(1))
'light', SERVICE_TURN_ON, lambda x: runs.append(1))
comps.turn_on(self.hass, 'light.Ceiling')
@ -50,24 +52,10 @@ class TestComponentsCore(unittest.TestCase):
""" Test turn_off method. """
runs = []
self.hass.services.register(
'light', comps.SERVICE_TURN_OFF, lambda x: runs.append(1))
'light', SERVICE_TURN_OFF, lambda x: runs.append(1))
comps.turn_off(self.hass, 'light.Bowl')
self.hass._pool.block_till_done()
self.assertEqual(1, len(runs))
def test_extract_entity_ids(self):
""" Test extract_entity_ids method. """
call = ha.ServiceCall('light', 'turn_on',
{comps.ATTR_ENTITY_ID: 'light.Bowl'})
self.assertEqual(['light.Bowl'],
comps.extract_entity_ids(self.hass, call))
call = ha.ServiceCall('light', 'turn_on',
{comps.ATTR_ENTITY_ID: ['light.Bowl']})
self.assertEqual(['light.Bowl'],
comps.extract_entity_ids(self.hass, call))

View File

@ -9,7 +9,7 @@ import unittest
import homeassistant as ha
import homeassistant.components.demo as demo
from homeassistant.components import (
from homeassistant.const import (
SERVICE_TURN_ON, SERVICE_TURN_OFF, STATE_ON, STATE_OFF, ATTR_ENTITY_ID)

View File

@ -12,11 +12,11 @@ import os
import homeassistant as ha
import homeassistant.loader as loader
from homeassistant.components import (
STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE)
from homeassistant.const import (
STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, CONF_PLATFORM)
import homeassistant.components.device_tracker as device_tracker
from helper import get_test_home_assistant
from helpers import get_test_home_assistant
def setUpModule(): # pylint: disable=invalid-name
@ -64,7 +64,7 @@ class TestComponentsDeviceTracker(unittest.TestCase):
# Test with non-existing component
self.assertFalse(device_tracker.setup(
self.hass, {device_tracker.DOMAIN: {ha.CONF_TYPE: 'nonexisting'}}
self.hass, {device_tracker.DOMAIN: {CONF_PLATFORM: 'nonexisting'}}
))
# Test with a bad known device file around
@ -72,7 +72,7 @@ class TestComponentsDeviceTracker(unittest.TestCase):
fil.write("bad data\nbad data\n")
self.assertFalse(device_tracker.setup(self.hass, {
device_tracker.DOMAIN: {ha.CONF_TYPE: 'test'}
device_tracker.DOMAIN: {CONF_PLATFORM: 'test'}
}))
def test_device_tracker(self):
@ -84,7 +84,7 @@ class TestComponentsDeviceTracker(unittest.TestCase):
scanner.come_home('dev2')
self.assertTrue(device_tracker.setup(self.hass, {
device_tracker.DOMAIN: {ha.CONF_TYPE: 'test'}
device_tracker.DOMAIN: {CONF_PLATFORM: 'test'}
}))
# Ensure a new known devices file has been created.

View File

@ -9,7 +9,7 @@ import unittest
import logging
import homeassistant as ha
import homeassistant.components as comps
from homeassistant.const import STATE_ON, STATE_OFF, STATE_HOME, STATE_NOT_HOME
import homeassistant.components.group as group
@ -25,9 +25,9 @@ class TestComponentsGroup(unittest.TestCase):
""" Init needed objects. """
self.hass = ha.HomeAssistant()
self.hass.states.set('light.Bowl', comps.STATE_ON)
self.hass.states.set('light.Ceiling', comps.STATE_OFF)
self.hass.states.set('switch.AC', comps.STATE_OFF)
self.hass.states.set('light.Bowl', STATE_ON)
self.hass.states.set('light.Ceiling', STATE_OFF)
self.hass.states.set('switch.AC', STATE_OFF)
group.setup_group(self.hass, 'init_group',
['light.Bowl', 'light.Ceiling'], False)
group.setup_group(self.hass, 'mixed_group',
@ -47,27 +47,27 @@ class TestComponentsGroup(unittest.TestCase):
self.assertIn(self.group_name, self.hass.states.entity_ids())
group_state = self.hass.states.get(self.group_name)
self.assertEqual(comps.STATE_ON, group_state.state)
self.assertEqual(STATE_ON, group_state.state)
self.assertTrue(group_state.attributes[group.ATTR_AUTO])
# Turn the Bowl off and see if group turns off
self.hass.states.set('light.Bowl', comps.STATE_OFF)
self.hass.states.set('light.Bowl', STATE_OFF)
self.hass._pool.block_till_done()
group_state = self.hass.states.get(self.group_name)
self.assertEqual(comps.STATE_OFF, group_state.state)
self.assertEqual(STATE_OFF, group_state.state)
# Turn the Ceiling on and see if group turns on
self.hass.states.set('light.Ceiling', comps.STATE_ON)
self.hass.states.set('light.Ceiling', STATE_ON)
self.hass._pool.block_till_done()
group_state = self.hass.states.get(self.group_name)
self.assertEqual(comps.STATE_ON, group_state.state)
self.assertEqual(STATE_ON, group_state.state)
# Try to setup a group with mixed groupable states
self.hass.states.set('device_tracker.Paulus', comps.STATE_HOME)
self.hass.states.set('device_tracker.Paulus', STATE_HOME)
self.assertFalse(group.setup_group(
self.hass, 'person_and_light',
['light.Bowl', 'device_tracker.Paulus']))
@ -91,12 +91,12 @@ class TestComponentsGroup(unittest.TestCase):
def test__get_group_type(self):
""" Test _get_group_type method. """
self.assertEqual('on_off', group._get_group_type(comps.STATE_ON))
self.assertEqual('on_off', group._get_group_type(comps.STATE_OFF))
self.assertEqual('on_off', group._get_group_type(STATE_ON))
self.assertEqual('on_off', group._get_group_type(STATE_OFF))
self.assertEqual('home_not_home',
group._get_group_type(comps.STATE_HOME))
group._get_group_type(STATE_HOME))
self.assertEqual('home_not_home',
group._get_group_type(comps.STATE_NOT_HOME))
group._get_group_type(STATE_NOT_HOME))
# Unsupported state
self.assertIsNone(group._get_group_type('unsupported_state'))
@ -104,7 +104,7 @@ class TestComponentsGroup(unittest.TestCase):
def test_is_on(self):
""" Test is_on method. """
self.assertTrue(group.is_on(self.hass, self.group_name))
self.hass.states.set('light.Bowl', comps.STATE_OFF)
self.hass.states.set('light.Bowl', STATE_OFF)
self.hass._pool.block_till_done()
self.assertFalse(group.is_on(self.hass, self.group_name))
@ -159,5 +159,5 @@ class TestComponentsGroup(unittest.TestCase):
group_state = self.hass.states.get(
group.ENTITY_ID_FORMAT.format('second_group'))
self.assertEqual(comps.STATE_ON, group_state.state)
self.assertEqual(STATE_ON, group_state.state)
self.assertFalse(group_state.attributes[group.ATTR_AUTO])

View File

@ -11,12 +11,12 @@ import os
import homeassistant as ha
import homeassistant.loader as loader
import homeassistant.util as util
from homeassistant.components import (
get_component, ATTR_ENTITY_ID, STATE_ON, STATE_OFF,
from homeassistant.const import (
ATTR_ENTITY_ID, STATE_ON, STATE_OFF, CONF_TYPE,
SERVICE_TURN_ON, SERVICE_TURN_OFF)
import homeassistant.components.light as light
from helper import mock_service, get_test_home_assistant
from helpers import mock_service, get_test_home_assistant
class TestLight(unittest.TestCase):
@ -98,11 +98,11 @@ class TestLight(unittest.TestCase):
def test_services(self):
""" Test the provided services. """
platform = get_component('light.test')
platform = loader.get_component('light.test')
platform.init()
self.assertTrue(
light.setup(self.hass, {light.DOMAIN: {ha.CONF_TYPE: 'test'}}))
light.setup(self.hass, {light.DOMAIN: {CONF_TYPE: 'test'}}))
dev1, dev2, dev3 = platform.get_lights(None, None)
@ -223,22 +223,22 @@ class TestLight(unittest.TestCase):
# Test with non-existing component
self.assertFalse(light.setup(
self.hass, {light.DOMAIN: {ha.CONF_TYPE: 'nonexisting'}}
self.hass, {light.DOMAIN: {CONF_TYPE: 'nonexisting'}}
))
# Test if light component returns 0 lightes
platform = get_component('light.test')
platform = loader.get_component('light.test')
platform.init(True)
self.assertEqual([], platform.get_lights(None, None))
self.assertFalse(light.setup(
self.hass, {light.DOMAIN: {ha.CONF_TYPE: 'test'}}
self.hass, {light.DOMAIN: {CONF_TYPE: 'test'}}
))
def test_light_profiles(self):
""" Test light profiles. """
platform = get_component('light.test')
platform = loader.get_component('light.test')
platform.init()
user_light_file = self.hass.get_config_path(light.LIGHT_PROFILES_FILE)
@ -249,7 +249,7 @@ class TestLight(unittest.TestCase):
user_file.write('I,WILL,NOT,WORK\n')
self.assertFalse(light.setup(
self.hass, {light.DOMAIN: {ha.CONF_TYPE: 'test'}}
self.hass, {light.DOMAIN: {CONF_TYPE: 'test'}}
))
# Clean up broken file
@ -260,7 +260,7 @@ class TestLight(unittest.TestCase):
user_file.write('test,.4,.6,100\n')
self.assertTrue(light.setup(
self.hass, {light.DOMAIN: {ha.CONF_TYPE: 'test'}}
self.hass, {light.DOMAIN: {CONF_TYPE: 'test'}}
))
dev1, dev2, dev3 = platform.get_lights(None, None)

View File

@ -11,6 +11,7 @@ import datetime as dt
import ephem
import homeassistant as ha
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
import homeassistant.components.sun as sun
@ -37,8 +38,8 @@ class TestSun(unittest.TestCase):
self.assertTrue(sun.setup(
self.hass,
{ha.DOMAIN: {
ha.CONF_LATITUDE: '32.87336',
ha.CONF_LONGITUDE: '117.22743'
CONF_LATITUDE: '32.87336',
CONF_LONGITUDE: '117.22743'
}}))
observer = ephem.Observer()
@ -76,8 +77,8 @@ class TestSun(unittest.TestCase):
self.assertTrue(sun.setup(
self.hass,
{ha.DOMAIN: {
ha.CONF_LATITUDE: '32.87336',
ha.CONF_LONGITUDE: '117.22743'
CONF_LATITUDE: '32.87336',
CONF_LONGITUDE: '117.22743'
}}))
if sun.is_on(self.hass):
@ -101,24 +102,24 @@ class TestSun(unittest.TestCase):
self.assertFalse(sun.setup(self.hass, {}))
self.assertFalse(sun.setup(self.hass, {sun.DOMAIN: {}}))
self.assertFalse(sun.setup(
self.hass, {ha.DOMAIN: {ha.CONF_LATITUDE: '32.87336'}}))
self.hass, {ha.DOMAIN: {CONF_LATITUDE: '32.87336'}}))
self.assertFalse(sun.setup(
self.hass, {ha.DOMAIN: {ha.CONF_LONGITUDE: '117.22743'}}))
self.hass, {ha.DOMAIN: {CONF_LONGITUDE: '117.22743'}}))
self.assertFalse(sun.setup(
self.hass, {ha.DOMAIN: {ha.CONF_LATITUDE: 'hello'}}))
self.hass, {ha.DOMAIN: {CONF_LATITUDE: 'hello'}}))
self.assertFalse(sun.setup(
self.hass, {ha.DOMAIN: {ha.CONF_LONGITUDE: 'how are you'}}))
self.hass, {ha.DOMAIN: {CONF_LONGITUDE: 'how are you'}}))
self.assertFalse(sun.setup(
self.hass, {ha.DOMAIN: {
ha.CONF_LATITUDE: 'wrong', ha.CONF_LONGITUDE: '117.22743'
CONF_LATITUDE: 'wrong', CONF_LONGITUDE: '117.22743'
}}))
self.assertFalse(sun.setup(
self.hass, {ha.DOMAIN: {
ha.CONF_LATITUDE: '32.87336', ha.CONF_LONGITUDE: 'wrong'
CONF_LATITUDE: '32.87336', CONF_LONGITUDE: 'wrong'
}}))
# Test with correct config
self.assertTrue(sun.setup(
self.hass, {ha.DOMAIN: {
ha.CONF_LATITUDE: '32.87336', ha.CONF_LONGITUDE: '117.22743'
CONF_LATITUDE: '32.87336', CONF_LONGITUDE: '117.22743'
}}))

View File

@ -9,10 +9,10 @@ import unittest
import homeassistant as ha
import homeassistant.loader as loader
from homeassistant.components import get_component, STATE_ON, STATE_OFF
from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM
import homeassistant.components.switch as switch
from helper import get_test_home_assistant
from helpers import get_test_home_assistant
class TestSwitch(unittest.TestCase):
@ -22,11 +22,11 @@ class TestSwitch(unittest.TestCase):
self.hass = get_test_home_assistant()
loader.prepare(self.hass)
platform = get_component('switch.test')
platform = loader.get_component('switch.test')
platform.init()
self.assertTrue(switch.setup(
self.hass, {switch.DOMAIN: {ha.CONF_TYPE: 'test'}}
self.hass, {switch.DOMAIN: {CONF_PLATFORM: 'test'}}
))
# Switch 1 is ON, switch 2 is OFF
@ -90,15 +90,27 @@ class TestSwitch(unittest.TestCase):
# Test with non-existing component
self.assertFalse(switch.setup(
self.hass, {switch.DOMAIN: {ha.CONF_TYPE: 'nonexisting'}}
self.hass, {switch.DOMAIN: {CONF_PLATFORM: 'nonexisting'}}
))
# Test if switch component returns 0 switches
get_component('switch.test').init(True)
test_platform = loader.get_component('switch.test')
test_platform.init(True)
self.assertEqual(
[], get_component('switch.test').get_switches(None, None))
[], test_platform.get_switches(None, None))
self.assertFalse(switch.setup(
self.hass, {switch.DOMAIN: {ha.CONF_TYPE: 'test'}}
self.hass, {switch.DOMAIN: {CONF_PLATFORM: 'test'}}
))
# Test if we can load 2 platforms
loader.set_component('switch.test2', test_platform)
test_platform.init(False)
self.assertTrue(switch.setup(
self.hass, {
switch.DOMAIN: {CONF_PLATFORM: 'test'},
'{} 2'.format(switch.DOMAIN): {CONF_PLATFORM: 'test2'},
}
))

49
ha_test/test_helpers.py Normal file
View File

@ -0,0 +1,49 @@
"""
ha_test.test_helpers
~~~~~~~~~~~~~~~~~~~~
Tests component helpers.
"""
# pylint: disable=protected-access,too-many-public-methods
import unittest
from helpers import get_test_home_assistant
import homeassistant as ha
import homeassistant.loader as loader
from homeassistant.const import STATE_ON, STATE_OFF, ATTR_ENTITY_ID
from homeassistant.helpers import extract_entity_ids
class TestComponentsCore(unittest.TestCase):
""" Tests homeassistant.components module. """
def setUp(self): # pylint: disable=invalid-name
""" Init needed objects. """
self.hass = get_test_home_assistant()
loader.prepare(self.hass)
self.hass.states.set('light.Bowl', STATE_ON)
self.hass.states.set('light.Ceiling', STATE_OFF)
self.hass.states.set('light.Kitchen', STATE_OFF)
loader.get_component('group').setup_group(
self.hass, 'test', ['light.Ceiling', 'light.Kitchen'])
def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """
self.hass.stop()
def test_extract_entity_ids(self):
""" Test extract_entity_ids method. """
call = ha.ServiceCall('light', 'turn_on',
{ATTR_ENTITY_ID: 'light.Bowl'})
self.assertEqual(['light.Bowl'],
extract_entity_ids(self.hass, call))
call = ha.ServiceCall('light', 'turn_on',
{ATTR_ENTITY_ID: 'group.test'})
self.assertEqual(['light.Ceiling', 'light.Kitchen'],
extract_entity_ids(self.hass, call))

View File

@ -10,7 +10,7 @@ import unittest
import homeassistant.loader as loader
import homeassistant.components.http as http
from helper import get_test_home_assistant, MockModule
from helpers import get_test_home_assistant, MockModule
class TestLoader(unittest.TestCase):

View File

@ -15,32 +15,14 @@ import re
import datetime as dt
import functools as ft
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
SERVICE_HOMEASSISTANT_STOP, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED,
EVENT_CALL_SERVICE, ATTR_NOW, ATTR_DOMAIN, ATTR_SERVICE, MATCH_ALL)
import homeassistant.util as util
MATCH_ALL = '*'
DOMAIN = "homeassistant"
SERVICE_HOMEASSISTANT_STOP = "stop"
EVENT_HOMEASSISTANT_START = "homeassistant_start"
EVENT_HOMEASSISTANT_STOP = "homeassistant_stop"
EVENT_STATE_CHANGED = "state_changed"
EVENT_TIME_CHANGED = "time_changed"
EVENT_CALL_SERVICE = "services.call"
ATTR_NOW = "now"
ATTR_DOMAIN = "domain"
ATTR_SERVICE = "service"
CONF_LATITUDE = "latitude"
CONF_LONGITUDE = "longitude"
CONF_TYPE = "type"
CONF_HOST = "host"
CONF_HOSTS = "hosts"
CONF_USERNAME = "username"
CONF_PASSWORD = "password"
# How often time_changed event should fire
TIMER_INTERVAL = 10 # seconds

View File

@ -37,8 +37,9 @@ def from_config_dict(config, hass=None):
# Convert it to defaultdict so components can always have config dict
config = defaultdict(dict, config)
# Filter out the common config section [homeassistant]
components = (key for key in config.keys() if key != homeassistant.DOMAIN)
# Filter out the repeating and common config section [homeassistant]
components = (key for key in config.keys()
if ' ' not in key and key != homeassistant.DOMAIN)
# Setup the components
if core_components.setup(hass, config):

View File

@ -19,36 +19,10 @@ import logging
import homeassistant as ha
import homeassistant.util as util
from homeassistant.helpers import extract_entity_ids
from homeassistant.loader import get_component
# Contains one string or a list of strings, each being an entity id
ATTR_ENTITY_ID = 'entity_id'
# String with a friendly name for the entity
ATTR_FRIENDLY_NAME = "friendly_name"
# A picture to represent entity
ATTR_ENTITY_PICTURE = "entity_picture"
# The unit of measurement if applicable
ATTR_UNIT_OF_MEASUREMENT = "unit_of_measurement"
STATE_ON = 'on'
STATE_OFF = 'off'
STATE_HOME = 'home'
STATE_NOT_HOME = 'not_home'
SERVICE_TURN_ON = 'turn_on'
SERVICE_TURN_OFF = 'turn_off'
SERVICE_VOLUME_UP = "volume_up"
SERVICE_VOLUME_DOWN = "volume_down"
SERVICE_VOLUME_MUTE = "volume_mute"
SERVICE_MEDIA_PLAY_PAUSE = "media_play_pause"
SERVICE_MEDIA_PLAY = "media_play"
SERVICE_MEDIA_PAUSE = "media_pause"
SERVICE_MEDIA_NEXT_TRACK = "media_next_track"
SERVICE_MEDIA_PREV_TRACK = "media_prev_track"
from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF)
_LOGGER = logging.getLogger(__name__)
@ -96,79 +70,6 @@ def turn_off(hass, entity_id=None, **service_data):
hass.services.call(ha.DOMAIN, SERVICE_TURN_OFF, service_data)
def extract_entity_ids(hass, service):
"""
Helper method to extract a list of entity ids from a service call.
Will convert group entity ids to the entity ids it represents.
"""
entity_ids = []
if service.data and ATTR_ENTITY_ID in service.data:
group = get_component('group')
# Entity ID attr can be a list or a string
service_ent_id = service.data[ATTR_ENTITY_ID]
if isinstance(service_ent_id, list):
ent_ids = service_ent_id
else:
ent_ids = [service_ent_id]
entity_ids.extend(
ent_id for ent_id
in group.expand_entity_ids(hass, ent_ids)
if ent_id not in entity_ids)
return entity_ids
class ToggleDevice(object):
""" ABC for devices that can be turned on and off. """
# pylint: disable=no-self-use
entity_id = None
def get_name(self):
""" Returns the name of the device if any. """
return None
def turn_on(self, **kwargs):
""" Turn the device on. """
pass
def turn_off(self, **kwargs):
""" Turn the device off. """
pass
def is_on(self):
""" True if device is on. """
return False
def get_state_attributes(self):
""" Returns optional state attributes. """
return {}
def update(self):
""" Retrieve latest state from the real device. """
pass
def update_ha_state(self, hass, force_refresh=False):
"""
Updates Home Assistant with current state of device.
If force_refresh == True will update device before setting state.
"""
if self.entity_id is None:
raise ha.NoEntitySpecifiedError(
"No entity specified for device {}".format(self.get_name()))
if force_refresh:
self.update()
state = STATE_ON if self.is_on() else STATE_OFF
return hass.states.set(self.entity_id, state,
self.get_state_attributes())
# pylint: disable=unused-argument
def setup(hass, config):
""" Setup general services related to homeassistant. """

View File

@ -6,9 +6,14 @@ Provides functionality to interact with Chromecasts.
"""
import logging
import homeassistant as ha
import homeassistant.util as util
import homeassistant.components as components
from homeassistant.helpers import extract_entity_ids
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, SERVICE_TURN_OFF, SERVICE_VOLUME_UP,
SERVICE_VOLUME_DOWN, SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY,
SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREV_TRACK,
CONF_HOSTS)
DOMAIN = 'chromecast'
DEPENDENCIES = []
@ -46,58 +51,58 @@ def is_on(hass, entity_id=None):
def turn_off(hass, entity_id=None):
""" Will turn off specified Chromecast or all. """
data = {components.ATTR_ENTITY_ID: entity_id} if entity_id else {}
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, components.SERVICE_TURN_OFF, data)
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
def volume_up(hass, entity_id=None):
""" Send the chromecast the command for volume up. """
data = {components.ATTR_ENTITY_ID: entity_id} if entity_id else {}
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, components.SERVICE_VOLUME_UP, data)
hass.services.call(DOMAIN, SERVICE_VOLUME_UP, data)
def volume_down(hass, entity_id=None):
""" Send the chromecast the command for volume down. """
data = {components.ATTR_ENTITY_ID: entity_id} if entity_id else {}
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, components.SERVICE_VOLUME_DOWN, data)
hass.services.call(DOMAIN, SERVICE_VOLUME_DOWN, data)
def media_play_pause(hass, entity_id=None):
""" Send the chromecast the command for play/pause. """
data = {components.ATTR_ENTITY_ID: entity_id} if entity_id else {}
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, components.SERVICE_MEDIA_PLAY_PAUSE, data)
hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY_PAUSE, data)
def media_play(hass, entity_id=None):
""" Send the chromecast the command for play/pause. """
data = {components.ATTR_ENTITY_ID: entity_id} if entity_id else {}
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, components.SERVICE_MEDIA_PLAY, data)
hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY, data)
def media_pause(hass, entity_id=None):
""" Send the chromecast the command for play/pause. """
data = {components.ATTR_ENTITY_ID: entity_id} if entity_id else {}
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, components.SERVICE_MEDIA_PAUSE, data)
hass.services.call(DOMAIN, SERVICE_MEDIA_PAUSE, data)
def media_next_track(hass, entity_id=None):
""" Send the chromecast the command for next track. """
data = {components.ATTR_ENTITY_ID: entity_id} if entity_id else {}
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, components.SERVICE_MEDIA_NEXT_TRACK, data)
hass.services.call(DOMAIN, SERVICE_MEDIA_NEXT_TRACK, data)
def media_prev_track(hass, entity_id=None):
""" Send the chromecast the command for prev track. """
data = {components.ATTR_ENTITY_ID: entity_id} if entity_id else {}
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, components.SERVICE_MEDIA_PREV_TRACK, data)
hass.services.call(DOMAIN, SERVICE_MEDIA_PREV_TRACK, data)
# pylint: disable=too-many-locals, too-many-branches
@ -114,8 +119,8 @@ def setup(hass, config):
return False
if ha.CONF_HOSTS in config[DOMAIN]:
hosts = config[DOMAIN][ha.CONF_HOSTS].split(",")
if CONF_HOSTS in config[DOMAIN]:
hosts = config[DOMAIN][CONF_HOSTS].split(",")
# If no hosts given, scan for chromecasts
else:
@ -131,7 +136,7 @@ def setup(hass, config):
entity_id = util.ensure_unique_string(
ENTITY_ID_FORMAT.format(
util.slugify(cast.device.friendly_name)),
list(casts.keys()))
casts.keys())
casts[entity_id] = cast
@ -148,7 +153,7 @@ def setup(hass, config):
status = chromecast.app
state_attr = {components.ATTR_FRIENDLY_NAME:
state_attr = {ATTR_FRIENDLY_NAME:
chromecast.device.friendly_name}
if status and status.app_id != pychromecast.APP_ID['HOME']:
@ -196,7 +201,7 @@ def setup(hass, config):
def _service_to_entities(service):
""" Helper method to get entities from service. """
entity_ids = components.extract_entity_ids(hass, service)
entity_ids = extract_entity_ids(hass, service)
if entity_ids:
for entity_id in entity_ids:
@ -274,25 +279,25 @@ def setup(hass, config):
hass.track_time_change(update_chromecast_states)
hass.services.register(DOMAIN, components.SERVICE_TURN_OFF,
hass.services.register(DOMAIN, SERVICE_TURN_OFF,
turn_off_service)
hass.services.register(DOMAIN, components.SERVICE_VOLUME_UP,
hass.services.register(DOMAIN, SERVICE_VOLUME_UP,
volume_up_service)
hass.services.register(DOMAIN, components.SERVICE_VOLUME_DOWN,
hass.services.register(DOMAIN, SERVICE_VOLUME_DOWN,
volume_down_service)
hass.services.register(DOMAIN, components.SERVICE_MEDIA_PLAY_PAUSE,
hass.services.register(DOMAIN, SERVICE_MEDIA_PLAY_PAUSE,
media_play_pause_service)
hass.services.register(DOMAIN, components.SERVICE_MEDIA_PLAY,
hass.services.register(DOMAIN, SERVICE_MEDIA_PLAY,
media_play_service)
hass.services.register(DOMAIN, components.SERVICE_MEDIA_PAUSE,
hass.services.register(DOMAIN, SERVICE_MEDIA_PAUSE,
media_pause_service)
hass.services.register(DOMAIN, components.SERVICE_MEDIA_NEXT_TRACK,
hass.services.register(DOMAIN, SERVICE_MEDIA_NEXT_TRACK,
media_next_track_service)
hass.services.register(DOMAIN, "start_fireplace",

View File

@ -8,9 +8,10 @@ import random
import homeassistant as ha
import homeassistant.loader as loader
from homeassistant.components import (
from homeassistant.helpers import extract_entity_ids
from homeassistant.const import (
SERVICE_TURN_ON, SERVICE_TURN_OFF, STATE_ON, STATE_OFF,
ATTR_ENTITY_PICTURE, ATTR_ENTITY_ID, extract_entity_ids)
ATTR_ENTITY_PICTURE, ATTR_ENTITY_ID, CONF_LATITUDE, CONF_LONGITUDE)
from homeassistant.components.light import (
ATTR_XY_COLOR, ATTR_BRIGHTNESS, GROUP_NAME_ALL_LIGHTS)
from homeassistant.util import split_entity_id
@ -65,11 +66,11 @@ def setup(hass, config):
hass.states.set(entity_id, STATE_OFF)
# Setup sun
if ha.CONF_LATITUDE not in config[ha.DOMAIN]:
config[ha.DOMAIN][ha.CONF_LATITUDE] = '32.87336'
if CONF_LATITUDE not in config[ha.DOMAIN]:
config[ha.DOMAIN][CONF_LATITUDE] = '32.87336'
if ha.CONF_LONGITUDE not in config[ha.DOMAIN]:
config[ha.DOMAIN][ha.CONF_LONGITUDE] = '-117.22743'
if CONF_LONGITUDE not in config[ha.DOMAIN]:
config[ha.DOMAIN][CONF_LONGITUDE] = '-117.22743'
loader.get_component('sun').setup(hass, config)

View File

@ -8,7 +8,7 @@ the state of the sun and devices.
import logging
from datetime import datetime, timedelta
import homeassistant.components as components
from homeassistant.const import STATE_HOME, STATE_NOT_HOME
from . import light, sun, device_tracker, group
DOMAIN = "device_sun_light_trigger"
@ -108,7 +108,7 @@ def setup(hass, config):
# Specific device came home ?
if entity != device_tracker.ENTITY_ID_ALL_DEVICES and \
new_state.state == components.STATE_HOME:
new_state.state == STATE_HOME:
# These variables are needed for the elif check
now = datetime.now()
@ -143,7 +143,7 @@ def setup(hass, config):
# Did all devices leave the house?
elif (entity == device_tracker.ENTITY_ID_ALL_DEVICES and
new_state.state == components.STATE_NOT_HOME and lights_are_on
new_state.state == STATE_NOT_HOME and lights_are_on
and not disable_turn_off):
logger.info(
@ -154,12 +154,12 @@ def setup(hass, config):
# Track home coming of each device
hass.states.track_change(
device_entity_ids, check_light_on_dev_state_change,
components.STATE_NOT_HOME, components.STATE_HOME)
STATE_NOT_HOME, STATE_HOME)
# Track when all devices are gone to shut down lights
hass.states.track_change(
device_tracker.ENTITY_ID_ALL_DEVICES,
check_light_on_dev_state_change,
components.STATE_HOME, components.STATE_NOT_HOME)
STATE_HOME, STATE_NOT_HOME)
return True

View File

@ -10,12 +10,14 @@ import os
import csv
from datetime import datetime, timedelta
import homeassistant as ha
from homeassistant.loader import get_component
from homeassistant.helpers import validate_config
import homeassistant.util as util
from homeassistant.components import (
group, STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, ATTR_FRIENDLY_NAME)
from homeassistant.const import (
STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, ATTR_FRIENDLY_NAME,
CONF_PLATFORM, CONF_TYPE)
from homeassistant.components import group
DOMAIN = "device_tracker"
DEPENDENCIES = []
@ -49,10 +51,20 @@ def is_on(hass, entity_id=None):
def setup(hass, config):
""" Sets up the device tracker. """
if not util.validate_config(config, {DOMAIN: [ha.CONF_TYPE]}, _LOGGER):
# CONF_TYPE is deprecated for CONF_PLATOFRM. We keep supporting it for now.
if not (validate_config(config, {DOMAIN: [CONF_PLATFORM]}, _LOGGER)
or validate_config(config, {DOMAIN: [CONF_TYPE]}, _LOGGER)):
return False
tracker_type = config[DOMAIN][ha.CONF_TYPE]
tracker_type = config[DOMAIN].get(CONF_PLATFORM)
if tracker_type is None:
tracker_type = config[DOMAIN][CONF_TYPE]
_LOGGER.warning((
"Please update your config for %s to use 'platform' "
"instead of 'type'"), tracker_type)
tracker_implementation = get_component(
'device_tracker.{}'.format(tracker_type))

View File

@ -6,8 +6,9 @@ import re
import threading
import requests
import homeassistant as ha
import homeassistant.util as util
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
from homeassistant.components.device_tracker import DOMAIN
# Return cached results if last scan was less then this time ago
@ -19,10 +20,9 @@ _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def get_scanner(hass, config):
""" Validates config and returns a Luci scanner. """
if not util.validate_config(config,
{DOMAIN: [ha.CONF_HOST, ha.CONF_USERNAME,
ha.CONF_PASSWORD]},
_LOGGER):
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
return None
scanner = LuciDeviceScanner(config[DOMAIN])
@ -45,8 +45,8 @@ class LuciDeviceScanner(object):
"""
def __init__(self, config):
host = config[ha.CONF_HOST]
username, password = config[ha.CONF_USERNAME], config[ha.CONF_PASSWORD]
host = config[CONF_HOST]
username, password = config[CONF_USERNAME], config[CONF_PASSWORD]
self.parse_api_pattern = re.compile(r"(?P<param>\w*) = (?P<value>.*);")
@ -87,7 +87,7 @@ class LuciDeviceScanner(object):
return
return self.mac2name.get(device, None)
@util.Throttle(MIN_TIME_BETWEEN_SCANS)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
""" Ensures the information from the Luci router is up to date.
Returns boolean if scanning successful. """

View File

@ -3,8 +3,9 @@ import logging
from datetime import timedelta
import threading
import homeassistant as ha
import homeassistant.util as util
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
from homeassistant.components.device_tracker import DOMAIN
# Return cached results if last scan was less then this time ago
@ -16,10 +17,9 @@ _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def get_scanner(hass, config):
""" Validates config and returns a Netgear scanner. """
if not util.validate_config(config,
{DOMAIN: [ha.CONF_HOST, ha.CONF_USERNAME,
ha.CONF_PASSWORD]},
_LOGGER):
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
return None
scanner = NetgearDeviceScanner(config[DOMAIN])
@ -31,8 +31,8 @@ class NetgearDeviceScanner(object):
""" This class queries a Netgear wireless router using the SOAP-api. """
def __init__(self, config):
host = config[ha.CONF_HOST]
username, password = config[ha.CONF_USERNAME], config[ha.CONF_PASSWORD]
host = config[CONF_HOST]
username, password = config[CONF_USERNAME], config[CONF_PASSWORD]
self.last_results = []
@ -82,7 +82,7 @@ class NetgearDeviceScanner(object):
else:
return None
@util.Throttle(MIN_TIME_BETWEEN_SCANS)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
""" Retrieves latest information from the Netgear router.
Returns boolean if scanning successful. """

View File

@ -7,8 +7,9 @@ import threading
import requests
import homeassistant as ha
import homeassistant.util as util
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
from homeassistant.components.device_tracker import DOMAIN
# Return cached results if last scan was less then this time ago
@ -22,10 +23,10 @@ _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def get_scanner(hass, config):
""" Validates config and returns a Tomato scanner. """
if not util.validate_config(config,
{DOMAIN: [ha.CONF_HOST, ha.CONF_USERNAME,
ha.CONF_PASSWORD, CONF_HTTP_ID]},
_LOGGER):
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME,
CONF_PASSWORD, CONF_HTTP_ID]},
_LOGGER):
return None
return TomatoDeviceScanner(config[DOMAIN])
@ -40,8 +41,8 @@ class TomatoDeviceScanner(object):
"""
def __init__(self, config):
host, http_id = config[ha.CONF_HOST], config[CONF_HTTP_ID]
username, password = config[ha.CONF_USERNAME], config[ha.CONF_PASSWORD]
host, http_id = config[CONF_HOST], config[CONF_HTTP_ID]
username, password = config[CONF_USERNAME], config[CONF_PASSWORD]
self.req = requests.Request('POST',
'http://{}/update.cgi'.format(host),
@ -78,7 +79,7 @@ class TomatoDeviceScanner(object):
else:
return filter_named[0]
@util.Throttle(MIN_TIME_BETWEEN_SCANS)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_tomato_info(self):
""" Ensures the information from the Tomato router is up to date.
Returns boolean if scanning successful. """

View File

@ -9,7 +9,8 @@ import logging
import re
import threading
import homeassistant.util as util
from homeassistant.helpers import validate_config
from homeassistant.util import sanitize_filename
DOMAIN = "downloader"
DEPENDENCIES = []
@ -36,7 +37,7 @@ def setup(hass, config):
return False
if not util.validate_config(config, {DOMAIN: [CONF_DOWNLOAD_DIR]}, logger):
if not validate_config(config, {DOMAIN: [CONF_DOWNLOAD_DIR]}, logger):
return False
download_path = config[DOMAIN][CONF_DOWNLOAD_DIR]
@ -64,7 +65,7 @@ def setup(hass, config):
subdir = service.data.get(ATTR_SUBDIR)
if subdir:
subdir = util.sanitize_filename(subdir)
subdir = sanitize_filename(subdir)
final_path = None
@ -88,7 +89,7 @@ def setup(hass, config):
filename = "ha_download"
# Remove stuff to ruin paths
filename = util.sanitize_filename(filename)
filename = sanitize_filename(filename)
# Do we want to download to subdir, create if needed
if subdir:

View File

@ -9,9 +9,8 @@ import logging
import homeassistant as ha
import homeassistant.util as util
from homeassistant.components import (STATE_ON, STATE_OFF,
STATE_HOME, STATE_NOT_HOME,
ATTR_ENTITY_ID)
from homeassistant.const import (
ATTR_ENTITY_ID, STATE_ON, STATE_OFF, STATE_HOME, STATE_NOT_HOME)
DOMAIN = "group"
DEPENDENCIES = []

View File

@ -83,6 +83,10 @@ from socketserver import ThreadingMixIn
from urllib.parse import urlparse, parse_qs
import homeassistant as ha
from homeassistant.const import (
SERVER_PORT, URL_API, URL_API_STATES, URL_API_EVENTS, URL_API_SERVICES,
URL_API_EVENT_FORWARD, URL_API_STATES_ENTITY, AUTH_HEADER)
from homeassistant.helpers import validate_config
import homeassistant.remote as rem
import homeassistant.util as util
from . import frontend
@ -116,8 +120,7 @@ _LOGGER = logging.getLogger(__name__)
def setup(hass, config):
""" Sets up the HTTP API and debug interface. """
if not util.validate_config(config, {DOMAIN: [CONF_API_PASSWORD]},
_LOGGER):
if not validate_config(config, {DOMAIN: [CONF_API_PASSWORD]}, _LOGGER):
return False
api_password = config[DOMAIN][CONF_API_PASSWORD]
@ -125,7 +128,7 @@ def setup(hass, config):
# 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, rem.SERVER_PORT)
server_port = config[DOMAIN].get(CONF_SERVER_PORT, SERVER_PORT)
development = config[DOMAIN].get(CONF_DEVELOPMENT, "") == "1"
@ -196,10 +199,10 @@ class RequestHandler(SimpleHTTPRequestHandler):
('GET', URL_ROOT, '_handle_get_root'),
# /api - for validation purposes
('GET', rem.URL_API, '_handle_get_api'),
('GET', URL_API, '_handle_get_api'),
# /states
('GET', rem.URL_API_STATES, '_handle_get_api_states'),
('GET', URL_API_STATES, '_handle_get_api_states'),
('GET',
re.compile(r'/api/states/(?P<entity_id>[a-zA-Z\._0-9]+)'),
'_handle_get_api_states_entity'),
@ -211,13 +214,13 @@ class RequestHandler(SimpleHTTPRequestHandler):
'_handle_post_state_entity'),
# /events
('GET', rem.URL_API_EVENTS, '_handle_get_api_events'),
('GET', URL_API_EVENTS, '_handle_get_api_events'),
('POST',
re.compile(r'/api/events/(?P<event_type>[a-zA-Z\._0-9]+)'),
'_handle_api_post_events_event'),
# /services
('GET', rem.URL_API_SERVICES, '_handle_get_api_services'),
('GET', URL_API_SERVICES, '_handle_get_api_services'),
('POST',
re.compile((r'/api/services/'
r'(?P<domain>[a-zA-Z\._0-9]+)/'
@ -225,8 +228,8 @@ class RequestHandler(SimpleHTTPRequestHandler):
'_handle_post_api_services_domain_service'),
# /event_forwarding
('POST', rem.URL_API_EVENT_FORWARD, '_handle_post_api_event_forward'),
('DELETE', rem.URL_API_EVENT_FORWARD,
('POST', URL_API_EVENT_FORWARD, '_handle_post_api_event_forward'),
('DELETE', URL_API_EVENT_FORWARD,
'_handle_delete_api_event_forward'),
# Static files
@ -270,7 +273,7 @@ class RequestHandler(SimpleHTTPRequestHandler):
"Error parsing JSON", HTTP_UNPROCESSABLE_ENTITY)
return
api_password = self.headers.get(rem.AUTH_HEADER)
api_password = self.headers.get(AUTH_HEADER)
if not api_password and DATA_API_PASSWORD in data:
api_password = data[DATA_API_PASSWORD]
@ -427,7 +430,7 @@ class RequestHandler(SimpleHTTPRequestHandler):
self._write_json(
state.as_dict(),
status_code=status_code,
location=rem.URL_API_STATES_ENTITY.format(entity_id))
location=URL_API_STATES_ENTITY.format(entity_id))
def _handle_get_api_events(self, path_match, data):
""" Handles getting overview of event listeners. """

View File

@ -1,12 +1,16 @@
"""
homeassistant.components.keyboard
homeassistant.keyboard
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to emulate keyboard presses on host machine.
"""
import logging
import homeassistant.components as components
from homeassistant.const import (
SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_MUTE,
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREV_TRACK,
SERVICE_MEDIA_PLAY_PAUSE)
DOMAIN = "keyboard"
DEPENDENCIES = []
@ -14,32 +18,32 @@ DEPENDENCIES = []
def volume_up(hass):
""" Press the keyboard button for volume up. """
hass.services.call(DOMAIN, components.SERVICE_VOLUME_UP)
hass.services.call(DOMAIN, SERVICE_VOLUME_UP)
def volume_down(hass):
""" Press the keyboard button for volume down. """
hass.services.call(DOMAIN, components.SERVICE_VOLUME_DOWN)
hass.services.call(DOMAIN, SERVICE_VOLUME_DOWN)
def volume_mute(hass):
""" Press the keyboard button for muting volume. """
hass.services.call(DOMAIN, components.SERVICE_VOLUME_MUTE)
hass.services.call(DOMAIN, SERVICE_VOLUME_MUTE)
def media_play_pause(hass):
""" Press the keyboard button for play/pause. """
hass.services.call(DOMAIN, components.SERVICE_MEDIA_PLAY_PAUSE)
hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY_PAUSE)
def media_next_track(hass):
""" Press the keyboard button for next track. """
hass.services.call(DOMAIN, components.SERVICE_MEDIA_NEXT_TRACK)
hass.services.call(DOMAIN, SERVICE_MEDIA_NEXT_TRACK)
def media_prev_track(hass):
""" Press the keyboard button for prev track. """
hass.services.call(DOMAIN, components.SERVICE_MEDIA_PREV_TRACK)
hass.services.call(DOMAIN, SERVICE_MEDIA_PREV_TRACK)
# pylint: disable=unused-argument
@ -56,27 +60,27 @@ def setup(hass, config):
keyboard = pykeyboard.PyKeyboard()
keyboard.special_key_assignment()
hass.services.register(DOMAIN, components.SERVICE_VOLUME_UP,
hass.services.register(DOMAIN, SERVICE_VOLUME_UP,
lambda service:
keyboard.tap_key(keyboard.volume_up_key))
hass.services.register(DOMAIN, components.SERVICE_VOLUME_DOWN,
hass.services.register(DOMAIN, SERVICE_VOLUME_DOWN,
lambda service:
keyboard.tap_key(keyboard.volume_down_key))
hass.services.register(DOMAIN, components.SERVICE_VOLUME_MUTE,
hass.services.register(DOMAIN, SERVICE_VOLUME_MUTE,
lambda service:
keyboard.tap_key(keyboard.volume_mute_key))
hass.services.register(DOMAIN, components.SERVICE_MEDIA_PLAY_PAUSE,
hass.services.register(DOMAIN, SERVICE_MEDIA_PLAY_PAUSE,
lambda service:
keyboard.tap_key(keyboard.media_play_pause_key))
hass.services.register(DOMAIN, components.SERVICE_MEDIA_NEXT_TRACK,
hass.services.register(DOMAIN, SERVICE_MEDIA_NEXT_TRACK,
lambda service:
keyboard.tap_key(keyboard.media_next_track_key))
hass.services.register(DOMAIN, components.SERVICE_MEDIA_PREV_TRACK,
hass.services.register(DOMAIN, SERVICE_MEDIA_PREV_TRACK,
lambda service:
keyboard.tap_key(keyboard.media_prev_track_key))

View File

@ -52,12 +52,12 @@ import logging
import os
import csv
import homeassistant as ha
from homeassistant.loader import get_component
import homeassistant.util as util
from homeassistant.components import (
group, extract_entity_ids, STATE_ON,
SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
from homeassistant.const import (
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
from homeassistant.helpers import (
extract_entity_ids, platform_devices_from_config)
from homeassistant.components import group
DOMAIN = "light"
@ -138,9 +138,6 @@ def turn_off(hass, entity_id=None, transition=None):
def setup(hass, config):
""" Exposes light control via statemachine and services. """
if not util.validate_config(config, {DOMAIN: [ha.CONF_TYPE]}, _LOGGER):
return False
# Load built-in profiles and custom profiles
profile_paths = [os.path.join(os.path.dirname(__file__),
LIGHT_PROFILES_FILE),
@ -169,20 +166,9 @@ def setup(hass, config):
return False
# Load platform
light_type = config[DOMAIN][ha.CONF_TYPE]
lights = platform_devices_from_config(config, DOMAIN, hass, _LOGGER)
light_init = get_component('light.{}'.format(light_type))
if light_init is None:
_LOGGER.error("Unknown light type specified: %s", light_type)
return False
lights = light_init.get_lights(hass, config[DOMAIN])
if len(lights) == 0:
_LOGGER.error("No lights found")
if not lights:
return False
ent_to_light = {}
@ -198,7 +184,7 @@ def setup(hass, config):
entity_id = util.ensure_unique_string(
ENTITY_ID_FORMAT.format(util.slugify(name)),
list(ent_to_light.keys()))
ent_to_light.keys())
light.entity_id = entity_id
ent_to_light[entity_id] = light

View File

@ -3,9 +3,9 @@ import logging
import socket
from datetime import timedelta
import homeassistant as ha
import homeassistant.util as util
from homeassistant.components import ToggleDevice, ATTR_FRIENDLY_NAME
from homeassistant.helpers import ToggleDevice
from homeassistant.const import ATTR_FRIENDLY_NAME, CONF_HOST
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_XY_COLOR, ATTR_TRANSITION)
@ -15,7 +15,7 @@ MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1)
PHUE_CONFIG_FILE = "phue.conf"
def get_lights(hass, config):
def get_devices(hass, config):
""" Gets the Hue lights. """
logger = logging.getLogger(__name__)
try:
@ -25,7 +25,7 @@ def get_lights(hass, config):
return []
host = config.get(ha.CONF_HOST, None)
host = config.get(CONF_HOST, None)
try:
bridge = phue.Bridge(

View File

@ -10,7 +10,7 @@ Author: Markus Stenberg <fingon@iki.fi>
import os
from homeassistant.components import STATE_ON, STATE_OFF
from homeassistant.const import STATE_ON, STATE_OFF
import homeassistant.util as util
DOMAIN = 'process'

View File

@ -8,7 +8,9 @@ import logging
from datetime import datetime, timedelta
import homeassistant as ha
import homeassistant.util as util
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
from homeassistant.helpers import validate_config
from homeassistant.util import str_to_datetime, datetime_to_str
DEPENDENCIES = []
DOMAIN = "sun"
@ -35,7 +37,7 @@ def next_setting(hass, entity_id=None):
state = hass.states.get(ENTITY_ID)
try:
return util.str_to_datetime(state.attributes[STATE_ATTR_NEXT_SETTING])
return str_to_datetime(state.attributes[STATE_ATTR_NEXT_SETTING])
except (AttributeError, KeyError):
# AttributeError if state is None
# KeyError if STATE_ATTR_NEXT_SETTING does not exist
@ -49,7 +51,7 @@ def next_rising(hass, entity_id=None):
state = hass.states.get(ENTITY_ID)
try:
return util.str_to_datetime(state.attributes[STATE_ATTR_NEXT_RISING])
return str_to_datetime(state.attributes[STATE_ATTR_NEXT_RISING])
except (AttributeError, KeyError):
# AttributeError if state is None
# KeyError if STATE_ATTR_NEXT_RISING does not exist
@ -60,10 +62,9 @@ def setup(hass, config):
""" Tracks the state of the sun. """
logger = logging.getLogger(__name__)
if not util.validate_config(config,
{ha.DOMAIN: [ha.CONF_LATITUDE,
ha.CONF_LONGITUDE]},
logger):
if not validate_config(config,
{ha.DOMAIN: [CONF_LATITUDE, CONF_LONGITUDE]},
logger):
return False
try:
@ -74,8 +75,8 @@ def setup(hass, config):
sun = ephem.Sun() # pylint: disable=no-member
latitude = config[ha.DOMAIN][ha.CONF_LATITUDE]
longitude = config[ha.DOMAIN][ha.CONF_LONGITUDE]
latitude = config[ha.DOMAIN][CONF_LATITUDE]
longitude = config[ha.DOMAIN][CONF_LONGITUDE]
# Validate latitude and longitude
observer = ephem.Observer()
@ -123,8 +124,8 @@ def setup(hass, config):
new_state, next_change.strftime("%H:%M"))
state_attributes = {
STATE_ATTR_NEXT_RISING: util.datetime_to_str(next_rising_dt),
STATE_ATTR_NEXT_SETTING: util.datetime_to_str(next_setting_dt)
STATE_ATTR_NEXT_RISING: datetime_to_str(next_rising_dt),
STATE_ATTR_NEXT_SETTING: datetime_to_str(next_setting_dt)
}
hass.states.set(ENTITY_ID, new_state, state_attributes)

View File

@ -6,12 +6,12 @@ Component to interface with various switches that can be controlled remotely.
import logging
from datetime import timedelta
import homeassistant as ha
import homeassistant.util as util
from homeassistant.loader import get_component
from homeassistant.components import (
group, extract_entity_ids, STATE_ON,
SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
from homeassistant.const import (
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
from homeassistant.helpers import (
extract_entity_ids, platform_devices_from_config)
from homeassistant.components import group
DOMAIN = 'switch'
DEPENDENCIES = []
@ -53,27 +53,13 @@ def turn_off(hass, entity_id=None):
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
# pylint: disable=too-many-branches
def setup(hass, config):
""" Track states and offer events for switches. """
logger = logging.getLogger(__name__)
if not util.validate_config(config, {DOMAIN: [ha.CONF_TYPE]}, logger):
return False
switches = platform_devices_from_config(config, DOMAIN, hass, logger)
switch_type = config[DOMAIN][ha.CONF_TYPE]
switch_init = get_component('switch.{}'.format(switch_type))
if switch_init is None:
logger.error("Error loading switch component %s", switch_type)
return False
switches = switch_init.get_switches(hass, config[DOMAIN])
if len(switches) == 0:
logger.error("No switches found")
if not switches:
return False
# Setup a dict mapping entity IDs to devices
@ -90,7 +76,7 @@ def setup(hass, config):
entity_id = util.ensure_unique_string(
ENTITY_ID_FORMAT.format(util.slugify(name)),
list(ent_to_switch.keys()))
ent_to_switch.keys())
switch.entity_id = entity_id
ent_to_switch[entity_id] = switch

View File

@ -1,7 +1,8 @@
""" Support for Tellstick switches. """
import logging
from homeassistant.components import ToggleDevice, ATTR_FRIENDLY_NAME
from homeassistant.helpers import ToggleDevice
from homeassistant.const import ATTR_FRIENDLY_NAME
try:
import tellcore.constants as tc_constants
@ -11,7 +12,7 @@ except ImportError:
# pylint: disable=unused-argument
def get_switches(hass, config):
def get_devices(hass, config):
""" Find and return Tellstick switches. """
try:
import tellcore.telldus as telldus

View File

@ -1,12 +1,12 @@
""" Support for WeMo switchces. """
import logging
import homeassistant as ha
from homeassistant.components import ToggleDevice, ATTR_FRIENDLY_NAME
from homeassistant.helpers import ToggleDevice
from homeassistant.const import ATTR_FRIENDLY_NAME, CONF_HOSTS
# pylint: disable=unused-argument
def get_switches(hass, config):
def get_devices(hass, config):
""" Find and return WeMo switches. """
try:
@ -21,9 +21,9 @@ def get_switches(hass, config):
return []
if ha.CONF_HOSTS in config:
if CONF_HOSTS in config:
switches = (pywemo.device_from_host(host) for host
in config[ha.CONF_HOSTS].split(","))
in config[CONF_HOSTS].split(","))
else:
logging.getLogger(__name__).info("Scanning for WeMo devices")

View File

@ -26,8 +26,7 @@ import logging
from collections import namedtuple
import homeassistant.util as util
from homeassistant.components import (ATTR_FRIENDLY_NAME,
ATTR_UNIT_OF_MEASUREMENT)
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
# The domain of your component. Should be equal to the name of your component
DOMAIN = "tellstick_sensor"

78
homeassistant/const.py Normal file
View File

@ -0,0 +1,78 @@
""" Constants used by Home Assistant components. """
# Can be used to specify a catch all when registering state or event listeners.
MATCH_ALL = '*'
# #### CONFIG ####
CONF_LATITUDE = "latitude"
CONF_LONGITUDE = "longitude"
# This one is deprecated. Use platform instead.
CONF_TYPE = "type"
CONF_PLATFORM = "platform"
CONF_HOST = "host"
CONF_HOSTS = "hosts"
CONF_USERNAME = "username"
CONF_PASSWORD = "password"
# #### EVENTS ####
EVENT_HOMEASSISTANT_START = "homeassistant_start"
EVENT_HOMEASSISTANT_STOP = "homeassistant_stop"
EVENT_STATE_CHANGED = "state_changed"
EVENT_TIME_CHANGED = "time_changed"
EVENT_CALL_SERVICE = "services.call"
# #### STATES ####
STATE_ON = 'on'
STATE_OFF = 'off'
STATE_HOME = 'home'
STATE_NOT_HOME = 'not_home'
# #### STATE ATTRIBUTES ####
# Contains current time for a TIME_CHANGED event
ATTR_NOW = "now"
# Contains domain, service for a SERVICE_CALL event
ATTR_DOMAIN = "domain"
ATTR_SERVICE = "service"
# Contains one string or a list of strings, each being an entity id
ATTR_ENTITY_ID = 'entity_id'
# String with a friendly name for the entity
ATTR_FRIENDLY_NAME = "friendly_name"
# A picture to represent entity
ATTR_ENTITY_PICTURE = "entity_picture"
# The unit of measurement if applicable
ATTR_UNIT_OF_MEASUREMENT = "unit_of_measurement"
# #### SERVICES ####
SERVICE_HOMEASSISTANT_STOP = "stop"
SERVICE_TURN_ON = 'turn_on'
SERVICE_TURN_OFF = 'turn_off'
SERVICE_VOLUME_UP = "volume_up"
SERVICE_VOLUME_DOWN = "volume_down"
SERVICE_VOLUME_MUTE = "volume_mute"
SERVICE_MEDIA_PLAY_PAUSE = "media_play_pause"
SERVICE_MEDIA_PLAY = "media_play"
SERVICE_MEDIA_PAUSE = "media_pause"
SERVICE_MEDIA_NEXT_TRACK = "media_next_track"
SERVICE_MEDIA_PREV_TRACK = "media_prev_track"
# #### API / REMOTE ####
SERVER_PORT = 8123
AUTH_HEADER = "HA-access"
URL_API = "/api/"
URL_API_STATES = "/api/states"
URL_API_STATES_ENTITY = "/api/states/{}"
URL_API_EVENTS = "/api/events"
URL_API_EVENTS_EVENT = "/api/events/{}"
URL_API_SERVICES = "/api/services"
URL_API_SERVICES_SERVICE = "/api/services/{}/{}"
URL_API_EVENT_FORWARD = "/api/event_forwarding"

176
homeassistant/helpers.py Normal file
View File

@ -0,0 +1,176 @@
"""
Helper methods for components within Home Assistant.
"""
from homeassistant import NoEntitySpecifiedError
from homeassistant.loader import get_component
from homeassistant.const import (
ATTR_ENTITY_ID, STATE_ON, STATE_OFF, CONF_PLATFORM, CONF_TYPE)
def extract_entity_ids(hass, service):
"""
Helper method to extract a list of entity ids from a service call.
Will convert group entity ids to the entity ids it represents.
"""
entity_ids = []
if service.data and ATTR_ENTITY_ID in service.data:
group = get_component('group')
# Entity ID attr can be a list or a string
service_ent_id = service.data[ATTR_ENTITY_ID]
if isinstance(service_ent_id, list):
ent_ids = service_ent_id
else:
ent_ids = [service_ent_id]
entity_ids.extend(
ent_id for ent_id
in group.expand_entity_ids(hass, ent_ids)
if ent_id not in entity_ids)
return entity_ids
def validate_config(config, items, logger):
"""
Validates if all items are available in the configuration.
config is the general dictionary with all the configurations.
items is a dict with per domain which attributes we require.
logger is the logger from the caller to log the errors to.
Returns True if all required items were found.
"""
errors_found = False
for domain in items.keys():
config.setdefault(domain, {})
errors = [item for item in items[domain] if item not in config[domain]]
if errors:
logger.error(
"Missing required configuration items in {}: {}".format(
domain, ", ".join(errors)))
errors_found = True
return not errors_found
def config_per_platform(config, domain, logger):
"""
Generator to break a component config into different platforms.
For example, will find 'switch', 'switch 2', 'switch 3', .. etc
"""
config_key = domain
found = 1
while config_key in config:
platform_config = config[config_key]
platform_type = platform_config.get(CONF_PLATFORM)
# DEPRECATED, still supported for now.
if platform_type is None:
platform_type = platform_config.get(CONF_TYPE)
if platform_type is not None:
logger.warning((
'Please update your config for {}.{} to use "platform" '
'instead of "type"').format(domain, platform_type))
if platform_type is None:
logger.warning('No platform specified for %s', config_key)
break
yield platform_type, platform_config
found += 1
config_key = "{} {}".format(domain, found)
def platform_devices_from_config(config, domain, hass, logger):
""" Parses the config for specified domain.
Loads different platforms and retrieve domains. """
devices = []
for p_type, p_config in config_per_platform(config, domain, logger):
platform = get_component('{}.{}'.format(domain, p_type))
if platform is None:
logger.error("Unknown %s type specified: %s", domain, p_type)
else:
try:
p_devices = platform.get_devices(hass, p_config)
except AttributeError:
# DEPRECATED, still supported for now
logger.warning(
'Platform %s should migrate to use the method get_devices',
p_type)
if domain == 'light':
p_devices = platform.get_lights(hass, p_config)
elif domain == 'switch':
p_devices = platform.get_switches(hass, p_config)
else:
raise
logger.info("Found %d %s %ss", len(p_devices), p_type, domain)
devices.extend(p_devices)
if len(devices) == 0:
logger.error("No devices found for %s", domain)
return devices
class ToggleDevice(object):
""" ABC for devices that can be turned on and off. """
# pylint: disable=no-self-use
entity_id = None
def get_name(self):
""" Returns the name of the device if any. """
return None
def turn_on(self, **kwargs):
""" Turn the device on. """
pass
def turn_off(self, **kwargs):
""" Turn the device off. """
pass
def is_on(self):
""" True if device is on. """
return False
def get_state_attributes(self):
""" Returns optional state attributes. """
return {}
def update(self):
""" Retrieve latest state from the real device. """
pass
def update_ha_state(self, hass, force_refresh=False):
"""
Updates Home Assistant with current state of device.
If force_refresh == True will update device before setting state.
"""
if self.entity_id is None:
raise NoEntitySpecifiedError(
"No entity specified for device {}".format(self.get_name()))
if force_refresh:
self.update()
state = STATE_ON if self.is_on() else STATE_OFF
return hass.states.set(self.entity_id, state,
self.get_state_attributes())

View File

@ -19,18 +19,10 @@ import requests
import homeassistant as ha
SERVER_PORT = 8123
AUTH_HEADER = "HA-access"
URL_API = "/api/"
URL_API_STATES = "/api/states"
URL_API_STATES_ENTITY = "/api/states/{}"
URL_API_EVENTS = "/api/events"
URL_API_EVENTS_EVENT = "/api/events/{}"
URL_API_SERVICES = "/api/services"
URL_API_SERVICES_SERVICE = "/api/services/{}/{}"
URL_API_EVENT_FORWARD = "/api/event_forwarding"
from homeassistant.const import (
SERVER_PORT, AUTH_HEADER, URL_API, URL_API_STATES, URL_API_STATES_ENTITY,
URL_API_EVENTS, URL_API_EVENTS_EVENT, URL_API_SERVICES,
URL_API_SERVICES_SERVICE, URL_API_EVENT_FORWARD)
METHOD_GET = "get"
METHOD_POST = "post"

View File

@ -127,6 +127,7 @@ def ensure_unique_string(preferred_string, current_strings):
""" Returns a string that is not present in current_strings.
If preferred string exists will append _2, _3, .. """
string = preferred_string
current_strings = list(current_strings)
tries = 1
@ -248,32 +249,6 @@ class OrderedSet(collections.MutableSet):
return set(self) == set(other)
def validate_config(config, items, logger):
"""
Validates if all items are available in the configuration.
config is the general dictionary with all the configurations.
items is a dict with per domain which attributes we require.
logger is the logger from the caller to log the errors to.
Returns True if all required items were found.
"""
errors_found = False
for domain in items.keys():
config.setdefault(domain, {})
errors = [item for item in items[domain] if item not in config[domain]]
if errors:
logger.error(
"Missing required configuration items in {}: {}".format(
domain, ", ".join(errors)))
errors_found = True
return not errors_found
class Throttle(object):
"""
A method decorator to add a cooldown to a method to prevent it from being