Tutorial - TEMPO tariff update (automatic PDF update)

Bonjour à tous,

Comme promis dans le tutoriel de @mutmut : (Merci à lui pour ce tuto)

Et suite à mon tutoriel qui permets de récupérer les tarifs hors tempo :

J’ai voulu voir pour automatiser la récupération des différents coûts.

Je suis donc tomber sur ce tutoriel Jeedom (Merci à son auteur) dont je me suis fortement inspiré, avec le copie du code node-red :

Le but ici est de récupérer le fichier pdf de la grille tarifaire tempo directement sur le site d’EDF pour mettre à jour les valeurs directement dans Gladys et avoir son coût réel au kWh à la minute prêt pour pouvoir l’utiliser dans d’autres scènes :slight_smile:

Le fichier est trouvable ici :
https://particulier.edf.fr/content/dam/2-Actifs/Documents/Offres/Grille_prix_Tarif_Bleu.pdf

Il faudra juste espérer que la structure/composition du fichier ne soit pas changé par EDF :grin:

Le résultat final :

image

Voici le flux Node-Red :
( Un catch all est présent pour récupérer les erreurs et les envoyer par email )

[
    {
        "id": "670838a9edef0758",
        "type": "catch",
        "z": "d6333988d9cfb817",
        "name": "Erreurs",
        "scope": null,
        "uncaught": false,
        "x": 110,
        "y": 60,
        "wires": [
            [
                "c097dad994334b5e"
            ]
        ]
    },
    {
        "id": "c097dad994334b5e",
        "type": "e-mail",
        "z": "d6333988d9cfb817",
        "server": "monserveuremail",
        "port": "465",
        "authtype": "BASIC",
        "saslformat": false,
        "token": "oauth2Response.access_token",
        "secure": true,
        "tls": true,
        "name": "monadresse@email.fr",
        "dname": "Mail",
        "x": 250,
        "y": 60,
        "wires": []
    },
    {
        "id": "bf708d78a507d9c3",
        "type": "http request",
        "z": "d6333988d9cfb817",
        "name": "",
        "method": "GET",
        "ret": "bin",
        "paytoqs": "ignore",
        "url": "https://particulier.edf.fr/content/dam/2-Actifs/Documents/Offres/Grille_prix_Tarif_Bleu.pdf",
        "tls": "",
        "persist": false,
        "proxy": "",
        "insecureHTTPParser": false,
        "authType": "",
        "senderr": false,
        "headers": [],
        "x": 430,
        "y": 140,
        "wires": [
            [
                "c6602cec6bea28ca"
            ]
        ]
    },
    {
        "id": "c6602cec6bea28ca",
        "type": "file",
        "z": "d6333988d9cfb817",
        "name": "",
        "filename": "/tmp/Grille_prix_Tarif_Bleu.pdf",
        "filenameType": "str",
        "appendNewline": true,
        "createDir": false,
        "overwriteFile": "true",
        "encoding": "none",
        "x": 290,
        "y": 200,
        "wires": [
            [
                "866bd16b214c1d1d"
            ]
        ]
    },
    {
        "id": "a0349e7017779b4a",
        "type": "change",
        "z": "d6333988d9cfb817",
        "name": "Texte",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "payload.text",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 670,
        "y": 200,
        "wires": [
            [
                "d23eb75a79c8996d"
            ]
        ]
    },
    {
        "id": "d23eb75a79c8996d",
        "type": "function",
        "z": "d6333988d9cfb817",
        "name": "Après \"Option Tempo\"",
        "func": "const regex = /Option Tempo((.|\\n)*)/gm;\nconst found = msg.payload.match(regex);\nmsg.payload = found[0];\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 480,
        "y": 280,
        "wires": [
            [
                "09faecd167fed230"
            ]
        ]
    },
    {
        "id": "09faecd167fed230",
        "type": "function",
        "z": "d6333988d9cfb817",
        "name": "6 kVA",
        "func": "const regex = /6 .*/gm; // puissance souscrite\nconst found = msg.payload.match(regex);\nconst searchRegExp = /,/g; // remplace les virgules par des points\nconst replaceWith = '.';\nmsg.payload = found[0];\nmsg.payload = msg.payload.replace(searchRegExp, replaceWith);\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 670,
        "y": 280,
        "wires": [
            [
                "e7ca8ee4d891c581"
            ]
        ]
    },
    {
        "id": "e7ca8ee4d891c581",
        "type": "split",
        "z": "d6333988d9cfb817",
        "name": "",
        "splt": " ",
        "spltType": "str",
        "arraySplt": 1,
        "arraySpltType": "len",
        "stream": false,
        "addname": "",
        "x": 510,
        "y": 360,
        "wires": [
            [
                "9f3df8ae0bd5d4bf"
            ]
        ]
    },
    {
        "id": "9f3df8ae0bd5d4bf",
        "type": "join",
        "z": "d6333988d9cfb817",
        "name": "",
        "mode": "custom",
        "build": "array",
        "property": "payload",
        "propertyType": "msg",
        "key": "topic",
        "joiner": "\\n",
        "joinerType": "str",
        "accumulate": false,
        "timeout": "",
        "count": "",
        "reduceRight": false,
        "reduceExp": "",
        "reduceInit": "",
        "reduceInitType": "num",
        "reduceFixup": "",
        "x": 650,
        "y": 360,
        "wires": [
            [
                "88eaf52f02be48c8"
            ]
        ]
    },
    {
        "id": "88eaf52f02be48c8",
        "type": "function",
        "z": "d6333988d9cfb817",
        "name": "JSON",
        "func": "msg.payload = {\n    \"puissance\": msg.payload[0],\n    \"prix_abonnement\": Number((Number(msg.payload[1]) * 12).toFixed(2)),\n    \"prix_HCJB\": Number(Number(msg.payload[2] / 100).toFixed(4)),\n    \"prix_HPJB\": Number(Number(msg.payload[3] / 100).toFixed(4)),\n    \"prix_HCJW\": Number(Number(msg.payload[4] / 100).toFixed(4)),\n    \"prix_HPJW\": Number(Number(msg.payload[5] / 100).toFixed(4)),\n    \"prix_HCJR\": Number(Number(msg.payload[6] / 100).toFixed(4)),\n    \"prix_HPJR\": Number(Number(msg.payload[7] / 100).toFixed(4)),\n}\nreturn msg;",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 790,
        "y": 360,
        "wires": [
            [
                "6e2b78e27c34c026",
                "4d19e36f157bd2c0",
                "50608e9e9bdecdc6",
                "d9fe4a5ac7c52d90",
                "b4a2b0d2177a9058",
                "c5f9959555f3e7b9"
            ]
        ]
    },
    {
        "id": "1316dbce795332d5",
        "type": "inject",
        "z": "d6333988d9cfb817",
        "name": "Tous les jours à 9h30",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "30 09 * * *",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 220,
        "y": 140,
        "wires": [
            [
                "bf708d78a507d9c3"
            ]
        ]
    },
    {
        "id": "866bd16b214c1d1d",
        "type": "pdfparse",
        "z": "d6333988d9cfb817",
        "name": "",
        "path": "/tmp/Grille_prix_Tarif_Bleu.pdf",
        "x": 520,
        "y": 200,
        "wires": [
            [
                "a0349e7017779b4a"
            ]
        ]
    },
    {
        "id": "6e2b78e27c34c026",
        "type": "function",
        "z": "d6333988d9cfb817",
        "name": "Jour bleu HC",
        "func": "msg.payload = msg.payload.prix_HCJB;\nmsg.payload = parseFloat(msg.payload);\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 960,
        "y": 300,
        "wires": [
            [
                "0a6e473635b8b423"
            ]
        ]
    },
    {
        "id": "4d19e36f157bd2c0",
        "type": "function",
        "z": "d6333988d9cfb817",
        "name": "Jour bleu HP",
        "func": "msg.payload = msg.payload.prix_HPJB;\nmsg.payload = parseFloat(msg.payload);\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 960,
        "y": 340,
        "wires": [
            [
                "69c1fb09c3ede0e0"
            ]
        ]
    },
    {
        "id": "50608e9e9bdecdc6",
        "type": "function",
        "z": "d6333988d9cfb817",
        "name": "Jour blanc HC",
        "func": "msg.payload = msg.payload.prix_HCJW;\nmsg.payload = parseFloat(msg.payload);\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 970,
        "y": 380,
        "wires": [
            [
                "467f6ce03f7ed6c0"
            ]
        ]
    },
    {
        "id": "d9fe4a5ac7c52d90",
        "type": "function",
        "z": "d6333988d9cfb817",
        "name": "Jour blanc HP",
        "func": "msg.payload = msg.payload.prix_HPJW;\nmsg.payload = parseFloat(msg.payload);\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 970,
        "y": 420,
        "wires": [
            [
                "ed628e4d71dbbc38"
            ]
        ]
    },
    {
        "id": "b4a2b0d2177a9058",
        "type": "function",
        "z": "d6333988d9cfb817",
        "name": "Jour rouge HC",
        "func": "msg.payload = msg.payload.prix_HCJR;\nmsg.payload = parseFloat(msg.payload);\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 970,
        "y": 460,
        "wires": [
            [
                "f2b3869570196614"
            ]
        ]
    },
    {
        "id": "c5f9959555f3e7b9",
        "type": "function",
        "z": "d6333988d9cfb817",
        "name": "Jour rouge HP",
        "func": "msg.payload = msg.payload.prix_HPJR;\nmsg.payload = parseFloat(msg.payload);\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 970,
        "y": 500,
        "wires": [
            [
                "ae1d548f6cb679aa"
            ]
        ]
    },
    {
        "id": "0a6e473635b8b423",
        "type": "mqtt out",
        "z": "d6333988d9cfb817",
        "name": "",
        "topic": "gladys/master/device/mqtt:edf/feature/mqtt:cout-abonnement-jour-bleu-hc/state",
        "qos": "2",
        "retain": "true",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "0ee77e0f90aa9681",
        "x": 1370,
        "y": 300,
        "wires": []
    },
    {
        "id": "69c1fb09c3ede0e0",
        "type": "mqtt out",
        "z": "d6333988d9cfb817",
        "name": "",
        "topic": "gladys/master/device/mqtt:edf/feature/mqtt:cout-abonnement-jour-bleu-hp/state",
        "qos": "2",
        "retain": "true",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "0ee77e0f90aa9681",
        "x": 1370,
        "y": 340,
        "wires": []
    },
    {
        "id": "467f6ce03f7ed6c0",
        "type": "mqtt out",
        "z": "d6333988d9cfb817",
        "name": "",
        "topic": "gladys/master/device/mqtt:edf/feature/mqtt:cout-abonnement-jour-blanc-hc/state",
        "qos": "2",
        "retain": "true",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "0ee77e0f90aa9681",
        "x": 1370,
        "y": 380,
        "wires": []
    },
    {
        "id": "ed628e4d71dbbc38",
        "type": "mqtt out",
        "z": "d6333988d9cfb817",
        "name": "",
        "topic": "gladys/master/device/mqtt:edf/feature/mqtt:cout-abonnement-jour-blanc-hp/state",
        "qos": "2",
        "retain": "true",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "0ee77e0f90aa9681",
        "x": 1370,
        "y": 420,
        "wires": []
    },
    {
        "id": "f2b3869570196614",
        "type": "mqtt out",
        "z": "d6333988d9cfb817",
        "name": "",
        "topic": "gladys/master/device/mqtt:edf/feature/mqtt:cout-abonnement-jour-rouge-hc/state",
        "qos": "2",
        "retain": "true",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "0ee77e0f90aa9681",
        "x": 1370,
        "y": 460,
        "wires": []
    },
    {
        "id": "ae1d548f6cb679aa",
        "type": "mqtt out",
        "z": "d6333988d9cfb817",
        "name": "",
        "topic": "gladys/master/device/mqtt:edf/feature/mqtt:cout-abonnement-jour-rouge-hp/state",
        "qos": "2",
        "retain": "true",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "0ee77e0f90aa9681",
        "x": 1370,
        "y": 500,
        "wires": []
    },
    {
        "id": "0ee77e0f90aa9681",
        "type": "mqtt-broker",
        "name": "GLADYS-MQTT",
        "broker": "mqtt://192.168.0.248",
        "port": "1883",
        "clientid": "",
        "autoConnect": true,
        "usetls": false,
        "protocolVersion": "4",
        "keepalive": "60",
        "cleansession": true,
        "autoUnsubscribe": true,
        "birthTopic": "",
        "birthQos": "0",
        "birthRetain": "false",
        "birthPayload": "",
        "birthMsg": {},
        "closeTopic": "",
        "closeQos": "0",
        "closeRetain": "false",
        "closePayload": "",
        "closeMsg": {},
        "willTopic": "",
        "willQos": "0",
        "willRetain": "false",
        "willPayload": "",
        "willMsg": {},
        "userProps": "",
        "sessionExpiry": ""
    }
]

