Why and how to avoid device_ids in automations and scripts

Background

When you start writing automations, a device trigger seems the obvious choice. Everybody knows what a device is and it comes at the top of the UI dropdown list.

It works… but it is certainly not a “great way to start” because it is storing up problems for the future. Far better to use an entity, with state or numeric_state.

Device_id problems

  • The yaml will be longer and harder to follow, which may make maintenance difficult, especially a few months down the line when you have forgotten the details. For the same reason, sharing code (on this forum, for example) is harder. Since you can’t add comments to automations each step needs to be as clear as possible. Why have this:

    trigger: 
      - type: battery_level
        platform: device
        device_id: 16fbcd84302f4606819c58559aa90c82
        entity_id: 6c2e634686ebe58e571fefcdd6474f22
        domain: sensor
        below: 20
    

    When you could have this:

    trigger:
      - platform: numeric_state
        entity_id: sensor.mobile_battery_level
        below: "20"
    
  • If you replace a device you will have to go through every automation it appears in, changing the device_id. If you only ever use entity_ids in your triggers, conditions and actions, then when you have to replace a device you can change the new entity_id to match the old one in one place, and all your automations keep working.

  • Automations using device_id report as unavailable if they depend on missing devices.

  • Using entity states you can exclude “unavailable” and “unknown” from your triggers:

    trigger:
      - platform: state
        entity_id: button.EXAMPLE
        not_to:
          - unavailable
          - unknown
        not_from:
          - unavailable
          - unknown
    
  • Device actions, triggers and conditions do not support more advanced features like templating.

    Templating - Home Assistant

Devices and entities

So entity ids are preferable to device ids. What’s the difference?

Essentially devices, often physical objects, are containers for a group of related entities. The entities are the functions or services the device provides.

For example, a Philips Hue motion sensor is a device which acts as a container for seven entities.

  • Three of the entities are sensors (temperature, light and motion). The temperature and light sensors have a numeric state; the motion sensor is binary - its state will be either “on” or “off”.

  • Two are switches allowing light and motion sensors to be switched on and off; these are also binary.

  • Two are diagnostic (battery level and zigbee connectivity). The battery level sensor will have a numeric state; the zigbee sensor will have a state such as “connected”.

Confusingly, while all devices should have associated entities, an entity does not have to belong to a device. For example, you might want to have a binary sensor that was “on” when your phone was connected to your home wi-fi and otherwise “off”, even if it was connected elsewhere. In this case you could create a template sensor, derived from, but not part of the device:

template:
  - binary_sensor:
      - name: "phone on wifi"
        state: >
          {{ is_state('sensor.mobile_wifi_connection', 'MyNetwork') }}

In addition a great many entities are created by integrations that have nothing to do with any device:

A few entities are not part of a device or service. Examples of standalone entities are automation, script, scene entities, and helper entities.

States

Numeric state triggers are for anything you can count. Both binary sensors and non-numeric sensors use State triggers and conditions (binary sensors will just be “on” or “off”).

Unfortunately, in an effort to make the UI more friendly, some developers have introduced substitutions. In the Philips Hue example above, for example, the motion sensor will show as either “Clear” or “Detected” in the UI.

But it’s a binary sensor, and if you look at the yaml it will always be “on” or “off”:

trigger:
  - platform: state
    entity_id:
      - binary_sensor.yard_motion_sensor_motion
    from: "off"
    to: "on"

If you write in yaml, “on” and “off” are the states you have to use. Note too that in the UI all the states have an initial capital letter; in yaml they should be lower case. If you need to check which states you should be using, go to Developer Tools | States:

Binary sensor state in Developer Tools

Buttons

Having said all that, buttons are an oddity. They do not have a state. A button press is an event - it doesn’t remain on or off. The entity does, however, retain a timestamp showing the last time it was pressed and this becomes its state, allowing it to be used as an automation trigger when it changes.

Then do…

To avoid device ids in the “Then do” section of an automation, choose other actions and perform action from the dropdown lists.

Most action names begin with the device class - light, switch, media_player etc. - followed by the action - turn on, turn off, play, pause, etc.

You will then be invited to select one or more target entities.

Changing the entity name on a new device

If you replace one of your devices, say the motion sensor mentioned above, you can change the names of the entities associated with it to the names you have already used in your automations, saving yourself a lot of work. Go to Devices & Services | Devices and select the device. For each of the entities in turn, click on the name, then on the cogwheeel “settings” icon top right.

