Pronote is a brilliant application of the company Index Education which equips a majority of schools.

If you do not know, go your way, you do not need to integrate it into your home automation.
|
Be careful, this Retex is outdated, indeed since June 2023, a integration (thank you comes Delphiki) take care of everything described below. Some links or files in my HA may no longer match the rest of this text. Of course, I would recommend using integration. Here is the latest Retex: I'm working on Pronote's HA cards. |
This integration is based on the Dathosim script (Thank you very much!!)
https://github.com/dathosim/Pronote2Homeassistant
I modified a few lines of the code, either to adapt to my use or to adapt to the new HA standards (platform: template/sensors: become template:/sensor for example)
Installation of the PronotePy library
It is a python library developed in open source: pronotepy
To install this library, run on the command line:
pip install pronotepy
or to have the latest version (not recommended):
pip install https://github.com/bain3/pronotepy/archive/refs/heads/master.zip
From time to time, you have to think about throwing a little shot of:
pip install –upgrade pronotepy
Installation of the Dathosim python script
Go to the /config folder of your HA and create a new folder that can be called python _scripts and that will contain your custom scripts.

In this /config/python_scripts folder, place the files pronote.py and config.ini of distribution Pronote2Homeassistant.

Configure Python script
It is necessary to edit the file config.ini and add specific information.
Specific configuration Sigalou
I had problems identifying with the ENT atrium _sud. For some reason, I had to change the Python script. I will pass this information on to Dathosim to see what happens. I give here my modifications, they may be useful. I have amended line 57.
# client = (pronotepy.ParentClient if type _account == 'parent' else pronotepy.Client)(url, username, password, ent) client = (pronotepy.ParentClient if type _account == 'parent' else pronotepy.Client)('https://0xx0xxxx.index-education.net/pronote/mobile.parent.html', username = 'xxxxx', password='yyyyy', ent=atrium _sud)
- To get the URL, I went on pronote to look at the proposed URL when flashing the QR code.
- For xxxx you have to enter the username and yyyy the password.
With this, the script works perfectly. If you are a little more fortunate than me, everything should work well with the script provided by completing the configuration file with your credentials.
Launch the script
This is where you have to cross your fingers to make sure everything is going well.
You must connect to the SSH command line, go to the phyton_scripts folder previously created with one of the following:
cd /config/python_scripts
cd /usr/share/hassio/homeassistant/python_scripts/ (in Dathosim)
then run the script with:
python3 pronote.py
If nothing special happens, no error message is that it is OK, the script went well.

When everything went well, a file pronote_prenomenfant.json is created in /config/www
It is this file that will be queried by HA to retrieve the information.
Automate script launch
There is the classic method, probably preferred by Linux purists, i.e. adding a crontab
*/10 * * * /usr/bin/python3 /usr/share/hassio/homeassistant/python_scripts/pronote.py > /tmp/pronote.log 2>&1
or depending on your system:
*/10 * * * * /config/python_scripts/pronote.py > /tmp/pronote.log 2>&1
This is not the method I have chosen, I would like to use 100% Home Assistant and in my learning phase, I looked at how to launch a cron and how to launch a Python script.
Adding an automation that launches a shell command
HA calls this a timer pattern
I add a _command.yaml shell file to my configuration.yaml file
![]()
In this _command.yaml shell file, I add:
update_pronote: python3 /config/python_scripts/pronote.py
In Automatisations, I add a new automation that will launch the script update_pronote

Trigger is a trigger every 10 minutes

and the action is a shell command that launches update_pronote

In YAML mode, this gives:

