diff --git a/homeassistant/components/binary_sensor/command_line.py b/homeassistant/components/binary_sensor/command_line.py index da75f08726c..2289ad5d906 100644 --- a/homeassistant/components/binary_sensor/command_line.py +++ b/homeassistant/components/binary_sensor/command_line.py @@ -46,7 +46,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): value_template = config.get(CONF_VALUE_TEMPLATE) if value_template is not None: value_template.hass = hass - data = CommandSensorData(command) + data = CommandSensorData(hass, command) add_devices([CommandBinarySensor( hass, data, name, device_class, payload_on, payload_off, diff --git a/homeassistant/components/sensor/command_line.py b/homeassistant/components/sensor/command_line.py index a365555a3c9..f326a57b137 100644 --- a/homeassistant/components/sensor/command_line.py +++ b/homeassistant/components/sensor/command_line.py @@ -6,12 +6,16 @@ https://home-assistant.io/components/sensor.command_line/ """ import logging import subprocess +import shlex + from datetime import timedelta import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.helpers import template +from homeassistant.exceptions import TemplateError from homeassistant.const import ( CONF_NAME, CONF_VALUE_TEMPLATE, CONF_UNIT_OF_MEASUREMENT, CONF_COMMAND, STATE_UNKNOWN) @@ -40,7 +44,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): value_template = config.get(CONF_VALUE_TEMPLATE) if value_template is not None: value_template.hass = hass - data = CommandSensorData(command) + data = CommandSensorData(hass, command) add_devices([CommandSensor(hass, data, name, unit, value_template)], True) @@ -89,20 +93,52 @@ class CommandSensor(Entity): class CommandSensorData(object): """The class for handling the data retrieval.""" - def __init__(self, command): + def __init__(self, hass, command): """Initialize the data object.""" - self.command = command self.value = None + self.hass = hass + self.command = command def update(self): """Get the latest data with a shell command.""" - _LOGGER.info("Running command: %s", self.command) + command = self.command + cache = {} + if command in cache: + prog, args, args_compiled = cache[command] + elif ' ' not in command: + prog = command + args = None + args_compiled = None + cache[command] = (prog, args, args_compiled) + else: + prog, args = command.split(' ', 1) + args_compiled = template.Template(args, self.hass) + cache[command] = (prog, args, args_compiled) + + if args_compiled: + try: + args_to_render = {"arguments": args} + rendered_args = args_compiled.render(args_to_render) + except TemplateError as ex: + _LOGGER.exception("Error rendering command template: %s", ex) + return + else: + rendered_args = None + + if rendered_args == args: + # No template used. default behavior + shell = True + else: + # Template used. Construct the string used in the shell + command = str(' '.join([prog] + shlex.split(rendered_args))) + shell = True try: + _LOGGER.info("Running command: %s", command) return_value = subprocess.check_output( - self.command, shell=True, timeout=15) + command, shell=shell, timeout=15) self.value = return_value.strip().decode('utf-8') except subprocess.CalledProcessError: - _LOGGER.error("Command failed: %s", self.command) + _LOGGER.error("Command failed: %s", command) except subprocess.TimeoutExpired: - _LOGGER.error("Timeout for command: %s", self.command) + _LOGGER.error("Timeout for command: %s", command) diff --git a/tests/components/binary_sensor/test_command_line.py b/tests/components/binary_sensor/test_command_line.py index 93075967b22..f35e6f08452 100644 --- a/tests/components/binary_sensor/test_command_line.py +++ b/tests/components/binary_sensor/test_command_line.py @@ -54,7 +54,7 @@ class TestCommandSensorBinarySensor(unittest.TestCase): def test_template(self): """Test setting the state with a template.""" - data = command_line.CommandSensorData('echo 10') + data = command_line.CommandSensorData(self.hass, 'echo 10') entity = command_line.CommandBinarySensor( self.hass, data, 'test', None, '1.0', '0', @@ -64,7 +64,7 @@ class TestCommandSensorBinarySensor(unittest.TestCase): def test_sensor_off(self): """Test setting the state with a template.""" - data = command_line.CommandSensorData('echo 0') + data = command_line.CommandSensorData(self.hass, 'echo 0') entity = command_line.CommandBinarySensor( self.hass, data, 'test', None, '1', '0', None) diff --git a/tests/components/sensor/test_command_line.py b/tests/components/sensor/test_command_line.py index dd61a7ee114..6eb97b41e11 100644 --- a/tests/components/sensor/test_command_line.py +++ b/tests/components/sensor/test_command_line.py @@ -52,7 +52,7 @@ class TestCommandSensorSensor(unittest.TestCase): def test_template(self): """Test command sensor with template.""" - data = command_line.CommandSensorData('echo 50') + data = command_line.CommandSensorData(self.hass, 'echo 50') entity = command_line.CommandSensor( self.hass, data, 'test', 'in', @@ -61,9 +61,20 @@ class TestCommandSensorSensor(unittest.TestCase): entity.update() self.assertEqual(5, float(entity.state)) + def test_template_render(self): + """Ensure command with templates get rendered properly.""" + self.hass.states.set('sensor.test_state', 'Works') + data = command_line.CommandSensorData( + self.hass, + 'echo {{ states.sensor.test_state.state }}' + ) + data.update() + + self.assertEqual("Works", data.value) + def test_bad_command(self): """Test bad command.""" - data = command_line.CommandSensorData('asdfasdf') + data = command_line.CommandSensorData(self.hass, 'asdfasdf') data.update() self.assertEqual(None, data.value)