From b4cf0e874afeca102780b0936faefd290b72585d Mon Sep 17 00:00:00 2001 From: Oliver van Porten Date: Fri, 20 Nov 2015 22:03:17 +0100 Subject: [PATCH 01/10] Support parsing mqtt messages via jsonpath --- homeassistant/components/mqtt/__init__.py | 11 +++++++++++ homeassistant/components/sensor/mqtt.py | 15 ++++++++++++--- homeassistant/components/switch/mqtt.py | 13 +++++++++++-- requirements_all.txt | 1 + 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index cd5b5370175..24a19494d41 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -9,6 +9,8 @@ https://home-assistant.io/components/mqtt/ import logging import os import socket +import json +import jsonpath_rw from homeassistant.exceptions import HomeAssistantError import homeassistant.util as util @@ -127,6 +129,15 @@ def setup(hass, config): return True +class JsonFmtParser(object): + """ Implements a json parser on xpath""" + def __init__(self, jsonpath): + self._expr = jsonpath_rw.parse(jsonpath) + def __call__(self, payload): + match = self._expr.find(json.loads(payload)) + return match[0].value if len(match) > 0 else None + + # This is based on one of the paho-mqtt examples: # http://git.eclipse.org/c/paho/org.eclipse.paho.mqtt.python.git/tree/examples/sub-class.py # pylint: disable=too-many-arguments diff --git a/homeassistant/components/sensor/mqtt.py b/homeassistant/components/sensor/mqtt.py index 2623d2fdcce..f2608bd1657 100644 --- a/homeassistant/components/sensor/mqtt.py +++ b/homeassistant/components/sensor/mqtt.py @@ -7,6 +7,8 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.mqtt/ """ import logging +import json +import jsonpath_rw from homeassistant.helpers.entity import Entity import homeassistant.components.mqtt as mqtt @@ -31,23 +33,30 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): config.get('name', DEFAULT_NAME), config.get('state_topic'), config.get('qos', DEFAULT_QOS), - config.get('unit_of_measurement'))]) + config.get('unit_of_measurement'), + config.get('state_format'))]) # pylint: disable=too-many-arguments, too-many-instance-attributes class MqttSensor(Entity): """ Represents a sensor that can be updated using MQTT. """ - def __init__(self, hass, name, state_topic, qos, unit_of_measurement): + def __init__(self, hass, name, state_topic, qos, unit_of_measurement,state_format): self._state = "-" self._hass = hass self._name = name self._state_topic = state_topic self._qos = qos self._unit_of_measurement = unit_of_measurement + self._state_format = state_format + + if self._state_format.startswith('json:'): + self._parser = mqtt.JsonFmtParser(self._state_format[5:]) + else: + self._parser = lambda x: x def message_received(topic, payload, qos): """ A new MQTT message has been received. """ - self._state = payload + self._state = self._parser(payload) self.update_ha_state() mqtt.subscribe(hass, self._state_topic, message_received, self._qos) diff --git a/homeassistant/components/switch/mqtt.py b/homeassistant/components/switch/mqtt.py index 12d3f486323..a94ebe6b6b1 100644 --- a/homeassistant/components/switch/mqtt.py +++ b/homeassistant/components/switch/mqtt.py @@ -37,14 +37,15 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): config.get('qos', DEFAULT_QOS), config.get('payload_on', DEFAULT_PAYLOAD_ON), config.get('payload_off', DEFAULT_PAYLOAD_OFF), - config.get('optimistic', DEFAULT_OPTIMISTIC))]) + config.get('optimistic', DEFAULT_OPTIMISTIC), + config.get('state_format'))]) # pylint: disable=too-many-arguments, too-many-instance-attributes class MqttSwitch(SwitchDevice): """ Represents a switch that can be togggled using MQTT. """ def __init__(self, hass, name, state_topic, command_topic, qos, - payload_on, payload_off, optimistic): + payload_on, payload_off, optimistic, state_format): self._state = False self._hass = hass self._name = name @@ -54,9 +55,17 @@ class MqttSwitch(SwitchDevice): self._payload_on = payload_on self._payload_off = payload_off self._optimistic = optimistic + + self._state_format = state_format + + if self._state_format.startswith('json:'): + self._parser = mqtt.JsonFmtParser(self._state_format[5:]) + else: + self._parser = lambda x: x def message_received(topic, payload, qos): """ A new MQTT message has been received. """ + payload = self._parser(payload) if payload == self._payload_on: self._state = True self.update_ha_state() diff --git a/requirements_all.txt b/requirements_all.txt index c6b4ae81721..7e15e594313 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -74,6 +74,7 @@ https://github.com/bashwork/pymodbus/archive/d7fc4f1cc975631e0a9011390e8017f64b6 # homeassistant.components.mqtt paho-mqtt==1.1 +jsonpath-rw==1.4.0 # homeassistant.components.notify.pushbullet pushbullet.py==0.9.0 From 799043dc0a2031318b235d4a78f0c6d1178e7dca Mon Sep 17 00:00:00 2001 From: Oliver van Porten Date: Fri, 20 Nov 2015 22:45:09 +0100 Subject: [PATCH 02/10] refactor format mqtt format parser --- homeassistant/components/mqtt/__init__.py | 23 +++++++++++++++++------ homeassistant/components/sensor/mqtt.py | 7 +------ homeassistant/components/switch/mqtt.py | 8 +------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 24a19494d41..b2884a0ff34 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -129,13 +129,24 @@ def setup(hass, config): return True -class JsonFmtParser(object): - """ Implements a json parser on xpath""" - def __init__(self, jsonpath): - self._expr = jsonpath_rw.parse(jsonpath) +class FmtParser(object): + """ wrapper for all supported formats """ + + class _JsonFmtParser(object): + """ Implements a json parser on xpath""" + def __init__(self, jsonpath): + self._expr = jsonpath_rw.parse(jsonpath) + def __call__(self, payload): + match = self._expr.find(json.loads(payload)) + return match[0].value if len(match) > 0 else None + + def __init__(self, fmt): + if fmt.startswith('json:'): + self._parser = FmtParser._JsonFmtParser(fmt[5:]) + else: + self._parser = lambda x: x def __call__(self, payload): - match = self._expr.find(json.loads(payload)) - return match[0].value if len(match) > 0 else None + return self._parser(payload) # This is based on one of the paho-mqtt examples: diff --git a/homeassistant/components/sensor/mqtt.py b/homeassistant/components/sensor/mqtt.py index f2608bd1657..64a8852d06e 100644 --- a/homeassistant/components/sensor/mqtt.py +++ b/homeassistant/components/sensor/mqtt.py @@ -47,12 +47,7 @@ class MqttSensor(Entity): self._state_topic = state_topic self._qos = qos self._unit_of_measurement = unit_of_measurement - self._state_format = state_format - - if self._state_format.startswith('json:'): - self._parser = mqtt.JsonFmtParser(self._state_format[5:]) - else: - self._parser = lambda x: x + self._parser = mqtt.FmtParser(state_format) def message_received(topic, payload, qos): """ A new MQTT message has been received. """ diff --git a/homeassistant/components/switch/mqtt.py b/homeassistant/components/switch/mqtt.py index a94ebe6b6b1..eeb4551b921 100644 --- a/homeassistant/components/switch/mqtt.py +++ b/homeassistant/components/switch/mqtt.py @@ -55,13 +55,7 @@ class MqttSwitch(SwitchDevice): self._payload_on = payload_on self._payload_off = payload_off self._optimistic = optimistic - - self._state_format = state_format - - if self._state_format.startswith('json:'): - self._parser = mqtt.JsonFmtParser(self._state_format[5:]) - else: - self._parser = lambda x: x + self._parser = mqtt.FmtParser(state_format) def message_received(topic, payload, qos): """ A new MQTT message has been received. """ From 030686a978225255d386fe3b9b6925744514f2f6 Mon Sep 17 00:00:00 2001 From: Oliver van Porten Date: Fri, 20 Nov 2015 22:55:52 +0100 Subject: [PATCH 03/10] fix flak8 warnings --- homeassistant/components/mqtt/__init__.py | 32 ++++++++++++----------- homeassistant/components/sensor/mqtt.py | 5 ++-- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index b2884a0ff34..b5b8ca9cca7 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -129,24 +129,26 @@ def setup(hass, config): return True -class FmtParser(object): - """ wrapper for all supported formats """ - - class _JsonFmtParser(object): +class _JsonFmtParser(object): """ Implements a json parser on xpath""" def __init__(self, jsonpath): - self._expr = jsonpath_rw.parse(jsonpath) + self._expr = jsonpath_rw.parse(jsonpath) + def __call__(self, payload): - match = self._expr.find(json.loads(payload)) - return match[0].value if len(match) > 0 else None - - def __init__(self, fmt): - if fmt.startswith('json:'): - self._parser = FmtParser._JsonFmtParser(fmt[5:]) - else: - self._parser = lambda x: x - def __call__(self, payload): - return self._parser(payload) + match = self._expr.find(json.loads(payload)) + return match[0].value if len(match) > 0 else payload + + +class FmtParser(object): + """ wrapper for all supported formats """ + def __init__(self, fmt): + if fmt.startswith('json:'): + self._parser = _JsonFmtParser(fmt[5:]) + else: + self._parser = lambda x: x + + def __call__(self, payload): + return self._parser(payload) # This is based on one of the paho-mqtt examples: diff --git a/homeassistant/components/sensor/mqtt.py b/homeassistant/components/sensor/mqtt.py index 64a8852d06e..384d0f8a48a 100644 --- a/homeassistant/components/sensor/mqtt.py +++ b/homeassistant/components/sensor/mqtt.py @@ -7,8 +7,6 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.mqtt/ """ import logging -import json -import jsonpath_rw from homeassistant.helpers.entity import Entity import homeassistant.components.mqtt as mqtt @@ -40,7 +38,8 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): # pylint: disable=too-many-arguments, too-many-instance-attributes class MqttSensor(Entity): """ Represents a sensor that can be updated using MQTT. """ - def __init__(self, hass, name, state_topic, qos, unit_of_measurement,state_format): + def __init__(self, hass, name, state_topic, qos, unit_of_measurement, + state_format): self._state = "-" self._hass = hass self._name = name From 44714614ada3b5974c706235811c53f7709ff162 Mon Sep 17 00:00:00 2001 From: Oliver van Porten Date: Fri, 20 Nov 2015 23:42:22 +0100 Subject: [PATCH 04/10] Fix unit tests for mqtt --- homeassistant/components/mqtt/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index b5b8ca9cca7..5b92da90d0a 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -142,10 +142,10 @@ class _JsonFmtParser(object): class FmtParser(object): """ wrapper for all supported formats """ def __init__(self, fmt): - if fmt.startswith('json:'): - self._parser = _JsonFmtParser(fmt[5:]) - else: - self._parser = lambda x: x + self._parser = lambda x: x + if fmt: + if fmt.startswith('json:'): + self._parser = _JsonFmtParser(fmt[5:]) def __call__(self, payload): return self._parser(payload) From 820b2a31b3745e2dbd162565915fba2ce23592b3 Mon Sep 17 00:00:00 2001 From: Oliver van Porten Date: Fri, 20 Nov 2015 23:44:46 +0100 Subject: [PATCH 05/10] Add additional unit tests for mqtt state format parsing --- tests/components/sensor/test_mqtt.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/components/sensor/test_mqtt.py b/tests/components/sensor/test_mqtt.py index b59ea867c58..8d1aef4d57f 100644 --- a/tests/components/sensor/test_mqtt.py +++ b/tests/components/sensor/test_mqtt.py @@ -39,3 +39,22 @@ class TestSensorMQTT(unittest.TestCase): self.assertEqual('100', state.state) self.assertEqual('fav unit', state.attributes.get('unit_of_measurement')) + + def test_setting_sensor_value_via_mqtt_json_message(self): + self.assertTrue(sensor.setup(self.hass, { + 'sensor': { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'test-topic', + 'unit_of_measurement': 'fav unit', + 'state_format': 'json:val' + } + })) + + fire_mqtt_message(self.hass, 'test-topic', '{ "val": "100" }') + self.hass.pool.block_till_done() + state = self.hass.states.get('sensor.test') + + self.assertEqual('100', state.state) + self.assertEqual('fav unit', + state.attributes.get('unit_of_measurement')) From 427944cc4491225bc043b43175d81fce6fd44e27 Mon Sep 17 00:00:00 2001 From: Oliver van Porten Date: Fri, 20 Nov 2015 23:50:46 +0100 Subject: [PATCH 06/10] add test for mqtt+json switch --- tests/components/sensor/test_mqtt.py | 3 +-- tests/components/switch/test_mqtt.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/tests/components/sensor/test_mqtt.py b/tests/components/sensor/test_mqtt.py index 8d1aef4d57f..0c17b95e212 100644 --- a/tests/components/sensor/test_mqtt.py +++ b/tests/components/sensor/test_mqtt.py @@ -56,5 +56,4 @@ class TestSensorMQTT(unittest.TestCase): state = self.hass.states.get('sensor.test') self.assertEqual('100', state.state) - self.assertEqual('fav unit', - state.attributes.get('unit_of_measurement')) + diff --git a/tests/components/switch/test_mqtt.py b/tests/components/switch/test_mqtt.py index a09fcf86c58..4754e64e575 100644 --- a/tests/components/switch/test_mqtt.py +++ b/tests/components/switch/test_mqtt.py @@ -80,3 +80,31 @@ class TestSensorMQTT(unittest.TestCase): self.mock_publish.mock_calls[-1][1]) state = self.hass.states.get('switch.test') self.assertEqual(STATE_OFF, state.state) + + def test_controlling_state_via_topic_and_json_message(self): + self.assertTrue(switch.setup(self.hass, { + 'switch': { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'state-topic', + 'command_topic': 'command-topic', + 'payload_on': 'beer on', + 'payload_off': 'beer off', + 'state_format': 'json:val' + } + })) + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_OFF, state.state) + + fire_mqtt_message(self.hass, 'state-topic', '{"val":"beer on"}') + self.hass.pool.block_till_done() + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_ON, state.state) + + fire_mqtt_message(self.hass, 'state-topic', '{"val":"beer off"}') + self.hass.pool.block_till_done() + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_OFF, state.state) \ No newline at end of file From 715abf241e732d69c43798493b134ca51cae9194 Mon Sep 17 00:00:00 2001 From: Oliver van Porten Date: Sat, 21 Nov 2015 17:57:15 +0100 Subject: [PATCH 07/10] Disable pylint warning for callable classes --- homeassistant/components/mqtt/__init__.py | 8 +++++--- homeassistant/components/sensor/mqtt.py | 4 ++-- homeassistant/components/switch/mqtt.py | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 5b92da90d0a..c27abbebe58 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -129,6 +129,7 @@ def setup(hass, config): return True +# pylint: disable=too-few-public-methods class _JsonFmtParser(object): """ Implements a json parser on xpath""" def __init__(self, jsonpath): @@ -139,16 +140,17 @@ class _JsonFmtParser(object): return match[0].value if len(match) > 0 else payload +# pylint: disable=too-few-public-methods class FmtParser(object): """ wrapper for all supported formats """ def __init__(self, fmt): - self._parser = lambda x: x + self._parse = lambda x: x if fmt: if fmt.startswith('json:'): - self._parser = _JsonFmtParser(fmt[5:]) + self._parse = _JsonFmtParser(fmt[5:]) def __call__(self, payload): - return self._parser(payload) + return self._parse(payload) # This is based on one of the paho-mqtt examples: diff --git a/homeassistant/components/sensor/mqtt.py b/homeassistant/components/sensor/mqtt.py index 384d0f8a48a..2bbed97e40c 100644 --- a/homeassistant/components/sensor/mqtt.py +++ b/homeassistant/components/sensor/mqtt.py @@ -46,11 +46,11 @@ class MqttSensor(Entity): self._state_topic = state_topic self._qos = qos self._unit_of_measurement = unit_of_measurement - self._parser = mqtt.FmtParser(state_format) + self._parse = mqtt.FmtParser(state_format) def message_received(topic, payload, qos): """ A new MQTT message has been received. """ - self._state = self._parser(payload) + self._state = self._parse(payload) self.update_ha_state() mqtt.subscribe(hass, self._state_topic, message_received, self._qos) diff --git a/homeassistant/components/switch/mqtt.py b/homeassistant/components/switch/mqtt.py index eeb4551b921..ed99a87868a 100644 --- a/homeassistant/components/switch/mqtt.py +++ b/homeassistant/components/switch/mqtt.py @@ -55,11 +55,11 @@ class MqttSwitch(SwitchDevice): self._payload_on = payload_on self._payload_off = payload_off self._optimistic = optimistic - self._parser = mqtt.FmtParser(state_format) + self._parse = mqtt.FmtParser(state_format) def message_received(topic, payload, qos): """ A new MQTT message has been received. """ - payload = self._parser(payload) + payload = self._parse(payload) if payload == self._payload_on: self._state = True self.update_ha_state() From dbcd055cfecb4c0757f3d4d42826b2638f3fc52c Mon Sep 17 00:00:00 2001 From: Oliver van Porten Date: Sun, 22 Nov 2015 16:18:05 +0100 Subject: [PATCH 08/10] move import of jsonpath-rw to c'tor of _JsonFmtParser --- homeassistant/components/mqtt/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index c27abbebe58..f729d02af8a 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -10,7 +10,6 @@ import logging import os import socket import json -import jsonpath_rw from homeassistant.exceptions import HomeAssistantError import homeassistant.util as util @@ -32,7 +31,8 @@ SERVICE_PUBLISH = 'publish' EVENT_MQTT_MESSAGE_RECEIVED = 'MQTT_MESSAGE_RECEIVED' DEPENDENCIES = [] -REQUIREMENTS = ['paho-mqtt==1.1'] +REQUIREMENTS = ['paho-mqtt==1.1' + 'jsonpath-rw==1.4.0'] CONF_BROKER = 'broker' CONF_PORT = 'port' @@ -133,6 +133,7 @@ def setup(hass, config): class _JsonFmtParser(object): """ Implements a json parser on xpath""" def __init__(self, jsonpath): + import jsonpath_rw self._expr = jsonpath_rw.parse(jsonpath) def __call__(self, payload): From 90681c2dc97fdddf9dee418ddb0b6e2981e26c62 Mon Sep 17 00:00:00 2001 From: Oliver van Porten Date: Sun, 22 Nov 2015 16:19:08 +0100 Subject: [PATCH 09/10] fix incorrect requirements --- 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 f729d02af8a..24c30c5eca6 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -31,7 +31,7 @@ SERVICE_PUBLISH = 'publish' EVENT_MQTT_MESSAGE_RECEIVED = 'MQTT_MESSAGE_RECEIVED' DEPENDENCIES = [] -REQUIREMENTS = ['paho-mqtt==1.1' +REQUIREMENTS = ['paho-mqtt==1.1', 'jsonpath-rw==1.4.0'] CONF_BROKER = 'broker' From 100400f14940bfade04ed29c51434ec55f3ac83d Mon Sep 17 00:00:00 2001 From: Oliver van Porten Date: Sun, 22 Nov 2015 16:28:21 +0100 Subject: [PATCH 10/10] move requirements to single line to not to affect coverage --- homeassistant/components/mqtt/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 24c30c5eca6..9ec5169c729 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -31,8 +31,7 @@ SERVICE_PUBLISH = 'publish' EVENT_MQTT_MESSAGE_RECEIVED = 'MQTT_MESSAGE_RECEIVED' DEPENDENCIES = [] -REQUIREMENTS = ['paho-mqtt==1.1', - 'jsonpath-rw==1.4.0'] +REQUIREMENTS = ['paho-mqtt==1.1', 'jsonpath-rw==1.4.0'] CONF_BROKER = 'broker' CONF_PORT = 'port'