Gettling list of day's events from a calendar

I want to use TTS to summarise the events from my calendar for the day, however the calender entitity only has the “next” event listed. Is there a way to do this ?

2 Likes

Atomic Calendar Revive from HACS will show all events for the day on Lovelace. I don’t think it can do a TTS summary, but it’s a simple way to display everything planned for the day.

I did exactly that a few days ago actually, a morning routine that gives me all my events through TTS in the morning.

How to get all today events from google calendar? - Configuration - Home Assistant Community (home-assistant.io)

This has a lot of other things like weather and garbageday but it should give you what you need. When motion is triggered in the kitchen in the morning, and last time it was triggered was not on the same day (first motion in kitchen for the day) it wil trigger a TTS action.

Make sure to enter a high number for generating the iCal events, i normally dont have more then 10 events in a day so i created 10 iCal events when i configured the iCal sensor plugin, and then loop through those to see if each event is available or not.

Trigger

type: motion
platform: device
device_id: 09b5b6c6edabf56bb038b9f63990688f
entity_id: binary_sensor.kitchen_motion_sensor_motion
domain: binary_sensor

Condition

condition: template
value_template: >-
  {{as_timestamp(state_attr('automation.morning_announcement','last_triggered'))|timestamp_custom('%-d')
  != as_timestamp(now())|timestamp_custom('%-d')}}

Action

service: tts.google_say
data:
  cache: false
  entity_id: media_player.home_nest_mini_group
  message: >-
    Godmorgon! Temperaturen inne är just nu {{
    states('sensor.dorr_hylla_temperature_measurement_2') }} , Utetemperaturen
    är {{
    states('sensor.outdoor_back_entrance_motion_sensor_temperature_measurement')
    }}  och det blir runt {{
    states('sensor.openweathermap_forecast_temperature') | round }}   som högst
    och {{ states('sensor.openweathermap_forecast_temperature_low') | round }}
    som lägst.

    {% if states('sensor.openweathermap_forecast_condition') == 'snowy' %}
      Det kommer runt {{ states('sensor.openweathermap_forecast_precipitation') | round }} millimeter snö under dagen.
    {% elif states('sensor.openweathermap_forecast_condition') == 'rainy' %}
      Det kommer runt {{ states('sensor.openweathermap_forecast_precipitation') | round }} millimeter regn under dagen.
    {% elif states('sensor.openweathermap_forecast_condition') == 'pouring' %}
      Det kommer mycket regn med runt {{ states('sensor.openweathermap_forecast_precipitation') | round }} millimeter över dagen.
    {% elif states('sensor.openweathermap_forecast_condition') ==
    'snowy-rainy'    %}
      Det kommer snö-blandat på runt {{ states('sensor.openweathermap_forecast_precipitation') | round }} millimeter regn under dagen.
    {% elif states('sensor.openweathermap_forecast_condition') ==   
    'lightning-rainy' %}
      Det blir åsk-regn med runt {{ states('sensor.openweathermap_forecast_precipitation') | round }} millimeter under dagen.
    {% elif states('sensor.openweathermap_forecast_condition') == 'hail' %}
      Kan komma hagel under dagen med {{ states('sensor.openweathermap_forecast_precipitation') | round }} millimeter under dagen.

    {% elif states('sensor.openweathermap_forecast_condition') == 'cloudy' %}
      Räkna med mestadels molnigt väder.
    {% elif states('sensor.openweathermap_forecast_condition') == 'partlycloudy'
    %}
      Räkna med delvis molnigt väder.
    {% elif states('sensor.openweathermap_forecast_condition') == 'lightning' %}
      Räkna med åskväder under dagen.
    {% elif states('sensor.openweathermap_forecast_condition') == 'sunny' %}
      Räkna med mestadels soligt väder.
    {% elif states('sensor.openweathermap_forecast_condition') == 'windy' %}
      Räkna med mestadels blåsigt väder.
    {% elif states('sensor.openweathermap_forecast_condition') ==
    'windy-variant' %}
      Räkna med mestadels blåsigt och målnigt väder.
    {% endif %}  

    {% if is_state('input_boolean.isgarbageday','on') %}
      ... Imorgon är sophämtning, glöm inte ställa ut soptunnan.
    {% endif %} 



    {% for i in range(0, 9) %}
      {% if is_state('sensor.ical_christian_kalender_event_' + i|string,'unavailable') %}
      {% elif as_timestamp(state_attr('sensor.ical_christian_kalender_event_' + i|string, "start"))|timestamp_custom('%-d')
          == as_timestamp(now())|timestamp_custom('%-d') %}
        {% if i == 0 %}
          Din första kalenderpost för idag är 
        {% endif %} 
        Klockan 
        {{ as_timestamp(state_attr('sensor.ical_christian_kalender_event_' + i|string, "start")) | timestamp_custom('%H:%M') }}
        {{ state_attr('sensor.ical_christian_kalender_event_' + i|string, "summary") }}
      {% endif %} 
    {% endfor%}

