Need help with repeat for_each template

Here’s my repeat:

repeat:
  sequence:
    - service: google.create_event
      data:
        summary: "{{ trigger.entity_id[7:][:-9] | capitalize }} - {{ repeat.item.title }}"
        start_date_time: "{{ repeat.item.start }}"
        end_date_time: "{{ repeat.item.stop }}"
      target:
        entity_id: calendar.tv
  for_each: "{{ state_attr(trigger.entity_id,'list') }}" 

I guess I don’t have a list of items. This is what {{ state_attr(trigger.entity_id,‘list’) }} looks like:

Automation error:

  • TV to Calendar: Repeat ‘for_each’ must be a list of items in TV to Calendar, got: {“title”:“CBS Overnight News”, “start”:“2023-11-16 00:37:00”, “stop”:“2023-11-16 04:30:00”} , {“title”:“CBS Morning News”, “start”:“2023-11-16 04:30:00”, “stop”:“2023-11-16 05:00:00”} , {“title”:“KXLH Montana This Morning”, “start”:“2023-11-16 05:00:00”, “stop”:“2023-11-16 07:00:00”} , {“title”:“CBS Mornings”, “start”:“2023-11-16 07:00:00”, “stop”:“2023-11-16 09:00:00”} , {“title”:“The Price Is Right”, “start”:“2023-11-16 09:00:00”, “stop”:“2023-11-16 10:00:00”} , {“title”:“Let’s Make a Deal”, “start”:“2023-11-16 10:00:00”, “stop”:“2023-11-16 11:00:00”} , {“title”:“The Young and the Restless”, “start”:“2023-11-16 11:00:00”, “stop”:“2023-11-16 12:00:00”} …

Hoping to get some help from the wonderful Home Assistant community!

In the Template editor tool, what is returned when you test the following?

{{ state_attr('sensor.cbs_programs', 'list')[0] }}

The first item is returned:

{
“title”: “CBS Overnight News”,
“start”: “2023-11-16 00:37:00”,
“stop”: “2023-11-16 04:30:00”
}

Everything looks good from what I’ve read so far. However, this will only be available w/ specific triggers and could be the source of your issues:

Can you post your automation triggers?

Here’s the whole yaml for this automation:

alias: TV to Calendar
description: ""
trigger:
  - platform: state
    entity_id:
      - sensor.nbc_programs
      - sensor.abc_programs
      - sensor.cbs_programs
      - sensor.fox_programs
    attribute: list
condition: []
action:
  - repeat:
      sequence:
        - service: google.create_event
          data:
            summary: >-
              {{ trigger.entity_id[7:][:-9] | capitalize }} - {{
              repeat.item.title }}
            start_date_time: "{{ repeat.item.start }}"
            end_date_time: "{{ repeat.item.stop }}"
          target:
            entity_id: calendar.tv
      for_each: "{{ state_attr(trigger.entity_id,'list') }}"
mode: queued
max: 4

Ok, based on that and what I see in the template editor, try using this instead.

alias: TV to Calendar
description: ""
trigger:
  - platform: state
    entity_id:
      - sensor.nbc_programs
      - sensor.abc_programs
      - sensor.cbs_programs
      - sensor.fox_programs
    attribute: list
variables:
  programs: >
    {{ trigger.to_state.attributes.get('list', []) }}
  channel: >
    {{ trigger.to_state.object_id.split('_')[0] }}
condition: []
action:
  - repeat:
      sequence:
        - service: google.create_event
          data:
            summary: "{{ channel }} - {{ repeat.item.title }}"
            start_date_time: "{{ repeat.item.start }}"
            end_date_time: "{{ repeat.item.stop }}"
          target:
            entity_id: calendar.tv
      for_each: "{{ programs }}"
mode: queued
max: 4

This will help you debug what you’re getting from your trigger in the changed variables section of your automation’s trace.

I forgot about using trigger.to_state but it seems to make no difference and in changed variables I see the following which is exactly what I thought it should be. What’s wrong with it? I get identical error: Repeat ‘for_each’ must be a list of items

programs:
  - title: CBS Mornings
    start: '2023-11-16 07:00:00'
    stop: '2023-11-16 09:00:00'
  - title: The Price Is Right
    start: '2023-11-16 09:00:00'
    stop: '2023-11-16 10:00:00'
  - title: Let's Make a Deal
    start: '2023-11-16 10:00:00'
    stop: '2023-11-16 11:00:00'
  - title: The Young and the Restless
    start: '2023-11-16 11:00:00'
    stop: '2023-11-16 12:00:00'
  - ...

