diff --git a/homeassistant/components/isy994/__init__.py b/homeassistant/components/isy994/__init__.py index 05e44ea4fdd..e75cb7659c0 100644 --- a/homeassistant/components/isy994/__init__.py +++ b/homeassistant/components/isy994/__init__.py @@ -11,6 +11,7 @@ from homeassistant import config_entries from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import config_validation as cv +import homeassistant.helpers.device_registry as dr from homeassistant.helpers.typing import ConfigType from .const import ( @@ -26,6 +27,7 @@ from .const import ( ISY994_ISY, ISY994_NODES, ISY994_PROGRAMS, + MANUFACTURER, SUPPORTED_PLATFORMS, SUPPORTED_PROGRAM_PLATFORMS, UNDO_UPDATE_LISTENER, @@ -156,6 +158,7 @@ async def async_setup_entry( _LOGGER.info(repr(isy.clock)) hass_isy_data[ISY994_ISY] = isy + await _async_get_or_create_isy_device_in_registry(hass, entry, isy) # Load platforms for the devices in the ISY controller that we support. for platform in SUPPORTED_PLATFORMS: @@ -203,6 +206,22 @@ def _async_import_options_from_data_if_missing( hass.config_entries.async_update_entry(entry, options=options) +async def _async_get_or_create_isy_device_in_registry( + hass: HomeAssistant, entry: config_entries.ConfigEntry, isy +) -> None: + device_registry = await dr.async_get_registry(hass) + + device_registry.async_get_or_create( + config_entry_id=entry.entry_id, + connections={(dr.CONNECTION_NETWORK_MAC, isy.configuration["uuid"])}, + identifiers={(DOMAIN, isy.configuration["uuid"])}, + manufacturer=MANUFACTURER, + name=isy.configuration["name"], + model=isy.configuration["model"], + sw_version=isy.configuration["firmware"], + ) + + async def async_unload_entry( hass: HomeAssistant, entry: config_entries.ConfigEntry ) -> bool: diff --git a/homeassistant/components/isy994/entity.py b/homeassistant/components/isy994/entity.py index 9a1444e61d3..b1642b55672 100644 --- a/homeassistant/components/isy994/entity.py +++ b/homeassistant/components/isy994/entity.py @@ -5,6 +5,8 @@ from pyisy.constants import ( EMPTY_TIME, EVENT_PROPS_IGNORED, ISY_VALUE_UNKNOWN, + PROTO_GROUP, + PROTO_ZWAVE, ) from pyisy.helpers import NodeProperty @@ -12,6 +14,8 @@ from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNKNOWN from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import Dict +from .const import DOMAIN + class ISYEntity(Entity): """Representation of an ISY994 device.""" @@ -53,6 +57,53 @@ class ISYEntity(Entity): self.hass.bus.fire("isy994_control", event_data) + @property + def device_info(self): + """Return the device_info of the device.""" + if hasattr(self._node, "protocol") and self._node.protocol == PROTO_GROUP: + # not a device + return None + uuid = self._node.isy.configuration["uuid"] + node = self._node + basename = self.name + + if hasattr(self._node, "parent_node") and self._node.parent_node is not None: + # This is not the parent node, get the parent node. + node = self._node.parent_node + basename = node.name + + device_info = { + "name": basename, + "identifiers": {}, + "model": "Unknown", + "manufacturer": "Unknown", + "via_device": (DOMAIN, uuid), + } + + if hasattr(node, "address"): + device_info["name"] += f" ({node.address})" + if hasattr(node, "primary_node"): + device_info["identifiers"] = {(DOMAIN, f"{uuid}_{node.address}")} + # ISYv5 Device Types + if hasattr(node, "node_def_id") and node.node_def_id is not None: + device_info["model"] = node.node_def_id + # Numerical Device Type + if hasattr(node, "type") and node.type is not None: + device_info["model"] += f" {node.type}" + if hasattr(node, "protocol"): + device_info["manufacturer"] = node.protocol + if node.protocol == PROTO_ZWAVE: + # Get extra information for Z-Wave Devices + device_info["manufacturer"] += f" MfrID:{node.zwave_props.mfr_id}" + device_info["model"] += ( + f" Type:{node.zwave_props.devtype_gen} " + f"ProductTypeID:{node.zwave_props.prod_type_id} " + f"ProductID:{node.zwave_props.product_id}" + ) + # Note: sw_version is not exposed by the ISY for the individual devices. + + return device_info + @property def unique_id(self) -> str: """Get the unique identifier of the device.""" diff --git a/tests/components/isy994/test_config_flow.py b/tests/components/isy994/test_config_flow.py index c9c914b5a79..c1ca0ab4b5f 100644 --- a/tests/components/isy994/test_config_flow.py +++ b/tests/components/isy994/test_config_flow.py @@ -74,7 +74,7 @@ async def test_form(hass: HomeAssistantType): PATCH_ASYNC_SETUP_ENTRY, return_value=True, ) as mock_setup_entry: isy_conn = mock_connection_class.return_value - isy_conn.get_config.return_value = "" + isy_conn.get_config.return_value = None mock_config_class.return_value = MOCK_VALIDATED_RESPONSE result2 = await hass.config_entries.flow.async_configure( result["flow_id"], MOCK_USER_INPUT, @@ -154,7 +154,7 @@ async def test_form_existing_config_entry(hass: HomeAssistantType): PATCH_CONNECTION ) as mock_connection_class: isy_conn = mock_connection_class.return_value - isy_conn.get_config.return_value = "" + isy_conn.get_config.return_value = None mock_config_class.return_value = MOCK_VALIDATED_RESPONSE result2 = await hass.config_entries.flow.async_configure( result["flow_id"], MOCK_USER_INPUT, @@ -170,7 +170,7 @@ async def test_import_flow_some_fields(hass: HomeAssistantType) -> None: PATCH_ASYNC_SETUP_ENTRY, return_value=True, ): isy_conn = mock_connection_class.return_value - isy_conn.get_config.return_value = "" + isy_conn.get_config.return_value = None mock_config_class.return_value = MOCK_VALIDATED_RESPONSE result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=MOCK_IMPORT_BASIC_CONFIG, @@ -190,7 +190,7 @@ async def test_import_flow_all_fields(hass: HomeAssistantType) -> None: PATCH_ASYNC_SETUP_ENTRY, return_value=True, ): isy_conn = mock_connection_class.return_value - isy_conn.get_config.return_value = "" + isy_conn.get_config.return_value = None mock_config_class.return_value = MOCK_VALIDATED_RESPONSE result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=MOCK_IMPORT_FULL_CONFIG,