3 Likes

Almost forgot, it might be of some help or more of a tip, i also have a TTS message play once the calendar event becomes active as a reminder, but only if I´m home.

Trigger

platform: state
entity_id: calendar.christian_XXXX
from: 'off'
to: 'on'

Condition

condition: zone
entity_id: person.christian
zone: zone.home

Action

service: tts.google_say
data:
  cache: false
  entity_id: media_player.home_nest_mini_group
  message: |-
    Du har en kalenderpost nu klockan 
    {% for cal_events in expand(states.calendar.christian_XXXX) %}
      {{  as_timestamp(cal_events.attributes.start_time) | timestamp_custom('%H:%M') }},
      {{ cal_events.attributes.message }}
    {%- endfor -%}

2 Likes

Thanks Christian, will take a look at this.

This was exacly what i was looking for.
When i put in your code and modified it i get an mallfuntion error at data sequence , do you have any idea what it could be.
When iam at the pc i can put my code in

There is a feature request for this functionality.

If you are also interested in getting a calender event list, please vote for this feature: Calendar - new entitites that show a list of events with a certain scope

My use case is to show a list of upcoming events on a epaper display. I already tried some ICS/ICAL custom_components, like Christian suggested, however they all seem to have huge problems with reoccuring events (e.g. birthdays events) - at least for a Google Calendar.

1 Like

This was just the inspiration i needed.
I really don’t like {% ... %} script thing, so i made it in python instead.

But Thanks for sharing, it really jumped me in the right direction.

1 Like

This is old school since 2023.7.0 and services that can output

alias: Agenda
sequence:
  - service: calendar.list_events
    data:
      start_date_time: "{{ date }}"
      duration:
        hours: 24
        minutes: 0
        seconds: 0
    target:
      entity_id: "{{ calendrier }}"
    response_variable: evenements
  - variables:
      reponse: |-
        {%- set ns = namespace(events={}) %}
        {%- for event in evenements.events %}
          {%- set default_time = "00:00:00Z" %}
          {%- set date_format = "%Y-%m-%dT%H:%M:%S%z" %}
          {%- set datetime_var = strptime(event.start if "T" in event.start else event.start + "T" + default_time, date_format) %}
          {%- set hour = datetime_var.strftime("%d/%m/%Y %H:%M") if "T" in event.start else datetime_var.strftime("%d/%m/%Y 00:00") %}
          {%- set ns.events = dict(ns.events, **{hour:event.summary}) %}
        {%- endfor %}
        {{ ns.events }}
  - stop: Success
    response_variable: reponse
mode: single
icon: mdi:calendar-alert-outline

And an example of call

service: script.agenda
data:
  date: "{% import 'date.jinja' as dt %} {{ dt.aujourdhui() }}"
  calendrier: calendar.olivier

