Make MQTT platforms config entries (#16904)

* Make MQTT platforms config entries

* Fix tests

* Address Comment

* Rework tests

* Undo style auto-reformat style changes
This commit is contained in:
Otto Winter 2018-09-27 16:07:56 +02:00 committed by Paulus Schoutsen
parent 8d65230a36
commit d1ad2cc225
4 changed files with 87 additions and 17 deletions

View File

@ -13,6 +13,7 @@ from homeassistant.components.mqtt import CONF_STATE_TOPIC, ATTR_DISCOVERY_HASH
from homeassistant.const import CONF_PLATFORM from homeassistant.const import CONF_PLATFORM
from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.typing import HomeAssistantType
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -38,11 +39,18 @@ ALLOWED_PLATFORMS = {
'alarm_control_panel': ['mqtt'], 'alarm_control_panel': ['mqtt'],
} }
CONFIG_ENTRY_PLATFORMS = {
'sensor': ['mqtt'],
}
ALREADY_DISCOVERED = 'mqtt_discovered_components' ALREADY_DISCOVERED = 'mqtt_discovered_components'
CONFIG_ENTRY_IS_SETUP = 'mqtt_config_entry_is_setup'
MQTT_DISCOVERY_UPDATED = 'mqtt_discovery_updated_{}' 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.""" """Initialize of MQTT Discovery."""
async def async_device_message_received(topic, payload, qos): async def async_device_message_received(topic, payload, qos):
"""Process the received message.""" """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) _LOGGER.info("Found new component: %s %s", component, discovery_id)
await async_load_platform( if platform not in CONFIG_ENTRY_PLATFORMS.get(component, []):
hass, component, platform, payload, hass_config) 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( await mqtt.async_subscribe(
hass, discovery_topic + '/#', async_device_message_received, 0) hass, discovery_topic + '/#', async_device_message_received, 0)

View File

