✅ Carte Timer

licence

Tous est fait de façon native (sans carte personnalisée/custom-card) .

Même si vous ne souhaitez pas utiliser ce genre de carte, je vous conseille de lire tranquillement ce tutoriel pour y approfondir vos connaissances :man_teacher:

Mon approche utilise l’utilisation d’un timer, d’entités personnalisées, de plusieurs automations et d’un script python.

Si vous ne souhaitez pas la lire (vous passerez à côté des explications), rendez-vous directement au Résumé pour obtenir les codes à incorporer à votre installation.

timer

Niveau requis

  • Débutant / Intermédiaire / Avancé

Contexte de l’aide

Parce que le but de la communauté est de s’entraider, voici comment créer une carte sur votre tableau de bord représentant un minuteur demandé par un utilisateur :

Création des entités

Tout d’abord, il faut créer un timer (ici, il sera d’une heure une minute et quarante cinq secondes):

#Définition des timers
timer:
  timer_1h_temps:
    duration: '01:01:45'

Pour pouvoir afficher le temps restant du timer, il va falloir passer par des entités personnalisées :

Pourquoi ? Car nativement seule la carte entités permet d’afficher le temps restant :
timer

Il va donc falloir créer une entité qui représentera le temps restant.

Pour la créer, je me suis basé sur les états et attributs de l’entité timer.timer_1h_temps.

Son état peut prendre les valeurs idle (non démarré), paused (en pause) ou active (démarré). Aucun de ces états ne permet de récupérer le temps restant.

Passons donc au attributs :

Il y en a plusieurs, duration (temp du timer), editable (edition possible).

Et c’est tout ? Non ! Pour voir apparaître d’autre attributs, il faut lancer le timer :

Les attributs remaining (temps restant du timer qui se met à jour seulement quand le timer est en pause) et finishes_at (horaire de fin du timer qui apparait uniquement lorsque le timer est démarré) apparaissent.

On va donc utiliser l’attribut finishes_at qui indique l’horaire de fin du timer pour créer notre entité personnalisée :

sensor:
  - platform: template
    sensors:
      #Temps restant du timer timer.timer_1h_temps suivant l'attribut 'finishes_at' de l'entité 'timer.timer_1h_temps'
      #l'attribut 'finishes_at' n'est présent dans les attributs que lorsque le timer est démarré et represente l'horaire de fin du timer
      timer_1h_temps_restant_int:
        friendly_name: 'Timer 1h : Temps restant'
        value_template: >
          {% if is_state("timer.timer_1h_temps", "active") %}
            {{ ( as_timestamp(state_attr('timer.timer_1h_temps', 'finishes_at')) | int - (now().timestamp()) | int ) | timestamp_custom('%H:%M:%S', False) }}
          {% elif is_state("timer.timer_1h_temps", "paused") %}
            En pause
          {% else %}
             Non démarré
          {% endif %}
        attribute_templates:
          icon: 'mdi:clock-fast' 

Explication de la formule dans la balise value_template

Il y a 3 possibilités d’état de l’entité à l’aide de l’utilisation de if/elif/else :

1. Quand le timer est démarré

{% if is_state("timer.timer_1h_temps", "active") %}

alors l’entité prend la valeur :

( as_timestamp(state_attr('timer.timer_1h_temps', 'finishes_at')) | int - (now().timestamp()) | int ) | timestamp_custom('%H:%M:%S', False)

Décortiquons :

as_timestamp(state_attr('timer.timer_1h_temps', 'finishes_at')) |int

Cette formule récupère l’attribut ‹ finishes_at › de l’entité timer.timer_1h_temps state_attr('timer.timer_1h_temps', 'finishes_at') que l’on transforme en timestamp à l’aide de la fonction as_timestamp() et où l’on applique le format int pour faire des calcul.

(now().timestamp()) | int

Cette formule récupère le timestamp() de l’heure actuelle que l’on convertie en integer afin de pourvoir utiliser les fonctions de calcul

Ensuite on soustrait ces deux formules :

