返回 Skill 列表
extension
分类: 开发与工程无需 API Key

ha-diagnostics

为Home Assistant集成实现诊断功能。这是集成质量等级达到Gold级别所必需的。当被问及diagnostics.py、调试信息、故障排除数据或隐藏敏感信息时使用。

person作者: jakexiaohubgithub

Home Assistant Diagnostics Implementation

Diagnostics provide users and developers with structured debug information while protecting sensitive data. Required for Gold tier on the Integration Quality Scale.

File Structure

custom_components/{domain}/
├── __init__.py
├── diagnostics.py    # Diagnostics implementation
└── ...

Basic diagnostics.py Template

"""Diagnostics support for {Name}."""
from __future__ import annotations

from typing import Any

from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant

# Keys to redact from diagnostics output
TO_REDACT = {
    # Authentication
    "password",
    "token",
    "api_key",
    "access_token",
    "refresh_token",
    "secret",
    "credentials",
    # Personal information
    "email",
    "username",
    "serial",
    "serial_number",
    "unique_id",
    "mac",
    "mac_address",
    # Location
    "latitude",
    "longitude",
    "location",
    "address",
}


async def async_get_config_entry_diagnostics(
    hass: HomeAssistant, entry: ConfigEntry
) -> dict[str, Any]:
    """Return diagnostics for a config entry."""
    coordinator = entry.runtime_data

    return {
        "config_entry": async_redact_data(entry.as_dict(), TO_REDACT),
        "coordinator_data": async_redact_data(
            coordinator.data if coordinator.data else {}, TO_REDACT
        ),
    }

Advanced Diagnostics with Device Info

"""Diagnostics support for {Name}."""
from __future__ import annotations

from typing import Any

from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er

TO_REDACT = {
    "password",
    "token",
    "api_key",
    "access_token",
    "refresh_token",
    "secret",
    "email",
    "username",
    "serial",
    "mac",
    "latitude",
    "longitude",
}

# Additional keys in nested structures
TO_REDACT_NESTED = TO_REDACT | {"ip_address", "host", "ssid"}


async def async_get_config_entry_diagnostics(
    hass: HomeAssistant, entry: ConfigEntry
) -> dict[str, Any]:
    """Return diagnostics for a config entry."""
    coordinator = entry.runtime_data

    # Get device and entity info
    device_registry = dr.async_get(hass)
    entity_registry = er.async_get(hass)

    devices = []
    for device in dr.async_entries_for_config_entry(device_registry, entry.entry_id):
        entities = []
        for entity in er.async_entries_for_device(
            entity_registry, device.id, include_disabled_entities=True
        ):
            entities.append({
                "entity_id": entity.entity_id,
                "disabled": entity.disabled,
                "disabled_by": entity.disabled_by,
                "platform": entity.platform,
            })
        
        devices.append({
            "name": device.name,
            "model": device.model,
            "manufacturer": device.manufacturer,
            "sw_version": device.sw_version,
            "hw_version": device.hw_version,
            "entities": entities,
        })

    return {
        "config_entry": {
            "entry_id": entry.entry_id,
            "version": entry.version,
            "domain": entry.domain,
            "title": entry.title,
            "data": async_redact_data(dict(entry.data), TO_REDACT),
            "options": async_redact_data(dict(entry.options), TO_REDACT),
        },
        "devices": devices,
        "coordinator": {
            "last_update_success": coordinator.last_update_success,
            "last_exception": str(coordinator.last_exception) if coordinator.last_exception else None,
            "update_interval": str(coordinator.update_interval),
            "data": async_redact_data(coordinator.data, TO_REDACT_NESTED) if coordinator.data else None,
        },
    }

Device Diagnostics (Optional)

For per-device diagnostics (useful for hub integrations):

async def async_get_device_diagnostics(
    hass: HomeAssistant, entry: ConfigEntry, device: dr.DeviceEntry
) -> dict[str, Any]:
    """Return diagnostics for a device."""
    coordinator = entry.runtime_data
    
    # Find device data from coordinator
    device_id = next(
        (identifier[1] for identifier in device.identifiers if identifier[0] == DOMAIN),
        None
    )
    
    device_data = coordinator.data.get(device_id, {}) if device_id else {}
    
    return {
        "device": {
            "name": device.name,
            "model": device.model,
            "sw_version": device.sw_version,
        },
        "data": async_redact_data(device_data, TO_REDACT),
    }

Redaction Guidelines

Always Redact

| Category | Keys to Redact | |----------|---------------| | Auth | password, token, api_key, access_token, refresh_token, secret, credentials, auth | | Personal | email, username, user_id, account_id | | Device IDs | serial, serial_number, unique_id, mac, mac_address | | Location | latitude, longitude, lat, lon, location, address, coordinates | | Network | ip_address, ip, ssid (consider context) |

Consider Redacting

  • Phone numbers
  • Names (if personally identifiable)
  • URLs with auth tokens
  • Custom identifiers specific to your integration

Never Redact

  • Model numbers
  • Firmware versions
  • Feature flags
  • Error messages
  • Timestamps
  • Entity states (unless containing PII)

Using async_redact_data

The helper recursively redacts matching keys:

from homeassistant.components.diagnostics import async_redact_data

data = {
    "device": {
        "name": "Living Room",
        "serial": "ABC123",  # Will be redacted
        "firmware": "1.2.3",
    },
    "auth": {
        "token": "secret123",  # Will be redacted
        "expires": 3600,
    }
}

redacted = async_redact_data(data, {"serial", "token"})
# Result:
# {
#     "device": {"name": "Living Room", "serial": "**REDACTED**", "firmware": "1.2.3"},
#     "auth": {"token": "**REDACTED**", "expires": 3600}
# }

Custom Redaction

For complex redaction needs:

def redact_url(url: str) -> str:
    """Redact auth tokens from URLs."""
    import re
    return re.sub(r'(token=)[^&]+', r'\1**REDACTED**', url)

async def async_get_config_entry_diagnostics(
    hass: HomeAssistant, entry: ConfigEntry
) -> dict[str, Any]:
    coordinator = entry.runtime_data
    
    # Custom redaction for URLs
    api_url = redact_url(coordinator.client.base_url)
    
    return {
        "api_url": api_url,
        "data": async_redact_data(coordinator.data, TO_REDACT),
    }

Testing Diagnostics

"""Test diagnostics."""
from homeassistant.components.diagnostics import async_get_config_entry_diagnostics
from homeassistant.core import HomeAssistant

from custom_components.{domain} import DOMAIN


async def test_diagnostics(hass: HomeAssistant, mock_client) -> None:
    """Test diagnostics output."""
    entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG)
    entry.add_to_hass(hass)
    await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    diagnostics = await async_get_config_entry_diagnostics(hass, entry)
    
    # Verify structure
    assert "config_entry" in diagnostics
    assert "coordinator_data" in diagnostics
    
    # Verify sensitive data is redacted
    assert diagnostics["config_entry"]["data"]["password"] == "**REDACTED**"

Related Skills

  • Config entries → ha-config-flow
  • Coordinator → ha-coordinator
  • Quality scale → ha-quality-review