Pronote est une application génialissime de la société Index Education qui équipe une majorité des établissements scolaires.
Si vous ne connaissez pas, passez votre chemin, c’est que vous n’avez pas besoin de l’intégrer à votre domotique.
|
Attention, ce Retex est dépassé, en effet depuis Juin 2023, une intégration (merci vient Delphiki) s’occuper de tout ce qui est décrit ci-dessous. Certains liens ou fichiers de mon HA risque de ne plus correspondre avec la suite de ce texte. Je conseille évidemment d’utiliser l’intégration. Voici le dernier Retex : Je travaille sur les cartes HA de Pronote |
Cette intégration est basé sur le script de Dathosim (Merci beaucoup !!)
https://github.com/dathosim/Pronote2Homeassistant
J’ai modifié quelques lignes du code, soit pour adapter à mon utilisation soit pour adapter aux nouvelles normes de HA (platform: template/sensors: devenue template:/sensor par exemple)
Installation de la librairie PronotePy
Il s’agit d’une librairie python développé en open source : pronotepy
Pour installer cette librairie, lancer en ligne de commande :
pip install pronotepy
ou pour avoir la dernière version (déconseillé) :
pip install https://github.com/bain3/pronotepy/archive/refs/heads/master.zip
De temps en temps, il faut penser à lancer un petit coup de :
pip install –upgrade pronotepy
Installation du script python de Dathosim
Aller dans le dossier /config de votre HA et y créer un nouveau dossier qui peut s’appeler python_scripts et qui contiendra vos scripts personnalisés.
Dans ce dossier /config/python_scripts, y placer les fichiers pronote.py et config.ini de la distribution Pronote2Homeassistant.
Configurer le script Python
Il est nécessaire d’éditer le fichier config.ini et d’y ajouter les informations spécifiques.
Configuration spécifique Sigalou
J’ai rencontré des soucis dans l’identification avec l’ENT atrium_sud. Pour une raison que j’ignore, j’ai dû modifier le script Python. Je vais transmettre ces informations à Dathosim pour voir ce qu’il se passe. Je donne ici mes modifications, elles pourront être utiles. J’ai modifié la ligne 57.
# client = (pronotepy.ParentClient if type_compte == 'parent' else pronotepy.Client)(url, username, password, ent) client = (pronotepy.ParentClient if type_compte == 'parent' else pronotepy.Client)('https://0xx0xxxx.index-education.net/pronote/mobile.parent.html', username = 'xxxxx', password='yyyyy', ent=atrium_sud)
- Pour avoir l’URL, je suis allé sur pronote regarder l’url proposée lorsqu’on flash le QR code.
- Pour xxxx il faut mettre le nom d’utilisateur et yyyy le mot de passe.
Avec cela, le script fonctionne parfaitement. Si vous avez un peu plus de chance que moi, tout devrait bien fonctionner avec le script fourni en complétant le fichier de configuration avec vos identifiants.
Lancer le script
C’est là qu’il faut croiser les doigts pour vérifier que tout se passe bien.
Il faut se connecter en ligne de commande SSH, aller dans le dossier phyton_scripts précédemment créé avec un :
cd /config/python_scripts
cd /usr/share/hassio/homeassistant/python_scripts/ (chez Dathosim)
puis lancer le script avec :
python3 pronote.py
S’il ne se passe rien de spécial, pas de message d’erreur c’est que c’est OK, le script a bien déroulé.
Quand tout s’est bien passé, un fichier pronote_prenomenfant.json est créé dans /config/www
C’est ce fichier qui sera interrogé par HA pour récupérer les informations.
Automatiser le lancement du script
Il y a la méthode classique, probablement préférée des puristes Linux, c’est à dire d’ajouter un crontab
*/10 * * * * /usr/bin/python3 /usr/share/hassio/homeassistant/python_scripts/pronote.py > /tmp/pronote.log 2>&1
ou en fonction de votre système :
*/10 * * * * /config/python_scripts/pronote.py > /tmp/pronote.log 2>&1
Ce n’est pas la méthode que j’ai choisie, j’ai souhaiter utiliser à 100% Home Assistant et dans ma phase d’apprentissage, j’ai cherché comment lancer un cron et comment lancer un script Python.
Ajout d’une automatisation qui lance une commande shell
HA appelle cela un timer pattern
J’ajoute un fichier shell_command.yaml à mon fichier configuration.yaml
Dans ce fichier shell_command.yaml, j’ajoute :
update_pronote : python3 /config/python_scripts/pronote.py
Dans Automatisations, j’ajoute une nouvelle automatisation qui va lancer le script update_pronote
Le déclencheur est un déclenchement toutes les 10 minutes
et l’action est une commande shell qui lance update_pronote
En mode YAML, cela donne :
Toutes les 10 minutes, j’ai donc le fichier /config/www/pronote_enfant.json qui est généré.
Récupérer les données
C’est ensuite HA qui va lancer des requêtes REST pour aller chercher les données
Dathosim donne toutes les requêtes à lancer, elles sont sur :
https://github.com/dathosim/Pronote2Homeassistant/blob/main/configuration.yaml
Il faut bien sur remplacer:
- demo par le prénom de l’enfant
- 192.168.XX.XX par l’adresse ip de HA
J’ai modifié quelques syntaxes ou capteurs (les templates), cf mon fichier templates.yaml
Créer l’écran Pronote
La dernière étape consiste à afficher les sensors qui débutent par pronote… sur des cartes pour faire un bel écran.
Voici le code de l’écran de Dathosim : https://github.com/dathosim/Pronote2Homeassistant/blob/main/lovelace.yaml
Voici mon code :
type: horizontal-stack columns: 2 square: false cards: - type: vertical-stack cards: - type: markdown content: |- <div>Emploi du temps d'aujourd'hui </div> <table> <tbody> {%-for attr in states.sensor.pronote_edt_coralie_aujourdhui.attributes.edt_aujourdhui -%} <tr style="background-color:#FF0000"><td> {%- if state_attr('sensor.pronote_edt_coralie_aujourdhui', 'edt_aujourdhui')[loop.index-1]['annulation'] == false -%} <mark> {{state_attr('sensor.pronote_edt_coralie_aujourdhui', 'edt_aujourdhui')[loop.index-1]['heure']}} </mark> {%- else -%} <span> {{state_attr('sensor.pronote_edt_coralie_aujourdhui', 'edt_aujourdhui')[loop.index-1]['heure']}} </span> {% endif %}</td> <td>{{state_attr('sensor.pronote_edt_coralie_aujourdhui', 'edt_aujourdhui')[loop.index-1]['heure_fin']}}</td> <td> {{state_attr('sensor.pronote_edt_coralie_aujourdhui', 'edt_aujourdhui')[loop.index-1]['cours']}} {% if not state_attr('sensor.pronote_edt_coralie_aujourdhui', 'edt_aujourdhui')[loop.index-1]['status'] == None %}<span>{{state_attr('sensor.pronote_edt_coralie_aujourdhui', 'edt_aujourdhui')[loop.index-1]['status']}}</span> {% endif %}</td> <td>{{state_attr('sensor.pronote_edt_coralie_aujourdhui', 'edt_aujourdhui')[loop.index-1]['salle']}}</td> </tr> {% endfor %} </tbody> </table> card_mod: style: .: | ha-card ha-markdown { padding:0px } ha-card ha-markdown.no-header { padding:0px } ha-markdown$: | div { background-color:rgb(100, 100, 100); padding: 12px 12px; color:white; font-weight:normal; font-size:1.2em; border-top-left-radius: 5px; border-top-right-radius: 5px; } table{ border-collapse: collapse; font-size: 0.9em; font-family: Roboto; width: 100%; outline: 0px solid #393c3d; margin-top:5px; } caption { text-align: center; font-weight: bold; font-size: 1.2em; } td { padding: 5px 10px 5px 10px; text-align: left; border-bottom: 0px solid #1c2020; } tr { border-bottom: 0px solid #1c2020; } tr:nth-of-type(even) { background-color: rgb(54, 54, 54, 0.3); } tr:last-of-type { border-bottom: transparent; }* mark { background: #009767; color: #222627; border-radius: 5px; padding: 5px; } span { background: #EC4B34; color: #222627; border-radius: 5px; padding: 5px; } span { padding: 5px; } tr:nth-child(n+2) > td:nth-child(2) { text-align: left; } - type: markdown content: >- <div>Emploi du temps du : {{state_attr('sensor.pronote_edt_coralie_prochainjour', 'edt_prochainjour')[0]['date']}}</div> <table> <tbody> {%-for attr in states.sensor.pronote_edt_coralie_prochainjour.attributes.edt_prochainjour -%} <tr style="background-color:#FF0000"><td> {%- if state_attr('sensor.pronote_edt_coralie_prochainjour', 'edt_prochainjour')[loop.index-1]['annulation'] == false -%} <mark> {{state_attr('sensor.pronote_edt_coralie_prochainjour', 'edt_prochainjour')[loop.index-1]['heure']}} </mark> {%- else -%} <span> {{state_attr('sensor.pronote_edt_coralie_prochainjour', 'edt_prochainjour')[loop.index-1]['heure']}} </span> {% endif %}</td> <td>{{state_attr('sensor.pronote_edt_coralie_prochainjour', 'edt_prochainjour')[loop.index-1]['heure_fin']}}</td> <td> {{state_attr('sensor.pronote_edt_coralie_prochainjour', 'edt_prochainjour')[loop.index-1]['cours']}} {% if not state_attr('sensor.pronote_edt_coralie_prochainjour', 'edt_prochainjour')[loop.index-1]['status'] == None %}<span>{{state_attr('sensor.pronote_edt_coralie_prochainjour', 'edt_prochainjour')[loop.index-1]['status']}}</span> {% endif %}</td> <td>{{state_attr('sensor.pronote_edt_coralie_prochainjour', 'edt_prochainjour')[loop.index-1]['salle']}}</td> </tr> {% endfor %} </tbody> </table> card_mod: style: .: | ha-card ha-markdown { padding:0px } ha-card ha-markdown.no-header { padding:0px } ha-markdown$: | div { background-color:rgb(100, 100, 100); padding: 12px 12px; color:white; font-weight:normal; font-size:1.2em; border-top-left-radius: 5px; border-top-right-radius: 5px; } table{ border-collapse: collapse; font-size: 0.9em; font-family: Roboto; width: 100%; outline: 0px solid #393c3d; margin-top:5px; } caption { text-align: center; font-weight: bold; font-size: 1.2em; } td { padding: 5px 10px 5px 10px; text-align: left; border-bottom: 0px solid #1c2020; } tr { border-bottom: 0px solid #1c2020; } tr:nth-of-type(even) { background-color: rgb(54, 54, 54, 0.3); } tr:last-of-type { border-bottom: transparent; }* mark { background: #009767; color: #222627; border-radius: 5px; padding: 5px; } span { background: #EC4B34; color: #222627; border-radius: 5px; padding: 5px; } span { padding: 5px; } tr:nth-child(n+2) > td:nth-child(2) { text-align: left; } - type: markdown content: |- <div>Devoirs</div> <table> <tbody> {%-for attr in states.sensor.pronote_devoir_coralie.attributes.devoir -%} <tr> <td> {%- if state_attr('sensor.pronote_devoir_coralie', 'devoir')[loop.index-1]['done'] == true -%} <mark> {{state_attr('sensor.pronote_devoir_coralie', 'devoir')[loop.index-1]['date']}} </mark> {%- else -%} <span> {{state_attr('sensor.pronote_devoir_coralie', 'devoir')[loop.index-1]['date']}} </span> {% endif %}</td> <td>{{state_attr('sensor.pronote_devoir_coralie', 'devoir')[loop.index-1]['title']}}</td> <td>{{state_attr('sensor.pronote_devoir_coralie', 'devoir')[loop.index-1]['description']}}</td> </tr> {% endfor %} </tbody> </table> card_mod: style: .: | ha-card ha-markdown { padding:0px } ha-card ha-markdown.no-header { padding:0px } ha-markdown$: | h1 { font-weight: normal; font-size: 24px; } div { background-color:rgb(100, 100, 100); padding: 12px 12px; color:white; font-weight:normal; font-size:1.2em; border-top-left-radius: 5px; border-top-right-radius: 5px; } table{ border-collapse: collapse; font-size: 0.9em; font-family: Roboto; width: auto; outline: 0px solid #393c3d; margin-top: 10px; } caption { text-align: center; font-weight: bold; font-size: 1.2em; } td { padding: 5px 5px 5px 5px; text-align: left; border-bottom: 0px solid #1c2020; } tr { border-bottom: 0px solid #1c2020; } tr:nth-of-type(even) { background-color: rgb(54, 54, 54, 0.3); } tr:last-of-type { border-bottom: transparent; } mark { background: #009767; color: #222627; border-radius: 5px; padding: 5px; } span { background: #EC4B34; color: #222627; border-radius: 5px; padding: 5px; } span { padding: 5px; } tr:nth-child(n+2) > td:nth-child(2) { text-align: left; } - type: markdown content: |- <div>Evaluations</div> <table width='100%'> <tbody> {%-for attr in states.sensor.pronote_eval_coralie.attributes.evaluation -%} {% if loop.index < 40 %} <tr> <td width='10%'>{{attr['date_courte']}}</td> <td width='70%'>{{attr['eval']}}</td> <td width='20%'> {%for attr2 in attr.acquisitions-%}{% if attr2['acquisition_niveau'] == "A+" %} 💚 {% elif attr2['acquisition_niveau'] == "A" %} 🟢 {% elif attr2['acquisition_niveau'] == "B+" %} ⚪️+ {% elif attr2['acquisition_niveau'] == "B" %} ⚪️ {% elif attr2['acquisition_niveau'] == "C+" %} 🟡+ {% elif attr2['acquisition_niveau'] == "C" %} 🟡 {% elif attr2['acquisition_niveau'] == "D+" %} ⚪️+ {% elif attr2['acquisition_niveau'] == "D" %} ⚪️ {% elif attr2['acquisition_niveau'] == "E" %} 🔴 {% else %}{{ attr2['acquisition_niveau']}} {% endif %} {% endfor %} </td> </tr> {% endif %} {% endfor %} </tbody> </table> card_mod: style: .: | ha-card ha-markdown { padding:0px } ha-card ha-markdown.no-header { padding:0px } ha-markdown$: | h1 { font-weight: normal; font-size: 24px; } div { background-color:rgb(100, 100, 100); padding: 12px 12px; color:white; font-weight:normal; font-size:1.2em; border-top-left-radius: 5px; border-top-right-radius: 5px; } table{ border-collapse: collapse; font-size: 0.9em; font-family: Roboto; width: auto; outline: 0px solid #393c3d; margin-top: 10px; } caption { text-align: center; font-weight: bold; font-size: 1.2em; } td { padding: 5px 5px 5px 5px; text-align: left; border-bottom: 0px solid #1c2020; } tr { border-bottom: 0px solid #1c2020; } tr:nth-of-type(even) { background-color: rgb(54, 54, 54, 0.3); } tr:last-of-type { border-bottom: transparent; } mark { background: #009767; color: #222627; border-radius: 10px; padding: 5px; } span { background: #EC4B34; color: #222627; border-radius: 10px; padding: 5px; } span { padding: 5px; } tr:nth-child(n+2) > td:nth-child(2) { text-align: left; }
A noter :
J’ai modifier la couleur des points car la correspondance n’était pas bonne avec la configuration du pronote de l’établissement de ma fille. Pour le vert plus, j’ai mis un coeur vert.
Bonus
Ajout d’une carte de mise à jour
Dans l’objectif de diminuer la mise à jour, de la passer de toutes les 10 min à toutes les 4 heures, j’ai ajouté la carte de l’image ci-dessus.
Ainsi, on a le délai depuis la dernière mise à jour et si on veut une mise à jour immédiate, on appui dessus et on lance EXECUTER
Code de la carte :
- type: custom:auto-entities card: type: entities filter: include: - entity_id: automation.mise_a_jour_pronote options: secondary_info: last-triggered
Limiter la mise à jour aux jours ouvrables
Après avoir basculé de 10 min à 4 heures la tempo de mise à jour, j’ai cherché à limiter cette mise à jour aux jours ouvrables.
Il existe une intégration qui créé un binary_sensor qui nous donne cette information, il s’agit de Workday.
Je n’entre pas dans son installation, la doc est archi-simple.
J’ai juste ajouté ensuite dans l’automatisation une condition :
Voici le code complet de l’automatisation :
alias: Mise à jour Pronote description: "" trigger: - platform: time_pattern hours: /4 condition: - type: is_on condition: device device_id: 64bbc75616d4864596e1fd861c865904 entity_id: binary_sensor.workday domain: binary_sensor action: - service: shell_command.update_pronote data: {} mode: single
Edit : un nouveau tuto permet de ne déclencher la mise à jour Pronote que les jours ouvrables et hors vacances scolaires.
Lier Pronote et le déclenchement du reveil Alexa
Ce sera la prochaine étape
Mais je programmerai cela un dimanche pour avoir une heure de début des cours demain