diagnostics.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. """Diagnostics support for tuya-local."""
  2. from __future__ import annotations
  3. from typing import Any
  4. from homeassistant.components.diagnostics import REDACTED
  5. from homeassistant.config_entries import ConfigEntry
  6. from homeassistant.core import HomeAssistant, callback
  7. from homeassistant.helpers import device_registry as dr
  8. from homeassistant.helpers import entity_registry as er
  9. from homeassistant.helpers.device_registry import DeviceEntry
  10. from tinytuya import __version__ as tinytuya_version
  11. from .const import (
  12. API_PROTOCOL_VERSIONS,
  13. CONF_DEVICE_CID,
  14. CONF_PROTOCOL_VERSION,
  15. CONF_TYPE,
  16. DOMAIN,
  17. )
  18. from .device import TuyaLocalDevice
  19. from .helpers.config import get_device_id
  20. async def async_get_config_entry_diagnostics(
  21. hass: HomeAssistant, entry: ConfigEntry
  22. ) -> dict[str, Any]:
  23. """Return diagnostics for a config entry."""
  24. return _async_get_diagnostics(hass, entry)
  25. async def async_get_device_diagnostics(
  26. hass: HomeAssistant, entry: ConfigEntry, device: DeviceEntry
  27. ) -> dict[str, Any]:
  28. """Return diagnostics for a device entry."""
  29. return _async_get_diagnostics(hass, entry, device)
  30. @callback
  31. def _async_get_diagnostics(
  32. hass: HomeAssistant,
  33. entry: ConfigEntry,
  34. device: DeviceEntry | None = None,
  35. ) -> dict[str, Any]:
  36. """Return diagnostics for a tuya-local config entry."""
  37. hass_data = hass.data[DOMAIN][get_device_id(entry.data)]
  38. data = {
  39. "name": entry.title,
  40. "type": entry.data[CONF_TYPE],
  41. "device_id": REDACTED,
  42. "device_cid": REDACTED if entry.data.get(CONF_DEVICE_CID, "") != "" else "",
  43. "local_key": REDACTED,
  44. "host": REDACTED,
  45. "protocol_version": entry.data[CONF_PROTOCOL_VERSION],
  46. "tinytuya_version": tinytuya_version,
  47. }
  48. # The DeviceEntry also has interesting looking data, but this
  49. # integration does not publish anything to it other than some hardcoded
  50. # values that don't change between devices. Instead get the live data
  51. # from the running hass.
  52. data |= _async_device_as_dict(hass, hass_data["device"])
  53. return data
  54. @callback
  55. def _async_device_as_dict(
  56. hass: HomeAssistant, device: TuyaLocalDevice
  57. ) -> dict[str, Any]:
  58. """Represent a Tuya Local device as a dictionary."""
  59. # Base device information, without sensitive information
  60. data = {
  61. "name": device.name,
  62. "api_version_set": device._api.version,
  63. "api_version_used": (
  64. "none"
  65. if device._api_protocol_version_index is None
  66. else API_PROTOCOL_VERSIONS[device._api_protocol_version_index]
  67. ),
  68. "api_working": device._api_protocol_working,
  69. "status": device._api.dps_cache,
  70. "cached_state": device._cached_state,
  71. "pending_state": device._pending_updates,
  72. "connected": device._running,
  73. "force_dps": device._force_dps,
  74. }
  75. device_registry = dr.async_get(hass)
  76. entity_registry = er.async_get(hass)
  77. hass_device = device_registry.async_get_device(
  78. identifiers={(DOMAIN, device.unique_id)}
  79. )
  80. if hass_device:
  81. data["home_assistant"] = {
  82. "name": hass_device.name,
  83. "name_by_user": hass_device.name_by_user,
  84. "disabled": hass_device.disabled,
  85. "disabled_by": hass_device.disabled_by,
  86. "entities": [],
  87. }
  88. hass_entities = er.async_entries_for_device(
  89. entity_registry,
  90. device_id=hass_device.id,
  91. include_disabled_entities=True,
  92. )
  93. for entity_entry in hass_entities:
  94. state = hass.states.get(entity_entry.entity_id)
  95. state_dict = None
  96. if state:
  97. state_dict = dict(state.as_dict())
  98. # Redact entity_picture in case it is sensitive
  99. if "entity_picture" in state_dict["attributes"]:
  100. state_dict["attributes"] = {
  101. **state_dict["attributes"],
  102. "entity_picture": REDACTED,
  103. }
  104. # Context is not useful information
  105. state_dict.pop("context", None)
  106. data["home_assistant"]["entities"].append(
  107. {
  108. "disabled": entity_entry.disabled,
  109. "disabled_by": entity_entry.disabled_by,
  110. "entity_category": entity_entry.entity_category,
  111. "device_class": entity_entry.device_class,
  112. "original_device_class": entity_entry.original_device_class,
  113. "icon": entity_entry.icon,
  114. "unit_of_measurement": entity_entry.unit_of_measurement,
  115. "state": state_dict,
  116. }
  117. )
  118. return data