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 ?
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.
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%}
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 -%}
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.
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.
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 %}
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?
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
I hope this helps you guys!
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.
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
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 -%}