diff --git a/homeassistant/components/bond/entity.py b/homeassistant/components/bond/entity.py index 65bb79e42f3..d435faf0a7c 100644 --- a/homeassistant/components/bond/entity.py +++ b/homeassistant/components/bond/entity.py @@ -10,9 +10,8 @@ from typing import Any from aiohttp import ClientError from bond_api import BPUPSubscriptions -from homeassistant.const import ATTR_NAME from homeassistant.core import callback -from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity import DeviceInfo, Entity from homeassistant.helpers.event import async_track_time_interval from .const import DOMAIN @@ -65,18 +64,24 @@ class BondEntity(Entity): return False @property - def device_info(self) -> dict[str, Any] | None: + def device_info(self) -> DeviceInfo: """Get a an HA device representing this Bond controlled device.""" - device_info = { - ATTR_NAME: self.name, + device_info: DeviceInfo = { "manufacturer": self._hub.make, - "identifiers": {(DOMAIN, self._hub.bond_id, self._device.device_id)}, - "suggested_area": self._device.location, - "via_device": (DOMAIN, self._hub.bond_id), + # type ignore: tuple items should not be Optional + "identifiers": {(DOMAIN, self._hub.bond_id, self._device.device_id)}, # type: ignore[arg-type] } + if self.name is not None: + device_info["name"] = self.name + if self._hub.bond_id is not None: + device_info["via_device"] = (DOMAIN, self._hub.bond_id) + if self._device.location is not None: + device_info["suggested_area"] = self._device.location if not self._hub.is_bridge: - device_info["model"] = self._hub.model - device_info["sw_version"] = self._hub.fw_ver + if self._hub.model is not None: + device_info["model"] = self._hub.model + if self._hub.fw_ver is not None: + device_info["sw_version"] = self._hub.fw_ver else: model_data = [] if self._device.branding_profile: diff --git a/homeassistant/components/brother/sensor.py b/homeassistant/components/brother/sensor.py index 594da5fe84c..8cb153ac199 100644 --- a/homeassistant/components/brother/sensor.py +++ b/homeassistant/components/brother/sensor.py @@ -7,6 +7,7 @@ from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import DEVICE_CLASS_TIMESTAMP from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -32,7 +33,7 @@ async def async_setup_entry( sensors = [] - device_info = { + device_info: DeviceInfo = { "identifiers": {(DOMAIN, coordinator.data.serial)}, "name": coordinator.data.model, "manufacturer": ATTR_MANUFACTURER, @@ -53,7 +54,7 @@ class BrotherPrinterSensor(CoordinatorEntity, SensorEntity): self, coordinator: BrotherDataUpdateCoordinator, kind: str, - device_info: dict[str, Any], + device_info: DeviceInfo, ) -> None: """Initialize.""" super().__init__(coordinator) @@ -110,7 +111,7 @@ class BrotherPrinterSensor(CoordinatorEntity, SensorEntity): return self._description["unit"] @property - def device_info(self) -> dict[str, Any]: + def device_info(self) -> DeviceInfo: """Return the device info.""" return self._device_info diff --git a/homeassistant/components/heos/media_player.py b/homeassistant/components/heos/media_player.py index 565c1ac7aa4..63b592f1359 100644 --- a/homeassistant/components/heos/media_player.py +++ b/homeassistant/components/heos/media_player.py @@ -31,6 +31,7 @@ from homeassistant.components.media_player.const import ( from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_IDLE, STATE_PAUSED, STATE_PLAYING from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import DeviceInfo from homeassistant.util.dt import utcnow from .const import DATA_SOURCE_MANAGER, DOMAIN as HEOS_DOMAIN, SIGNAL_HEOS_UPDATED @@ -253,7 +254,7 @@ class HeosMediaPlayer(MediaPlayerEntity): return self._player.available @property - def device_info(self) -> dict: + def device_info(self) -> DeviceInfo: """Get attributes about the device.""" return { "identifiers": {(HEOS_DOMAIN, self._player.player_id)}, diff --git a/homeassistant/components/huawei_lte/__init__.py b/homeassistant/components/huawei_lte/__init__.py index 108e581ddd6..7bd30cbdbf6 100644 --- a/homeassistant/components/huawei_lte/__init__.py +++ b/homeassistant/components/huawei_lte/__init__.py @@ -49,7 +49,7 @@ from homeassistant.helpers import ( discovery, ) from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send -from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity import DeviceInfo, Entity from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import ConfigType @@ -600,7 +600,7 @@ class HuaweiLteBaseEntity(Entity): return False @property - def device_info(self) -> dict[str, Any]: + def device_info(self) -> DeviceInfo: """Get info for matching with parent router.""" return { "identifiers": self.router.device_identifiers, diff --git a/homeassistant/components/hyperion/light.py b/homeassistant/components/hyperion/light.py index 4449b9baf71..f8a384731b3 100644 --- a/homeassistant/components/hyperion/light.py +++ b/homeassistant/components/hyperion/light.py @@ -24,6 +24,7 @@ from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, ) +from homeassistant.helpers.entity import DeviceInfo import homeassistant.util.color as color_util from . import ( @@ -234,7 +235,7 @@ class HyperionBaseLight(LightEntity): return self._unique_id @property - def device_info(self) -> dict[str, Any] | None: + def device_info(self) -> DeviceInfo: """Return device information.""" return { "identifiers": {(DOMAIN, self._device_id)}, diff --git a/homeassistant/components/hyperion/switch.py b/homeassistant/components/hyperion/switch.py index b7e7847e447..e24a6ac7dda 100644 --- a/homeassistant/components/hyperion/switch.py +++ b/homeassistant/components/hyperion/switch.py @@ -31,6 +31,7 @@ from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, ) +from homeassistant.helpers.entity import DeviceInfo from homeassistant.util import slugify from . import ( @@ -179,7 +180,7 @@ class HyperionComponentSwitch(SwitchEntity): return bool(self._client.has_loaded_state) @property - def device_info(self) -> dict[str, Any] | None: + def device_info(self) -> DeviceInfo: """Return device information.""" return { "identifiers": {(DOMAIN, self._device_id)}, diff --git a/homeassistant/components/twinkly/light.py b/homeassistant/components/twinkly/light.py index 1918839b4b2..0c03ceaf74a 100644 --- a/homeassistant/components/twinkly/light.py +++ b/homeassistant/components/twinkly/light.py @@ -3,7 +3,6 @@ from __future__ import annotations import asyncio import logging -from typing import Any from aiohttp import ClientError @@ -14,6 +13,7 @@ from homeassistant.components.light import ( ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import DeviceInfo from .const import ( ATTR_HOST, @@ -105,7 +105,7 @@ class TwinklyLight(LightEntity): return "mdi:string-lights" @property - def device_info(self) -> dict[str, Any] | None: + def device_info(self) -> DeviceInfo | None: """Get device specific attributes.""" return ( { diff --git a/homeassistant/components/waze_travel_time/sensor.py b/homeassistant/components/waze_travel_time/sensor.py index 2796838ee50..4277d0bffb5 100644 --- a/homeassistant/components/waze_travel_time/sensor.py +++ b/homeassistant/components/waze_travel_time/sensor.py @@ -4,7 +4,7 @@ from __future__ import annotations from datetime import timedelta import logging import re -from typing import Any, Callable +from typing import Callable from WazeRouteCalculator import WazeRouteCalculator, WRCError import voluptuous as vol @@ -23,6 +23,7 @@ from homeassistant.const import ( ) from homeassistant.core import Config, CoreState, HomeAssistant import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import DeviceInfo from .const import ( ATTR_DESTINATION, @@ -263,7 +264,7 @@ class WazeTravelTime(SensorEntity): self._waze_data.update() @property - def device_info(self) -> dict[str, Any] | None: + def device_info(self) -> DeviceInfo: """Return device specific attributes.""" return { "name": "Waze", diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 322bdb0f286..2d7dc961e68 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -9,7 +9,7 @@ from zwave_js_server.model.value import Value as ZwaveValue, get_value_id from homeassistant.config_entries import ConfigEntry from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity import DeviceInfo, Entity from .const import DOMAIN from .discovery import ZwaveDiscoveryInfo @@ -92,7 +92,7 @@ class ZWaveBaseEntity(Entity): ) @property - def device_info(self) -> dict: + def device_info(self) -> DeviceInfo: """Return device information for the device registry.""" # device is precreated in main handler return { diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 1706d9b309d..dd6d1836857 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -10,7 +10,7 @@ import logging import math import sys from timeit import default_timer as timer -from typing import Any +from typing import Any, TypedDict from homeassistant.config import DATA_CUSTOMIZE from homeassistant.const import ( @@ -110,6 +110,23 @@ def get_supported_features(hass: HomeAssistant, entity_id: str) -> int: return entry.supported_features or 0 +class DeviceInfo(TypedDict, total=False): + """Entity device information for device registry.""" + + name: str + connections: set[tuple[str, str]] + identifiers: set[tuple[str, ...]] + manufacturer: str + model: str + suggested_area: str + sw_version: str + via_device: tuple[str, str] + entry_type: str | None + default_name: str + default_manufacturer: str + default_model: str + + class Entity(ABC): """An abstract class for Home Assistant entities.""" @@ -214,7 +231,7 @@ class Entity(ABC): return None @property - def device_info(self) -> Mapping[str, Any] | None: + def device_info(self) -> DeviceInfo | None: """Return device specific attributes. Implemented by platform classes.