From d1ad2cc22536df7ff65c578f054685a608077dc3 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 27 Sep 2018 16:07:56 +0200 Subject: [PATCH] Make MQTT platforms config entries (#16904) * Make MQTT platforms config entries * Fix tests * Address Comment * Rework tests * Undo style auto-reformat style changes --- homeassistant/components/mqtt/discovery.py | 27 ++++++++++-- homeassistant/components/sensor/mqtt.py | 22 +++++++++- tests/components/mqtt/test_discovery.py | 49 +++++++++++++++++----- tests/components/sensor/test_mqtt.py | 6 ++- 4 files changed, 87 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py index f42c1ed58e9..d5ed5e25b47 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -13,6 +13,7 @@ from homeassistant.components.mqtt import CONF_STATE_TOPIC, ATTR_DISCOVERY_HASH from homeassistant.const import CONF_PLATFORM from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.typing import HomeAssistantType _LOGGER = logging.getLogger(__name__) @@ -38,11 +39,18 @@ ALLOWED_PLATFORMS = { 'alarm_control_panel': ['mqtt'], } +CONFIG_ENTRY_PLATFORMS = { + 'sensor': ['mqtt'], +} + ALREADY_DISCOVERED = 'mqtt_discovered_components' +CONFIG_ENTRY_IS_SETUP = 'mqtt_config_entry_is_setup' MQTT_DISCOVERY_UPDATED = 'mqtt_discovery_updated_{}' +MQTT_DISCOVERY_NEW = 'mqtt_discovery_new_{}_{}' -async def async_start(hass, discovery_topic, hass_config): +async def async_start(hass: HomeAssistantType, discovery_topic, hass_config, + config_entry=None) -> bool: """Initialize of MQTT Discovery.""" async def async_device_message_received(topic, payload, qos): """Process the received message.""" @@ -98,8 +106,21 @@ async def async_start(hass, discovery_topic, hass_config): _LOGGER.info("Found new component: %s %s", component, discovery_id) - await async_load_platform( - hass, component, platform, payload, hass_config) + if platform not in CONFIG_ENTRY_PLATFORMS.get(component, []): + await async_load_platform( + hass, component, platform, payload, hass_config) + return + + config_entries_key = '{}.{}'.format(component, platform) + if config_entries_key not in hass.data[CONFIG_ENTRY_IS_SETUP]: + await hass.config_entries.async_forward_entry_setup( + config_entry, component) + hass.data[CONFIG_ENTRY_IS_SETUP].add(config_entries_key) + + async_dispatcher_send(hass, MQTT_DISCOVERY_NEW.format( + component, platform), payload) + + hass.data[CONFIG_ENTRY_IS_SETUP] = set() await mqtt.async_subscribe( hass, discovery_topic + '/#', async_device_message_received, 0) diff --git a/homeassistant/components/sensor/mqtt.py b/homeassistant/components/sensor/mqtt.py index 56e4055ea87..2d15fca755b 100644 --- a/homeassistant/components/sensor/mqtt.py +++ b/homeassistant/components/sensor/mqtt.py @@ -12,10 +12,12 @@ from typing import Optional import voluptuous as vol from homeassistant.core import callback +from homeassistant.components import sensor from homeassistant.components.mqtt import ( ATTR_DISCOVERY_HASH, CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC, CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, MqttAvailability, MqttDiscoveryUpdate) +from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW from homeassistant.components.sensor import DEVICE_CLASSES_SCHEMA from homeassistant.const import ( CONF_FORCE_UPDATE, CONF_NAME, CONF_VALUE_TEMPLATE, STATE_UNKNOWN, @@ -24,6 +26,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.components import mqtt import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import HomeAssistantType, ConfigType +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.util import dt as dt_util @@ -53,7 +56,24 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({ async def async_setup_platform(hass: HomeAssistantType, config: ConfigType, async_add_entities, discovery_info=None): - """Set up MQTT Sensor.""" + """Set up MQTT sensors through configuration.yaml.""" + await _async_setup_platform(hass, config, async_add_entities, + discovery_info) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up MQTT sensors dynamically through MQTT discovery.""" + async def async_discover_sensor(config): + """Discover and add a discovered MQTT sensor.""" + await _async_setup_platform(hass, {}, async_add_entities, config) + + async_dispatcher_connect(hass, + MQTT_DISCOVERY_NEW.format(sensor.DOMAIN, 'mqtt'), + async_discover_sensor) + + +async def _async_setup_platform(hass: HomeAssistantType, config: ConfigType, + async_add_entities, discovery_info=None): if discovery_info is not None: config = PLATFORM_SCHEMA(discovery_info) diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py index 9e0ef14a3fa..36b022de7a6 100644 --- a/tests/components/mqtt/test_discovery.py +++ b/tests/components/mqtt/test_discovery.py @@ -2,18 +2,23 @@ import asyncio from unittest.mock import patch +from homeassistant.components import mqtt from homeassistant.components.mqtt.discovery import async_start, \ ALREADY_DISCOVERED -from tests.common import async_fire_mqtt_message, mock_coro +from tests.common import async_fire_mqtt_message, mock_coro, MockConfigEntry @asyncio.coroutine def test_subscribing_config_topic(hass, mqtt_mock): """Test setting up discovery.""" + entry = MockConfigEntry(domain=mqtt.DOMAIN, data={ + mqtt.CONF_BROKER: 'test-broker' + }) + hass_config = {} discovery_topic = 'homeassistant' - yield from async_start(hass, discovery_topic, hass_config) + yield from async_start(hass, discovery_topic, hass_config, entry) assert mqtt_mock.async_subscribe.called call_args = mqtt_mock.async_subscribe.mock_calls[0][1] @@ -25,8 +30,12 @@ def test_subscribing_config_topic(hass, mqtt_mock): @asyncio.coroutine def test_invalid_topic(mock_load_platform, hass, mqtt_mock): """Test sending to invalid topic.""" + entry = MockConfigEntry(domain=mqtt.DOMAIN, data={ + mqtt.CONF_BROKER: 'test-broker' + }) + mock_load_platform.return_value = mock_coro() - yield from async_start(hass, 'homeassistant', {}) + yield from async_start(hass, 'homeassistant', {}, entry) async_fire_mqtt_message(hass, 'homeassistant/binary_sensor/bla/not_config', '{}') @@ -38,8 +47,12 @@ def test_invalid_topic(mock_load_platform, hass, mqtt_mock): @asyncio.coroutine def test_invalid_json(mock_load_platform, hass, mqtt_mock, caplog): """Test sending in invalid JSON.""" + entry = MockConfigEntry(domain=mqtt.DOMAIN, data={ + mqtt.CONF_BROKER: 'test-broker' + }) + mock_load_platform.return_value = mock_coro() - yield from async_start(hass, 'homeassistant', {}) + yield from async_start(hass, 'homeassistant', {}, entry) async_fire_mqtt_message(hass, 'homeassistant/binary_sensor/bla/config', 'not json') @@ -52,10 +65,12 @@ def test_invalid_json(mock_load_platform, hass, mqtt_mock, caplog): @asyncio.coroutine def test_only_valid_components(mock_load_platform, hass, mqtt_mock, caplog): """Test for a valid component.""" + entry = MockConfigEntry(domain=mqtt.DOMAIN) + invalid_component = "timer" mock_load_platform.return_value = mock_coro() - yield from async_start(hass, 'homeassistant', {}) + yield from async_start(hass, 'homeassistant', {}, entry) async_fire_mqtt_message(hass, 'homeassistant/{}/bla/config'.format( invalid_component @@ -73,7 +88,9 @@ def test_only_valid_components(mock_load_platform, hass, mqtt_mock, caplog): @asyncio.coroutine def test_correct_config_discovery(hass, mqtt_mock, caplog): """Test sending in correct JSON.""" - yield from async_start(hass, 'homeassistant', {}) + entry = MockConfigEntry(domain=mqtt.DOMAIN) + + yield from async_start(hass, 'homeassistant', {}, entry) async_fire_mqtt_message(hass, 'homeassistant/binary_sensor/bla/config', '{ "name": "Beer" }') @@ -89,7 +106,9 @@ def test_correct_config_discovery(hass, mqtt_mock, caplog): @asyncio.coroutine def test_discover_fan(hass, mqtt_mock, caplog): """Test discovering an MQTT fan.""" - yield from async_start(hass, 'homeassistant', {}) + entry = MockConfigEntry(domain=mqtt.DOMAIN) + + yield from async_start(hass, 'homeassistant', {}, entry) async_fire_mqtt_message(hass, 'homeassistant/fan/bla/config', ('{ "name": "Beer",' @@ -106,7 +125,9 @@ def test_discover_fan(hass, mqtt_mock, caplog): @asyncio.coroutine def test_discover_climate(hass, mqtt_mock, caplog): """Test discovering an MQTT climate component.""" - yield from async_start(hass, 'homeassistant', {}) + entry = MockConfigEntry(domain=mqtt.DOMAIN) + + yield from async_start(hass, 'homeassistant', {}, entry) data = ( '{ "name": "ClimateTest",' @@ -127,7 +148,9 @@ def test_discover_climate(hass, mqtt_mock, caplog): @asyncio.coroutine def test_discover_alarm_control_panel(hass, mqtt_mock, caplog): """Test discovering an MQTT alarm control panel component.""" - yield from async_start(hass, 'homeassistant', {}) + entry = MockConfigEntry(domain=mqtt.DOMAIN) + + yield from async_start(hass, 'homeassistant', {}, entry) data = ( '{ "name": "AlarmControlPanelTest",' @@ -149,7 +172,9 @@ def test_discover_alarm_control_panel(hass, mqtt_mock, caplog): @asyncio.coroutine def test_discovery_incl_nodeid(hass, mqtt_mock, caplog): """Test sending in correct JSON with optional node_id included.""" - yield from async_start(hass, 'homeassistant', {}) + entry = MockConfigEntry(domain=mqtt.DOMAIN) + + yield from async_start(hass, 'homeassistant', {}, entry) async_fire_mqtt_message(hass, 'homeassistant/binary_sensor/my_node_id/bla' '/config', '{ "name": "Beer" }') @@ -165,7 +190,9 @@ def test_discovery_incl_nodeid(hass, mqtt_mock, caplog): @asyncio.coroutine def test_non_duplicate_discovery(hass, mqtt_mock, caplog): """Test for a non duplicate component.""" - yield from async_start(hass, 'homeassistant', {}) + entry = MockConfigEntry(domain=mqtt.DOMAIN) + + yield from async_start(hass, 'homeassistant', {}, entry) async_fire_mqtt_message(hass, 'homeassistant/binary_sensor/bla/config', '{ "name": "Beer" }') diff --git a/tests/components/sensor/test_mqtt.py b/tests/components/sensor/test_mqtt.py index 527c92997ec..4f70c37e04f 100644 --- a/tests/components/sensor/test_mqtt.py +++ b/tests/components/sensor/test_mqtt.py @@ -6,6 +6,7 @@ from unittest.mock import patch import homeassistant.core as ha from homeassistant.setup import setup_component, async_setup_component +from homeassistant.components import mqtt from homeassistant.components.mqtt.discovery import async_start import homeassistant.components.sensor as sensor from homeassistant.const import EVENT_STATE_CHANGED, STATE_UNAVAILABLE @@ -13,7 +14,7 @@ import homeassistant.util.dt as dt_util from tests.common import mock_mqtt_component, fire_mqtt_message, \ assert_setup_component, async_fire_mqtt_message, \ - async_mock_mqtt_component + async_mock_mqtt_component, MockConfigEntry from tests.common import get_test_home_assistant, mock_component @@ -392,7 +393,8 @@ async def test_unique_id(hass): async def test_discovery_removal_sensor(hass, mqtt_mock, caplog): """Test removal of discovered sensor.""" - await async_start(hass, 'homeassistant', {}) + entry = MockConfigEntry(domain=mqtt.DOMAIN) + await async_start(hass, 'homeassistant', {}, entry) data = ( '{ "name": "Beer",' ' "status_topic": "test_topic" }'