Replies: 6 comments 8 replies
-
|
Yes, this is a good idea. Some notes:
|
Beta Was this translation helpful? Give feedback.
-
|
Love it! I notice that the proposal suggests that we do device naming as "Socket 1" rather than embedding the parent device name like "Power strip Socket 1". This is good, but there are integrations today that implement sub-devices that do not follow this naming scheme and add the parent device name to the sub-device name. This should likely be then tracked as one of the dev migration tasks for this proposal. One example that comes to mind that I worked on recently is the Reolink integration but I am sure that there are more.
This also needs to be taken into account, since if we omit the parent device name from the This also raises the question that if we use the new toggle to include the parent device name in the sub-device entity names, should that also affect the
Have we ever done migrations on To contribute to the bikeshedding, I think |
Beta Was this translation helpful? Give feedback.
-
|
I think the suggestion in this proposal is very good and would answer the sub-device vs how is the device connected. Since the proposal mention frontend changes that are part of this discussion frontend #52512 it would be nice to understand or have a sample picture of the final result of a device with sub-devices, mainly to understand if the user experience would be similar or better to the existing implementation (I have raised some concerns or clarifications at frontend #52512) |
Beta Was this translation helpful? Give feedback.
-
|
I was actually working on a similar proposal, which I called "Device parts". So that would be my preference when it comes to naming, I never liked "sub-device" to begin with. I would also suggest to make it a completely separate class, as the majority of |
Beta Was this translation helpful? Give feedback.
-
|
This makes a lot of sense. I have only 2 comments/questions. The first is about having a new class instead of adding more attributes to The second is regarding migration: is it really a concern for this proposal? Because even if this proposal was rejected, the "not split yet" integrations would still have the same problem if they used |
Beta Was this translation helpful? Give feedback.
-
|
Comments:
|
Beta Was this translation helpful? Give feedback.

Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Alright, before you dive into this I just wanted to say this similar topics have been discussed many times before. I've taken into account MANY of the historical architectural discussions, issues, historical PRs, meeting minutes, everything I could find.
I seek to provide a complete architectural change to help moving forward problems recently raised, and hopefully coming up with something that provides a meaningful path forward.
This is a long one, but please read it and hear me out.
../Frenck
Background
A lot of physical devices are really several logical devices in one box. A smart power strip is one product, but each outlet switches independently and often measures its own energy. A multi-gang wall switch is one product, but each channel can control lights in a different room. An ESP board or RF bridge can drive things spread across the whole house.
We already split these into sub-devices, and it's the documented, recommended approach. From the [developer documentation](https://developers.home-assistant.io/docs/device_registry_index#what-is-a-device):
This is what [#1036](#1036) accepted in principle, and integrations already do it. ESPHome, for instance, ships sub-devices today.
The catch is the field we use to express it.
via_deviceis doing double duty. Its original and primary job is connectivity (how does Home Assistant reach this device?) for hubs and radios; it literally started life ashub_device_id(ZHA sets it for everything behind the coordinator stick, for instance). We're now also using that exact same field for a parent/child relationship: "this device is a part of that physical device." One pointer, two unrelated meanings.That overload is the actual problem this proposal fixes: give the parent/child relationship its own place in the device model, and let
via_devicego back to meaning only connectivity.What a sub-device is (and isn't)
Let's be precise, because the whole proposal hinges on it.
A sub-device is a virtual device split out of a single physical device. One physical product, subdivided into logical parts. The power strip is the physical device; its outlets are sub-devices. The multi-gang switch is the physical device; its channels are sub-devices. There's usually no separate radio involved. The split is logical, so each part gets its own identity, area, name, and grouped entities.
That is not the same as
via_device.via_deviceanswers a different question: how does Home Assistant reach this device? It's connectivity. The two ends of avia_devicelink are separate physical products, one acting as a gateway for the other:via_devicehere, because a device roams between proxies and the relationship isn't a stable parent at all.So a hub is not a parent of the devices behind it. Those are independent products it provides connectivity to. A power strip is a parent of its outlets. These are two different relationships, and we've been overloading one field to mean both.
The cost of the overload is concrete: the frontend can't tell "connected via X (a gateway)" from "part of X (one physical device)", the existing copy ("Connected via" / "Connected devices") reads wrong for sub-devices, deletion semantics are ambiguous (orphaning a child makes sense for a gateway, not for a sub-device that can't exist without its physical parent), and there's no clean place to inherit an area, or to give a true child proper ownership and lifecycle.
Proposal
Give sub-devices their own dedicated place in the device registry, separate from
via_device.Data model
A sub-device is registered as a dedicated, lightweight sub-device object, not as a full device entry that happens to point at a parent. It carries only what a logical part actually needs: a link to its parent, a name, and an optional area override. It deliberately does not carry connectivity (
via_device, connections), a MAC address, or firmware, because those belong to the physical parent, not to an abstraction.This is the direction we landed on in the architecture meeting: a separate class,
ChildDeviceInfo, reads cleaner for integrations and removes the need for merge checks and inapplicable fields during loading. It's still provided the normal way, through an entity'sdevice_info, which accepts either aDeviceInfoor aChildDeviceInfo. So integrations don't learn a new mechanism, they just use a lighter shape for the parts. Whether it ends up as a separate class or a flag on the existingDeviceInfois still being finalized, but the shape is the same: a minimal sub-device that references one parent.A few rules fall straight out of that:
parent_deviceon theChildDeviceInfo: the parent's identifier. The registry resolves that into a storedparent_device_idon the device entry, exactly likevia_deviceresolves tovia_device_idtoday. Because a sub-device is always created by the same integration as its parent (one physical product split up, not two products linked), the reference only needs the identifier. It does not need the(domain, identifier)tuple thatvia_deviceuses to reach across integrations.via_deviceand no connections. It's an abstraction; the connectivity is the physical parent's.via_devicestays for real connectivity between separate physical devices (hubs, radios, proxies) and never appears on a sub-device. This is what finally un-overloadsvia_device: the parent/child link lives in its own construct, sovia_devicegoes back to meaning only "how do we reach this device."Since almost no integrations read directly from the device registry, exposing this new relationship through the read and websocket APIs is acceptable; the main impact is on those APIs and on migration.
Terminology is still open. In the meeting "child device" came up as possibly more accurate than "sub-device," "device part," or "sibling," and there's a small naming question for the parent reference too (
parent_devicevspart_of). I use "sub-device" throughout this document for consistency, but I'm happy to settle the final term.Depth and cardinality
Keep it to a single level: a device has at most one parent, and a sub-device cannot itself be a parent. No grandchildren, no arbitrary trees.
This isn't laziness, it's a deliberate constraint. Arbitrary nesting multiplies the entity-naming choices (see naming below; every level adds another "include the parent name?" decision), the UI rendering, and the lifecycle rules, for use cases we don't actually have. A power strip with outlets is one level. If a real two-level case shows up later, we can revisit, but we shouldn't pay for it now.
Lifecycle
A sub-device is owned by its physical parent. That ownership has consequences:
via_device, where deleting the parent only resets the children's pointer toNone(orphaning them). A sub-device has no meaning without the physical device it was split from, so it goes with it.Inheritance
Sub-devices inherit exactly one thing from their parent: area. Everything else is set on the sub-device itself.
christmaslabel from its parent, how would you clear just that one on the child? Labels stay explicit per device.Disabled state isn't an inherited value either; it's a hard cascade (see lifecycle). And devices have no state or availability in Home Assistant, so there's nothing of that kind to inherit.
Entity naming
Sub-devices fix entity naming with zero changes to the naming rules, because the rules already key off the device. Initial
entity_idgeneration is based on the area, device, and entity names, and once a channel is a sub-device, the device part is simply the sub-device's name. This is exactly why #1036 preferred real sub-devices over a "drop the prefix" opt-out, and it still holds.The same applies to display names. Home Assistant is moving away from the pre-composed
friendly_nametoward composing the display name from structured parts (area, device, entity). A sub-device slots straight into that as the device-level part, so the displayed name comes out right without special-casing.One thing sub-devices add here is purely a display choice: whether to include the parent device's name when showing the entity. For a "Server Rack PDU" with an "Outlet 3" sub-device, one user wants "Outlet 3 Power" and another wants "Server Rack PDU Outlet 3 Power". That's a per-context toggle in how the name is composed for display. It is not name inheritance in the registry: the sub-device's stored name stays "Outlet 3" either way. It's also a real reason to keep depth at one level, because every extra level multiplies these composition choices.
This couples the proposal to the entity-naming work ([roadmap #73](OpenHomeFoundation/roadmap#73)). For a sub-device rename to flow through to its entities correctly and locale-safely, we need the structured-name backend (its Phase 1) and a resolution to [#1185](#1185) (renaming an entity should set
entity.name, not the oldfriendly_name). The sub-device model should be specified against that work, not assume it's already done.Targeting in automations and actions
Because the parent/child link is now a real relationship, the parent device becomes a meaningful automation/action target. "Turn off the power strip" should expand the parent to its sub-devices' entities, slotting in as a natural level below the existing floor, area, device, entity hierarchy, and showing up in the target picker.
Today this does not happen: target expansion (
homeassistant/helpers/target.py) maps adevice_idonly to entities directly on that device, and never followsvia_device. The crucial design rule: cascade follows the parent/child edge, nevervia_device. You want "target the power strip, hit all outlets"; you absolutely do not want "target the Zigbee coordinator, actuate every Zigbee device" or "target the Bluetooth proxy, hit everything it can see." This is, on its own, one of the strongest reasons to keep the parent/child relationship separate from connectivity.Frontend
Two distinct representations, because they're two distinct relationships:
via_devicekeeps its existing "Connected via" / "Connected devices" treatment, ideally as a separate network/diagnostics view. Copy should clearly distinguish "part of X" from "connected via X."The parent device page is just one surface. The devices overview page is another: the devices data table could, for example, show sub-devices nested under their parent rather than as loose rows. Exactly how to present that (here and elsewhere) is for the UX designers to work out; the point for this proposal is that a real parent/child relationship in the registry is what makes any of these views possible.
The frontend work is also the current practical blocker. Splitting has stalled before precisely because the UI needs to represent links between related devices cleanly, so this is a design challenge to take on, not a core-functionality gap.
Integrations that benefit today
This isn't theoretical. Concrete candidates that would benefit right away:
The already-split ones (ESPHome, Reolink, Shelly) are the lowest-risk early adopters, since they've already accepted separate devices for the parts. Whether their existing
device_ids carry over unchanged depends on how we migrate them to the new sub-device object (see migration).Migration: the part that actually decides whether this ships
The hard problem isn't the data model, it's that splitting a device into sub-devices moves entities onto new sub-device objects, which changes their
device_idand breaks every automation and script that uses device triggers or device actions, most painfully device triggers for remotes. This is the blocker the OHF Protocols team flagged, and the proposal has to answer it head-on. In the meeting we agreed the direction (a dedicated sub-device class), but left the exact migration mechanism for further discussion; this section is where that work lands.There are two very different cases:
via_device: ESPHome, Shelly, Reolink, Zigbee2MQTT. Their sub-devices already exist as separate device entries, so they're the lowest-risk group. Whether we can preserve their existingdevice_ids when converting them to the new sub-device object is a key migration question; if we can, these integrations move over with no breakage and gain area inheritance for free.via_deviceway. They're one flat device. When they split, entities move to new sub-device objects and theirdevice_idchanges. This is where automations break, and since it's the common case, the breakage is broad, not narrow.The one thing that softens it: splitting is opt-in and gradual. It lands integration by integration, as each one chooses to adopt sub-devices, not in a single big migration.
The mechanism I'd lead with is a
device_idredirect. When an integration splits, keep a mapping from the old (parent)device_idto the new sub-device, and resolve old device references in triggers and actions through it, so existing automations keep working without the user touching anything. Automatically rewriting hand-writtenconfiguration.yamlis not viable: we can't preserve comments and formatting, so a rewrite would mangle the user's file. UI-managed automations (stored in.storage) could in principle be migrated, but the redirect covers both cases and is far less risky.One more migration task: some integrations that already split embed the parent name in the sub-device name (Reolink, for example). Those should drop the prefix, so the sub-device is named "Outlet 3", not "Power strip Outlet 3".
There are also sequencing dependencies.
via_deviceitself is untouched: ZHA keeps using it for the coordinator link, exactly as today. The dependency only applies if we split a ZHA device such as a remote into sub-devices by endpoint, which interacts badly with ZHA still using device triggers rather than event entities. That kind of split wants the event-entities-for-remotes work ([#1377](#1377)) and theevent.receivedtrigger out of HA Labs first. And there's an agreed interim stop-gap: a number suffix for duplicated entities, like Matter already does ([core #168901](home-assistant/core#168901)), to improve the UX today while this lands and to be removed once it does.Open questions
parent_devicevspart_of).ChildDeviceInfo) versus a flag on the existingDeviceInfo. Direction is the separate class; the exact form is being finalized.device_idredirect, whether already-split integrations keep their existingdevice_ids, and whether migration runs automatically when an integration opts in.Why this is worth it
Sub-devices give us logical grouping, clean names, per-sub-device areas with sane inheritance, a parent that's a real automation target, and a frontend that finally shows a power strip as a power strip. Most of that is only cleanly achievable once the parent/child relationship exists in its own right. The overloaded
via_devicecan't do inheritance or safe targeting without doing the wrong thing for hubs and radios. And it's a foundational piece of "devices in context": before Home Assistant can treat a bundle of entities as a fridge or a car, it needs to actually model what's part of what.For whomever is interested, here is a full research done with AI, with tons of history and references that have really helped me land this proposal.
https://docs.google.com/document/d/14hnKDhOrOGddepo_pOatEo0XsWbuqevVJz-9RnUbf-U/edit?tab=t.0
Changelog
via_deviceis auto-derived fromparent_device. Corrected the earlier claim that the two fields are independent. (Superseded by the 2026-07-02 direction below.)device_idredirect as the viable mechanism, and ruled out rewriting hand-written YAML. Added a task to drop parent-name prefixes from sub-device names (for example, Reolink).via_devicecoordinator link is unaffected; the event-entity dependency only applies to devices we would split into sub-devices.parent_devicefield. It has no connectivity, MAC, or firmware of its own. Removed the earlier auto-derivedvia_deviceframing: a sub-device is an abstraction, so it simply has novia_device, and that is what un-overloads the field. The exact class-vs-field form is deferred to a follow-up.ChildDeviceInfo, and noted it is still provided through an entity'sdevice_info, which accepts either aDeviceInfoor aChildDeviceInfo.Beta Was this translation helpful? Give feedback.
All reactions