Have HA Voice announce when a door sensor is below 20%

Below is the YAML I made but I can’t really test it until a battery is actually below 20% so for now I have a few questions; First, is the most efficient way to be doing this?

Second, when I run test it says, “The door sensor battery is below 20 percent.” and skips over the trigger.entity_id part which I guess makes sense since an entity didn’t actually trigger the automation but when it does run, will it say a friendly name of the device/event or read out the actual ID of the code like, “7basdfasdfasdf0e” ?

alias: Door sensor (any) below 20%
description: ""
triggers:
 - trigger: sun
    event: sunset
    offset: "00:05:00"
conditions:
 - type: is_battery_level
    condition: device
    device_id: 16asdfasdfasdf59
    entity_id: 09asdfasdfasdff7
    domain: sensor
    below: 20
  - type: is_battery_level
    condition: device
    device_id: b1asdfasdfasdfd
    entity_id: 2asdfasdfasdf30
    domain: sensor
    below: 20
  - type: is_battery_level
    condition: device
    device_id: 9fasdfasdfasdf34
    entity_id: 97asdfasdfasdf4d
    domain: sensor
    below: 20
  - type: is_battery_level
    condition: device
    device_id: f0asdfasdfasdf04
    entity_id: 7basdfasdfasdf0e
    domain: sensor
    below: 20
actions:
  - action: tts.cloud_say
    metadata: {}
    data:
      cache: false
      entity_id: media_player.home_assistant_voice_12a345_media_player
      message: The door sensor {{ trigger.entity_id }} battery is below 20 percent.
mode: single

First you have the right idea but

  1. avoid device triggers they do not survive device replacement, instead look for state or numeric state triggers that do the same thing.

Then if your device gets replaced all you need is give the new entity old name and your automation keeps going.

Second no. It will say ’ the door sensor is’ [entity_id] because thats what trigger.entity_id is. You probably want the ‘friendly_name’ attribute of entity_id instead - use select_attr

2 Likes

Of course you can! Go to the developer tools and set the battery state to 19%

That will trigger your automation if it’s set up correctly. Don’t worry about your actual battery state - it’ll bounce back to the correct value when your device sends another report

1 Like

Thanks for the tip, I’ve changed the conditional triggers to reference the numeric state as you suggested. I’m not familiar with select_attr, can you tell me how to use it here or show an example? Thanks.

alias: Door sensor (any) below 20%
description: ""
triggers:
  - trigger: sun
    event: sunset
    offset: "00:05:00"
  - trigger: time
    at: "12:00:00"
conditions:
  - condition: numeric_state
    entity_id: sensor.patio_door_sensor_battery
    below: 20
  - condition: numeric_state
    entity_id: sensor.mudroom_door_sensor_battery
    below: 20
  - condition: numeric_state
    entity_id: sensor.entryway_door_sensor_battery
    below: 20
  - condition: numeric_state
    entity_id: sensor.basement_storage_door_battery
    below: 20
actions:
  - action: tts.cloud_say
    metadata: {}
    data:
      cache: false
      entity_id: media_player.home_assistant_voice_09b642_media_player
      message: >-
        The door sensor {{ condition.entity_id.friendly_name }} battery is below
        20 percent.
mode: single

Sorry state_attr but… Either way…
You had the trigger entity_id there before you need that. But then read this:

See how state_attr pulls attribute x of an entity. You have the entity but need it’s friendly_name. State_attr does that.

1 Like

There are several issues here which might not have been immediately obvious to you.

Your initial automation had the problem that the trigger.entity_id is always going to be “sun.” The trigger variable references the trigger, not the conditions.

Seems you caught that in the second version you posted, but there is no such thing as condition.entity_id, unfortunately. If you want to trigger at a given time of day and use conditions, you will need some logic in your automation to identify which of the sensors is low, if any, even if that seems redundant with the conditions.

Finally, be aware that the conditions as written must ALL be met for the automation to fire–that is, every single sensor battery to be below 20. That’s because, by default, the list of conditions is evaluated as a logical AND. If you meant for the automation to run if any one of the sensors is low, you need to explicitly specify an OR:

conditions:
  - condition: or
    conditions:
    - condition: numeric_state
      entity_id: sensor.patio_door_sensor_battery
      below: 20
    - condition: numeric_state
      entity_id: sensor.mudroom_door_sensor_battery
      below: 20
    - condition: numeric_state
      entity_id: sensor.entryway_door_sensor_battery
      below: 20
    - condition: numeric_state
      entity_id: sensor.basement_storage_door_battery
      below: 20

