From de6c5a503be125606170f74156acad1443c4e7bf Mon Sep 17 00:00:00 2001 From: iandday <iandday@gmail.com> Date: Thu, 1 Dec 2016 15:48:08 -0500 Subject: [PATCH] Remote Component and Harmony Platform (#4254) * Initial Harmony device support, working current activity sensor and switch for each activity TODO: add new device per hub to send device specific activity Changes to be committed: new file: homeassistant/components/harmony.py new file: homeassistant/components/sensor/harmony.py new file: homeassistant/components/switch/harmony.py * ready for beta, I think Changes to be committed: modified: homeassistant/components/harmony.py modified: homeassistant/components/sensor/harmony.py modified: homeassistant/components/switch/harmony.py * Changes to be committed: modified: homeassistant/components/harmony.py new file: homeassistant/components/remote/__init__.py new file: homeassistant/components/remote/harmony.py new file: homeassistant/components/remote/services.yaml modified: homeassistant/components/sensor/harmony.py modified: homeassistant/components/switch/harmony.py Implemented remote component and harmony platform * streamlined harmony support * typo * Initial Harmony device support, working current activity sensor and switch for each activity TODO: add new device per hub to send device specific activity Changes to be committed: new file: homeassistant/components/harmony.py new file: homeassistant/components/sensor/harmony.py new file: homeassistant/components/switch/harmony.py * ready for beta, I think Changes to be committed: modified: homeassistant/components/harmony.py modified: homeassistant/components/sensor/harmony.py modified: homeassistant/components/switch/harmony.py * Changes to be committed: modified: homeassistant/components/harmony.py new file: homeassistant/components/remote/__init__.py new file: homeassistant/components/remote/harmony.py new file: homeassistant/components/remote/services.yaml modified: homeassistant/components/sensor/harmony.py modified: homeassistant/components/switch/harmony.py Implemented remote component and harmony platform * streamlined harmony support * typo * reworked token generation * delete * Initial Harmony device support, working current activity sensor and switch for each activity TODO: add new device per hub to send device specific activity Changes to be committed: new file: homeassistant/components/harmony.py new file: homeassistant/components/sensor/harmony.py new file: homeassistant/components/switch/harmony.py * Initial Harmony device support, working current activity sensor and switch for each activity TODO: add new device per hub to send device specific activity Changes to be committed: new file: homeassistant/components/harmony.py new file: homeassistant/components/sensor/harmony.py new file: homeassistant/components/switch/harmony.py * ready for beta, I think Changes to be committed: modified: homeassistant/components/harmony.py modified: homeassistant/components/sensor/harmony.py modified: homeassistant/components/switch/harmony.py * ready for beta, I think Changes to be committed: modified: homeassistant/components/harmony.py modified: homeassistant/components/sensor/harmony.py modified: homeassistant/components/switch/harmony.py * Changes to be committed: modified: homeassistant/components/harmony.py new file: homeassistant/components/remote/__init__.py new file: homeassistant/components/remote/harmony.py new file: homeassistant/components/remote/services.yaml modified: homeassistant/components/sensor/harmony.py modified: homeassistant/components/switch/harmony.py Implemented remote component and harmony platform * streamlined harmony support * typo * reworked token generation * delete * readded after rebase * cleaning up style errors * modified .coveragerc * moved import statements * added more debug logging * Added URL encoding of token received from Logitech * Corrected import for python 3 * new pyharmony version * new pyharmony version * remote tests * only write config file if not present or sync service is called * more tests * more tests * bumped pyharmony version to work with new auth * bumped pyharmony version to work with new auth * style corrections * harmony local auth and remote demo platform * style fix * PR refinements and permission issues * forgot a blank line * removed sync test from test_init * removed sync test from test_init * visual indent * send_command test in demo platform --- .coveragerc | 1 + homeassistant/components/remote/__init__.py | 142 +++++++++++++ homeassistant/components/remote/demo.py | 57 +++++ homeassistant/components/remote/harmony.py | 198 ++++++++++++++++++ homeassistant/components/remote/services.yaml | 42 ++++ requirements_all.txt | 3 + tests/components/remote/__init__.py | 1 + tests/components/remote/test_demo.py | 117 +++++++++++ tests/components/remote/test_init.py | 99 +++++++++ 9 files changed, 660 insertions(+) create mode 100755 homeassistant/components/remote/__init__.py create mode 100644 homeassistant/components/remote/demo.py create mode 100755 homeassistant/components/remote/harmony.py create mode 100644 homeassistant/components/remote/services.yaml create mode 100755 tests/components/remote/__init__.py create mode 100755 tests/components/remote/test_demo.py create mode 100755 tests/components/remote/test_init.py diff --git a/.coveragerc b/.coveragerc index 37d412b63d4..d0967918a60 100644 --- a/.coveragerc +++ b/.coveragerc @@ -241,6 +241,7 @@ omit = homeassistant/components/notify/xmpp.py homeassistant/components/nuimo_controller.py homeassistant/components/openalpr.py + homeassistant/components/remote/harmony.py homeassistant/components/scene/hunterdouglas_powerview.py homeassistant/components/sensor/arest.py homeassistant/components/sensor/arwn.py diff --git a/homeassistant/components/remote/__init__.py b/homeassistant/components/remote/__init__.py new file mode 100755 index 00000000000..57d816fd0c9 --- /dev/null +++ b/homeassistant/components/remote/__init__.py @@ -0,0 +1,142 @@ +""" +Component to interface with universal remote control devices. + +For more details about this component, please refer to the documentation +at https://home-assistant.io/components/remote/ +""" +from datetime import timedelta +import logging +import os + +import voluptuous as vol +from homeassistant.config import load_yaml_config_file +from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.helpers.entity import ToggleEntity +import homeassistant.helpers.config_validation as cv +from homeassistant.const import ( + STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID) +from homeassistant.components import group +from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa + +ATTR_DEVICE = 'device' +ATTR_COMMAND = 'command' +ATTR_ACTIVITY = 'activity' +SERVICE_SEND_COMMAND = 'send_command' +SERVICE_SYNC = 'sync' + +DOMAIN = 'remote' +SCAN_INTERVAL = 30 + +GROUP_NAME_ALL_REMOTES = 'all remotes' +ENTITY_ID_ALL_REMOTES = group.ENTITY_ID_FORMAT.format('all_remotes') + +ENTITY_ID_FORMAT = DOMAIN + '.{}' + +MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) + +REMOTE_SERVICE_SCHEMA = vol.Schema({ + vol.Required(ATTR_ENTITY_ID): cv.entity_ids, +}) + +REMOTE_SERVICE_TURN_ON_SCHEMA = REMOTE_SERVICE_SCHEMA.extend({ + vol.Optional(ATTR_ACTIVITY): cv.string +}) + +REMOTE_SERVICE_SEND_COMMAND_SCHEMA = REMOTE_SERVICE_SCHEMA.extend({ + vol.Required(ATTR_DEVICE): cv.string, + vol.Required(ATTR_COMMAND): cv.string, +}) + +_LOGGER = logging.getLogger(__name__) + + +def is_on(hass, entity_id=None): + """Return if the remote is on based on the statemachine.""" + entity_id = entity_id or ENTITY_ID_ALL_REMOTES + return hass.states.is_state(entity_id, STATE_ON) + + +def turn_on(hass, activity=None, entity_id=None): + """Turn all or specified remote on.""" + data = {ATTR_ACTIVITY: activity} + if entity_id: + data[ATTR_ENTITY_ID] = entity_id + hass.services.call(DOMAIN, SERVICE_TURN_ON, data) + + +def turn_off(hass, entity_id=None): + """Turn all or specified remote off.""" + data = {ATTR_ENTITY_ID: entity_id} if entity_id else None + hass.services.call(DOMAIN, SERVICE_TURN_OFF, data) + + +def send_command(hass, device, command, entity_id=None): + """Send a command to a device.""" + data = {ATTR_DEVICE: str(device), ATTR_COMMAND: command} + if entity_id: + data[ATTR_ENTITY_ID] = entity_id + hass.services.call(DOMAIN, SERVICE_SEND_COMMAND, data) + + +def sync(hass, entity_id=None): + """Sync remote device.""" + data = {ATTR_ENTITY_ID: entity_id} if entity_id else None + hass.services.call(DOMAIN, SERVICE_SYNC, data) + + +def setup(hass, config): + """Track states and offer events for remotes.""" + component = EntityComponent( + _LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_REMOTES) + component.setup(config) + + def handle_remote_service(service): + """Handle calls to the remote services.""" + target_remotes = component.extract_from_service(service) + + activity_id = service.data.get(ATTR_ACTIVITY) + device = service.data.get(ATTR_DEVICE) + command = service.data.get(ATTR_COMMAND) + + for remote in target_remotes: + if service.service == SERVICE_TURN_ON: + remote.turn_on(activity=activity_id) + elif service.service == SERVICE_SEND_COMMAND: + remote.send_command(device=device, command=command) + elif service.service == SERVICE_SYNC: + remote.sync() + else: + remote.turn_off() + + if remote.should_poll: + remote.update_ha_state(True) + + descriptions = load_yaml_config_file( + os.path.join(os.path.dirname(__file__), 'services.yaml')) + hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_remote_service, + descriptions.get(SERVICE_TURN_OFF), + schema=REMOTE_SERVICE_SCHEMA) + hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_remote_service, + descriptions.get(SERVICE_TURN_ON), + schema=REMOTE_SERVICE_TURN_ON_SCHEMA) + hass.services.register(DOMAIN, SERVICE_SEND_COMMAND, handle_remote_service, + descriptions.get(SERVICE_SEND_COMMAND), + schema=REMOTE_SERVICE_SEND_COMMAND_SCHEMA) + + return True + + +class RemoteDevice(ToggleEntity): + """Representation of a remote.""" + + def turn_on(self, **kwargs): + """Turn a device on with the remote.""" + raise NotImplementedError() + + def turn_off(self, **kwargs): + """Turn a device off with the remote.""" + raise NotImplementedError() + + def send_command(self, **kwargs): + """Send a command to a device.""" + raise NotImplementedError() diff --git a/homeassistant/components/remote/demo.py b/homeassistant/components/remote/demo.py new file mode 100644 index 00000000000..90c691a3d3c --- /dev/null +++ b/homeassistant/components/remote/demo.py @@ -0,0 +1,57 @@ +""" +Demo platform that has two fake remotes. + +For more details about this platform, please refer to the documentation +https://home-assistant.io/components/demo/ +""" +from homeassistant.components.remote import RemoteDevice +from homeassistant.const import DEVICE_DEFAULT_NAME + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """Setup the demo remotes.""" + add_devices_callback([ + DemoRemote('Remote One', False, None), + DemoRemote('Remote Two', True, 'mdi:remote'), + ]) + + +class DemoRemote(RemoteDevice): + """Representation of a demo remote.""" + + def __init__(self, name, state, icon): + """Initialize the Demo Remote.""" + self._name = name or DEVICE_DEFAULT_NAME + self._state = state + self._icon = icon + + @property + def should_poll(self): + """No polling needed for a demo remote.""" + return False + + @property + def name(self): + """Return the name of the device if any.""" + return self._name + + @property + def icon(self): + """Return the icon to use for device if any.""" + return self._icon + + @property + def is_on(self): + """Return true if remote is on.""" + return self._state + + def turn_on(self, **kwargs): + """Turn the remote on.""" + self._state = True + self.schedule_update_ha_state() + + def turn_off(self, **kwargs): + """Turn the remote off.""" + self._state = False + self.schedule_update_ha_state() diff --git a/homeassistant/components/remote/harmony.py b/homeassistant/components/remote/harmony.py new file mode 100755 index 00000000000..9e799fab066 --- /dev/null +++ b/homeassistant/components/remote/harmony.py @@ -0,0 +1,198 @@ +""" +Support for Harmony Hub devices. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/remote.harmony/ + +""" + +import logging +from os import path +import urllib.parse +from homeassistant.const import ( + CONF_NAME, CONF_HOST, CONF_PORT, ATTR_ENTITY_ID) +from homeassistant.components.remote import PLATFORM_SCHEMA, DOMAIN +from homeassistant.util import slugify +from homeassistant.config import load_yaml_config_file +import homeassistant.components.remote as remote +import homeassistant.helpers.config_validation as cv +import voluptuous as vol + + +REQUIREMENTS = ['pyharmony==1.0.12'] +_LOGGER = logging.getLogger(__name__) + +ATTR_DEVICE = 'device' +ATTR_COMMAND = 'command' +ATTR_ACTIVITY = 'activity' + +SERVICE_SYNC = 'harmony_sync' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PORT): cv.string, + vol.Required(ATTR_ACTIVITY, default=None): cv.string, +}) + +HARMONY_SYNC_SCHEMA = vol.Schema({ + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, +}) + +# List of devices that have been registered +DEVICES = [] + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup Harmony platform.""" + import pyharmony + global DEVICES + + name = config.get(CONF_NAME) + host = config.get(CONF_HOST) + port = config.get(CONF_PORT) + _LOGGER.info('Loading Harmony platform: ' + name) + + harmony_conf_file = hass.config.path('harmony_' + slugify(name) + '.conf') + + try: + _LOGGER.debug('calling pyharmony.ha_get_token for remote at: ' + + host + ':' + port) + token = urllib.parse.quote_plus(pyharmony.ha_get_token(host, port)) + except ValueError as err: + _LOGGER.critical(err.args[0] + ' for remote: ' + name) + return False + + _LOGGER.debug('received token: ' + token) + DEVICES = [HarmonyRemote(config.get(CONF_NAME), + config.get(CONF_HOST), + config.get(CONF_PORT), + config.get(ATTR_ACTIVITY), + harmony_conf_file, + token)] + add_devices(DEVICES, True) + register_services(hass) + return True + + +def register_services(hass): + """Register all services for harmony devices.""" + descriptions = load_yaml_config_file( + path.join(path.dirname(__file__), 'services.yaml')) + + hass.services.register(DOMAIN, SERVICE_SYNC, + _sync_service, + descriptions.get(SERVICE_SYNC), + schema=HARMONY_SYNC_SCHEMA) + + +def _apply_service(service, service_func, *service_func_args): + """Internal func for applying a service.""" + entity_ids = service.data.get('entity_id') + + if entity_ids: + _devices = [device for device in DEVICES + if device.entity_id in entity_ids] + else: + _devices = DEVICES + + for device in _devices: + service_func(device, *service_func_args) + device.update_ha_state(True) + + +def _sync_service(service): + _apply_service(service, HarmonyRemote.sync) + + +class HarmonyRemote(remote.RemoteDevice): + """Remote representation used to control a Harmony device.""" + + def __init__(self, name, host, port, activity, out_path, token): + """Initialize HarmonyRemote class.""" + import pyharmony + from pathlib import Path + + _LOGGER.debug('HarmonyRemote device init started for: ' + name) + self._name = name + self._ip = host + self._port = port + self._state = None + self._current_activity = None + self._default_activity = activity + self._token = token + self._config_path = out_path + _LOGGER.debug('retrieving harmony config using token: ' + token) + self._config = pyharmony.ha_get_config(self._token, host, port) + if not Path(self._config_path).is_file(): + _LOGGER.debug('writing harmony configuration to file: ' + out_path) + pyharmony.ha_write_config_file(self._config, self._config_path) + + @property + def name(self): + """Return the Harmony device's name.""" + return self._name + + @property + def device_state_attributes(self): + """Add platform specific attributes.""" + return {'current_activity': self._current_activity} + + @property + def is_on(self): + """Return False if PowerOff is the current activity, otherwise True.""" + return self._current_activity != 'PowerOff' + + def update(self): + """Return current activity.""" + import pyharmony + name = self._name + _LOGGER.debug('polling ' + name + ' for current activity') + state = pyharmony.ha_get_current_activity(self._token, + self._config, + self._ip, + self._port) + _LOGGER.debug(name + '\'s current activity reported as: ' + state) + self._current_activity = state + self._state = bool(state != 'PowerOff') + + def turn_on(self, **kwargs): + """Start an activity from the Harmony device.""" + import pyharmony + if kwargs[ATTR_ACTIVITY]: + activity = kwargs[ATTR_ACTIVITY] + else: + activity = self._default_activity + + if activity: + pyharmony.ha_start_activity(self._token, + self._ip, + self._port, + self._config, + activity) + self._state = True + else: + _LOGGER.error('No activity specified with turn_on service') + + def turn_off(self): + """Start the PowerOff activity.""" + import pyharmony + pyharmony.ha_power_off(self._token, self._ip, self._port) + + def send_command(self, **kwargs): + """Send a command to one device.""" + import pyharmony + pyharmony.ha_send_command(self._token, self._ip, + self._port, kwargs[ATTR_DEVICE], + kwargs[ATTR_COMMAND]) + + def sync(self): + """Sync the Harmony device with the web service.""" + import pyharmony + _LOGGER.debug('syncing hub with Harmony servers') + pyharmony.ha_sync(self._token, self._ip, self._port) + self._config = pyharmony.ha_get_config(self._token, + self._ip, + self._port) + _LOGGER.debug('writing hub config to file: ' + self._config_path) + pyharmony.ha_write_config_file(self._config, self._config_path) diff --git a/homeassistant/components/remote/services.yaml b/homeassistant/components/remote/services.yaml new file mode 100644 index 00000000000..2023588fcc2 --- /dev/null +++ b/homeassistant/components/remote/services.yaml @@ -0,0 +1,42 @@ +# Describes the format for available remote services + +turn_on: + description: Semds the Power On Command + + fields: + entity_id: + description: Name(s) of entities to turn on + example: 'remote.family_room' + activity: + description: Activity ID or Activity Name to start + example: 'BedroomTV' + +turn_off: + description: Sends the Power Off Command + + fields: + entity_id: + description: Name(s) of entities to turn off + example: 'remote.family_room' + +send_command: + description: Semds a single command to a single device + + fields: + entity_id: + description: Name(s) of entities to send command from + example: 'remote.family_room' + device: + description: Device ID to send command to + example: '32756745' + command: + description: Command to send + example: 'Play' + +harmony_sync: + description: Syncs the remote's configuration + + fields: + entity_id: + description: Name(s) of entities to sync + example: 'remote.family_room' diff --git a/requirements_all.txt b/requirements_all.txt index 8da898fb3d0..0d3709509fb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -392,6 +392,9 @@ pyenvisalink==1.9 # homeassistant.components.ifttt pyfttt==0.3 +# homeassistant.components.remote.harmony +pyharmony==1.0.12 + # homeassistant.components.homematic pyhomematic==0.1.18 diff --git a/tests/components/remote/__init__.py b/tests/components/remote/__init__.py new file mode 100755 index 00000000000..77870a11f20 --- /dev/null +++ b/tests/components/remote/__init__.py @@ -0,0 +1 @@ +"""The tests for Remote platforms.""" diff --git a/tests/components/remote/test_demo.py b/tests/components/remote/test_demo.py new file mode 100755 index 00000000000..f43f9e8610c --- /dev/null +++ b/tests/components/remote/test_demo.py @@ -0,0 +1,117 @@ +"""The tests for the demo remote component.""" +# pylint: disable=protected-access +import unittest + +from homeassistant.bootstrap import setup_component +import homeassistant.components.remote as remote +from homeassistant.const import ( + ATTR_ENTITY_ID, STATE_ON, STATE_OFF, CONF_PLATFORM, + SERVICE_TURN_ON, SERVICE_TURN_OFF) +from tests.common import get_test_home_assistant, mock_service + +SERVICE_SYNC = 'sync' +SERVICE_SEND_COMMAND = 'send_command' + + +class TestDemoRemote(unittest.TestCase): + """Test the demo remote.""" + + # pylint: disable=invalid-name + def setUp(self): + """Setup things to be run when tests are started.""" + self.hass = get_test_home_assistant() + self.assertTrue(setup_component(self.hass, remote.DOMAIN, {'remote': { + 'platform': 'demo', + }})) + + # pylint: disable=invalid-name + def tearDown(self): + """Stop down everything that was started.""" + self.hass.stop() + + def test_methods(self): + """Test if methods call the services as expected.""" + + self.assertTrue( + setup_component(self.hass, remote.DOMAIN, + {remote.DOMAIN: {CONF_PLATFORM: 'demo'}})) + + # Test is_on + self.hass.states.set('remote.demo', STATE_ON) + self.assertTrue(remote.is_on(self.hass, 'remote.demo')) + + self.hass.states.set('remote.demo', STATE_OFF) + self.assertFalse(remote.is_on(self.hass, 'remote.demo')) + + self.hass.states.set(remote.ENTITY_ID_ALL_REMOTES, STATE_ON) + self.assertTrue(remote.is_on(self.hass)) + + self.hass.states.set(remote.ENTITY_ID_ALL_REMOTES, STATE_OFF) + self.assertFalse(remote.is_on(self.hass)) + + def test_services(self): + """Test the provided services.""" + + # Test turn_on + turn_on_calls = mock_service( + self.hass, remote.DOMAIN, SERVICE_TURN_ON) + + remote.turn_on( + self.hass, + entity_id='entity_id_val') + + self.hass.block_till_done() + + self.assertEqual(1, len(turn_on_calls)) + call = turn_on_calls[-1] + + self.assertEqual(remote.DOMAIN, call.domain) + + # Test turn_off + turn_off_calls = mock_service( + self.hass, remote.DOMAIN, SERVICE_TURN_OFF) + + remote.turn_off( + self.hass, entity_id='entity_id_val') + + self.hass.block_till_done() + + self.assertEqual(1, len(turn_off_calls)) + call = turn_off_calls[-1] + + self.assertEqual(remote.DOMAIN, call.domain) + self.assertEqual(SERVICE_TURN_OFF, call.service) + self.assertEqual('entity_id_val', call.data[ATTR_ENTITY_ID]) + + # Test sync + sync_calls = mock_service( + self.hass, remote.DOMAIN, SERVICE_SYNC) + + remote.sync( + self.hass, entity_id='entity_id_val') + + self.hass.block_till_done() + + self.assertEqual(1, len(sync_calls)) + call = sync_calls[-1] + + self.assertEqual(remote.DOMAIN, call.domain) + self.assertEqual(SERVICE_SYNC, call.service) + self.assertEqual('entity_id_val', call.data[ATTR_ENTITY_ID]) + + # Test send_command + send_command_calls = mock_service( + self.hass, remote.DOMAIN, SERVICE_SEND_COMMAND) + + remote.send_command( + self.hass, entity_id='entity_id_val', + device='test_device', command='test_command') + + self.hass.block_till_done() + + self.assertEqual(1, len(send_command_calls)) + call = send_command_calls[-1] + + self.assertEqual(remote.DOMAIN, call.domain) + self.assertEqual(SERVICE_SEND_COMMAND, call.service) + self.assertEqual('entity_id_val', call.data[ATTR_ENTITY_ID]) diff --git a/tests/components/remote/test_init.py b/tests/components/remote/test_init.py new file mode 100755 index 00000000000..799ed3b5ea7 --- /dev/null +++ b/tests/components/remote/test_init.py @@ -0,0 +1,99 @@ +"""The tests for the Remote component, adapted from Light Test.""" +# pylint: disable=protected-access + +import unittest + +from homeassistant.bootstrap import setup_component +from homeassistant.const import ( + ATTR_ENTITY_ID, STATE_ON, STATE_OFF, CONF_PLATFORM, + SERVICE_TURN_ON, SERVICE_TURN_OFF) +import homeassistant.components.remote as remote + +from tests.common import mock_service, get_test_home_assistant +TEST_PLATFORM = {remote.DOMAIN: {CONF_PLATFORM: 'test'}} +SERVICE_SYNC = 'sync' +SERVICE_SEND_COMMAND = 'send_command' + + +class TestRemote(unittest.TestCase): + """Test the remote module.""" + + # pylint: disable=invalid-name + def setUp(self): + """Setup things to be run when tests are started.""" + self.hass = get_test_home_assistant() + + # pylint: disable=invalid-name + def tearDown(self): + """Stop everything that was started.""" + self.hass.stop() + + def test_is_on(self): + """ Test is_on""" + self.hass.states.set('remote.test', STATE_ON) + self.assertTrue(remote.is_on(self.hass, 'remote.test')) + + self.hass.states.set('remote.test', STATE_OFF) + self.assertFalse(remote.is_on(self.hass, 'remote.test')) + + self.hass.states.set(remote.ENTITY_ID_ALL_REMOTES, STATE_ON) + self.assertTrue(remote.is_on(self.hass)) + + self.hass.states.set(remote.ENTITY_ID_ALL_REMOTES, STATE_OFF) + self.assertFalse(remote.is_on(self.hass)) + + def test_turn_on(self): + """ Test turn_on""" + turn_on_calls = mock_service( + self.hass, remote.DOMAIN, SERVICE_TURN_ON) + + remote.turn_on( + self.hass, + entity_id='entity_id_val') + + self.hass.block_till_done() + + self.assertEqual(1, len(turn_on_calls)) + call = turn_on_calls[-1] + + self.assertEqual(remote.DOMAIN, call.domain) + + def test_turn_off(self): + """ Test turn_off""" + turn_off_calls = mock_service( + self.hass, remote.DOMAIN, SERVICE_TURN_OFF) + + remote.turn_off( + self.hass, entity_id='entity_id_val') + + self.hass.block_till_done() + + self.assertEqual(1, len(turn_off_calls)) + call = turn_off_calls[-1] + + self.assertEqual(remote.DOMAIN, call.domain) + self.assertEqual(SERVICE_TURN_OFF, call.service) + self.assertEqual('entity_id_val', call.data[ATTR_ENTITY_ID]) + + def test_send_command(self): + """ Test send_command""" + send_command_calls = mock_service( + self.hass, remote.DOMAIN, SERVICE_SEND_COMMAND) + + remote.send_command( + self.hass, entity_id='entity_id_val', + device='test_device', command='test_command') + + self.hass.block_till_done() + + self.assertEqual(1, len(send_command_calls)) + call = send_command_calls[-1] + + self.assertEqual(remote.DOMAIN, call.domain) + self.assertEqual(SERVICE_SEND_COMMAND, call.service) + self.assertEqual('entity_id_val', call.data[ATTR_ENTITY_ID]) + + def test_services(self): + """Test the provided services.""" + self.assertTrue(setup_component(self.hass, remote.DOMAIN, + TEST_PLATFORM)) -- GitLab