Bien penser à modifier la puissance souscrite :slight_smile:

Pré-requis :

Installer le module node-red-contrib-pdfparse :

Voici l’appareil MQTT avec les fonctionnalités :





Voici les scènes :

Jour blanc - HC :





Jour blanc - HP :





Jour bleu - HC :





Jour bleu - HP :





Jour rouge - HC :





Jour rouge - HP :





Si vous avez des questions/remarques, n’hésitez pas à m’en faire part :slight_smile:

Changelog à venir :

  • Aucun (A vos commentaires :slight_smile: )
2 Likes

Great tutorial! Thanks @prohand!

1 Like

Well done @prohand, I didn’t have the courage to add all my screenshots for all the HCHP&JourQuiVaBien cases :wink:

Quick question about the triggers — why did you put one at 00:01? Doesn’t Gladys handle tracking from one day to the next?

EDIT: there’s a « Download CSV » paragraph dangling at the end of your flow (I removed it because I didn’t understand it) and for those who have a somewhat clean Node-RED like mine, I’d add that you need to install pdfparse:


Of course, don’t forget to configure your MQTT server (with login/password).

EDIT 2: @prohand which module did you use for mail?
I’m having installation issues :frowning:

2024-12-13T18:18:42.602Z Installer : node-red-node-email 3.0.2

