From 6aee535d7c0b4b0490dc7537fe60d1a79edff997 Mon Sep 17 00:00:00 2001 From: "Hovo (Luke)" Date: Mon, 13 Aug 2018 19:23:27 +1000 Subject: [PATCH] Allow wait template to run the remainder of the script (#15836) * Adding new feature to allow a wait template to run the remainer of the script on timeout * Styling changes * Fixing file permissions, adding test for new code * changed variable name, refactored script to pass information into async_set_timeout * Changing the default behaviour to continue to run the script after timeout --- homeassistant/helpers/config_validation.py | 1 + homeassistant/helpers/script.py | 20 ++++-- tests/helpers/test_script.py | 82 +++++++++++++++++++++- 3 files changed, 94 insertions(+), 9 deletions(-) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 056d45ad656..26de41387f5 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -613,6 +613,7 @@ _SCRIPT_WAIT_TEMPLATE_SCHEMA = vol.Schema({ vol.Optional(CONF_ALIAS): string, vol.Required("wait_template"): template, vol.Optional(CONF_TIMEOUT): vol.All(time_period, positive_timedelta), + vol.Optional("continue_on_timeout"): boolean, }) SCRIPT_SCHEMA = vol.All( diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index a139be4b260..acaeb545815 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -30,6 +30,7 @@ CONF_EVENT_DATA = 'event_data' CONF_EVENT_DATA_TEMPLATE = 'event_data_template' CONF_DELAY = 'delay' CONF_WAIT_TEMPLATE = 'wait_template' +CONF_CONTINUE = 'continue_on_timeout' def call_from_config(hass: HomeAssistant, config: ConfigType, @@ -143,7 +144,8 @@ class Script(): self.hass.async_add_job(self._change_listener) if CONF_TIMEOUT in action: - self._async_set_timeout(action, variables) + self._async_set_timeout( + action, variables, action.get(CONF_CONTINUE, True)) return @@ -214,17 +216,23 @@ class Script(): self._log("Test condition {}: {}".format(self.last_action, check)) return check - def _async_set_timeout(self, action, variables): - """Schedule a timeout to abort script.""" + def _async_set_timeout(self, action, variables, continue_on_timeout=True): + """Schedule a timeout to abort or continue script.""" timeout = action[CONF_TIMEOUT] unsub = None @callback def async_script_timeout(now): - """Call after timeout is retrieve stop script.""" + """Call after timeout is retrieve.""" self._async_listener.remove(unsub) - self._log("Timeout reached, abort script.") - self.async_stop() + + # Check if we want to continue to execute + # the script after the timeout + if continue_on_timeout: + self.hass.async_add_job(self.async_run(variables)) + else: + self._log("Timeout reached, abort script.") + self.async_stop() unsub = async_track_point_in_utc_time( self.hass, async_script_timeout, diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 4297ca26e7d..7e60cc796cc 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -375,8 +375,84 @@ class TestScriptHelper(unittest.TestCase): assert script_obj.can_cancel assert len(events) == 2 - def test_wait_template_timeout(self): - """Test the wait template.""" + def test_wait_template_timeout_halt(self): + """Test the wait template, halt on timeout.""" + event = 'test_event' + events = [] + + @callback + def record_event(event): + """Add recorded event to set.""" + events.append(event) + + self.hass.bus.listen(event, record_event) + + self.hass.states.set('switch.test', 'on') + + script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([ + {'event': event}, + { + 'wait_template': "{{states.switch.test.state == 'off'}}", + 'continue_on_timeout': False, + 'timeout': 5 + }, + {'event': event}])) + + script_obj.run() + self.hass.block_till_done() + + assert script_obj.is_running + assert script_obj.can_cancel + assert script_obj.last_action == event + assert len(events) == 1 + + future = dt_util.utcnow() + timedelta(seconds=5) + fire_time_changed(self.hass, future) + self.hass.block_till_done() + + assert not script_obj.is_running + assert len(events) == 1 + + def test_wait_template_timeout_continue(self): + """Test the wait template with continuing the script.""" + event = 'test_event' + events = [] + + @callback + def record_event(event): + """Add recorded event to set.""" + events.append(event) + + self.hass.bus.listen(event, record_event) + + self.hass.states.set('switch.test', 'on') + + script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([ + {'event': event}, + { + 'wait_template': "{{states.switch.test.state == 'off'}}", + 'timeout': 5, + 'continue_on_timeout': True + }, + {'event': event}])) + + script_obj.run() + self.hass.block_till_done() + + assert script_obj.is_running + assert script_obj.can_cancel + assert script_obj.last_action == event + assert len(events) == 1 + + future = dt_util.utcnow() + timedelta(seconds=5) + fire_time_changed(self.hass, future) + self.hass.block_till_done() + + assert not script_obj.is_running + assert len(events) == 2 + + def test_wait_template_timeout_default(self): + """Test the wait template with default contiune.""" event = 'test_event' events = [] @@ -410,7 +486,7 @@ class TestScriptHelper(unittest.TestCase): self.hass.block_till_done() assert not script_obj.is_running - assert len(events) == 1 + assert len(events) == 2 def test_wait_template_variables(self): """Test the wait template with variables."""