Add availability_mode "all" and "any" to MQTT entities (#44987)

* Add availability_mode "all" to MQTT entities

* Add availability mode any
This commit is contained in:
Erik Montnemery 2021-01-11 16:04:22 +01:00 committed by GitHub
parent 74e7f7c879
commit d60fc0de38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 170 additions and 4 deletions

View File

@ -8,6 +8,7 @@ ABBREVIATIONS = {
"aux_stat_tpl": "aux_state_template",
"aux_stat_t": "aux_state_topic",
"avty": "availability",
"avty_mode": "availability_mode",
"avty_t": "availability_topic",
"away_mode_cmd_t": "away_mode_command_topic",
"away_mode_stat_tpl": "away_mode_state_template",

View File

@ -42,7 +42,14 @@ from .util import valid_subscribe_topic
_LOGGER = logging.getLogger(__name__)
AVAILABILITY_ALL = "all"
AVAILABILITY_ANY = "any"
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_PAYLOAD_AVAILABLE = "payload_available"
CONF_PAYLOAD_NOT_AVAILABLE = "payload_not_available"
@ -71,6 +78,9 @@ MQTT_AVAILABILITY_SINGLE_SCHEMA = vol.Schema(
MQTT_AVAILABILITY_LIST_SCHEMA = vol.Schema(
{
vol.Optional(CONF_AVAILABILITY_MODE, default=AVAILABILITY_LATEST): vol.All(
cv.string, vol.In(AVAILABILITY_MODES)
),
vol.Exclusive(CONF_AVAILABILITY, "availability"): vol.All(
cv.ensure_list,
[
@ -227,7 +237,8 @@ class MqttAvailability(Entity):
def __init__(self, config: dict) -> None:
"""Initialize the availability mixin."""
self._availability_sub_state = None
self._available = False
self._available = {}
self._available_latest = False
self._availability_setup_from_config(config)
async def async_added_to_hass(self) -> None:
@ -275,12 +286,15 @@ class MqttAvailability(Entity):
"""Handle a new received MQTT availability message."""
topic = msg.topic
if msg.payload == self._avail_topics[topic][CONF_PAYLOAD_AVAILABLE]:
self._available = True
self._available[topic] = True
self._available_latest = True
elif msg.payload == self._avail_topics[topic][CONF_PAYLOAD_NOT_AVAILABLE]:
self._available = False
self._available[topic] = False
self._available_latest = False
self.async_write_ha_state()
self._available = {topic: False for topic in self._avail_topics}
topics = {
f"availability_{topic}": {
"topic": topic,
@ -313,7 +327,13 @@ class MqttAvailability(Entity):
"""Return if the device is available."""
if not self.hass.data[DATA_MQTT].connected and not self.hass.is_stopping:
return False
return not self._avail_topics or self._available
if not self._avail_topics:
return True
if self._avail_config[CONF_AVAILABILITY_MODE] == AVAILABILITY_ALL:
return all(self._available.values())
if self._avail_config[CONF_AVAILABILITY_MODE] == AVAILABILITY_ANY:
return any(self._available.values())
return self._available_latest
async def cleanup_device_registry(hass, device_id):

View File

@ -171,6 +171,135 @@ async def help_test_default_availability_list_payload(
assert state.state != STATE_UNAVAILABLE
async def help_test_default_availability_list_payload_all(
hass,
mqtt_mock,
domain,
config,
no_assumed_state=False,
state_topic=None,
state_message=None,
):
"""Test availability by default payload with defined topic.
This is a test helper for the MqttAvailability mixin.
"""
# Add availability settings to config
config = copy.deepcopy(config)
config[domain]["availability_mode"] = "all"
config[domain]["availability"] = [
{"topic": "availability-topic1"},
{"topic": "availability-topic2"},
]
assert await async_setup_component(
hass,
domain,
config,
)
await hass.async_block_till_done()
state = hass.states.get(f"{domain}.test")
assert state.state == STATE_UNAVAILABLE
async_fire_mqtt_message(hass, "availability-topic1", "online")
state = hass.states.get(f"{domain}.test")
assert state.state == STATE_UNAVAILABLE
if no_assumed_state:
assert not state.attributes.get(ATTR_ASSUMED_STATE)
async_fire_mqtt_message(hass, "availability-topic2", "online")
state = hass.states.get(f"{domain}.test")
assert state.state != STATE_UNAVAILABLE
async_fire_mqtt_message(hass, "availability-topic2", "offline")
state = hass.states.get(f"{domain}.test")
assert state.state == STATE_UNAVAILABLE
if no_assumed_state:
assert not state.attributes.get(ATTR_ASSUMED_STATE)
async_fire_mqtt_message(hass, "availability-topic2", "online")
state = hass.states.get(f"{domain}.test")
assert state.state != STATE_UNAVAILABLE
async_fire_mqtt_message(hass, "availability-topic1", "offline")
state = hass.states.get(f"{domain}.test")
assert state.state == STATE_UNAVAILABLE
if no_assumed_state:
assert not state.attributes.get(ATTR_ASSUMED_STATE)
async_fire_mqtt_message(hass, "availability-topic1", "online")
state = hass.states.get(f"{domain}.test")
assert state.state != STATE_UNAVAILABLE
async def help_test_default_availability_list_payload_any(
hass,
mqtt_mock,
domain,
config,
no_assumed_state=False,
state_topic=None,
state_message=None,
):
"""Test availability by default payload with defined topic.
This is a test helper for the MqttAvailability mixin.
"""
# Add availability settings to config
config = copy.deepcopy(config)
config[domain]["availability_mode"] = "any"
config[domain]["availability"] = [
{"topic": "availability-topic1"},
{"topic": "availability-topic2"},
]
assert await async_setup_component(
hass,
domain,
config,
)
await hass.async_block_till_done()
state = hass.states.get(f"{domain}.test")
assert state.state == STATE_UNAVAILABLE
async_fire_mqtt_message(hass, "availability-topic1", "online")
state = hass.states.get(f"{domain}.test")
assert state.state != STATE_UNAVAILABLE
if no_assumed_state:
assert not state.attributes.get(ATTR_ASSUMED_STATE)
async_fire_mqtt_message(hass, "availability-topic2", "online")
state = hass.states.get(f"{domain}.test")
assert state.state != STATE_UNAVAILABLE
async_fire_mqtt_message(hass, "availability-topic2", "offline")
state = hass.states.get(f"{domain}.test")
assert state.state != STATE_UNAVAILABLE
if no_assumed_state:
assert not state.attributes.get(ATTR_ASSUMED_STATE)
async_fire_mqtt_message(hass, "availability-topic1", "offline")
state = hass.states.get(f"{domain}.test")
assert state.state == STATE_UNAVAILABLE
async_fire_mqtt_message(hass, "availability-topic1", "online")
state = hass.states.get(f"{domain}.test")
assert state.state != STATE_UNAVAILABLE
if no_assumed_state:
assert not state.attributes.get(ATTR_ASSUMED_STATE)
async def help_test_default_availability_list_single(
hass,
mqtt_mock,

View File

@ -17,6 +17,8 @@ from .test_common import (
help_test_availability_without_topic,
help_test_custom_availability_payload,
help_test_default_availability_list_payload,
help_test_default_availability_list_payload_all,
help_test_default_availability_list_payload_any,
help_test_default_availability_list_single,
help_test_default_availability_payload,
help_test_discovery_broken,
@ -297,6 +299,20 @@ async def test_default_availability_list_payload(hass, mqtt_mock):
)
async def test_default_availability_list_payload_all(hass, mqtt_mock):
"""Test availability by default payload with defined topic."""
await help_test_default_availability_list_payload_all(
hass, mqtt_mock, sensor.DOMAIN, DEFAULT_CONFIG
)
async def test_default_availability_list_payload_any(hass, mqtt_mock):
"""Test availability by default payload with defined topic."""
await help_test_default_availability_list_payload_any(
hass, mqtt_mock, sensor.DOMAIN, DEFAULT_CONFIG
)
async def test_default_availability_list_single(hass, mqtt_mock, caplog):
"""Test availability list and availability_topic are mutually exclusive."""
await help_test_default_availability_list_single(