as_timestamp(state_attr('timer.timer_1h_temps', 'finishes_at')) | int - (now().timestamp()) | int

ce qui peux donner comme résultat 3694

:face_with_raised_eyebrow: On va le rendre plus compréhensible en englobant la formule par :

timestamp_custom('%H:%M:%S', False)

ce qui donne au final la formule plus haut :

( as_timestamp(state_attr('timer.timer_1h_temps', 'finishes_at')) | int - (now().timestamp()) | int ) | timestamp_custom('%H:%M:%S', False)

ce qui donne comme résultat : 01:01:34

:heart_eyes: Beaucoup plus compréhensible.

2. Quand le timer est en pause

{% elif is_state("timer.timer_1h_temps", "paused") %}

alors l’entité prend la valeur En pause

3. Quand le timer n’est pas démarré ni en pause

{% else %}

alors l’entité prend la valeur Non démarré

Mise à jour de l’entité

:bangbang: Malheureusement, l’entité que nous venons de créer ne se met à jour que sur un changement d’état de l’entité timer.timer_1h_temps.

Donc on aura une mise à jour du temps restant seulement lorsque l’on démarre le timer, le met en pause ou l’arrête. Pour avoir une mise à jour toutes les secondes, on va passer par une automatisation (automation).

Mise à jour de l’entité " timer.timer_1h_temps_restant_int "

L’automation suivante permet de forcer la mise à jour de l’entité :

automation:
  - alias: timer_1h_mise_a_jour_pour_les_secondes
    #Mise à jour de l'entité sensor.timer_1h_temps_restant toutes les secondes
    id: 'timer_1h_mise_a_jour_pour_les_secondes'
    trigger:
      platform: time_pattern
      seconds: "/1"
    condition:
    action:
      - service: homeassistant.update_entity
        entity_id: sensor.timer_1h_temps_restant_int

Mais comme il n’est pas nécessaire de mettre à jour l’entité si le timer n’est pas démarré, on va optimiser tout ça par une seconde automation qui l’activera/désactivera dès qu’un changement d’état de l’entité est détecté :

automation:
  - alias: 'activation_desactivation_automation_timer_1h_mise_a_jour_pour_les_secondes'
    #Permet d'activer la mise à jour de l'entité sensor.timer_1h_temps_restant
    trigger:
      platform: state
      entity_id: timer.timer_1h_temps
    condition:
    action:
      - choose:
          - conditions:
            #Le timer n'est pas démarré
              - condition: state
                entity_id: timer.timer_1h_temps
                state: "idle"
            sequence:
              - service: automation.turn_off
                entity_id: automation.timer_1h_mise_a_jour_pour_les_secondes

          - conditions:
            #Le timer est démarré
              - condition: state
                entity_id: timer.timer_1h_temps
                state: "active"
            sequence:
              - service: automation.turn_on
                entity_id: automation.timer_1h_mise_a_jour_pour_les_secondes
          - conditions:
            #Le timer est en pause
              - condition: state
                entity_id: timer.timer_1h_temps
                state: "paused"
            sequence:
              - service: automation.turn_off
                entity_id: automation.timer_1h_mise_a_jour_pour_les_secondes
        default:
          - service: automation.turn_off
            entity_id: automation.timer_1h_mise_a_jour_pour_les_secondes

L’utilisation de la balise choose est semblable à l’utilisation des if/elif/else.
La mise à jour toute les secondes est activée quand l’état correspond à active.

Où en est-on ?

Si nous affichons l’entité sensor.timer_1h_temps_restant_int dans Lovelace, on verra bien le timer mis à jour toutes les secondes.

:partying_face:

Mais … :unamused:

Lorsque l’on met en pause le timer, l’attribut finishes_at n’est plus disponible, et donc, la valeur de l’entité devient En pause, alors qu’il serait plus intéressant de garder le nombre d’heures/minutes/secondes restantes.

Afficher le temps restant (même en cas de pause du timer)

