diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 056d45ad656512..26de41387f5dc6 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -613,6 +613,7 @@ def validator(value): 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 a139be4b2607bf..acaeb545815518 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -30,6 +30,7 @@ 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 @@ def async_script_wait(entity_id, from_s, to_s): 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 @@ def _async_check_condition(self, action, variables): 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 4297ca26e7dcac..7e60cc796ccb15 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -375,8 +375,8 @@ def record_event(event): 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 = [] @@ -393,6 +393,7 @@ def record_event(event): {'event': event}, { 'wait_template': "{{states.switch.test.state == 'off'}}", + 'continue_on_timeout': False, 'timeout': 5 }, {'event': event}])) @@ -412,6 +413,81 @@ def record_event(event): 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 = [] + + @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 + }, + {'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_variables(self): """Test the wait template with variables.""" event = 'test_event'