How to get events from a dynamically selected calendar

I have a dropdown that is populated with (names of) calendar entities of interest (from a label).

I would like to have an automation trigger when an event happens. I know how to do this for a hard-coded calendar entity, but am struggling to be able to do the same given either the friendly name or entity_id of a calendar stored in a variable (like a text helper) that dynamically identifies the calendar to use whose events to trigger the automation. I tried to use templating language with no success.

Here’s the simple one that’s hard-coded:

trigger: calendar
entity_id: calendar.hvac_schedule
event: start
offset: “0:0:0”

And I’ve tried

entity_id: “{{ states(‘input_select.dropdown’) }}”

with various levels of quotes and {{'s but all report syntax error and do not save.

I also considered using a template trigger, but don’t know how to specify an event for a template trigger, let alone a calendar event.

Any suggestions? Thanks in advance

You can’t do it that way… Calendar triggers don’t accept templates.

You would need to set your automation to trigger for all calendars then use the conditions to stop the automation if the event that caused the trigger doesn’t match the state of the helper… I think that would mean individual triggers for each calendar with assigned ID’s. IIRC, the trigger variable for Calendar triggers doesn’t include the ID of the source calendar entity so you would have to use the assigned trigger ID.

triggers:
  - id: HVAC
    trigger: calendar 
    entity_id: calendar.hvac_schedule
    event: start
    offset: "0:0:0"
  - id: Erik
    trigger: calendar 
    entity_id: calendar.erik
    event: start
    offset: "0:0:0"
conditions:
  - condition: template
    value_template: |
      {{ trigger.id == states('input_select.calendar_selector') }}
actions:
....

Thanks for the feedback.

Are you saying I would even have to enumerate each of the potential calendars explicitly in the triggers list? That is, there’s no way to trigger on events of “all” calendars implicitly?

Is there a way to express event triggers using a template trigger?

Yes

To my knowledge, this isn’t currently possible.

Not reliably. Calendars don’t post start and stop events to the Event Bus so you can’t use an Event trigger. You could monitor the states of calendar entities with a template, but all-day or other contemporaneous calendar events will make it unreliable.

Ok, thanks.

What I’m doing instead then is to get all the events from the calendar for the next 24 hrs, and filter to find the next upcoming event (i.e. event.start being past now), set a timer for that calendar event.start - now(), and trigger my automation on that timer.

alias: Test Events Loop
description: ""
triggers: []
conditions: []
actions:
  - variables:
      calendar: |-
        {{
          'calendar.' + 
            states('input_select.eevac_periods_calendar_choice') |
          lower | 
          replace(' ', '_')
        }}
  - action: calendar.get_events
    metadata: {}
    data:
      duration:
        hours: 24
        minutes: 0
        seconds: 0
    target:
      entity_id: "{{ calendar }}"
    response_variable: response
  - variables:
      events: "{{ response[calendar].events }}"
      profile_now: "{{ events[0].summary }}"
      time_now: "{{ now() | as_datetime }}"
      next_event: |-
        {# events[i].start >= now () #}
        {% set ns = namespace(x=undefined) %} 
        {% for e in events %}
          {% if e.start | as_datetime >= time_now | as_datetime and (ns.x is undefined) %}
            {% set ns.x = e %}
          {% endif %}
        {% endfor %}
        {{ ns.x }}
      next_time: "{{ next_event.start | as_datetime - time_now | as_datetime }}"
  - action: timer.start
    metadata: {}
    data:
      duration: "{{ next_time }}"
    target:
      entity_id: timer.eevac_hold_timer
mode: single

The function now() returns a datetime object, so there’s no need to use the as_datetime filter on it. Likewise, there is no need to use it on time_now.

You can simplify the next_event template using built-in filters instead of a for loop:

...
  - variables:
      events: "{{ response[calendar].events }}"
      profile_now: "{{ events[0].summary }}"
      time_now: "{{ now() }}"
      next_event: |-
        {% set not_past = events | sort(attribute='start') | selectattr('start', 'ge', time_now|string) | list %}
        {{ not_past[0] if not_past != [] else {"start": time_now} }}
      next_time: "{{ next_event.start | as_datetime - time_now }}"

Finally, just FYI: The automation config you have shared doesn’t show a defined trigger. If that was done for expediency, fine. But, if you don’t actually plan on defining a trigger, you should be using a Script, not an Automation. Automations without defined triggers are known to cause reliability issues.

I see, thanks for the update. I’m remain unsure as to whether variables are stored as string or typed like date, and whether items coming from various sources are strings or typed items.

For example, trigger.idx appeared to be an integer-like string. Some things coming out of integrations are often strings even though they are basically numbers. And not sure about calendar event.start/.end as whether are strings or date objects…

Is there a “typeof” operator that can tell me to help me learn the types?

Yes, this is just a test to be able to identify and trigger some automation relative to a user-designated/chosen calendar entity; these calendar entities are dedicated to holding hvac period information, basically pairs of profile-name & time-range entered into such calendar.

Currently, there are a bunch of individual type checks, but I thinktypeof() is in the list of functions to be added in the 2025.4 beta next week. So, assuming it makes it through beta, it should be available soon.

Those are ISO-formatted datetime strings.

I just found that when you put expressions into a template using “Developer tools → Template” it give a “Result” and what the “Result type is”. Having it say a type is gold for learning!

Be careful with how the Template Editor reports type.

Try this:

Test 1

Delete everything in the Template Editor.

Copy-paste the following template:

{{ [1, 2, 3] }}

The Template Editor reports the result’s type is list.

Test 2

Delete everything in the Template Editor.

Copy-paste the following template:

{{ {'name': 'cat', 'qty': 2} }}

The Template Editor reports the result’s type is dict.

Test 3

Delete everything in the Template Editor.

Copy-paste the following template:

{{ [1, 2, 3] }}{{ {'name': 'cat', 'qty': 2} }}

The Template Editor reports the result’s type is string.