And to use the response, like in a notification

      - service: script.agenda
        data:
          date: "{% import 'date.jinja' as dt %} {{ dt.aujourdhui() }}"
          calendrier: calendar.olivier
        response_variable: olivier
      - if:
          - condition: template
            value_template: "{{ olivier|length > 0 }}"
        then:
          - service: notify.mobile_app_iphone
            data:
              title: Today
              message: |-
                {% for key, value in olivier.items() %}
                  {%- if key[-5:] != "00:00" %}
                    {{- key[-5:] + " " + value }}
                  {%- else %}
                    {{ value }}  
                  {%- endif %}
                {% endfor %}
5 Likes

How would I go about to store the service call response for later use?
Currently I have a template sensor where the next calendar event is an attribute but I would like to have a list of all events in next X days.

Can I use the output from the service call calendar.list_events to update the template sensor?

1 Like

HI guys, I’ve struggled to find an answer across multiple posts and forums.

but I have persevered and combined multiple suggestions into a solution that works for me as a morning agenda for the day.

Step 1 - go to google calendar, click the 3 dots on the calendar you want, settings > scroll to bottom - copy ICAL Link

Step 2 - go to HAC > search for ICAL and download and install

Step 3 - Reboot HA

Step 4 - Go to Integrations > Add > ICAL

Step 5 - Paste your ICAL Link you copied earlier and set number of events to grab, I used 10 as I would never have more than 10 events to check, do note it checks multiple days, so if have only 5 tasks over a week, it will list them across the week for you

Step 6 - Wait for about 1-2 minutes for the sensors to populate

now you can built your template for your text to speech

here is my template I wrote … I only need 4 or so but you can adapt it for more if you like
the code has IF functions to determine if the event is today and alexa or google will say the event is at 8 today instead of the day of the week etc.

Its not perfect yet but its working!

Your next 4 events .. at {{ as_timestamp(state_attr('sensor.ical_carl_event_0' |string, "start")) | timestamp_custom('%H:%M') }} {% if (now().date() + timedelta(days=0))  | string  ==
 as_timestamp(state_attr('sensor.ical_carl_event_0' |string, "start")) |timestamp_custom('%Y-%m-%d')   %}Today {% else %} {{ as_timestamp(state_attr('sensor.ical_carl_event_0' |string, "start")) | timestamp_custom('%A') }} 
{% endif %}   
.. {{ state_attr('sensor.ical_carl_event_0' |string, "summary") }} ..
 and at {{ as_timestamp(state_attr('sensor.ical_carl_event_1' |string, "start")) | timestamp_custom('%H:%M') }}

{% if (now().date() + timedelta(days=0))  | string  ==
 as_timestamp(state_attr('sensor.ical_carl_event_1' |string, "start")) |timestamp_custom('%Y-%m-%d')   %}Today {% else %} {{ as_timestamp(state_attr('sensor.ical_carl_event_1' |string, "start")) | timestamp_custom('%A') }} 
{% endif %}   
 .. {{ state_attr('sensor.ical_carl_event_1' |string, "summary") }}
 then at {{ as_timestamp(state_attr('sensor.ical_carl_event_2' |string, "start")) | timestamp_custom('%H:%M') }} 

{% if (now().date() + timedelta(days=0))  | string  ==
 as_timestamp(state_attr('sensor.ical_carl_event_2' |string, "start")) |timestamp_custom('%Y-%m-%d')   %}Today {% else %} {{ as_timestamp(state_attr('sensor.ical_carl_event_2' |string, "start")) | timestamp_custom('%A') }} 
{% endif %}    .. {{ state_attr('sensor.ical_carl_event_2' |string, "summary") }}
 and finally at  {{ as_timestamp(state_attr('sensor.ical_carl_event_3' |string, "start")) | timestamp_custom('%H:%M') }} 

{% if (now().date() + timedelta(days=0))  | string  ==
 as_timestamp(state_attr('sensor.ical_carl_event_3' |string, "start")) |timestamp_custom('%Y-%m-%d')   %}Today {% else %} {{ as_timestamp(state_attr('sensor.ical_carl_event_3' |string, "start")) | timestamp_custom('%A') }} 
{% endif %}    .. {{ state_attr('sensor.ical_carl_event_3' |string, "summary") }}  

