diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index b743626ebbb..c610a430711 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -110,7 +110,7 @@ PROTOCOL_31 = "3.1" DEFAULT_PORT = 1883 DEFAULT_KEEPALIVE = 60 DEFAULT_PROTOCOL = PROTOCOL_311 -DEFAULT_DISCOVERY_PREFIX = "homeassistant" +DEFAULT_PREFIX = "homeassistant" DEFAULT_TLS_PROTOCOL = "auto" DEFAULT_PAYLOAD_AVAILABLE = "online" DEFAULT_PAYLOAD_NOT_AVAILABLE = "offline" @@ -139,10 +139,24 @@ CLIENT_KEY_AUTH_MSG = ( "the MQTT broker configuration" ) +DEFAULT_BIRTH = { + ATTR_TOPIC: DEFAULT_PREFIX + "/status", + CONF_PAYLOAD: DEFAULT_PAYLOAD_AVAILABLE, + ATTR_QOS: DEFAULT_QOS, + ATTR_RETAIN: DEFAULT_RETAIN, +} + +DEFAULT_WILL = { + ATTR_TOPIC: DEFAULT_PREFIX + "/status", + CONF_PAYLOAD: DEFAULT_PAYLOAD_NOT_AVAILABLE, + ATTR_QOS: DEFAULT_QOS, + ATTR_RETAIN: DEFAULT_RETAIN, +} + MQTT_WILL_BIRTH_SCHEMA = vol.Schema( { - vol.Required(ATTR_TOPIC): valid_publish_topic, - vol.Required(ATTR_PAYLOAD, CONF_PAYLOAD): cv.string, + 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, }, @@ -188,13 +202,17 @@ CONFIG_SCHEMA = vol.Schema( 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_WILL_MESSAGE, default=DEFAULT_WILL + ): MQTT_WILL_BIRTH_SCHEMA, + vol.Optional( + CONF_BIRTH_MESSAGE, default=DEFAULT_BIRTH + ): MQTT_WILL_BIRTH_SCHEMA, 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_DISCOVERY_PREFIX + CONF_DISCOVERY_PREFIX, default=DEFAULT_PREFIX ): valid_publish_topic, } ), @@ -698,7 +716,10 @@ class MQTT: self._mqttc.on_disconnect = self._mqtt_on_disconnect self._mqttc.on_message = self._mqtt_on_message - if CONF_WILL_MESSAGE in self.conf: + if ( + CONF_WILL_MESSAGE in self.conf + and ATTR_TOPIC in self.conf[CONF_WILL_MESSAGE] + ): will_message = Message(**self.conf[CONF_WILL_MESSAGE]) else: will_message = None @@ -749,7 +770,7 @@ class MQTT: def stop(): """Stop the MQTT client.""" - self._mqttc.disconnect() + # Do not disconnect, we want the broker to always publish will self._mqttc.loop_stop() await self.hass.async_add_executor_job(stop) @@ -848,7 +869,10 @@ class MQTT: max_qos = max(subscription.qos for subscription in subs) self.hass.add_job(self._async_perform_subscription, topic, max_qos) - if CONF_BIRTH_MESSAGE in self.conf: + if ( + CONF_BIRTH_MESSAGE in self.conf + and ATTR_TOPIC in self.conf[CONF_BIRTH_MESSAGE] + ): birth_message = Message(**self.conf[CONF_BIRTH_MESSAGE]) self.hass.add_job( self.async_publish( # pylint: disable=no-value-for-parameter diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 58a982544f0..3dee1dc874b 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -755,7 +755,7 @@ async def test_setup_without_tls_config_uses_tlsv1_under_python36(hass): } ], ) -async def test_birth_message(hass, mqtt_client_mock, mqtt_mock): +async def test_custom_birth_message(hass, mqtt_client_mock, mqtt_mock): """Test sending birth message.""" calls = [] mqtt_client_mock.publish.side_effect = lambda *args: calls.append(args) @@ -764,6 +764,62 @@ async def test_birth_message(hass, mqtt_client_mock, mqtt_mock): assert calls[-1] == ("birth", "birth", 0, False) +async def test_default_birth_message(hass, mqtt_client_mock, mqtt_mock): + """Test sending birth message.""" + calls = [] + mqtt_client_mock.publish.side_effect = lambda *args: calls.append(args) + mqtt_mock._mqtt_on_connect(None, None, 0, 0) + await hass.async_block_till_done() + assert calls[-1] == ("homeassistant/status", "online", 0, False) + + +@pytest.mark.parametrize( + "mqtt_config", [{mqtt.CONF_BROKER: "mock-broker", mqtt.CONF_BIRTH_MESSAGE: {}}], +) +async def test_no_birth_message(hass, mqtt_client_mock, mqtt_mock): + """Test sending birth message.""" + calls = [] + mqtt_client_mock.publish.side_effect = lambda *args: calls.append(args) + mqtt_mock._mqtt_on_connect(None, None, 0, 0) + await hass.async_block_till_done() + assert not calls + + +@pytest.mark.parametrize( + "mqtt_config", + [ + { + mqtt.CONF_BROKER: "mock-broker", + mqtt.CONF_WILL_MESSAGE: { + mqtt.ATTR_TOPIC: "death", + mqtt.ATTR_PAYLOAD: "death", + }, + } + ], +) +async def test_custom_will_message(hass, mqtt_client_mock, mqtt_mock): + """Test will message.""" + mqtt_client_mock.will_set.assert_called_with("death", "death", 0, False, None) + + +async def test_default_will_message(hass, mqtt_client_mock, mqtt_mock): + """Test will message.""" + mqtt_client_mock.will_set.assert_called_with( + "homeassistant/status", "offline", 0, False, None + ) + + +@pytest.mark.parametrize( + "mqtt_config", [{mqtt.CONF_BROKER: "mock-broker", mqtt.CONF_WILL_MESSAGE: {}}], +) +async def test_no_will_message(hass, mqtt_client_mock, mqtt_mock): + """Test will message.""" + mqtt_client_mock.will_set.assert_not_called() + + +@pytest.mark.parametrize( + "mqtt_config", [{mqtt.CONF_BROKER: "mock-broker", mqtt.CONF_BIRTH_MESSAGE: {}}], +) async def test_mqtt_subscribes_topics_on_connect(hass, mqtt_client_mock, mqtt_mock): """Test subscription to topic on connect.""" await mqtt.async_subscribe(hass, "topic/test", None)