mirror of
https://github.com/home-assistant/core.git
synced 2025-04-26 10:17:51 +00:00
MQTT: Managing binary payloads (#6976)
* Managing binary payloads Hello, background: I wrote a HA camera component that gets the image from a binary payload. I'm testing it with Zanzito (https://play.google.com/store/apps/details?id=it.barbaro.zanzito) and it works apparently well: it gets the image and correctly displays it in the front-end. But I had to make the changes I'm proposing here: the message was being blocked because the utf-8 decoding failed. As far as I know, the utf-8 encoding is required for the topic, not for the payload. What I did here was try the utf-8 decoding, but even if unsuccessful, it dispatches the message anyway. Is there anything else I'm missing? thanks Gianluca * Update __init__.py * Update __init__.py * Update __init__.py * git test - ignore * Should work * minor fixes * updated mqtt/services.yaml * added two tests, modified threaded subscribe * removing polymer * requested changes * requested changes - minor fix * security wrap around payload_file_path * services.yaml updated * removed file publishing * minor fix
This commit is contained in:
parent
9a9342ec3f
commit
fa65783f39
@ -201,7 +201,8 @@ def publish_template(hass, topic, payload_template, qos=None, retain=None):
|
|||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_subscribe(hass, topic, msg_callback, qos=DEFAULT_QOS):
|
def async_subscribe(hass, topic, msg_callback, qos=DEFAULT_QOS,
|
||||||
|
encoding='utf-8'):
|
||||||
"""Subscribe to an MQTT topic."""
|
"""Subscribe to an MQTT topic."""
|
||||||
@callback
|
@callback
|
||||||
def async_mqtt_topic_subscriber(dp_topic, dp_payload, dp_qos):
|
def async_mqtt_topic_subscriber(dp_topic, dp_payload, dp_qos):
|
||||||
@ -209,7 +210,21 @@ def async_subscribe(hass, topic, msg_callback, qos=DEFAULT_QOS):
|
|||||||
if not _match_topic(topic, dp_topic):
|
if not _match_topic(topic, dp_topic):
|
||||||
return
|
return
|
||||||
|
|
||||||
hass.async_run_job(msg_callback, dp_topic, dp_payload, dp_qos)
|
if encoding is not None:
|
||||||
|
try:
|
||||||
|
payload = dp_payload.decode(encoding)
|
||||||
|
_LOGGER.debug("Received message on %s: %s",
|
||||||
|
dp_topic, payload)
|
||||||
|
except (AttributeError, UnicodeDecodeError):
|
||||||
|
_LOGGER.error("Illegal payload encoding %s from "
|
||||||
|
"MQTT topic: %s, Payload: %s",
|
||||||
|
encoding, dp_topic, dp_payload)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
_LOGGER.debug("Received binary message on %s", dp_topic)
|
||||||
|
payload = dp_payload
|
||||||
|
|
||||||
|
hass.async_run_job(msg_callback, dp_topic, payload, dp_qos)
|
||||||
|
|
||||||
async_remove = async_dispatcher_connect(
|
async_remove = async_dispatcher_connect(
|
||||||
hass, SIGNAL_MQTT_MESSAGE_RECEIVED, async_mqtt_topic_subscriber)
|
hass, SIGNAL_MQTT_MESSAGE_RECEIVED, async_mqtt_topic_subscriber)
|
||||||
@ -218,10 +233,12 @@ def async_subscribe(hass, topic, msg_callback, qos=DEFAULT_QOS):
|
|||||||
return async_remove
|
return async_remove
|
||||||
|
|
||||||
|
|
||||||
def subscribe(hass, topic, msg_callback, qos=DEFAULT_QOS):
|
def subscribe(hass, topic, msg_callback, qos=DEFAULT_QOS,
|
||||||
|
encoding='utf-8'):
|
||||||
"""Subscribe to an MQTT topic."""
|
"""Subscribe to an MQTT topic."""
|
||||||
async_remove = run_coroutine_threadsafe(
|
async_remove = run_coroutine_threadsafe(
|
||||||
async_subscribe(hass, topic, msg_callback, qos),
|
async_subscribe(hass, topic, msg_callback,
|
||||||
|
qos, encoding),
|
||||||
hass.loop
|
hass.loop
|
||||||
).result()
|
).result()
|
||||||
|
|
||||||
@ -372,8 +389,8 @@ def async_setup(hass, config):
|
|||||||
payload_template = call.data.get(ATTR_PAYLOAD_TEMPLATE)
|
payload_template = call.data.get(ATTR_PAYLOAD_TEMPLATE)
|
||||||
qos = call.data[ATTR_QOS]
|
qos = call.data[ATTR_QOS]
|
||||||
retain = call.data[ATTR_RETAIN]
|
retain = call.data[ATTR_RETAIN]
|
||||||
try:
|
|
||||||
if payload_template is not None:
|
if payload_template is not None:
|
||||||
|
try:
|
||||||
payload = \
|
payload = \
|
||||||
template.Template(payload_template, hass).async_render()
|
template.Template(payload_template, hass).async_render()
|
||||||
except template.jinja2.TemplateError as exc:
|
except template.jinja2.TemplateError as exc:
|
||||||
@ -564,16 +581,8 @@ class MQTT(object):
|
|||||||
|
|
||||||
def _mqtt_on_message(self, _mqttc, _userdata, msg):
|
def _mqtt_on_message(self, _mqttc, _userdata, msg):
|
||||||
"""Message received callback."""
|
"""Message received callback."""
|
||||||
try:
|
|
||||||
payload = msg.payload.decode('utf-8')
|
|
||||||
except (AttributeError, UnicodeDecodeError):
|
|
||||||
_LOGGER.error("Illegal utf-8 unicode payload from "
|
|
||||||
"MQTT topic: %s, Payload: %s", msg.topic,
|
|
||||||
msg.payload)
|
|
||||||
else:
|
|
||||||
_LOGGER.info("Received message on %s: %s", msg.topic, payload)
|
|
||||||
dispatcher_send(
|
dispatcher_send(
|
||||||
self.hass, SIGNAL_MQTT_MESSAGE_RECEIVED, msg.topic, payload,
|
self.hass, SIGNAL_MQTT_MESSAGE_RECEIVED, msg.topic, msg.payload,
|
||||||
msg.qos
|
msg.qos
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -170,8 +170,11 @@ def mock_service(hass, domain, service):
|
|||||||
@ha.callback
|
@ha.callback
|
||||||
def async_fire_mqtt_message(hass, topic, payload, qos=0):
|
def async_fire_mqtt_message(hass, topic, payload, qos=0):
|
||||||
"""Fire the MQTT message."""
|
"""Fire the MQTT message."""
|
||||||
|
if isinstance(payload, str):
|
||||||
|
payload = payload.encode('utf-8')
|
||||||
async_dispatcher_send(
|
async_dispatcher_send(
|
||||||
hass, mqtt.SIGNAL_MQTT_MESSAGE_RECEIVED, topic, payload, qos)
|
hass, mqtt.SIGNAL_MQTT_MESSAGE_RECEIVED, topic,
|
||||||
|
payload, qos)
|
||||||
|
|
||||||
|
|
||||||
def fire_mqtt_message(hass, topic, payload, qos=0):
|
def fire_mqtt_message(hass, topic, payload, qos=0):
|
||||||
|
@ -209,6 +209,31 @@ class TestMQTT(unittest.TestCase):
|
|||||||
self.hass.block_till_done()
|
self.hass.block_till_done()
|
||||||
self.assertEqual(0, len(self.calls))
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
|
def test_subscribe_binary_topic(self):
|
||||||
|
"""Test the subscription to a binary topic."""
|
||||||
|
mqtt.subscribe(self.hass, 'test-topic', self.record_calls,
|
||||||
|
0, None)
|
||||||
|
|
||||||
|
fire_mqtt_message(self.hass, 'test-topic', 0x9a)
|
||||||
|
|
||||||
|
self.hass.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
self.assertEqual('test-topic', self.calls[0][0])
|
||||||
|
self.assertEqual(0x9a, self.calls[0][1])
|
||||||
|
|
||||||
|
def test_receiving_non_utf8_message_gets_logged(self):
|
||||||
|
"""Test receiving a non utf8 encoded message."""
|
||||||
|
mqtt.subscribe(self.hass, 'test-topic', self.record_calls)
|
||||||
|
|
||||||
|
with self.assertLogs(level='ERROR') as test_handle:
|
||||||
|
fire_mqtt_message(self.hass, 'test-topic', 0x9a)
|
||||||
|
self.hass.block_till_done()
|
||||||
|
self.assertIn(
|
||||||
|
"ERROR:homeassistant.components.mqtt:Illegal payload "
|
||||||
|
"encoding utf-8 from MQTT "
|
||||||
|
"topic: test-topic, Payload: 154",
|
||||||
|
test_handle.output[0])
|
||||||
|
|
||||||
|
|
||||||
class TestMQTTCallbacks(unittest.TestCase):
|
class TestMQTTCallbacks(unittest.TestCase):
|
||||||
"""Test the MQTT callbacks."""
|
"""Test the MQTT callbacks."""
|
||||||
@ -255,7 +280,8 @@ class TestMQTTCallbacks(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(1, len(calls))
|
self.assertEqual(1, len(calls))
|
||||||
last_event = calls[0]
|
last_event = calls[0]
|
||||||
self.assertEqual('Hello World!', last_event['payload'])
|
self.assertEqual(bytearray('Hello World!', 'utf-8'),
|
||||||
|
last_event['payload'])
|
||||||
self.assertEqual(message.topic, last_event['topic'])
|
self.assertEqual(message.topic, last_event['topic'])
|
||||||
self.assertEqual(message.qos, last_event['qos'])
|
self.assertEqual(message.qos, last_event['qos'])
|
||||||
|
|
||||||
@ -298,38 +324,6 @@ class TestMQTTCallbacks(unittest.TestCase):
|
|||||||
self.assertRaises(vol.Invalid, mqtt.valid_publish_topic, 'bad+topic')
|
self.assertRaises(vol.Invalid, mqtt.valid_publish_topic, 'bad+topic')
|
||||||
self.assertRaises(vol.Invalid, mqtt.valid_subscribe_topic, 'bad\0one')
|
self.assertRaises(vol.Invalid, mqtt.valid_subscribe_topic, 'bad\0one')
|
||||||
|
|
||||||
def test_receiving_non_utf8_message_gets_logged(self):
|
|
||||||
"""Test receiving a non utf8 encoded message."""
|
|
||||||
calls = []
|
|
||||||
|
|
||||||
@callback
|
|
||||||
def record(topic, payload, qos):
|
|
||||||
"""Helper to record calls."""
|
|
||||||
data = {
|
|
||||||
'topic': topic,
|
|
||||||
'payload': payload,
|
|
||||||
'qos': qos,
|
|
||||||
}
|
|
||||||
calls.append(data)
|
|
||||||
|
|
||||||
async_dispatcher_connect(
|
|
||||||
self.hass, mqtt.SIGNAL_MQTT_MESSAGE_RECEIVED, record)
|
|
||||||
|
|
||||||
payload = 0x9a
|
|
||||||
topic = 'test_topic'
|
|
||||||
MQTTMessage = namedtuple('MQTTMessage', ['topic', 'qos', 'payload'])
|
|
||||||
message = MQTTMessage(topic, 1, payload)
|
|
||||||
with self.assertLogs(level='ERROR') as test_handle:
|
|
||||||
self.hass.data['mqtt']._mqtt_on_message(
|
|
||||||
None,
|
|
||||||
{'hass': self.hass},
|
|
||||||
message)
|
|
||||||
self.hass.block_till_done()
|
|
||||||
self.assertIn(
|
|
||||||
"ERROR:homeassistant.components.mqtt:Illegal utf-8 unicode "
|
|
||||||
"payload from MQTT topic: %s, Payload: " % topic,
|
|
||||||
test_handle.output[0])
|
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def test_setup_embedded_starts_with_no_config(hass):
|
def test_setup_embedded_starts_with_no_config(hass):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user