Now, with all that out of the way, since you’re seemingly comfortable running this just once or twice per day, I suggest removing the conditions entirely and just doing an if, so that you only need the list of sensors in one place (as opposed to in the conditions: block and then in the actions: block).

For example,

  alias: Door sensor (any) below 20%
  description: ""
  triggers:
    - trigger: sun
      event: sunset
      offset: "00:05:00"
    - trigger: time
      at: "12:00:00"
  
  actions:
    - variables:
        all_sensors:
            "sensor.patio_door_sensor_battery" : "patio"
            "sensor.mudroom_door_sensor_battery" : "mudroom"
            "sensor.entryway_door_sensor_battery" : "entryway"
            "sensor.basement_storage_door_battery" : "basement storage"
        low_sensors:   >
          {% set NS = namespace(low_sensors=[]) %}
          {% for sensor in all_sensors.keys() %}
             {% if states(sensor)|int(0) <= 20 %}
                {% set NS.low_sensors = NS.low_sensors + [all_sensors[sensor]] %}
             {% endif %}
          {% endfor %}
          {{ NS.low_sensors }}
        phrase:    >
           {% if low_sensors|length > 1 %}
             {{low_sensors | length}} sensors are
           {% else %}
             sensor is
           {% endif %}
  
    - if: "{{low_sensors | length}}"
      then:
        - action: tts.cloud_say
          metadata: {}
          data:
            cache: false
            entity_id: media_player.home_assistant_voice_09b642_media_player
            message: >-
              The following {{phrase}} at battery level below 20 percent: {{low_sensors|join(', ')}}
  mode: single

EDIT: I corrected a couple of small errors, including an indent. I haven’t tested this code, so if it gives you problems, just post here and I can help you fix.

2 Likes

I’m getting close but I’m missing the final piece. I created the /custom_templates folder and added formatter.jinja and did the reload custom templates action as per the helpful link. Then I changed my YAML to:

  message: >-
    The door sensor  {% from 'formatter.jinja' import format_entity %} {{
    format_entity('trigger.condition.entity_id') }} battery is below 20 percent.

and now it says “The door sensor none unknown battery is below 20 percent.”

I’ve tried trigger.condition.entity_id and condition.entity_id and a few other combos but I can’t get it right. Any suggestions?

oh interesting because that does seem to answer a few unanswered questions I had. let me try this.

1 Like

I tried your YAML but I was getting undefined variable errors for all_sensors and low_sensors from the template tool: http://homeassistant.local:8123/developer-tools/template

I’m out of my league here so I tried AI for help and got this which does pass the template testing tool but doesn’t actually do anything, lol

alias: Door sensor (any) below 20%
description: ""
triggers:
  - trigger: sun
    event: sunset
    offset: "00:05:00"
  - trigger: time
    at: "12:00:00"
actions:
  - if: >
      {% set sensor_mapping = {
        'sensor.patio_door_sensor_battery': 'patio',
        'sensor.mudroom_door_sensor_battery': 'mudroom',
        'sensor.entryway_door_sensor_battery': 'entryway',
        'sensor.basement_storage_door_battery': 'basement storage'
      } %}
      {% set low_sensors = [] %}
      {% for sensor, name in sensor_mapping.items() %}
        {% if states(sensor)|int(0) <= 20 %}
          {% set low_sensors = low_sensors + [name] %}
        {% endif %}
      {% endfor %}
      {{ low_sensors | length > 0 }}
    then:
      - service: tts.cloud_say
        data:
          cache: false
          entity_id: media_player.home_assistant_voice_09b642_media_player
          message: >-
            {% set sensor_mapping = {
              'sensor.patio_door_sensor_battery': 'patio',
              'sensor.mudroom_door_sensor_battery': 'mudroom',
              'sensor.entryway_door_sensor_battery': 'entryway',
              'sensor.basement_storage_door_battery': 'basement storage'
            } %}
            {% set low_sensors = [] %}
            {% for sensor, name in sensor_mapping.items() %}
              {% if states(sensor)|int(0) <= 20 %}
                {% set low_sensors = low_sensors + [name] %}
              {% endif %}
            {% endfor %}
            The following {% if low_sensors | length > 1 %}{{ low_sensors | length }} sensors are{% else %}sensor is{% endif %} at battery level below 20 percent: {{ low_sensors | join(', ') }}
mode: single
1 Like

I should also note that my custom_templates/formatter.jinja file only has this in it:

{% macro format_entity(entity_id) %}
{{ state_attr(entity_id, 'friendly_name') }} - {{ states(entity_id) }}
{% endmacro %}

Is something else needed to make this work?

Let me try to put this into my system and see what’s up.

Yeah, this is typical AI c***. That is never going to work because it fails to account for Jinja’s variable scoping, among (probably) other problems.