Every 10 minutes, I have the file /config/www/pronote_enfant.json generated.
Recover data
It is then HA who will launch REST queries to fetch the data
Dathosim gives all the requests to launch, they are on:
https://github.com/dathosim/Pronote2Homeassistant/blob/main/configuration.yaml
It is of course necessary to replace:
- demo by the child’s first name
- 192.168.XX.XX by the IP address of HA
I have modified some syntaxes or sensors (the templates), see my file templates.yaml
Create Pronote screen
The last step is to display the sensors that start with pronote … on maps to make a beautiful screen.
Here is the code of the Dathosim screen: https://github.com/dathosim/Pronote2Homeassistant/blob/main/lovelace.yaml
Here is my code:
Type: horizontal-stack columns: 2 square: false cards: - type: vertical-stack cards: - type: markdown content: | -
<div>Today's schedule </div>
<table>
<tbody>
{%-for attr in states.sensor.pronote_edt_coralie_today.attributes.edt_today -%}
<tr style="background-color:#FF0000"><td>
{%- if state_attr('sensor.pronote_edt_coralie_today', 'edt_today')[loop.index-1]['cancellation'] == false -%}
<mark>
{{state _attr('sensor.pronote_edt_coralie_today', 'edt_today')[loop.index-1]['hour']}}
</mark>
{%- else -%}
<span>
{{state _attr('sensor.pronote_edt_coralie_today', 'edt_today')[loop.index-1]['hour']}}
</span>
{% endif %}</td>
<td>{{state _attr('sensor.pronote_edt_coralie_today', 'edt_today')[loop.index-1]['_fin time']}}</td>
<td>
{{state _attr('sensor.pronote_edt_coralie_today', 'edt_today')[loop.index-1]['course']}} {% if not state_attr('sensor.pronote_edt_coralie_today', 'edt_today')[loop.index-1]['status'] == None %}<span>{{state _attr('sensor.pronote_edt_coralie_today', 'edt_today')[loop.index-1]['status']}}</span>
{% endif %}</td>
<td>{{state _attr('sensor.pronote_edt_coralie_today', 'edt_today')[loop.index-1]['room']}}</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>Time use of: {{state _attr('sensor.pronote_edt_coralie_nextday', 'edt_nextday')[0]['date']}}</div>
<table>
<tbody>
{%-for attr in states.sensor.pronote_edt_coralie_nextday.attributes.edt_nextday -%}
<tr style="background-color:#FF0000"><td>
{%- if state_attr('sensor.pronote_edt_coralie_nextday', 'edt_nextday')[loop.index-1]['cancellation'] == false -%}
<mark>
{{state _attr('sensor.pronote_edt_coralie_nextday', 'edt_nextday')[loop.index-1]['hour']}}
</mark>
{%- else -%}
<span>
{{state _attr('sensor.pronote_edt_coralie_nextday', 'edt_nextday')[loop.index-1]['hour']}}
</span>
{% endif %}</td>
<td>{{state _attr('sensor.pronote_edt_coralie_nextday', 'edt_nextday')[loop.index-1]['_fin time']}}</td>
<td>
{{state _attr('sensor.pronote_edt_coralie_nextday', 'edt_nextday')[loop.index-1]['course']}} {% if not state_attr('sensor.pronote_edt_coralie_nextday', 'edt_nextday')[loop.index-1]['status'] == None %}<span>{{state _attr('sensor.pronote_edt_coralie_nextday', 'edt_nextday')[loop.index-1]['status']}}</span>
{% endif %}</td>
<td>{{state _attr('sensor.pronote_edt_coralie_nextday', 'edt_nextday')[loop.index-1]['room']}}</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>Duties</div>
<table>
<tbody>
{%-for attr in states.sensor.pronote_devoir_coralie.attributes.devoir -%}
<tr>
<td>
{%- if state_attr('sensor.pronote_devoir_coralie', 'duty')[loop.index-1]['done'] == true -%}
<mark>
{{state _attr('sensor.pronote_devoir _coralie', 'duty')[loop.index-1]['date']}}
</mark>
{%- else -%}
<span>
{{state _attr('sensor.pronote_devoir _coralie', 'duty')[loop.index-1]['date']}}
</span>
{% endif %}</td>
<td>{{state _attr('sensor.pronote_devoir _coralie', 'duty')[loop.index-1]['title']}}</td>
<td>{{state _attr('sensor.pronote_devoir _coralie', 'duty')[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 _level'] == "A+" %} 💚 {% elif attr2['acquisition _level'] == "A" %} 🟢 {% elif attr2['acquisition _level'] == "B+" %} ⚪ + {% elif attr2['acquisition _level'] == "B" %} ⚪ {% elif attr2['acquisition _level'] == "C+" %} 🟡+ {% elif attr2['acquisition _level'] == "C" %} 🟡 {% elif attr2['acquisition _level'] == "D+" %} ⚪ + {% elif attr2['acquisition _level'] == "D" %} ⚪ {% elif attr2['acquisition _level'] == "E" %} 🔴 {% else %}{{ attr2['acquisition _level']}} {% 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; }

Please note:
I changed the colour of the dots because the correspondence was not good with the configuration of the pronote of my daughter’s establishment. For green plus, I put a green heart.
Bonus
Adding an update map

In order to reduce the update from every 10 minutes to every 4 hours, I have added the map in the image above.
Thus, we have the delay since the last update and if we want an immediate update, we press it and start EXECUTION

Card code:
- type: custom:auto-entities card: Type: entities filter: include: - entity_id: automation.mise _a _jour _pronote options: secondary_info: last-triggered
Limit the update to working days
After switching the update tempo from 10 minutes to 4 hours, I tried to limit this update to working days.
There is an integration that creates a binary _sensor that gives us this information: Workday.
I do not enter its installation, the doc is archi-simple.
I just added a condition in automation:

Here is the full automation code:
alias: Updated 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 : one new tutorial allows you to trigger the Pronote update only on working days and off school holidays.
Linking Pronote and triggering the Alexa alarm clock
This will be the next step
But I'll schedule this on a Sunday to have a class start time tomorrow