Are you using the UI to make this automation? Might be a UI bug that you can ignore.

I don’t think that’s it because the automation aborts at the point of that error.

I fixed my problem. This problem could not have been solved by anyone else because there was missing information from the beginning. I’ll try to show what went wrong.

First I create sensors like the following (and if anyone can help me simplify any of it that would be great):

template:
    sensor:
      - name: CBS Programs
        state: "{{ state_attr('sensor.zap2it','programme') | selectattr('@channel', 'eq', 'I9.1.70140.zap2it.com') | list | count }}"
        attributes:
          list: >-
            {% for i in range(state_attr('sensor.zap2it','programme') | selectattr('@channel', 'eq', 'I9.1.70140.zap2it.com') | list | count) %}
            {"title":"{{ (state_attr('sensor.zap2it','programme') | selectattr('@channel', 'eq', 'I9.1.70140.zap2it.com') | map(attribute='title') | list)[i]['#text'] }}",
            "start":"{{ (as_timestamp(strptime((state_attr('sensor.zap2it','programme') | selectattr('@channel', 'eq', 'I9.1.70140.zap2it.com') | map(attribute='@start') | list)[i].split(' ')[0],"%Y%m%d%H%M%S", 0)) | int + states('sensor.utc_offset_seconds') | int) | timestamp_custom("%Y-%m-%d %H:%M:%S")}}",
            "stop":"{{ (as_timestamp(strptime((state_attr('sensor.zap2it','programme') | selectattr('@channel', 'eq', 'I9.1.70140.zap2it.com') | map(attribute='@stop') | list)[i].split(' ')[0],"%Y%m%d%H%M%S", 0)) | int + states('sensor.utc_offset_seconds') | int) | timestamp_custom("%Y-%m-%d %H:%M:%S") }}"}
            {% if not loop.last %},{% endif %}{% endfor %}

Home Assistant’s template editor shows me that this sensor’s list attribute has Result type: list so I feel confident I can use this list in a repeat for_each.

My automation would not work with the error telling me that I was not giving a list of items.

The solution ended up being to add square brackets on the above template sensor like this:

template:
    sensor:
      - name: CBS Programs
        state: "{{ state_attr('sensor.zap2it','programme') | selectattr('@channel', 'eq', 'I9.1.70140.zap2it.com') | list | count }}"
        attributes:
          list: >-
            [{% for i in range(state_attr('sensor.zap2it','programme') | selectattr('@channel', 'eq', 'I9.1.70140.zap2it.com') | list | count) %}
            {"title":"{{ (state_attr('sensor.zap2it','programme') | selectattr('@channel', 'eq', 'I9.1.70140.zap2it.com') | map(attribute='title') | list)[i]['#text'] }}",
            "start":"{{ (as_timestamp(strptime((state_attr('sensor.zap2it','programme') | selectattr('@channel', 'eq', 'I9.1.70140.zap2it.com') | map(attribute='@start') | list)[i].split(' ')[0],"%Y%m%d%H%M%S", 0)) | int + states('sensor.utc_offset_seconds') | int) | timestamp_custom("%Y-%m-%d %H:%M:%S")}}",
            "stop":"{{ (as_timestamp(strptime((state_attr('sensor.zap2it','programme') | selectattr('@channel', 'eq', 'I9.1.70140.zap2it.com') | map(attribute='@stop') | list)[i].split(' ')[0],"%Y%m%d%H%M%S", 0)) | int + states('sensor.utc_offset_seconds') | int) | timestamp_custom("%Y-%m-%d %H:%M:%S") }}"}
            {% if not loop.last %},{% endif %}{% endfor %}]

Now my sensor’s list attribute is truly a list of items and the automation runs without error. Sorry to @petro for wasting time not giving complete information, and a big thank you as well for pointing me towards looking elsewhere.

I’m kind of surprised that is working… Normally, I would use a namespace to extract a real list out of the for loop.

