Fix MQTT config schema to ensure correct validation (#73619)
* Ensure config schema validation * Use correct schema for device_tracker * Remove schema validation from the platform setup * Remove loop to build schema
This commit is contained in:
parent
fcd8859542
commit
57daeaa174
25 changed files with 258 additions and 176 deletions
|
@ -47,7 +47,11 @@ from .client import ( # noqa: F401
|
||||||
publish,
|
publish,
|
||||||
subscribe,
|
subscribe,
|
||||||
)
|
)
|
||||||
from .config import CONFIG_SCHEMA_BASE, DEFAULT_VALUES, DEPRECATED_CONFIG_KEYS
|
from .config_integration import (
|
||||||
|
CONFIG_SCHEMA_BASE,
|
||||||
|
DEFAULT_VALUES,
|
||||||
|
DEPRECATED_CONFIG_KEYS,
|
||||||
|
)
|
||||||
from .const import ( # noqa: F401
|
from .const import ( # noqa: F401
|
||||||
ATTR_PAYLOAD,
|
ATTR_PAYLOAD,
|
||||||
ATTR_QOS,
|
ATTR_QOS,
|
||||||
|
|
|
@ -148,7 +148,7 @@ async def async_setup_entry(
|
||||||
"""Set up MQTT alarm control panel through configuration.yaml and dynamically through MQTT discovery."""
|
"""Set up MQTT alarm control panel through configuration.yaml and dynamically through MQTT discovery."""
|
||||||
# load and initialize platform config from configuration.yaml
|
# load and initialize platform config from configuration.yaml
|
||||||
config_entry.async_on_unload(
|
config_entry.async_on_unload(
|
||||||
await async_setup_platform_discovery(hass, alarm.DOMAIN, PLATFORM_SCHEMA_MODERN)
|
await async_setup_platform_discovery(hass, alarm.DOMAIN)
|
||||||
)
|
)
|
||||||
# setup for discovery
|
# setup for discovery
|
||||||
setup = functools.partial(
|
setup = functools.partial(
|
||||||
|
|
|
@ -103,9 +103,7 @@ async def async_setup_entry(
|
||||||
"""Set up MQTT binary sensor through configuration.yaml and dynamically through MQTT discovery."""
|
"""Set up MQTT binary sensor through configuration.yaml and dynamically through MQTT discovery."""
|
||||||
# load and initialize platform config from configuration.yaml
|
# load and initialize platform config from configuration.yaml
|
||||||
config_entry.async_on_unload(
|
config_entry.async_on_unload(
|
||||||
await async_setup_platform_discovery(
|
await async_setup_platform_discovery(hass, binary_sensor.DOMAIN)
|
||||||
hass, binary_sensor.DOMAIN, PLATFORM_SCHEMA_MODERN
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
# setup for discovery
|
# setup for discovery
|
||||||
setup = functools.partial(
|
setup = functools.partial(
|
||||||
|
|
|
@ -83,9 +83,7 @@ async def async_setup_entry(
|
||||||
"""Set up MQTT button through configuration.yaml and dynamically through MQTT discovery."""
|
"""Set up MQTT button through configuration.yaml and dynamically through MQTT discovery."""
|
||||||
# load and initialize platform config from configuration.yaml
|
# load and initialize platform config from configuration.yaml
|
||||||
config_entry.async_on_unload(
|
config_entry.async_on_unload(
|
||||||
await async_setup_platform_discovery(
|
await async_setup_platform_discovery(hass, button.DOMAIN)
|
||||||
hass, button.DOMAIN, PLATFORM_SCHEMA_MODERN
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
# setup for discovery
|
# setup for discovery
|
||||||
setup = functools.partial(
|
setup = functools.partial(
|
||||||
|
|
|
@ -81,9 +81,7 @@ async def async_setup_entry(
|
||||||
"""Set up MQTT camera through configuration.yaml and dynamically through MQTT discovery."""
|
"""Set up MQTT camera through configuration.yaml and dynamically through MQTT discovery."""
|
||||||
# load and initialize platform config from configuration.yaml
|
# load and initialize platform config from configuration.yaml
|
||||||
config_entry.async_on_unload(
|
config_entry.async_on_unload(
|
||||||
await async_setup_platform_discovery(
|
await async_setup_platform_discovery(hass, camera.DOMAIN)
|
||||||
hass, camera.DOMAIN, PLATFORM_SCHEMA_MODERN
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
# setup for discovery
|
# setup for discovery
|
||||||
setup = functools.partial(
|
setup = functools.partial(
|
||||||
|
|
|
@ -392,9 +392,7 @@ async def async_setup_entry(
|
||||||
"""Set up MQTT climate device through configuration.yaml and dynamically through MQTT discovery."""
|
"""Set up MQTT climate device through configuration.yaml and dynamically through MQTT discovery."""
|
||||||
# load and initialize platform config from configuration.yaml
|
# load and initialize platform config from configuration.yaml
|
||||||
config_entry.async_on_unload(
|
config_entry.async_on_unload(
|
||||||
await async_setup_platform_discovery(
|
await async_setup_platform_discovery(hass, climate.DOMAIN)
|
||||||
hass, climate.DOMAIN, PLATFORM_SCHEMA_MODERN
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
# setup for discovery
|
# setup for discovery
|
||||||
setup = functools.partial(
|
setup = functools.partial(
|
||||||
|
|
|
@ -3,126 +3,21 @@ from __future__ import annotations
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import CONF_VALUE_TEMPLATE
|
||||||
CONF_CLIENT_ID,
|
|
||||||
CONF_DISCOVERY,
|
|
||||||
CONF_PASSWORD,
|
|
||||||
CONF_PORT,
|
|
||||||
CONF_PROTOCOL,
|
|
||||||
CONF_USERNAME,
|
|
||||||
CONF_VALUE_TEMPLATE,
|
|
||||||
)
|
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_PAYLOAD,
|
|
||||||
ATTR_QOS,
|
|
||||||
ATTR_RETAIN,
|
|
||||||
ATTR_TOPIC,
|
|
||||||
CONF_BIRTH_MESSAGE,
|
|
||||||
CONF_BROKER,
|
|
||||||
CONF_CERTIFICATE,
|
|
||||||
CONF_CLIENT_CERT,
|
|
||||||
CONF_CLIENT_KEY,
|
|
||||||
CONF_COMMAND_TOPIC,
|
CONF_COMMAND_TOPIC,
|
||||||
CONF_DISCOVERY_PREFIX,
|
|
||||||
CONF_ENCODING,
|
CONF_ENCODING,
|
||||||
CONF_KEEPALIVE,
|
|
||||||
CONF_QOS,
|
CONF_QOS,
|
||||||
CONF_RETAIN,
|
CONF_RETAIN,
|
||||||
CONF_STATE_TOPIC,
|
CONF_STATE_TOPIC,
|
||||||
CONF_TLS_INSECURE,
|
|
||||||
CONF_TLS_VERSION,
|
|
||||||
CONF_WILL_MESSAGE,
|
|
||||||
DEFAULT_BIRTH,
|
|
||||||
DEFAULT_DISCOVERY,
|
|
||||||
DEFAULT_ENCODING,
|
DEFAULT_ENCODING,
|
||||||
DEFAULT_PREFIX,
|
|
||||||
DEFAULT_QOS,
|
DEFAULT_QOS,
|
||||||
DEFAULT_RETAIN,
|
DEFAULT_RETAIN,
|
||||||
DEFAULT_WILL,
|
|
||||||
PLATFORMS,
|
|
||||||
PROTOCOL_31,
|
|
||||||
PROTOCOL_311,
|
|
||||||
)
|
)
|
||||||
from .util import _VALID_QOS_SCHEMA, valid_publish_topic, valid_subscribe_topic
|
from .util import _VALID_QOS_SCHEMA, valid_publish_topic, valid_subscribe_topic
|
||||||
|
|
||||||
DEFAULT_PORT = 1883
|
|
||||||
DEFAULT_KEEPALIVE = 60
|
|
||||||
DEFAULT_PROTOCOL = PROTOCOL_311
|
|
||||||
DEFAULT_TLS_PROTOCOL = "auto"
|
|
||||||
|
|
||||||
DEFAULT_VALUES = {
|
|
||||||
CONF_BIRTH_MESSAGE: DEFAULT_BIRTH,
|
|
||||||
CONF_DISCOVERY: DEFAULT_DISCOVERY,
|
|
||||||
CONF_PORT: DEFAULT_PORT,
|
|
||||||
CONF_TLS_VERSION: DEFAULT_TLS_PROTOCOL,
|
|
||||||
CONF_WILL_MESSAGE: DEFAULT_WILL,
|
|
||||||
}
|
|
||||||
|
|
||||||
CLIENT_KEY_AUTH_MSG = (
|
|
||||||
"client_key and client_cert must both be present in "
|
|
||||||
"the MQTT broker configuration"
|
|
||||||
)
|
|
||||||
|
|
||||||
MQTT_WILL_BIRTH_SCHEMA = vol.Schema(
|
|
||||||
{
|
|
||||||
vol.Inclusive(ATTR_TOPIC, "topic_payload"): valid_publish_topic,
|
|
||||||
vol.Inclusive(ATTR_PAYLOAD, "topic_payload"): cv.string,
|
|
||||||
vol.Optional(ATTR_QOS, default=DEFAULT_QOS): _VALID_QOS_SCHEMA,
|
|
||||||
vol.Optional(ATTR_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
|
|
||||||
},
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
PLATFORM_CONFIG_SCHEMA_BASE = vol.Schema(
|
|
||||||
{vol.Optional(platform.value): cv.ensure_list for platform in PLATFORMS}
|
|
||||||
)
|
|
||||||
|
|
||||||
CONFIG_SCHEMA_BASE = PLATFORM_CONFIG_SCHEMA_BASE.extend(
|
|
||||||
{
|
|
||||||
vol.Optional(CONF_CLIENT_ID): cv.string,
|
|
||||||
vol.Optional(CONF_KEEPALIVE, default=DEFAULT_KEEPALIVE): vol.All(
|
|
||||||
vol.Coerce(int), vol.Range(min=15)
|
|
||||||
),
|
|
||||||
vol.Optional(CONF_BROKER): cv.string,
|
|
||||||
vol.Optional(CONF_PORT): cv.port,
|
|
||||||
vol.Optional(CONF_USERNAME): cv.string,
|
|
||||||
vol.Optional(CONF_PASSWORD): cv.string,
|
|
||||||
vol.Optional(CONF_CERTIFICATE): vol.Any("auto", cv.isfile),
|
|
||||||
vol.Inclusive(
|
|
||||||
CONF_CLIENT_KEY, "client_key_auth", msg=CLIENT_KEY_AUTH_MSG
|
|
||||||
): cv.isfile,
|
|
||||||
vol.Inclusive(
|
|
||||||
CONF_CLIENT_CERT, "client_key_auth", msg=CLIENT_KEY_AUTH_MSG
|
|
||||||
): cv.isfile,
|
|
||||||
vol.Optional(CONF_TLS_INSECURE): cv.boolean,
|
|
||||||
vol.Optional(CONF_TLS_VERSION): vol.Any("auto", "1.0", "1.1", "1.2"),
|
|
||||||
vol.Optional(CONF_PROTOCOL, default=DEFAULT_PROTOCOL): vol.All(
|
|
||||||
cv.string, vol.In([PROTOCOL_31, PROTOCOL_311])
|
|
||||||
),
|
|
||||||
vol.Optional(CONF_WILL_MESSAGE): MQTT_WILL_BIRTH_SCHEMA,
|
|
||||||
vol.Optional(CONF_BIRTH_MESSAGE): MQTT_WILL_BIRTH_SCHEMA,
|
|
||||||
vol.Optional(CONF_DISCOVERY): cv.boolean,
|
|
||||||
# discovery_prefix must be a valid publish topic because if no
|
|
||||||
# state topic is specified, it will be created with the given prefix.
|
|
||||||
vol.Optional(
|
|
||||||
CONF_DISCOVERY_PREFIX, default=DEFAULT_PREFIX
|
|
||||||
): valid_publish_topic,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
DEPRECATED_CONFIG_KEYS = [
|
|
||||||
CONF_BIRTH_MESSAGE,
|
|
||||||
CONF_BROKER,
|
|
||||||
CONF_DISCOVERY,
|
|
||||||
CONF_PASSWORD,
|
|
||||||
CONF_PORT,
|
|
||||||
CONF_TLS_VERSION,
|
|
||||||
CONF_USERNAME,
|
|
||||||
CONF_WILL_MESSAGE,
|
|
||||||
]
|
|
||||||
|
|
||||||
SCHEMA_BASE = {
|
SCHEMA_BASE = {
|
||||||
vol.Optional(CONF_QOS, default=DEFAULT_QOS): _VALID_QOS_SCHEMA,
|
vol.Optional(CONF_QOS, default=DEFAULT_QOS): _VALID_QOS_SCHEMA,
|
||||||
vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): cv.string,
|
vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): cv.string,
|
||||||
|
|
193
homeassistant/components/mqtt/config_integration.py
Normal file
193
homeassistant/components/mqtt/config_integration.py
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
"""Support for MQTT platform config setup."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_CLIENT_ID,
|
||||||
|
CONF_DISCOVERY,
|
||||||
|
CONF_PASSWORD,
|
||||||
|
CONF_PORT,
|
||||||
|
CONF_PROTOCOL,
|
||||||
|
CONF_USERNAME,
|
||||||
|
Platform,
|
||||||
|
)
|
||||||
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
|
||||||
|
from . import (
|
||||||
|
alarm_control_panel as alarm_control_panel_platform,
|
||||||
|
binary_sensor as binary_sensor_platform,
|
||||||
|
button as button_platform,
|
||||||
|
camera as camera_platform,
|
||||||
|
climate as climate_platform,
|
||||||
|
cover as cover_platform,
|
||||||
|
device_tracker as device_tracker_platform,
|
||||||
|
fan as fan_platform,
|
||||||
|
humidifier as humidifier_platform,
|
||||||
|
light as light_platform,
|
||||||
|
lock as lock_platform,
|
||||||
|
number as number_platform,
|
||||||
|
scene as scene_platform,
|
||||||
|
select as select_platform,
|
||||||
|
sensor as sensor_platform,
|
||||||
|
siren as siren_platform,
|
||||||
|
switch as switch_platform,
|
||||||
|
vacuum as vacuum_platform,
|
||||||
|
)
|
||||||
|
from .const import (
|
||||||
|
ATTR_PAYLOAD,
|
||||||
|
ATTR_QOS,
|
||||||
|
ATTR_RETAIN,
|
||||||
|
ATTR_TOPIC,
|
||||||
|
CONF_BIRTH_MESSAGE,
|
||||||
|
CONF_BROKER,
|
||||||
|
CONF_CERTIFICATE,
|
||||||
|
CONF_CLIENT_CERT,
|
||||||
|
CONF_CLIENT_KEY,
|
||||||
|
CONF_DISCOVERY_PREFIX,
|
||||||
|
CONF_KEEPALIVE,
|
||||||
|
CONF_TLS_INSECURE,
|
||||||
|
CONF_TLS_VERSION,
|
||||||
|
CONF_WILL_MESSAGE,
|
||||||
|
DEFAULT_BIRTH,
|
||||||
|
DEFAULT_DISCOVERY,
|
||||||
|
DEFAULT_PREFIX,
|
||||||
|
DEFAULT_QOS,
|
||||||
|
DEFAULT_RETAIN,
|
||||||
|
DEFAULT_WILL,
|
||||||
|
PROTOCOL_31,
|
||||||
|
PROTOCOL_311,
|
||||||
|
)
|
||||||
|
from .util import _VALID_QOS_SCHEMA, valid_publish_topic
|
||||||
|
|
||||||
|
DEFAULT_PORT = 1883
|
||||||
|
DEFAULT_KEEPALIVE = 60
|
||||||
|
DEFAULT_PROTOCOL = PROTOCOL_311
|
||||||
|
DEFAULT_TLS_PROTOCOL = "auto"
|
||||||
|
|
||||||
|
DEFAULT_VALUES = {
|
||||||
|
CONF_BIRTH_MESSAGE: DEFAULT_BIRTH,
|
||||||
|
CONF_DISCOVERY: DEFAULT_DISCOVERY,
|
||||||
|
CONF_PORT: DEFAULT_PORT,
|
||||||
|
CONF_TLS_VERSION: DEFAULT_TLS_PROTOCOL,
|
||||||
|
CONF_WILL_MESSAGE: DEFAULT_WILL,
|
||||||
|
}
|
||||||
|
|
||||||
|
PLATFORM_CONFIG_SCHEMA_BASE = vol.Schema(
|
||||||
|
{
|
||||||
|
Platform.ALARM_CONTROL_PANEL.value: vol.All(
|
||||||
|
cv.ensure_list, [alarm_control_panel_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
|
||||||
|
),
|
||||||
|
Platform.BINARY_SENSOR.value: vol.All(
|
||||||
|
cv.ensure_list, [binary_sensor_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
|
||||||
|
),
|
||||||
|
Platform.BUTTON.value: vol.All(
|
||||||
|
cv.ensure_list, [button_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
|
||||||
|
),
|
||||||
|
Platform.CAMERA.value: vol.All(
|
||||||
|
cv.ensure_list, [camera_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
|
||||||
|
),
|
||||||
|
Platform.CLIMATE.value: vol.All(
|
||||||
|
cv.ensure_list, [climate_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
|
||||||
|
),
|
||||||
|
Platform.COVER.value: vol.All(
|
||||||
|
cv.ensure_list, [cover_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
|
||||||
|
),
|
||||||
|
Platform.DEVICE_TRACKER.value: vol.All(
|
||||||
|
cv.ensure_list, [device_tracker_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
|
||||||
|
),
|
||||||
|
Platform.FAN.value: vol.All(
|
||||||
|
cv.ensure_list, [fan_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
|
||||||
|
),
|
||||||
|
Platform.HUMIDIFIER.value: vol.All(
|
||||||
|
cv.ensure_list, [humidifier_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
|
||||||
|
),
|
||||||
|
Platform.LOCK.value: vol.All(
|
||||||
|
cv.ensure_list, [lock_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
|
||||||
|
),
|
||||||
|
Platform.LIGHT.value: vol.All(
|
||||||
|
cv.ensure_list, [light_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
|
||||||
|
),
|
||||||
|
Platform.NUMBER.value: vol.All(
|
||||||
|
cv.ensure_list, [number_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
|
||||||
|
),
|
||||||
|
Platform.SCENE.value: vol.All(
|
||||||
|
cv.ensure_list, [scene_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
|
||||||
|
),
|
||||||
|
Platform.SELECT.value: vol.All(
|
||||||
|
cv.ensure_list, [select_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
|
||||||
|
),
|
||||||
|
Platform.SENSOR.value: vol.All(
|
||||||
|
cv.ensure_list, [sensor_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
|
||||||
|
),
|
||||||
|
Platform.SIREN.value: vol.All(
|
||||||
|
cv.ensure_list, [siren_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
|
||||||
|
),
|
||||||
|
Platform.SWITCH.value: vol.All(
|
||||||
|
cv.ensure_list, [switch_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
|
||||||
|
),
|
||||||
|
Platform.VACUUM.value: vol.All(
|
||||||
|
cv.ensure_list, [vacuum_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
CLIENT_KEY_AUTH_MSG = (
|
||||||
|
"client_key and client_cert must both be present in "
|
||||||
|
"the MQTT broker configuration"
|
||||||
|
)
|
||||||
|
|
||||||
|
MQTT_WILL_BIRTH_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Inclusive(ATTR_TOPIC, "topic_payload"): valid_publish_topic,
|
||||||
|
vol.Inclusive(ATTR_PAYLOAD, "topic_payload"): cv.string,
|
||||||
|
vol.Optional(ATTR_QOS, default=DEFAULT_QOS): _VALID_QOS_SCHEMA,
|
||||||
|
vol.Optional(ATTR_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
|
||||||
|
},
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA_BASE = PLATFORM_CONFIG_SCHEMA_BASE.extend(
|
||||||
|
{
|
||||||
|
vol.Optional(CONF_CLIENT_ID): cv.string,
|
||||||
|
vol.Optional(CONF_KEEPALIVE, default=DEFAULT_KEEPALIVE): vol.All(
|
||||||
|
vol.Coerce(int), vol.Range(min=15)
|
||||||
|
),
|
||||||
|
vol.Optional(CONF_BROKER): cv.string,
|
||||||
|
vol.Optional(CONF_PORT): cv.port,
|
||||||
|
vol.Optional(CONF_USERNAME): cv.string,
|
||||||
|
vol.Optional(CONF_PASSWORD): cv.string,
|
||||||
|
vol.Optional(CONF_CERTIFICATE): vol.Any("auto", cv.isfile),
|
||||||
|
vol.Inclusive(
|
||||||
|
CONF_CLIENT_KEY, "client_key_auth", msg=CLIENT_KEY_AUTH_MSG
|
||||||
|
): cv.isfile,
|
||||||
|
vol.Inclusive(
|
||||||
|
CONF_CLIENT_CERT, "client_key_auth", msg=CLIENT_KEY_AUTH_MSG
|
||||||
|
): cv.isfile,
|
||||||
|
vol.Optional(CONF_TLS_INSECURE): cv.boolean,
|
||||||
|
vol.Optional(CONF_TLS_VERSION): vol.Any("auto", "1.0", "1.1", "1.2"),
|
||||||
|
vol.Optional(CONF_PROTOCOL, default=DEFAULT_PROTOCOL): vol.All(
|
||||||
|
cv.string, vol.In([PROTOCOL_31, PROTOCOL_311])
|
||||||
|
),
|
||||||
|
vol.Optional(CONF_WILL_MESSAGE): MQTT_WILL_BIRTH_SCHEMA,
|
||||||
|
vol.Optional(CONF_BIRTH_MESSAGE): MQTT_WILL_BIRTH_SCHEMA,
|
||||||
|
vol.Optional(CONF_DISCOVERY): cv.boolean,
|
||||||
|
# discovery_prefix must be a valid publish topic because if no
|
||||||
|
# state topic is specified, it will be created with the given prefix.
|
||||||
|
vol.Optional(
|
||||||
|
CONF_DISCOVERY_PREFIX, default=DEFAULT_PREFIX
|
||||||
|
): valid_publish_topic,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
DEPRECATED_CONFIG_KEYS = [
|
||||||
|
CONF_BIRTH_MESSAGE,
|
||||||
|
CONF_BROKER,
|
||||||
|
CONF_DISCOVERY,
|
||||||
|
CONF_PASSWORD,
|
||||||
|
CONF_PORT,
|
||||||
|
CONF_TLS_VERSION,
|
||||||
|
CONF_USERNAME,
|
||||||
|
CONF_WILL_MESSAGE,
|
||||||
|
]
|
|
@ -241,7 +241,7 @@ async def async_setup_entry(
|
||||||
"""Set up MQTT cover through configuration.yaml and dynamically through MQTT discovery."""
|
"""Set up MQTT cover through configuration.yaml and dynamically through MQTT discovery."""
|
||||||
# load and initialize platform config from configuration.yaml
|
# load and initialize platform config from configuration.yaml
|
||||||
config_entry.async_on_unload(
|
config_entry.async_on_unload(
|
||||||
await async_setup_platform_discovery(hass, cover.DOMAIN, PLATFORM_SCHEMA_MODERN)
|
await async_setup_platform_discovery(hass, cover.DOMAIN)
|
||||||
)
|
)
|
||||||
# setup for discovery
|
# setup for discovery
|
||||||
setup = functools.partial(
|
setup = functools.partial(
|
||||||
|
|
|
@ -4,6 +4,7 @@ import voluptuous as vol
|
||||||
from homeassistant.components import device_tracker
|
from homeassistant.components import device_tracker
|
||||||
|
|
||||||
from ..mixins import warn_for_legacy_schema
|
from ..mixins import warn_for_legacy_schema
|
||||||
|
from .schema_discovery import PLATFORM_SCHEMA_MODERN # noqa: F401
|
||||||
from .schema_discovery import async_setup_entry_from_discovery
|
from .schema_discovery import async_setup_entry_from_discovery
|
||||||
from .schema_yaml import PLATFORM_SCHEMA_YAML, async_setup_scanner_from_yaml
|
from .schema_yaml import PLATFORM_SCHEMA_YAML, async_setup_scanner_from_yaml
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,7 @@ async def async_setup_entry_from_discovery(hass, config_entry, async_add_entitie
|
||||||
*(
|
*(
|
||||||
_async_setup_entity(hass, async_add_entities, config, config_entry)
|
_async_setup_entity(hass, async_add_entities, config, config_entry)
|
||||||
for config in await async_get_platform_config_from_yaml(
|
for config in await async_get_platform_config_from_yaml(
|
||||||
hass, device_tracker.DOMAIN, PLATFORM_SCHEMA_MODERN
|
hass, device_tracker.DOMAIN
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -231,9 +231,7 @@ async def async_setup_entry(
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up MQTT fan through configuration.yaml and dynamically through MQTT discovery."""
|
"""Set up MQTT fan through configuration.yaml and dynamically through MQTT discovery."""
|
||||||
# load and initialize platform config from configuration.yaml
|
# load and initialize platform config from configuration.yaml
|
||||||
config_entry.async_on_unload(
|
config_entry.async_on_unload(await async_setup_platform_discovery(hass, fan.DOMAIN))
|
||||||
await async_setup_platform_discovery(hass, fan.DOMAIN, PLATFORM_SCHEMA_MODERN)
|
|
||||||
)
|
|
||||||
# setup for discovery
|
# setup for discovery
|
||||||
setup = functools.partial(
|
setup = functools.partial(
|
||||||
_async_setup_entity, hass, async_add_entities, config_entry=config_entry
|
_async_setup_entity, hass, async_add_entities, config_entry=config_entry
|
||||||
|
|
|
@ -188,9 +188,7 @@ async def async_setup_entry(
|
||||||
"""Set up MQTT humidifier through configuration.yaml and dynamically through MQTT discovery."""
|
"""Set up MQTT humidifier through configuration.yaml and dynamically through MQTT discovery."""
|
||||||
# load and initialize platform config from configuration.yaml
|
# load and initialize platform config from configuration.yaml
|
||||||
config_entry.async_on_unload(
|
config_entry.async_on_unload(
|
||||||
await async_setup_platform_discovery(
|
await async_setup_platform_discovery(hass, humidifier.DOMAIN)
|
||||||
hass, humidifier.DOMAIN, PLATFORM_SCHEMA_MODERN
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
# setup for discovery
|
# setup for discovery
|
||||||
setup = functools.partial(
|
setup = functools.partial(
|
||||||
|
|
|
@ -112,7 +112,7 @@ async def async_setup_entry(
|
||||||
"""Set up MQTT lights configured under the light platform key (deprecated)."""
|
"""Set up MQTT lights configured under the light platform key (deprecated)."""
|
||||||
# load and initialize platform config from configuration.yaml
|
# load and initialize platform config from configuration.yaml
|
||||||
config_entry.async_on_unload(
|
config_entry.async_on_unload(
|
||||||
await async_setup_platform_discovery(hass, light.DOMAIN, PLATFORM_SCHEMA_MODERN)
|
await async_setup_platform_discovery(hass, light.DOMAIN)
|
||||||
)
|
)
|
||||||
# setup for discovery
|
# setup for discovery
|
||||||
setup = functools.partial(
|
setup = functools.partial(
|
||||||
|
|
|
@ -104,7 +104,7 @@ async def async_setup_entry(
|
||||||
"""Set up MQTT lock through configuration.yaml and dynamically through MQTT discovery."""
|
"""Set up MQTT lock through configuration.yaml and dynamically through MQTT discovery."""
|
||||||
# load and initialize platform config from configuration.yaml
|
# load and initialize platform config from configuration.yaml
|
||||||
config_entry.async_on_unload(
|
config_entry.async_on_unload(
|
||||||
await async_setup_platform_discovery(hass, lock.DOMAIN, PLATFORM_SCHEMA_MODERN)
|
await async_setup_platform_discovery(hass, lock.DOMAIN)
|
||||||
)
|
)
|
||||||
# setup for discovery
|
# setup for discovery
|
||||||
setup = functools.partial(
|
setup = functools.partial(
|
||||||
|
|
|
@ -10,7 +10,6 @@ from typing import Any, Protocol, cast, final
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.config import async_log_exception
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_CONFIGURATION_URL,
|
ATTR_CONFIGURATION_URL,
|
||||||
|
@ -263,7 +262,7 @@ class SetupEntity(Protocol):
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform_discovery(
|
async def async_setup_platform_discovery(
|
||||||
hass: HomeAssistant, platform_domain: str, schema: vol.Schema
|
hass: HomeAssistant, platform_domain: str
|
||||||
) -> CALLBACK_TYPE:
|
) -> CALLBACK_TYPE:
|
||||||
"""Set up platform discovery for manual config."""
|
"""Set up platform discovery for manual config."""
|
||||||
|
|
||||||
|
@ -282,7 +281,7 @@ async def async_setup_platform_discovery(
|
||||||
*(
|
*(
|
||||||
discovery.async_load_platform(hass, platform_domain, DOMAIN, config, {})
|
discovery.async_load_platform(hass, platform_domain, DOMAIN, config, {})
|
||||||
for config in await async_get_platform_config_from_yaml(
|
for config in await async_get_platform_config_from_yaml(
|
||||||
hass, platform_domain, schema, config_yaml
|
hass, platform_domain, config_yaml
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -295,32 +294,17 @@ async def async_setup_platform_discovery(
|
||||||
async def async_get_platform_config_from_yaml(
|
async def async_get_platform_config_from_yaml(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
platform_domain: str,
|
platform_domain: str,
|
||||||
schema: vol.Schema,
|
|
||||||
config_yaml: ConfigType = None,
|
config_yaml: ConfigType = None,
|
||||||
) -> list[ConfigType]:
|
) -> list[ConfigType]:
|
||||||
"""Return a list of validated configurations for the domain."""
|
"""Return a list of validated configurations for the domain."""
|
||||||
|
|
||||||
def async_validate_config(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
config: list[ConfigType],
|
|
||||||
) -> list[ConfigType]:
|
|
||||||
"""Validate config."""
|
|
||||||
validated_config = []
|
|
||||||
for config_item in config:
|
|
||||||
try:
|
|
||||||
validated_config.append(schema(config_item))
|
|
||||||
except vol.MultipleInvalid as err:
|
|
||||||
async_log_exception(err, platform_domain, config_item, hass)
|
|
||||||
|
|
||||||
return validated_config
|
|
||||||
|
|
||||||
if config_yaml is None:
|
if config_yaml is None:
|
||||||
config_yaml = hass.data.get(DATA_MQTT_CONFIG)
|
config_yaml = hass.data.get(DATA_MQTT_CONFIG)
|
||||||
if not config_yaml:
|
if not config_yaml:
|
||||||
return []
|
return []
|
||||||
if not (platform_configs := config_yaml.get(platform_domain)):
|
if not (platform_configs := config_yaml.get(platform_domain)):
|
||||||
return []
|
return []
|
||||||
return async_validate_config(hass, platform_configs)
|
return platform_configs
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry_helper(hass, domain, async_setup, schema):
|
async def async_setup_entry_helper(hass, domain, async_setup, schema):
|
||||||
|
|
|
@ -136,9 +136,7 @@ async def async_setup_entry(
|
||||||
"""Set up MQTT number through configuration.yaml and dynamically through MQTT discovery."""
|
"""Set up MQTT number through configuration.yaml and dynamically through MQTT discovery."""
|
||||||
# load and initialize platform config from configuration.yaml
|
# load and initialize platform config from configuration.yaml
|
||||||
config_entry.async_on_unload(
|
config_entry.async_on_unload(
|
||||||
await async_setup_platform_discovery(
|
await async_setup_platform_discovery(hass, number.DOMAIN)
|
||||||
hass, number.DOMAIN, PLATFORM_SCHEMA_MODERN
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
# setup for discovery
|
# setup for discovery
|
||||||
setup = functools.partial(
|
setup = functools.partial(
|
||||||
|
|
|
@ -80,7 +80,7 @@ async def async_setup_entry(
|
||||||
"""Set up MQTT scene through configuration.yaml and dynamically through MQTT discovery."""
|
"""Set up MQTT scene through configuration.yaml and dynamically through MQTT discovery."""
|
||||||
# load and initialize platform config from configuration.yaml
|
# load and initialize platform config from configuration.yaml
|
||||||
config_entry.async_on_unload(
|
config_entry.async_on_unload(
|
||||||
await async_setup_platform_discovery(hass, scene.DOMAIN, PLATFORM_SCHEMA_MODERN)
|
await async_setup_platform_discovery(hass, scene.DOMAIN)
|
||||||
)
|
)
|
||||||
# setup for discovery
|
# setup for discovery
|
||||||
setup = functools.partial(
|
setup = functools.partial(
|
||||||
|
|
|
@ -95,9 +95,7 @@ async def async_setup_entry(
|
||||||
"""Set up MQTT select through configuration.yaml and dynamically through MQTT discovery."""
|
"""Set up MQTT select through configuration.yaml and dynamically through MQTT discovery."""
|
||||||
# load and initialize platform config from configuration.yaml
|
# load and initialize platform config from configuration.yaml
|
||||||
config_entry.async_on_unload(
|
config_entry.async_on_unload(
|
||||||
await async_setup_platform_discovery(
|
await async_setup_platform_discovery(hass, select.DOMAIN)
|
||||||
hass, select.DOMAIN, PLATFORM_SCHEMA_MODERN
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
# setup for discovery
|
# setup for discovery
|
||||||
setup = functools.partial(
|
setup = functools.partial(
|
||||||
|
|
|
@ -148,9 +148,7 @@ async def async_setup_entry(
|
||||||
"""Set up MQTT sensor through configuration.yaml and dynamically through MQTT discovery."""
|
"""Set up MQTT sensor through configuration.yaml and dynamically through MQTT discovery."""
|
||||||
# load and initialize platform config from configuration.yaml
|
# load and initialize platform config from configuration.yaml
|
||||||
config_entry.async_on_unload(
|
config_entry.async_on_unload(
|
||||||
await async_setup_platform_discovery(
|
await async_setup_platform_discovery(hass, sensor.DOMAIN)
|
||||||
hass, sensor.DOMAIN, PLATFORM_SCHEMA_MODERN
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
# setup for discovery
|
# setup for discovery
|
||||||
setup = functools.partial(
|
setup = functools.partial(
|
||||||
|
|
|
@ -144,7 +144,7 @@ async def async_setup_entry(
|
||||||
"""Set up MQTT siren through configuration.yaml and dynamically through MQTT discovery."""
|
"""Set up MQTT siren through configuration.yaml and dynamically through MQTT discovery."""
|
||||||
# load and initialize platform config from configuration.yaml
|
# load and initialize platform config from configuration.yaml
|
||||||
config_entry.async_on_unload(
|
config_entry.async_on_unload(
|
||||||
await async_setup_platform_discovery(hass, siren.DOMAIN, PLATFORM_SCHEMA_MODERN)
|
await async_setup_platform_discovery(hass, siren.DOMAIN)
|
||||||
)
|
)
|
||||||
# setup for discovery
|
# setup for discovery
|
||||||
setup = functools.partial(
|
setup = functools.partial(
|
||||||
|
|
|
@ -98,9 +98,7 @@ async def async_setup_entry(
|
||||||
"""Set up MQTT switch through configuration.yaml and dynamically through MQTT discovery."""
|
"""Set up MQTT switch through configuration.yaml and dynamically through MQTT discovery."""
|
||||||
# load and initialize platform config from configuration.yaml
|
# load and initialize platform config from configuration.yaml
|
||||||
config_entry.async_on_unload(
|
config_entry.async_on_unload(
|
||||||
await async_setup_platform_discovery(
|
await async_setup_platform_discovery(hass, switch.DOMAIN)
|
||||||
hass, switch.DOMAIN, PLATFORM_SCHEMA_MODERN
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
# setup for discovery
|
# setup for discovery
|
||||||
setup = functools.partial(
|
setup = functools.partial(
|
||||||
|
|
|
@ -92,9 +92,7 @@ async def async_setup_entry(
|
||||||
"""Set up MQTT vacuum through configuration.yaml and dynamically through MQTT discovery."""
|
"""Set up MQTT vacuum through configuration.yaml and dynamically through MQTT discovery."""
|
||||||
# load and initialize platform config from configuration.yaml
|
# load and initialize platform config from configuration.yaml
|
||||||
config_entry.async_on_unload(
|
config_entry.async_on_unload(
|
||||||
await async_setup_platform_discovery(
|
await async_setup_platform_discovery(hass, vacuum.DOMAIN)
|
||||||
hass, vacuum.DOMAIN, PLATFORM_SCHEMA_MODERN
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
# setup for discovery
|
# setup for discovery
|
||||||
setup = functools.partial(
|
setup = functools.partial(
|
||||||
|
|
|
@ -13,6 +13,7 @@ from homeassistant.components.humidifier import (
|
||||||
SERVICE_SET_HUMIDITY,
|
SERVICE_SET_HUMIDITY,
|
||||||
SERVICE_SET_MODE,
|
SERVICE_SET_MODE,
|
||||||
)
|
)
|
||||||
|
from homeassistant.components.mqtt import CONFIG_SCHEMA
|
||||||
from homeassistant.components.mqtt.humidifier import (
|
from homeassistant.components.mqtt.humidifier import (
|
||||||
CONF_MODE_COMMAND_TOPIC,
|
CONF_MODE_COMMAND_TOPIC,
|
||||||
CONF_MODE_STATE_TOPIC,
|
CONF_MODE_STATE_TOPIC,
|
||||||
|
@ -1283,3 +1284,15 @@ async def test_setup_manual_entity_from_yaml(hass):
|
||||||
del config["platform"]
|
del config["platform"]
|
||||||
await help_test_setup_manual_entity_from_yaml(hass, platform, config)
|
await help_test_setup_manual_entity_from_yaml(hass, platform, config)
|
||||||
assert hass.states.get(f"{platform}.test") is not None
|
assert hass.states.get(f"{platform}.test") is not None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_schema_validation(hass):
|
||||||
|
"""Test invalid platform options in the config schema do pass the config validation."""
|
||||||
|
platform = humidifier.DOMAIN
|
||||||
|
config = copy.deepcopy(DEFAULT_CONFIG[platform])
|
||||||
|
config["name"] = "test"
|
||||||
|
del config["platform"]
|
||||||
|
CONFIG_SCHEMA({DOMAIN: {platform: config}})
|
||||||
|
CONFIG_SCHEMA({DOMAIN: {platform: [config]}})
|
||||||
|
with pytest.raises(MultipleInvalid):
|
||||||
|
CONFIG_SCHEMA({"mqtt": {"humidifier": [{"bla": "bla"}]}})
|
||||||
|
|
|
@ -14,7 +14,7 @@ import yaml
|
||||||
|
|
||||||
from homeassistant import config as hass_config
|
from homeassistant import config as hass_config
|
||||||
from homeassistant.components import mqtt
|
from homeassistant.components import mqtt
|
||||||
from homeassistant.components.mqtt import debug_info
|
from homeassistant.components.mqtt import CONFIG_SCHEMA, debug_info
|
||||||
from homeassistant.components.mqtt.mixins import MQTT_ENTITY_DEVICE_INFO_SCHEMA
|
from homeassistant.components.mqtt.mixins import MQTT_ENTITY_DEVICE_INFO_SCHEMA
|
||||||
from homeassistant.components.mqtt.models import ReceiveMessage
|
from homeassistant.components.mqtt.models import ReceiveMessage
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
@ -1373,40 +1373,47 @@ async def test_setup_override_configuration(hass, caplog, tmp_path):
|
||||||
assert calls_username_password_set[0][1] == "somepassword"
|
assert calls_username_password_set[0][1] == "somepassword"
|
||||||
|
|
||||||
|
|
||||||
@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.LIGHT])
|
@patch("homeassistant.components.mqtt.PLATFORMS", [])
|
||||||
async def test_setup_manual_mqtt_with_platform_key(hass, caplog):
|
async def test_setup_manual_mqtt_with_platform_key(hass, caplog):
|
||||||
"""Test set up a manual MQTT item with a platform key."""
|
"""Test set up a manual MQTT item with a platform key."""
|
||||||
config = {"platform": "mqtt", "name": "test", "command_topic": "test-topic"}
|
config = {"platform": "mqtt", "name": "test", "command_topic": "test-topic"}
|
||||||
await help_test_setup_manual_entity_from_yaml(hass, "light", config)
|
with pytest.raises(AssertionError):
|
||||||
|
await help_test_setup_manual_entity_from_yaml(hass, "light", config)
|
||||||
assert (
|
assert (
|
||||||
"Invalid config for [light]: [platform] is an invalid option for [light]. "
|
"Invalid config for [mqtt]: [platform] is an invalid option for [mqtt]"
|
||||||
"Check: light->platform. (See ?, line ?)" in caplog.text
|
in caplog.text
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.LIGHT])
|
@patch("homeassistant.components.mqtt.PLATFORMS", [])
|
||||||
async def test_setup_manual_mqtt_with_invalid_config(hass, caplog):
|
async def test_setup_manual_mqtt_with_invalid_config(hass, caplog):
|
||||||
"""Test set up a manual MQTT item with an invalid config."""
|
"""Test set up a manual MQTT item with an invalid config."""
|
||||||
config = {"name": "test"}
|
config = {"name": "test"}
|
||||||
await help_test_setup_manual_entity_from_yaml(hass, "light", config)
|
with pytest.raises(AssertionError):
|
||||||
|
await help_test_setup_manual_entity_from_yaml(hass, "light", config)
|
||||||
assert (
|
assert (
|
||||||
"Invalid config for [light]: required key not provided @ data['command_topic']."
|
"Invalid config for [mqtt]: required key not provided @ data['mqtt']['light'][0]['command_topic']."
|
||||||
" Got None. (See ?, line ?)" in caplog.text
|
" Got None. (See ?, line ?)" in caplog.text
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@patch("homeassistant.components.mqtt.PLATFORMS", [])
|
||||||
async def test_setup_manual_mqtt_empty_platform(hass, caplog):
|
async def test_setup_manual_mqtt_empty_platform(hass, caplog):
|
||||||
"""Test set up a manual MQTT platform without items."""
|
"""Test set up a manual MQTT platform without items."""
|
||||||
config = None
|
config = []
|
||||||
await help_test_setup_manual_entity_from_yaml(hass, "light", config)
|
await help_test_setup_manual_entity_from_yaml(hass, "light", config)
|
||||||
assert "voluptuous.error.MultipleInvalid" not in caplog.text
|
assert "voluptuous.error.MultipleInvalid" not in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
@patch("homeassistant.components.mqtt.PLATFORMS", [])
|
||||||
async def test_setup_mqtt_client_protocol(hass):
|
async def test_setup_mqtt_client_protocol(hass):
|
||||||
"""Test MQTT client protocol setup."""
|
"""Test MQTT client protocol setup."""
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain=mqtt.DOMAIN,
|
domain=mqtt.DOMAIN,
|
||||||
data={mqtt.CONF_BROKER: "test-broker", mqtt.config.CONF_PROTOCOL: "3.1"},
|
data={
|
||||||
|
mqtt.CONF_BROKER: "test-broker",
|
||||||
|
mqtt.config_integration.CONF_PROTOCOL: "3.1",
|
||||||
|
},
|
||||||
)
|
)
|
||||||
with patch("paho.mqtt.client.Client") as mock_client:
|
with patch("paho.mqtt.client.Client") as mock_client:
|
||||||
mock_client.on_connect(return_value=0)
|
mock_client.on_connect(return_value=0)
|
||||||
|
@ -2612,3 +2619,10 @@ async def test_one_deprecation_warning_per_platform(
|
||||||
):
|
):
|
||||||
count += 1
|
count += 1
|
||||||
assert count == 1
|
assert count == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_schema_validation(hass):
|
||||||
|
"""Test invalid platform options in the config schema do not pass the config validation."""
|
||||||
|
config = {"mqtt": {"sensor": [{"some_illegal_topic": "mystate/topic/path"}]}}
|
||||||
|
with pytest.raises(vol.MultipleInvalid):
|
||||||
|
CONFIG_SCHEMA(config)
|
||||||
|
|
Loading…
Add table
Reference in a new issue