Looping to generate MQTT.publish commands to a group of entities?

Background:

New to homeassistant, and just learning how it works (as well as being somewhat limited in my programming knowledge) I am adding a number of TUYA radiator valves, controlled by HA through zigbee2mqtt. While I am slowly switching over, eventually I should have a total of 15 valves scattered around the house. I am able to use MQTT.publish to push settings to individual valves.

alias: Set Away Temperature
sequence:
  - service: mqtt.publish
    data:
      topic: zigbee2mqtt/Kitchen Playroom Valve/set
      payload: '{"away_preset_temperature": 10}'
mode: single

However, I would love to be able to send settings to all 14 valves simultaneously, rather than having to create 14 separate scripts/automations. It would seem that the groups function of zigbee2mqtt would be helpful, but it does not seem to support these radiator valves currently, so I am trying to find another workaround

Question:

Is there some way to create a script or automation that can cycle through the list of valves, generating an MQTT.publish call for each of them? I have been playing with templates to try and generate a list of the MQTT.publish topics, but not having a ton of luck.

I know the scripting language does support looping, but having trouble figuring out how to directly apply it to my use case. The template below seems to generate the sequence of calls I would need, but it doesn’t seem that I can do this in a script (at least it won’t save or execute correctly).

{% for state in states.climate %}
- service: mqtt.publish
  data:
    topic: zigbee2mqtt/{{ state.name }}/set
    payload: '{"away_preset_temperature": 12}'
{% endfor %}

Am I even possibly on the right track here? Or am I heading off into the wrong direction?

You can only template the value part of a key: value pair. You have both keys and values in your template.

I believe the usual solution to this is to use a python script or use AppDaemon.

Here’s an example of a python script.

Though the script syntax has been updated with quite a few repeat functions. There may be a way to use them:

Try this:

alias: Set Away Temperature
mode: single
sequence:
- variables:
    thermostats: "{{ states.climate | map(attribute='name') | list }}"
- repeat:
    count: '{{ thermostats | count }}'
    sequence:
    - service: mqtt.publish
      data:
        topic: zigbee2mqtt/{{ thermostats[repeat.index-1] }}/set
        payload: '{"away_preset_temperature": 12}'
4 Likes

Taras,

That worked! Thank you. Plus, it demonstrates a lot of useful strategies for me to learn. I really like the loop using count to simulate a for loop, and referring to the thermostats array is also new to me.

I clearly have a lot to learn about the home assistant scripting language…

Thanks again!

Ted

1 Like

To clarify, repeat is the way you iterate a group of actions when creating a YAML-based automation. In the documentation you’ll see there are several ways to Repeat a Group of Actions.

The way you were trying is using Jinja2 statements which are only applicable within a template.

  something: >
     {% set ns = namespace(z = 0) %}
     {% for i in range(0, 11) %}
       {% set ns.z = i %}
     {% endfor %}
     {{ ns.z }}

The result of this contrived example is 10.

Hi Taras,

Thanks for this solution, this works fine.
I just forgot to verify that the entity has the same name as the device zig2mqtt friendly name. :smirk:

Hiya,

Now I was trying to just get the names of valves in a specific defined group, called “Upstairs_Valves”

The template below seems to generate the correct list of valves that I want:

{{ states.climate | selectattr('entity_id', 'in', state_attr('group.Upstairs_Valves', 'entity_id')) | list | map(attribute='name') | list }}

However, whenever I try to paste it into my script , based on the example above, it won’t save. Is there something different about this template that doesn’t allow it to create the variable array?

This is what I would like the script to look like, but it won’t save correct, it defaults the variable definition back to null.

alias: WIP Set Default Workday Schedule on Upstairs Valves
sequence:
  - variables:
      thermostats: '{{ states.climate | selectattr('entity_id', 'in', state_attr('group.Upstairs_Valves', 'entity_id')) | list | map(attribute='name') | list }}'
  - repeat:
      count: '{{ thermostats | count }}'
      sequence:
        - service: mqtt.publish
          data:
            topic: 'zigbee2mqtt/{{ thermostats[repeat.index-1] }}/set/schedule'
            payload: >-
              {"workdays":
              [{"hour":6,"minute":0,"temperature":19},{"hour":8,"minute":0,"temperature":19},{"hour":11,"minute":30,"temperature":19},{"hour":12,"minute":30,"temperature":19},{"hour":17,"minute":30,"temperature":19},{"hour":22,"minute":0,"temperature":19}]
              }
mode: single

Any idea what I am doing wrong?

Thanks for any guidance,

Ted

That template is needlessly complicated and it contains a syntax error (the outer and inner quotes are the same; they must be different).

If you want the names of all members of a group, it can be done like this:

  - variables:
      thermostats: "{{ expand('group.upstairs_valves') | map(attribute='name') | list }}"
1 Like

Thank you again for your time / guidance! Your version worked perfectly.

Ted

1 Like

