diff --git a/homeassistant/components/mqtt/const.py b/homeassistant/components/mqtt/const.py index 6c334eca311..6435670e92c 100644 --- a/homeassistant/components/mqtt/const.py +++ b/homeassistant/components/mqtt/const.py @@ -9,11 +9,13 @@ ATTR_QOS = "qos" ATTR_RETAIN = "retain" ATTR_TOPIC = "topic" +CONF_AVAILABILITY = "availability" CONF_BROKER = "broker" CONF_BIRTH_MESSAGE = "birth_message" CONF_QOS = ATTR_QOS CONF_RETAIN = ATTR_RETAIN CONF_STATE_TOPIC = "state_topic" +CONF_TOPIC = "topic" CONF_WILL_MESSAGE = "will_message" DATA_MQTT_CONFIG = "mqtt_config" diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py index 6996072d226..5678f31f5b2 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -22,6 +22,8 @@ from .const import ( ATTR_DISCOVERY_HASH, ATTR_DISCOVERY_PAYLOAD, ATTR_DISCOVERY_TOPIC, + CONF_AVAILABILITY, + CONF_TOPIC, DOMAIN, ) @@ -138,6 +140,14 @@ async def async_start( # noqa: C901 payload[key] = f"{base}{value[1:]}" if value[-1] == TOPIC_BASE and key.endswith("topic"): payload[key] = f"{value[:-1]}{base}" + if payload.get(CONF_AVAILABILITY): + for availability_conf in payload[CONF_AVAILABILITY]: + topic = availability_conf.get(CONF_TOPIC) + if topic: + if topic[0] == TOPIC_BASE: + availability_conf[CONF_TOPIC] = f"{base}{topic[1:]}" + if topic[-1] == TOPIC_BASE: + availability_conf[CONF_TOPIC] = f"{topic[:-1]}{base}" # If present, the node_id will be included in the discovered object id discovery_id = " ".join((node_id, object_id)) if node_id else object_id diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index e21f3f5c280..6c29938c75d 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -18,12 +18,14 @@ from homeassistant.helpers.dispatcher import ( from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import ConfigType -from . import CONF_TOPIC, DATA_MQTT, debug_info, publish, subscription +from . import DATA_MQTT, debug_info, publish, subscription from .const import ( ATTR_DISCOVERY_HASH, ATTR_DISCOVERY_PAYLOAD, ATTR_DISCOVERY_TOPIC, + CONF_AVAILABILITY, CONF_QOS, + CONF_TOPIC, DEFAULT_PAYLOAD_AVAILABLE, DEFAULT_PAYLOAD_NOT_AVAILABLE, DOMAIN, @@ -50,7 +52,6 @@ AVAILABILITY_LATEST = "latest" AVAILABILITY_MODES = [AVAILABILITY_ALL, AVAILABILITY_ANY, AVAILABILITY_LATEST] -CONF_AVAILABILITY = "availability" CONF_AVAILABILITY_MODE = "availability_mode" CONF_AVAILABILITY_TOPIC = "availability_topic" CONF_ENABLED_BY_DEFAULT = "enabled_by_default" @@ -108,7 +109,7 @@ MQTT_AVAILABILITY_LIST_SCHEMA = vol.Schema( cv.ensure_list, [ { - vol.Optional(CONF_TOPIC): valid_subscribe_topic, + vol.Required(CONF_TOPIC): valid_subscribe_topic, vol.Optional( CONF_PAYLOAD_AVAILABLE, default=DEFAULT_PAYLOAD_AVAILABLE ): cv.string, diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py index 907c3d398b6..8d2106c90fa 100644 --- a/tests/components/mqtt/test_discovery.py +++ b/tests/components/mqtt/test_discovery.py @@ -12,7 +12,12 @@ from homeassistant.components.mqtt.abbreviations import ( DEVICE_ABBREVIATIONS, ) from homeassistant.components.mqtt.discovery import ALREADY_DISCOVERED, async_start -from homeassistant.const import EVENT_STATE_CHANGED, STATE_OFF, STATE_ON +from homeassistant.const import ( + EVENT_STATE_CHANGED, + STATE_OFF, + STATE_ON, + STATE_UNAVAILABLE, +) import homeassistant.core as ha from tests.common import ( @@ -449,6 +454,18 @@ async def test_discovery_expansion(hass, mqtt_mock, caplog): ' "name": "DiscoveryExpansionTest1",' ' "stat_t": "test_topic/~",' ' "cmd_t": "~/test_topic",' + ' "availability": [' + " {" + ' "topic":"~/avail_item1",' + ' "payload_available": "available",' + ' "payload_not_available": "not_available"' + " }," + " {" + ' "topic":"avail_item2/~",' + ' "payload_available": "available",' + ' "payload_not_available": "not_available"' + " }" + " ]," ' "dev":{' ' "ids":["5706DF"],' ' "name":"DiscoveryExpansionTest1 Device",' @@ -463,6 +480,12 @@ async def test_discovery_expansion(hass, mqtt_mock, caplog): async_fire_mqtt_message(hass, "homeassistant/switch/bla/config", data) await hass.async_block_till_done() + state = hass.states.get("switch.DiscoveryExpansionTest1") + assert state.state == STATE_UNAVAILABLE + + async_fire_mqtt_message(hass, "avail_item2/some/base/topic", "available") + await hass.async_block_till_done() + state = hass.states.get("switch.DiscoveryExpansionTest1") assert state is not None assert state.name == "DiscoveryExpansionTest1" @@ -474,6 +497,12 @@ async def test_discovery_expansion(hass, mqtt_mock, caplog): state = hass.states.get("switch.DiscoveryExpansionTest1") assert state.state == STATE_ON + async_fire_mqtt_message(hass, "some/base/topic/avail_item1", "not_available") + await hass.async_block_till_done() + + state = hass.states.get("switch.DiscoveryExpansionTest1") + assert state.state == STATE_UNAVAILABLE + ABBREVIATIONS_WHITE_LIST = [ # MQTT client/server/trigger settings