diff --git a/homeassistant/components/light/mqtt/__init__.py b/homeassistant/components/light/mqtt/__init__.py new file mode 100644 index 00000000000..93f32cd2791 --- /dev/null +++ b/homeassistant/components/light/mqtt/__init__.py @@ -0,0 +1,72 @@ +""" +Support for MQTT lights. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/light.mqtt/ +""" +import logging + +import voluptuous as vol + +from homeassistant.components import light +from homeassistant.components.mqtt import ATTR_DISCOVERY_HASH +from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.typing import HomeAssistantType, ConfigType + +from . import schema_basic +from . import schema_json +from . import schema_template + +_LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = ['mqtt'] + +CONF_SCHEMA = 'schema' + + +def validate_mqtt_light(value): + """Validate MQTT light schema.""" + schemas = { + 'basic': schema_basic.PLATFORM_SCHEMA_BASIC, + 'json': schema_json.PLATFORM_SCHEMA_JSON, + 'template': schema_template.PLATFORM_SCHEMA_TEMPLATE, + } + return schemas[value[CONF_SCHEMA]](value) + + +PLATFORM_SCHEMA = vol.All(vol.Schema({ + vol.Optional(CONF_SCHEMA, default='basic'): vol.All( + vol.Lower, vol.Any('basic', 'json', 'template')) +}, extra=vol.ALLOW_EXTRA), validate_mqtt_light) + + +async def async_setup_platform(hass: HomeAssistantType, config: ConfigType, + async_add_entities, discovery_info=None): + """Set up MQTT light through configuration.yaml.""" + await _async_setup_entity(hass, config, async_add_entities) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up MQTT light dynamically through MQTT discovery.""" + async def async_discover(discovery_payload): + """Discover and add a MQTT light.""" + config = PLATFORM_SCHEMA(discovery_payload) + await _async_setup_entity(hass, config, async_add_entities, + discovery_payload[ATTR_DISCOVERY_HASH]) + + async_dispatcher_connect( + hass, MQTT_DISCOVERY_NEW.format(light.DOMAIN, 'mqtt'), + async_discover) + + +async def _async_setup_entity(hass, config, async_add_entities, + discovery_hash=None): + """Set up a MQTT Light.""" + setup_entity = { + 'basic': schema_basic.async_setup_entity_basic, + 'json': schema_json.async_setup_entity_json, + 'template': schema_template.async_setup_entity_template, + } + await setup_entity[config['schema']]( + hass, config, async_add_entities, discovery_hash) diff --git a/homeassistant/components/light/mqtt.py b/homeassistant/components/light/mqtt/schema_basic.py similarity index 94% rename from homeassistant/components/light/mqtt.py rename to homeassistant/components/light/mqtt/schema_basic.py index 92030c8617a..6c7b0e75301 100644 --- a/homeassistant/components/light/mqtt.py +++ b/homeassistant/components/light/mqtt/schema_basic.py @@ -9,7 +9,7 @@ import logging import voluptuous as vol from homeassistant.core import callback -from homeassistant.components import mqtt, light +from homeassistant.components import mqtt from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_HS_COLOR, ATTR_WHITE_VALUE, Light, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, @@ -19,13 +19,10 @@ from homeassistant.const import ( CONF_OPTIMISTIC, CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, STATE_ON, CONF_RGB, CONF_STATE, CONF_VALUE_TEMPLATE, CONF_WHITE_VALUE, CONF_XY) from homeassistant.components.mqtt import ( - ATTR_DISCOVERY_HASH, CONF_AVAILABILITY_TOPIC, CONF_COMMAND_TOPIC, - CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_RETAIN, - CONF_STATE_TOPIC, MqttAvailability, MqttDiscoveryUpdate) -from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW + CONF_AVAILABILITY_TOPIC, CONF_COMMAND_TOPIC, CONF_PAYLOAD_AVAILABLE, + CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, + MqttAvailability, MqttDiscoveryUpdate) from homeassistant.helpers.restore_state import async_get_last_state -from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.typing import HomeAssistantType, ConfigType import homeassistant.helpers.config_validation as cv import homeassistant.util.color as color_util @@ -72,7 +69,7 @@ DEFAULT_ON_COMMAND_TYPE = 'last' VALUES_ON_COMMAND_TYPE = ['first', 'last', 'brightness'] -PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({ +PLATFORM_SCHEMA_BASIC = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({ vol.Optional(CONF_BRIGHTNESS_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_BRIGHTNESS_SCALE, default=DEFAULT_BRIGHTNESS_SCALE): vol.All(vol.Coerce(int), vol.Range(min=1)), @@ -111,27 +108,8 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({ }).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) -async def async_setup_platform(hass: HomeAssistantType, config: ConfigType, - async_add_entities, discovery_info=None): - """Set up MQTT light through configuration.yaml.""" - await _async_setup_entity(hass, config, async_add_entities) - - -async def async_setup_entry(hass, config_entry, async_add_entities): - """Set up MQTT light dynamically through MQTT discovery.""" - async def async_discover(discovery_payload): - """Discover and add a MQTT light.""" - config = PLATFORM_SCHEMA(discovery_payload) - await _async_setup_entity(hass, config, async_add_entities, - discovery_payload[ATTR_DISCOVERY_HASH]) - - async_dispatcher_connect( - hass, MQTT_DISCOVERY_NEW.format(light.DOMAIN, 'mqtt'), - async_discover) - - -async def _async_setup_entity(hass, config, async_add_entities, - discovery_hash=None): +async def async_setup_entity_basic(hass, config, async_add_entities, + discovery_hash=None): """Set up a MQTT Light.""" config.setdefault( CONF_STATE_VALUE_TEMPLATE, config.get(CONF_VALUE_TEMPLATE)) @@ -688,7 +666,7 @@ class MqttLight(MqttAvailability, MqttDiscoveryUpdate, Light): should_update = True if self._optimistic: - # Optimistically assume that switch has changed state. + # Optimistically assume that the light has changed state. self._state = True should_update = True @@ -705,6 +683,6 @@ class MqttLight(MqttAvailability, MqttDiscoveryUpdate, Light): self._qos, self._retain) if self._optimistic: - # Optimistically assume that switch has changed state. + # Optimistically assume that the light has changed state. self._state = False self.async_schedule_update_ha_state() diff --git a/homeassistant/components/light/mqtt_json.py b/homeassistant/components/light/mqtt/schema_json.py similarity index 94% rename from homeassistant/components/light/mqtt_json.py rename to homeassistant/components/light/mqtt/schema_json.py index 1ed43a6385a..43e0f655f0b 100644 --- a/homeassistant/components/light/mqtt_json.py +++ b/homeassistant/components/light/mqtt/schema_json.py @@ -14,14 +14,12 @@ from homeassistant.components import mqtt from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH, ATTR_HS_COLOR, ATTR_TRANSITION, ATTR_WHITE_VALUE, FLASH_LONG, FLASH_SHORT, - PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, - SUPPORT_EFFECT, SUPPORT_FLASH, SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE, - Light) -from homeassistant.components.light.mqtt import CONF_BRIGHTNESS_SCALE + SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, + SUPPORT_FLASH, SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE, Light) from homeassistant.components.mqtt import ( - ATTR_DISCOVERY_HASH, CONF_AVAILABILITY_TOPIC, CONF_COMMAND_TOPIC, - CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_RETAIN, - CONF_STATE_TOPIC, MqttAvailability, MqttDiscoveryUpdate) + CONF_AVAILABILITY_TOPIC, CONF_COMMAND_TOPIC, CONF_PAYLOAD_AVAILABLE, + CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, + MqttAvailability, MqttDiscoveryUpdate) from homeassistant.const import ( CONF_BRIGHTNESS, CONF_COLOR_TEMP, CONF_EFFECT, CONF_NAME, CONF_OPTIMISTIC, CONF_RGB, CONF_WHITE_VALUE, CONF_XY, STATE_ON) @@ -31,6 +29,8 @@ from homeassistant.helpers.restore_state import async_get_last_state from homeassistant.helpers.typing import ConfigType, HomeAssistantType import homeassistant.util.color as color_util +from .schema_basic import CONF_BRIGHTNESS_SCALE + _LOGGER = logging.getLogger(__name__) DOMAIN = 'mqtt_json' @@ -58,7 +58,7 @@ CONF_HS = 'hs' CONF_UNIQUE_ID = 'unique_id' # Stealing some of these from the base MQTT configs. -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ +PLATFORM_SCHEMA_JSON = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({ vol.Optional(CONF_BRIGHTNESS, default=DEFAULT_BRIGHTNESS): cv.boolean, vol.Optional(CONF_BRIGHTNESS_SCALE, default=DEFAULT_BRIGHTNESS_SCALE): vol.All(vol.Coerce(int), vol.Range(min=1)), @@ -84,17 +84,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ }).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) -async def async_setup_platform(hass: HomeAssistantType, config: ConfigType, - async_add_entities, discovery_info=None): +async def async_setup_entity_json(hass: HomeAssistantType, config: ConfigType, + async_add_entities, discovery_hash): """Set up a MQTT JSON Light.""" - if discovery_info is not None: - config = PLATFORM_SCHEMA(discovery_info) - - discovery_hash = None - if discovery_info is not None and ATTR_DISCOVERY_HASH in discovery_info: - discovery_hash = discovery_info[ATTR_DISCOVERY_HASH] - - async_add_entities([MqttJson( + async_add_entities([MqttLightJson( config.get(CONF_NAME), config.get(CONF_UNIQUE_ID), config.get(CONF_EFFECT_LIST), @@ -128,7 +121,7 @@ async def async_setup_platform(hass: HomeAssistantType, config: ConfigType, )]) -class MqttJson(MqttAvailability, MqttDiscoveryUpdate, Light): +class MqttLightJson(MqttAvailability, MqttDiscoveryUpdate, Light): """Representation of a MQTT JSON light.""" def __init__(self, name, unique_id, effect_list, topic, qos, retain, diff --git a/homeassistant/components/light/mqtt_template.py b/homeassistant/components/light/mqtt/schema_template.py similarity index 97% rename from homeassistant/components/light/mqtt_template.py rename to homeassistant/components/light/mqtt/schema_template.py index 72cfd6b678c..082e4674cb9 100644 --- a/homeassistant/components/light/mqtt_template.py +++ b/homeassistant/components/light/mqtt/schema_template.py @@ -11,7 +11,7 @@ from homeassistant.core import callback from homeassistant.components import mqtt from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH, - ATTR_HS_COLOR, ATTR_TRANSITION, ATTR_WHITE_VALUE, Light, PLATFORM_SCHEMA, + ATTR_HS_COLOR, ATTR_TRANSITION, ATTR_WHITE_VALUE, Light, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, SUPPORT_FLASH, SUPPORT_COLOR, SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE) from homeassistant.const import CONF_NAME, CONF_OPTIMISTIC, STATE_ON, STATE_OFF @@ -44,7 +44,7 @@ CONF_RED_TEMPLATE = 'red_template' CONF_STATE_TEMPLATE = 'state_template' CONF_WHITE_VALUE_TEMPLATE = 'white_value_template' -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ +PLATFORM_SCHEMA_TEMPLATE = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({ vol.Optional(CONF_BLUE_TEMPLATE): cv.template, vol.Optional(CONF_BRIGHTNESS_TEMPLATE): cv.template, vol.Optional(CONF_COLOR_TEMP_TEMPLATE): cv.template, @@ -66,12 +66,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ }).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_entity_template(hass, config, async_add_entities, + discovery_hash): """Set up a MQTT Template light.""" - if discovery_info is not None: - config = PLATFORM_SCHEMA(discovery_info) - async_add_entities([MqttTemplate( hass, config.get(CONF_NAME), diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py index d91ab6ee445..9ea3151c65c 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -27,31 +27,25 @@ SUPPORTED_COMPONENTS = [ 'light', 'sensor', 'switch', 'lock', 'climate', 'alarm_control_panel'] -ALLOWED_PLATFORMS = { - 'binary_sensor': ['mqtt'], - 'camera': ['mqtt'], - 'cover': ['mqtt'], - 'fan': ['mqtt'], - 'light': ['mqtt', 'mqtt_json', 'mqtt_template'], - 'lock': ['mqtt'], - 'sensor': ['mqtt'], - 'switch': ['mqtt'], - 'climate': ['mqtt'], - 'alarm_control_panel': ['mqtt'], +CONFIG_ENTRY_COMPONENTS = [ + 'binary_sensor', + 'camera', + 'cover', + 'light', + 'lock', + 'sensor', + 'switch', + 'climate', + 'alarm_control_panel', + 'fan', +] + +DEPRECATED_PLATFORM_TO_SCHEMA = { + 'mqtt': 'basic', + 'mqtt_json': 'json', + 'mqtt_template': 'template', } -CONFIG_ENTRY_PLATFORMS = { - 'binary_sensor': ['mqtt'], - 'camera': ['mqtt'], - 'cover': ['mqtt'], - 'light': ['mqtt'], - 'lock': ['mqtt'], - 'sensor': ['mqtt'], - 'switch': ['mqtt'], - 'climate': ['mqtt'], - 'alarm_control_panel': ['mqtt'], - 'fan': ['mqtt'], -} ALREADY_DISCOVERED = 'mqtt_discovered_components' DATA_CONFIG_ENTRY_LOCK = 'mqtt_config_entry_lock' @@ -216,12 +210,15 @@ async def async_start(hass: HomeAssistantType, discovery_topic, hass_config, discovery_hash = (component, discovery_id) if payload: - platform = payload.get(CONF_PLATFORM, 'mqtt') - if platform not in ALLOWED_PLATFORMS.get(component, []): - _LOGGER.warning("Platform %s (component %s) is not allowed", - platform, component) - return - payload[CONF_PLATFORM] = platform + if CONF_PLATFORM in payload: + platform = payload[CONF_PLATFORM] + if platform in DEPRECATED_PLATFORM_TO_SCHEMA: + schema = DEPRECATED_PLATFORM_TO_SCHEMA[platform] + payload['schema'] = schema + _LOGGER.warning('"platform": "%s" is deprecated, ' + 'replace with "schema":"%s"', + platform, schema) + payload[CONF_PLATFORM] = 'mqtt' if CONF_STATE_TOPIC not in payload: payload[CONF_STATE_TOPIC] = '{}/{}/{}{}/state'.format( @@ -244,12 +241,12 @@ async def async_start(hass: HomeAssistantType, discovery_topic, hass_config, _LOGGER.info("Found new component: %s %s", component, discovery_id) hass.data[ALREADY_DISCOVERED][discovery_hash] = None - if platform not in CONFIG_ENTRY_PLATFORMS.get(component, []): + if component not in CONFIG_ENTRY_COMPONENTS: await async_load_platform( - hass, component, platform, payload, hass_config) + hass, component, 'mqtt', payload, hass_config) return - config_entries_key = '{}.{}'.format(component, platform) + config_entries_key = '{}.{}'.format(component, 'mqtt') async with hass.data[DATA_CONFIG_ENTRY_LOCK]: if config_entries_key not in hass.data[CONFIG_ENTRY_IS_SETUP]: await hass.config_entries.async_forward_entry_setup( @@ -257,7 +254,7 @@ async def async_start(hass: HomeAssistantType, discovery_topic, hass_config, hass.data[CONFIG_ENTRY_IS_SETUP].add(config_entries_key) async_dispatcher_send(hass, MQTT_DISCOVERY_NEW.format( - component, platform), payload) + component, 'mqtt'), payload) hass.data[DATA_CONFIG_ENTRY_LOCK] = asyncio.Lock() hass.data[CONFIG_ENTRY_IS_SETUP] = set() diff --git a/tests/components/light/test_mqtt.py b/tests/components/light/test_mqtt.py index f09f3726252..c56835afc9f 100644 --- a/tests/components/light/test_mqtt.py +++ b/tests/components/light/test_mqtt.py @@ -585,7 +585,8 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): 'effect': 'random', 'color_temp': 100, 'white_value': 50}) - with patch('homeassistant.components.light.mqtt.async_get_last_state', + with patch('homeassistant.components.light.mqtt.schema_basic' + '.async_get_last_state', return_value=mock_coro(fake_state)): with assert_setup_component(1, light.DOMAIN): assert await async_setup_component(hass, light.DOMAIN, config) @@ -1063,3 +1064,20 @@ async def test_discovery_removal_light(hass, mqtt_mock, caplog): state = hass.states.get('light.beer') assert state is None + + +async def test_discovery_deprecated(hass, mqtt_mock, caplog): + """Test removal of discovered mqtt_json lights.""" + entry = MockConfigEntry(domain=mqtt.DOMAIN) + await async_start(hass, 'homeassistant', {'mqtt': {}}, entry) + data = ( + '{ "name": "Beer",' + ' "platform": "mqtt",' + ' "command_topic": "test_topic"}' + ) + async_fire_mqtt_message(hass, 'homeassistant/light/bla/config', + data) + await hass.async_block_till_done() + state = hass.states.get('light.beer') + assert state is not None + assert state.name == 'Beer' diff --git a/tests/components/light/test_mqtt_json.py b/tests/components/light/test_mqtt_json.py index 03a3927472a..e509cd5718c 100644 --- a/tests/components/light/test_mqtt_json.py +++ b/tests/components/light/test_mqtt_json.py @@ -93,18 +93,19 @@ from homeassistant.setup import async_setup_component from homeassistant.const import ( STATE_ON, STATE_OFF, STATE_UNAVAILABLE, ATTR_ASSUMED_STATE, ATTR_SUPPORTED_FEATURES) -import homeassistant.components.light as light +from homeassistant.components import light, mqtt from homeassistant.components.mqtt.discovery import async_start import homeassistant.core as ha -from tests.common import mock_coro, async_fire_mqtt_message +from tests.common import mock_coro, async_fire_mqtt_message, MockConfigEntry async def test_fail_setup_if_no_command_topic(hass, mqtt_mock): """Test if setup fails with no command topic.""" assert await async_setup_component(hass, light.DOMAIN, { light.DOMAIN: { - 'platform': 'mqtt_json', + 'platform': 'mqtt', + 'schema': 'json', 'name': 'test', } }) @@ -116,7 +117,8 @@ async def test_no_color_brightness_color_temp_white_val_if_no_topics( """Test for no RGB, brightness, color temp, effect, white val or XY.""" assert await async_setup_component(hass, light.DOMAIN, { light.DOMAIN: { - 'platform': 'mqtt_json', + 'platform': 'mqtt', + 'schema': 'json', 'name': 'test', 'state_topic': 'test_light_rgb', 'command_topic': 'test_light_rgb/set', @@ -152,7 +154,8 @@ async def test_controlling_state_via_topic(hass, mqtt_mock): """Test the controlling of the state via topic.""" assert await async_setup_component(hass, light.DOMAIN, { light.DOMAIN: { - 'platform': 'mqtt_json', + 'platform': 'mqtt', + 'schema': 'json', 'name': 'test', 'state_topic': 'test_light_rgb', 'command_topic': 'test_light_rgb/set', @@ -276,12 +279,13 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): 'color_temp': 100, 'white_value': 50}) - with patch('homeassistant.components.light.mqtt_json' + with patch('homeassistant.components.light.mqtt.schema_json' '.async_get_last_state', return_value=mock_coro(fake_state)): assert await async_setup_component(hass, light.DOMAIN, { light.DOMAIN: { - 'platform': 'mqtt_json', + 'platform': 'mqtt', + 'schema': 'json', 'name': 'test', 'command_topic': 'test_light_rgb/set', 'brightness': True, @@ -308,7 +312,8 @@ async def test_sending_hs_color(hass, mqtt_mock): """Test light.turn_on with hs color sends hs color parameters.""" assert await async_setup_component(hass, light.DOMAIN, { light.DOMAIN: { - 'platform': 'mqtt_json', + 'platform': 'mqtt', + 'schema': 'json', 'name': 'test', 'command_topic': 'test_light_rgb/set', 'hs': True, @@ -323,7 +328,8 @@ async def test_flash_short_and_long(hass, mqtt_mock): """Test for flash length being sent when included.""" assert await async_setup_component(hass, light.DOMAIN, { light.DOMAIN: { - 'platform': 'mqtt_json', + 'platform': 'mqtt', + 'schema': 'json', 'name': 'test', 'state_topic': 'test_light_rgb', 'command_topic': 'test_light_rgb/set', @@ -342,7 +348,8 @@ async def test_transition(hass, mqtt_mock): """Test for transition time being sent when included.""" assert await async_setup_component(hass, light.DOMAIN, { light.DOMAIN: { - 'platform': 'mqtt_json', + 'platform': 'mqtt', + 'schema': 'json', 'name': 'test', 'state_topic': 'test_light_rgb', 'command_topic': 'test_light_rgb/set', @@ -359,7 +366,8 @@ async def test_brightness_scale(hass, mqtt_mock): """Test for brightness scaling.""" assert await async_setup_component(hass, light.DOMAIN, { light.DOMAIN: { - 'platform': 'mqtt_json', + 'platform': 'mqtt', + 'schema': 'json', 'name': 'test', 'state_topic': 'test_light_bright_scale', 'command_topic': 'test_light_bright_scale/set', @@ -395,7 +403,8 @@ async def test_invalid_color_brightness_and_white_values(hass, mqtt_mock): """Test that invalid color/brightness/white values are ignored.""" assert await async_setup_component(hass, light.DOMAIN, { light.DOMAIN: { - 'platform': 'mqtt_json', + 'platform': 'mqtt', + 'schema': 'json', 'name': 'test', 'state_topic': 'test_light_rgb', 'command_topic': 'test_light_rgb/set', @@ -466,7 +475,8 @@ async def test_default_availability_payload(hass, mqtt_mock): """Test availability by default payload with defined topic.""" assert await async_setup_component(hass, light.DOMAIN, { light.DOMAIN: { - 'platform': 'mqtt_json', + 'platform': 'mqtt', + 'schema': 'json', 'name': 'test', 'state_topic': 'test_light_rgb', 'command_topic': 'test_light_rgb/set', @@ -495,7 +505,8 @@ async def test_custom_availability_payload(hass, mqtt_mock): """Test availability by custom payload with defined topic.""" assert await async_setup_component(hass, light.DOMAIN, { light.DOMAIN: { - 'platform': 'mqtt_json', + 'platform': 'mqtt', + 'schema': 'json', 'name': 'test', 'state_topic': 'test_light_rgb', 'command_topic': 'test_light_rgb/set', @@ -524,10 +535,11 @@ async def test_custom_availability_payload(hass, mqtt_mock): async def test_discovery_removal(hass, mqtt_mock, caplog): """Test removal of discovered mqtt_json lights.""" - await async_start(hass, 'homeassistant', {'mqtt': {}}) + entry = MockConfigEntry(domain=mqtt.DOMAIN) + await async_start(hass, 'homeassistant', {'mqtt': {}}, entry) data = ( '{ "name": "Beer",' - ' "platform": "mqtt_json",' + ' "schema": "json",' ' "command_topic": "test_topic" }' ) async_fire_mqtt_message(hass, 'homeassistant/light/bla/config', @@ -542,3 +554,20 @@ async def test_discovery_removal(hass, mqtt_mock, caplog): await hass.async_block_till_done() state = hass.states.get('light.beer') assert state is None + + +async def test_discovery_deprecated(hass, mqtt_mock, caplog): + """Test removal of discovered mqtt_json lights.""" + entry = MockConfigEntry(domain=mqtt.DOMAIN) + await async_start(hass, 'homeassistant', {'mqtt': {}}, entry) + data = ( + '{ "name": "Beer",' + ' "platform": "mqtt_json",' + ' "command_topic": "test_topic"}' + ) + async_fire_mqtt_message(hass, 'homeassistant/light/bla/config', + data) + await hass.async_block_till_done() + state = hass.states.get('light.beer') + assert state is not None + assert state.name == 'Beer' diff --git a/tests/components/light/test_mqtt_template.py b/tests/components/light/test_mqtt_template.py index 6bc0b4536ea..0d26d6edb12 100644 --- a/tests/components/light/test_mqtt_template.py +++ b/tests/components/light/test_mqtt_template.py @@ -31,11 +31,13 @@ from unittest.mock import patch from homeassistant.setup import async_setup_component from homeassistant.const import ( STATE_ON, STATE_OFF, STATE_UNAVAILABLE, ATTR_ASSUMED_STATE) -import homeassistant.components.light as light +from homeassistant.components import light, mqtt +from homeassistant.components.mqtt.discovery import async_start import homeassistant.core as ha from tests.common import ( - async_fire_mqtt_message, assert_setup_component, mock_coro) + async_fire_mqtt_message, assert_setup_component, mock_coro, + MockConfigEntry) async def test_setup_fails(hass, mqtt_mock): @@ -43,19 +45,56 @@ async def test_setup_fails(hass, mqtt_mock): with assert_setup_component(0, light.DOMAIN): assert await async_setup_component(hass, light.DOMAIN, { light.DOMAIN: { - 'platform': 'mqtt_template', + 'platform': 'mqtt', + 'schema': 'template', 'name': 'test', } }) assert hass.states.get('light.test') is None + with assert_setup_component(0, light.DOMAIN): + assert await async_setup_component(hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt', + 'schema': 'template', + 'name': 'test', + 'command_topic': 'test_topic', + } + }) + assert hass.states.get('light.test') is None + + with assert_setup_component(0, light.DOMAIN): + assert await async_setup_component(hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt', + 'schema': 'template', + 'name': 'test', + 'command_topic': 'test_topic', + 'command_on_template': 'on', + } + }) + assert hass.states.get('light.test') is None + + with assert_setup_component(0, light.DOMAIN): + assert await async_setup_component(hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt', + 'schema': 'template', + 'name': 'test', + 'command_topic': 'test_topic', + 'command_off_template': 'off', + } + }) + assert hass.states.get('light.test') is None + async def test_state_change_via_topic(hass, mqtt_mock): """Test state change via topic.""" with assert_setup_component(1, light.DOMAIN): assert await async_setup_component(hass, light.DOMAIN, { light.DOMAIN: { - 'platform': 'mqtt_template', + 'platform': 'mqtt', + 'schema': 'template', 'name': 'test', 'state_topic': 'test_light_rgb', 'command_topic': 'test_light_rgb/set', @@ -96,7 +135,8 @@ async def test_state_brightness_color_effect_temp_white_change_via_topic( with assert_setup_component(1, light.DOMAIN): assert await async_setup_component(hass, light.DOMAIN, { light.DOMAIN: { - 'platform': 'mqtt_template', + 'platform': 'mqtt', + 'schema': 'template', 'name': 'test', 'effect_list': ['rainbow', 'colorloop'], 'state_topic': 'test_light_rgb', @@ -205,13 +245,14 @@ async def test_optimistic(hass, mqtt_mock): 'color_temp': 100, 'white_value': 50}) - with patch('homeassistant.components.light.mqtt_template' + with patch('homeassistant.components.light.mqtt.schema_template' '.async_get_last_state', return_value=mock_coro(fake_state)): with assert_setup_component(1, light.DOMAIN): assert await async_setup_component(hass, light.DOMAIN, { light.DOMAIN: { - 'platform': 'mqtt_template', + 'platform': 'mqtt', + 'schema': 'template', 'name': 'test', 'command_topic': 'test_light_rgb/set', 'command_on_template': 'on,' @@ -243,7 +284,8 @@ async def test_flash(hass, mqtt_mock): with assert_setup_component(1, light.DOMAIN): assert await async_setup_component(hass, light.DOMAIN, { light.DOMAIN: { - 'platform': 'mqtt_template', + 'platform': 'mqtt', + 'schema': 'template', 'name': 'test', 'command_topic': 'test_light_rgb/set', 'command_on_template': 'on,{{ flash }}', @@ -261,7 +303,8 @@ async def test_transition(hass, mqtt_mock): with assert_setup_component(1, light.DOMAIN): assert await async_setup_component(hass, light.DOMAIN, { light.DOMAIN: { - 'platform': 'mqtt_template', + 'platform': 'mqtt', + 'schema': 'template', 'name': 'test', 'command_topic': 'test_light_rgb/set', 'command_on_template': 'on,{{ transition }}', @@ -278,7 +321,8 @@ async def test_invalid_values(hass, mqtt_mock): with assert_setup_component(1, light.DOMAIN): assert await async_setup_component(hass, light.DOMAIN, { light.DOMAIN: { - 'platform': 'mqtt_template', + 'platform': 'mqtt', + 'schema': 'template', 'name': 'test', 'effect_list': ['rainbow', 'colorloop'], 'state_topic': 'test_light_rgb', @@ -380,7 +424,8 @@ async def test_default_availability_payload(hass, mqtt_mock): """Test availability by default payload with defined topic.""" assert await async_setup_component(hass, light.DOMAIN, { light.DOMAIN: { - 'platform': 'mqtt_template', + 'platform': 'mqtt', + 'schema': 'template', 'name': 'test', 'command_topic': 'test_light_rgb/set', 'command_on_template': 'on,{{ transition }}', @@ -410,7 +455,8 @@ async def test_custom_availability_payload(hass, mqtt_mock): """Test availability by custom payload with defined topic.""" assert await async_setup_component(hass, light.DOMAIN, { light.DOMAIN: { - 'platform': 'mqtt_template', + 'platform': 'mqtt', + 'schema': 'template', 'name': 'test', 'command_topic': 'test_light_rgb/set', 'command_on_template': 'on,{{ transition }}', @@ -436,3 +482,41 @@ async def test_custom_availability_payload(hass, mqtt_mock): state = hass.states.get('light.test') assert STATE_UNAVAILABLE == state.state + + +async def test_discovery(hass, mqtt_mock, caplog): + """Test removal of discovered mqtt_json lights.""" + entry = MockConfigEntry(domain=mqtt.DOMAIN) + await async_start(hass, 'homeassistant', {'mqtt': {}}, entry) + data = ( + '{ "name": "Beer",' + ' "schema": "template",' + ' "command_topic": "test_topic",' + ' "command_on_template": "on",' + ' "command_off_template": "off"}' + ) + async_fire_mqtt_message(hass, 'homeassistant/light/bla/config', + data) + await hass.async_block_till_done() + state = hass.states.get('light.beer') + assert state is not None + assert state.name == 'Beer' + + +async def test_discovery_deprecated(hass, mqtt_mock, caplog): + """Test removal of discovered mqtt_json lights.""" + entry = MockConfigEntry(domain=mqtt.DOMAIN) + await async_start(hass, 'homeassistant', {'mqtt': {}}, entry) + data = ( + '{ "name": "Beer",' + ' "platform": "mqtt_template",' + ' "command_topic": "test_topic",' + ' "command_on_template": "on",' + ' "command_off_template": "off"}' + ) + async_fire_mqtt_message(hass, 'homeassistant/light/bla/config', + data) + await hass.async_block_till_done() + state = hass.states.get('light.beer') + assert state is not None + assert state.name == 'Beer' diff --git a/tests/scripts/test_check_config.py b/tests/scripts/test_check_config.py index 28438a5e4b3..217f26e71f7 100644 --- a/tests/scripts/test_check_config.py +++ b/tests/scripts/test_check_config.py @@ -79,45 +79,6 @@ class TestCheckConfig(unittest.TestCase): assert res['secrets'] == {} assert len(res['yaml_files']) == 1 - @patch('os.path.isfile', return_value=True) - def test_config_component_platform_fail_validation(self, isfile_patch): - """Test errors if component & platform not found.""" - files = { - YAML_CONFIG_FILE: BASE_CONFIG + 'http:\n password: err123', - } - with patch_yaml_files(files): - res = check_config.check(get_test_config_dir()) - assert res['components'].keys() == {'homeassistant'} - assert res['except'].keys() == {'http'} - assert res['except']['http'][1] == {'http': {'password': 'err123'}} - assert res['secret_cache'] == {} - assert res['secrets'] == {} - assert len(res['yaml_files']) == 1 - - files = { - YAML_CONFIG_FILE: (BASE_CONFIG + 'mqtt:\n\n' - 'light:\n platform: mqtt_json'), - } - with patch_yaml_files(files): - res = check_config.check(get_test_config_dir()) - assert res['components'].keys() == { - 'homeassistant', 'light', 'mqtt'} - assert res['components']['light'] == [] - assert res['components']['mqtt'] == { - 'keepalive': 60, - 'port': 1883, - 'protocol': '3.1.1', - 'discovery': False, - 'discovery_prefix': 'homeassistant', - 'tls_version': 'auto', - } - assert res['except'].keys() == {'light.mqtt_json'} - assert res['except']['light.mqtt_json'][1] == { - 'platform': 'mqtt_json'} - assert res['secret_cache'] == {} - assert res['secrets'] == {} - assert len(res['yaml_files']) == 1 - @patch('os.path.isfile', return_value=True) def test_component_platform_not_found(self, isfile_patch): """Test errors if component or platform not found."""