You can edit the Entity ID field to match the entity ID from the old device.


The Home Assistant Cookbook - Index.

40 Likes

That should have been fixed. The automations should now report as unavailable if they depend on missing devices. However that change may have made things worse:

1 Like

Damn. I got that from one of your posts :grinning_face_with_smiling_eyes:. Guide updated, thanks…

Excellent advice! Just went through and updated all my automations to not use device IDs in the actions, but some of the automations use device_id’s in the conditions. is there a way to fix that? Most of my conditions are simple “is it on”, for example:

      - condition: device
        device_id: 5f3f155a7995b74b6a36ec9555cfc217
        domain: media_player
        entity_id: media_player.big_tv
        type: is_on
1 Like

Use a State condition (or whatever the appropriate condition is for other cases)…

- condition: state
  entity_id: media_player.big_tv
  state: 'on'
4 Likes

How does one apply this advice to a manufacturer-specific ZHA command? Example: I have an Inovelli switch and the Device action gives me this “Issue effect for individual LED” option which then enables various fields (defined here in ZHA):

The YAML for this action looks like:

type: issue_individual_led_effect
domain: zha
device_id: ccd62cc4a251872fa2edef785bdf798e
led_number: 4
effect_type: Clear
color: 150
level: 100
duration: 255

However, there does not appear to be a service named zha.issue_individual_led_effect and when I try to use that name in a “Call service” action I don’t see the fields anymore:

Further, I was hoping to be able to set the device id (or entity id) of this action using a template, so that I can fill in a script input there. Is that possible?

The service call is zha.issue_zigbee_cluster_command.

Here’s a script blueprint:

3 Likes

Here’s a current Jan-2024 output from the UI editor.

Which of these 2 conditions would you rather troubleshoot?
2 - 32 character random hex strings or an entity_id?

action:
  - choose:
      - conditions:
          - condition: trigger
            id:
              - dirty
        sequence:
          - type: turn_on
            device_id: a98db942fb9411339403edc7c7f9e3de
            entity_id: fa492ac875bdb706179edac181b3297a
            domain: fan
      - conditions:
          - condition: trigger
            id:
              - clean
        sequence:
          - service: fan.turn_off
            target:
              entity_id: fan.r2d2
            data: {}

I rest my case.

So how does using Entities affect automations say for instance i have a motion sensor that turns on a led strip if all the upstairs lights are off. If these are smart bulbs and someone flicks the (not smart) switch and the bulb becomes unavailable will this cancel the automation or will it count “unavailable” as off?

The state will be unavailable. If you want to trigger from on to off or from on to unavailable or unknown do this:

trigger:
  - platform: state
    entity_id: light.my_smart_light
    from: 'on'

if you only want to trigger from on to off do:

trigger:
  - platform: state
    entity_id: light.my_smart_light
    from: 'on'
    to: 'off'

Correct. Unavailable is unavailable, unknown is unknown, neither would be considered as off.

Off is off.

I had 1 of those, mainly as an experiment in the UI editor for automations (I have them all in yaml.), and it was even more cryptical than most above:

  - type: moist
    platform: device
    device_id: afa7a35223f4392eaaf6c1b88ab072db
    entity_id: 2adfe81af765e65dad7ce32ca8bdf1e9
    domain: binary_sensor

I mean, we can deduce its about a sensor detecting moist, but it doesnt even contain a to state? only a type?

  - platform: state
    entity_id: binary_sensor.waterlek_sensor
    to: 'on'
    # from: 'off' # if you require so

yep definitely agree on the easier choice here…

funny thing is that the automation fires a notification, during the period the device is ‘on’, and that actually does:

        while:
          - condition: state
            entity_id: binary_sensor.waterlek_sensor
            state: 'on'

so even the UI thinks its better to use that entity_id :wink:

2 Likes

If you want to rename the device and all the entities below are modified, then you have to go to all the automation to modifying those.
Using device_id will not be impacted by this. There are some pro and cons.

1 Like

That was helpful, for me at least, in understanding how that works.

And in turn it has me appreciate the Watchman integration even more, and thought the integration deserved a mention in for those even greener than I am with HA, in this regard.

Watchman is a great help in there.

Also there other benefits to use device_id as this will be used by HA in some parts of the code like in logbook apparently (I’m just repeating info that I got from the forum).

Check my Feature request if you are interested:

1 Like