Back again with another question. I am trying to loop through a group of valves to see which ones are overshooting the heating goal, that is to say that they are at the target temperature or above, and have a valve that is very open (>40). I have a version that is working fine for a single valve, using a templated test, but when I try to implement it using the looping and variable that Taras has been good enough to show me, it isn’t working. It appears that the “choose” template isn’t evaluating right, though it works fine with individual valve names. Am I not allowed to use the variable declared in the script in this way?

The goal is to have the valves in overshoot get interrupted, I am trying to call a second script that will actually do the interrupt, which works off the the “friendly name” attribute of the valve.

alias: WIP Overshoot Monitor
sequence:
  - variables:
      entityids: >-
        {{ expand('group.downstairs_valves') | map(attribute='entity_id') | list
        }}
  - repeat:
      count: '{{ entityids | count }}'
      sequence:
        - choose:
            - conditions:
                - condition: template
                  value_template: >-
                    {{
                    state_attr('{{entityids[repeat.index-1]}}','current_temperature')|int
                    >=  state_attr('{{entityids[repeat.index-1]}}',
                    'temperature')|int  }}
                - condition: template
                  value_template: >-
                    {{ state_attr('{{entityids[repeat.index-1]}}','position') |
                    int > 40 }}
              sequence:
                - service: script.wip_overshoot_valve_interrupt
                  data:
                    service: script.wip_overshoot_valve_interrupt
                    data_template:
                      valvetointerrupt: >-
                        {{ state_attr('{{entityids[repeat.index-1]}}',
                        'friendly_name') }}
          default: []
mode: singl

Any help appreciated.

It contains syntax errors. You can’t nest templates but that’s what all of your templates are doing.

Whenever you find yourself creating templates with an outer set of {{ }} and an inner set of {{ }} then you have created nested templates and that’s invalid.

In your posted example, wherever you have this:

'{{entityids[repeat.index-1]}}'

replace it with this:

entityids[repeat.index-1]

Gah! Thank you Taras. I guess I thought you had to refer to variables with double curlies {{ }}.

I am still having issues with the templates not evaluating correct. Below is another version where I removed the extra curlies, but have added some debugging code so I can see what the script is doing. However, when I run it, it gives me an error:

Failed to call service script…template value is none for dictionary value @ ‘data’ message.

This is making me think that the {{valvetointerrupt}} isn’t evaluating correctly, and remaining “none”.

alias: WIP Overshoot Monitor with debug2
sequence:
  - variables:
      entityids: '{{ expand(''group.all_valves'') | map(attribute=''entity_id'') | list }}'
  - repeat:
      count: '{{ entityids | count }}'
      sequence:
        - variables:
            valvetointerrupt: '{{state_attr(''entityids[repeat.index-1]'',''friendly_name'')}}'
        - service: notify.persistent_notification
          data_template:
            message: '{{valvetointerrupt}}'
            title: We entered the loop
        - choose:
            - conditions:
                - condition: template
                  value_template: >-
                    {{
                    state_attr('entityids[repeat.index-1]','current_temperature')|int
                    >=  state_attr('entityids[repeat.index-1]',
                    'temperature')|int  }}
                - condition: template
                  value_template: >-
                    {{ state_attr('entityids[repeat.index-1]','position') | int
                    > 40 }}
              sequence:
                - service: notify.persistent_notification
                  data:
                    message: We found a valve in overshoot.
                    title: From your overshoot script
                - service: mqtt.publish
                  data:
                    topic: 'zigbee2mqtt/{{valvetointerrupt}}/set'
                    payload: '{"force": "close"}'
                - delay:
                    hours: 0
                    minutes: 0
                    seconds: 30
                    milliseconds: 0
                - service: mqtt.publish
                  data:
                    topic: 'zigbee2mqtt/{{valvetointerrupt}}/set'
                    payload: '{"force": "normal"}'
          default: []
mode: single

I’m also never seeing my notification from inside my choose statement, despite triggering the script when I know that a valve is overshooting. I can generate notifications from inside the repeat just fine (so I know it’s looping through), but I don’t think the state_attr templates are working.

I really appreciate all of your help - I realize that watching me flail around may be rather painful…

That’s correct and it’s because you wrapped entityids[repeat.index-1] in quotes.

When you do this:

'entityids[repeat.index-1]'

or this:

''entityids[repeat.index-1]''

or this:

"entityids[repeat.index-1]"

everything between the quotation marks is understood to be a literal string. In other words, if there’s a variable in there, its meaning is lost; it’s not interpreted as anything more than a literal string.

If you remove those quotation marks:

entityids[repeat.index-1]

that will not be interpreted as a literal string. Now it will be handled as a directive. In this case it’s understood to be the name of a list variable (entityids) and a list index variable (repeat.index).

If you refer to my original example, you’ll see that thermostats[repeat.index-1] is never wrapped in quotes.

I think this is starting to make sense - it’s been a long time since I did computer science in college. We use quotes outside templates when we want to make them into strings to be stored in variable, or passed to services. We are using quotes inside templates only when we want them to be string values like ‘current temperature’ so that they can be looked up by function in a template.

Thank you again for your patience…I guess the danger of a project like homeassistant is that it draws in people like me who don’t code for a living, and thus while we read the documentation, we don’t understand it on a deeper level. Thank you for taking the time to explain this.

1 Like