Générer une liste d'anniversaires depuis Home Assistant et un calendrier
Dans cet article, nous allons voir comment réaliser un script Home Assistant permettant la génération du texte suivant :
🎂 C'est l'anniversaire de Pauline. Prochainement, ce sera celui de Benjamin (sam 16) et Xavier (ven 15).
Ce script permet ensuite de générer des notifications sur téléphone ou de créer des annonces sur des enceintes connectées.
Pré-requis
Avant de commencer, vous devez disposer de :
- Une instance Home Assistant fonctionnelle ;
- Un calendrier déjà intégré à Home Assistant (Google Calendar, CalDAV...) sur lequel se trouvent les anniversaires.
Configuration du script
Le script nommé « Génération liste anniversaires » est configuré avec :
- une icône de gâteau ;
- un fonctionnement en mode parallèle, avec jusqu'à 10 instances simultanées.
(yml)1alias: Génération liste anniversaires 2icon: mdi:cake 3mode: parallel 4max: 10
Paramètres d'entrée
En entrée, le script accepte trois paramètres :
- date : date de référence pour la recherche (utile pour préparer un message la veille par exemple) ;
- days_to_show : nombre de jours à afficher après la date de référence (pour savoir que d'autres anniversaires vont arriver) ;
- calendar_name : entité du calendrier où récupérer les événements.
(yml)1fields: 2 date: 3 name: Date de référence 4 selector: 5 date: {} 6 7 days_to_show: 8 name: Nombre de jours à afficher 9 selector: 10 number: 11 min: 1 12 max: 60 13 step: 1 14 mode: slider 15 default: 15 16 17 calendar_name: 18 name: Calendrier à utiliser 19 selector: 20 entity: 21 domain: calendar 22 default: calendar.anniversaires
Déroulé du script
Les différentes étapes du script sont définies dans un objet sequence
:
(yml)1sequence:
Définition de variables
Nous allons définir un certain nombre de variables qui nous seront utiles plus tard :
- daynames et monthnames : le nom des jours de la semaine et des mois en français ;
- period_start et period_end : qui correspondent à la date de début et de fin de la période.
(yml)1- variables: 2 daynames: ['lun', 'mar', 'mer', 'jeu', 'ven', 'sam', 'dim'] 3 monthnames: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre'] 4 5 period_start: "{% if date is not defined %}{{ now().date() }}{% else %}{{ date }}{% endif %}" 6 period_end: "{{ (as_datetime(period_start) + timedelta(days=days_to_show)).date() }}"
Les variables auront donc des valeurs correspondantes aux suivantes :
period_start: '2025-08-13'
period_end: '2025-08-28'
Récupération des événements du calendrier
Nous allons maintenant récupérer les événements du calendrier pour la période :
(yml)1- action: calendar.get_events 2 target: 3 entity_id: "{{ calendar_name }}" 4 data: 5 start_date_time: "{{ period_start }}" 6 end_date_time: "{{ period_end }}" 7 response_variable: events_entities
ce qui retournera un objet comme suit :
events_entities:
calendar.anniversaires:
events:
- start: '2025-08-13'
end: '2025-08-14'
summary: Pauline
- start: '2025-08-16'
end: '2025-08-17'
summary: Benjamin
- start: '2025-08-22'
end: '2025-08-23'
summary: Xavier
Séparation des événements
Nous allons maintenant séparer les événements en deux :
- Les événements du jour de référence (period_start), et les autres événements à venir :
(yml)1- variables: 2 date_events: "{{ events_entities[calendar_name]['events'] | selectattr('start', 'eq', period_start) | list }}" 3 later_events: "{{ events_entities[calendar_name]['events'] | rejectattr('start', 'eq', period_start) | list }}"
date_events:
- start: '2025-08-13'
end: '2025-08-14'
summary: Pauline
later_events:
- start: '2025-08-16'
end: '2025-08-17'
summary: Benjamin
- start: '2025-08-22'
end: '2025-08-23'
summary: Xavier
Génération du message
Nous allons enfin pouvoir passer à la construction du message final. Celui-ci est composé de plusieurs parties, qui pourront être adaptées selon le cas et les préférences :
- start_sentence : la phrase d'introduction (aujourd'hui, demain ou la date) ;
- date_names : le nom des personnes dont l'anniversaire correspond à
period_start
, séparés par des virgules ou "et" pour le dernier ; - later_events_with_date : la liste des anniversaires à venir avec la date ;
- response : la mise en forme finale.
(yml)1- variables: 2 start_sentence: |- 3 {% if date_events | count > 0 %} 4 {% set d = as_datetime(period_start).date() %} 5 {% set today = now().date() %} 6 {% set tomorrow = (now() + timedelta(days=1)).date() %} 7 🎂 {% if d == today %}C'est{% elif d == tomorrow %}Demain, ce sera{% else %}Le {{ daynames[d.weekday()] ~ ' ' ~ '%02d'|format(d.day) }}, ce sera{% endif %} l'anniversaire de 8 {% endif %} 9 10 date_names: |- 11 {% set names = date_events | map(attribute='summary') | list %} 12 {% if names | count > 1 %} 13 {{ names[:-1] | join(', ') ~ ' et ' ~ names[-1] }} 14 {% else %} 15 {{ names | join('') }} 16 {% endif %} 17 18 later_events_with_date: |- 19 {% set l = namespace(values=[]) %} 20 {% for event in later_events %} 21 {% set d = as_datetime(event.start) %} 22 {% set l.values = l.values + [ 23 event.summary ~ ' (' ~ daynames[d.weekday()] ~ ' ' ~ '%02d'|format(d.day) ~ ')' 24 ] %} 25 {% endfor %} 26 {{ l.values }} 27 28 later_names_with_date: |- 29 {% if later_events_with_date | count > 1 %} 30 {{ later_events_with_date[:-1] | join(', ') ~ ' et ' ~ later_events_with_date[-1] }} 31 {% else %} 32 {{ later_events_with_date | join('') }} 33 {% endif %} 34 35 response: 36 value: >- 37 {% if date_events | count > 0 %}{{ start_sentence }} {{ date_names }}.{% endif %} 38 {% if later_events_with_date %} Prochainement, ce sera {% if date_events | count <= 0 %}l'anniversaire{% else %}celui{% endif %} de {{ later_names_with_date }}.{% endif %}
Sauvegarde dans un input_text (optionnel)
Il peut être intéressant de stocker le résultat du script dans une variable pour le réutiliser sur un portail par exemple :
(yml)1- target: 2 entity_id: input_text.anniversaires_journee 3 data: 4 value: "{{ response.value }}" 5 action: input_text.set_value
Retour du script
Enfin, le script retourne le texte en résultat du script. Ce qui permet d'utiliser le script directement dans une autre automatisation :
(yml)1- stop: returning value 2 response_variable: response
Script final
Fichier : anniversaires.yml (yml)1alias: Génération liste anniversaires 2icon: mdi:cake 3mode: parallel 4max: 10 5 6fields: 7 date: 8 name: Date de référence 9 selector: 10 date: {} 11 12 days_to_show: 13 name: Nombre de jours à afficher 14 selector: 15 number: 16 min: 1 17 max: 60 18 step: 1 19 mode: slider 20 default: 15 21 22 calendar_name: 23 name: Calendrier à utiliser 24 selector: 25 entity: 26 domain: calendar 27 default: calendar.anniversaires 28 29sequence: 30 - variables: 31 daynames: ['lun', 'mar', 'mer', 'jeu', 'ven', 'sam', 'dim'] 32 monthnames: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre'] 33 34 period_start: "{% if date is not defined %}{{ now().date() }}{% else %}{{ date }}{% endif %}" 35 period_end: "{{ (as_datetime(period_start) + timedelta(days=days_to_show)).date() }}" 36 37 - action: calendar.get_events 38 target: 39 entity_id: "{{ calendar_name }}" 40 data: 41 start_date_time: "{{ period_start }}" 42 end_date_time: "{{ period_end }}" 43 response_variable: events_entities 44 45 - variables: 46 date_events: "{{ events_entities[calendar_name]['events'] | selectattr('start', 'eq', period_start) | list }}" 47 later_events: "{{ events_entities[calendar_name]['events'] | rejectattr('start', 'eq', period_start) | list }}" 48 49 - variables: 50 start_sentence: |- 51 {% if date_events | count > 0 %} 52 {% set d = as_datetime(period_start).date() %} 53 {% set today = now().date() %} 54 {% set tomorrow = (now() + timedelta(days=1)).date() %} 55 🎂 {% if d == today %}C'est{% elif d == tomorrow %}Demain, ce sera{% else %}Le {{ daynames[d.weekday()] ~ ' ' ~ '%02d'|format(d.day) }}, ce sera{% endif %} l'anniversaire de 56 {% endif %} 57 58 date_names: |- 59 {% set names = date_events | map(attribute='summary') | list %} 60 {% if names | count > 1 %} 61 {{ names[:-1] | join(', ') ~ ' et ' ~ names[-1] }} 62 {% else %} 63 {{ names | join('') }} 64 {% endif %} 65 66 later_events_with_date: |- 67 {% set l = namespace(values=[]) %} 68 {% for event in later_events %} 69 {% set d = as_datetime(event.start) %} 70 {% set l.values = l.values + [ 71 event.summary ~ ' (' ~ daynames[d.weekday()] ~ ' ' ~ '%02d'|format(d.day) ~ ')' 72 ] %} 73 {% endfor %} 74 {{ l.values }} 75 76 later_names_with_date: |- 77 {% if later_events_with_date | count > 1 %} 78 {{ later_events_with_date[:-1] | join(', ') ~ ' et ' ~ later_events_with_date[-1] }} 79 {% else %} 80 {{ later_events_with_date | join('') }} 81 {% endif %} 82 83 response: 84 value: >- 85 {% if date_events | count > 0 %}{{ start_sentence }} {{ date_names }}.{% endif %} 86 {% if later_events_with_date %} Prochainement, ce sera {% if date_events | count <= 0 %}l'anniversaire{% else %}celui{% endif %} de {{ later_names_with_date }}.{% endif %} 87 88 - target: 89 entity_id: input_text.anniversaires_journee 90 data: 91 value: "{{ response.value }}" 92 action: input_text.set_value 93 94 - stop: returning value 95 response_variable: response
― Valentin LORTET