mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
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:
parent
74e7f7c879
commit
d60fc0de38
@ -8,6 +8,7 @@ ABBREVIATIONS = {
|
|||||||
"aux_stat_tpl": "aux_state_template",
|
"aux_stat_tpl": "aux_state_template",
|
||||||
"aux_stat_t": "aux_state_topic",
|
"aux_stat_t": "aux_state_topic",
|
||||||
"avty": "availability",
|
"avty": "availability",
|
||||||
|
"avty_mode": "availability_mode",
|
||||||
"avty_t": "availability_topic",
|
"avty_t": "availability_topic",
|
||||||
"away_mode_cmd_t": "away_mode_command_topic",
|
"away_mode_cmd_t": "away_mode_command_topic",
|
||||||
"away_mode_stat_tpl": "away_mode_state_template",
|
"away_mode_stat_tpl": "away_mode_state_template",
|
||||||
|
@ -42,7 +42,14 @@ from .util import valid_subscribe_topic
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_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 = "availability"
|
||||||
|
CONF_AVAILABILITY_MODE = "availability_mode"
|
||||||
CONF_AVAILABILITY_TOPIC = "availability_topic"
|
CONF_AVAILABILITY_TOPIC = "availability_topic"
|
||||||
CONF_PAYLOAD_AVAILABLE = "payload_available"
|
CONF_PAYLOAD_AVAILABLE = "payload_available"
|
||||||
CONF_PAYLOAD_NOT_AVAILABLE = "payload_not_available"
|
CONF_PAYLOAD_NOT_AVAILABLE = "payload_not_available"
|
||||||
@ -71,6 +78,9 @@ MQTT_AVAILABILITY_SINGLE_SCHEMA = vol.Schema(
|
|||||||
|
|
||||||
MQTT_AVAILABILITY_LIST_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(
|
vol.Exclusive(CONF_AVAILABILITY, "availability"): vol.All(
|
||||||
cv.ensure_list,
|
cv.ensure_list,
|
||||||
[
|
[
|
||||||
@ -227,7 +237,8 @@ class MqttAvailability(Entity):
|
|||||||
def __init__(self, config: dict) -> None:
|
def __init__(self, config: dict) -> None:
|
||||||
"""Initialize the availability mixin."""
|
"""Initialize the availability mixin."""
|
||||||
self._availability_sub_state = None
|
self._availability_sub_state = None
|
||||||
self._available = False
|
self._available = {}
|
||||||
|
self._available_latest = False
|
||||||
self._availability_setup_from_config(config)
|
self._availability_setup_from_config(config)
|
||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
@ -275,12 +286,15 @@ class MqttAvailability(Entity):
|
|||||||
"""Handle a new received MQTT availability message."""
|
"""Handle a new received MQTT availability message."""
|
||||||
topic = msg.topic
|
topic = msg.topic
|
||||||
if msg.payload == self._avail_topics[topic][CONF_PAYLOAD_AVAILABLE]:
|
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]:
|
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.async_write_ha_state()
|
||||||
|
|
||||||
|
self._available = {topic: False for topic in self._avail_topics}
|
||||||
topics = {
|
topics = {
|
||||||
f"availability_{topic}": {
|
f"availability_{topic}": {
|
||||||
"topic": topic,
|
"topic": topic,
|
||||||
@ -313,7 +327,13 @@ class MqttAvailability(Entity):
|
|||||||
"""Return if the device is available."""
|
"""Return if the device is available."""
|
||||||
if not self.hass.data[DATA_MQTT].connected and not self.hass.is_stopping:
|
if not self.hass.data[DATA_MQTT].connected and not self.hass.is_stopping:
|
||||||
return False
|
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):
|
async def cleanup_device_registry(hass, device_id):
|
||||||
|
@ -171,6 +171,135 @@ async def help_test_default_availability_list_payload(
|
|||||||
assert state.state != STATE_UNAVAILABLE
|
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(
|
async def help_test_default_availability_list_single(
|
||||||
hass,
|
hass,
|
||||||
mqtt_mock,
|
mqtt_mock,
|
||||||
|
@ -17,6 +17,8 @@ from .test_common import (
|
|||||||
help_test_availability_without_topic,
|
help_test_availability_without_topic,
|
||||||
help_test_custom_availability_payload,
|
help_test_custom_availability_payload,
|
||||||
help_test_default_availability_list_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_list_single,
|
||||||
help_test_default_availability_payload,
|
help_test_default_availability_payload,
|
||||||
help_test_discovery_broken,
|
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):
|
async def test_default_availability_list_single(hass, mqtt_mock, caplog):
|
||||||
"""Test availability list and availability_topic are mutually exclusive."""
|
"""Test availability list and availability_topic are mutually exclusive."""
|
||||||
await help_test_default_availability_list_single(
|
await help_test_default_availability_list_single(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user