2024-12-13T18:18:42.699Z npm install --no-audit --no-update-notifier --no-fund --save --save-prefix=~ --production --engine-strict node-red-node-email@3.0.2
2024-12-13T18:18:43.732Z [err] npm
2024-12-13T18:18:43.733Z [err]  WARN
2024-12-13T18:18:43.734Z [err]  config production Use `--omit=dev` instead.
2024-12-13T18:18:45.070Z [err] npm
2024-12-13T18:18:45.071Z [err]  ERR! code EBADENGINE
2024-12-13T18:18:45.075Z [err] npm
2024-12-13T18:18:45.076Z [err]  ERR!
2024-12-13T18:18:45.076Z [err]  engine Unsupported engine
2024-12-13T18:18:45.076Z [err] npm ERR!
2024-12-13T18:18:45.076Z [err]  
2024-12-13T18:18:45.076Z [err] engine Not compatible with your version of node/npm: node-red-node-email@3.0.2
2024-12-13T18:18:45.077Z [err] npm 
2024-12-13T18:18:45.077Z [err] ERR! notsup Not compatible with your version of node/npm: node-red-node-email@3.0.2
2024-12-13T18:18:45.077Z [err] npm ERR! notsup
2024-12-13T18:18:45.077Z [err]  Required: {"node":">=18.0.0"}
2024-12-13T18:18:45.077Z [err] npm 
2024-12-13T18:18:45.077Z [err] ERR! notsup Actual:   {"npm":"8.19.4","node":"v16.20.2"}
2024-12-13T18:18:45.082Z [err] 
2024-12-13T18:18:45.082Z [err] npm ERR!
2024-12-13T18:18:45.083Z [err]  A complete log of this run can be found in:
2024-12-13T18:18:45.083Z [err] npm ERR!     /data/.npm/_logs/2024-12-13T18_18_43_653Z-debug-0.log
2024-12-13T18:18:45.094Z rc=1

