diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index f5e61c6060f..1fcbddbb400 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -3,6 +3,7 @@ import logging from homeassistant.helpers.entity import Entity from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import device_registry as dr # We need an import from .config_flow, without it .config_flow is never loaded. from .config_flow import HomekitControllerFlowHandler # noqa: F401 @@ -139,6 +140,29 @@ class HomeKitEntity(Entity): """Return True if entity is available.""" return self._available + @property + def device_info(self): + """Return the device info.""" + accessory_serial = self._accessory_info['serial-number'] + + device_info = { + 'identifiers': { + (DOMAIN, 'serial-number', accessory_serial), + }, + 'name': self._accessory_info['name'], + 'manufacturer': self._accessory_info.get('manufacturer', ''), + 'model': self._accessory_info.get('model', ''), + 'sw_version': self._accessory_info.get('firmware.revision', ''), + } + + # Some devices only have a single accessory - we don't add a via_hub + # otherwise it would be self referential. + bridge_serial = self._accessory.connection_info['serial-number'] + if accessory_serial != bridge_serial: + device_info['via_hub'] = (DOMAIN, 'serial-number', bridge_serial) + + return device_info + def get_characteristic_types(self): """Define the homekit characteristics the entity cares about.""" raise NotImplementedError @@ -153,6 +177,21 @@ async def async_setup_entry(hass, entry): del hass.data[KNOWN_DEVICES][conn.unique_id] raise ConfigEntryNotReady + conn_info = conn.connection_info + + device_registry = await dr.async_get_registry(hass) + device_registry.async_get_or_create( + config_entry_id=entry.entry_id, + identifiers={ + (DOMAIN, 'serial-number', conn_info['serial-number']), + (DOMAIN, 'accessory-id', conn.unique_id), + }, + name=conn.name, + manufacturer=conn_info.get('manufacturer'), + model=conn_info.get('model'), + sw_version=conn_info.get('firmware.revision'), + ) + return True diff --git a/homeassistant/components/homekit_controller/connection.py b/homeassistant/components/homekit_controller/connection.py index 080d6034237..3819bef680d 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -218,3 +218,13 @@ class HKDevice(): This id is random and will change if a device undergoes a hard reset. """ return self.pairing_data['AccessoryPairingID'] + + @property + def connection_info(self): + """Return accessory information for the main accessory.""" + return get_bridge_information(self.accessories) + + @property + def name(self): + """Name of the bridge accessory.""" + return get_accessory_name(self.connection_info) or self.unique_id diff --git a/tests/components/homekit_controller/specific_devices/test_aqara_gateway.py b/tests/components/homekit_controller/specific_devices/test_aqara_gateway.py index e0738d67083..0c77aa37196 100644 --- a/tests/components/homekit_controller/specific_devices/test_aqara_gateway.py +++ b/tests/components/homekit_controller/specific_devices/test_aqara_gateway.py @@ -39,3 +39,16 @@ async def test_aqara_gateway_setup(hass): assert light_state.attributes['supported_features'] == ( SUPPORT_BRIGHTNESS | SUPPORT_COLOR ) + + device_registry = await hass.helpers.device_registry.async_get_registry() + + # All the entities are services of the same accessory + # So it looks at the protocol like a single physical device + assert alarm.device_id == light.device_id + + device = device_registry.async_get(light.device_id) + assert device.manufacturer == 'Aqara' + assert device.name == 'Aqara Hub-1563' + assert device.model == 'ZHWA11LM' + assert device.sw_version == '1.4.7' + assert device.hub_device_id is None diff --git a/tests/components/homekit_controller/specific_devices/test_ecobee3.py b/tests/components/homekit_controller/specific_devices/test_ecobee3.py index 166ef32784b..10e01437cda 100644 --- a/tests/components/homekit_controller/specific_devices/test_ecobee3.py +++ b/tests/components/homekit_controller/specific_devices/test_ecobee3.py @@ -67,6 +67,24 @@ async def test_ecobee3_setup(hass): occ3 = entity_registry.async_get('binary_sensor.basement') assert occ3.unique_id == 'homekit-AB3C-56' + device_registry = await hass.helpers.device_registry.async_get_registry() + + climate_device = device_registry.async_get(climate.device_id) + assert climate_device.manufacturer == 'ecobee Inc.' + assert climate_device.name == 'HomeW' + assert climate_device.model == 'ecobee3' + assert climate_device.sw_version == '4.2.394' + assert climate_device.hub_device_id is None + + # Check that an attached sensor has its own device entity that + # is linked to the bridge + sensor_device = device_registry.async_get(occ1.device_id) + assert sensor_device.manufacturer == 'ecobee Inc.' + assert sensor_device.name == 'Kitchen' + assert sensor_device.model == 'REMOTE SENSOR' + assert sensor_device.sw_version == '1.0.0' + assert sensor_device.hub_device_id == climate_device.id + async def test_ecobee3_setup_from_cache(hass, hass_storage): """Test that Ecbobee can be correctly setup from its cached entity map.""" diff --git a/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py b/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py index a741885c6d5..8de3d1587b6 100644 --- a/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py +++ b/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py @@ -38,6 +38,15 @@ async def test_koogeek_ls1_setup(hass): SUPPORT_BRIGHTNESS | SUPPORT_COLOR ) + device_registry = await hass.helpers.device_registry.async_get_registry() + + device = device_registry.async_get(entry.device_id) + assert device.manufacturer == 'Koogeek' + assert device.name == 'Koogeek-LS1-20833F' + assert device.model == 'LS1' + assert device.sw_version == '2.2.15' + assert device.hub_device_id is None + @pytest.mark.parametrize('failure_cls', [ AccessoryDisconnectedError, EncryptionError diff --git a/tests/components/homekit_controller/specific_devices/test_lennox_e30.py b/tests/components/homekit_controller/specific_devices/test_lennox_e30.py index 1869161b1f8..9825e1ab4ab 100644 --- a/tests/components/homekit_controller/specific_devices/test_lennox_e30.py +++ b/tests/components/homekit_controller/specific_devices/test_lennox_e30.py @@ -27,3 +27,15 @@ async def test_lennox_e30_setup(hass): assert climate_state.attributes['supported_features'] == ( SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE ) + + device_registry = await hass.helpers.device_registry.async_get_registry() + + device = device_registry.async_get(climate.device_id) + assert device.manufacturer == 'Lennox' + assert device.name == 'Lennox' + assert device.model == 'E30 2B' + assert device.sw_version == '3.40.XX' + + # The fixture contains a single accessory - so its a single device + # and no bridge + assert device.hub_device_id is None