Home AssistantHome AssistantContributors
On this page

The device registry is where Home Assistant keeps track of devices. A device groups the entities that belong together: a temperature and humidity sensor with a battery is one device with three entities. Users see devices on their own page, put them in areas, and target them in automations.

What is a device

A device is a physical device with its own control unit, or a service. The control unit does not have to be smart, but it has to be in control of what happens.

  • A thermostat with four room sensors is five devices: one for the thermostat with its built-in sensors, and one per room sensor.
  • A sensor connected to another device to read its data is a separate device from that other device. The sensor could be moved to read another device.
  • A product with several independent parts, like a power strip with separately switched outlets, can be split into a parent device with child devices.

Devices belong to the config entry that registered them. A physical device that is set up by two integrations is two devices, one per config entry.

Device properties

Property What it is
id The ID Home Assistant generated for the device
identifiers A set of (DOMAIN, identifier) tuples, like a serial number. Each one is unique within the config entry.
connections A set of (connection_type, value) tuples, like a MAC address. Each one is unique within the config entry. The types are CONNECTION_NETWORK_MAC, CONNECTION_BLUETOOTH, CONNECTION_UPNP, and CONNECTION_ZIGBEE.
config_entry_id The config entry the device belongs to
config_subentry_id The subentry the device belongs to, or None
name The name of the device
name_by_user The name the user gave it
manufacturer The manufacturer
model The model name
model_id The model identifier
serial_number The serial number. Unlike a serial number in identifiers, it does not have to be unique.
sw_version The firmware version
hw_version The hardware version
configuration_url Where the device or service can be configured. Use homeassistant://<path> to link to a page inside Home Assistant.
entry_type None, or DeviceEntryType.SERVICE for a service
via_device_id The device Home Assistant reaches this device through, like a hub
parent_device_id For a child device: its parent
area_id The area the device is in
labels The labels of the device
disabled_by Who disabled the device, or None
created_at, modified_at When the device was created and last changed. Managed by the registry.

Register a device through an entity

The usual way: give your entities a device_info. Home Assistant creates or updates the device when the entity is added. That only works for entities added from a config entry, with a unique ID.

Python
from homeassistant.helpers.device_registry import DeviceInfo


class ExampleEntity(CoordinatorEntity[ExampleCoordinator]):
    """Base entity of the Example integration."""

    _attr_has_entity_name = True

    def __init__(self, coordinator: ExampleCoordinator) -> None:
        """Initialize the entity."""
        super().__init__(coordinator)
        device = coordinator.data.device
        self._attr_device_info = DeviceInfo(
            identifiers={(DOMAIN, device.serial_number)},
            manufacturer="Example",
            model=device.model_name,
            model_id=device.model_id,
            name=device.name,
            sw_version=device.firmware_version,
        )

DeviceInfo takes identifiers, connections, name, manufacturer, model, model_id, serial_number, sw_version, hw_version, configuration_url, entry_type, suggested_area, via_device_id, and translation_key with translation_placeholders for a translated name.

The registry looks for an existing device of the same config entry, by identifiers first and then by connections. One match is enough: the other identifiers and connections you pass are then added to that device. If they already belong to another device of the same config entry, that is a collision, and the registry raises an error.

Register a device yourself

A device without entities, like a hub, is registered directly. Do this in async_setup_entry:

Python
from homeassistant.helpers import device_registry as dr

device_registry = dr.async_get(hass)

hub = device_registry.async_get_or_create(
    config_entry_id=entry.entry_id,
    connections={(dr.CONNECTION_NETWORK_MAC, dr.format_mac(info.mac))},
    identifiers={(DOMAIN, info.bridge_id)},
    manufacturer="Signify",
    model=info.model_name,
    model_id=info.model_id,
    name=info.name,
    sw_version=info.sw_version,
)

It returns the DeviceEntry, with its id.

via_device_id tells Home Assistant which device it reaches another device through, like the bulbs behind a bridge. It shows the topology of the network. It takes the Home Assistant ID of the hub device, so the hub has to be registered first.

If you registered the hub yourself, you already have its ID: pass hub.id to your entities. Otherwise, look it up from the identifiers of the hub:

Python
via_device_id = dr.async_get_device_id_by_identifier(
    hass, (DOMAIN, bridge_id), config_entry_id=entry.entry_id
)

It raises ValueError when the hub does not exist yet.

Child devices

Warning

Child devices are new, and the design is still being finalized. The API and behavior can change.

