diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 7dbf7656d37..951b7bc25d4 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -24,7 +24,7 @@ from homeassistant.const import ( SERVICE_RELOAD, ) from homeassistant.core import HassJob, HomeAssistant, ServiceCall, callback -from homeassistant.exceptions import ConfigEntryError, TemplateError, Unauthorized +from homeassistant.exceptions import TemplateError, Unauthorized from homeassistant.helpers import config_validation as cv, event, template from homeassistant.helpers.device_registry import DeviceEntry from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -45,7 +45,7 @@ from .client import ( # noqa: F401 publish, subscribe, ) -from .config_integration import CONFIG_SCHEMA_ENTRY, PLATFORM_CONFIG_SCHEMA_BASE +from .config_integration import PLATFORM_CONFIG_SCHEMA_BASE from .const import ( # noqa: F401 ATTR_PAYLOAD, ATTR_QOS, @@ -68,7 +68,9 @@ from .const import ( # noqa: F401 CONF_WS_HEADERS, CONF_WS_PATH, DATA_MQTT, + DEFAULT_DISCOVERY, DEFAULT_ENCODING, + DEFAULT_PREFIX, DEFAULT_QOS, DEFAULT_RETAIN, DOMAIN, @@ -178,7 +180,9 @@ async def _async_setup_discovery( This method is a coroutine. """ - await discovery.async_start(hass, conf[CONF_DISCOVERY_PREFIX], config_entry) + await discovery.async_start( + hass, conf.get(CONF_DISCOVERY_PREFIX, DEFAULT_PREFIX), config_entry + ) async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: @@ -198,15 +202,8 @@ async def _async_config_entry_updated(hass: HomeAssistant, entry: ConfigEntry) - async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Load a config entry.""" - # validate entry config - try: - conf = CONFIG_SCHEMA_ENTRY(dict(entry.data)) - except vol.MultipleInvalid as ex: - raise ConfigEntryError( - f"The MQTT config entry is invalid, please correct it: {ex}" - ) from ex - - # Fetch configuration and add default values + conf = dict(entry.data) + # Fetch configuration hass_config = await conf_util.async_hass_config_yaml(hass) mqtt_yaml = PLATFORM_CONFIG_SCHEMA_BASE(hass_config.get(DOMAIN, {})) client = MQTT(hass, entry, conf) @@ -390,7 +387,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) ) # Setup discovery - if conf.get(CONF_DISCOVERY): + if conf.get(CONF_DISCOVERY, DEFAULT_DISCOVERY): await _async_setup_discovery(hass, conf, entry) # Setup reload service after all platforms have loaded await async_setup_reload_service() diff --git a/homeassistant/components/mqtt/client.py b/homeassistant/components/mqtt/client.py index 27b13a24b7c..2064594fbf0 100644 --- a/homeassistant/components/mqtt/client.py +++ b/homeassistant/components/mqtt/client.py @@ -44,7 +44,6 @@ from homeassistant.util.async_ import run_callback_threadsafe from homeassistant.util.logging import catch_log_exception from .const import ( - ATTR_TOPIC, CONF_BIRTH_MESSAGE, CONF_BROKER, CONF_CERTIFICATE, @@ -56,10 +55,16 @@ from .const import ( CONF_WILL_MESSAGE, CONF_WS_HEADERS, CONF_WS_PATH, + DEFAULT_BIRTH, DEFAULT_ENCODING, + DEFAULT_KEEPALIVE, + DEFAULT_PORT, DEFAULT_PROTOCOL, DEFAULT_QOS, DEFAULT_TRANSPORT, + DEFAULT_WILL, + DEFAULT_WS_HEADERS, + DEFAULT_WS_PATH, MQTT_CONNECTED, MQTT_DISCONNECTED, PROTOCOL_5, @@ -273,8 +278,8 @@ class MqttClientSetup: client_cert = get_file_path(CONF_CLIENT_CERT, config.get(CONF_CLIENT_CERT)) tls_insecure = config.get(CONF_TLS_INSECURE) if transport == TRANSPORT_WEBSOCKETS: - ws_path: str = config[CONF_WS_PATH] - ws_headers: dict[str, str] = config[CONF_WS_HEADERS] + ws_path: str = config.get(CONF_WS_PATH, DEFAULT_WS_PATH) + ws_headers: dict[str, str] = config.get(CONF_WS_HEADERS, DEFAULT_WS_HEADERS) self._client.ws_set_options(ws_path, ws_headers) if certificate is not None: self._client.tls_set( @@ -452,15 +457,8 @@ class MQTT: self._mqttc.on_subscribe = self._mqtt_on_callback self._mqttc.on_unsubscribe = self._mqtt_on_callback - if ( - CONF_WILL_MESSAGE in self.conf - and ATTR_TOPIC in self.conf[CONF_WILL_MESSAGE] - ): - will_message = PublishMessage(**self.conf[CONF_WILL_MESSAGE]) - else: - will_message = None - - if will_message is not None: + if will := self.conf.get(CONF_WILL_MESSAGE, DEFAULT_WILL): + will_message = PublishMessage(**will) self._mqttc.will_set( topic=will_message.topic, payload=will_message.payload, @@ -503,8 +501,8 @@ class MQTT: result = await self.hass.async_add_executor_job( self._mqttc.connect, self.conf[CONF_BROKER], - self.conf[CONF_PORT], - self.conf[CONF_KEEPALIVE], + self.conf.get(CONF_PORT, DEFAULT_PORT), + self.conf.get(CONF_KEEPALIVE, DEFAULT_KEEPALIVE), ) except OSError as err: _LOGGER.error("Failed to connect to MQTT server due to exception: %s", err) @@ -738,16 +736,13 @@ class MQTT: _LOGGER.info( "Connected to MQTT server %s:%s (%s)", self.conf[CONF_BROKER], - self.conf[CONF_PORT], + self.conf.get(CONF_PORT, DEFAULT_PORT), result_code, ) self.hass.create_task(self._async_resubscribe()) - if ( - CONF_BIRTH_MESSAGE in self.conf - and ATTR_TOPIC in self.conf[CONF_BIRTH_MESSAGE] - ): + if birth := self.conf.get(CONF_BIRTH_MESSAGE, DEFAULT_BIRTH): async def publish_birth_message(birth_message: PublishMessage) -> None: await self._ha_started.wait() # Wait for Home Assistant to start @@ -761,7 +756,7 @@ class MQTT: retain=birth_message.retain, ) - birth_message = PublishMessage(**self.conf[CONF_BIRTH_MESSAGE]) + birth_message = PublishMessage(**birth) asyncio.run_coroutine_threadsafe( publish_birth_message(birth_message), self.hass.loop ) @@ -880,7 +875,7 @@ class MQTT: _LOGGER.warning( "Disconnected from MQTT server %s:%s (%s)", self.conf[CONF_BROKER], - self.conf[CONF_PORT], + self.conf.get(CONF_PORT, DEFAULT_PORT), result_code, ) diff --git a/homeassistant/components/mqtt/config_flow.py b/homeassistant/components/mqtt/config_flow.py index 77c3856aac1..e23280c7f2b 100644 --- a/homeassistant/components/mqtt/config_flow.py +++ b/homeassistant/components/mqtt/config_flow.py @@ -47,7 +47,6 @@ from homeassistant.helpers.selector import ( from homeassistant.util.json import JSON_DECODE_EXCEPTIONS, json_loads from .client import MqttClientSetup -from .config_integration import CONFIG_SCHEMA_ENTRY from .const import ( ATTR_PAYLOAD, ATTR_QOS, @@ -369,7 +368,6 @@ class MQTTOptionsFlowHandler(config_entries.OptionsFlow): updated_config = {} updated_config.update(self.broker_config) updated_config.update(options_config) - CONFIG_SCHEMA_ENTRY(updated_config) self.hass.config_entries.async_update_entry( self.config_entry, data=updated_config, diff --git a/homeassistant/components/mqtt/config_integration.py b/homeassistant/components/mqtt/config_integration.py index 443fc070fa1..469f52e1488 100644 --- a/homeassistant/components/mqtt/config_integration.py +++ b/homeassistant/components/mqtt/config_integration.py @@ -45,23 +45,8 @@ from .const import ( CONF_DISCOVERY_PREFIX, CONF_KEEPALIVE, CONF_TLS_INSECURE, - CONF_TRANSPORT, CONF_WILL_MESSAGE, - CONF_WS_HEADERS, - CONF_WS_PATH, - DEFAULT_BIRTH, - DEFAULT_DISCOVERY, - DEFAULT_KEEPALIVE, - DEFAULT_PORT, - DEFAULT_PREFIX, - DEFAULT_PROTOCOL, - DEFAULT_TRANSPORT, - DEFAULT_WILL, - SUPPORTED_PROTOCOLS, - TRANSPORT_TCP, - TRANSPORT_WEBSOCKETS, ) -from .util import valid_birth_will, valid_publish_topic DEFAULT_TLS_PROTOCOL = "auto" @@ -155,41 +140,6 @@ CLIENT_KEY_AUTH_MSG = ( "client_key and client_cert must both be present in the MQTT broker configuration" ) -CONFIG_SCHEMA_ENTRY = vol.Schema( - { - vol.Optional(CONF_CLIENT_ID): cv.string, - vol.Optional(CONF_KEEPALIVE, default=DEFAULT_KEEPALIVE): vol.All( - vol.Coerce(int), vol.Range(min=15) - ), - vol.Required(CONF_BROKER): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_USERNAME): cv.string, - vol.Optional(CONF_PASSWORD): cv.string, - vol.Optional(CONF_CERTIFICATE): str, - vol.Inclusive(CONF_CLIENT_KEY, "client_key_auth", msg=CLIENT_KEY_AUTH_MSG): str, - vol.Inclusive( - CONF_CLIENT_CERT, "client_key_auth", msg=CLIENT_KEY_AUTH_MSG - ): str, - vol.Optional(CONF_TLS_INSECURE): cv.boolean, - vol.Optional(CONF_PROTOCOL, default=DEFAULT_PROTOCOL): vol.All( - cv.string, vol.In(SUPPORTED_PROTOCOLS) - ), - vol.Optional(CONF_WILL_MESSAGE, default=DEFAULT_WILL): valid_birth_will, - vol.Optional(CONF_BIRTH_MESSAGE, default=DEFAULT_BIRTH): valid_birth_will, - vol.Optional(CONF_DISCOVERY, default=DEFAULT_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, - vol.Optional(CONF_TRANSPORT, default=DEFAULT_TRANSPORT): vol.All( - cv.string, vol.In([TRANSPORT_TCP, TRANSPORT_WEBSOCKETS]) - ), - vol.Optional(CONF_WS_PATH, default="/"): cv.string, - vol.Optional(CONF_WS_HEADERS, default={}): {cv.string: cv.string}, - } -) - DEPRECATED_CONFIG_KEYS = [ CONF_BIRTH_MESSAGE, CONF_BROKER, diff --git a/homeassistant/components/mqtt/const.py b/homeassistant/components/mqtt/const.py index 41fd353359e..fe7c6eb4cf0 100644 --- a/homeassistant/components/mqtt/const.py +++ b/homeassistant/components/mqtt/const.py @@ -46,6 +46,7 @@ DEFAULT_PAYLOAD_AVAILABLE = "online" DEFAULT_PAYLOAD_NOT_AVAILABLE = "offline" DEFAULT_PORT = 1883 DEFAULT_RETAIN = False +DEFAULT_WS_HEADERS: dict[str, str] = {} DEFAULT_WS_PATH = "/" PROTOCOL_31 = "3.1" diff --git a/tests/components/mqtt/test_diagnostics.py b/tests/components/mqtt/test_diagnostics.py index 386fb15f1f4..3eec6887b1a 100644 --- a/tests/components/mqtt/test_diagnostics.py +++ b/tests/components/mqtt/test_diagnostics.py @@ -19,20 +19,6 @@ from tests.typing import ClientSessionGenerator, MqttMockHAClientGenerator default_config = { "birth_message": {}, "broker": "mock-broker", - "discovery": True, - "discovery_prefix": "homeassistant", - "keepalive": 60, - "port": 1883, - "protocol": "3.1.1", - "transport": "tcp", - "will_message": { - "payload": "offline", - "qos": 0, - "retain": False, - "topic": "homeassistant/status", - }, - "ws_headers": {}, - "ws_path": "/", } @@ -57,6 +43,7 @@ async def test_entry_diagnostics( config_entry = hass.config_entries.async_entries(mqtt.DOMAIN)[0] mqtt_mock.connected = True + await get_diagnostics_for_config_entry(hass, hass_client, config_entry) assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { "connected": True, "devices": [], diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 5337668e016..3cab3d63f68 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -2288,23 +2288,6 @@ async def test_default_entry_setting_are_applied( assert device_entry is not None -async def test_fail_no_broker( - hass: HomeAssistant, - device_registry: dr.DeviceRegistry, - mqtt_client_mock: MqttMockPahoClient, - caplog: pytest.LogCaptureFixture, -) -> None: - """Test the MQTT entry setup when broker configuration is missing.""" - # Config entry data is incomplete - entry = MockConfigEntry(domain=mqtt.DOMAIN, data={}) - entry.add_to_hass(hass) - assert not await hass.config_entries.async_setup(entry.entry_id) - assert ( - "The MQTT config entry is invalid, please correct it: required key not provided @ data['broker']" - in caplog.text - ) - - @pytest.mark.no_fail_on_log_exception async def test_message_callback_exception_gets_logged( hass: HomeAssistant, @@ -3312,41 +3295,16 @@ async def test_setup_manual_items_with_unique_ids( assert bool("Platform mqtt does not generate unique IDs." in caplog.text) != unique -async def test_fail_with_unknown_conf_entry_options( - hass: HomeAssistant, - mqtt_client_mock: MqttMockPahoClient, - caplog: pytest.LogCaptureFixture, -) -> None: - """Test unknown keys in config entry data is removed.""" - mqtt_config_entry_data = { - mqtt.CONF_BROKER: "mock-broker", - mqtt.CONF_BIRTH_MESSAGE: {}, - "old_option": "old_value", - } - - entry = MockConfigEntry( - data=mqtt_config_entry_data, - domain=mqtt.DOMAIN, - title="MQTT", - ) - - entry.add_to_hass(hass) - assert await hass.config_entries.async_setup(entry.entry_id) is False - - assert ("extra keys not allowed @ data['old_option']") in caplog.text - - -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.LIGHT]) @pytest.mark.parametrize( "hass_config", [ { "mqtt": { - "light": [ + "sensor": [ { "name": "test_manual", "unique_id": "test_manual_unique_id123", - "command_topic": "test-topic_manual", + "state_topic": "test-topic_manual", } ] } @@ -3366,15 +3324,16 @@ async def test_link_config_entry( config_discovery = { "name": "test_discovery", "unique_id": "test_discovery_unique456", - "command_topic": "test-topic_discovery", + "state_topic": "test-topic_discovery", } async_fire_mqtt_message( - hass, "homeassistant/light/bla/config", json.dumps(config_discovery) + hass, "homeassistant/sensor/bla/config", json.dumps(config_discovery) ) await hass.async_block_till_done() + 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 + assert hass.states.get("sensor.test_manual") is not None + assert hass.states.get("sensor.test_discovery") is not None entity_names = ["test_manual", "test_discovery"] # Check if both entities were linked to the MQTT config entry @@ -3402,7 +3361,7 @@ async def test_link_config_entry( assert _check_entities() == 1 # set up item through discovery async_fire_mqtt_message( - hass, "homeassistant/light/bla/config", json.dumps(config_discovery) + hass, "homeassistant/sensor/bla/config", json.dumps(config_discovery) ) await hass.async_block_till_done() assert _check_entities() == 2