When you onboard a new device you should first give it a sensible name. If you’re renaming after you’ve started using it, that’s a different matter, but you likely will only have to do the search and replace once ever (assuming it gets a proper name the second time around). If you replace a device you will have to rename the usages every time you replace a device, as opposed to renaming device entities.

1 Like

Renaming entities is a choice, if you do not, automations will work, if you do change them, it is exactly the same amount of work as changing device IDs.

The biggest dealbreakers using device ids for me are:

  • I had integrations suddenly changing all device id’s, breaking all automations. Then there’s no help what changed to what, because old id’s no longer exist. Automations using entity ids did not break.
  • If a device breaks that is used in a trigger, the whole automation is disabled, even if there are more triggers that could have still worked perfectly. Entities don’t have that problem.
  • Device conditions/triggers are hard to get support on from others here, because the behavior and actions are specific to the device.
  • When a device breaks or is replaced by something else: entity - no problem, with device ids: always needs fixing everywhere. It is not even a given that just search/replace the device id will work across different brands/integrations.
4 Likes

How to avoid device_id for scene controller?

I have a Z-Wave button, or really a “scene controller”, that I use to turn on a pump. If I use the UI to create an automation for scene 002:

The resulting action looks like this, which does trigger:

trigger:
  - platform: device
    device_id: f381791969ec48f1a1405bfe1f9f39b0
    domain: zwave_js
    type: event.value_notification.central_scene
    property: scene
    property_key: "002"
    endpoint: 0
    command_class: 91
    subtype: Endpoint 0 Scene 002

Here’s the entity for that scene:

I can test state like this, but that only triggers on the attribute changing – yes, it will trigger if I first press the button 2x times to set KeyPressed2x, but if it is already KeyPressed it won’t trigger since it’s not changing.

trigger:
  - platform: state
    entity_id:
      - event.recirc_pump_remote_switch_scene_002
    attribute: event_type
    to: KeyPressed

I tried using an event trigger like this, but it’s not triggering. Plus, I’m wondering if triggering on such a frequent event is a bad idea.

trigger:
  - platform: event
    event_type: state_changed
    event_data:
      new_state:
        entity_id: event.recirc_pump_remote_switch_scene_002
        attributes:
          event_type: KeyPressed

Is there another approach?

Here’s what the event looks like:

event_type: state_changed
data:
  entity_id: event.recirc_pump_remote_switch_scene_002
  old_state:
    entity_id: event.recirc_pump_remote_switch_scene_002
    state: "2024-06-09T17:39:03.528+00:00"
    attributes:
      event_types:
        - KeyHeldDown
        - KeyPressed
        - KeyPressed2x
        - KeyPressed3x
        - KeyPressed4x
        - KeyPressed5x
        - KeyReleased
      event_type: KeyPressed
      value: 0
      friendly_name: Recirc Pump Remote Switch Scene 002
    last_changed: "2024-06-09T17:39:03.528460+00:00"
    last_reported: "2024-06-09T17:39:03.528460+00:00"
    last_updated: "2024-06-09T17:39:03.528460+00:00"
    context:
      id: 01HZZ1A3784S14M58679N86CM9
      parent_id: null
      user_id: null
  new_state:
    entity_id: event.recirc_pump_remote_switch_scene_002
    state: "2024-06-09T17:47:41.061+00:00"
    attributes:
      event_types:
        - KeyHeldDown
        - KeyPressed
        - KeyPressed2x
        - KeyPressed3x
        - KeyPressed4x
        - KeyPressed5x
        - KeyReleased
      event_type: KeyPressed
      value: 0
      friendly_name: Recirc Pump Remote Switch Scene 002
    last_changed: "2024-06-09T17:47:41.061750+00:00"
    last_reported: "2024-06-09T17:47:41.061750+00:00"
    last_updated: "2024-06-09T17:47:41.061750+00:00"
    context:
      id: 01HZZ1SWM57FP2QKQH328A7YWF
      parent_id: null
      user_id: null
origin: LOCAL
time_fired: "2024-06-09T17:47:41.061750+00:00"
context:
  id: 01HZZ1SWM57FP2QKQH328A7YWF
  parent_id: null
  user_id: null
trigger:
  - platform: state
    entity_id: event.recirc_pump_remote_switch_scene_002
    to: null
condition: 
  - condition: state 
    entity_id: event.recirc_pump_remote_switch_scene_002
    attribute: event_type
    state: KeyPressed

this would be a solution.
But I would combine all actions for each event type in one automation using choose