Template Sensor Configuration for Calendar Events

:wave: Hi

I’m working on tracking waste collection events from my calendar. My goal is to filter events by waste type (e.g., Food Waste, Non-Recyclable Waste, etc.) and use these filtered results to populate the template sensors for each waste type. My below configuration works and populates everything correctly, but I feel like there has to be a more efficient and cleaner way to do this.

Ideally, I’d like to perform the selectattr operation just once for each waste type and reuse the result, minimizing redundant processing. Is there a way to do this?

Here’s an overview of my functional but bulky setup:

template:
  - trigger:
      - platform: time_pattern
        seconds: /10
    action:
      - service: calendar.list_events
        target:
          entity_id: calendar.waste_collection
        data:
          duration:
            days: 14
        response_variable: scheduled_waste_events
    sensor:
      - name: Food Waste Collection Events
        state: |
          {{ scheduled_waste_events.events | selectattr('summary', 'search', 'Food', true) | list | count() }}
        attributes:
          scheduled_events: |
            {{ scheduled_waste_events.events | selectattr('summary', 'search', 'Food', true) | list }}
          next_collection_date: |
            {{ (scheduled_waste_events.events | selectattr('summary', 'search', 'Food', true) | list | first).start }}
          countdown: |
            {{ int((as_timestamp((scheduled_waste_events.events | selectattr('summary', 'search', 'Food', true) | list | first).start) - as_timestamp(today_at('00:00')))/86400)}}

      - name: Non-recyclable Waste Collection Events
        state: |
          {{ scheduled_waste_events.events | selectattr('summary', 'search', 'Non-Recyclable', true) | list | count() }}
        attributes:
          scheduled_events: |
            {{ scheduled_waste_events.events | selectattr('summary', 'search', 'Non-Recyclable', true) | list }}
          next_collection_date: |
            {{ (scheduled_waste_events.events | selectattr('summary', 'search', 'Non-Recyclable', true) | list | first).start }}
          countdown: |
            {{ int((as_timestamp((scheduled_waste_events.events | selectattr('summary', 'search', 'Non-Recyclable', true) | list | first).start) - as_timestamp(today_at('00:00')))/86400)}}

      - name: Paper Cardboard Waste Collection Events
        state: |
          {{ scheduled_waste_events.events | selectattr('summary', 'search', 'Paper', true) | list | count() }}
        attributes:
          scheduled_events: |
            {{ scheduled_waste_events.events | selectattr('summary', 'search', 'Paper', true) | list }}
          next_collection_date: |
            {{ (scheduled_waste_events.events | selectattr('summary', 'search', 'Paper', true) | list | first).start }}
          countdown: |
            {{ int((as_timestamp((scheduled_waste_events.events | selectattr('summary', 'search', 'Paper', true) | list | first).start) - as_timestamp(today_at('00:00')))/86400)}}

# And so on for each waste type...

Thanks.

Be advised that the service call calendar.list_events has been deprecated and replaced by calendar.get_events. The deprecated service call will stop working in version 2024.6.0.

The new service call is even “bulkier” than the new one because it’s designed to handle multiple calendars in the response_variable. For example, this:

scheduled_waste_events.events

changes to this:

scheduled_waste_events['calendar.waste_collection']['events']

As for your original question, I can’t think of a way to reduce the size of each template or minimize duplication. A macro template wouldn’t substantially reduce the overall template size for each attribute’s template and YAML Anchors and Aliases aren’t useful here because each template is slightly different (varying by the type of waste material).

1 Like

Thanks very much @123 . I’ve actually only been able to make this work by iterating on a snippet you’ve shared a while back. Otherwise I’d be lost.

I’ve now changed to calendar.get_events and it has indeed made the templates bulkier, but at least I can rely on it for the foreseeble future.

1 Like

You can define variables in the action block:

template:
  - trigger:
      - platform: time_pattern
        seconds: /10
    action:
      - service: calendar.list_events
        target:
          entity_id: calendar.waste_collection
        data:
          duration:
            days: 14
        response_variable: waste_events
      - variables:
          all_events: "{{ waste_events['calendar.waste_collection'].events }}"
          paper_ev: "{{ all_events | selectattr('summary', 'search', 'Paper', true) }}"
          food_ev: "{{ all_events | selectattr('summary', 'search', 'Food', true) }}"
          non_recycle_ev: "{{ all_events | selectattr('summary', 'search', 'Non-Recyclable', true) }}"
    sensor:
      - name: Food Waste Collection Events
        state: |
          {{ food_ev | list | count() }}
        attributes:
          scheduled_events: |
            {{ food_ev | list }}
          next_collection_date: |
            {{ (food_ev | first).start }}
          countdown: |
            {{ int((as_timestamp((food_ev | first).start) - as_timestamp(today_at('00:00')))/86400)}}

# And so on for each waste type...
2 Likes

I suggest you mark Didgeridrew’s post with the Solution tag because I believe that’s currently the best you can do to reduce overall template size.

1 Like

Thanks @Didgeridrew ! This works great. Pasting below the template I ended up using, if anyone is interested.

template:
  - trigger:
      - platform: time_pattern
        seconds: /10
    action:
      - service: calendar.get_events
        target:
          entity_id: calendar.waste_collection
        data:
          duration:
            days: 14
        response_variable: scheduled_waste_events
      - variables:
          all_events: "{{ scheduled_waste_events['calendar.waste_collection'].events  }}"
          food_ev: "{{ all_events | selectattr('summary', 'search', 'Food', true) | list }}"
          non_recycle_ev: "{{ all_events | selectattr('summary', 'search', 'Non-Recyclable', true) | list }}"
          paper_ev: "{{ all_events | selectattr('summary', 'search', 'Paper', true) | list }}"
          mixed_ev: "{{ all_events | selectattr('summary', 'search', 'Mixed', true) | list }}"
          garden_ev: "{{ all_events | selectattr('summary', 'search', 'Garden', true) | list }}"
    sensor:
      - name: Food Waste Collection Events
        state: |
          {{ food_ev | count() }}
        attributes:
          scheduled_events: |
            {{ food_ev }}
          next_collection_date: |
            {{ (food_ev | first).start }}
          countdown: |
            {{ int((as_timestamp((food_ev | first).start) - as_timestamp(today_at('00:00')))/86400)}}

      - name: Non-recyclable Waste Collection Events
        state: |
          {{ non_recycle_ev | count() }}
        attributes:
          scheduled_events: |
            {{ non_recycle_ev }}
          next_collection_date: |
            {{ (non_recycle_ev | first).start }}
          countdown: |
            {{ int((as_timestamp((non_recycle_ev | first).start) - as_timestamp(today_at('00:00')))/86400)}}

      - name: Paper Cardboard Waste Collection Events
        state: |
          {{ paper_ev | count() }}
        attributes:
          scheduled_events: |
            {{ paper_ev }}
          next_collection_date: |
            {{ (paper_ev | first).start }}
          countdown: |
            {{ int((as_timestamp((paper_ev | first).start) - as_timestamp(today_at('00:00')))/86400)}}

      - name: Mixed Recycling Waste Collection Events
        state: |
          {{ mixed_ev | count() }}
        attributes:
          scheduled_events: |
            {{ mixed_ev }}
          next_collection_date: |
            {{ (mixed_ev | first).start }}
          countdown: |
            {{ int((as_timestamp((mixed_ev | first).start) - as_timestamp(today_at('00:00')))/86400)}}
        icon: mdi:calendar

      - name: Garden Waste Collection Events
        state: |
          {{ garden_ev | count() }}
        attributes:
          scheduled_events: |
            {{ garden_ev }}
          next_collection_date: |
            {{ (garden_ev | first).start }}
          countdown: |
            {{ int((as_timestamp((garden_ev | first).start) - as_timestamp(today_at('00:00')))/86400)}}

Which gives me below: