Meilleure intégration Asana dans Home Assistant

Voilà un nouveau projet que je viens de finaliser avec l’aide de l’IA (comme dans un post précédent Retour d’expérience IA et Projet de reconnaissance de bruit de sonnette - Home Assistant - Tutoriels & Partages / Intégration - Home Assistant Communauté Francophone (hacf.fr)).

Là encore, un github existe peut-être déjà et offre un meilleur résultat mais je n’ai pas trouvé :sweat_smile:

Contexte
Je trouvais en effet que l’intégration officielle d’Asana pouvait être améliorée; en ce sens, mon projet était donc de faire en sorte qu’une modification portée sur Asana puisse être « automatiquement » synchronisée sur l’intégration To-do List de HA.

Ci-dessous la version Asana :


Ci-dessous la version To-do List :

Un peu compliqué à mettre en oeuvre au final, pas forcément dans une rigueur « algorithmique » (puisque que je vide les listes HA pour les re-remplir), mais ça me convient dans mon cas. :sweat_smile:
J’ai jugé la synchronisation dans l’autre sens trop complexe, car To-do list n’a pas autant de champs que Asana, il aurait fallu changer la logique.

Merci à @ddfdom qui m’a aidé pour l’https de n8n :wink:

Voici le tutoriel généré par l’IA avec mes ajustements.
NB : il y aura probablement pas mal d’ajustements nécessaires liés à la structuration de votre projet Asana (je vous conseille donc de procéder pas à pas) + à manipuler avec précaution car il y aura suppression de toutes vos anciennes tâches To-do List :

Prérequis

  • Home Assistant : Intégration To-do List installée.
  • n8n : Installé et accessible via votre navigateur en HTTPS. Edit : cette étape peut sans doute être simplifiée par l’envoi d’un cURL que je testerai ultérieurement
  • Asana : Compte actif avec un projet configuré.

Étape 1 : Dans l’intégration To-do List de Home Assistant
NB : de par le code actuel, vos tâches existantes seront toutes supprimées.
Définir une ou des listes cohérentes des noms que vous avez mis dans le code et par simplicité reprenant le même nom que vos sections Asana
image

Étape 2 : Obtenez la clé Asana en allant

  • Sous Asana dans vos Paramètres > Développer de nouvelles applications > Nouvelle Application > Token

Étape 3 : Configuration de n8n pour recevoir des webhooks d’Asana
Edit : cette étape peut sans doute être simplifiée par l’envoi d’un cURL que je testerai ultérieurement

  1. Créer un workflow dans n8n :
  • Accédez à votre instance n8n et créez un nouveau workflow.
  • Ajoutez un nœud « Asana Trigger » comme point de départ. Configurez-le en Production URL pour écouter les événements d’Asana. Notez l’URL générée par ce nœud.
    image
    image
  1. Ajouter un nœud HTTP Request dans n8n :
{
    "action": "{{$node["Asana Trigger"].json["action"]}}",
    "resource_type": "{{$node["Asana Trigger"].json["resource"]["resource_type"]}}",
    "resource_gid": "{{$node["Asana Trigger"].json["resource"]["gid"]}}",
    "user_gid": "{{$node["Asana Trigger"].json["user"]["gid"]}}",
    "change_field": "{{$node["Asana Trigger"].json["change"]["field"]}}",
    "change_action": "{{$node["Asana Trigger"].json["change"]["action"]}}"
}

Étape 2 : Configurer Home Assistant pour recevoir les données de n8n et des scripts pythons

  1. Définisser l’utilisation d’un webhook dans Home Assistant dans configuration.yaml:
webhook:
python_script:
  1. Configurer l’automatisation dans Home Assistant :
  • Utilisez le code suivant pour gérer les événements reçus (envoi d’une notification et lancement de la synchronisation) :
automation:
  - alias: "Asana Task Update"
    trigger:
      - platform: webhook
        webhook_id: asana_updated
    action:
      - variables:
          webhook_data: "{{ trigger.json if trigger.json is defined else trigger.data }}"
      - choose:
          - conditions:
              - condition: template
                value_template: "{{ webhook_data.action == 'deleted' and webhook_data.resource_type == 'task' }}"
            sequence:
              - action: notify.notify
                data:
                  message: "Une tâche Asana a été supprimée."
          - conditions:
              - condition: template
                value_template: "{{ webhook_data.action == 'added' and webhook_data.resource_type == 'task' }}"
            sequence:
              - action: notify.notify
                data:
                  message: "Une nouvelle tâche Asana a été ajoutée."
          - conditions:
              - condition: template
                value_template: "{{ webhook_data.action == 'changed' and webhook_data.resource_type == 'task' }}"
            sequence:
              - action: notify.notify
                data:
                  message: "Une tâche Asana a été modifiée."
        default:
          - action: notify.notify
            data:
              message: >
                Mise à jour Asana reçue : 
                Action: {{ webhook_data.action }}
                Type de ressource : {{ webhook_data.resource_type }}
                ID de la ressource : {{ webhook_data.resource_gid }}
                ID de l'utilisateur : {{ webhook_data.user_gid }}
                Champ modifié : {{ webhook_data.change_field }}
                Action de changement : {{ webhook_data.change_action }}
      - action: shell_command.sync_asana_tasks
      - action: system_log.write
        data:
          message: "Webhook Asana traité : {{ webhook_data | to_json }}"
          level: info
  1. Configurer le shell_command dans Home Assistant :
  • Ajoutez la commande suivante à votre fichier configuration.yaml :
