You might know a teenager who a) likes to sleep in as late as possible and b) uses Magister to check his school calendar. In this project I created an alarm that automatically adjusts itself to a Magister or other iCAL calendar.
Magister is an app that is used by a lot of schools in the Netherlands. But the same principles can be applied to any iCAL calendar that offers a subscription link.
Requirements:
- Calendar sharing has to be enabled in the school’s Magister instance (if not, the school needs to ask the provider to enable this).
- Add-on iCal Sensor support for Home Assistant (it can be installed through HACS).
- A media player that can be used to play the alarm’s song. I am using a Google Home (Nest) Mini.
- The Time & Date integration configured so sensor.time and sensor.date are available.
- Some skills to add it to your dashboard.
Steps:
-
Get the calendar URL. You need to log into Magister online using your kid’s credentials. Then find the Settings page (find it by clicking on the portrait picture in the bottom left corner). There should be a widget called ‘Calendar sharing’ that displays the webcal:// url you need.
-
Install the iCal Sensor add-on through HACS. Follow the instructions to add it to your integrations. Configure it to look a maximum of 14 days into the future and for 50 events (=50 sensors). Name it ‘magister’ to make it work with the following templates.
-
Create some helpers:
- An input_boolean ‘alarm_magister’ for toggling the alarm.
- An input_number ‘alarm_magister_offset’ min 1 max 180, for storing the offset of how many minutes before the first lesson the kid needs to wake up.
-
Create a template sensor that contains the date and time for the next ‘first lesson of the day’. This one will be used as the trigger for the alarm automation.
template:
- trigger:
- platform: state
entity_id: sensor.date
- platform: state
entity_id:
- sensor.ical_magister_event_0
- sensor.ical_magister_event_1
- sensor.ical_magister_event_2
- sensor.ical_magister_event_3
- sensor.ical_magister_event_4
- sensor.ical_magister_event_5
- sensor.ical_magister_event_6
- sensor.ical_magister_event_7
- sensor.ical_magister_event_8
- sensor.ical_magister_event_9
- sensor.ical_magister_event_10
- sensor.ical_magister_event_11
- sensor.ical_magister_event_12
- sensor.ical_magister_event_13
- sensor.ical_magister_event_14
- sensor.ical_magister_event_15
- sensor.ical_magister_event_16
- sensor.ical_magister_event_17
- sensor.ical_magister_event_18
- sensor.ical_magister_event_19
- sensor.ical_magister_event_20
- sensor.ical_magister_event_21
- sensor.ical_magister_event_22
- sensor.ical_magister_event_23
- sensor.ical_magister_event_24
- sensor.ical_magister_event_25
- sensor.ical_magister_event_26
- sensor.ical_magister_event_27
- sensor.ical_magister_event_28
- sensor.ical_magister_event_29
- sensor.ical_magister_event_30
- sensor.ical_magister_event_31
- sensor.ical_magister_event_32
- sensor.ical_magister_event_33
- sensor.ical_magister_event_34
- sensor.ical_magister_event_35
- sensor.ical_magister_event_36
- sensor.ical_magister_event_37
- sensor.ical_magister_event_38
- sensor.ical_magister_event_39
- sensor.ical_magister_event_40
- sensor.ical_magister_event_41
- sensor.ical_magister_event_42
- sensor.ical_magister_event_43
- sensor.ical_magister_event_44
- sensor.ical_magister_event_45
- sensor.ical_magister_event_46
- sensor.ical_magister_event_47
- sensor.ical_magister_event_48
- sensor.ical_magister_event_49
sensor:
- name: "Nextup lesson Magister"
# device_class: timestamp
unique_id: nextup_lesson_magister
state: >-
{% if now().hour < 12 %}
{% set x = states.sensor
| selectattr('object_id', 'match', 'ical_magister')
| selectattr('attributes.start', 'defined')
| rejectattr('attributes.location', 'in', ('','verborgen'))
| rejectattr('attributes.all_day', 'eq', true)
| sort(attribute='attributes.start')
| map(attribute='attributes.start') | list %}
{{ x|first if x != [] else '' }}
{% else %}
{% set x = states.sensor
| selectattr('object_id', 'match', 'ical_magister')
| selectattr('attributes.start', 'defined')
| rejectattr('attributes.start', 'search', as_timestamp(now()) | timestamp_custom ('%Y-%m-%d') )
| rejectattr('attributes.location', 'in', ('','verborgen'))
| rejectattr('attributes.all_day', 'eq', true)
| sort(attribute='attributes.start')
| map(attribute='attributes.start') | list %}
{{ x|first if x != [] else '' }}
{% endif %}
- Create a template sensor that contains a nice displayable description of the alarm time. E.g. “Tomorrow at 6.55”. This one you can use in your Lovelace dashboard, for display purposes.
template:
- trigger:
- platform: state
entity_id: sensor.date
- platform: state
entity_id: sensor.nextup_lesson_magister
- platform: state
entity_id: input_number.magister_offset
sensor:
- name: Description of next up lesson
unique_id: description_of_nextup_lesson
state: >-
{% set midnight = now().replace(hour=0, minute=0, second=0, microsecond=0).timestamp() %}
{% set event = states('sensor.nextup_lesson_magister') | as_timestamp(default=0) %}
{% set delta = ((event - midnight) / 86400) | int %}
{% if is_state('sensor.nextup_lesson_magister','') %} Onbekend
{% elif delta < 0 %} Onbekend
{% elif delta == 0 %} Vandaag {{ (states('sensor.nextup_lesson_magister')|as_timestamp - (states('input_number.magister_offset')|int(default=90) * 60)) |timestamp_custom('%H:%M', true)}}
{% elif delta == 1 %} Morgen {{ (states('sensor.nextup_lesson_magister')|as_timestamp -(states('input_number.magister_offset')|int(default=90) * 60)) |timestamp_custom('%H:%M', true)}}
{% else %} Over {{ delta }} dagen {{ (states('sensor.nextup_lesson_magister')|as_timestamp - (states('input_number.magister_offset')|int(default=90) * 60)) |timestamp_custom('%H:%M', true)}}
{% endif %}
- Create an automation for triggering the alarm.
- alias: "Alarm Magister"
id: alarm_magister
trigger:
- platform: template
value_template: "{{ (as_timestamp(states('sensor.nextup_lesson_magister')) - (states('input_number.magister_offset')|int(default=90) * 60) )|timestamp_custom('%H:%M', true) == states('sensor.time') }}"
condition:
- condition: state
entity_id: input_boolean.alarm_magister
state: 'on'
- condition: template
value_template: "{{ (as_timestamp(states('sensor.nextup_lesson_magister')) - (states('input_number.magister_offset')|int(default=90) * 60) )|timestamp_custom('%Y-%m-%d', true) == states('sensor.date') }}"
action:
- service: media_player.play_media
entity_id: media_player.googlehomemini
data:
media_content_id: "{{ <your base url here> + '/local/audio/dont_worry_be_happy.mp3' }}"
media_content_type: "audio/mp3"
-
Add it to your dashboard:
- Something to toggle the input_boolean, for toggling the alarm.
- Something to set the offset (input_number).
- Something to display the description for next ‘first lesson of the day’ (template sensor).
I’ve been fine tuning this setup for a couple of months now. The alarm works for the way our school uses the calendar. Of course other schools might use the calendar differently, I imagine the template might need to be tuned for that.
If you have any questions let me know. I’m hoping some more teenagers get to sleep in once in a while using these examples
Edit 1: excluded all-day calendar events (these start at midnight, usually holiday reminders).
Edit 2: good to know: at the beginning of each school year, calendar sharing is disabled by Magister. It gets enabled when all schools in the Netherlands have started again.
Edit 3: a little housekeeping, fixed a wrong reference in the second template sensor.
Edit 4: added the check suggested below, to check if a start attribute exists. Preventing an error when a calendar item is empty / without a starting time.