- Published on
Marking Dependent Devices in Home Assistant: No Native Way, So Here Is What I Built
- Authors

- Name
- Susanne Moog
I have been cleaning up my Home Assistant instance before building new dashboards on top of it, and one recurring annoyance was entities that looked broken but were not: media players, smart speakers, and sensors sitting at unavailable because I deliberately switch off the smart socket powering them when they are not in use, mostly for energy saving — no point keeping a media console on standby draw overnight. Each one is electrically dependent on a socket that is a completely separate device in Home Assistant's eyes.
Functionally that is fine. It is supposed to go dark when the power is cut. The problem is Home Assistant has no way of knowing that is expected. Every one of those entities shows up identically to a genuinely dead sensor: red, "unavailable," logged, recorded, dashboarded — indistinguishable from an actual fault unless you already know which entities are innocent.
Home Assistant has no built-in way to express that relationship between two devices — whether you think of it as a parent/child device relationship, a master switch with dependents, linked devices, or a power cascade. via_device_id looks like a candidate but models connectivity, not power — it is how a Zigbee bulb points at its coordinator, not how a plug relates to whatever is plugged into it. There is no dedicated fix for this in core.
The solution: labels + Spook
The fix is a combination of a native feature and a third-party integration.
Labels (Settings → Areas, Labels & Zones) are flat tags you can stick on devices, entities, or areas. No hierarchy, no dependency semantics — but they are enough to say "these things all belong to the same physical socket." I settled on a dep:sockets-<socket-name> naming convention and tag every device — not entity, see the note below — that goes dark with a given socket.
Spook is where the actual mechanism comes from. It is a well-known "does the things core will not" toolbox integration, installed via HACS and then added separately as an integration under Settings → Devices & Services — HACS alone only stages the code, the integration entry is a required second step. Among its many features it adds homeassistant.disable_device / homeassistant.enable_device actions you can call from automations.
Combine the two, and you get: socket turns off → disable its dependent devices; socket turns on → re-enable them. Disabled devices stop polling, drop out of the recorder automatically, and stop cluttering the UI — which solves the "looks broken" problem and keeps history clean, for free. That matters because the recorder still cannot exclude entities by label (open feature request), so this does double duty as a recorder-exclude workaround too.
The actual setup
One shared script that takes a label and a target state, created via the UI script editor:
alias: Spook - Set dependents by label
fields:
label:
required: true
example: 'dep:sockets-media-console'
state:
required: true
example: 'off'
sequence:
- choose:
- conditions: "{{ state == 'off' }}"
sequence:
- action: homeassistant.disable_device
target:
device_id: '{{ label_devices(label) }}'
default:
- action: homeassistant.enable_device
target:
device_id: '{{ label_devices(label) }}'
And then one small automation per socket:
alias: 'Dependents: media console socket'
trigger:
- trigger: state
entity_id: switch.media_console
to: 'off'
- trigger: state
entity_id: switch.media_console
to: 'on'
action:
- action: script.spook_set_dependents_by_label
data:
label: 'dep:sockets-media-console'
state: '{{ trigger.to_state.state }}'
Tag the devices behind a socket with its label, add one automation, done. Adding the next socket is copy-paste plus a new label. One note on the UI: the single-script and single-automation "Edit in YAML" views only want the inner keys (alias/fields/sequence, or alias/trigger/action) — no script: or automation: wrapper, that is config-entry-level YAML only.
It is possible to avoid the copy-paste entirely with a single automation that triggers on all the socket entities at once and maps trigger.entity_id to the right label via a lookup dict in a variables: block, instead of hardcoding the label in each automation's action: data. That is a real trade-off, not a strictly-better-or-worse one: one generic automation is less to maintain in the list view, but the socket-to-label mapping moves into templating logic that is harder to read at a glance a year later — whereas the per-socket copy is trivial to scan, trivial to copy for the next socket, and does not require remembering how the mapping is built. For the handful of sockets in a typical setup, the copy-paste stays the easier one to come back to.
One detail worth getting right from the start: use the device-level actions (homeassistant.disable_device / enable_device) together with label_devices(), not their entity-level equivalents. A label applied to a device does not cascade down to that device's entities, so label_entities() on a device-labeled setup silently returns nothing. Device-level lookups also scale better: one label on a device covers every entity it exposes, and it handles a socket powering several distinct devices at once without extra bookkeeping. Before wiring anything into an automation, confirm the label resolves as expected in Developer Tools → Template — {{ label_devices('dep:sockets-media-console') }} — and test the automation itself by toggling the real switch or using its own ▶ Run button, rather than calling the script directly from Developer Tools → Actions, since trigger context like trigger.to_state only exists inside a real automation run.
Links
- Spook — the integration doing the actual enable/disable work
- Recorder include/exclude on area-based label — the still-open recorder-side version of this gap