"""Diagnostics support for HomeKit Controller.""" from __future__ import annotations from typing import Any from aiohomekit.model.characteristics.characteristic_types import CharacteristicsTypes from homeassistant.components.diagnostics import REDACTED, async_redact_data from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.device_registry import DeviceEntry from .connection import HKDevice from .const import KNOWN_DEVICES REDACTED_CHARACTERISTICS = [ CharacteristicsTypes.SERIAL_NUMBER, ] REDACTED_CONFIG_ENTRY_KEYS = [ "AccessoryIP", "iOSDeviceLTSK", ] REDACTED_STATE = ["access_token", "entity_picture"] async def async_get_config_entry_diagnostics( hass: HomeAssistant, entry: ConfigEntry ) -> dict[str, Any]: """Return diagnostics for a config entry.""" return _async_get_diagnostics(hass, entry) async def async_get_device_diagnostics( hass: HomeAssistant, entry: ConfigEntry, device: DeviceEntry ) -> dict[str, Any]: """Return diagnostics for a device entry.""" return _async_get_diagnostics(hass, entry, device) @callback def _async_get_diagnostics_for_device( hass: HomeAssistant, device: DeviceEntry ) -> dict[str, Any]: data = {} data["name"] = device.name data["model"] = device.model data["manfacturer"] = device.manufacturer data["sw_version"] = device.sw_version data["hw_version"] = device.hw_version entities = data["entities"] = [] hass_entities = er.async_entries_for_device( er.async_get(hass), device_id=device.id, include_disabled_entities=True, ) hass_entities.sort(key=lambda entry: entry.original_name) for entity_entry in hass_entities: state = hass.states.get(entity_entry.entity_id) state_dict = None if state: state_dict = async_redact_data(state.as_dict(), REDACTED_STATE) state_dict.pop("context", None) entities.append( { "original_name": entity_entry.original_name, "original_device_class": entity_entry.original_device_class, "entity_category": entity_entry.entity_category, "original_icon": entity_entry.original_icon, "icon": entity_entry.icon, "unit_of_measurement": entity_entry.unit_of_measurement, "device_class": entity_entry.device_class, "disabled": entity_entry.disabled, "disabled_by": entity_entry.disabled_by, "state": state_dict, } ) return data @callback def _async_get_diagnostics( hass: HomeAssistant, entry: ConfigEntry, device: DeviceEntry | None = None ) -> dict[str, Any]: """Return diagnostics for a config entry.""" hkid = entry.data["AccessoryPairingID"] connection: HKDevice = hass.data[KNOWN_DEVICES][hkid] data = { "config-entry": { "title": entry.title, "version": entry.version, "data": async_redact_data(entry.data, REDACTED_CONFIG_ENTRY_KEYS), } } # This is the raw data as returned by homekit # It is roughly equivalent to what is in .storage/homekit_controller-entity-map # But it also has the latest values seen by the polling or events data["entity-map"] = accessories = connection.entity_map.serialize() # It contains serial numbers, which we should strip out for accessory in accessories: for service in accessory.get("services", []): for char in service.get("characteristics", []): if char["type"] in REDACTED_CHARACTERISTICS: char["value"] = REDACTED if device: data["device"] = _async_get_diagnostics_for_device(hass, device) else: device_registry = dr.async_get(hass) devices = data["devices"] = [] for device_id in connection.devices.values(): device = device_registry.async_get(device_id) devices.append(_async_get_diagnostics_for_device(hass, device)) return data