Notification créneau de vaccination disponible (doctolib)

A partir du 12 mai, il sera possible pour tout le monde de se faire vacciner si un créneau est disponible le lendemain. Voici ce que j’ai mis en place afin d’être alerté des créneaux disponibles d’un centre de vaccination sur Doctolib.

Génération de l’url contenant les données

Comme exemple, je vais utiliser le centre de vaccination Paris La Défense Arena https://www.doctolib.fr/centre-de-sante/nanterre/centre-de-vaccination-covid-pld-arena.

Sur cette page, il faut ouvrir l’outil de développement web du navigateur et se rendre sur l’onglet réseau. Ensuite en sélectionnant un motif de consultation sur la page, un appel http va être effectué et il faudra récupérer son url commençant par https://www.doctolib.fr/availabilities.json :

Dans l’exemple, l’url sera https://www.doctolib.fr/availabilities.json?start_date=2021-05-08&visit_motive_ids=2821077&agenda_ids=466712-466714-466715-466718-466722-466725-466713-466708-466717-466710-467838-466721-466724-466720&insurance_sector=public&practice_ids=185169&destroy_temporary=true&limit=4

Création du sensor

A partir de cette URL, on peut créer un sensor rest, qui va se mettre à jour toutes les minutes :

sensor:
  - platform: rest
    resource_template: >-
      {{
        'https://www.doctolib.fr/availabilities.json?'
        ~ 'start_date=' ~ now().strftime("%Y-%m-%d")
        ~ '&visit_motive_ids=2821077'
        ~ '&agenda_ids=466712-466714-466715-466718-466722-466725-466713-466708-466717-466710-467838-466721-466724-466720'
        ~ '&practice_ids=185169'
        ~ '&insurance_sector=public'
        ~ '&destroy_temporary=true'
        ~ '&limit=7'
      }}
    method: GET
    name: Vaccination available slots Arena
    value_template: "{{ value_json.total }}"
    scan_interval: 60
    timeout: 10
    headers:
      User-Agent: "Mozilla/5.0 Gecko/20100101 Firefox/88.0"
    unit_of_measurement: appointments
    json_attributes:
      - next_slot
      - availabilities
      - reason
      - message
  • Il faut remplacer les paramètres visit_motive_ids, agenda_ids et practice_ids par les valeurs présentes dans l’url récupérée plus haut.
  • Le paramètre start_date correspond à la date à partir de laquelle effectuer la recherche.
  • Le paramètre limit correspond au nombre de jour à rechercher. (La valeur minimum est 3 et maximale 20, je crois).

Ce sensor va remonter le nombre de créneau disponible sur la période configurée et dans ses attributs les créneaux disponibles par jour.

Alerte sur les créneaux disponibles du lendemain

Le sensor créé va remonter le nombre de créneau disponible sur une durée d’au moins 3 jours mais ce qui m’intéresse ce sont uniquement ceux du lendemain.

Ajout d’un binary sensor

Afin de créer une alerte, il faut commencer par définir un binary sensor basé sur un template permettant de vérifier si des créneaux sont disponibles sur la période souhaitée. Un attribut days est ajouté sur cette entité permettant de lister ces créneaux par jour (jour même et lendemain).

Depuis la version 2021.5 :

template:
  - binary_sensor:
    - name: Appointment available Arena
      state: >-
        {%- set days = state_attr('sensor.vaccination_available_slots_arena', 'availabilities') -%}
        {%- set max_date = (now() + timedelta(days=2)).replace(hour=0, minute=0, second=0, microsecond=0)|as_timestamp -%}
        {%- set ns = namespace() -%}
        {%- set ns.slots = [] -%}
        {%- for day in days if day.slots|length > 0 -%}
          {%- set ns.slots =
            ns.slots +
            day.slots|map(attribute='start_date')|map('as_timestamp')|select('<', max_date)|list
          -%}
        {%- endfor -%}
        {{ ns.slots|length > 0 }}
      attributes:
        days: >-
          {%- set days = state_attr('sensor.vaccination_available_slots_arena', 'availabilities') -%}
          {%- set max_date = (now() + timedelta(days=2)).replace(hour=0, minute=0, second=0, microsecond=0)|as_timestamp -%}
          {%- set ns = namespace() -%}
          {%- set ns.days = [] -%}
          {%- for day in days if day.slots|length > 0 -%}
            {%- set slots = day.slots
                  |map(attribute='start_date')
                  |map('as_timestamp')
                  |select('<', max_date)
                  |map('timestamp_custom', '%H:%M', true)
                  |list
            -%}
            {%- if slots|length > 0 -%}
              {%- set ns.days = ns.days + [{
                "date": day.date,
                "slots": slots
              }] -%}
            {%- endif -%}
          {%- endfor -%}
          {{ ns.days }}