A child device is a lightweight device for a logical part of one physical product, like one outlet of a power strip or one gang of a wall switch. The parent device holds what belongs to the whole product, like the network connection and firmware updates. Each child holds the entities of its part, like a switch and an energy sensor per outlet. Users can put each part in a different area.

Do not mix this up with via_device_id: that is about connectivity between separate products. Child devices are about the parts of one product.

A child device:

  • Belongs to one parent, referenced by the device ID of the parent (parent_device_id).
  • Belongs to the same config entry and subentry as its parent.
  • Has no identity of its own: no connections, via_device_id, manufacturer, model, firmware versions, or serial number. Those belong to the parent.
  • Gets the area of its parent, unless it has its own.
  • Cannot have child devices of its own.

Register a child device

Through an entity, return a ChildDeviceInfo with identifiers and parent_device_id. The parent must already be registered:

Python
from homeassistant.helpers.device_registry import ChildDeviceInfo

self._attr_device_info = ChildDeviceInfo(
    identifiers={(DOMAIN, f"{strip_id}_outlet_{number}")},
    name=f"Outlet {number}",
    parent_device_id=parent_device_id,
)

Or directly:

Python
outlet = device_registry.async_get_or_create_child(
    config_entry_id=entry.entry_id,
    parent_device_id=parent.id,
    identifiers={(DOMAIN, f"{strip_id}_outlet_1")},
    name="Outlet 1",
)

If the parent belongs to a subentry, pass its config_subentry_id as well. It is not inherited. The registry never creates the parent for you: it raises a DeviceInfoError when the parent does not exist, is a child device itself, or belongs to another config entry or subentry.

Move existing devices to child devices

If the identifiers of a child device match an existing device of the same config entry, that device becomes a child device, and keeps its ID. Automations that use the device keep working. That makes it easy to move an integration that already split products into separate devices.

The other way around is not supported directly. To turn a child device back into a normal device, remove it with async_remove_device, then register it again with async_get_or_create, with the same identifiers.

What follows the parent

  • Removing a parent removes its children.
  • Disabling a parent disables its children, and enabling the parent enables the children it disabled again. A child of a disabled parent cannot be enabled on its own.
  • A child without an area of its own is in the area of its parent. dr.async_get_effective_area_id(hass, device) returns the area that applies.
  • Labels are not inherited.
  • A parent with children cannot move to another config entry or subentry.
  • An action that targets a parent device also targets the entities of its children. An action that targets a child only targets the entities of that child.

Look up devices

Inside an entity, use self.device_entry. Elsewhere, scope lookups to the config entry: identifiers and connections are only unique within one config entry.

Python
device_registry = dr.async_get(hass)

# By the ID Home Assistant gave it
device = device_registry.async_get(device_id)

# By one of its identifiers or connections
device = device_registry.async_get_device_by_identifier(
    (DOMAIN, serial_number), entry.entry_id
)
device = device_registry.async_get_device_by_connection(
    (dr.CONNECTION_NETWORK_MAC, mac), entry.entry_id
)

# All devices of a config entry
devices = dr.async_entries_for_config_entry(device_registry, entry.entry_id)

These return normal devices, not child devices. For child devices, use async_get_child_device_by_identifier(identifier, config_entry_id), or dr.async_entries_for_parent_device(device_registry, parent_device_id) for all children of a device. To get every device with an identifier or connection across config entries, use device_registry.async_get_devices().

To get a device together with the config entry of your integration that owns it, for example in an action, use dr.async_get_device_and_config_entry_for_domain(hass, device_id, domain=DOMAIN). It returns (None, None) when the device is unknown, and (device, None) when no config entry of your domain owns it.

Update devices

When something changes, like the firmware version after an update, update the device:

Python
device_registry.async_update_device(device.id, sw_version=new_version)

Let users remove devices

Users can only delete a device from the UI if your integration allows it. Implement async_remove_config_entry_device in __init__.py. Return True if the device can go, after any cleanup it needs:

Python
from homeassistant.helpers.device_registry import DeviceEntry


async def async_remove_config_entry_device(
    hass: HomeAssistant, entry: ExampleConfigEntry, device_entry: DeviceEntry
) -> bool:
    """Allow removing a device that is no longer connected."""
    return not any(
        identifier
        for identifier in device_entry.identifiers
        if identifier[0] == DOMAIN
        and identifier[1] in entry.runtime_data.data.devices
    )

This example only allows removing devices that the hub no longer reports. The integration can also act on EVENT_DEVICE_REGISTRY_UPDATED instead, if that is more convenient.

Integrations that see devices disappear should also remove stale devices by themselves: that is a rule of the Gold tier of the quality scale.

Was this page helpful?

Last updated on .