shell_command:
  sync_asana_tasks: "python3 /config/python_scripts/script_sync_asana.py"

Étape 3 : Script Python pour synchroniser Asana vers Home Assistant
Voici le script Python que vous devrez adapter à votre projet Asana pour synchroniser les données d’Asana vers Home Assistant, incluant un token longue durée Home Assistant et la token Asana généré dans la console developer :

import requests

# Configuration Asana
ASANA_TOKEN = "votre_token_asana"
SECTIONS_GID = {
    "Liste à Faire": {"asana_gid": "1208212345857008", "ha_entity": "todo.liste_a_faire"},
    # Ajoutez d'autres sections ici si nécessaire...
}

# Configuration Home Assistant
HA_URL = "https://votre_url_home_assistant"
HA_TOKEN = "votre_token_ha"

def get_asana_tasks(section_gid):
    headers = {
        "Authorization": f"Bearer {ASANA_TOKEN}",
        "Accept": "application/json"
    }
    response = requests.get(
        f"https://app.asana.com/api/1.0/sections/{section_gid}/tasks?opt_fields=name,completed",
        headers=headers
    )
    if response.status_code != 200:
        print(f"Erreur lors de la récupération des tâches Asana : {response.status_code}")
        return []
    tasks = response.json()["data"]
    return [task for task in tasks if not task['completed']]

def get_ha_tasks(entity_id):
    headers = {
        "Authorization": f"Bearer {HA_TOKEN}",
        "Content-Type": "application/json"
    }
    
    response = requests.post(
        f"{HA_URL}/api/services/todo/get_items?return_response=true",
        headers=headers,
        json={"entity_id": entity_id}
    )
    
    if response.status_code == 200:
        return response.json().get('service_response', {}).get(entity_id, {}).get('items', [])
    else:
        print(f"Erreur lors de la récupération des tâches de Home Assistant : {response.status_code}")
        return []

def remove_ha_task(entity_id, task_name):
    headers = {
        "Authorization": f"Bearer {HA_TOKEN}",
        "Content-Type": "application/json"
    }
    
    response = requests.post(
        f"{HA_URL}/api/services/todo/remove_item",
        headers=headers,
        json={"entity_id": entity_id, "item": task_name}
    )
    
    if response.status_code != 200:
        print(f"Erreur lors de la suppression de la tâche '{task_name}'")
        
def add_task_to_ha(entity_id, task_name):
    headers = {
        "Authorization": f"Bearer {HA_TOKEN}",
        "Content-Type": "application/json"
    }
    
    response = requests.post(
        f"{HA_URL}/api/services/todo/add_item",
        headers=headers,
        json={"entity_id": entity_id, "item": task_name}
    )
    
    if response.status_code != 200:
        print(f"Erreur lors de l'ajout de la tâche '{task_name}'")

def sync_tasks():
    for section_name, section_info in SECTIONS_GID.items():
        asana_tasks = get_asana_tasks(section_info['asana_gid'])
        
        ha_tasks = get_ha_tasks(section_info['ha_entity'])
        
        for ha_task in ha_tasks:
            remove_ha_task(section_info['ha_entity'], ha_task['summary'])
        
        for asana_task in asana_tasks:
            add_task_to_ha(section_info['ha_entity'], asana_task['name'])

if __name__ == "__main__":
    sync_tasks()

ou "1208212345857008 " correspond au GID de votre section « Liste à Faire » qui est spécifié dans l’url : https://app.asana.com/api/1.0/projects/1208212345815532/sections
Exemple de contenu url :

{« gid »:« 1208212354251303 »,« name »:« Arts »,« resource_type »:« section »},{« gid »:« 1208212345857008 »,« name »:« Liste à Faire »,« resource_type »:« section »},{« gid »:« 1208212345857009 »,« name »:« Vendre »,« resource_type »:« section »},{« gid »:« 1208212345857010 »,« name »:« Idées Sorties »,« resource_type »:« section »},{« gid »:« 1208212345857013 »,« name »:« Liste des Invitations »,« resource_type »:« section »},{« gid »:« 1208212345857016 »,« name »:« Liste d’Achats Personnels »,« resource_type »:« section »},{« gid »:« 1208212345857017 »,« name »:« Idées Cadeaux »,« resource_type »:« section »},{« gid »:« 1208212345857018 »,« name »:« Liste des Projets Domotique et IA »,« resource_type »:« section »}

et où « 1208212345815532 » correspond au GID de votre projet disponible dans l’adresse URL de l’Asana de votre projet.

Faites les tests pour vérifier qu’une modification sous Asana :

  • déclenche l’évènement sous n8n
  • lance l’automatisation sous Home Assistant
  • reformate et reremplis la liste dans To-do list

Conclusion
Ce guide couvre les étapes essentielles pour intégrer Asana avec Home Assistant en utilisant n8n comme intermédiaire et un script Python pour la synchronisation des données. V

Post-Edit : je me rends compte à l’instant que l’étape de passer par n8n HTTPS peut être simplifié par l’exécution d’un webhook cURL (je testerai probablement cela plus tard) :

curl -X POST https://app.asana.com/api/1.0/webhooks \
  -H "Authorization: Bearer votre_token_personnel" \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "resource": "VOTRE_PROJECT_GID",
      "target": "https://votre-home-assistant.com/api/webhook/asana_webhook",
      "filters": [
        {
          "action": "changed",
          "resource_type": "task"
        },
        {
          "action": "added",
          "resource_type": "task"
        },
        {
          "action": "deleted",
          "resource_type": "task"
        }
      ]
    }
  }'