I did try doing {% set ns=namespace(cbs=“state_attr(‘sensor.zap2it’,‘programme’) | selectattr(‘@channel’, ‘eq’, ‘I9.1.70140.zap2it.com’)”) %} and using ns.cbs throughout my for loop but it doesn’t work so I got stuck with this extra long template. I would like to understand why it doesn’t work. My only guess is that maybe state_attr(‘sensor.zap2it’,‘programme’) | selectattr(‘@channel’, ‘eq’, ‘I9.1.70140.zap2it.com’) can’t evaluate to anything without a filter?

I think you’ve made your sensors a lot more complicated than they need to be… Can you post the input and desired output formats and details for “@start” and “@stop”?

This is the general structure I would use:

template:
    sensor:
      - name: CBS Programs
        state: "{{ state_attr('sensor.zap2it', 'programme') | selectattr('@channel', 'eq', 'I9.1.70140.zap2it.com') | list | count }}"
        attributes:
          list: >-
            {% set ns = namespace(show_sched= []) %}
            {% for show in state_attr('sensor.zap2it', 'programme') | selectattr('@channel', 'eq', 'I9.1.70140.zap2it.com') | list %}
            {% set title = show.title.text %}
            {% set start = #TBD# %}
            {% set stop = #TBD# %}
            {% set ns.show_sched = ns.show_sched + [ {'title': title, 'start': start, 'stop': stop} ] %}
            {% endfor %}
            {{ ns.show_sched }}

EDIT: Fixed expression delimiters

2 Likes
{{ ns.show_sched }}

:wink:

1 Like

Couple things, if you’re iterating the generator, there’s no need for |list on the for loop. Removing that will allow you to only iterate once, but it’s more efficient (i.e. faster, less memory).

2nd, it seems that the keys have special characters, they should be included sets.

3rd, we can gather what the format is from @start & @stop by just using what OP has in their code. We know the result is in UTC, so we just need to convert it to a datetime, then to a local datetime. Lastly, convert it to an isoformatted string so that it resolves properly in other areas. We can keep it a datetime if we wanted but typically that breaks the template resolver because it can’t handle complex objects. Because we use as_datetime and as_local, there’s no need to add or subtract the utc offset because this is done for us with those filters.

            {% set ns = namespace(items=[]) %}
            {% for show in state_attr('sensor.zap2it', 'programme') | selectattr('@channel', 'eq', 'I9.1.70140.zap2it.com') %}
              {% set title = show.title['#text'] %}
              {% set start = show['@start'].split(' ')[0] | as_datetime | as_local %}
              {% set stop = show['@stop'].split(' ')[0] | as_datetime | as_local  %}
              {% set ns.items = ns.items + [ {'title': title, 'start': start.isoformat(), 'stop': stop.isoformat()} ] %}
            {% endfor %}
            {{ ns.items }}
2 Likes

Thanks so much @petro and @Didgeridrew for help with simplifying and making this more efficient! Now I know how to create a list and append items. Will never face issues over forgetting to add brackets again! Looks so clean!

And thanks for redoing my dates. I give up on learning how to deal with dates. That’s a headache. I usually just go to the template editor until I find something that works. Ugh!

If you want to learn dates, avoid as_timestamp, just stick with as_datetime, as_local, as_timedelta, and timedelta.

It makes working with dates easier. Still can be confusing, but you don’t need to spend as much time messing w/ utc to local offsets.

1 Like

Date headache continues. This doesn’t work and I swear I do believe I’ve tried every date manipulation possible!

{% set start = show['@start'].split(' ')[0] | as_datetime | as_local %}

input: 20231117020000
desired output: 2023-11-16 19:00

Ok, so you need to do this then

{% set start =  now().strptime(show['@start'].split(' ')[0], '%Y%m%d%H%M%S') | as_local %}
{% set stop = now().strptime(show['@stop'].split(' ')[0], '%Y%m%d%H%M%S') | as_local  %}

And if that value is UTC and not local… Then you need to do this

{% set start =  now().strptime(show['@start'].split(' ')[0] ~ 'Z', '%Y%m%d%H%M%S%z') | as_local %}
{% set stop = now().strptime(show['@stop'].split(' ')[0] ~ 'Z', '%Y%m%d%H%M%S%z') | as_local  %}

I’d wager the value is utc if you need it to be 19:00:00, assuming you’re in the Mountain Time time zone.

Wow thank you. This is what I tried without the now(). ahead of it. Like I said I give up. It’s too confusing! Is there a “buy me a coffee” link on this page?