My first and complicated blueprint for an alarm clock

I am trying to create a blueprint for the Magister alarm clock automation. This is my first attempt at a blueprint, but I’m in over my head I’m afraid. So I’m turning for help to my fellow community members :slight_smile:

This does not seem to work, can anyone help me out here? I can create an automation from this, but when I activate it nothing happens. The automation trace is empty.

blueprint:
  name: Magister alarm clock
  description: Trigger an alarm based on the starting time of the next lesson in Magister
  domain: automation
  author: Emphyrio
  input:
    ical_suffix:
      name: Name for iCal calendar entities
      description: This is the (unique) name you enter when you add the iCal Sensor. Suggestion is to use a format like magister_john
      default: ""
    alarm_offset:
      name: Alarm offset
      description: The input_number helper that contains the offset. The offset is the number of minutes before the first lesson that the alarm should sound. Think of it as the time needed to wake up, prepare and travel to school.
      selector:
        entity:
          filter:
            domain: input_number
    alarm_toggle:
      name: Alarm toggle
      description: The input_boolean helper that toggles this alarm on or off.
      selector:
        entity:
          filter:
            domain: input_boolean
    wakeup_song:
      name: Wake up song
      description: Audio file to be played when it is time to wake up.
      selector:
        media:

mode: single

trigger_variables:
  ical_suffix: !input ical_suffix
  alarm_offset: !input alarm_toggle
  alarm_time: >-
    {% set y = expand('group.'~ical_suffix) | map(attribute='entity_id') | list | join(', ') %}
    {% set x = states.sensor
      | selectattr('object_id', 'match', 'ical_'~ical_suffix)
      | rejectattr('attributes.location', 'in', ('','verborgen','verborgen (persoonlijk)'))
      | rejectattr('attributes.all_day', 'eq', true)
      | sort(attribute='attributes.start')
      | map(attribute='attributes.start') | list %}
    {{ (as_timestamp(x|first if x != [] else '') - (states(alarm_offset)|int(default=90) * 60) )|timestamp_custom('%H:%M', true) }}
  alarm_date: >-
    {% set x = states.sensor
      | selectattr('object_id', 'match', 'ical_'~ical_suffix)
      | rejectattr('attributes.location', 'in', ('','verborgen','verborgen (persoonlijk)'))
      | rejectattr('attributes.all_day', 'eq', true)
      | sort(attribute='attributes.start')
      | map(attribute='attributes.start') | list %}
    {{ (as_timestamp(x|first if x != [] else '') - (states(alarm_offset)|int(default=90) * 60) )|timestamp_custom('%Y-%m-%d', true) }}

# The seemingly random 'expand' is my attempt to force a refresh when one of the 50 iCal entities is changed.

variables:
  current_date: "{{ states('sensor.date') }}"
  alarm_toggle: "{{ states('input_boolean.alarm_enabled')}}"

triggers:
  - trigger: template
    id: sound_the_alarm
    value_template: "{{ alarm_time == states('sensor.time') }}"
condition:
  - condition: state
    entity_id: !input alarm_toggle
    state: 'on'
  - condition: template
    value_template: "{{ alarm_date == states('sensor.date') }}"
actions:
  - action: media_player.play_media
    data: >-
      !input wakeup_song

Hello Roelof,

Looks pretty good. I always recommend that to build a blueprint, you first write an automation without using the device_id stuff and debug that. Once that is working with static values, then build a blueprint.

First glance you are doing a LOT of stuff in the trigger_variables. You need to remember you only have limited templates available there. I’m not sure that attributes can be used, as I just avoid templates in triggers except for very basic ones.

Will it try to run, do you get a trace at all?

Does the alarm_date variable work, have you tried it in developer - templates - sandbox? This doesn’t mean it works as a limited template, but if it works at all.

For that are the 2 dates in the same format? Can also check in dev-template.

Open your Home Assistant instance and show your template developer tools.

After upgrading to 2024.12 I see a log error. It seems ‘expand’ is not allowed in limited templates.

That was probably the culprit. Well at least I learned something :slight_smile:

Why compare time and date separately, rather than both simultaneously? I am also writing an alarm clock automation/blueprints and the first thing I do with datetimes are to convert then into timestamps which are so much easier to deal with.

Also as mentioned already, trigger_variables can only use limited templates. A template trigger however can use full templates. So in my blueprints I typically only pass the selector values I need into trigger_variables, then do all of the heavy lifting as part of the template trigger.

@Mayhem_SWE Good question about why I compare date and time separately. This is copied from my ‘regular’ alarm clock automation. There I also use the ‘workday’ sensor so the alarm does not wake us in weekends or on national holidays. For the alarm clock as presented in this topic, it would not be necessary indeed.

As for your other suggestion: I do not see how I could pass the selector values from the trigger variables to the template triggers. For this automation to trigger, I would like to use a state change in any of 50 calendar entities. By using ‘expand’, I was hoping the user would not have to manually select these 50 entities when installing the blueprint.

But it seems there is no way around that. Or I am just not seeing it yet perhaps?

Why does the alarm clock need to know about 50 entities (calendar entries?)? Would it not be enough for the alarm clock to know about the next upcoming event, rather than all of them?

I don’t at all understand how your particular calendar integration works or why it puts calendar entries into groups that need to be expanded. Can’t you access the calendar directly? The local calendar integration has start_time / end_time attributes which I imagine should also be available on other calender integrations to preserve API compatibility. In my alarm clock automation, I only read those two attributes and the calendar’s state to figure out when to trigger.

If you really need the trigger to know about all 50 entities, just move the code from your alarm_time / alarm_date trigger variables to execute inside the value_template of your template trigger. And again there is no point to comparing date and time separately with all of that unnecessary code duplication, just do it in a single operation and get rid of the separate date condition.

Here is how I would simplify your current blueprint. Obviously not at all tested as I don’t have access to your calendars, but if there’s a typo or something in there should be easy enough for you to figure out. The template trigger will evaluate once at the start of every minute.

blueprint:
  name: Magister alarm clock
  description: Trigger an alarm based on the starting time of the next lesson in Magister
  domain: automation
  author: Emphyrio
  input:
    ical_suffix:
      name: Name for iCal calendar entities
      description: This is the (unique) name you enter when you add the iCal Sensor. Suggestion is to use a format like magister_john
      default: ""
    alarm_offset:
      name: Alarm offset
      description: The input_number helper that contains the offset. The offset is the number of minutes before the first lesson that the alarm should sound. Think of it as the time needed to wake up, prepare and travel to school.
      selector:
        entity:
          filter:
            domain: input_number
    alarm_toggle:
      name: Alarm toggle
      description: The input_boolean helper that toggles this alarm on or off.
      selector:
        entity:
          filter:
            domain: input_boolean
    wakeup_song:
      name: Wake up song
      description: Audio file to be played when it is time to wake up.
      selector:
        media:
mode: single

trigger_variables:
  ical_suffix: !input ical_suffix
  alarm_offset: !input alarm_offset
triggers:
  - trigger: template
    id: sound_the_alarm
    value_template: >-
      {{ now().timestamp() >= states.sensor
      | selectattr('object_id', 'match', 'ical_'~ical_suffix)
      | rejectattr('attributes.location', 'in', ['', 'verborgen', 'verborgen (persoonlijk)'])
      | rejectattr('attributes.all_day', 'true')
      | map(attribute='attributes.start') | sort | list | first
      | as_timestamp - states(alarm_offset) | int(90) * 60 }}
condition:
  - condition: state
    entity_id: !input alarm_toggle
    state: 'on'
actions:
  - action: media_player.play_media
    data: !input wakeup_song