From 90c705354aa1d6d13403869795dfda33b5fcfd49 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 19 Dec 2015 19:15:05 -0800 Subject: [PATCH 1/4] Add template support to MQTT lights --- homeassistant/components/light/mqtt.py | 41 +++++++++++++++----------- tests/components/light/test_mqtt.py | 35 +++++++++++++++++++++- 2 files changed, 58 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/light/mqtt.py b/homeassistant/components/light/mqtt.py index 30f968d758f..05de40b0b0e 100644 --- a/homeassistant/components/light/mqtt.py +++ b/homeassistant/components/light/mqtt.py @@ -6,23 +6,29 @@ Allows to configure a MQTT light. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/light.mqtt/ """ +from functools import partial import logging import homeassistant.components.mqtt as mqtt from homeassistant.components.light import (Light, ATTR_BRIGHTNESS, ATTR_RGB_COLOR) +from homeassistant.util.template import render_with_possible_json_value _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = "MQTT Light" +DEFAULT_NAME = 'MQTT Light' DEFAULT_QOS = 0 -DEFAULT_PAYLOAD_ON = "on" -DEFAULT_PAYLOAD_OFF = "off" +DEFAULT_PAYLOAD_ON = 'ON' +DEFAULT_PAYLOAD_OFF = 'OFF' DEFAULT_OPTIMISTIC = False DEPENDENCIES = ['mqtt'] -# pylint: disable=unused-argument +CONF_TOPICS = [typ + topic + for typ in ('', 'brightness_', 'rgb_') + for topic in ('state_topic', 'command_topic')] +CONF_VALUE_TEMPLATES = [typ + '_value_template' + for typ in ('state', 'brightness', 'rgb')] def setup_platform(hass, config, add_devices_callback, discovery_info=None): @@ -35,18 +41,13 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): add_devices_callback([MqttLight( hass, config.get('name', DEFAULT_NAME), - { - "state_topic": config.get('state_topic'), - "command_topic": config.get('command_topic'), - "brightness_state_topic": config.get('brightness_state_topic'), - "brightness_command_topic": config.get('brightness_command_topic'), - "rgb_state_topic": config.get('rgb_state_topic'), - "rgb_command_topic": config.get('rgb_command_topic') - }, + {key: config.get(key) for key in CONF_TOPICS}, + {key: config.get(key + '_value_template') + for key in ('state', 'brightness', 'rgb')}, config.get('qos', DEFAULT_QOS), { - "on": config.get('payload_on', DEFAULT_PAYLOAD_ON), - "off": config.get('payload_off', DEFAULT_PAYLOAD_OFF) + 'on': config.get('payload_on', DEFAULT_PAYLOAD_ON), + 'off': config.get('payload_off', DEFAULT_PAYLOAD_OFF) }, config.get('optimistic', DEFAULT_OPTIMISTIC))]) @@ -55,7 +56,7 @@ class MqttLight(Light): """ Provides a MQTT light. """ # pylint: disable=too-many-arguments,too-many-instance-attributes - def __init__(self, hass, name, topic, qos, payload, optimistic): + def __init__(self, hass, name, topic, templates, qos, payload, optimistic): self._hass = hass self._name = name @@ -68,8 +69,13 @@ class MqttLight(Light): topic["brightness_state_topic"] is None) self._state = False + templates = {key: ((lambda value: value) if tpl is None else + partial(render_with_possible_json_value, hass, tpl)) + for key, tpl in templates.items()} + def state_received(topic, payload, qos): """ A new MQTT message has been received. """ + payload = templates['state'](payload) if payload == self._payload["on"]: self._state = True elif payload == self._payload["off"]: @@ -83,7 +89,7 @@ class MqttLight(Light): def brightness_received(topic, payload, qos): """ A new MQTT message for the brightness has been received. """ - self._brightness = int(payload) + self._brightness = int(templates['brightness'](payload)) self.update_ha_state() if self._topic["brightness_state_topic"] is not None: @@ -95,7 +101,8 @@ class MqttLight(Light): def rgb_received(topic, payload, qos): """ A new MQTT message has been received. """ - self._rgb = [int(val) for val in payload.split(',')] + self._rgb = [int(val) for val in + templates['rgb'](payload).split(',')] self.update_ha_state() if self._topic["rgb_state_topic"] is not None: diff --git a/tests/components/light/test_mqtt.py b/tests/components/light/test_mqtt.py index 8172a6c7c63..321d904fc87 100644 --- a/tests/components/light/test_mqtt.py +++ b/tests/components/light/test_mqtt.py @@ -77,7 +77,7 @@ class TestLightMQTT(unittest.TestCase): self.assertIsNone(state.attributes.get('rgb_color')) self.assertIsNone(state.attributes.get('brightness')) - fire_mqtt_message(self.hass, 'test_light_rgb/status', 'on') + fire_mqtt_message(self.hass, 'test_light_rgb/status', 'ON') self.hass.pool.block_till_done() state = self.hass.states.get('light.test') @@ -143,6 +143,39 @@ class TestLightMQTT(unittest.TestCase): self.assertEqual([125, 125, 125], light_state.attributes.get('rgb_color')) + def test_controlling_state_via_topic_with_templates(self): + self.assertTrue(light.setup(self.hass, { + 'light': { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'test_light_rgb/status', + 'command_topic': 'test_light_rgb/set', + 'brightness_state_topic': 'test_light_rgb/brightness/status', + 'rgb_state_topic': 'test_light_rgb/rgb/status', + 'state_value_template': '{{ value_json.hello }}', + 'brightness_value_template': '{{ value_json.hello }}', + 'rgb_value_template': '{{ value_json.hello | join(",") }}', + } + })) + + state = self.hass.states.get('light.test') + self.assertEqual(STATE_OFF, state.state) + self.assertIsNone(state.attributes.get('brightness')) + self.assertIsNone(state.attributes.get('rgb_color')) + + fire_mqtt_message(self.hass, 'test_light_rgb/status', + '{"hello": "ON"}') + fire_mqtt_message(self.hass, 'test_light_rgb/brightness/status', + '{"hello": "50"}') + fire_mqtt_message(self.hass, 'test_light_rgb/rgb/status', + '{"hello": [1, 2, 3]}') + self.hass.pool.block_till_done() + + state = self.hass.states.get('light.test') + self.assertEqual(STATE_ON, state.state) + self.assertEqual(50, state.attributes.get('brightness')) + self.assertEqual([1, 2, 3], state.attributes.get('rgb_color')) + def test_sending_mqtt_commands_and_optimistic(self): self.assertTrue(light.setup(self.hass, { 'light': { From 7bb07cdc0324039db5d1a566acc286824571d250 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 19 Dec 2015 19:26:37 -0800 Subject: [PATCH 2/4] Remove MQTT light from coveragerc --- .coveragerc | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/.coveragerc b/.coveragerc index d39169439d6..4287c8e1fce 100644 --- a/.coveragerc +++ b/.coveragerc @@ -44,24 +44,23 @@ omit = homeassistant/components/device_tracker/geofancy.py homeassistant/components/device_tracker/icloud.py homeassistant/components/device_tracker/luci.py - homeassistant/components/device_tracker/ubus.py homeassistant/components/device_tracker/netgear.py homeassistant/components/device_tracker/nmap_tracker.py homeassistant/components/device_tracker/owntracks.py + homeassistant/components/device_tracker/snmp.py homeassistant/components/device_tracker/thomson.py homeassistant/components/device_tracker/tomato.py homeassistant/components/device_tracker/tplink.py - homeassistant/components/device_tracker/snmp.py + homeassistant/components/device_tracker/ubus.py homeassistant/components/discovery.py homeassistant/components/downloader.py homeassistant/components/ifttt.py homeassistant/components/influxdb.py homeassistant/components/keyboard.py - homeassistant/components/light/hue.py - homeassistant/components/light/mqtt.py - homeassistant/components/light/limitlessled.py homeassistant/components/light/blinksticklight.py + homeassistant/components/light/hue.py homeassistant/components/light/hyperion.py + homeassistant/components/light/limitlessled.py homeassistant/components/media_player/cast.py homeassistant/components/media_player/denon.py homeassistant/components/media_player/firetv.py @@ -69,9 +68,8 @@ omit = homeassistant/components/media_player/kodi.py homeassistant/components/media_player/mpd.py homeassistant/components/media_player/plex.py - homeassistant/components/media_player/squeezebox.py homeassistant/components/media_player/sonos.py - homeassistant/components/notify/file.py + homeassistant/components/media_player/squeezebox.py homeassistant/components/notify/instapush.py homeassistant/components/notify/nma.py homeassistant/components/notify/pushbullet.py @@ -84,7 +82,6 @@ omit = homeassistant/components/notify/xmpp.py homeassistant/components/sensor/arest.py homeassistant/components/sensor/bitcoin.py - homeassistant/components/sensor/command_sensor.py homeassistant/components/sensor/cpuspeed.py homeassistant/components/sensor/dht.py homeassistant/components/sensor/dweet.py @@ -105,7 +102,6 @@ omit = homeassistant/components/sensor/twitch.py homeassistant/components/sensor/worldclock.py homeassistant/components/switch/arest.py - homeassistant/components/switch/command_switch.py homeassistant/components/switch/edimax.py homeassistant/components/switch/hikvisioncam.py homeassistant/components/switch/mystrom.py @@ -114,11 +110,11 @@ omit = homeassistant/components/switch/rpi_gpio.py homeassistant/components/switch/transmission.py homeassistant/components/switch/wemo.py + homeassistant/components/thermostat/heatmiser.py homeassistant/components/thermostat/homematic.py homeassistant/components/thermostat/honeywell.py homeassistant/components/thermostat/nest.py homeassistant/components/thermostat/radiotherm.py - homeassistant/components/thermostat/heatmiser.py [report] From 1c08923ffcd2e4292aeb73c9ec8bf2533980713c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 19 Dec 2015 19:36:38 -0800 Subject: [PATCH 3/4] Last test for 100% coverage of mqtt light --- tests/components/light/test_mqtt.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/components/light/test_mqtt.py b/tests/components/light/test_mqtt.py index 321d904fc87..552c4f1e028 100644 --- a/tests/components/light/test_mqtt.py +++ b/tests/components/light/test_mqtt.py @@ -62,6 +62,15 @@ class TestLightMQTT(unittest.TestCase): """ Stop down stuff we started. """ self.hass.stop() + def test_fail_setup_if_no_command_topic(self): + self.assertTrue(light.setup(self.hass, { + 'light': { + 'platform': 'mqtt', + 'name': 'test', + } + })) + self.assertIsNone(self.hass.states.get('light.test')) + def test_no_color_or_brightness_if_no_topics(self): self.assertTrue(light.setup(self.hass, { 'light': { From 177590fd65b3daa4aad77e3297fd0b92ae74f04c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 19 Dec 2015 19:45:24 -0800 Subject: [PATCH 4/4] Simplify MQTT light code --- homeassistant/components/light/mqtt.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/light/mqtt.py b/homeassistant/components/light/mqtt.py index 05de40b0b0e..3493c04db1b 100644 --- a/homeassistant/components/light/mqtt.py +++ b/homeassistant/components/light/mqtt.py @@ -24,12 +24,6 @@ DEFAULT_OPTIMISTIC = False DEPENDENCIES = ['mqtt'] -CONF_TOPICS = [typ + topic - for typ in ('', 'brightness_', 'rgb_') - for topic in ('state_topic', 'command_topic')] -CONF_VALUE_TEMPLATES = [typ + '_value_template' - for typ in ('state', 'brightness', 'rgb')] - def setup_platform(hass, config, add_devices_callback, discovery_info=None): """ Add MQTT Light. """ @@ -41,7 +35,10 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): add_devices_callback([MqttLight( hass, config.get('name', DEFAULT_NAME), - {key: config.get(key) for key in CONF_TOPICS}, + {key: config.get(key) for key in + (typ + topic + for typ in ('', 'brightness_', 'rgb_') + for topic in ('state_topic', 'command_topic'))}, {key: config.get(key + '_value_template') for key in ('state', 'brightness', 'rgb')}, config.get('qos', DEFAULT_QOS),