1 Like

This code has correct syntax and works on my system. I explain below how you can test/tweak it using the Template Editor, and I rewrote the code a bit to make that easier.

- alias: Door sensor (any) below 20%
  description: ""
  triggers:
    - trigger: sun
      event: sunset
      offset: "00:05:00"
    - trigger: time
      at: "12:00:00"
  
  actions:
    - variables:
        low_sensors:    >
              {# a dictionary of sensor entity_ids to be monitored, identifying sensor locations #}
              {% 
                  set all_sensors = {
                    'sensor.patio_door_sensor_battery'      : 'patio',
                    'sensor.mudroom_door_sensor_battery'    : 'mudroom',
                    'sensor.entryway_door_sensor_battery'   : 'entryway',
                    'sensor.basement_storage_door_battery'  : 'basement storage'
                                    }
              %}

              {# build the list NS.low_sensors #}
              {% set NS = namespace(low_sensors=[]) %}
              {% for sensor in all_sensors.keys() %}
                {% if states(sensor)|int(0) <= 20 %}
                  {% set NS.low_sensors = NS.low_sensors + [all_sensors[sensor]] %}
                {% endif %}
              {% endfor %}          
              {% set low_sensors = NS.low_sensors %}

              {# print the list so the YAML binds it to the low_sensors variable #}
              {{ low_sensors }}
        phrase:         >
              {% set NS = namespace(phrase="sensor is") %}
              {% if low_sensors | length > 1 %}
                {% set NS.phrase = low_sensors|length|string + " sensors are" %}
              {% endif %}
              {% set phrase = NS.phrase %}

              {# print the string so the YAML binds it to the phrase variable #}
              {{ phrase }}
   
        message:        >
              The following {{phrase}} have battery level below 20 percent: {{low_sensors|join(', ')}}        

  
    # I'm surprised that the bool filter is required here.
    - if: "{{low_sensors | length | bool}}"
      then:
        - action: tts.cloud_say
          metadata: {}
          data:
            cache: false
            entity_id: media_player.home_assistant_voice_09b642_media_player
            message: >-
              {{ message }}
  mode: single

The following code is not the same as the YAML above; it’s a copy-and-paste of only the Jinja code, which I contrived with some quirks to make it possible to seamlessly paste together for purposes of using the Template Editor. The following should work in the Template Editor, and give you an idea of what the code is doing. But note that you do NOT want to paste this into your automation–use the code above for that.

              {# a dictionary of sensor entity_ids to be monitored, identifying sensor locations #}
              {% 
                  set all_sensors = {
                    'sensor.patio_door_sensor_battery'      : 'patio',
                    'sensor.mudroom_door_sensor_battery'    : 'mudroom',
                    'sensor.entryway_door_sensor_battery'   : 'entryway',
                    'sensor.basement_storage_door_battery'  : 'basement storage'
                                    }
              %}

              {# build the list NS.low_sensors #}
              {% set NS = namespace(low_sensors=[]) %}
              {% for sensor in all_sensors.keys() %}
                {% if states(sensor)|int(0) <= 20 %}
                  {% set NS.low_sensors = NS.low_sensors + [all_sensors[sensor]] %}
                {% endif %}
              {% endfor %}          
              {% set low_sensors = NS.low_sensors %}
              {{ low_sensors }}

              {# build grammatically correct phrasing for the message #}
              {% set NS = namespace(phrase="sensor is") %}
              {% if low_sensors | length > 1 %}
                {% set NS.phrase = low_sensors|length|string + " sensors are" %}
              {% endif %}
              {% set phrase = NS.phrase %}
              {{ phrase }}
              
              {# print the message #}
 The following {{phrase}} at battery level below 20 percent: {{low_sensors|join(', ')}} 

Note that the Template Editor will output something nonsensical if there are NO sensors that are at or below 20. That’s okay, because the YAML logic has an if statement that will prevent the nonsensical message from ever hitting the airwaves.

Also, the Template editor is going to also output a whole bunch of whitespace. You can ignore that–it won’t get in the way of the YAML code working.

May I suggest that if you have trouble with this, you DM me–if there are any edits that are needed to the code, I’ll merge them into this post.

As to your other question, by the way:

That’s a macro, so it only does something if you call it explicitly.

The code that I wrote doesn’t use this macro. But if you wanted to see how it works, and/or add it to your code, try this in the Template Editor:

{% from 'formatter.jinja' import format_entity %}
{{ format_entity('sensor.patio_door_sensor_battery') }}

EDIT: YAML code edited to add a bool filter on the if: condition. I’m somewhat surprised that it’s needed, but… it… is.

2 Likes