Indeed, 00:01 is a mistake because

2 Likes

Hello,
I just noticed that I had installed the same node-red-node-email module as you but I only have 3.0.2 and you have 2.2.1, and the problem is that it requires Node.js >18 for this module in the 3.x.x version :frowning:
How did you manage to get that specific version?

I second the Node.js update if that’s possible of course :wink:

Hello,

I probably downloaded it when it was the latest version, quite simply ^^
If you find a way to install an older version, I’d be interested :slight_smile:

I’m struggling: I tried in a Docker console to run npm i node-red-node-email@2.2.1
It says a new module is installed, others need updating, but nothing shows up in Node-RED, even after refreshing the web page.

So I wanted to disable the Node-RED integration from GLadys and enable it again.
Result: I lost everything (flows and installed modules) :cry:
Disabling removes the container but also all the data saved on the mounted volume (/volume1/docker/gladysassistant/node-red in my case on a Syno). What a bummer :grimacing:

I don’t know if this is normal behavior but it’s really stupid especially when you haven’t backed up that folder, @pierre-gilles can you tell us?

Ouch, yeah that’s beyond my skills ^^
That’s why I back up the entire VM every night on my end :slight_smile:
I also export my feeds to my email address just in case.
Good luck to you :neutral_face:

I’m not the developer of this integration but yes it’s the expected behavior :slight_smile:

A small warning message would be welcome though!

I created an issue :

1 Like

@prohand tomorrow (Feb 1) we should normally know whether the file keeps the same name and, at worst, we’ll have to change the link in Node-RED :wink:

Hello,

You’ll tell me because I’m not keeping time at the moment :wink:

EDIT : Everything below is a pretty big mistake on my part but I’ll leave it for the thread’s history.

Résumé

The URL unfortunately changed, here is the new one: https://particuliers.es.fr/sites/default/files/documents/2025.02.01_PART_TRV_Bleu_(INF456).pdf
And the table format too :frowning:
BEFORE :


NOW :

And since I’m terrible at Node-RED, the flow is out …
@prohand would you have a little time to modify the flow? Thanks in advance

Ah darn :frowning:

I won’t have time over the next few days, unfortunately.

Besides, there’s nothing very complicated — you can give the current code to ChatGPT and ask it to adapt it to the new one.

For the current Node-RED flow, that’s what I did :wink:

I made a mistake because I’m with ES and not EDF, I took the ES file… I’m analyzing and looking for the EDF file.

Hear ye, hear ye, I’m an idiot!

Aside from that, the file from EDF still has the same name and same URL since February 1, 2025 with its new rates and everything works perfectly without changing anything!

Thanks @prohand for the original flow :wink: and I won’t tap multiple times on my keyboard before checking five times anymore :sweat_smile:

EDIT: and for info, the TEMPO rates are now available as open data since January 30, 2025: https://www.data.gouv.fr/fr/datasets/historique-des-tarifs-reglementes-de-vente-delectricite-pour-les-consommateurs-residentiels/

2 Likes