Versions antérieures à 2021.5 : https://forum.hacf.fr/t/notification-creneau-de-vaccination-disponible-doctolib/4595/20?u=j5lien

Ajout d’une alerte

Pour finir, en créant une alerte, on pourra être notifié lorsque le binary sensor passe à on.

alert:
  appointment_available_arena:
    name: Rendez-vous vaccination disponible
    entity_id: binary_sensor.appointment_available_arena
    state: "on"
    repeat:
      - 5
      - 30
      - 60
    skip_first: false
    can_acknowledge: true
    notifiers:
      - mobile_app_***
    message: >-
      {%- set days = state_attr('binary_sensor.appointment_available_arena', 'days') -%}
      {%- for day in days -%}
      {{ day.date }} :
      {%- for slot in day.slots %}
        - {{ slot }}
      {%- endfor -%}
      {%- endfor -%}

Lovelace

Afin d’afficher l’ensemble des rendez-vous disponibles, on peut utiliser une carte de type markdown avec ce code:

{%- set days = state_attr('sensor.vaccination_available_slots_arena', 'availabilities') -%}
**Rendez-vous: {{ states('sensor.vaccination_available_slots_arena') }}**
{% for day in days if day.slots|length > 0 %}
  #### {{ day.date }} :
  {% for slot in day.slots %}
  * {{ slot.start_date|as_timestamp|timestamp_custom('%H:%M', true)  }}
  {%- endfor %}

{% endfor %}

image

10 « J'aime »

Merci pour ce tuto absolument génial :slight_smile:

Tout semble bien fonctionner pour moi, sauf la carte lovelace en Markdown qui reste vide. (j’ai bien vérifié, le sensor est bien provisionné avec 11 rdvs listés).
Peux-tu partager le code complet de cette carte en mode « code editor » stp ? (j’ai forcément raté un truc tout bête, c’est ma première carte Markdown…)

1 « J'aime »

Felicitations pour ce tutoriel

Au niveau de l’alert, {{ slot.place }} semble vide

Aurais-je manqué quelque chose ?

Merci pour partage.
J’ai quelques remarques tout le même sur le principe dans le sens où à ce jour la réservations des places disponibles au lendemain sera ouverte uniquement à partir de 16h. Ce qui fait qu’un sensor qui envoi une requête de mise à jour h24 toutes les heures est trop brut à mon sens. Un Automation qui commence à 15h45 et se termine dans la soirée serait plus ‹ optimisé ›.

En effet, {{ slot.place }} n’existe pas, j’ai une alerte un peu plus complexe chez car je fusionne plusieurs centres et je n’ai pas modifié cela dans le post. Le message doit plus correspondre à cela :

    message: >-
      {%- set days = state_attr('binary_sensor.appointment_available_arena', 'days') -%}
      {%- for day in days -%}
      {{ day.date }} :
      {%- for slot in day.slots %}
        - {{ slot }}
      {%- endfor -%}
      {%- endfor -%}

Je suis d’accord que ce n’est pas optimisé, et il envoie une requête toutes minutes :wink: . Après ça permet d’être notifié des créneaux qui se libèrent en cours de journée et j’espère que ce sera temporaire comme sensor.

1 « J'aime »

Voici le code complet de la carte:

type: markdown
title: U Arena
content: >
  {%- set days = state_attr('sensor.vaccination_available_slots_arena', 'availabilities')
  -%} **Rendez-vous: {{ states('sensor.vaccination_available_slots_arena') }}**

  {% for day in days if day.slots|length > 0 %}
    #### {{ day.date }} :
    {% for slot in day.slots %}
    * {{ slot.start_date|as_timestamp|timestamp_custom('%H:%M', true)  }}
    {%- endfor %}

  {% endfor %}
1 « J'aime »

J’espère que ça ne blacklistera pas les requêtes de HA.

En utilisant le code complet de ta carte Lovelace, ça fonctionne. Merci

Salut @j5lien , merci pour ce tuto.
Pour ma part, HA me retourne l’erreur suivante dès la création du sensor
Template variable error: 'value_json' is undefined when rendering '{{ value_json.total }}'

