mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Add origin info support for MQTT discovered items (#98782)
* Add integration info support for MQTT discovery. * Moving logs to discovery * Revert adding class property * Rename to origin * Follow up comments
This commit is contained in:
parent
fe164d06a7
commit
8b232047c4
@ -111,6 +111,7 @@ ABBREVIATIONS = {
|
||||
"mode_stat_tpl": "mode_state_template",
|
||||
"modes": "modes",
|
||||
"name": "name",
|
||||
"o": "origin",
|
||||
"obj_id": "object_id",
|
||||
"off_dly": "off_delay",
|
||||
"on_cmd_type": "on_command_type",
|
||||
@ -275,3 +276,9 @@ DEVICE_ABBREVIATIONS = {
|
||||
"sw": "sw_version",
|
||||
"sa": "suggested_area",
|
||||
}
|
||||
|
||||
ORIGIN_ABBREVIATIONS = {
|
||||
"name": "name",
|
||||
"sw": "sw_version",
|
||||
"url": "support_url",
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ CONF_COMMAND_TOPIC = "command_topic"
|
||||
CONF_DISCOVERY_PREFIX = "discovery_prefix"
|
||||
CONF_ENCODING = "encoding"
|
||||
CONF_KEEPALIVE = "keepalive"
|
||||
CONF_ORIGIN = "origin"
|
||||
CONF_QOS = ATTR_QOS
|
||||
CONF_RETAIN = ATTR_RETAIN
|
||||
CONF_SCHEMA = "schema"
|
||||
@ -57,6 +58,19 @@ CONF_CLIENT_KEY = "client_key"
|
||||
CONF_CLIENT_CERT = "client_cert"
|
||||
CONF_TLS_INSECURE = "tls_insecure"
|
||||
|
||||
# Device and integration info options
|
||||
CONF_IDENTIFIERS = "identifiers"
|
||||
CONF_CONNECTIONS = "connections"
|
||||
CONF_MANUFACTURER = "manufacturer"
|
||||
CONF_HW_VERSION = "hw_version"
|
||||
CONF_SW_VERSION = "sw_version"
|
||||
CONF_VIA_DEVICE = "via_device"
|
||||
CONF_DEPRECATED_VIA_HUB = "via_hub"
|
||||
CONF_SUGGESTED_AREA = "suggested_area"
|
||||
CONF_CONFIGURATION_URL = "configuration_url"
|
||||
CONF_OBJECT_ID = "object_id"
|
||||
CONF_SUPPORT_URL = "support_url"
|
||||
|
||||
DATA_MQTT = "mqtt"
|
||||
DATA_MQTT_AVAILABLE = "mqtt_client_available"
|
||||
|
||||
|
@ -9,8 +9,10 @@ import re
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_DEVICE, CONF_PLATFORM
|
||||
from homeassistant.const import CONF_DEVICE, CONF_NAME, CONF_PLATFORM
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
@ -24,16 +26,19 @@ from homeassistant.loader import async_get_mqtt
|
||||
from homeassistant.util.json import json_loads_object
|
||||
|
||||
from .. import mqtt
|
||||
from .abbreviations import ABBREVIATIONS, DEVICE_ABBREVIATIONS
|
||||
from .abbreviations import ABBREVIATIONS, DEVICE_ABBREVIATIONS, ORIGIN_ABBREVIATIONS
|
||||
from .const import (
|
||||
ATTR_DISCOVERY_HASH,
|
||||
ATTR_DISCOVERY_PAYLOAD,
|
||||
ATTR_DISCOVERY_TOPIC,
|
||||
CONF_AVAILABILITY,
|
||||
CONF_ORIGIN,
|
||||
CONF_SUPPORT_URL,
|
||||
CONF_SW_VERSION,
|
||||
CONF_TOPIC,
|
||||
DOMAIN,
|
||||
)
|
||||
from .models import ReceiveMessage
|
||||
from .models import MqttOriginInfo, ReceiveMessage
|
||||
from .util import get_mqtt_data
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -77,6 +82,16 @@ MQTT_DISCOVERY_DONE = "mqtt_discovery_done_{}"
|
||||
|
||||
TOPIC_BASE = "~"
|
||||
|
||||
MQTT_ORIGIN_INFO_SCHEMA = vol.All(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_NAME): cv.string,
|
||||
vol.Optional(CONF_SW_VERSION): cv.string,
|
||||
vol.Optional(CONF_SUPPORT_URL): cv.configuration_url,
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class MQTTDiscoveryPayload(dict[str, Any]):
|
||||
"""Class to hold and MQTT discovery payload and discovery data."""
|
||||
@ -94,6 +109,30 @@ def set_discovery_hash(hass: HomeAssistant, discovery_hash: tuple[str, str]) ->
|
||||
get_mqtt_data(hass).discovery_already_discovered.add(discovery_hash)
|
||||
|
||||
|
||||
@callback
|
||||
def async_log_discovery_origin_info(
|
||||
message: str, discovery_payload: MQTTDiscoveryPayload
|
||||
) -> None:
|
||||
"""Log information about the discovery and origin."""
|
||||
if CONF_ORIGIN not in discovery_payload:
|
||||
_LOGGER.info(message)
|
||||
return
|
||||
origin_info: MqttOriginInfo = discovery_payload[CONF_ORIGIN]
|
||||
sw_version_log = ""
|
||||
if sw_version := origin_info.get("sw_version"):
|
||||
sw_version_log = f", version: {sw_version}"
|
||||
support_url_log = ""
|
||||
if support_url := origin_info.get("support_url"):
|
||||
support_url_log = f", support URL: {support_url}"
|
||||
_LOGGER.info(
|
||||
"%s from external application %s%s%s",
|
||||
message,
|
||||
origin_info["name"],
|
||||
sw_version_log,
|
||||
support_url_log,
|
||||
)
|
||||
|
||||
|
||||
async def async_start( # noqa: C901
|
||||
hass: HomeAssistant, discovery_topic: str, config_entry: ConfigEntry
|
||||
) -> None:
|
||||
@ -149,6 +188,22 @@ async def async_start( # noqa: C901
|
||||
key = DEVICE_ABBREVIATIONS.get(key, key)
|
||||
device[key] = device.pop(abbreviated_key)
|
||||
|
||||
if CONF_ORIGIN in discovery_payload:
|
||||
origin_info: dict[str, Any] = discovery_payload[CONF_ORIGIN]
|
||||
try:
|
||||
for key in list(origin_info):
|
||||
abbreviated_key = key
|
||||
key = ORIGIN_ABBREVIATIONS.get(key, key)
|
||||
origin_info[key] = origin_info.pop(abbreviated_key)
|
||||
MQTT_ORIGIN_INFO_SCHEMA(discovery_payload[CONF_ORIGIN])
|
||||
except Exception: # pylint: disable=broad-except
|
||||
_LOGGER.warning(
|
||||
"Unable to parse origin information "
|
||||
"from discovery message, got %s",
|
||||
discovery_payload[CONF_ORIGIN],
|
||||
)
|
||||
return
|
||||
|
||||
if CONF_AVAILABILITY in discovery_payload:
|
||||
for availability_conf in cv.ensure_list(
|
||||
discovery_payload[CONF_AVAILABILITY]
|
||||
@ -246,17 +301,15 @@ async def async_start( # noqa: C901
|
||||
|
||||
if discovery_hash in mqtt_data.discovery_already_discovered:
|
||||
# Dispatch update
|
||||
_LOGGER.info(
|
||||
"Component has already been discovered: %s %s, sending update",
|
||||
component,
|
||||
discovery_id,
|
||||
)
|
||||
message = f"Component has already been discovered: {component} {discovery_id}, sending update"
|
||||
async_log_discovery_origin_info(message, payload)
|
||||
async_dispatcher_send(
|
||||
hass, MQTT_DISCOVERY_UPDATED.format(discovery_hash), payload
|
||||
)
|
||||
elif payload:
|
||||
# Add component
|
||||
_LOGGER.info("Found new component: %s %s", component, discovery_id)
|
||||
message = f"Found new component: {component} {discovery_id}"
|
||||
async_log_discovery_origin_info(message, payload)
|
||||
mqtt_data.discovery_already_discovered.add(discovery_hash)
|
||||
async_dispatcher_send(
|
||||
hass, MQTT_DISCOVERY_NEW.format(component, "mqtt"), payload
|
||||
|
@ -69,9 +69,20 @@ from .const import (
|
||||
ATTR_DISCOVERY_PAYLOAD,
|
||||
ATTR_DISCOVERY_TOPIC,
|
||||
CONF_AVAILABILITY,
|
||||
CONF_CONFIGURATION_URL,
|
||||
CONF_CONNECTIONS,
|
||||
CONF_DEPRECATED_VIA_HUB,
|
||||
CONF_ENCODING,
|
||||
CONF_HW_VERSION,
|
||||
CONF_IDENTIFIERS,
|
||||
CONF_MANUFACTURER,
|
||||
CONF_OBJECT_ID,
|
||||
CONF_ORIGIN,
|
||||
CONF_QOS,
|
||||
CONF_SUGGESTED_AREA,
|
||||
CONF_SW_VERSION,
|
||||
CONF_TOPIC,
|
||||
CONF_VIA_DEVICE,
|
||||
DEFAULT_ENCODING,
|
||||
DEFAULT_PAYLOAD_AVAILABLE,
|
||||
DEFAULT_PAYLOAD_NOT_AVAILABLE,
|
||||
@ -84,6 +95,7 @@ from .discovery import (
|
||||
MQTT_DISCOVERY_DONE,
|
||||
MQTT_DISCOVERY_NEW,
|
||||
MQTT_DISCOVERY_UPDATED,
|
||||
MQTT_ORIGIN_INFO_SCHEMA,
|
||||
MQTTDiscoveryPayload,
|
||||
clear_discovery_hash,
|
||||
set_discovery_hash,
|
||||
@ -119,17 +131,6 @@ CONF_PAYLOAD_NOT_AVAILABLE = "payload_not_available"
|
||||
CONF_JSON_ATTRS_TOPIC = "json_attributes_topic"
|
||||
CONF_JSON_ATTRS_TEMPLATE = "json_attributes_template"
|
||||
|
||||
CONF_IDENTIFIERS = "identifiers"
|
||||
CONF_CONNECTIONS = "connections"
|
||||
CONF_MANUFACTURER = "manufacturer"
|
||||
CONF_HW_VERSION = "hw_version"
|
||||
CONF_SW_VERSION = "sw_version"
|
||||
CONF_VIA_DEVICE = "via_device"
|
||||
CONF_DEPRECATED_VIA_HUB = "via_hub"
|
||||
CONF_SUGGESTED_AREA = "suggested_area"
|
||||
CONF_CONFIGURATION_URL = "configuration_url"
|
||||
CONF_OBJECT_ID = "object_id"
|
||||
|
||||
MQTT_ATTRIBUTES_BLOCKED = {
|
||||
"assumed_state",
|
||||
"available",
|
||||
@ -228,6 +229,7 @@ MQTT_ENTITY_DEVICE_INFO_SCHEMA = vol.All(
|
||||
MQTT_ENTITY_COMMON_SCHEMA = MQTT_AVAILABILITY_SCHEMA.extend(
|
||||
{
|
||||
vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA,
|
||||
vol.Optional(CONF_ORIGIN): MQTT_ORIGIN_INFO_SCHEMA,
|
||||
vol.Optional(CONF_ENABLED_BY_DEFAULT, default=True): cv.boolean,
|
||||
vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA,
|
||||
vol.Optional(CONF_ICON): cv.icon,
|
||||
|
@ -99,6 +99,16 @@ class PendingDiscovered(TypedDict):
|
||||
unsub: CALLBACK_TYPE
|
||||
|
||||
|
||||
class MqttOriginInfo(TypedDict, total=False):
|
||||
"""Integration info of discovered entity."""
|
||||
|
||||
name: str
|
||||
manufacturer: str
|
||||
sw_version: str
|
||||
hw_version: str
|
||||
support_url: str
|
||||
|
||||
|
||||
class MqttCommandTemplate:
|
||||
"""Class for rendering MQTT payload with command templates."""
|
||||
|
||||
|
@ -168,6 +168,83 @@ async def test_correct_config_discovery(
|
||||
assert ("binary_sensor", "bla") in hass.data["mqtt"].discovery_already_discovered
|
||||
|
||||
|
||||
@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.BINARY_SENSOR])
|
||||
async def test_discovery_integration_info(
|
||||
hass: HomeAssistant,
|
||||
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test logging discovery of new and updated items."""
|
||||
await mqtt_mock_entry()
|
||||
async_fire_mqtt_message(
|
||||
hass,
|
||||
"homeassistant/binary_sensor/bla/config",
|
||||
'{ "name": "Beer", "state_topic": "test-topic", "o": {"name": "bla2mqtt", "sw": "1.0" } }',
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("binary_sensor.beer")
|
||||
|
||||
assert state is not None
|
||||
assert state.name == "Beer"
|
||||
|
||||
assert (
|
||||
"Found new component: binary_sensor bla from external application bla2mqtt, version: 1.0"
|
||||
in caplog.text
|
||||
)
|
||||
caplog.clear()
|
||||
|
||||
# Send an update and add support url
|
||||
async_fire_mqtt_message(
|
||||
hass,
|
||||
"homeassistant/binary_sensor/bla/config",
|
||||
'{ "name": "Milk", "state_topic": "test-topic", "o": {"name": "bla2mqtt", "sw": "1.1", "url": "https://bla2mqtt.example.com/support" } }',
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("binary_sensor.beer")
|
||||
|
||||
assert state is not None
|
||||
assert state.name == "Milk"
|
||||
|
||||
assert (
|
||||
"Component has already been discovered: binary_sensor bla, sending update from external application bla2mqtt, version: 1.1, support URL: https://bla2mqtt.example.com/support"
|
||||
in caplog.text
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"config_message",
|
||||
[
|
||||
'{ "name": "Beer", "state_topic": "test-topic", "o": "bla2mqtt" }',
|
||||
'{ "name": "Beer", "state_topic": "test-topic", "o": 2.0 }',
|
||||
'{ "name": "Beer", "state_topic": "test-topic", "o": null }',
|
||||
'{ "name": "Beer", "state_topic": "test-topic", "o": {"sw": "bla2mqtt"} }',
|
||||
],
|
||||
)
|
||||
@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.BINARY_SENSOR])
|
||||
async def test_discovery_with_invalid_integration_info(
|
||||
hass: HomeAssistant,
|
||||
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
config_message: str,
|
||||
) -> None:
|
||||
"""Test sending in correct JSON."""
|
||||
await mqtt_mock_entry()
|
||||
async_fire_mqtt_message(
|
||||
hass,
|
||||
"homeassistant/binary_sensor/bla/config",
|
||||
config_message,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("binary_sensor.beer")
|
||||
|
||||
assert state is None
|
||||
assert (
|
||||
"Unable to parse origin information from discovery message, got" in caplog.text
|
||||
)
|
||||
|
||||
|
||||
@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.FAN])
|
||||
async def test_discover_fan(
|
||||
hass: HomeAssistant,
|
||||
@ -1266,6 +1343,8 @@ ABBREVIATIONS_WHITE_LIST = [
|
||||
"CONF_WILL_MESSAGE",
|
||||
"CONF_WS_PATH",
|
||||
"CONF_WS_HEADERS",
|
||||
# Integration info
|
||||
"CONF_SUPPORT_URL",
|
||||
# Undocumented device configuration
|
||||
"CONF_DEPRECATED_VIA_HUB",
|
||||
"CONF_VIA_DEVICE",
|
||||
|
Loading…
x
Reference in New Issue
Block a user