This code produces text like this

Your next 4 events at 08:30 Today blah blah
and at 09:30 Today blah blah
then at 19:30 Today blah blah
and finally at 09:00 Tuesday … blah blah

the . . in the code is a breather for alexa so they speaks normally instead of rattling it off like a robot :slight_smile:

I hope this helps you guys!

1 Like

It is probably working, I was using ical from HACS before.
But it is so 2021, lol.
Since the introduction of the service calendar.list_events, the method that I described earlier is the way to go. And, as bonus, my script (in French) to wake my kids in the morning with their echo dot.

alias: Annonce du matin
sequence:
  - service: script.agenda
    data:
      date: |-
        {%- import "date.jinja" as dt %}
        {{ dt.aujourdhui() }}
      calendrier: calendar.conges_scolaires
    response_variable: scolaire
  - service: weather.get_forecast
    data:
      type: daily
    target:
      entity_id: weather.openweathermap
    response_variable: owmforecast
  - service: notify.alexa_media
    data:
      message: |
        <speak>
          <audio src="soundbank://soundlibrary/animals/amzn_sfx_bird_chickadee_chirps_01"/>
          <audio src="soundbank://soundlibrary/animals/amzn_sfx_bird_forest_short_01"/>
          Bonjour les enfants.   
          {% set jours = ['lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi', 'dimanche'] %}
          {% set temperature = states('sensor.openweathermap_outside') | default(-100) | round(0) | string %}   
          {% set maximum = owmforecast['forecast'][0]['temperature'] | default(-100) | round(0) | string %}
          {% set condition = owmforecast['forecast'][0]['condition'] %}
          {% set precipitation = owmforecast['forecast'][0]['precipitation'] | round(0) %}
          
          {% set mois = ['janvier','février','mars','avril','mai','juin','juillet','août','septembre','octobre','novembre','décembre'] %} 
          {% set forecast = { 'snowy':'hivernale', 
                              'rainy':'pluvieuse', 
                              'pouring':'pluie abondante', 
                              'snowy-rainy':'un mélange de neige et de pluie', 
                              'lightning-rainy':'pluvieuse et orageuse', 
                              'hail':'grêle', 
                              'cloudy':'nuageuse', 
                              'partlycloudy':'partiellement nuageuse',
                              'lightning':'orageuse',
                              'sunny':'ensoleillée',
                              'windy':'venteuse',
                              'windy-variant':'brumeuse',
                              'clear-night':'nuit claire'
                            } 
          %} 
          {% set date = now().strftime('%d') + ' ' + mois[now().strftime('%m') | int - 1] + ' ' + now().strftime('%Y') %} 
          Nous sommes le {{ jours[now().isoweekday()-1] +' ' + date }}.
          La météo est {{forecast[condition] if condition in forecast.keys() else condition}}{% if temperature | int > -100 %} avec {{temperature}} degrés. {% endif %}
          {% if condition == 'snowy' %}
            Il y aura environ {{precipitation}} millimètre de neige dans la journée
          {% elif condition == 'rainy' %}
            Il y aura environ {{precipitation}} millimètre de pluie dans la journée
          {% elif condition == 'pouring' %}
            Il y aura beaucoup de pluie avec environ {{precipitation}} millimètres au cours de la journée
          {% elif condition == 'snowy-rainy' %}
            Il y aura de la neige mêlée de pluie avec environ {{precipitation}} millimètres de précipitation au cours de la journée
          {% elif condition == 'lightning-rainy' %}
            Il y aura des averses orageuses avec environ {{precipitation}} millimètres pendant la journée
          {% elif condition == 'hail' %}
            Possibilité de grêle pendant la journée avec {{precipitation}} millimètres de précipitation pendant la journée
          {% elif condition == 'cloudy' %}
            Attendez-vous à un temps principalement nuageux
          {% elif condition == 'partlycloudy' %}
            Attendez-vous à un temps partiellement nuageux
          {% elif condition == 'lightning' %}
            Des orages sont attendus au cours de la journée
          {% elif condition == 'sunny' %}
            Un temps majoritairement ensoleillé est attendu
          {% elif condition == 'windy' %}
            Un temps très venteux est attendu
          {% elif condition == 'windy-variant' %}
            Un temps généralement venteux et brumeux est attendu
          {% endif %}{% if maximum | int > -100 %} avec une température maximale de {{maximum}} degrés.{% endif %}
          {% if (state_attr('sun.sun', 'elevation') >= 0) %}
            Couché du soleil à {{ (state_attr('sun.sun', 'next_setting') | as_datetime | as_local).strftime('%H:%M') }}
          {% else %}
            Levé du soleil à {{ (state_attr('sun.sun', 'next_rising') | as_datetime | as_local).strftime('%H:%M') }}
          {% endif %}
          {% if scolaire | length > 0 %}
            Aujourd'hui, il y a 
            {% for key, value in scolaire.items() %}
              {{ value }}
            {% endfor %}
          {% endif %}
          <break duration="1s"/>
          Bonne journée !
        </speak>
      data:
        type: tts
      target: media_player.echo_dot_enfants
