From d5179b4bdc6f0a183c53257ff89eb4e3bfb33df1 Mon Sep 17 00:00:00 2001 From: happyleaves Date: Mon, 21 Dec 2015 19:49:39 -0500 Subject: [PATCH 1/4] add statecmd to command_switch --- .../components/switch/command_switch.py | 51 ++++++++++++++++--- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/switch/command_switch.py b/homeassistant/components/switch/command_switch.py index 91171be3680..1882d73fc51 100644 --- a/homeassistant/components/switch/command_switch.py +++ b/homeassistant/components/switch/command_switch.py @@ -10,6 +10,8 @@ import logging import subprocess from homeassistant.components.switch import SwitchDevice +from homeassistant.const import CONF_VALUE_TEMPLATE +from homeassistant.util import template _LOGGER = logging.getLogger(__name__) @@ -22,22 +24,36 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): devices = [] for dev_name, properties in switches.items(): + if 'statecmd' in properties and CONF_VALUE_TEMPLATE not in properties: + _LOGGER.warn("Specify a %s when using statemcd", + CONF_VALUE_TEMPLATE) + continue devices.append( CommandSwitch( + hass, properties.get('name', dev_name), properties.get('oncmd', 'true'), - properties.get('offcmd', 'true'))) + properties.get('offcmd', 'true'), + properties.get('statecmd', False), + properties.get(CONF_VALUE_TEMPLATE, False))) add_devices_callback(devices) class CommandSwitch(SwitchDevice): """ Represents a switch that can be togggled using shell commands. """ - def __init__(self, name, command_on, command_off): + + # pylint: disable=too-many-arguments + def __init__(self, hass, name, command_on, command_off, + command_state, value_template): + + self._hass = hass self._name = name self._state = False self._command_on = command_on self._command_off = command_off + self._command_state = command_state + self._value_template = value_template @staticmethod def _switch(command): @@ -51,10 +67,21 @@ class CommandSwitch(SwitchDevice): return success + @staticmethod + def _query_state(command): + """ Execute state command. """ + _LOGGER.info('Running state command: %s', command) + + try: + return_value = subprocess.check_output(command, shell=True) + return return_value.strip().decode('utf-8') + except subprocess.CalledProcessError: + _LOGGER.error('Command failed: %s', command) + @property def should_poll(self): """ No polling needed. """ - return False + return True @property def name(self): @@ -66,14 +93,24 @@ class CommandSwitch(SwitchDevice): """ True if device is on. """ return self._state + def update(self): + """ Update device state. """ + if self._command_state and self._value_template: + payload = CommandSwitch._query_state(self._command_state) + payload = template.render_with_possible_json_value( + self._hass, self._value_template, payload) + self._state = (payload == "True") + def turn_on(self, **kwargs): """ Turn the device on. """ if CommandSwitch._switch(self._command_on): - self._state = True - self.update_ha_state() + if not self._command_state: + self._state = True + self.update_ha_state() def turn_off(self, **kwargs): """ Turn the device off. """ if CommandSwitch._switch(self._command_off): - self._state = False - self.update_ha_state() + if not self._command_state: + self._state = False + self.update_ha_state() From fba5becd909ed826fbe8b8368abbdf848b5efcfd Mon Sep 17 00:00:00 2001 From: happyleaves Date: Mon, 21 Dec 2015 20:18:00 -0500 Subject: [PATCH 2/4] warn->warning --- homeassistant/components/switch/command_switch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/switch/command_switch.py b/homeassistant/components/switch/command_switch.py index 1882d73fc51..5af197193d3 100644 --- a/homeassistant/components/switch/command_switch.py +++ b/homeassistant/components/switch/command_switch.py @@ -25,8 +25,8 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): for dev_name, properties in switches.items(): if 'statecmd' in properties and CONF_VALUE_TEMPLATE not in properties: - _LOGGER.warn("Specify a %s when using statemcd", - CONF_VALUE_TEMPLATE) + _LOGGER.warning("Specify a %s when using statemcd", + CONF_VALUE_TEMPLATE) continue devices.append( CommandSwitch( From e9059a3ed9b6d311b79750eeb672b153a6c1bfe0 Mon Sep 17 00:00:00 2001 From: happyleaves Date: Sun, 27 Dec 2015 22:49:55 -0500 Subject: [PATCH 3/4] added test; addressed comments --- .../components/switch/command_switch.py | 38 +++-- .../components/switch/test_command_switch.py | 158 ++++++++++++++++++ 2 files changed, 183 insertions(+), 13 deletions(-) create mode 100644 tests/components/switch/test_command_switch.py diff --git a/homeassistant/components/switch/command_switch.py b/homeassistant/components/switch/command_switch.py index 5af197193d3..c36ca4e9ce9 100644 --- a/homeassistant/components/switch/command_switch.py +++ b/homeassistant/components/switch/command_switch.py @@ -24,10 +24,6 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): devices = [] for dev_name, properties in switches.items(): - if 'statecmd' in properties and CONF_VALUE_TEMPLATE not in properties: - _LOGGER.warning("Specify a %s when using statemcd", - CONF_VALUE_TEMPLATE) - continue devices.append( CommandSwitch( hass, @@ -68,8 +64,8 @@ class CommandSwitch(SwitchDevice): return success @staticmethod - def _query_state(command): - """ Execute state command. """ + def _query_state_value(command): + """ Execute state command for return value. """ _LOGGER.info('Running state command: %s', command) try: @@ -78,10 +74,16 @@ class CommandSwitch(SwitchDevice): except subprocess.CalledProcessError: _LOGGER.error('Command failed: %s', command) + @staticmethod + def _query_state_code(command): + """ Execute state command for return code. """ + _LOGGER.info('Running state command: %s', command) + return subprocess.call(command, shell=True) == 0 + @property def should_poll(self): - """ No polling needed. """ - return True + """ Only poll if we have statecmd. """ + return self._command_state is not None @property def name(self): @@ -93,13 +95,23 @@ class CommandSwitch(SwitchDevice): """ True if device is on. """ return self._state + def _query_state(self): + """ Query for state. """ + if not self._command_state: + _LOGGER.error('No state command specified') + return + if self._value_template: + return CommandSwitch._query_state_value(self._command_state) + return CommandSwitch._query_state_code(self._command_state) + def update(self): """ Update device state. """ - if self._command_state and self._value_template: - payload = CommandSwitch._query_state(self._command_state) - payload = template.render_with_possible_json_value( - self._hass, self._value_template, payload) - self._state = (payload == "True") + if self._command_state: + payload = str(self._query_state()) + if self._value_template: + payload = template.render_with_possible_json_value( + self._hass, self._value_template, payload) + self._state = (payload.lower() == "true") def turn_on(self, **kwargs): """ Turn the device on. """ diff --git a/tests/components/switch/test_command_switch.py b/tests/components/switch/test_command_switch.py new file mode 100644 index 00000000000..3684f78fff4 --- /dev/null +++ b/tests/components/switch/test_command_switch.py @@ -0,0 +1,158 @@ +""" +tests.components.switch.test_command_switch +~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests command switch. +""" +import json +import os +import tempfile +import unittest + +from homeassistant import core +from homeassistant.const import STATE_ON, STATE_OFF +import homeassistant.components.switch as switch + + +class TestCommandSwitch(unittest.TestCase): + """ Test the command switch. """ + + def setUp(self): # pylint: disable=invalid-name + self.hass = core.HomeAssistant() + + def tearDown(self): # pylint: disable=invalid-name + """ Stop down stuff we started. """ + self.hass.stop() + + def test_state_none(self): + with tempfile.TemporaryDirectory() as tempdirname: + path = os.path.join(tempdirname, 'switch_status') + test_switch = { + 'oncmd': 'echo 1 > {}'.format(path), + 'offcmd': 'echo 0 > {}'.format(path), + } + self.assertTrue(switch.setup(self.hass, { + 'switch': { + 'platform': 'command_switch', + 'switches': { + 'test': test_switch + } + } + })) + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_OFF, state.state) + + switch.turn_on(self.hass, 'switch.test') + self.hass.pool.block_till_done() + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_ON, state.state) + + switch.turn_off(self.hass, 'switch.test') + self.hass.pool.block_till_done() + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_OFF, state.state) + + + def test_state_value(self): + 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), + 'value_template': '{{ value=="1" }}' + } + self.assertTrue(switch.setup(self.hass, { + 'switch': { + 'platform': 'command_switch', + 'switches': { + 'test': test_switch + } + } + })) + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_OFF, state.state) + + switch.turn_on(self.hass, 'switch.test') + self.hass.pool.block_till_done() + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_ON, state.state) + + switch.turn_off(self.hass, 'switch.test') + self.hass.pool.block_till_done() + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_OFF, state.state) + + + def test_state_json_value(self): + with tempfile.TemporaryDirectory() as tempdirname: + path = os.path.join(tempdirname, 'switch_status') + 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), + 'value_template': '{{ value_json.status=="ok" }}' + } + self.assertTrue(switch.setup(self.hass, { + 'switch': { + 'platform': 'command_switch', + 'switches': { + 'test': test_switch + } + } + })) + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_OFF, state.state) + + switch.turn_on(self.hass, 'switch.test') + self.hass.pool.block_till_done() + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_ON, state.state) + + switch.turn_off(self.hass, 'switch.test') + self.hass.pool.block_till_done() + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_OFF, state.state) + + def test_state_code(self): + 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), + } + self.assertTrue(switch.setup(self.hass, { + 'switch': { + 'platform': 'command_switch', + 'switches': { + 'test': test_switch + } + } + })) + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_OFF, state.state) + + switch.turn_on(self.hass, 'switch.test') + self.hass.pool.block_till_done() + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_ON, state.state) + + switch.turn_off(self.hass, 'switch.test') + self.hass.pool.block_till_done() + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_ON, state.state) From 9c85702c875b4934595de0b9e2625be2949bc728 Mon Sep 17 00:00:00 2001 From: happyleaves Date: Thu, 31 Dec 2015 18:39:40 -0500 Subject: [PATCH 4/4] combine ifs --- .../components/switch/command_switch.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/switch/command_switch.py b/homeassistant/components/switch/command_switch.py index c36ca4e9ce9..a90ed61c3e2 100644 --- a/homeassistant/components/switch/command_switch.py +++ b/homeassistant/components/switch/command_switch.py @@ -115,14 +115,14 @@ class CommandSwitch(SwitchDevice): def turn_on(self, **kwargs): """ Turn the device on. """ - if CommandSwitch._switch(self._command_on): - if not self._command_state: - self._state = True - self.update_ha_state() + if (CommandSwitch._switch(self._command_on) and + not self._command_state): + self._state = True + self.update_ha_state() def turn_off(self, **kwargs): """ Turn the device off. """ - if CommandSwitch._switch(self._command_off): - if not self._command_state: - self._state = False - self.update_ha_state() + if (CommandSwitch._switch(self._command_off) and + not self._command_state): + self._state = False + self.update_ha_state()