Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added possibilities to use template in the command_line sensor #8505

Merged
merged 10 commits into from
Aug 10, 2017
2 changes: 1 addition & 1 deletion homeassistant/components/binary_sensor/command_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
50 changes: 43 additions & 7 deletions homeassistant/components/sensor/command_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@
"""
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)
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -89,20 +93,52 @@ def update(self):
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not necessary. By the time update gets called, self.hass will be set by Home Assistant.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is not the entity class, hass needs to be passed in. I think @balloob was thinking about the entity class where it could be removed.

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)
4 changes: 2 additions & 2 deletions tests/components/binary_sensor/test_command_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def test_setup_bad_config(self):

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',
Expand All @@ -64,7 +64,7 @@ def test_template(self):

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)
Expand Down
15 changes: 13 additions & 2 deletions tests/components/sensor/test_command_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def test_setup_bad_config(self):

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',
Expand All @@ -61,9 +61,20 @@ def test_template(self):
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)