mode: single
icon: mdi:weather-sunset-up

And, cherry on the cake, it is compatible with the new forecast service introduced in 2023.9.0.

2 Likes

I was experimenting with announcing calendar events some years back, but have up in the end.
This looks excellent. Great Use of the New Services that return data.

I also like the idea to trigger on first motion of the Day :slightly_smiling_face:

Bonjour Olivier1974,
I could implement your script Agenda successfully but I can not replicate your example of call.
What is is about the last 2 lines you set in data especially for date with jinja and dt.aujourdhui.
Do I need to add a specific integration addon in HA to make it work?
Cheers

It is a file that I did to avoid repeating a lot of jinja date functions.
It is located in the folder custom_templates in you HA installation directory.

It defines a various set of macro that I’m using all the time.
And you can use them by adding an import. Here is an exemple to use it

{% import 'date.jinja' as dt %}
{{ dt.is_weekend() }}

This will be true on Saturday and Sunday, <empty> otherwise.
Why not True/False? Because macro is always a string and in an if statement, empty is considered false

Here is the content of the date.jinja file for your info (sorry, as I’m from Belgium, I’m french speaking)

{%- macro aujourdhui() -%}
{{- today_at() -}}
{%- endmacro -%}

{%- macro demain() -%}
{{- today_at() + timedelta(days=1) -}}
{%- endmacro -%}

{%- macro is_weekend() -%}
{{- iif(now().isoweekday() in [6, 7],'true','') -}}
{%- endmacro -%}

{%- macro is_conge_scolaire() -%}
{{- iif(is_state('calendar.conges_scolaires', 'on'),'true','') -}}
{%- endmacro -%}

{%- macro is_ferie() -%}
{{- iif(is_state('calendar.jours_feries_en_belgique', 'on'),'true','') -}}
{%- endmacro -%}

{%- macro debut_conge_scolaire() -%}
{{- state_attr('calendar.conges_scolaires','start_time') | as_datetime | as_local -}}
{%- endmacro -%}

{%- macro debut_ferie() -%}
{{- state_attr('calendar.jours_feries_en_belgique','start_time') | as_datetime |  as_local -}}
{%- endmacro -%}

{%- macro fin_conge_scolaire() -%}
{{- state_attr('calendar.conges_scolaires','end_time') | as_datetime | as_local - timedelta(days = 1) -}}
{%- endmacro -%}

{%- macro fin_ferie() -%}
{{- state_attr('calendar.jours_feries_en_belgique','end_time') | as_datetime |  as_local - timedelta(days = 1) -}}
{%- endmacro -%}