mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 21:57:51 +00:00
Move MQTT debug_info to dataclass (#78788)
* Add MQTT debug_info to dataclass * Remove total attr, assign factory * Rename typed dict to MqttDebugInfo and use helper * Split entity and trigger debug info * Refactor * More rework
This commit is contained in:
parent
d39ed0cde4
commit
81514b0d1c
@ -170,7 +170,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
|
|
||||||
websocket_api.async_register_command(hass, websocket_subscribe)
|
websocket_api.async_register_command(hass, websocket_subscribe)
|
||||||
websocket_api.async_register_command(hass, websocket_mqtt_info)
|
websocket_api.async_register_command(hass, websocket_mqtt_info)
|
||||||
debug_info.initialize(hass)
|
|
||||||
|
|
||||||
if conf:
|
if conf:
|
||||||
conf = dict(conf)
|
conf = dict(conf)
|
||||||
|
@ -11,29 +11,26 @@ import attr
|
|||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
from homeassistant.helpers.typing import DiscoveryInfoType
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from .const import ATTR_DISCOVERY_PAYLOAD, ATTR_DISCOVERY_TOPIC
|
from .const import ATTR_DISCOVERY_PAYLOAD, ATTR_DISCOVERY_TOPIC
|
||||||
from .models import MessageCallbackType, PublishPayloadType
|
from .models import MessageCallbackType, PublishPayloadType
|
||||||
|
from .util import get_mqtt_data
|
||||||
|
|
||||||
DATA_MQTT_DEBUG_INFO = "mqtt_debug_info"
|
|
||||||
STORED_MESSAGES = 10
|
STORED_MESSAGES = 10
|
||||||
|
|
||||||
|
|
||||||
def initialize(hass: HomeAssistant):
|
|
||||||
"""Initialize MQTT debug info."""
|
|
||||||
hass.data[DATA_MQTT_DEBUG_INFO] = {"entities": {}, "triggers": {}}
|
|
||||||
|
|
||||||
|
|
||||||
def log_messages(
|
def log_messages(
|
||||||
hass: HomeAssistant, entity_id: str
|
hass: HomeAssistant, entity_id: str
|
||||||
) -> Callable[[MessageCallbackType], MessageCallbackType]:
|
) -> Callable[[MessageCallbackType], MessageCallbackType]:
|
||||||
"""Wrap an MQTT message callback to support message logging."""
|
"""Wrap an MQTT message callback to support message logging."""
|
||||||
|
|
||||||
|
debug_info_entities = get_mqtt_data(hass).debug_info_entities
|
||||||
|
|
||||||
def _log_message(msg):
|
def _log_message(msg):
|
||||||
"""Log message."""
|
"""Log message."""
|
||||||
debug_info = hass.data[DATA_MQTT_DEBUG_INFO]
|
messages = debug_info_entities[entity_id]["subscriptions"][
|
||||||
messages = debug_info["entities"][entity_id]["subscriptions"][
|
|
||||||
msg.subscribed_topic
|
msg.subscribed_topic
|
||||||
]["messages"]
|
]["messages"]
|
||||||
if msg not in messages:
|
if msg not in messages:
|
||||||
@ -72,8 +69,7 @@ def log_message(
|
|||||||
retain: bool,
|
retain: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Log an outgoing MQTT message."""
|
"""Log an outgoing MQTT message."""
|
||||||
debug_info = hass.data[DATA_MQTT_DEBUG_INFO]
|
entity_info = get_mqtt_data(hass).debug_info_entities.setdefault(
|
||||||
entity_info = debug_info["entities"].setdefault(
|
|
||||||
entity_id, {"subscriptions": {}, "discovery_data": {}, "transmitted": {}}
|
entity_id, {"subscriptions": {}, "discovery_data": {}, "transmitted": {}}
|
||||||
)
|
)
|
||||||
if topic not in entity_info["transmitted"]:
|
if topic not in entity_info["transmitted"]:
|
||||||
@ -86,11 +82,14 @@ def log_message(
|
|||||||
entity_info["transmitted"][topic]["messages"].append(msg)
|
entity_info["transmitted"][topic]["messages"].append(msg)
|
||||||
|
|
||||||
|
|
||||||
def add_subscription(hass, message_callback, subscription):
|
def add_subscription(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
message_callback: MessageCallbackType,
|
||||||
|
subscription: str,
|
||||||
|
) -> None:
|
||||||
"""Prepare debug data for subscription."""
|
"""Prepare debug data for subscription."""
|
||||||
if entity_id := getattr(message_callback, "__entity_id", None):
|
if entity_id := getattr(message_callback, "__entity_id", None):
|
||||||
debug_info = hass.data[DATA_MQTT_DEBUG_INFO]
|
entity_info = get_mqtt_data(hass).debug_info_entities.setdefault(
|
||||||
entity_info = debug_info["entities"].setdefault(
|
|
||||||
entity_id, {"subscriptions": {}, "discovery_data": {}, "transmitted": {}}
|
entity_id, {"subscriptions": {}, "discovery_data": {}, "transmitted": {}}
|
||||||
)
|
)
|
||||||
if subscription not in entity_info["subscriptions"]:
|
if subscription not in entity_info["subscriptions"]:
|
||||||
@ -101,65 +100,81 @@ def add_subscription(hass, message_callback, subscription):
|
|||||||
entity_info["subscriptions"][subscription]["count"] += 1
|
entity_info["subscriptions"][subscription]["count"] += 1
|
||||||
|
|
||||||
|
|
||||||
def remove_subscription(hass, message_callback, subscription):
|
def remove_subscription(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
message_callback: MessageCallbackType,
|
||||||
|
subscription: str,
|
||||||
|
) -> None:
|
||||||
"""Remove debug data for subscription if it exists."""
|
"""Remove debug data for subscription if it exists."""
|
||||||
entity_id = getattr(message_callback, "__entity_id", None)
|
if (entity_id := getattr(message_callback, "__entity_id", None)) and entity_id in (
|
||||||
if entity_id and entity_id in hass.data[DATA_MQTT_DEBUG_INFO]["entities"]:
|
debug_info_entities := get_mqtt_data(hass).debug_info_entities
|
||||||
hass.data[DATA_MQTT_DEBUG_INFO]["entities"][entity_id]["subscriptions"][
|
):
|
||||||
subscription
|
debug_info_entities[entity_id]["subscriptions"][subscription]["count"] -= 1
|
||||||
]["count"] -= 1
|
if not debug_info_entities[entity_id]["subscriptions"][subscription]["count"]:
|
||||||
if not hass.data[DATA_MQTT_DEBUG_INFO]["entities"][entity_id]["subscriptions"][
|
debug_info_entities[entity_id]["subscriptions"].pop(subscription)
|
||||||
subscription
|
|
||||||
]["count"]:
|
|
||||||
hass.data[DATA_MQTT_DEBUG_INFO]["entities"][entity_id]["subscriptions"].pop(
|
|
||||||
subscription
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def add_entity_discovery_data(hass, discovery_data, entity_id):
|
def add_entity_discovery_data(
|
||||||
|
hass: HomeAssistant, discovery_data: DiscoveryInfoType, entity_id: str
|
||||||
|
) -> None:
|
||||||
"""Add discovery data."""
|
"""Add discovery data."""
|
||||||
debug_info = hass.data[DATA_MQTT_DEBUG_INFO]
|
entity_info = get_mqtt_data(hass).debug_info_entities.setdefault(
|
||||||
entity_info = debug_info["entities"].setdefault(
|
|
||||||
entity_id, {"subscriptions": {}, "discovery_data": {}, "transmitted": {}}
|
entity_id, {"subscriptions": {}, "discovery_data": {}, "transmitted": {}}
|
||||||
)
|
)
|
||||||
entity_info["discovery_data"] = discovery_data
|
entity_info["discovery_data"] = discovery_data
|
||||||
|
|
||||||
|
|
||||||
def update_entity_discovery_data(hass, discovery_payload, entity_id):
|
def update_entity_discovery_data(
|
||||||
|
hass: HomeAssistant, discovery_payload: DiscoveryInfoType, entity_id: str
|
||||||
|
) -> None:
|
||||||
"""Update discovery data."""
|
"""Update discovery data."""
|
||||||
entity_info = hass.data[DATA_MQTT_DEBUG_INFO]["entities"][entity_id]
|
assert (
|
||||||
entity_info["discovery_data"][ATTR_DISCOVERY_PAYLOAD] = discovery_payload
|
discovery_data := get_mqtt_data(hass).debug_info_entities[entity_id][
|
||||||
|
"discovery_data"
|
||||||
|
]
|
||||||
|
) is not None
|
||||||
|
discovery_data[ATTR_DISCOVERY_PAYLOAD] = discovery_payload
|
||||||
|
|
||||||
|
|
||||||
def remove_entity_data(hass, entity_id):
|
def remove_entity_data(hass: HomeAssistant, entity_id: str) -> None:
|
||||||
"""Remove discovery data."""
|
"""Remove discovery data."""
|
||||||
if entity_id in hass.data[DATA_MQTT_DEBUG_INFO]["entities"]:
|
if entity_id in (debug_info_entities := get_mqtt_data(hass).debug_info_entities):
|
||||||
hass.data[DATA_MQTT_DEBUG_INFO]["entities"].pop(entity_id)
|
debug_info_entities.pop(entity_id)
|
||||||
|
|
||||||
|
|
||||||
def add_trigger_discovery_data(hass, discovery_hash, discovery_data, device_id):
|
def add_trigger_discovery_data(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
discovery_hash: tuple[str, str],
|
||||||
|
discovery_data: DiscoveryInfoType,
|
||||||
|
device_id: str,
|
||||||
|
) -> None:
|
||||||
"""Add discovery data."""
|
"""Add discovery data."""
|
||||||
debug_info = hass.data[DATA_MQTT_DEBUG_INFO]
|
get_mqtt_data(hass).debug_info_triggers[discovery_hash] = {
|
||||||
debug_info["triggers"][discovery_hash] = {
|
|
||||||
"device_id": device_id,
|
"device_id": device_id,
|
||||||
"discovery_data": discovery_data,
|
"discovery_data": discovery_data,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def update_trigger_discovery_data(hass, discovery_hash, discovery_payload):
|
def update_trigger_discovery_data(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
discovery_hash: tuple[str, str],
|
||||||
|
discovery_payload: DiscoveryInfoType,
|
||||||
|
) -> None:
|
||||||
"""Update discovery data."""
|
"""Update discovery data."""
|
||||||
trigger_info = hass.data[DATA_MQTT_DEBUG_INFO]["triggers"][discovery_hash]
|
get_mqtt_data(hass).debug_info_triggers[discovery_hash]["discovery_data"][
|
||||||
trigger_info["discovery_data"][ATTR_DISCOVERY_PAYLOAD] = discovery_payload
|
ATTR_DISCOVERY_PAYLOAD
|
||||||
|
] = discovery_payload
|
||||||
|
|
||||||
|
|
||||||
def remove_trigger_discovery_data(hass, discovery_hash):
|
def remove_trigger_discovery_data(
|
||||||
|
hass: HomeAssistant, discovery_hash: tuple[str, str]
|
||||||
|
) -> None:
|
||||||
"""Remove discovery data."""
|
"""Remove discovery data."""
|
||||||
hass.data[DATA_MQTT_DEBUG_INFO]["triggers"].pop(discovery_hash)
|
get_mqtt_data(hass).debug_info_triggers.pop(discovery_hash)
|
||||||
|
|
||||||
|
|
||||||
def _info_for_entity(hass: HomeAssistant, entity_id: str) -> dict[str, Any]:
|
def _info_for_entity(hass: HomeAssistant, entity_id: str) -> dict[str, Any]:
|
||||||
mqtt_debug_info = hass.data[DATA_MQTT_DEBUG_INFO]
|
entity_info = get_mqtt_data(hass).debug_info_entities[entity_id]
|
||||||
entity_info = mqtt_debug_info["entities"][entity_id]
|
|
||||||
subscriptions = [
|
subscriptions = [
|
||||||
{
|
{
|
||||||
"topic": topic,
|
"topic": topic,
|
||||||
@ -205,9 +220,10 @@ def _info_for_entity(hass: HomeAssistant, entity_id: str) -> dict[str, Any]:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _info_for_trigger(hass: HomeAssistant, trigger_key: str) -> dict[str, Any]:
|
def _info_for_trigger(
|
||||||
mqtt_debug_info = hass.data[DATA_MQTT_DEBUG_INFO]
|
hass: HomeAssistant, trigger_key: tuple[str, str]
|
||||||
trigger = mqtt_debug_info["triggers"][trigger_key]
|
) -> dict[str, Any]:
|
||||||
|
trigger = get_mqtt_data(hass).debug_info_triggers[trigger_key]
|
||||||
discovery_data = None
|
discovery_data = None
|
||||||
if trigger["discovery_data"] is not None:
|
if trigger["discovery_data"] is not None:
|
||||||
discovery_data = {
|
discovery_data = {
|
||||||
@ -217,36 +233,39 @@ def _info_for_trigger(hass: HomeAssistant, trigger_key: str) -> dict[str, Any]:
|
|||||||
return {"discovery_data": discovery_data, "trigger_key": trigger_key}
|
return {"discovery_data": discovery_data, "trigger_key": trigger_key}
|
||||||
|
|
||||||
|
|
||||||
def info_for_config_entry(hass):
|
def info_for_config_entry(hass: HomeAssistant) -> dict[str, list[Any]]:
|
||||||
"""Get debug info for all entities and triggers."""
|
"""Get debug info for all entities and triggers."""
|
||||||
mqtt_info = {"entities": [], "triggers": []}
|
|
||||||
mqtt_debug_info = hass.data[DATA_MQTT_DEBUG_INFO]
|
|
||||||
|
|
||||||
for entity_id in mqtt_debug_info["entities"]:
|
mqtt_data = get_mqtt_data(hass)
|
||||||
|
mqtt_info: dict[str, list[Any]] = {"entities": [], "triggers": []}
|
||||||
|
|
||||||
|
for entity_id in mqtt_data.debug_info_entities:
|
||||||
mqtt_info["entities"].append(_info_for_entity(hass, entity_id))
|
mqtt_info["entities"].append(_info_for_entity(hass, entity_id))
|
||||||
|
|
||||||
for trigger_key in mqtt_debug_info["triggers"]:
|
for trigger_key in mqtt_data.debug_info_triggers:
|
||||||
mqtt_info["triggers"].append(_info_for_trigger(hass, trigger_key))
|
mqtt_info["triggers"].append(_info_for_trigger(hass, trigger_key))
|
||||||
|
|
||||||
return mqtt_info
|
return mqtt_info
|
||||||
|
|
||||||
|
|
||||||
def info_for_device(hass, device_id):
|
def info_for_device(hass: HomeAssistant, device_id: str) -> dict[str, list[Any]]:
|
||||||
"""Get debug info for a device."""
|
"""Get debug info for a device."""
|
||||||
mqtt_info = {"entities": [], "triggers": []}
|
|
||||||
|
mqtt_data = get_mqtt_data(hass)
|
||||||
|
|
||||||
|
mqtt_info: dict[str, list[Any]] = {"entities": [], "triggers": []}
|
||||||
entity_registry = er.async_get(hass)
|
entity_registry = er.async_get(hass)
|
||||||
|
|
||||||
entries = er.async_entries_for_device(
|
entries = er.async_entries_for_device(
|
||||||
entity_registry, device_id, include_disabled_entities=True
|
entity_registry, device_id, include_disabled_entities=True
|
||||||
)
|
)
|
||||||
mqtt_debug_info = hass.data[DATA_MQTT_DEBUG_INFO]
|
|
||||||
for entry in entries:
|
for entry in entries:
|
||||||
if entry.entity_id not in mqtt_debug_info["entities"]:
|
if entry.entity_id not in mqtt_data.debug_info_entities:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
mqtt_info["entities"].append(_info_for_entity(hass, entry.entity_id))
|
mqtt_info["entities"].append(_info_for_entity(hass, entry.entity_id))
|
||||||
|
|
||||||
for trigger_key, trigger in mqtt_debug_info["triggers"].items():
|
for trigger_key, trigger in mqtt_data.debug_info_triggers.items():
|
||||||
if trigger["device_id"] != device_id:
|
if trigger["device_id"] != device_id:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -865,6 +865,7 @@ class MqttDiscoveryUpdate(Entity):
|
|||||||
send_discovery_done(self.hass, self._discovery_data)
|
send_discovery_done(self.hass, self._discovery_data)
|
||||||
|
|
||||||
if discovery_hash:
|
if discovery_hash:
|
||||||
|
assert self._discovery_data is not None
|
||||||
debug_info.add_entity_discovery_data(
|
debug_info.add_entity_discovery_data(
|
||||||
self.hass, self._discovery_data, self.entity_id
|
self.hass, self._discovery_data, self.entity_id
|
||||||
)
|
)
|
||||||
|
@ -2,10 +2,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from ast import literal_eval
|
from ast import literal_eval
|
||||||
|
from collections import deque
|
||||||
from collections.abc import Callable, Coroutine
|
from collections.abc import Callable, Coroutine
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
from typing import TYPE_CHECKING, Any, Union
|
from typing import TYPE_CHECKING, Any, TypedDict, Union
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
|
|
||||||
@ -14,10 +15,11 @@ from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
|||||||
from homeassistant.helpers import template
|
from homeassistant.helpers import template
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.helpers.service_info.mqtt import ReceivePayloadType
|
from homeassistant.helpers.service_info.mqtt import ReceivePayloadType
|
||||||
from homeassistant.helpers.typing import ConfigType, TemplateVarsType
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, TemplateVarsType
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .client import MQTT, Subscription
|
from .client import MQTT, Subscription
|
||||||
|
from .debug_info import TimestampedPublishMessage
|
||||||
from .device_trigger import Trigger
|
from .device_trigger import Trigger
|
||||||
|
|
||||||
_SENTINEL = object()
|
_SENTINEL = object()
|
||||||
@ -53,6 +55,28 @@ AsyncMessageCallbackType = Callable[[ReceiveMessage], Coroutine[Any, Any, None]]
|
|||||||
MessageCallbackType = Callable[[ReceiveMessage], None]
|
MessageCallbackType = Callable[[ReceiveMessage], None]
|
||||||
|
|
||||||
|
|
||||||
|
class SubscriptionDebugInfo(TypedDict):
|
||||||
|
"""Class for holding subscription debug info."""
|
||||||
|
|
||||||
|
messages: deque[ReceiveMessage]
|
||||||
|
count: int
|
||||||
|
|
||||||
|
|
||||||
|
class EntityDebugInfo(TypedDict):
|
||||||
|
"""Class for holding entity based debug info."""
|
||||||
|
|
||||||
|
subscriptions: dict[str, SubscriptionDebugInfo]
|
||||||
|
discovery_data: DiscoveryInfoType
|
||||||
|
transmitted: dict[str, dict[str, deque[TimestampedPublishMessage]]]
|
||||||
|
|
||||||
|
|
||||||
|
class TriggerDebugInfo(TypedDict):
|
||||||
|
"""Class for holding trigger based debug info."""
|
||||||
|
|
||||||
|
device_id: str
|
||||||
|
discovery_data: DiscoveryInfoType
|
||||||
|
|
||||||
|
|
||||||
class MqttCommandTemplate:
|
class MqttCommandTemplate:
|
||||||
"""Class for rendering MQTT payload with command templates."""
|
"""Class for rendering MQTT payload with command templates."""
|
||||||
|
|
||||||
@ -187,6 +211,10 @@ class MqttData:
|
|||||||
|
|
||||||
client: MQTT | None = None
|
client: MQTT | None = None
|
||||||
config: ConfigType | None = None
|
config: ConfigType | None = None
|
||||||
|
debug_info_entities: dict[str, EntityDebugInfo] = field(default_factory=dict)
|
||||||
|
debug_info_triggers: dict[tuple[str, str], TriggerDebugInfo] = field(
|
||||||
|
default_factory=dict
|
||||||
|
)
|
||||||
device_triggers: dict[str, Trigger] = field(default_factory=dict)
|
device_triggers: dict[str, Trigger] = field(default_factory=dict)
|
||||||
discovery_registry_hooks: dict[tuple[str, str], CALLBACK_TYPE] = field(
|
discovery_registry_hooks: dict[tuple[str, str], CALLBACK_TYPE] = field(
|
||||||
default_factory=dict
|
default_factory=dict
|
||||||
|
@ -1391,7 +1391,7 @@ async def help_test_entity_debug_info_remove(
|
|||||||
debug_info_data = debug_info.info_for_device(hass, device.id)
|
debug_info_data = debug_info.info_for_device(hass, device.id)
|
||||||
assert len(debug_info_data["entities"]) == 0
|
assert len(debug_info_data["entities"]) == 0
|
||||||
assert len(debug_info_data["triggers"]) == 0
|
assert len(debug_info_data["triggers"]) == 0
|
||||||
assert entity_id not in hass.data[debug_info.DATA_MQTT_DEBUG_INFO]["entities"]
|
assert entity_id not in hass.data["mqtt"].debug_info_entities
|
||||||
|
|
||||||
|
|
||||||
async def help_test_entity_debug_info_update_entity_id(
|
async def help_test_entity_debug_info_update_entity_id(
|
||||||
@ -1449,9 +1449,7 @@ async def help_test_entity_debug_info_update_entity_id(
|
|||||||
"subscriptions"
|
"subscriptions"
|
||||||
]
|
]
|
||||||
assert len(debug_info_data["triggers"]) == 0
|
assert len(debug_info_data["triggers"]) == 0
|
||||||
assert (
|
assert f"{domain}.test" not in hass.data["mqtt"].debug_info_entities
|
||||||
f"{domain}.test" not in hass.data[debug_info.DATA_MQTT_DEBUG_INFO]["entities"]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def help_test_entity_disabled_by_default(
|
async def help_test_entity_disabled_by_default(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user