diff --git a/README.md b/README.md index 523ce94582c688..08564802e77a34 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/config/home-assistant.conf.example b/config/home-assistant.conf.example index 9eb0cf4a90ea07..5a919ac38b06ff 100644 --- a/config/home-assistant.conf.example +++ b/config/home-assistant.conf.example @@ -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 diff --git a/ha_test/config/custom_components/light/test.py b/ha_test/config/custom_components/light/test.py index 757099ddca044e..0ed04a21717a04 100644 --- a/ha_test/config/custom_components/light/test.py +++ b/ha_test/config/custom_components/light/test.py @@ -6,8 +6,8 @@ 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) ] diff --git a/ha_test/config/custom_components/switch/test.py b/ha_test/config/custom_components/switch/test.py index 927aca24feb641..682c27f695f428 100644 --- a/ha_test/config/custom_components/switch/test.py +++ b/ha_test/config/custom_components/switch/test.py @@ -6,8 +6,8 @@ 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) ] diff --git a/ha_test/helper.py b/ha_test/helpers.py similarity index 88% rename from ha_test/helper.py rename to ha_test/helpers.py index 109b4185ba59e0..f04dac72553c72 100644 --- a/ha_test/helper.py +++ b/ha_test/helpers.py @@ -7,7 +7,8 @@ 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 @@ def __init__(self, domain, dependencies=[], setup=None): 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 @@ def get_name(self): 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: diff --git a/ha_test/test_component_chromecast.py b/ha_test/test_component_chromecast.py index 3b80c700606b1d..3d9733648c9a5c 100644 --- a/ha_test/test_component_chromecast.py +++ b/ha_test/test_component_chromecast.py @@ -9,9 +9,13 @@ 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 @@ def test_services(self): 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 @@ def test_services(self): 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 @@ def test_setup(self): 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'}})) diff --git a/ha_test/test_component_core.py b/ha_test/test_component_core.py index 2a4a942a5ba052..927bfb98a7c293 100644 --- a/ha_test/test_component_core.py +++ b/ha_test/test_component_core.py @@ -9,6 +9,8 @@ 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 @@ def setUp(self): # pylint: disable=invalid-name 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 @@ def test_turn_on(self): """ 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 @@ def test_turn_off(self): """ 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)) diff --git a/ha_test/test_component_demo.py b/ha_test/test_component_demo.py index a510759a8ea2a2..72be2d12525282 100644 --- a/ha_test/test_component_demo.py +++ b/ha_test/test_component_demo.py @@ -9,7 +9,7 @@ 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) diff --git a/ha_test/test_component_device_scanner.py b/ha_test/test_component_device_scanner.py index 26a7ddb5590c5a..09900951eebbd9 100644 --- a/ha_test/test_component_device_scanner.py +++ b/ha_test/test_component_device_scanner.py @@ -12,11 +12,11 @@ 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 @@ def test_setup(self): # 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 @@ def test_setup(self): 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 @@ def test_device_tracker(self): 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. diff --git a/ha_test/test_component_group.py b/ha_test/test_component_group.py index d1d9dccbb27fed..d83596cee9db5a 100644 --- a/ha_test/test_component_group.py +++ b/ha_test/test_component_group.py @@ -9,7 +9,7 @@ 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 @@ def setUp(self): # pylint: disable=invalid-name """ 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 @@ def test_setup_and_monitor_group(self): 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 @@ def test_setup_and_monitor_group(self): 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 @@ def test__get_group_type(self): 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 @@ def test_setup(self): 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]) diff --git a/ha_test/test_component_light.py b/ha_test/test_component_light.py index 3d4b5c1f3eba33..4d781c70132169 100644 --- a/ha_test/test_component_light.py +++ b/ha_test/test_component_light.py @@ -11,12 +11,12 @@ 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 @@ def test_methods(self): 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 @@ def test_setup(self): # 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 @@ def test_light_profiles(self): 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 @@ def test_light_profiles(self): 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) diff --git a/ha_test/test_component_sun.py b/ha_test/test_component_sun.py index a83f8de51a7a27..daf8970f406b96 100644 --- a/ha_test/test_component_sun.py +++ b/ha_test/test_component_sun.py @@ -11,6 +11,7 @@ import ephem import homeassistant as ha +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE import homeassistant.components.sun as sun @@ -37,8 +38,8 @@ def test_setting_rising(self): 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 @@ def test_state_change(self): 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 @@ def test_setup(self): 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' }})) diff --git a/ha_test/test_component_switch.py b/ha_test/test_component_switch.py index 0df05f0617ba24..daab9cde4d1ac6 100644 --- a/ha_test/test_component_switch.py +++ b/ha_test/test_component_switch.py @@ -9,10 +9,10 @@ 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 @@ def setUp(self): # pylint: disable=invalid-name 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 @@ def test_setup(self): # 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'}, + } )) diff --git a/ha_test/test_helpers.py b/ha_test/test_helpers.py new file mode 100644 index 00000000000000..f61204c837f81f --- /dev/null +++ b/ha_test/test_helpers.py @@ -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)) diff --git a/ha_test/test_loader.py b/ha_test/test_loader.py index 4d9bc190145a72..b7ae75c0e2a6f6 100644 --- a/ha_test/test_loader.py +++ b/ha_test/test_loader.py @@ -10,7 +10,7 @@ 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): diff --git a/homeassistant/__init__.py b/homeassistant/__init__.py index 6fbe1a2c3e3d25..74f27b9f6f5a42 100644 --- a/homeassistant/__init__.py +++ b/homeassistant/__init__.py @@ -15,32 +15,14 @@ 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 diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 3875c23b4ce0c5..7675d3f12f32ed 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -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): diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py index 3acaf11a4fd035..0680b5c67af7c4 100644 --- a/homeassistant/components/__init__.py +++ b/homeassistant/components/__init__.py @@ -19,36 +19,10 @@ 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. """ diff --git a/homeassistant/components/chromecast.py b/homeassistant/components/chromecast.py index d6e9971dc0d6f4..fc5f7e73dc3d81 100644 --- a/homeassistant/components/chromecast.py +++ b/homeassistant/components/chromecast.py @@ -6,9 +6,14 @@ """ 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 update_chromecast_state(entity_id, chromecast): 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 update_chromecast_states(time): # pylint: disable=unused-argument 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 play_youtube_video_service(service, video_id): 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", diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo.py index ca04d90e7fc1a5..3bd7d2f1e33706 100644 --- a/homeassistant/components/demo.py +++ b/homeassistant/components/demo.py @@ -8,9 +8,10 @@ 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 mock_turn_off(service): 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) diff --git a/homeassistant/components/device_sun_light_trigger.py b/homeassistant/components/device_sun_light_trigger.py index 5e2b99e64a715c..5136340832fb53 100644 --- a/homeassistant/components/device_sun_light_trigger.py +++ b/homeassistant/components/device_sun_light_trigger.py @@ -8,7 +8,7 @@ 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 check_light_on_dev_state_change(entity, old_state, new_state): # 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 check_light_on_dev_state_change(entity, old_state, new_state): # 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 check_light_on_dev_state_change(entity, old_state, new_state): # 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 diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index 08f1abd9ff12d3..fa96a5b3c2815b 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -10,12 +10,14 @@ 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)) diff --git a/homeassistant/components/device_tracker/luci.py b/homeassistant/components/device_tracker/luci.py index dba41c11c031bf..9ed73f21375aa4 100644 --- a/homeassistant/components/device_tracker/luci.py +++ b/homeassistant/components/device_tracker/luci.py @@ -6,8 +6,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 @@ -19,10 +20,9 @@ # 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\w*) = (?P.*);") @@ -87,7 +87,7 @@ def get_device_name(self, device): 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. """ diff --git a/homeassistant/components/device_tracker/netgear.py b/homeassistant/components/device_tracker/netgear.py index a57e2f28c7034a..0b0f1107b21536 100644 --- a/homeassistant/components/device_tracker/netgear.py +++ b/homeassistant/components/device_tracker/netgear.py @@ -3,8 +3,9 @@ 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 @@ # 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 @@ def get_device_name(self, mac): 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. """ diff --git a/homeassistant/components/device_tracker/tomato.py b/homeassistant/components/device_tracker/tomato.py index c3b7e5dec940c5..81755f42c6608a 100644 --- a/homeassistant/components/device_tracker/tomato.py +++ b/homeassistant/components/device_tracker/tomato.py @@ -7,8 +7,9 @@ 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 @@ # 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 @@ def get_device_name(self, device): 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. """ diff --git a/homeassistant/components/downloader.py b/homeassistant/components/downloader.py index 362f7d43e0488d..6978dbd7fa9cec 100644 --- a/homeassistant/components/downloader.py +++ b/homeassistant/components/downloader.py @@ -9,7 +9,8 @@ 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 do_download(): 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 do_download(): 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: diff --git a/homeassistant/components/group.py b/homeassistant/components/group.py index f0ce5cc0f9434f..59033b02b688c1 100644 --- a/homeassistant/components/group.py +++ b/homeassistant/components/group.py @@ -9,9 +9,8 @@ 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 = [] diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index 02a1bea0272cbf..0149130c0fcc57 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -83,6 +83,10 @@ 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 @@ 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[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[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[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 @@ def _handle_request(self, method): # pylint: disable=too-many-branches "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 @@ def _handle_post_state_entity(self, path_match, data): 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. """ diff --git a/homeassistant/components/keyboard.py b/homeassistant/components/keyboard.py index e2d156d5c7e9ec..2d8c3995a2fb8d 100644 --- a/homeassistant/components/keyboard.py +++ b/homeassistant/components/keyboard.py @@ -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 @@ 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)) diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 957a318ce3c138..55bbdb307f2b12 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -52,12 +52,12 @@ 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] - - 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]) + lights = platform_devices_from_config(config, DOMAIN, hass, _LOGGER) - 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 diff --git a/homeassistant/components/light/hue.py b/homeassistant/components/light/hue.py index c7db591a4f7d65..19f4034eb392bf 100644 --- a/homeassistant/components/light/hue.py +++ b/homeassistant/components/light/hue.py @@ -3,9 +3,9 @@ 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 @@ 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( diff --git a/homeassistant/components/process.py b/homeassistant/components/process.py index 1c0e6b62373e21..5d18557f99865c 100644 --- a/homeassistant/components/process.py +++ b/homeassistant/components/process.py @@ -10,7 +10,7 @@ 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' diff --git a/homeassistant/components/sun.py b/homeassistant/components/sun.py index c6acbc05230ea2..c6bacb45f876af 100644 --- a/homeassistant/components/sun.py +++ b/homeassistant/components/sun.py @@ -8,7 +8,9 @@ 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 update_sun_state(now): 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) diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py index 98da108484e6f7..7f7a70e184207d 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -6,12 +6,12 @@ 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 - - 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]) + switches = platform_devices_from_config(config, DOMAIN, hass, logger) - 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 diff --git a/homeassistant/components/switch/tellstick.py b/homeassistant/components/switch/tellstick.py index b34aea38e67495..54b85b3ecb4f63 100644 --- a/homeassistant/components/switch/tellstick.py +++ b/homeassistant/components/switch/tellstick.py @@ -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 @@ # 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 diff --git a/homeassistant/components/switch/wemo.py b/homeassistant/components/switch/wemo.py index 5eb3d7c14f02bb..b6392ec0f0cf1a 100644 --- a/homeassistant/components/switch/wemo.py +++ b/homeassistant/components/switch/wemo.py @@ -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") diff --git a/homeassistant/components/tellstick_sensor.py b/homeassistant/components/tellstick_sensor.py index 89a32f1218b5ec..e021632095b708 100644 --- a/homeassistant/components/tellstick_sensor.py +++ b/homeassistant/components/tellstick_sensor.py @@ -26,8 +26,7 @@ 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" diff --git a/homeassistant/const.py b/homeassistant/const.py new file mode 100644 index 00000000000000..8930ae5649cbb9 --- /dev/null +++ b/homeassistant/const.py @@ -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" diff --git a/homeassistant/helpers.py b/homeassistant/helpers.py new file mode 100644 index 00000000000000..f673cc5345fdc6 --- /dev/null +++ b/homeassistant/helpers.py @@ -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()) diff --git a/homeassistant/remote.py b/homeassistant/remote.py index bcfe6e68e65f28..869c690be6be97 100644 --- a/homeassistant/remote.py +++ b/homeassistant/remote.py @@ -19,18 +19,10 @@ 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" diff --git a/homeassistant/util.py b/homeassistant/util.py index d6bb9423a42572..4598a230c655ea 100644 --- a/homeassistant/util.py +++ b/homeassistant/util.py @@ -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 @@ def __eq__(self, other): 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