From a90ef3a5752d39b13ec57c7acee83cc36c0cd3bd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 26 Oct 2022 03:05:33 -0500 Subject: [PATCH] Add additional data to HomeKit diagnostics (#80980) --- .../components/homekit/diagnostics.py | 33 ++++ tests/components/homekit/test_diagnostics.py | 151 +++++++++++++++++- 2 files changed, 182 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/homekit/diagnostics.py b/homeassistant/components/homekit/diagnostics.py index aadac9b4acc..dbd40c1d6f5 100644 --- a/homeassistant/components/homekit/diagnostics.py +++ b/homeassistant/components/homekit/diagnostics.py @@ -6,12 +6,16 @@ from typing import Any from pyhap.accessory_driver import AccessoryDriver from pyhap.state import State +from homeassistant.components.diagnostics import async_redact_data from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from . import HomeKit +from .accessories import HomeAccessory, HomeBridge from .const import DOMAIN, HOMEKIT +TO_REDACT = {"access_token", "entity_picture"} + async def async_get_config_entry_diagnostics( hass: HomeAssistant, entry: ConfigEntry @@ -30,6 +34,11 @@ async def async_get_config_entry_diagnostics( if not homekit.driver: # not started yet or startup failed return data driver: AccessoryDriver = homekit.driver + if driver.accessory: + if isinstance(driver.accessory, HomeBridge): + data["bridge"] = _get_bridge_diagnostics(hass, driver.accessory) + else: + data["accessory"] = _get_accessory_diagnostics(hass, driver.accessory) data.update(driver.get_accessories()) state: State = driver.state data.update( @@ -42,3 +51,27 @@ async def async_get_config_entry_diagnostics( } ) return data + + +def _get_bridge_diagnostics(hass: HomeAssistant, bridge: HomeBridge) -> dict[int, Any]: + """Return diagnostics for a bridge.""" + return { + aid: _get_accessory_diagnostics(hass, accessory) + for aid, accessory in bridge.accessories.items() + } + + +def _get_accessory_diagnostics( + hass: HomeAssistant, accessory: HomeAccessory +) -> dict[str, Any]: + """Return diagnostics for an accessory.""" + return { + "aid": accessory.aid, + "config": accessory.config, + "category": accessory.category, + "name": accessory.display_name, + "entity_id": accessory.entity_id, + "entity_state": async_redact_data( + hass.states.get(accessory.entity_id), TO_REDACT + ), + } diff --git a/tests/components/homekit/test_diagnostics.py b/tests/components/homekit/test_diagnostics.py index e3a85b85972..15d4a6f6e2e 100644 --- a/tests/components/homekit/test_diagnostics.py +++ b/tests/components/homekit/test_diagnostics.py @@ -1,7 +1,11 @@ """Test homekit diagnostics.""" from unittest.mock import ANY, patch -from homeassistant.components.homekit.const import DOMAIN +from homeassistant.components.homekit.const import ( + CONF_HOMEKIT_MODE, + DOMAIN, + HOMEKIT_MODE_ACCESSORY, +) from homeassistant.const import CONF_NAME, CONF_PORT, EVENT_HOMEASSISTANT_STARTED from .util import async_init_integration @@ -28,7 +32,7 @@ async def test_config_entry_not_running( async def test_config_entry_running(hass, hass_client, hk_driver, mock_async_zeroconf): - """Test generating diagnostics for a config entry.""" + """Test generating diagnostics for a bridge config entry.""" entry = MockConfigEntry( domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345} ) @@ -38,6 +42,7 @@ async def test_config_entry_running(hass, hass_client, hk_driver, mock_async_zer await hass.async_block_till_done() diag = await get_diagnostics_for_config_entry(hass, hass_client, entry) assert diag == { + "bridge": {}, "accessories": [ { "aid": 1, @@ -117,3 +122,145 @@ async def test_config_entry_running(hass, hass_client, hk_driver, mock_async_zer ), patch("homeassistant.components.homekit.async_port_is_available"): assert await hass.config_entries.async_unload(entry.entry_id) await hass.async_block_till_done() + + +async def test_config_entry_accessory( + hass, hass_client, hk_driver, mock_async_zeroconf +): + """Test generating diagnostics for an accessory config entry.""" + hass.states.async_set("light.demo", "on") + + entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_NAME: "mock_name", + CONF_PORT: 12345, + CONF_HOMEKIT_MODE: HOMEKIT_MODE_ACCESSORY, + "filter": { + "exclude_domains": [], + "exclude_entities": [], + "include_domains": [], + "include_entities": ["light.demo"], + }, + }, + ) + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + diag = await get_diagnostics_for_config_entry(hass, hass_client, entry) + assert diag == { + "accessories": [ + { + "aid": 1, + "services": [ + { + "characteristics": [ + {"format": "bool", "iid": 2, "perms": ["pw"], "type": "14"}, + { + "format": "string", + "iid": 3, + "perms": ["pr"], + "type": "20", + "value": "Home Assistant " "Light", + }, + { + "format": "string", + "iid": 4, + "perms": ["pr"], + "type": "21", + "value": "Light", + }, + { + "format": "string", + "iid": 5, + "perms": ["pr"], + "type": "23", + "value": "demo", + }, + { + "format": "string", + "iid": 6, + "perms": ["pr"], + "type": "30", + "value": "light.demo", + }, + { + "format": "string", + "iid": 7, + "perms": ["pr"], + "type": "52", + "value": "2022.11.0", + }, + ], + "iid": 1, + "type": "3E", + }, + { + "characteristics": [ + { + "format": "string", + "iid": 9, + "perms": ["pr", "ev"], + "type": "37", + "value": "01.01.00", + } + ], + "iid": 8, + "type": "A2", + }, + { + "characteristics": [ + { + "format": "bool", + "iid": 11, + "perms": ["pr", "pw", "ev"], + "type": "25", + "value": True, + } + ], + "iid": 10, + "type": "43", + }, + ], + } + ], + "accessory": { + "aid": 1, + "category": 5, + "config": {}, + "entity_id": "light.demo", + "entity_state": { + "attributes": {}, + "context": {"id": ANY, "parent_id": None, "user_id": None}, + "entity_id": "light.demo", + "last_changed": ANY, + "last_updated": ANY, + "state": "on", + }, + "name": "demo", + }, + "client_properties": {}, + "config-entry": { + "data": {"name": "mock_name", "port": 12345}, + "options": { + "filter": { + "exclude_domains": [], + "exclude_entities": [], + "include_domains": [], + "include_entities": ["light.demo"], + }, + "mode": "accessory", + }, + "title": "Mock Title", + "version": 1, + }, + "config_version": 2, + "pairing_id": ANY, + "status": 1, + } + with patch("pyhap.accessory_driver.AccessoryDriver.async_start"), patch( + "homeassistant.components.homekit.HomeKit.async_stop" + ), patch("homeassistant.components.homekit.async_port_is_available"): + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done()