@ -12,10 +12,12 @@ from typing import Optional
import voluptuous as vol import voluptuous as vol
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.components import sensor
from homeassistant.components.mqtt import ( from homeassistant.components.mqtt import (
ATTR_DISCOVERY_HASH, CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC, ATTR_DISCOVERY_HASH, CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC,
CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS,
MqttAvailability, MqttDiscoveryUpdate) MqttAvailability, MqttDiscoveryUpdate)
from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW
from homeassistant.components.sensor import DEVICE_CLASSES_SCHEMA from homeassistant.components.sensor import DEVICE_CLASSES_SCHEMA
from homeassistant.const import ( from homeassistant.const import (
CONF_FORCE_UPDATE, CONF_NAME, CONF_VALUE_TEMPLATE, STATE_UNKNOWN, 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 from homeassistant.components import mqtt
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import HomeAssistantType, ConfigType 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.helpers.event import async_track_point_in_utc_time
from homeassistant.util import dt as dt_util 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 def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
async_add_entities, discovery_info=None): 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: if discovery_info is not None:
config = PLATFORM_SCHEMA(discovery_info) config = PLATFORM_SCHEMA(discovery_info)

View File

@ -2,18 +2,23 @@
import asyncio import asyncio
from unittest.mock import patch from unittest.mock import patch
from homeassistant.components import mqtt
from homeassistant.components.mqtt.discovery import async_start, \ from homeassistant.components.mqtt.discovery import async_start, \
ALREADY_DISCOVERED 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 @asyncio.coroutine
def test_subscribing_config_topic(hass, mqtt_mock): def test_subscribing_config_topic(hass, mqtt_mock):
"""Test setting up discovery.""" """Test setting up discovery."""
entry = MockConfigEntry(domain=mqtt.DOMAIN, data={
mqtt.CONF_BROKER: 'test-broker'
})
hass_config = {} hass_config = {}
discovery_topic = 'homeassistant' 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 assert mqtt_mock.async_subscribe.called
call_args = mqtt_mock.async_subscribe.mock_calls[0][1] call_args = mqtt_mock.async_subscribe.mock_calls[0][1]
@ -25,8 +30,12 @@ def test_subscribing_config_topic(hass, mqtt_mock):
@asyncio.coroutine @asyncio.coroutine
def test_invalid_topic(mock_load_platform, hass, mqtt_mock): def test_invalid_topic(mock_load_platform, hass, mqtt_mock):
"""Test sending to invalid topic.""" """Test sending to invalid topic."""
entry = MockConfigEntry(domain=mqtt.DOMAIN, data={
mqtt.CONF_BROKER: 'test-broker'
})
mock_load_platform.return_value = mock_coro() 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', 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 @asyncio.coroutine
def test_invalid_json(mock_load_platform, hass, mqtt_mock, caplog): def test_invalid_json(mock_load_platform, hass, mqtt_mock, caplog):
"""Test sending in invalid JSON.""" """Test sending in invalid JSON."""
entry = MockConfigEntry(domain=mqtt.DOMAIN, data={
mqtt.CONF_BROKER: 'test-broker'
})
mock_load_platform.return_value = mock_coro() 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', async_fire_mqtt_message(hass, 'homeassistant/binary_sensor/bla/config',
'not json') 'not json')
@ -52,10 +65,12 @@ def test_invalid_json(mock_load_platform, hass, mqtt_mock, caplog):
@asyncio.coroutine @asyncio.coroutine
def test_only_valid_components(mock_load_platform, hass, mqtt_mock, caplog): def test_only_valid_components(mock_load_platform, hass, mqtt_mock, caplog):
"""Test for a valid component.""" """Test for a valid component."""
entry = MockConfigEntry(domain=mqtt.DOMAIN)
invalid_component = "timer" invalid_component = "timer"
mock_load_platform.return_value = mock_coro() 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( async_fire_mqtt_message(hass, 'homeassistant/{}/bla/config'.format(
invalid_component invalid_component
@ -73,7 +88,9 @@ def test_only_valid_components(mock_load_platform, hass, mqtt_mock, caplog):
@asyncio.coroutine @asyncio.coroutine
def test_correct_config_discovery(hass, mqtt_mock, caplog): def test_correct_config_discovery(hass, mqtt_mock, caplog):
"""Test sending in correct JSON.""" """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', async_fire_mqtt_message(hass, 'homeassistant/binary_sensor/bla/config',
'{ "name": "Beer" }') '{ "name": "Beer" }')
@ -89,7 +106,9 @@ def test_correct_config_discovery(hass, mqtt_mock, caplog):
@asyncio.coroutine @asyncio.coroutine
def test_discover_fan(hass, mqtt_mock, caplog): def test_discover_fan(hass, mqtt_mock, caplog):
"""Test discovering an MQTT fan.""" """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', async_fire_mqtt_message(hass, 'homeassistant/fan/bla/config',
('{ "name": "Beer",' ('{ "name": "Beer",'
@ -106,7 +125,9 @@ def test_discover_fan(hass, mqtt_mock, caplog):
@asyncio.coroutine @asyncio.coroutine
def test_discover_climate(hass, mqtt_mock, caplog): def test_discover_climate(hass, mqtt_mock, caplog):
"""Test discovering an MQTT climate component.""" """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 = ( data = (
'{ "name": "ClimateTest",' '{ "name": "ClimateTest",'
@ -127,7 +148,9 @@ def test_discover_climate(hass, mqtt_mock, caplog):
@asyncio.coroutine @asyncio.coroutine
def test_discover_alarm_control_panel(hass, mqtt_mock, caplog): def test_discover_alarm_control_panel(hass, mqtt_mock, caplog):
"""Test discovering an MQTT alarm control panel component.""" """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 = ( data = (
'{ "name": "AlarmControlPanelTest",' '{ "name": "AlarmControlPanelTest",'
@ -149,7 +172,9 @@ def test_discover_alarm_control_panel(hass, mqtt_mock, caplog):
@asyncio.coroutine @asyncio.coroutine
def test_discovery_incl_nodeid(hass, mqtt_mock, caplog): def test_discovery_incl_nodeid(hass, mqtt_mock, caplog):
"""Test sending in correct JSON with optional node_id included.""" """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' async_fire_mqtt_message(hass, 'homeassistant/binary_sensor/my_node_id/bla'
'/config', '{ "name": "Beer" }') '/config', '{ "name": "Beer" }')
@ -165,7 +190,9 @@ def test_discovery_incl_nodeid(hass, mqtt_mock, caplog):
@asyncio.coroutine @asyncio.coroutine
def test_non_duplicate_discovery(hass, mqtt_mock, caplog): def test_non_duplicate_discovery(hass, mqtt_mock, caplog):
"""Test for a non duplicate component.""" """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', async_fire_mqtt_message(hass, 'homeassistant/binary_sensor/bla/config',
'{ "name": "Beer" }') '{ "name": "Beer" }')

View File

@ -6,6 +6,7 @@ from unittest.mock import patch
import homeassistant.core as ha import homeassistant.core as ha
from homeassistant.setup import setup_component, async_setup_component from homeassistant.setup import setup_component, async_setup_component
from homeassistant.components import mqtt
from homeassistant.components.mqtt.discovery import async_start from homeassistant.components.mqtt.discovery import async_start
import homeassistant.components.sensor as sensor import homeassistant.components.sensor as sensor
from homeassistant.const import EVENT_STATE_CHANGED, STATE_UNAVAILABLE 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, \ from tests.common import mock_mqtt_component, fire_mqtt_message, \
assert_setup_component, async_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 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): async def test_discovery_removal_sensor(hass, mqtt_mock, caplog):
"""Test removal of discovered sensor.""" """Test removal of discovered sensor."""
await async_start(hass, 'homeassistant', {}) entry = MockConfigEntry(domain=mqtt.DOMAIN)
await async_start(hass, 'homeassistant', {}, entry)
data = ( data = (
'{ "name": "Beer",' '{ "name": "Beer",'
' "status_topic": "test_topic" }' ' "status_topic": "test_topic" }'