Pour réussir cela, nous allons créer une nouvelle fois une entité, mais nous n’allons pas conditionner sa mise à jour sur un changement d’état d’une quelconque entité mais la faire nous même à l’aide d’un script python.

Utilisation des scripts pythons

Pour pouvoir appeler le service python_script.nom_du_fichier_python, il faut ajouter ceci dans votre fichier configuration.yaml :

# configuration.yaml
python_script:

Et il faut placer tous vos scripts python dans le dossier <config>/python_scripts/<config> correspond au dossier où se trouve votre fichier configuration.yaml

Le code contenu dans notre fichier ecrire_etat_entite.py :

# ecrire_etat_entite.py

  #--------------------------------------------------------------------------------------------------
  # Force l'écriture de l'état d'une entité (ses attributs ne sont pas modifiés)
  #--------------------------------------------------------------------------------------------------
#Récupération de l'entité à écrire
inputEntity = data.get('entity_id')

#Récupératon de la valeur à écrire
inputState = data.get('state')

#Chargement de l'entité à son état actuel
inputStateObject = hass.states.get(inputEntity)

#On recopie les attributs
inputAttributesObject = inputStateObject.attributes.copy()

#Ecriture de la nouvelle valeur de l'entité avec conservation des attributs
hass.states.set(inputEntity, inputState, inputAttributesObject)

Et pour l’appeler, il suffit de connaitre le nom du fichier (ici : ecrire_etat_entite.py) :

- service: python_script.ecrire_etat_entite
  data:
    entity_id: nom_de_l_entite
    state: valeur_à_inscrire_pour_l_etat

On crée donc une entité personnelle qui n’a aucune valeur car nous la fixerons nous même à l’aide de l’appel du service python_script :

sensor:
  - platform: template
    sensors:
      #Temps restant du timer timer.timer_1h_temps mis à jour par le script python
      timer_1h_temps_restant:
        friendly_name: 'Timer 1h : Temps restant'
        value_template: ""
        attribute_templates:
          icon: 'mdi:clock-fast'

et on rajoute à l’automation plus haut l’utilisation du script python :

automation:
  - alias: timer_1h_mise_a_jour_pour_les_secondes
    #Mise à jour de l'entité sensor.timer_1h_temps_restant toutes les secondes
    id: 'timer_1h_mise_a_jour_pour_les_secondes'
    trigger:
      platform: time_pattern
      seconds: "/1"
    condition:
    action:
      - service: homeassistant.update_entity
        entity_id: sensor.timer_1h_temps_restant_int
      - service: python_script.ecrire_etat_entite
        data:
          entity_id: 'sensor.timer_1h_temps_restant'
          state: "{{states('sensor.timer_1h_temps_restant_int')}}"

Maintenant, l’affichage dans Lovelace est presque parfait car nous gardons bien l’affichage du temps restant même quand le timer est en pause.

:expressionless: « il a mis presque dans sa phrase ».

Vous suivez bien ! :+1:

Effectivement, j’ai mis presque car si on arrête le timer, la valeur affichée dans Lovelace reste le temps restant jusqu’au prochain lancement du timer, alors que je trouve plus naturel d’avoir le temps initial du timer.

On va y remédier, en ajoutant une fois de plus l’appel du script python mais quand le timer prend l’état idle, c’est à dire quand il est arrêter, dans l’automation plus haut :

- service: python_script.ecrire_etat_entite
  data:
    entity_id: 'sensor.timer_1h_temps_restant'
    state: "0{{state_attr('timer.timer_1h_temps', 'duration')}}"  

On met donc la valeur de l’attribut duration. le 0 permet juste d’avoir le format 01:01:45 au lieu de seulement 1:01:45 (la valeur de l’attribut duration).

Conclusion

