Link manually added MQTT entities the the MQTT config entry (#78547)

Co-authored-by: Erik <erik@montnemery.com>
pull/78704/head
Jan Bouwhuis 2022-09-18 18:55:31 +02:00 committed by GitHub
parent 49ead219a5
commit 354411feed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 117 additions and 105 deletions

View File

@ -30,6 +30,7 @@ from homeassistant.helpers import (
)
from homeassistant.helpers.device_registry import DeviceEntry
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import async_get_platforms
from homeassistant.helpers.reload import (
async_integration_yaml_config,
async_reload_integration_platforms,
@ -75,7 +76,7 @@ from .const import ( # noqa: F401
PLATFORMS,
RELOADABLE_PLATFORMS,
)
from .mixins import MqttData, async_discover_yaml_entities
from .mixins import MqttData
from .models import ( # noqa: F401
MqttCommandTemplate,
MqttValueTemplate,
@ -422,13 +423,25 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await async_reload_integration_platforms(hass, DOMAIN, RELOADABLE_PLATFORMS)
# Reload the modern yaml platforms
mqtt_platforms = async_get_platforms(hass, DOMAIN)
tasks = [
entity.async_remove()
for mqtt_platform in mqtt_platforms
for entity in mqtt_platform.entities.values()
if not entity._discovery_data # type: ignore[attr-defined] # pylint: disable=protected-access
if mqtt_platform.config_entry
and mqtt_platform.domain in RELOADABLE_PLATFORMS
]
await asyncio.gather(*tasks)
config_yaml = await async_integration_yaml_config(hass, DOMAIN) or {}
mqtt_data.updated_config = config_yaml.get(DOMAIN, {})
await asyncio.gather(
*(
[
async_discover_yaml_entities(hass, component)
mqtt_data.reload_handlers[component]()
for component in RELOADABLE_PLATFORMS
if component in mqtt_data.reload_handlers
]
)
)

View File

@ -44,7 +44,6 @@ from .debug_info import log_messages
from .mixins import (
MQTT_ENTITY_COMMON_SCHEMA,
MqttEntity,
async_discover_yaml_entities,
async_setup_entry_helper,
async_setup_platform_helper,
warn_for_legacy_schema,
@ -146,9 +145,6 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up MQTT alarm control panel through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
await async_discover_yaml_entities(hass, alarm.DOMAIN)
# setup for discovery
setup = functools.partial(
_async_setup_entity, hass, async_add_entities, config_entry=config_entry
)

View File

@ -42,7 +42,6 @@ from .mixins import (
MQTT_ENTITY_COMMON_SCHEMA,
MqttAvailability,
MqttEntity,
async_discover_yaml_entities,
async_setup_entry_helper,
async_setup_platform_helper,
warn_for_legacy_schema,
@ -102,9 +101,6 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up MQTT binary sensor through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
await async_discover_yaml_entities(hass, binary_sensor.DOMAIN)
# setup for discovery
setup = functools.partial(
_async_setup_entity, hass, async_add_entities, config_entry=config_entry
)

View File

@ -25,7 +25,6 @@ from .const import (
from .mixins import (
MQTT_ENTITY_COMMON_SCHEMA,
MqttEntity,
async_discover_yaml_entities,
async_setup_entry_helper,
async_setup_platform_helper,
warn_for_legacy_schema,
@ -81,9 +80,6 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up MQTT button through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
await async_discover_yaml_entities(hass, button.DOMAIN)
# setup for discovery
setup = functools.partial(
_async_setup_entity, hass, async_add_entities, config_entry=config_entry
)

View File

@ -23,7 +23,6 @@ from .debug_info import log_messages
from .mixins import (
MQTT_ENTITY_COMMON_SCHEMA,
MqttEntity,
async_discover_yaml_entities,
async_setup_entry_helper,
async_setup_platform_helper,
warn_for_legacy_schema,
@ -105,9 +104,6 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up MQTT camera through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
await async_discover_yaml_entities(hass, camera.DOMAIN)
# setup for discovery
setup = functools.partial(
_async_setup_entity, hass, async_add_entities, config_entry=config_entry
)

View File

@ -50,7 +50,6 @@ from .debug_info import log_messages
from .mixins import (
MQTT_ENTITY_COMMON_SCHEMA,
MqttEntity,
async_discover_yaml_entities,
async_setup_entry_helper,
async_setup_platform_helper,
warn_for_legacy_schema,
@ -350,9 +349,6 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up MQTT climate device through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
await async_discover_yaml_entities(hass, climate.DOMAIN)
# setup for discovery
setup = functools.partial(
_async_setup_entity, hass, async_add_entities, config_entry=config_entry
)

View File

@ -46,7 +46,6 @@ from .debug_info import log_messages
from .mixins import (
MQTT_ENTITY_COMMON_SCHEMA,
MqttEntity,
async_discover_yaml_entities,
async_setup_entry_helper,
async_setup_platform_helper,
warn_for_legacy_schema,
@ -242,9 +241,6 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up MQTT cover through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
await async_discover_yaml_entities(hass, cover.DOMAIN)
# setup for discovery
setup = functools.partial(
_async_setup_entity, hass, async_add_entities, config_entry=config_entry
)

View File

@ -50,7 +50,6 @@ from .debug_info import log_messages
from .mixins import (
MQTT_ENTITY_COMMON_SCHEMA,
MqttEntity,
async_discover_yaml_entities,
async_setup_entry_helper,
async_setup_platform_helper,
warn_for_legacy_schema,
@ -241,9 +240,6 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up MQTT fan through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
await async_discover_yaml_entities(hass, fan.DOMAIN)
# setup for discovery
setup = functools.partial(
_async_setup_entity, hass, async_add_entities, config_entry=config_entry
)

View File

@ -46,7 +46,6 @@ from .debug_info import log_messages
from .mixins import (
MQTT_ENTITY_COMMON_SCHEMA,
MqttEntity,
async_discover_yaml_entities,
async_setup_entry_helper,
async_setup_platform_helper,
warn_for_legacy_schema,
@ -187,9 +186,6 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up MQTT humidifier through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
await async_discover_yaml_entities(hass, humidifier.DOMAIN)
# setup for discovery
setup = functools.partial(
_async_setup_entity, hass, async_add_entities, config_entry=config_entry
)

View File

@ -14,7 +14,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from ..mixins import (
async_discover_yaml_entities,
async_setup_entry_helper,
async_setup_platform_helper,
warn_for_legacy_schema,
@ -111,9 +110,6 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up MQTT lights configured under the light platform key (deprecated)."""
# load and initialize platform config from configuration.yaml
await async_discover_yaml_entities(hass, light.DOMAIN)
# setup for discovery
setup = functools.partial(
_async_setup_entity, hass, async_add_entities, config_entry=config_entry
)

View File

@ -28,7 +28,6 @@ from .debug_info import log_messages
from .mixins import (
MQTT_ENTITY_COMMON_SCHEMA,
MqttEntity,
async_discover_yaml_entities,
async_setup_entry_helper,
async_setup_platform_helper,
warn_for_legacy_schema,
@ -102,9 +101,6 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up MQTT lock through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
await async_discover_yaml_entities(hass, lock.DOMAIN)
# setup for discovery
setup = functools.partial(
_async_setup_entity, hass, async_add_entities, config_entry=config_entry
)

View File

@ -39,7 +39,6 @@ from homeassistant.core import (
from homeassistant.helpers import (
config_validation as cv,
device_registry as dr,
discovery,
entity_registry as er,
)
from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED
@ -286,6 +285,9 @@ class MqttData:
last_discovery: float = 0.0
reload_dispatchers: list[CALLBACK_TYPE] = field(default_factory=list)
reload_entry: bool = False
reload_handlers: dict[str, Callable[[], Coroutine[Any, Any, None]]] = field(
default_factory=dict
)
reload_needed: bool = False
subscriptions_to_restore: list[Subscription] = field(default_factory=list)
updated_config: ConfigType = field(default_factory=dict)
@ -305,30 +307,6 @@ class SetupEntity(Protocol):
"""Define setup_entities type."""
async def async_discover_yaml_entities(
hass: HomeAssistant, platform_domain: str
) -> None:
"""Discover entities for a platform."""
mqtt_data: MqttData = hass.data[DATA_MQTT]
if mqtt_data.updated_config:
# The platform has been reloaded
config_yaml = mqtt_data.updated_config
else:
config_yaml = mqtt_data.config or {}
if not config_yaml:
return
if platform_domain not in config_yaml:
return
await asyncio.gather(
*(
discovery.async_load_platform(hass, platform_domain, DOMAIN, config, {})
for config in await async_get_platform_config_from_yaml(
hass, platform_domain, config_yaml
)
)
)
async def async_get_platform_config_from_yaml(
hass: HomeAssistant,
platform_domain: str,
@ -350,7 +328,7 @@ async def async_setup_entry_helper(
hass: HomeAssistant,
domain: str,
async_setup: partial[Coroutine[HomeAssistant, str, None]],
schema: vol.Schema,
discovery_schema: vol.Schema,
) -> None:
"""Set up entity, automation or tag creation dynamically through MQTT discovery."""
mqtt_data: MqttData = hass.data[DATA_MQTT]
@ -367,7 +345,7 @@ async def async_setup_entry_helper(
return
discovery_data = discovery_payload.discovery_data
try:
config = schema(discovery_payload)
config = discovery_schema(discovery_payload)
await async_setup(config, discovery_data=discovery_data)
except Exception:
discovery_hash = discovery_data[ATTR_DISCOVERY_HASH]
@ -383,6 +361,31 @@ async def async_setup_entry_helper(
)
)
async def _async_setup_entities() -> None:
"""Set up MQTT items from configuration.yaml."""
mqtt_data: MqttData = hass.data[DATA_MQTT]
if mqtt_data.updated_config:
# The platform has been reloaded
config_yaml = mqtt_data.updated_config
else:
config_yaml = mqtt_data.config or {}
if not config_yaml:
return
if domain not in config_yaml:
return
await asyncio.gather(
*[
async_setup(config)
for config in await async_get_platform_config_from_yaml(
hass, domain, config_yaml
)
]
)
# discover manual configured MQTT items
mqtt_data.reload_handlers[domain] = _async_setup_entities
await _async_setup_entities()
async def async_setup_platform_helper(
hass: HomeAssistant,

View File

@ -44,7 +44,6 @@ from .debug_info import log_messages
from .mixins import (
MQTT_ENTITY_COMMON_SCHEMA,
MqttEntity,
async_discover_yaml_entities,
async_setup_entry_helper,
async_setup_platform_helper,
warn_for_legacy_schema,
@ -138,9 +137,6 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up MQTT number through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
await async_discover_yaml_entities(hass, number.DOMAIN)
# setup for discovery
setup = functools.partial(
_async_setup_entity, hass, async_add_entities, config_entry=config_entry
)

View File

@ -23,7 +23,6 @@ from .mixins import (
CONF_OBJECT_ID,
MQTT_AVAILABILITY_SCHEMA,
MqttEntity,
async_discover_yaml_entities,
async_setup_entry_helper,
async_setup_platform_helper,
warn_for_legacy_schema,
@ -79,9 +78,6 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up MQTT scene through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
await async_discover_yaml_entities(hass, scene.DOMAIN)
# setup for discovery
setup = functools.partial(
_async_setup_entity, hass, async_add_entities, config_entry=config_entry
)

View File

@ -30,7 +30,6 @@ from .debug_info import log_messages
from .mixins import (
MQTT_ENTITY_COMMON_SCHEMA,
MqttEntity,
async_discover_yaml_entities,
async_setup_entry_helper,
async_setup_platform_helper,
warn_for_legacy_schema,
@ -93,9 +92,6 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up MQTT select through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
await async_discover_yaml_entities(hass, select.DOMAIN)
# setup for discovery
setup = functools.partial(
_async_setup_entity, hass, async_add_entities, config_entry=config_entry
)

View File

@ -1,7 +1,7 @@
"""Support for MQTT sensors."""
from __future__ import annotations
from datetime import timedelta
from datetime import datetime, timedelta
import functools
import logging
@ -30,7 +30,7 @@ from homeassistant.core import HomeAssistant, callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType
from homeassistant.util import dt as dt_util
from . import subscription
@ -41,7 +41,6 @@ from .mixins import (
MQTT_ENTITY_COMMON_SCHEMA,
MqttAvailability,
MqttEntity,
async_discover_yaml_entities,
async_setup_entry_helper,
async_setup_platform_helper,
warn_for_legacy_schema,
@ -146,9 +145,6 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up MQTT sensor through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
await async_discover_yaml_entities(hass, sensor.DOMAIN)
# setup for discovery
setup = functools.partial(
_async_setup_entity, hass, async_add_entities, config_entry=config_entry
)
@ -347,12 +343,12 @@ class MqttSensor(MqttEntity, RestoreSensor):
self.async_write_ha_state()
@property
def native_unit_of_measurement(self):
def native_unit_of_measurement(self) -> str | None:
"""Return the unit this state is expressed in."""
return self._config.get(CONF_UNIT_OF_MEASUREMENT)
@property
def native_value(self):
def native_value(self) -> StateType | datetime:
"""Return the state of the entity."""
return self._state

View File

@ -49,7 +49,6 @@ from .debug_info import log_messages
from .mixins import (
MQTT_ENTITY_COMMON_SCHEMA,
MqttEntity,
async_discover_yaml_entities,
async_setup_entry_helper,
async_setup_platform_helper,
warn_for_legacy_schema,
@ -140,9 +139,6 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up MQTT siren through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
await async_discover_yaml_entities(hass, siren.DOMAIN)
# setup for discovery
setup = functools.partial(
_async_setup_entity, hass, async_add_entities, config_entry=config_entry
)

View File

@ -42,7 +42,6 @@ from .debug_info import log_messages
from .mixins import (
MQTT_ENTITY_COMMON_SCHEMA,
MqttEntity,
async_discover_yaml_entities,
async_setup_entry_helper,
async_setup_platform_helper,
warn_for_legacy_schema,
@ -101,9 +100,6 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up MQTT switch through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
await async_discover_yaml_entities(hass, switch.DOMAIN)
# setup for discovery
setup = functools.partial(
_async_setup_entity, hass, async_add_entities, config_entry=config_entry
)

View File

@ -11,11 +11,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from ..mixins import (
async_discover_yaml_entities,
async_setup_entry_helper,
async_setup_platform_helper,
)
from ..mixins import async_setup_entry_helper, async_setup_platform_helper
from .schema import CONF_SCHEMA, LEGACY, MQTT_VACUUM_SCHEMA, STATE
from .schema_legacy import (
DISCOVERY_SCHEMA_LEGACY,
@ -90,9 +86,6 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up MQTT vacuum through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
await async_discover_yaml_entities(hass, vacuum.DOMAIN)
# setup for discovery
setup = functools.partial(
_async_setup_entity, hass, async_add_entities, config_entry=config_entry
)

View File

@ -29,11 +29,13 @@ from homeassistant.core import CoreState, HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr, template
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_platform import async_get_platforms
from homeassistant.setup import async_setup_component
from homeassistant.util.dt import utcnow
from .test_common import (
help_test_entry_reload_with_new_config,
help_test_reload_with_config,
help_test_setup_manual_entity_from_yaml,
)
@ -2986,3 +2988,68 @@ async def test_remove_unknown_conf_entry_options(hass, mqtt_client_mock, caplog)
"MQTT config entry: {'protocol'}. Add them to configuration.yaml if they "
"are needed"
) in caplog.text
@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.LIGHT])
async def test_link_config_entry(hass, tmp_path, caplog):
"""Test manual and dynamically setup entities are linked to the config entry."""
config_manual = {
"mqtt": {
"light": [
{
"name": "test_manual",
"unique_id": "test_manual_unique_id123",
"command_topic": "test-topic_manual",
}
]
}
}
config_discovery = {
"name": "test_discovery",
"unique_id": "test_discovery_unique456",
"command_topic": "test-topic_discovery",
}
# set up manual item
await help_test_setup_manual_entity_from_yaml(hass, config_manual)
# set up item through discovery
async_fire_mqtt_message(
hass, "homeassistant/light/bla/config", json.dumps(config_discovery)
)
await hass.async_block_till_done()
assert hass.states.get("light.test_manual") is not None
assert hass.states.get("light.test_discovery") is not None
entity_names = ["test_manual", "test_discovery"]
# Check if both entities were linked to the MQTT config entry
mqtt_config_entry = hass.config_entries.async_entries(mqtt.DOMAIN)[0]
mqtt_platforms = async_get_platforms(hass, mqtt.DOMAIN)
def _check_entities():
entities = []
for mqtt_platform in mqtt_platforms:
assert mqtt_platform.config_entry is mqtt_config_entry
entities += (entity for entity in mqtt_platform.entities.values())
for entity in entities:
assert entity.name in entity_names
return len(entities)
assert _check_entities() == 2
# reload entry and assert again
await help_test_entry_reload_with_new_config(hass, tmp_path, config_manual)
# manual set up item should remain
assert _check_entities() == 1
# set up item through discovery
async_fire_mqtt_message(
hass, "homeassistant/light/bla/config", json.dumps(config_discovery)
)
await hass.async_block_till_done()
assert _check_entities() == 2
# reload manual configured items and assert again
await help_test_reload_with_config(hass, caplog, tmp_path, config_manual)
assert _check_entities() == 2