Having trouble with script

I’m writing what I thought was a simple script to put together a voice announcement for low battery warnings, but it seems like if I reference the item in this way it doesn’t work:

low_battery_announcement:
  alias: Low Battery Announcement
  icon: "mdi:account-voice"
  sequence:
    - service: shell_command.speak
      data:
        text: >
          {# INTRO #}
          {%- set message = "" -%}
          
          {%- for zwave in states.zwave if zwave.attributes.battery_level is defined and zwave.attributes.battery_level | int < 75 -%}
            {%- set message = message + zwave.attributes.friendly_name + ", " -%}
          {%- endfor -%}
          
          {{ message }}
          Done.

It has something to do with using the zwave.attributes.friendly_name when trying to concat the “message” variable. When used as a literal it will say the phrase (i.e., {{ zwave.attributes.friendly_name }}) but as it is now it never concats the string and just says “Done.” (Done was added just as a test to make sure it got to the bottom).

I believe I have to call it with a function, i.e., states(‘entity_name’) type of call, but I’m not sure how I can wrap that value so that it concats it to the string.

The idea is that if there are no low batteries then the “message” will be empty and nothing is said via an if statement, but until I can get message to concat I cannot test for that yet.

try putting the “{{ message }}” inside the for loop:

{%- for zwave in states.zwave if zwave.attributes.battery_level is defined and zwave.attributes.battery_level | int < 75 -%}
  {%- set message = message ~ zwave.attributes.friendly_name ~ ", " -%}
    {{message}}
{%- endfor -%}

I tested it and it worked for me like that.

I tried that before and it speaks it as expected, but the reason I’m stuffing it into a variable is so I can test that variable after the loop to determine if anything should be said or not. If it’s not possible to concat the variable then I can just run the loop twice, one to flag a true/false and one to say the message if the condition is true.

After playing around, it looks like the variable is local to the loop rather than using the defined variable above it, so it is setting a new “message” on each iteration instead of concatenating the variable I need to be concatenated. This is why this hasn’t worked.

I had hoped that setting message to be global ( {%- global message -%} ) would do the trick but it doesn’t work that way in Jinja apparently.

I think I have my answer, what I’m doing is not supported inside of a block, so I have to set a namespace to be able to do this.

In case anyone else comes across this, here is the Jinja docs on this.

What did the trick is this:

low_battery_announcement:
  alias: Low Battery Announcement
  icon: "mdi:account-voice"
  sequence:
    - service: shell_command.speak
      data:
        text: >
          {# INTRO #}
          {% set ns = namespace(message="") %}
          
          {%- for zwave in states.zwave if zwave.attributes.battery_level is defined and zwave.attributes.battery_level | int < 75 -%}
            {% set ns.message = ns.message + zwave.attributes.friendly_name + ", " %}
          {%- endfor -%}          

          {{ ns.message }}
          Done. 

that was going to be my next suggestion. :wink:

1 Like

If anyone stumbles upon this and is curious how the entire thing is put together:

low_battery_announcement:
  alias: Low Battery Announcement
  icon: "mdi:account-voice"
  sequence:
    - service: shell_command.speak
      data:
        text: >
          {# Check all Z-Wave batteries plus non Z-Wave devices for battery warnings #}
          {% set ns = namespace(message="") %}
          
          {%- for zwave in states.zwave if zwave.attributes.battery_level is defined and zwave.attributes.battery_level | int < 30 -%}
            {% set ns.message = ns.message + zwave.attributes.friendly_name + ", " %}
          {%- endfor -%}     
          
          {% if is_state('binary_sensor.sitting_area_motion_sensor_battery', 'on') %}
            {% set ns.message = ns.message + "Sitting Area Motion Sensor, " %}
          {% endif %} 
          
          {% if is_state('binary_sensor.refrigerator_low_battery', 'on') %}
            {% set ns.message = ns.message + "Refrigerator Temperature Sensor, " %}
          {% endif %}
          
          {% if is_state('binary_sensor.deep_freeze_low_battery', 'on') %}
            {% set ns.message = ns.message + "Deep Freezer Temperature Sensor, " %}
          {% endif %}

          {% if ns.message == "" %}
          {% else %}
            The following devices have low batteries and will need to be replaced soon:
            {{ ns.message }}
            please review the battery dashboard in Home Assistant to view these devices.
          {% endif %}

I’m sure there are some improvements to be made (I left a blank message condition in place in case I decide to throw in “All batteries are fine” or something), but this works pretty well for what I wanted.