Pour information, ça ne devrait pas avoir d’impact avec l’erreur ci-dessus, d’après ce que je voie dans les données brut du JSON issue de Doctolib de mon centre, ce n’est pas le même formattage que celui de ton exemple. Voici le retour du JSON de mon centre (j’ai limité à 3 jours pour l’affichage ici) :

{
  "total": 0,
  "availabilities": [
    {
      "date": "2021-05-10",
      "slots": []
    },
    {
      "date": "2021-05-11",
      "slots": []
    },
    {
      "date": "2021-05-12",
      "slots": []
    }
  ],
  "next_slot": "2021-05-26"
}

Aussi, j’ai testé pour mon centre, le fichier JSON n’est pas pointé directement par https://www.doctolib.fr mais par https://partners.doctolib.fr. Mais ça ne change rien, puis que si je remplace l’un par l’autre, les valeur du JSON son identique.

Hello @Tank, ton retour est correct. Les données sont formatées ainsi quand il n’y a pas de créneau sur la période demandée mais qu’ils en existent en dehors (visible au niveau du next_slot).

Par contre c’est étrange que le value_json ne fonctionne pas, c’est censé être une variable automatique qui parse les données d’entrée au format json : Templating - Home Assistant

Est-ce que tu peux partager la configuration de ton sensor ?

Voici mon sensor issue du fichier sensors.yaml :

# Vaccination Doctolib
  - platform: rest
    resource_template: >-
      {{
        'https://www.doctolib.fr/availabilities.json?'
        ~ 'start_date=' ~ states('sensor.date')
        ~ '&visit_motive_ids=2622608'
        ~ '&agenda_ids=424738'
        ~ '&practice_ids=170990'
        ~ '&insurance_sector=public'
        ~ '&destroy_temporary=true'
        ~ '&limit=7'
      }}
    method: GET
    name: Vaccination available slots Arena
    value_template: "{{ value_json.total }}"
    scan_interval: 60
    timeout: 10
    unit_of_measurement: appointments
    json_attributes:
      - next_slot
      - availabilities
      - reason
      - message

J’ai simplement remplacé les différents éléments comme tu l’avais indiqué plus haut.
D’ailleurs, j’ai le même problème un faisant un simple copier/coller du sensor de ton tuto, sans rien changer.

Est-ce que tu as bien ajouté l’entité sensor.date ?

sensor:
  - platform: time_date
    display_options:
      - 'date'

Un message a été scindé en un nouveau sujet : Alert vs Automation

Ahh, ça change tout… Ca fonctionne ainsi ! Mais qu’est-ce qui t’a fait dire à partir de l’erreur que c’était cette entité qui n’était pas présente ?

Je pensais que c’était de base dans HA et que ce sensor n’était que si on voulait afficher des éléments (date/heure) en particulier.

EDIT : désormais j’ai une erreur en mettant le template dans le fichier configuration, qui me dit :

Invalid config for [template]: [binary_sensor] is an invalid option for [template]. Check: template->binary_sensor

Certainement un problème d’inclusion ou d’indentation, partage ton code…

Voici mon code, mais il n’a rien de différent de celui de j5lien, ce n’est qu’un copier/coller

# Vaccination Doctolib
alert:
  appointment_available_arena:
    name: Rendez-vous vaccination disponible
    entity_id: binary_sensor.appointment_available_arena
    state: "on"
    repeat:
      - 5
      - 30
      - 60
    skip_first: false
    can_acknowledge: true
    notifiers:
      - mobile_app_***
    message: >-
      {%- set days = state_attr('binary_sensor.appointment_available_arena', 'days') -%}
      {%- for day in days -%}
      {{ day.date }} :
      {%- for slot in day.slots %}
        - {{ slot }}
      {%- endfor -%}
      {%- endfor -%}

template:
  - binary_sensor:
    - name: Appointment available Arena
      state: >-
        {%- set days = state_attr('sensor.vaccination_available_slots_arena', 'availabilities') -%}
        {%- set max_date = (now() + timedelta(days=2)).replace(hour=0, minute=0, second=0, microsecond=0)|as_timestamp -%}
        {%- set ns = namespace() -%}
        {%- set ns.slots = [] -%}
        {%- for day in days if day.slots|length > 0 -%}
          {%- set ns.slots =
            ns.slots +
            day.slots|map(attribute='start_date')|map('as_timestamp')|select('<', max_date)|list
          -%}
        {%- endfor -%}
        {{ ns.slots|length > 0 }}
      attributes:
        days: >-
          {%- set days = state_attr('sensor.vaccination_available_slots_arena', 'availabilities') -%}
          {%- set max_date = (now() + timedelta(days=2)).replace(hour=0, minute=0, second=0, microsecond=0)|as_timestamp -%}
          {%- set ns = namespace() -%}
          {%- set ns.days = [] -%}
          {%- for day in days if day.slots|length > 0 -%}
            {%- set slots = day.slots
                  |map(attribute='start_date')
                  |map('as_timestamp')
                  |select('<', max_date)
                  |map('timestamp_custom', '%H:%M', true)
                  |list
            -%}
            {%- if slots|length > 0 -%}
              {%- set ns.days = ns.days + [{
                "date": day.date,
                "slots": slots
              }] -%}
            {%- endif -%}
          {%- endfor -%}
          {{ ns.days }}

