mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 19:27:45 +00:00
Add MQTT availability template and encoding (#60470)
* Add MQTT availability template and encoding * use generic encoding field * pylint and cleanup * remove additional topic check
This commit is contained in:
parent
42bae5439b
commit
3307e54363
@ -10,6 +10,7 @@ ABBREVIATIONS = {
|
|||||||
"avty": "availability",
|
"avty": "availability",
|
||||||
"avty_mode": "availability_mode",
|
"avty_mode": "availability_mode",
|
||||||
"avty_t": "availability_topic",
|
"avty_t": "availability_topic",
|
||||||
|
"avty_tpl": "availability_template",
|
||||||
"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",
|
||||||
"away_mode_stat_t": "away_mode_state_topic",
|
"away_mode_stat_t": "away_mode_state_topic",
|
||||||
|
@ -21,6 +21,7 @@ from homeassistant.const import (
|
|||||||
CONF_ICON,
|
CONF_ICON,
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
CONF_UNIQUE_ID,
|
CONF_UNIQUE_ID,
|
||||||
|
CONF_VALUE_TEMPLATE,
|
||||||
)
|
)
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
@ -42,6 +43,7 @@ from .const import (
|
|||||||
ATTR_DISCOVERY_PAYLOAD,
|
ATTR_DISCOVERY_PAYLOAD,
|
||||||
ATTR_DISCOVERY_TOPIC,
|
ATTR_DISCOVERY_TOPIC,
|
||||||
CONF_AVAILABILITY,
|
CONF_AVAILABILITY,
|
||||||
|
CONF_ENCODING,
|
||||||
CONF_QOS,
|
CONF_QOS,
|
||||||
CONF_TOPIC,
|
CONF_TOPIC,
|
||||||
DEFAULT_PAYLOAD_AVAILABLE,
|
DEFAULT_PAYLOAD_AVAILABLE,
|
||||||
@ -71,6 +73,7 @@ AVAILABILITY_LATEST = "latest"
|
|||||||
AVAILABILITY_MODES = [AVAILABILITY_ALL, AVAILABILITY_ANY, AVAILABILITY_LATEST]
|
AVAILABILITY_MODES = [AVAILABILITY_ALL, AVAILABILITY_ANY, AVAILABILITY_LATEST]
|
||||||
|
|
||||||
CONF_AVAILABILITY_MODE = "availability_mode"
|
CONF_AVAILABILITY_MODE = "availability_mode"
|
||||||
|
CONF_AVAILABILITY_TEMPLATE = "availability_template"
|
||||||
CONF_AVAILABILITY_TOPIC = "availability_topic"
|
CONF_AVAILABILITY_TOPIC = "availability_topic"
|
||||||
CONF_ENABLED_BY_DEFAULT = "enabled_by_default"
|
CONF_ENABLED_BY_DEFAULT = "enabled_by_default"
|
||||||
CONF_PAYLOAD_AVAILABLE = "payload_available"
|
CONF_PAYLOAD_AVAILABLE = "payload_available"
|
||||||
@ -112,6 +115,7 @@ MQTT_ATTRIBUTES_BLOCKED = {
|
|||||||
MQTT_AVAILABILITY_SINGLE_SCHEMA = vol.Schema(
|
MQTT_AVAILABILITY_SINGLE_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Exclusive(CONF_AVAILABILITY_TOPIC, "availability"): valid_subscribe_topic,
|
vol.Exclusive(CONF_AVAILABILITY_TOPIC, "availability"): valid_subscribe_topic,
|
||||||
|
vol.Optional(CONF_AVAILABILITY_TEMPLATE): cv.template,
|
||||||
vol.Optional(
|
vol.Optional(
|
||||||
CONF_PAYLOAD_AVAILABLE, default=DEFAULT_PAYLOAD_AVAILABLE
|
CONF_PAYLOAD_AVAILABLE, default=DEFAULT_PAYLOAD_AVAILABLE
|
||||||
): cv.string,
|
): cv.string,
|
||||||
@ -138,6 +142,7 @@ MQTT_AVAILABILITY_LIST_SCHEMA = vol.Schema(
|
|||||||
CONF_PAYLOAD_NOT_AVAILABLE,
|
CONF_PAYLOAD_NOT_AVAILABLE,
|
||||||
default=DEFAULT_PAYLOAD_NOT_AVAILABLE,
|
default=DEFAULT_PAYLOAD_NOT_AVAILABLE,
|
||||||
): cv.string,
|
): cv.string,
|
||||||
|
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -335,6 +340,7 @@ class MqttAvailability(Entity):
|
|||||||
self._avail_topics[config[CONF_AVAILABILITY_TOPIC]] = {
|
self._avail_topics[config[CONF_AVAILABILITY_TOPIC]] = {
|
||||||
CONF_PAYLOAD_AVAILABLE: config[CONF_PAYLOAD_AVAILABLE],
|
CONF_PAYLOAD_AVAILABLE: config[CONF_PAYLOAD_AVAILABLE],
|
||||||
CONF_PAYLOAD_NOT_AVAILABLE: config[CONF_PAYLOAD_NOT_AVAILABLE],
|
CONF_PAYLOAD_NOT_AVAILABLE: config[CONF_PAYLOAD_NOT_AVAILABLE],
|
||||||
|
CONF_AVAILABILITY_TEMPLATE: config.get(CONF_AVAILABILITY_TEMPLATE),
|
||||||
}
|
}
|
||||||
|
|
||||||
if CONF_AVAILABILITY in config:
|
if CONF_AVAILABILITY in config:
|
||||||
@ -342,8 +348,22 @@ class MqttAvailability(Entity):
|
|||||||
self._avail_topics[avail[CONF_TOPIC]] = {
|
self._avail_topics[avail[CONF_TOPIC]] = {
|
||||||
CONF_PAYLOAD_AVAILABLE: avail[CONF_PAYLOAD_AVAILABLE],
|
CONF_PAYLOAD_AVAILABLE: avail[CONF_PAYLOAD_AVAILABLE],
|
||||||
CONF_PAYLOAD_NOT_AVAILABLE: avail[CONF_PAYLOAD_NOT_AVAILABLE],
|
CONF_PAYLOAD_NOT_AVAILABLE: avail[CONF_PAYLOAD_NOT_AVAILABLE],
|
||||||
|
CONF_AVAILABILITY_TEMPLATE: avail.get(CONF_VALUE_TEMPLATE),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (
|
||||||
|
topic, # pylint: disable=unused-variable
|
||||||
|
avail_topic_conf,
|
||||||
|
) in self._avail_topics.items():
|
||||||
|
tpl = avail_topic_conf[CONF_AVAILABILITY_TEMPLATE]
|
||||||
|
if tpl is None:
|
||||||
|
avail_topic_conf[CONF_AVAILABILITY_TEMPLATE] = lambda value: value
|
||||||
|
else:
|
||||||
|
tpl.hass = self.hass
|
||||||
|
avail_topic_conf[
|
||||||
|
CONF_AVAILABILITY_TEMPLATE
|
||||||
|
] = tpl.async_render_with_possible_json_value
|
||||||
|
|
||||||
self._avail_config = config
|
self._avail_config = config
|
||||||
|
|
||||||
async def _availability_subscribe_topics(self):
|
async def _availability_subscribe_topics(self):
|
||||||
@ -354,10 +374,11 @@ class MqttAvailability(Entity):
|
|||||||
def availability_message_received(msg: ReceiveMessage) -> None:
|
def availability_message_received(msg: ReceiveMessage) -> None:
|
||||||
"""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]:
|
payload = self._avail_topics[topic][CONF_AVAILABILITY_TEMPLATE](msg.payload)
|
||||||
|
if payload == self._avail_topics[topic][CONF_PAYLOAD_AVAILABLE]:
|
||||||
self._available[topic] = True
|
self._available[topic] = True
|
||||||
self._available_latest = True
|
self._available_latest = True
|
||||||
elif msg.payload == self._avail_topics[topic][CONF_PAYLOAD_NOT_AVAILABLE]:
|
elif payload == self._avail_topics[topic][CONF_PAYLOAD_NOT_AVAILABLE]:
|
||||||
self._available[topic] = False
|
self._available[topic] = False
|
||||||
self._available_latest = False
|
self._available_latest = False
|
||||||
|
|
||||||
@ -372,6 +393,7 @@ class MqttAvailability(Entity):
|
|||||||
"topic": topic,
|
"topic": topic,
|
||||||
"msg_callback": availability_message_received,
|
"msg_callback": availability_message_received,
|
||||||
"qos": self._avail_config[CONF_QOS],
|
"qos": self._avail_config[CONF_QOS],
|
||||||
|
"encoding": self._avail_config[CONF_ENCODING] or None,
|
||||||
}
|
}
|
||||||
for topic in self._avail_topics
|
for topic in self._avail_topics
|
||||||
}
|
}
|
||||||
|
@ -737,6 +737,100 @@ async def test_discovery_expansion_3(hass, mqtt_mock, caplog):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_discovery_expansion_without_encoding_and_value_template_1(
|
||||||
|
hass, mqtt_mock, caplog
|
||||||
|
):
|
||||||
|
"""Test expansion of raw availability payload with a template as list."""
|
||||||
|
data = (
|
||||||
|
'{ "~": "some/base/topic",'
|
||||||
|
' "name": "DiscoveryExpansionTest1",'
|
||||||
|
' "stat_t": "test_topic/~",'
|
||||||
|
' "cmd_t": "~/test_topic",'
|
||||||
|
' "encoding":"",'
|
||||||
|
' "availability": [{'
|
||||||
|
' "topic":"~/avail_item1",'
|
||||||
|
' "payload_available": "1",'
|
||||||
|
' "payload_not_available": "0",'
|
||||||
|
' "value_template":"{{ value | bitwise_and(1) }}"'
|
||||||
|
" }],"
|
||||||
|
' "dev":{'
|
||||||
|
' "ids":["5706DF"],'
|
||||||
|
' "name":"DiscoveryExpansionTest1 Device",'
|
||||||
|
' "mdl":"Generic",'
|
||||||
|
' "sw":"1.2.3.4",'
|
||||||
|
' "mf":"None",'
|
||||||
|
' "sa":"default_area"'
|
||||||
|
" }"
|
||||||
|
"}"
|
||||||
|
)
|
||||||
|
|
||||||
|
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, "some/base/topic/avail_item1", b"\x01")
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("switch.DiscoveryExpansionTest1")
|
||||||
|
assert state is not None
|
||||||
|
assert state.name == "DiscoveryExpansionTest1"
|
||||||
|
assert ("switch", "bla") in hass.data[ALREADY_DISCOVERED]
|
||||||
|
assert state.state == STATE_OFF
|
||||||
|
|
||||||
|
async_fire_mqtt_message(hass, "some/base/topic/avail_item1", b"\x00")
|
||||||
|
|
||||||
|
state = hass.states.get("switch.DiscoveryExpansionTest1")
|
||||||
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
|
async def test_discovery_expansion_without_encoding_and_value_template_2(
|
||||||
|
hass, mqtt_mock, caplog
|
||||||
|
):
|
||||||
|
"""Test expansion of raw availability payload with a template directly."""
|
||||||
|
data = (
|
||||||
|
'{ "~": "some/base/topic",'
|
||||||
|
' "name": "DiscoveryExpansionTest1",'
|
||||||
|
' "stat_t": "test_topic/~",'
|
||||||
|
' "cmd_t": "~/test_topic",'
|
||||||
|
' "availability_topic":"~/avail_item1",'
|
||||||
|
' "payload_available": "1",'
|
||||||
|
' "payload_not_available": "0",'
|
||||||
|
' "encoding":"",'
|
||||||
|
' "availability_template":"{{ value | bitwise_and(1) }}",'
|
||||||
|
' "dev":{'
|
||||||
|
' "ids":["5706DF"],'
|
||||||
|
' "name":"DiscoveryExpansionTest1 Device",'
|
||||||
|
' "mdl":"Generic",'
|
||||||
|
' "sw":"1.2.3.4",'
|
||||||
|
' "mf":"None",'
|
||||||
|
' "sa":"default_area"'
|
||||||
|
" }"
|
||||||
|
"}"
|
||||||
|
)
|
||||||
|
|
||||||
|
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, "some/base/topic/avail_item1", b"\x01")
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("switch.DiscoveryExpansionTest1")
|
||||||
|
assert state is not None
|
||||||
|
assert state.name == "DiscoveryExpansionTest1"
|
||||||
|
assert ("switch", "bla") in hass.data[ALREADY_DISCOVERED]
|
||||||
|
assert state.state == STATE_OFF
|
||||||
|
|
||||||
|
async_fire_mqtt_message(hass, "some/base/topic/avail_item1", b"\x00")
|
||||||
|
|
||||||
|
state = hass.states.get("switch.DiscoveryExpansionTest1")
|
||||||
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
ABBREVIATIONS_WHITE_LIST = [
|
ABBREVIATIONS_WHITE_LIST = [
|
||||||
# MQTT client/server/trigger settings
|
# MQTT client/server/trigger settings
|
||||||
"CONF_BIRTH_MESSAGE",
|
"CONF_BIRTH_MESSAGE",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user