Add MQTT debug info (#33461)

* Add MQTT debug info

* Tweaks

* Tweaks
This commit is contained in:
Erik Montnemery 2020-04-01 19:00:40 +02:00 committed by GitHub
parent eff9b2a1a0
commit fb93b79b12
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 547 additions and 3 deletions

View file

@ -45,7 +45,8 @@ from homeassistant.util.async_ import run_callback_threadsafe
from homeassistant.util.logging import catch_log_exception
# Loading the config flow file will register the flow
from . import config_flow, discovery, server # noqa: F401 pylint: disable=unused-import
from . import config_flow # noqa: F401 pylint: disable=unused-import
from . import debug_info, discovery, server
from .const import (
ATTR_DISCOVERY_HASH,
ATTR_DISCOVERY_TOPIC,
@ -56,6 +57,7 @@ from .const import (
DEFAULT_QOS,
PROTOCOL_311,
)
from .debug_info import log_messages
from .discovery import MQTT_DISCOVERY_UPDATED, clear_discovery_hash, set_discovery_hash
from .models import Message, MessageCallbackType, PublishPayloadType
from .subscription import async_subscribe_topics, async_unsubscribe_topics
@ -513,6 +515,7 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
websocket_api.async_register_command(hass, websocket_subscribe)
websocket_api.async_register_command(hass, websocket_remove_device)
websocket_api.async_register_command(hass, websocket_mqtt_info)
if conf is None:
# If we have a config entry, setup is done by that config entry.
@ -1058,6 +1061,7 @@ class MqttAttributes(Entity):
attr_tpl.hass = self.hass
@callback
@log_messages(self.hass, self.entity_id)
def attributes_message_received(msg: Message) -> None:
try:
payload = msg.payload
@ -1122,6 +1126,7 @@ class MqttAvailability(Entity):
"""(Re)Subscribe to topics."""
@callback
@log_messages(self.hass, self.entity_id)
def availability_message_received(msg: Message) -> None:
"""Handle a new received MQTT availability message."""
if msg.payload == self._avail_config[CONF_PAYLOAD_AVAILABLE]:
@ -1207,6 +1212,7 @@ class MqttDiscoveryUpdate(Entity):
_LOGGER.info(
"Got update for entity with hash: %s '%s'", discovery_hash, payload,
)
debug_info.update_entity_discovery_data(self.hass, payload, self.entity_id)
if not payload:
# Empty payload: Remove component
_LOGGER.info("Removing component: %s", self.entity_id)
@ -1219,6 +1225,9 @@ class MqttDiscoveryUpdate(Entity):
await self._discovery_update(payload)
if discovery_hash:
debug_info.add_entity_discovery_data(
self.hass, self._discovery_data, self.entity_id
)
# Set in case the entity has been removed and is re-added
set_discovery_hash(self.hass, discovery_hash)
self._remove_signal = async_dispatcher_connect(
@ -1242,6 +1251,7 @@ class MqttDiscoveryUpdate(Entity):
def _cleanup_on_remove(self) -> None:
"""Stop listening to signal and cleanup discovery data."""
if self._discovery_data and not self._removed_from_hass:
debug_info.remove_entity_data(self.hass, self.entity_id)
clear_discovery_hash(self.hass, self._discovery_data[ATTR_DISCOVERY_HASH])
self._removed_from_hass = True
@ -1303,6 +1313,18 @@ class MqttEntityDeviceInfo(Entity):
return device_info_from_config(self._device_config)
@websocket_api.websocket_command(
{vol.Required("type"): "mqtt/device/debug_info", vol.Required("device_id"): str}
)
@websocket_api.async_response
async def websocket_mqtt_info(hass, connection, msg):
"""Get MQTT debug info for device."""
device_id = msg["device_id"]
mqtt_info = await debug_info.info_for_device(hass, device_id)
connection.send_result(msg["id"], mqtt_info)
@websocket_api.websocket_command(
{vol.Required("type"): "mqtt/device/remove", vol.Required("device_id"): str}
)

View file

@ -4,6 +4,7 @@ CONF_DISCOVERY = "discovery"
DEFAULT_DISCOVERY = False
ATTR_DISCOVERY_HASH = "discovery_hash"
ATTR_DISCOVERY_PAYLOAD = "discovery_payload"
ATTR_DISCOVERY_TOPIC = "discovery_topic"
CONF_STATE_TOPIC = "state_topic"
PROTOCOL_311 = "3.1.1"

View file

@ -0,0 +1,146 @@
"""Helper to handle a set of topics to subscribe to."""
from collections import deque
from functools import wraps
import logging
from typing import Any
from homeassistant.helpers.typing import HomeAssistantType
from .const import ATTR_DISCOVERY_PAYLOAD, ATTR_DISCOVERY_TOPIC
from .models import MessageCallbackType
_LOGGER = logging.getLogger(__name__)
DATA_MQTT_DEBUG_INFO = "mqtt_debug_info"
STORED_MESSAGES = 10
def log_messages(hass: HomeAssistantType, entity_id: str) -> MessageCallbackType:
"""Wrap an MQTT message callback to support message logging."""
def _log_message(msg):
"""Log message."""
debug_info = hass.data[DATA_MQTT_DEBUG_INFO]
messages = debug_info["entities"][entity_id]["topics"][msg.topic]
messages.append(msg.payload)
def _decorator(msg_callback: MessageCallbackType):
@wraps(msg_callback)
def wrapper(msg: Any) -> None:
"""Log message."""
_log_message(msg)
msg_callback(msg)
setattr(wrapper, "__entity_id", entity_id)
return wrapper
return _decorator
def add_topic(hass, message_callback, topic):
"""Prepare debug data for topic."""
entity_id = getattr(message_callback, "__entity_id", None)
if entity_id:
debug_info = hass.data.setdefault(
DATA_MQTT_DEBUG_INFO, {"entities": {}, "triggers": {}}
)
entity_info = debug_info["entities"].setdefault(
entity_id, {"topics": {}, "discovery_data": {}}
)
entity_info["topics"][topic] = deque([], STORED_MESSAGES)
def remove_topic(hass, message_callback, topic):
"""Remove debug data for topic."""
entity_id = getattr(message_callback, "__entity_id", None)
if entity_id and entity_id in hass.data[DATA_MQTT_DEBUG_INFO]["entities"]:
hass.data[DATA_MQTT_DEBUG_INFO]["entities"][entity_id]["topics"].pop(topic)
def add_entity_discovery_data(hass, discovery_data, entity_id):
"""Add discovery data."""
debug_info = hass.data.setdefault(
DATA_MQTT_DEBUG_INFO, {"entities": {}, "triggers": {}}
)
entity_info = debug_info["entities"].setdefault(
entity_id, {"topics": {}, "discovery_data": {}}
)
entity_info["discovery_data"] = discovery_data
def update_entity_discovery_data(hass, discovery_payload, entity_id):
"""Update discovery data."""
entity_info = hass.data[DATA_MQTT_DEBUG_INFO]["entities"][entity_id]
entity_info["discovery_data"][ATTR_DISCOVERY_PAYLOAD] = discovery_payload
def remove_entity_data(hass, entity_id):
"""Remove discovery data."""
hass.data[DATA_MQTT_DEBUG_INFO]["entities"].pop(entity_id)
def add_trigger_discovery_data(hass, discovery_hash, discovery_data, device_id):
"""Add discovery data."""
debug_info = hass.data.setdefault(
DATA_MQTT_DEBUG_INFO, {"entities": {}, "triggers": {}}
)
debug_info["triggers"][discovery_hash] = {
"device_id": device_id,
"discovery_data": discovery_data,
}
def update_trigger_discovery_data(hass, discovery_hash, discovery_payload):
"""Update discovery data."""
trigger_info = hass.data[DATA_MQTT_DEBUG_INFO]["triggers"][discovery_hash]
trigger_info["discovery_data"][ATTR_DISCOVERY_PAYLOAD] = discovery_payload
def remove_trigger_discovery_data(hass, discovery_hash):
"""Remove discovery data."""
hass.data[DATA_MQTT_DEBUG_INFO]["triggers"][discovery_hash]["discovery_data"] = None
async def info_for_device(hass, device_id):
"""Get debug info for a device."""
mqtt_info = {"entities": [], "triggers": []}
entity_registry = await hass.helpers.entity_registry.async_get_registry()
entries = hass.helpers.entity_registry.async_entries_for_device(
entity_registry, device_id
)
mqtt_debug_info = hass.data.setdefault(
DATA_MQTT_DEBUG_INFO, {"entities": {}, "triggers": {}}
)
for entry in entries:
if entry.entity_id not in mqtt_debug_info["entities"]:
continue
entity_info = mqtt_debug_info["entities"][entry.entity_id]
topics = [
{"topic": topic, "messages": list(messages)}
for topic, messages in entity_info["topics"].items()
]
discovery_data = {
"topic": entity_info["discovery_data"].get(ATTR_DISCOVERY_TOPIC, ""),
"payload": entity_info["discovery_data"].get(ATTR_DISCOVERY_PAYLOAD, ""),
}
mqtt_info["entities"].append(
{
"entity_id": entry.entity_id,
"topics": topics,
"discovery_data": discovery_data,
}
)
for trigger in mqtt_debug_info["triggers"].values():
if trigger["device_id"] != device_id:
continue
discovery_data = {
"topic": trigger["discovery_data"][ATTR_DISCOVERY_TOPIC],
"payload": trigger["discovery_data"][ATTR_DISCOVERY_PAYLOAD],
}
mqtt_info["triggers"].append({"discovery_data": discovery_data})
return mqtt_info

View file

@ -26,6 +26,7 @@ from . import (
CONF_QOS,
DOMAIN,
cleanup_device_registry,
debug_info,
)
from .discovery import MQTT_DISCOVERY_UPDATED, clear_discovery_hash
@ -183,6 +184,7 @@ async def async_setup_trigger(hass, config, config_entry, discovery_data):
if not payload:
# Empty payload: Remove trigger
_LOGGER.info("Removing trigger: %s", discovery_hash)
debug_info.remove_trigger_discovery_data(hass, discovery_hash)
if discovery_id in hass.data[DEVICE_TRIGGERS]:
device_trigger = hass.data[DEVICE_TRIGGERS][discovery_id]
device_trigger.detach_trigger()
@ -192,6 +194,7 @@ async def async_setup_trigger(hass, config, config_entry, discovery_data):
else:
# Non-empty payload: Update trigger
_LOGGER.info("Updating trigger: %s", discovery_hash)
debug_info.update_trigger_discovery_data(hass, discovery_hash, payload)
config = TRIGGER_DISCOVERY_SCHEMA(payload)
await _update_device(hass, config_entry, config)
device_trigger = hass.data[DEVICE_TRIGGERS][discovery_id]
@ -230,6 +233,9 @@ async def async_setup_trigger(hass, config, config_entry, discovery_data):
await hass.data[DEVICE_TRIGGERS][discovery_id].update_trigger(
config, discovery_hash, remove_signal
)
debug_info.add_trigger_discovery_data(
hass, discovery_hash, discovery_data, device.id
)
async def async_device_removed(hass: HomeAssistant, device_id: str):
@ -241,6 +247,7 @@ async def async_device_removed(hass: HomeAssistant, device_id: str):
discovery_hash = device_trigger.discovery_data[ATTR_DISCOVERY_HASH]
discovery_topic = device_trigger.discovery_data[ATTR_DISCOVERY_TOPIC]
debug_info.remove_trigger_discovery_data(hass, discovery_hash)
device_trigger.detach_trigger()
clear_discovery_hash(hass, discovery_hash)
device_trigger.remove_signal()

View file

@ -11,7 +11,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.typing import HomeAssistantType
from .abbreviations import ABBREVIATIONS, DEVICE_ABBREVIATIONS
from .const import ATTR_DISCOVERY_HASH, ATTR_DISCOVERY_TOPIC
from .const import ATTR_DISCOVERY_HASH, ATTR_DISCOVERY_PAYLOAD, ATTR_DISCOVERY_TOPIC
_LOGGER = logging.getLogger(__name__)
@ -135,6 +135,7 @@ async def async_start(
setattr(payload, "__configuration_source__", f"MQTT (topic: '{topic}')")
discovery_data = {
ATTR_DISCOVERY_HASH: discovery_hash,
ATTR_DISCOVERY_PAYLOAD: payload,
ATTR_DISCOVERY_TOPIC: topic,
}
setattr(payload, "discovery_data", discovery_data)

View file

@ -35,6 +35,7 @@ from . import (
MqttEntityDeviceInfo,
subscription,
)
from .debug_info import log_messages
from .discovery import MQTT_DISCOVERY_NEW, clear_discovery_hash
_LOGGER = logging.getLogger(__name__)
@ -137,6 +138,7 @@ class MqttSensor(
template.hass = self.hass
@callback
@log_messages(self.hass, self.entity_id)
def message_received(msg):
"""Handle new MQTT messages."""
payload = msg.payload

View file

@ -8,6 +8,7 @@ from homeassistant.components import mqtt
from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.loader import bind_hass
from . import debug_info
from .const import DEFAULT_QOS
from .models import MessageCallbackType
@ -18,6 +19,7 @@ _LOGGER = logging.getLogger(__name__)
class EntitySubscription:
"""Class to hold data about an active entity topic subscription."""
hass = attr.ib(type=HomeAssistantType)
topic = attr.ib(type=str)
message_callback = attr.ib(type=MessageCallbackType)
unsubscribe_callback = attr.ib(type=Optional[Callable[[], None]])
@ -31,11 +33,16 @@ class EntitySubscription:
if other is not None and other.unsubscribe_callback is not None:
other.unsubscribe_callback()
# Clear debug data if it exists
debug_info.remove_topic(self.hass, other.message_callback, other.topic)
if self.topic is None:
# We were asked to remove the subscription or not to create it
return
# Prepare debug data
debug_info.add_topic(self.hass, self.message_callback, self.topic)
self.unsubscribe_callback = await mqtt.async_subscribe(
hass, self.topic, self.message_callback, self.qos, self.encoding
)
@ -77,6 +84,7 @@ async def async_subscribe_topics(
unsubscribe_callback=None,
qos=value.get("qos", DEFAULT_QOS),
encoding=value.get("encoding", "utf-8"),
hass=hass,
)
# Get the current subscription state
current = current_subscriptions.pop(key, None)
@ -87,6 +95,8 @@ async def async_subscribe_topics(
for remaining in current_subscriptions.values():
if remaining.unsubscribe_callback is not None:
remaining.unsubscribe_callback()
# Clear debug data if it exists
debug_info.remove_topic(hass, remaining.message_callback, remaining.topic)
return new_state

View file

@ -4,6 +4,7 @@ import json
from unittest.mock import ANY
from homeassistant.components import mqtt
from homeassistant.components.mqtt import debug_info
from homeassistant.components.mqtt.discovery import async_start
from homeassistant.const import ATTR_ASSUMED_STATE, STATE_UNAVAILABLE
@ -519,3 +520,232 @@ async def help_test_entity_id_update_discovery_update(
async_fire_mqtt_message(hass, f"{topic}_2", "online")
state = hass.states.get(f"{domain}.milk")
assert state.state != STATE_UNAVAILABLE
async def help_test_entity_debug_info(hass, mqtt_mock, domain, config):
"""Test debug_info.
This is a test helper for MQTT debug_info.
"""
# Add device settings to config
config = copy.deepcopy(config[domain])
config["device"] = copy.deepcopy(DEFAULT_CONFIG_DEVICE_INFO_ID)
config["unique_id"] = "veryunique"
entry = MockConfigEntry(domain=mqtt.DOMAIN)
entry.add_to_hass(hass)
await async_start(hass, "homeassistant", {}, entry)
registry = await hass.helpers.device_registry.async_get_registry()
data = json.dumps(config)
async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data)
await hass.async_block_till_done()
device = registry.async_get_device({("mqtt", "helloworld")}, set())
assert device is not None
debug_info_data = await debug_info.info_for_device(hass, device.id)
assert len(debug_info_data["entities"]) == 1
assert (
debug_info_data["entities"][0]["discovery_data"]["topic"]
== f"homeassistant/{domain}/bla/config"
)
assert debug_info_data["entities"][0]["discovery_data"]["payload"] == config
assert len(debug_info_data["entities"][0]["topics"]) == 1
assert {"topic": "test-topic", "messages": []} in debug_info_data["entities"][0][
"topics"
]
assert len(debug_info_data["triggers"]) == 0
async def help_test_entity_debug_info_max_messages(hass, mqtt_mock, domain, config):
"""Test debug_info message overflow.
This is a test helper for MQTT debug_info.
"""
# Add device settings to config
config = copy.deepcopy(config[domain])
config["device"] = copy.deepcopy(DEFAULT_CONFIG_DEVICE_INFO_ID)
config["unique_id"] = "veryunique"
entry = MockConfigEntry(domain=mqtt.DOMAIN)
entry.add_to_hass(hass)
await async_start(hass, "homeassistant", {}, entry)
registry = await hass.helpers.device_registry.async_get_registry()
data = json.dumps(config)
async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data)
await hass.async_block_till_done()
device = registry.async_get_device({("mqtt", "helloworld")}, set())
assert device is not None
debug_info_data = await debug_info.info_for_device(hass, device.id)
assert len(debug_info_data["entities"][0]["topics"]) == 1
assert {"topic": "test-topic", "messages": []} in debug_info_data["entities"][0][
"topics"
]
for i in range(0, debug_info.STORED_MESSAGES + 1):
async_fire_mqtt_message(hass, "test-topic", f"{i}")
debug_info_data = await debug_info.info_for_device(hass, device.id)
assert len(debug_info_data["entities"][0]["topics"]) == 1
assert (
len(debug_info_data["entities"][0]["topics"][0]["messages"])
== debug_info.STORED_MESSAGES
)
messages = [f"{i}" for i in range(1, debug_info.STORED_MESSAGES + 1)]
assert {"topic": "test-topic", "messages": messages} in debug_info_data["entities"][
0
]["topics"]
async def help_test_entity_debug_info_message(
hass, mqtt_mock, domain, config, topic=None, payload=None
):
"""Test debug_info message overflow.
This is a test helper for MQTT debug_info.
"""
# Add device settings to config
config = copy.deepcopy(config[domain])
config["device"] = copy.deepcopy(DEFAULT_CONFIG_DEVICE_INFO_ID)
config["unique_id"] = "veryunique"
if topic is None:
# Add default topic to config
config["state_topic"] = "state-topic"
topic = "state-topic"
if payload is None:
payload = "ON"
entry = MockConfigEntry(domain=mqtt.DOMAIN)
entry.add_to_hass(hass)
await async_start(hass, "homeassistant", {}, entry)
registry = await hass.helpers.device_registry.async_get_registry()
data = json.dumps(config)
async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data)
await hass.async_block_till_done()
device = registry.async_get_device({("mqtt", "helloworld")}, set())
assert device is not None
debug_info_data = await debug_info.info_for_device(hass, device.id)
assert len(debug_info_data["entities"][0]["topics"]) >= 1
assert {"topic": topic, "messages": []} in debug_info_data["entities"][0]["topics"]
async_fire_mqtt_message(hass, topic, payload)
debug_info_data = await debug_info.info_for_device(hass, device.id)
assert len(debug_info_data["entities"][0]["topics"]) >= 1
assert {"topic": topic, "messages": [payload]} in debug_info_data["entities"][0][
"topics"
]
async def help_test_entity_debug_info_remove(hass, mqtt_mock, domain, config):
"""Test debug_info.
This is a test helper for MQTT debug_info.
"""
# Add device settings to config
config = copy.deepcopy(config[domain])
config["device"] = copy.deepcopy(DEFAULT_CONFIG_DEVICE_INFO_ID)
config["unique_id"] = "veryunique"
entry = MockConfigEntry(domain=mqtt.DOMAIN)
entry.add_to_hass(hass)
await async_start(hass, "homeassistant", {}, entry)
registry = await hass.helpers.device_registry.async_get_registry()
data = json.dumps(config)
async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data)
await hass.async_block_till_done()
device = registry.async_get_device({("mqtt", "helloworld")}, set())
assert device is not None
debug_info_data = await debug_info.info_for_device(hass, device.id)
assert len(debug_info_data["entities"]) == 1
assert (
debug_info_data["entities"][0]["discovery_data"]["topic"]
== f"homeassistant/{domain}/bla/config"
)
assert debug_info_data["entities"][0]["discovery_data"]["payload"] == config
assert len(debug_info_data["entities"][0]["topics"]) == 1
assert {"topic": "test-topic", "messages": []} in debug_info_data["entities"][0][
"topics"
]
assert len(debug_info_data["triggers"]) == 0
assert debug_info_data["entities"][0]["entity_id"] == f"{domain}.test"
entity_id = debug_info_data["entities"][0]["entity_id"]
async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", "")
await hass.async_block_till_done()
debug_info_data = await debug_info.info_for_device(hass, device.id)
assert len(debug_info_data["entities"]) == 0
assert len(debug_info_data["triggers"]) == 0
assert entity_id not in hass.data[debug_info.DATA_MQTT_DEBUG_INFO]["entities"]
async def help_test_entity_debug_info_update_entity_id(hass, mqtt_mock, domain, config):
"""Test debug_info.
This is a test helper for MQTT debug_info.
"""
# Add device settings to config
config = copy.deepcopy(config[domain])
config["device"] = copy.deepcopy(DEFAULT_CONFIG_DEVICE_INFO_ID)
config["unique_id"] = "veryunique"
entry = MockConfigEntry(domain=mqtt.DOMAIN)
entry.add_to_hass(hass)
await async_start(hass, "homeassistant", {}, entry)
dev_registry = await hass.helpers.device_registry.async_get_registry()
ent_registry = mock_registry(hass, {})
data = json.dumps(config)
async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data)
await hass.async_block_till_done()
device = dev_registry.async_get_device({("mqtt", "helloworld")}, set())
assert device is not None
debug_info_data = await debug_info.info_for_device(hass, device.id)
assert len(debug_info_data["entities"]) == 1
assert (
debug_info_data["entities"][0]["discovery_data"]["topic"]
== f"homeassistant/{domain}/bla/config"
)
assert debug_info_data["entities"][0]["discovery_data"]["payload"] == config
assert debug_info_data["entities"][0]["entity_id"] == f"{domain}.test"
assert len(debug_info_data["entities"][0]["topics"]) == 1
assert {"topic": "test-topic", "messages": []} in debug_info_data["entities"][0][
"topics"
]
assert len(debug_info_data["triggers"]) == 0
ent_registry.async_update_entity(f"{domain}.test", new_entity_id=f"{domain}.milk")
await hass.async_block_till_done()
await hass.async_block_till_done()
debug_info_data = await debug_info.info_for_device(hass, device.id)
assert len(debug_info_data["entities"]) == 1
assert (
debug_info_data["entities"][0]["discovery_data"]["topic"]
== f"homeassistant/{domain}/bla/config"
)
assert debug_info_data["entities"][0]["discovery_data"]["payload"] == config
assert debug_info_data["entities"][0]["entity_id"] == f"{domain}.milk"
assert len(debug_info_data["entities"][0]["topics"]) == 1
assert {"topic": "test-topic", "messages": []} in debug_info_data["entities"][0][
"topics"
]
assert len(debug_info_data["triggers"]) == 0
assert (
f"{domain}.test" not in hass.data[debug_info.DATA_MQTT_DEBUG_INFO]["entities"]
)

View file

@ -4,7 +4,7 @@ import json
import pytest
import homeassistant.components.automation as automation
from homeassistant.components.mqtt import DOMAIN
from homeassistant.components.mqtt import DOMAIN, debug_info
from homeassistant.components.mqtt.device_trigger import async_attach_trigger
from homeassistant.components.mqtt.discovery import async_start
from homeassistant.setup import async_setup_component
@ -1104,3 +1104,44 @@ async def test_cleanup_device_with_entity2(hass, device_reg, entity_reg, mqtt_mo
# Verify device registry entry is cleared
device_entry = device_reg.async_get_device({("mqtt", "helloworld")}, set())
assert device_entry is None
async def test_trigger_debug_info(hass, mqtt_mock):
"""Test debug_info.
This is a test helper for MQTT debug_info.
"""
entry = MockConfigEntry(domain=DOMAIN)
entry.add_to_hass(hass)
await async_start(hass, "homeassistant", {}, entry)
registry = await hass.helpers.device_registry.async_get_registry()
config = {
"platform": "mqtt",
"automation_type": "trigger",
"topic": "test-topic",
"type": "foo",
"subtype": "bar",
"device": {
"connections": [["mac", "02:5b:26:a8:dc:12"]],
"manufacturer": "Whatever",
"name": "Beer",
"model": "Glass",
"sw_version": "0.1-beta",
},
}
data = json.dumps(config)
async_fire_mqtt_message(hass, "homeassistant/device_automation/bla/config", data)
await hass.async_block_till_done()
device = registry.async_get_device(set(), {("mac", "02:5b:26:a8:dc:12")})
assert device is not None
debug_info_data = await debug_info.info_for_device(hass, device.id)
assert len(debug_info_data["entities"]) == 0
assert len(debug_info_data["triggers"]) == 1
assert (
debug_info_data["triggers"][0]["discovery_data"]["topic"]
== "homeassistant/device_automation/bla/config"
)
assert debug_info_data["triggers"][0]["discovery_data"]["payload"] == config

View file

@ -1,5 +1,6 @@
"""The tests for the MQTT component."""
from datetime import timedelta
import json
import ssl
import unittest
from unittest import mock
@ -934,3 +935,48 @@ async def test_mqtt_ws_remove_non_mqtt_device(
response = await client.receive_json()
assert not response["success"]
assert response["error"]["code"] == websocket_api.const.ERR_NOT_FOUND
async def test_mqtt_ws_get_device_debug_info(
hass, device_reg, hass_ws_client, mqtt_mock
):
"""Test MQTT websocket device debug info."""
config_entry = MockConfigEntry(domain=mqtt.DOMAIN)
config_entry.add_to_hass(hass)
await async_start(hass, "homeassistant", {}, config_entry)
config = {
"device": {"identifiers": ["0AFFD2"]},
"platform": "mqtt",
"state_topic": "foobar/sensor",
"unique_id": "unique",
}
data = json.dumps(config)
async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", data)
await hass.async_block_till_done()
# Verify device entry is created
device_entry = device_reg.async_get_device({("mqtt", "0AFFD2")}, set())
assert device_entry is not None
client = await hass_ws_client(hass)
await client.send_json(
{"id": 5, "type": "mqtt/device/debug_info", "device_id": device_entry.id}
)
response = await client.receive_json()
assert response["success"]
expected_result = {
"entities": [
{
"entity_id": "sensor.mqtt_sensor",
"topics": [{"topic": "foobar/sensor", "messages": []}],
"discovery_data": {
"payload": config,
"topic": "homeassistant/sensor/bla/config",
},
}
],
"triggers": [],
}
assert response["result"] == expected_result

View file

@ -19,6 +19,11 @@ from .test_common import (
help_test_discovery_removal,
help_test_discovery_update,
help_test_discovery_update_attr,
help_test_entity_debug_info,
help_test_entity_debug_info_max_messages,
help_test_entity_debug_info_message,
help_test_entity_debug_info_remove,
help_test_entity_debug_info_update_entity_id,
help_test_entity_device_info_remove,
help_test_entity_device_info_update,
help_test_entity_device_info_with_connection,
@ -437,3 +442,36 @@ async def test_entity_device_info_with_hub(hass, mqtt_mock):
device = registry.async_get_device({("mqtt", "helloworld")}, set())
assert device is not None
assert device.via_device_id == hub.id
async def test_entity_debug_info(hass, mqtt_mock):
"""Test MQTT sensor debug info."""
await help_test_entity_debug_info(hass, mqtt_mock, sensor.DOMAIN, DEFAULT_CONFIG)
async def test_entity_debug_info_max_messages(hass, mqtt_mock):
"""Test MQTT sensor debug info."""
await help_test_entity_debug_info_max_messages(
hass, mqtt_mock, sensor.DOMAIN, DEFAULT_CONFIG
)
async def test_entity_debug_info_message(hass, mqtt_mock):
"""Test MQTT debug info."""
await help_test_entity_debug_info_message(
hass, mqtt_mock, sensor.DOMAIN, DEFAULT_CONFIG
)
async def test_entity_debug_info_remove(hass, mqtt_mock):
"""Test MQTT sensor debug info."""
await help_test_entity_debug_info_remove(
hass, mqtt_mock, sensor.DOMAIN, DEFAULT_CONFIG
)
async def test_entity_debug_info_update_entity_id(hass, mqtt_mock):
"""Test MQTT sensor debug info."""
await help_test_entity_debug_info_update_entity_id(
hass, mqtt_mock, sensor.DOMAIN, DEFAULT_CONFIG
)