La ligne en erreur correspond à celle où il est écrit template:

D’ailleurs, il n’y a rien à activer / installer pour se servir des templates ? Je demande ça, vu que j’étais passé à côté du sensor.date

Tu n’as pas la dernière version de HA ! J’ai eu la même erreur jusqu’à ce que je mette la dernière version de HA

Cette phrase :

Car dans le code, seule l’entité sensor.date est pré-requis.

@j5lien, peut être remplacer sensor.date par now().strftime("%Y-%m-%d") pour ne plus avoir de dépendance ?

ce qui donne :

# Vaccination Doctolib
  - platform: rest
    resource_template: >-
      {{
        'https://www.doctolib.fr/availabilities.json?'
        ~ 'start_date=' ~ now().strftime("%Y-%m-%d")
        ~ '&visit_motive_ids=2622608'
        ~ '&agenda_ids=424738'
        ~ '&practice_ids=170990'
        ~ '&insurance_sector=public'
        ~ '&destroy_temporary=true'
        ~ '&limit=7'
      }}
    method: GET
    name: Vaccination available slots Arena - temp
    value_template: "{{ value_json.total }}"
    scan_interval: 60
    timeout: 10
    unit_of_measurement: appointments
    json_attributes:
      - next_slot
      - availabilities
      - reason
      - message

Comme le dit @JournaldeThomas , il faut l’une des dernière version pour cela ( 2021.4 je crois).
Si tu es en ancienne, tu peux l’écrire ainsi :

binary_sensor:
  - platform: template
    sensors:
      appointment_available_arena:
        friendly_name: "Appointment available Arena"
        value_template: >-
          {%- set days = state_attr('sensor.vaccination_available_slots_arena', 'availabilities') -%}
          {%- set max_date = (now() + timedelta(days=2)).replace(hour=0, minute=0, second=0, microsecond=0)|as_timestamp -%}
          {%- set ns = namespace() -%}
          {%- set ns.slots = [] -%}
          {%- for day in days if day.slots|length > 0 -%}
            {%- set ns.slots =
              ns.slots +
              day.slots|map(attribute='start_date')|map('as_timestamp')|select('<', max_date)|list
            -%}
          {%- endfor -%}
          {{ ns.slots|length > 0 }}
        attribute_templates:
          days: >-
            {%- set days = state_attr('sensor.vaccination_available_slots_arena', 'availabilities') -%}
            {%- set max_date = (now() + timedelta(days=2)).replace(hour=0, minute=0, second=0, microsecond=0)|as_timestamp -%}
            {%- set ns = namespace() -%}
            {%- set ns.days = [] -%}
            {%- for day in days if day.slots|length > 0 -%}
              {%- set slots = day.slots
                    |map(attribute='start_date')
                    |map('as_timestamp')
                    |select('<', max_date)
                    |map('timestamp_custom', '%H:%M', true)
                    |list
              -%}
              {%- if slots|length > 0 -%}
                {%- set ns.days = ns.days + [{
                  "date": day.date,
                  "slots": slots
                }] -%}
              {%- endif -%}
            {%- endfor -%}
            {{ ns.days }}

En effet, j’essaye d’éviter d’utiliser now() pour une date car ça a tendance à régénérer le template trop souvent mais ça ne s’applique pas pour un rest sensor.

Ça a en effet été ajouté dans la 2021.5.

Si t’as une erreur à ce niveau là, ça signifie que l’appel http a bien été effectué mais que HA n’a pas réussi à parser le résultat au format json et donc la varialbe value_json n’existe pas. Le soucis provenait surement de l’url, j’ai donc testé l’appel avec une date invalide et en effet une ça génère une erreur.

Toutes les minutes dixit la documentation → Templating - Home Assistant

Mais oui, dans le cas d’un rest, avec un scan_interval à 60… :wink: