From 4cf85294dbaba1704fe86b507f9b69aae234e753 Mon Sep 17 00:00:00 2001 From: Flyte Date: Fri, 5 Feb 2016 21:47:27 +0000 Subject: [PATCH 1/4] Add template support to mqtt.publish service payload. --- homeassistant/components/mqtt/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 2701ccad314..b2e62f720e5 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -131,7 +131,7 @@ def setup(hass, config): def publish_service(call): """Handle MQTT publish service calls.""" msg_topic = call.data.get(ATTR_TOPIC) - payload = call.data.get(ATTR_PAYLOAD) + 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) if msg_topic is None or payload is None: From 26fc637ab5c80ca2e614cca5962f7bcc00ae16a3 Mon Sep 17 00:00:00 2001 From: Flyte Date: Tue, 9 Feb 2016 15:41:31 +0000 Subject: [PATCH 2/4] Add payload_template to mqtt 'publish' service call. --- homeassistant/components/mqtt/__init__.py | 22 ++++++++++++-- tests/components/test_mqtt.py | 36 ++++++++++++++++++++++- 2 files changed, 54 insertions(+), 4 deletions(-) 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) From d52e2019c08943d35994dce43cd91fafdcaad504 Mon Sep 17 00:00:00 2001 From: Flyte Date: Wed, 10 Feb 2016 11:11:02 +0000 Subject: [PATCH 3/4] Update mqtt.publish() function to use template_payload. Reorganise publish service. Use mqtt.publish() in tests. --- homeassistant/components/mqtt/__init__.py | 25 ++++++++++++++--------- tests/components/test_mqtt.py | 20 ++++++++---------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 7c390a465d6..5e4e9ba2308 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -57,11 +57,14 @@ ATTR_RETAIN = 'retain' MAX_RECONNECT_WAIT = 300 # seconds -def publish(hass, topic, payload, qos=None, retain=None): +# pylint: disable=too-many-arguments +def publish(hass, topic, payload=None, qos=None, + retain=None, payload_template=None): """Publish message to an MQTT topic.""" data = { ATTR_TOPIC: topic, ATTR_PAYLOAD: payload, + ATTR_PAYLOAD_TEMPLATE: payload_template } if qos is not None: data[ATTR_QOS] = qos @@ -70,6 +73,7 @@ def publish(hass, topic, payload, qos=None, retain=None): data[ATTR_RETAIN] = retain hass.services.call(DOMAIN, SERVICE_PUBLISH, data) +# pylint: enable=too-many-arguments def subscribe(hass, topic, callback, qos=DEFAULT_QOS): @@ -134,24 +138,25 @@ def setup(hass, config): """Handle MQTT publish service calls.""" msg_topic = call.data.get(ATTR_TOPIC) payload = call.data.get(ATTR_PAYLOAD) + payload_template = call.data.get(ATTR_PAYLOAD_TEMPLATE) + qos = call.data.get(ATTR_QOS, DEFAULT_QOS) + retain = call.data.get(ATTR_RETAIN, DEFAULT_RETAIN) if payload is None: + if payload_template is None: + _LOGGER.error( + "You must set either '%s' or '%s' to use this service", + ATTR_PAYLOAD, ATTR_PAYLOAD_TEMPLATE) + return 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: + except template.jinja2.TemplateError as exc: _LOGGER.error( "Unable to publish to '%s': rendering payload template of " "'%s' failed because %s.", msg_topic, payload_template, exc) + return 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 5746dd5a273..de8192a6b10 100644 --- a/tests/components/test_mqtt.py +++ b/tests/components/test_mqtt.py @@ -80,10 +80,9 @@ class TestMQTT(unittest.TestCase): """ 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) + mqtt.publish(self.hass, "test/topic", + **{mqtt.ATTR_PAYLOAD_TEMPLATE: "{{ 1+1 }}"}) + self.hass.pool.block_till_done() self.assertTrue(mqtt.MQTT_CLIENT.publish.called) self.assertEqual(mqtt.MQTT_CLIENT.publish.call_args[0][1], "2") @@ -93,11 +92,9 @@ class TestMQTT(unittest.TestCase): """ 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) + mqtt.publish(self.hass, "test/topic", payload, + **{mqtt.ATTR_PAYLOAD_TEMPLATE: payload_template}) + self.hass.pool.block_till_done() self.assertTrue(mqtt.MQTT_CLIENT.publish.called) self.assertEqual(mqtt.MQTT_CLIENT.publish.call_args[0][1], payload) @@ -105,9 +102,8 @@ class TestMQTT(unittest.TestCase): """ 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) + mqtt.publish(self.hass, "test/topic") + self.hass.pool.block_till_done() self.assertFalse(mqtt.MQTT_CLIENT.publish.called) def test_subscribe_topic(self): From 4e0c7f8a3d9c45dc5f74e15ed06f8f27e6cd0945 Mon Sep 17 00:00:00 2001 From: Flyte Date: Wed, 10 Feb 2016 22:38:33 +0000 Subject: [PATCH 4/4] Create additional mqtt helper function for using template payload. --- homeassistant/components/mqtt/__init__.py | 27 ++++++++++++++--------- tests/components/test_mqtt.py | 20 +++++++++++------ 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 5e4e9ba2308..0721177c23d 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -57,23 +57,28 @@ ATTR_RETAIN = 'retain' MAX_RECONNECT_WAIT = 300 # seconds -# pylint: disable=too-many-arguments -def publish(hass, topic, payload=None, qos=None, - retain=None, payload_template=None): - """Publish message to an MQTT topic.""" - data = { - ATTR_TOPIC: topic, - ATTR_PAYLOAD: payload, - ATTR_PAYLOAD_TEMPLATE: payload_template - } +def _build_publish_data(topic, qos, retain): + """Build the arguments for the publish service without the payload.""" + data = {ATTR_TOPIC: topic} if qos is not None: data[ATTR_QOS] = qos - if retain is not None: data[ATTR_RETAIN] = retain + return data + +def publish(hass, topic, payload, qos=None, retain=None): + """Publish message to an MQTT topic.""" + data = _build_publish_data(topic, qos, retain) + data[ATTR_PAYLOAD] = payload + hass.services.call(DOMAIN, SERVICE_PUBLISH, data) + + +def publish_template(hass, topic, payload_template, qos=None, retain=None): + """Publish message to an MQTT topic using a template payload.""" + data = _build_publish_data(topic, qos, retain) + data[ATTR_PAYLOAD_TEMPLATE] = payload_template hass.services.call(DOMAIN, SERVICE_PUBLISH, data) -# pylint: enable=too-many-arguments def subscribe(hass, topic, callback, qos=DEFAULT_QOS): diff --git a/tests/components/test_mqtt.py b/tests/components/test_mqtt.py index de8192a6b10..1a33eb6276b 100644 --- a/tests/components/test_mqtt.py +++ b/tests/components/test_mqtt.py @@ -80,8 +80,7 @@ class TestMQTT(unittest.TestCase): """ If 'payload_template' is provided and 'payload' is not, then render it. """ - mqtt.publish(self.hass, "test/topic", - **{mqtt.ATTR_PAYLOAD_TEMPLATE: "{{ 1+1 }}"}) + mqtt.publish_template(self.hass, "test/topic", "{{ 1+1 }}") self.hass.pool.block_till_done() self.assertTrue(mqtt.MQTT_CLIENT.publish.called) self.assertEqual(mqtt.MQTT_CLIENT.publish.call_args[0][1], "2") @@ -92,9 +91,13 @@ class TestMQTT(unittest.TestCase): """ payload = "not a template" payload_template = "a template" - mqtt.publish(self.hass, "test/topic", payload, - **{mqtt.ATTR_PAYLOAD_TEMPLATE: payload_template}) - self.hass.pool.block_till_done() + # Call the service directly because the helper functions don't allow + # you to provide payload AND payload_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) @@ -102,8 +105,11 @@ class TestMQTT(unittest.TestCase): """ If neither 'payload' or 'payload_template' is provided then fail. """ - mqtt.publish(self.hass, "test/topic") - self.hass.pool.block_till_done() + # Call the service directly because the helper functions require you to + # provide a payload. + 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):