On a donc :

  • Un fichier python : ecrire_etat_entite.py (qui doit se trouver dans le dossier <config>/python_scripts :

    Code (cliquez pour afficher)
    # ecrire_etat_entite.py
    
    #--------------------------------------------------------------------------------------------------
    # Force l'écriture de l'état d'une entité (ses attributs ne sont pas modifiés)
    #--------------------------------------------------------------------------------------------------
    #Récupération de l'entité à écrire
    inputEntity = data.get('entity_id')
    
    #Récupératon de la valeur à écrire
    inputState = data.get('state')
    
    #Chargement de l'entité à son état actuel
    inputStateObject = hass.states.get(inputEntity)
    
    #On recopie les attributs
    inputAttributesObject = inputStateObject.attributes.copy()
    
    #Ecriture de la nouvelle valeur de l'entité avec conservation des attributs
    hass.states.set(inputEntity, inputState, inputAttributesObject)
    
  • Un fichier hacf_timer.yaml (si vous utilisez la méthode packages) :

    Code (cliquez pour afficher)
    #Définition des timers
    timer:
      timer_1h_temps:
        duration: '01:01:45'
    
    sensor:
      - platform: template
        sensors: 
          #Capteur dont l'état est vide (utilisé dans la carte picture elements pour le titre de la carte)
          vide:
              value_template: "" 
    
      - platform: template
        sensors:
          #Temps restant du timer timer.timer_1h_temps mis à jour par le script python
          timer_1h_temps_restant:
            friendly_name: 'Timer 1h : Temps restant'
            value_template: ""
            attribute_templates:
              icon: 'mdi:clock-fast'
    
      - platform: template
        sensors:
          #Temps restant du timer timer.timer_1h_temps suivant l'attribut 'finishes_at' de l'entité 'timer.timer_1h_temps'
          #l'attribut 'finishes_at' n'est présent dans les attributs que lorsque le timer est démarré et represente l'horaire de fin du timer
          timer_1h_temps_restant_int:
            friendly_name: 'Timer 1h : Temps restant'
            value_template: >
              {% if is_state("timer.timer_1h_temps", "active") %}
                {{ ( as_timestamp(state_attr('timer.timer_1h_temps', 'finishes_at')) | int - (now().timestamp()) | int ) | timestamp_custom('%H:%M:%S', False) }}
              {% elif is_state("timer.timer_1h_temps", "paused") %}
                En pause
              {% else %}
                 Non démarré
              {% endif %}
            attribute_templates:
              icon: 'mdi:clock-fast'
    
    automation:
      - alias: 'activation_desactivation_automation_timer_1h_mise_a_jour_pour_les_secondes'
        #Permet d'activer la mise à jour de l'entité sensor.timer_1h_temps_restant
        trigger:
          platform: state
          entity_id: timer.timer_1h_temps
        condition:
        action:
          - choose:
              - conditions:
                #Le timer n'est pas démarré
                  - condition: state
                    entity_id: timer.timer_1h_temps
                    state: "idle"
                sequence:
                  - service: automation.turn_off
                    entity_id: automation.timer_1h_mise_a_jour_pour_les_secondes
                  - service: python_script.ecrire_etat_entite
                    data:
                      entity_id: 'sensor.timer_1h_temps_restant'
                      state: "0{{state_attr('timer.timer_1h_temps', 'duration')}}"  
    
              - conditions:
                #Le timer est démarré
                  - condition: state
                    entity_id: timer.timer_1h_temps
                    state: "active"
                sequence:
                  - service: automation.turn_on
                    entity_id: automation.timer_1h_mise_a_jour_pour_les_secondes
              - conditions:
                #Le timer est en pause
                  - condition: state
                    entity_id: timer.timer_1h_temps
                    state: "paused"
                sequence:
                  - service: automation.turn_off
                    entity_id: automation.timer_1h_mise_a_jour_pour_les_secondes
            default:
              - service: automation.turn_off
                entity_id: automation.timer_1h_mise_a_jour_pour_les_secondes
    
      - alias: timer_1h_mise_a_jour_pour_les_secondes
        #Mise à jour de l'entité sensor.timer_1h_temps_restant toutes les secondes
        id: 'timer_1h_mise_a_jour_pour_les_secondes'
        trigger:
          platform: time_pattern
          seconds: "/1"
        condition:
        action:
          - service: homeassistant.update_entity
            entity_id: sensor.timer_1h_temps_restant_int
          - service: python_script.ecrire_etat_entite
            data:
              entity_id: 'sensor.timer_1h_temps_restant'
              state: "{{states('sensor.timer_1h_temps_restant_int')}}"
    
  • Une carte Lovelace :

    Code (cliquez pour afficher)
    elements:
      - entity: sensor.vide
        prefix: Timer Couloir
        style:
          color: var(--primary-color)
          font-size: 20px
          font-variant: small-caps
          left: 17%
          pointer-events: none
          top: 20%
        tap_action:
          action: none
        type: state-label
      - entity: sensor.timer_1h_temps_restant
        style:
          color: var(--primary-color)
          font-size: 200%
          font-variant: small-caps
          left: 50%
          pointer-events: none
          top: 40%
        tap_action:
          action: none
        type: state-label
      - type: conditional
        conditions:
          - entity: timer.timer_1h_temps
            state: idle
        elements:
          - entity: timer.timer_1h_temps
            icon: 'mdi:play'
            style:
              color: var(--primary-color)
              '--paper-item-icon-active-color': var(--primary-color)
              font-size: 20px
              font-variant: small-caps
              left: 50%
              bottom: 0%
            tap_action:
              action: call-service
              service: timer.start
              service_data:
                entity_id: timer.timer_1h_temps
            type: state-icon
      - type: conditional
        conditions:
          - entity: timer.timer_1h_temps
            state_not: idle
        elements:
          - entity: timer.timer_1h_temps
            icon: 'mdi:pause'
            style:
              color: var(--primary-color)
              '--paper-item-icon-active-color': var(--primary-color)
              font-size: 20px
              font-variant: small-caps
              left: 20%
              bottom: 0%
            tap_action:
              action: call-service
              service: timer.pause
              service_data:
                entity_id: timer.timer_1h_temps
            type: state-icon
          - entity: timer.timer_1h_temps
            icon: 'mdi:close'
            style:
              color: var(--primary-color)
              '--paper-item-icon-active-color': red
              font-size: 20px
              font-variant: small-caps
              left: 40%
              bottom: 0%
            tap_action:
              action: call-service
              service: timer.cancel
              service_data:
                entity_id: timer.timer_1h_temps
            type: state-icon
          - entity: timer.timer_1h_temps
            icon: 'mdi:checkbox-marked-circle-outline'
            style:
              color: var(--primary-color)
              '--paper-item-icon-active-color': green
              font-size: 20px
              font-variant: small-caps
              left: 60%
              bottom: 0%
            tap_action:
              action: call-service
              service: timer.finish
              service_data:
                entity_id: timer.timer_1h_temps
            type: state-icon
          - entity: timer.timer_1h_temps
            icon: 'mdi:play'
            style:
              color: var(--primary-color)
              '--paper-item-icon-active-color': var(--primary-color)
              font-size: 20px
              font-variant: small-caps
              left: 80%
              bottom: 0%
            tap_action:
              action: call-service
              service: timer.start
              service_data:
                entity_id: timer.timer_1h_temps
            type: state-icon
    image: /local/images/transparent/transparent.png
    type: picture-elements
    

L’image transparente est ici [transparent ] (entre les crochets)

La carte est fonctionnelle malgré quelques latences parfois :

Dues au fait que le déclenchement ne se fait pas réellement toutes les secondes mais à quelques centièmes de secondes près), mais au final, sur mon test d1h01m45s, l’affichage est fonctionnel et le timer s’arrête correctement sans une seconde d’erreur :+1: .

timer

Une question, un problème

Besoin d'aide ? Cliquez ici !

Suivi des modifications

  • 16/01/2022: correction appel icone au début du tuto @pascal_ha
  • 01/02/2021: Passage en article officiel (:hacf_tuto: ) @Clemalex
  • 19/10/2020 : Création @Clemalex
5 « J'aime »