diff --git a/homeassistant/components/binary_sensor/command_line.py b/homeassistant/components/binary_sensor/command_line.py index e589506eac7..f56f9cb7a39 100644 --- a/homeassistant/components/binary_sensor/command_line.py +++ b/homeassistant/components/binary_sensor/command_line.py @@ -7,46 +7,50 @@ https://home-assistant.io/components/binary_sensor.command_line/ import logging from datetime import timedelta -from homeassistant.components.binary_sensor import (BinarySensorDevice, - SENSOR_CLASSES) +import voluptuous as vol + +from homeassistant.components.binary_sensor import ( + BinarySensorDevice, SENSOR_CLASSES_SCHEMA, PLATFORM_SCHEMA) from homeassistant.components.sensor.command_line import CommandSensorData -from homeassistant.const import CONF_VALUE_TEMPLATE +from homeassistant.const import ( + CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, CONF_NAME, CONF_VALUE_TEMPLATE, + CONF_SENSOR_CLASS, CONF_COMMAND) from homeassistant.helpers import template +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = "Binary Command Sensor" -DEFAULT_SENSOR_CLASS = None +DEFAULT_NAME = 'Binary Command Sensor' DEFAULT_PAYLOAD_ON = 'ON' DEFAULT_PAYLOAD_OFF = 'OFF' -# Return cached results if last scan was less then this time ago MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_COMMAND): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, + vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, + vol.Optional(CONF_SENSOR_CLASS): SENSOR_CLASSES_SCHEMA, + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, +}) + # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Command Sensor.""" - if config.get('command') is None: - _LOGGER.error('Missing required variable: "command"') - return False + """Setup the Command line Binary Sensor.""" + name = config.get(CONF_NAME) + command = config.get(CONF_COMMAND) + payload_off = config.get(CONF_PAYLOAD_OFF) + payload_on = config.get(CONF_PAYLOAD_ON) + sensor_class = config.get(CONF_SENSOR_CLASS) + value_template = config.get(CONF_VALUE_TEMPLATE) - sensor_class = config.get('sensor_class') - if sensor_class not in SENSOR_CLASSES: - _LOGGER.warning('Unknown sensor class: %s', sensor_class) - sensor_class = DEFAULT_SENSOR_CLASS - - data = CommandSensorData(config.get('command')) + data = CommandSensorData(command) add_devices([CommandBinarySensor( - hass, - data, - config.get('name', DEFAULT_NAME), - sensor_class, - config.get('payload_on', DEFAULT_PAYLOAD_ON), - config.get('payload_off', DEFAULT_PAYLOAD_OFF), - config.get(CONF_VALUE_TEMPLATE) - )]) + hass, data, name, sensor_class, payload_on, payload_off, + value_template)]) # pylint: disable=too-many-arguments, too-many-instance-attributes diff --git a/homeassistant/components/cover/command_line.py b/homeassistant/components/cover/command_line.py index c2c8050f09f..0a1da9d7a20 100644 --- a/homeassistant/components/cover/command_line.py +++ b/homeassistant/components/cover/command_line.py @@ -7,29 +7,54 @@ https://home-assistant.io/components/cover.command_line/ import logging import subprocess -from homeassistant.components.cover import CoverDevice -from homeassistant.const import CONF_VALUE_TEMPLATE +import voluptuous as vol + +from homeassistant.components.cover import (CoverDevice, PLATFORM_SCHEMA) +from homeassistant.const import ( + CONF_COMMAND_CLOSE, CONF_COMMAND_OPEN, CONF_COMMAND_STATE, + CONF_COMMAND_STOP, CONF_COVERS, CONF_VALUE_TEMPLATE, CONF_FRIENDLY_NAME) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers import template _LOGGER = logging.getLogger(__name__) +COVER_SCHEMA = vol.Schema({ + vol.Optional(CONF_COMMAND_CLOSE, default='true'): cv.string, + vol.Optional(CONF_COMMAND_OPEN, default='true'): cv.string, + vol.Optional(CONF_COMMAND_STATE): cv.string, + vol.Optional(CONF_COMMAND_STOP, default='true'): cv.string, + vol.Optional(CONF_FRIENDLY_NAME): cv.string, + vol.Optional(CONF_VALUE_TEMPLATE, default='{{ value }}'): cv.template, +}) -def setup_platform(hass, config, add_devices_callback, discovery_info=None): +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}), +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): """Setup cover controlled by shell commands.""" - covers = config.get('covers', {}) - devices = [] + devices = config.get(CONF_COVERS, {}) + covers = [] - for dev_name, properties in covers.items(): - devices.append( + for device_name, device_config in devices.items(): + covers.append( CommandCover( hass, - properties.get('name', dev_name), - properties.get('opencmd', 'true'), - properties.get('closecmd', 'true'), - properties.get('stopcmd', 'true'), - properties.get('statecmd', False), - properties.get(CONF_VALUE_TEMPLATE, '{{ value }}'))) - add_devices_callback(devices) + device_config.get(CONF_FRIENDLY_NAME, device_name), + device_config.get(CONF_COMMAND_OPEN), + device_config.get(CONF_COMMAND_CLOSE), + device_config.get(CONF_COMMAND_STOP), + device_config.get(CONF_COMMAND_STATE), + device_config.get(CONF_VALUE_TEMPLATE), + ) + ) + + if not covers: + _LOGGER.error("No covers added") + return False + + add_devices(covers) # pylint: disable=too-many-arguments, too-many-instance-attributes diff --git a/homeassistant/components/notify/command_line.py b/homeassistant/components/notify/command_line.py index df77560c22b..9b637d71188 100644 --- a/homeassistant/components/notify/command_line.py +++ b/homeassistant/components/notify/command_line.py @@ -6,21 +6,25 @@ https://home-assistant.io/components/notify.command_line/ """ import logging import subprocess -from homeassistant.helpers import validate_config + +import voluptuous as vol + +from homeassistant.const import (CONF_COMMAND, CONF_NAME) from homeassistant.components.notify import ( - DOMAIN, BaseNotificationService) + BaseNotificationService, PLATFORM_SCHEMA) +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_COMMAND): cv.string, + vol.Optional(CONF_NAME): cv.string, +}) + def get_service(hass, config): """Get the Command Line notification service.""" - if not validate_config({DOMAIN: config}, - {DOMAIN: ['command']}, - _LOGGER): - return None - - command = config['command'] + command = config[CONF_COMMAND] return CommandLineNotificationService(command) diff --git a/homeassistant/components/sensor/command_line.py b/homeassistant/components/sensor/command_line.py index eb1fb4603e2..f26d2680a26 100644 --- a/homeassistant/components/sensor/command_line.py +++ b/homeassistant/components/sensor/command_line.py @@ -8,35 +8,41 @@ import logging import subprocess from datetime import timedelta -from homeassistant.const import CONF_VALUE_TEMPLATE +import voluptuous as vol + +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import ( + CONF_NAME, CONF_VALUE_TEMPLATE, CONF_UNIT_OF_MEASUREMENT, CONF_COMMAND) from homeassistant.helpers.entity import Entity from homeassistant.helpers import template from homeassistant.util import Throttle +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = "Command Sensor" +DEFAULT_NAME = 'Command Sensor' -# Return cached results if last scan was less then this time ago MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_COMMAND): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, +}) + # pylint: disable=unused-argument -def setup_platform(hass, config, add_devices_callback, discovery_info=None): +def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Command Sensor.""" - if config.get('command') is None: - _LOGGER.error('Missing required variable: "command"') - return False + name = config.get(CONF_NAME) + command = config.get(CONF_COMMAND) + unit = config.get(CONF_UNIT_OF_MEASUREMENT) + value_template = config.get(CONF_VALUE_TEMPLATE) - data = CommandSensorData(config.get('command')) + data = CommandSensorData(command) - add_devices_callback([CommandSensor( - hass, - data, - config.get('name', DEFAULT_NAME), - config.get('unit_of_measurement'), - config.get(CONF_VALUE_TEMPLATE) - )]) + add_devices([CommandSensor(hass, data, name, unit, value_template)]) # pylint: disable=too-many-arguments diff --git a/homeassistant/components/switch/command_line.py b/homeassistant/components/switch/command_line.py index 40b83371f9a..e20a47cf084 100644 --- a/homeassistant/components/switch/command_line.py +++ b/homeassistant/components/switch/command_line.py @@ -7,30 +7,53 @@ https://home-assistant.io/components/switch.command_line/ import logging import subprocess -from homeassistant.components.switch import SwitchDevice -from homeassistant.const import CONF_VALUE_TEMPLATE +import voluptuous as vol + +from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) +from homeassistant.const import ( + CONF_FRIENDLY_NAME, CONF_SWITCHES, CONF_VALUE_TEMPLATE, CONF_COMMAND_OFF, + CONF_COMMAND_ON, CONF_COMMAND_STATE) from homeassistant.helpers import template +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) +SWITCH_SCHEMA = vol.Schema({ + vol.Optional(CONF_COMMAND_OFF, default='true'): cv.string, + vol.Optional(CONF_COMMAND_ON, default='true'): cv.string, + vol.Optional(CONF_COMMAND_STATE): cv.string, + vol.Optional(CONF_FRIENDLY_NAME): cv.string, + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, +}) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_SWITCHES): vol.Schema({cv.slug: SWITCH_SCHEMA}), +}) + # pylint: disable=unused-argument -def setup_platform(hass, config, add_devices_callback, discovery_info=None): +def setup_platform(hass, config, add_devices, discovery_info=None): """Find and return switches controlled by shell commands.""" - switches = config.get('switches', {}) - devices = [] + devices = config.get(CONF_SWITCHES, {}) + switches = [] - for dev_name, properties in switches.items(): - devices.append( + for device_name, device_config in devices.items(): + switches.append( CommandSwitch( hass, - properties.get('name', dev_name), - properties.get('oncmd', 'true'), - properties.get('offcmd', 'true'), - properties.get('statecmd', False), - properties.get(CONF_VALUE_TEMPLATE, False))) + device_config.get(CONF_FRIENDLY_NAME, device_name), + device_config.get(CONF_COMMAND_ON), + device_config.get(CONF_COMMAND_OFF), + device_config.get(CONF_COMMAND_STATE), + device_config.get(CONF_VALUE_TEMPLATE) + ) + ) - add_devices_callback(devices) + if not switches: + _LOGGER.error("No switches added") + return False + + add_devices(switches) # pylint: disable=too-many-instance-attributes diff --git a/homeassistant/const.py b/homeassistant/const.py index 5bb11679076..f3c016015e6 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -28,7 +28,15 @@ CONF_BEFORE = 'before' CONF_BELOW = 'below' CONF_BLACKLIST = 'blacklist' CONF_CODE = 'code' +CONF_COMMAND = 'command' +CONF_COMMAND_CLOSE = 'command_close' +CONF_COMMAND_OFF = 'command_off' +CONF_COMMAND_ON = 'command_on' +CONF_COMMAND_OPEN = 'command_open' +CONF_COMMAND_STATE = 'command_state' +CONF_COMMAND_STOP = 'command_stop' CONF_CONDITION = 'condition' +CONF_COVERS = 'covers' CONF_CUSTOMIZE = 'customize' CONF_DEVICE = 'device' CONF_DEVICES = 'devices' @@ -40,6 +48,7 @@ CONF_ENTITY_NAMESPACE = 'entity_namespace' CONF_EVENT = 'event' CONF_FILE_PATH = 'file_path' CONF_FILENAME = 'filename' +CONF_FRIENDLY_NAME = 'friendly_name' CONF_HOST = 'host' CONF_HOSTS = 'hosts' CONF_ICON = 'icon' @@ -54,7 +63,10 @@ CONF_OFFSET = 'offset' CONF_OPTIMISTIC = 'optimistic' CONF_PASSWORD = 'password' CONF_PAYLOAD = 'payload' +CONF_PAYLOAD_OFF = 'payload_off' +CONF_PAYLOAD_ON = 'payload_on' CONF_PENDING_TIME = 'pending_time' +CONF_PIN = 'pin' CONF_PLATFORM = 'platform' CONF_PORT = 'port' CONF_PREFIX = 'prefix' @@ -62,9 +74,11 @@ CONF_RESOURCE = 'resource' CONF_RESOURCES = 'resources' CONF_SCAN_INTERVAL = 'scan_interval' CONF_SENSOR_CLASS = 'sensor_class' +CONF_SENSORS = 'sensors' CONF_SSL = 'ssl' CONF_STATE = 'state' CONF_STRUCTURE = 'structure' +CONF_SWITCHES = 'switches' CONF_TEMPERATURE_UNIT = 'temperature_unit' CONF_TIME_ZONE = 'time_zone' CONF_TOKEN = 'token' diff --git a/tests/components/binary_sensor/test_command_line.py b/tests/components/binary_sensor/test_command_line.py index 758911db353..62b856bbc23 100644 --- a/tests/components/binary_sensor/test_command_line.py +++ b/tests/components/binary_sensor/test_command_line.py @@ -3,6 +3,7 @@ import unittest from homeassistant.const import (STATE_ON, STATE_OFF) from homeassistant.components.binary_sensor import command_line +from homeassistant import bootstrap from tests.common import get_test_home_assistant @@ -24,6 +25,7 @@ class TestCommandSensorBinarySensor(unittest.TestCase): 'command': 'echo 1', 'payload_on': '1', 'payload_off': '0'} + devices = [] def add_dev_callback(devs): @@ -31,8 +33,7 @@ class TestCommandSensorBinarySensor(unittest.TestCase): for dev in devs: devices.append(dev) - command_line.setup_platform( - self.hass, config, add_dev_callback) + command_line.setup_platform(self.hass, config, add_dev_callback) self.assertEqual(1, len(devices)) entity = devices[0] @@ -41,19 +42,13 @@ class TestCommandSensorBinarySensor(unittest.TestCase): def test_setup_bad_config(self): """Test the setup with a bad configuration.""" - config = {} + config = {'name': 'test', + 'platform': 'not_command_line', + } - devices = [] - - def add_dev_callback(devs): - """Add callback to add devices.""" - for dev in devs: - devices.append(dev) - - self.assertFalse(command_line.setup_platform( - self.hass, config, add_dev_callback)) - - self.assertEqual(0, len(devices)) + self.assertFalse(bootstrap.setup_component(self.hass, 'test', { + 'command_line': config, + })) def test_template(self): """Test setting the state with a template.""" diff --git a/tests/components/cover/test_command_line.py b/tests/components/cover/test_command_line.py index bab0137f4f8..e4ef6793127 100644 --- a/tests/components/cover/test_command_line.py +++ b/tests/components/cover/test_command_line.py @@ -17,12 +17,10 @@ class TestCommandCover(unittest.TestCase): def setup_method(self, method): """Setup things to be run when tests are started.""" self.hass = ha.HomeAssistant() - self.hass.config.latitude = 32.87336 - self.hass.config.longitude = 117.22743 self.rs = cmd_rs.CommandCover(self.hass, 'foo', - 'cmd_open', 'cmd_close', - 'cmd_stop', 'cmd_state', - None) # FIXME + 'command_open', 'command_close', + 'command_stop', 'command_state', + None) def teardown_method(self, method): """Stop down everything that was started.""" @@ -47,10 +45,10 @@ class TestCommandCover(unittest.TestCase): with tempfile.TemporaryDirectory() as tempdirname: path = os.path.join(tempdirname, 'cover_status') test_cover = { - 'statecmd': 'cat {}'.format(path), - 'opencmd': 'echo 1 > {}'.format(path), - 'closecmd': 'echo 1 > {}'.format(path), - 'stopcmd': 'echo 0 > {}'.format(path), + 'command_state': 'cat {}'.format(path), + 'command_open': 'echo 1 > {}'.format(path), + 'command_close': 'echo 1 > {}'.format(path), + 'command_stop': 'echo 0 > {}'.format(path), 'value_template': '{{ value }}' } self.assertTrue(cover.setup(self.hass, { diff --git a/tests/components/notify/test_command_line.py b/tests/components/notify/test_command_line.py index ffe156deb9d..d350b0e4b37 100644 --- a/tests/components/notify/test_command_line.py +++ b/tests/components/notify/test_command_line.py @@ -2,13 +2,12 @@ import os import tempfile import unittest +from unittest.mock import patch import homeassistant.components.notify as notify - +from homeassistant import bootstrap from tests.common import get_test_home_assistant -from unittest.mock import patch - class TestCommandLine(unittest.TestCase): """Test the command line notifications.""" @@ -21,20 +20,23 @@ class TestCommandLine(unittest.TestCase): """Stop down everything that was started.""" self.hass.stop() + def test_setup(self): + """Test setup.""" + assert bootstrap.setup_component(self.hass, 'notify', { + 'notify': { + 'name': 'test', + 'platform': 'command_line', + 'command': 'echo $(cat); exit 1', + }}) + def test_bad_config(self): - """Test set up the platform with bad/missing config.""" + """Test set up the platform with bad/missing configuration.""" self.assertFalse(notify.setup(self.hass, { 'notify': { 'name': 'test', 'platform': 'bad_platform', } })) - self.assertFalse(notify.setup(self.hass, { - 'notify': { - 'name': 'test', - 'platform': 'command_line', - } - })) def test_command_line_output(self): """Test the command line output.""" diff --git a/tests/components/sensor/test_command_line.py b/tests/components/sensor/test_command_line.py index bd083f7b63e..b089a82356b 100644 --- a/tests/components/sensor/test_command_line.py +++ b/tests/components/sensor/test_command_line.py @@ -2,7 +2,7 @@ import unittest from homeassistant.components.sensor import command_line - +from homeassistant import bootstrap from tests.common import get_test_home_assistant @@ -21,7 +21,8 @@ class TestCommandSensorSensor(unittest.TestCase): """Test sensor setup.""" config = {'name': 'Test', 'unit_of_measurement': 'in', - 'command': 'echo 5'} + 'command': 'echo 5' + } devices = [] def add_dev_callback(devs): @@ -29,8 +30,7 @@ class TestCommandSensorSensor(unittest.TestCase): for dev in devs: devices.append(dev) - command_line.setup_platform( - self.hass, config, add_dev_callback) + command_line.setup_platform(self.hass, config, add_dev_callback) self.assertEqual(1, len(devices)) entity = devices[0] @@ -40,19 +40,13 @@ class TestCommandSensorSensor(unittest.TestCase): def test_setup_bad_config(self): """Test setup with a bad configuration.""" - config = {} + config = {'name': 'test', + 'platform': 'not_command_line', + } - devices = [] - - def add_dev_callback(devs): - """Add a callback to add devices.""" - for dev in devs: - devices.append(dev) - - self.assertFalse(command_line.setup_platform( - self.hass, config, add_dev_callback)) - - self.assertEqual(0, len(devices)) + self.assertFalse(bootstrap.setup_component(self.hass, 'test', { + 'command_line': config, + })) def test_template(self): """Test command sensor with template.""" diff --git a/tests/components/switch/test_command_line.py b/tests/components/switch/test_command_line.py index f71fb9c25aa..5e17710f8fd 100644 --- a/tests/components/switch/test_command_line.py +++ b/tests/components/switch/test_command_line.py @@ -27,8 +27,8 @@ class TestCommandSwitch(unittest.TestCase): with tempfile.TemporaryDirectory() as tempdirname: path = os.path.join(tempdirname, 'switch_status') test_switch = { - 'oncmd': 'echo 1 > {}'.format(path), - 'offcmd': 'echo 0 > {}'.format(path), + 'command_on': 'echo 1 > {}'.format(path), + 'command_off': 'echo 0 > {}'.format(path), } self.assertTrue(switch.setup(self.hass, { 'switch': { @@ -59,9 +59,9 @@ class TestCommandSwitch(unittest.TestCase): with tempfile.TemporaryDirectory() as tempdirname: path = os.path.join(tempdirname, 'switch_status') test_switch = { - 'statecmd': 'cat {}'.format(path), - 'oncmd': 'echo 1 > {}'.format(path), - 'offcmd': 'echo 0 > {}'.format(path), + 'command_state': 'cat {}'.format(path), + 'command_on': 'echo 1 > {}'.format(path), + 'command_off': 'echo 0 > {}'.format(path), 'value_template': '{{ value=="1" }}' } self.assertTrue(switch.setup(self.hass, { @@ -95,9 +95,9 @@ class TestCommandSwitch(unittest.TestCase): oncmd = json.dumps({'status': 'ok'}) offcmd = json.dumps({'status': 'nope'}) test_switch = { - 'statecmd': 'cat {}'.format(path), - 'oncmd': 'echo \'{}\' > {}'.format(oncmd, path), - 'offcmd': 'echo \'{}\' > {}'.format(offcmd, path), + 'command_state': 'cat {}'.format(path), + 'command_on': 'echo \'{}\' > {}'.format(oncmd, path), + 'command_off': 'echo \'{}\' > {}'.format(offcmd, path), 'value_template': '{{ value_json.status=="ok" }}' } self.assertTrue(switch.setup(self.hass, { @@ -129,9 +129,9 @@ class TestCommandSwitch(unittest.TestCase): with tempfile.TemporaryDirectory() as tempdirname: path = os.path.join(tempdirname, 'switch_status') test_switch = { - 'statecmd': 'cat {}'.format(path), - 'oncmd': 'echo 1 > {}'.format(path), - 'offcmd': 'echo 0 > {}'.format(path), + 'command_state': 'cat {}'.format(path), + 'command_on': 'echo 1 > {}'.format(path), + 'command_off': 'echo 0 > {}'.format(path), } self.assertTrue(switch.setup(self.hass, { 'switch': {