From 31f189da825de82388b5612237b056824ce197c0 Mon Sep 17 00:00:00 2001 From: Matt White Date: Mon, 11 Sep 2017 14:08:12 -0600 Subject: [PATCH] Added mqtt_statestream component (#9286) * Added mqtt_statestream component * Added tests for mqtt_statestream component * mqtt_statestream: add test for valid new_state * mqtt_statestream: Don't set initialized state * mqtt_statestream: Switch to using async_track_state_change * Cleanup --- homeassistant/components/mqtt_statestream.py | 45 ++++++++++++++ tests/components/test_mqtt_statestream.py | 65 ++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 homeassistant/components/mqtt_statestream.py create mode 100644 tests/components/test_mqtt_statestream.py diff --git a/homeassistant/components/mqtt_statestream.py b/homeassistant/components/mqtt_statestream.py new file mode 100644 index 00000000000..76154e4ab58 --- /dev/null +++ b/homeassistant/components/mqtt_statestream.py @@ -0,0 +1,45 @@ +""" +Publish simple item state changes via MQTT. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/mqtt_statestream/ +""" +import asyncio + +import voluptuous as vol + +from homeassistant.const import MATCH_ALL +from homeassistant.core import callback +from homeassistant.components.mqtt import valid_publish_topic +from homeassistant.helpers.event import async_track_state_change + +CONF_BASE_TOPIC = 'base_topic' +DEPENDENCIES = ['mqtt'] +DOMAIN = 'mqtt_statestream' + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_BASE_TOPIC): valid_publish_topic + }) +}, extra=vol.ALLOW_EXTRA) + + +@asyncio.coroutine +def async_setup(hass, config): + """Set up the MQTT state feed.""" + conf = config.get(DOMAIN, {}) + base_topic = conf.get(CONF_BASE_TOPIC) + if not base_topic.endswith('/'): + base_topic = base_topic + '/' + + @callback + def _state_publisher(entity_id, old_state, new_state): + if new_state is None: + return + payload = new_state.state + + topic = base_topic + entity_id.replace('.', '/') + hass.components.mqtt.async_publish(topic, payload, 1, True) + + async_track_state_change(hass, MATCH_ALL, _state_publisher) + return True diff --git a/tests/components/test_mqtt_statestream.py b/tests/components/test_mqtt_statestream.py new file mode 100644 index 00000000000..73e2dbd1ac4 --- /dev/null +++ b/tests/components/test_mqtt_statestream.py @@ -0,0 +1,65 @@ +"""The tests for the MQTT statestream component.""" +from unittest.mock import patch + +from homeassistant.setup import setup_component +import homeassistant.components.mqtt_statestream as statestream +from homeassistant.core import State + +from tests.common import ( + get_test_home_assistant, + mock_mqtt_component, + mock_state_change_event +) + + +class TestMqttStateStream(object): + """Test the MQTT statestream module.""" + + def setup_method(self): + """Setup things to be run when tests are started.""" + self.hass = get_test_home_assistant() + self.mock_mqtt = mock_mqtt_component(self.hass) + + def teardown_method(self): + """Stop everything that was started.""" + self.hass.stop() + + def add_statestream(self, base_topic=None): + """Add a mqtt_statestream component.""" + config = {} + if base_topic: + config['base_topic'] = base_topic + return setup_component(self.hass, statestream.DOMAIN, { + statestream.DOMAIN: config}) + + def test_fails_with_no_base(self): + """Setup should fail if no base_topic is set.""" + assert self.add_statestream() is False + + def test_setup_succeeds(self): + """"Test the success of the setup with a valid base_topic.""" + assert self.add_statestream(base_topic='pub') + + @patch('homeassistant.components.mqtt.async_publish') + @patch('homeassistant.core.dt_util.utcnow') + def test_state_changed_event_sends_message(self, mock_utcnow, mock_pub): + """"Test the sending of a new message if event changed.""" + e_id = 'fake.entity' + base_topic = 'pub' + + # Add the statestream component for publishing state updates + assert self.add_statestream(base_topic=base_topic) + self.hass.block_till_done() + + # Reset the mock because it will have already gotten calls for the + # mqtt_statestream state change on initialization, etc. + mock_pub.reset_mock() + + # Set a state of an entity + mock_state_change_event(self.hass, State(e_id, 'on')) + self.hass.block_till_done() + + # Make sure 'on' was published to pub/fake/entity + mock_pub.assert_called_with(self.hass, 'pub/fake/entity', 'on', 1, + True) + assert mock_pub.called