diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index b2e62f720e5..7c390a465d6 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -14,6 +14,7 @@ import time from homeassistant.exceptions import HomeAssistantError import homeassistant.util as util +from homeassistant.util import template from homeassistant.helpers import validate_config from homeassistant.const import ( EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) @@ -49,6 +50,7 @@ DEFAULT_PROTOCOL = PROTOCOL_311 ATTR_TOPIC = 'topic' ATTR_PAYLOAD = 'payload' +ATTR_PAYLOAD_TEMPLATE = 'payload_template' ATTR_QOS = 'qos' ATTR_RETAIN = 'retain' @@ -131,11 +133,25 @@ def setup(hass, config): def publish_service(call): """Handle MQTT publish service calls.""" msg_topic = call.data.get(ATTR_TOPIC) - payload = util.template.render(hass, call.data.get(ATTR_PAYLOAD)) - qos = call.data.get(ATTR_QOS, DEFAULT_QOS) - retain = call.data.get(ATTR_RETAIN, DEFAULT_RETAIN) + payload = call.data.get(ATTR_PAYLOAD) + if payload is None: + try: + payload_template = call.data.get(ATTR_PAYLOAD_TEMPLATE) + if payload_template is None: + _LOGGER.error( + "You must set either '%s' or '%s' to use this service", + ATTR_PAYLOAD, ATTR_PAYLOAD_TEMPLATE) + return + payload = template.render(hass, payload_template) + except AttributeError as exc: + _LOGGER.error( + "Unable to publish to '%s': rendering payload template of " + "'%s' failed because %s.", + msg_topic, payload_template, exc) if msg_topic is None or payload is None: return + qos = call.data.get(ATTR_QOS, DEFAULT_QOS) + retain = call.data.get(ATTR_RETAIN, DEFAULT_RETAIN) MQTT_CLIENT.publish(msg_topic, payload, qos, retain) hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_mqtt) diff --git a/tests/components/test_mqtt.py b/tests/components/test_mqtt.py index c36459e5500..5746dd5a273 100644 --- a/tests/components/test_mqtt.py +++ b/tests/components/test_mqtt.py @@ -68,7 +68,7 @@ class TestMQTT(unittest.TestCase): self.assertEqual('test-payload', self.calls[0][0].data['service_data'][mqtt.ATTR_PAYLOAD]) - def test_service_call_without_topic_does_not_publush(self): + def test_service_call_without_topic_does_not_publish(self): self.hass.bus.fire(EVENT_CALL_SERVICE, { ATTR_DOMAIN: mqtt.DOMAIN, ATTR_SERVICE: mqtt.SERVICE_PUBLISH @@ -76,6 +76,40 @@ class TestMQTT(unittest.TestCase): self.hass.pool.block_till_done() self.assertTrue(not mqtt.MQTT_CLIENT.publish.called) + def test_service_call_with_template_payload_renders_template(self): + """ + If 'payload_template' is provided and 'payload' is not, then render it. + """ + self.hass.services.call(mqtt.DOMAIN, mqtt.SERVICE_PUBLISH, { + mqtt.ATTR_TOPIC: "test/topic", + mqtt.ATTR_PAYLOAD_TEMPLATE: "{{ 1+1 }}" + }, blocking=True) + self.assertTrue(mqtt.MQTT_CLIENT.publish.called) + self.assertEqual(mqtt.MQTT_CLIENT.publish.call_args[0][1], "2") + + def test_service_call_with_payload_doesnt_render_template(self): + """ + If a 'payload' is provided then use that instead of 'payload_template'. + """ + payload = "not a template" + payload_template = "a template" + self.hass.services.call(mqtt.DOMAIN, mqtt.SERVICE_PUBLISH, { + mqtt.ATTR_TOPIC: "test/topic", + mqtt.ATTR_PAYLOAD: payload, + mqtt.ATTR_PAYLOAD_TEMPLATE: payload_template + }, blocking=True) + self.assertTrue(mqtt.MQTT_CLIENT.publish.called) + self.assertEqual(mqtt.MQTT_CLIENT.publish.call_args[0][1], payload) + + def test_service_call_without_payload_or_payload_template(self): + """ + If neither 'payload' or 'payload_template' is provided then fail. + """ + self.hass.services.call(mqtt.DOMAIN, mqtt.SERVICE_PUBLISH, { + mqtt.ATTR_TOPIC: "test/topic" + }, blocking=True) + self.assertFalse(mqtt.MQTT_CLIENT.publish.called) + def test_subscribe_topic(self): mqtt.subscribe(self.hass, 'test-topic', self.record_calls)