Publié il y a Mis à jour il y a JavaScript / Warp 1015 minutes de lecture (Environ 2285 mots)
Mon Linky dans Warp 10 avec un joli dashboard
Depuis longtemps, je cherchais un moyen simple de récupérer mes données de conso Linky et de les afficher dans un dashboard.
Pour accéder aux données du Linky collectées par Enedis, il n’y a pas 36 solutions :
Aller sur votre espace perso et cliquer sur un bouton pour télécharger un CSV avec ses données (pas pratique)
Utiliser une lib qui scrappe ce site pour récupérer ces données au format CSV. Sauf que depuis des mois, ils ont changé la façon de se loguer et ont ajouté un captcha. Il n’y a plus aucune lib de fonctionnelle (dommage, ça marchait bien)
Avoir un numéro de Siret, contractualiser avec Enedis, recueillir son propre consentement (oui, je sais, c’est con) et suivre une procédure très lourde pour se connecter à leur SGE.
Avoir un numéro de Siret, contractualiser avec Enedis, bâtir une app déclarée et se connecter sur leur DataHub.
Utiliser la connexion TéléInfo directement sur le Linky, ce qui fera l’objet d’un prochain post.
Bref, pour les particuliers qui, comme moi, veulent geeker un peu avec leurs propres données, c’est pas possible. Cependant, autour du DataHub, il y a quelques initiatives de tiers permettant de s’y connecter (via le tiers) comme :
C’est sur cet axe, à défaut d’avoir accès à une vraie API en direct, que je suis parti.
Attention, pensez à vous autoriser à collecter vos données horaires dans votre espace client!
Pour commencer, il vous faut un Warp 10. Facile à installer en local ou sur un serveur (il y a même un Docker), vous pouvez même tester dans le cloud avec leur SandBox. Ici, on va le faire avec la SandBox (mais dans la vraie vie, j’utilise une instance hébergée sur un Pi, oui, oui, ça tourne bien sur un Pi).
Préparation de Warp 10
Direction https://sandbox.senx.io/ pour se créer son espace et ses tokens. C’est pas compliqué, un clic suffit :
La puissance à portée d’un clic
Bon, ça nous laisse soit 2 jours (délai de rétention des données), soit 14 jours (délai des tokens mais il faut ré-uploader les données) pour jouer avec. (Plus d’info ici)
Copiez les 3 tokens quelque part et gardez les précieusement.
Récupération des données
Pour se faire, je vais m’appuyer sur cette librairie NodeJS : bokub/linky
Rien de bien sorcier, si ce n’est qu’on va devoir récupérer des jetons d’OAuth via https://conso.vercel.app/, un fameux tiers qui se connecte au DataHub Enedis. Suivez ce qui est écrit sur ce site, rien de bien sorcier. Vous aurez à autoriser cette app à se connecter à votre espace perso. Notez également quelque part, les jetons générés et gardez les au chaud.
Ouvrez un terminal, on va créer un nouveau projet javascript monstrueux :
1 2 3 4
$ mkdir linky2warp10 $ cd linky2warp10 $ npm init $ npm install @senx/warp10 dayjs linky node-cron
Créez un fichier conf.json dans lequel vous renseignerez les jetons Linky, votre n° de PDL (n° de compteur Linky, quoi, dispo dans votre espace client) et les tokens Warp 10:
Ensuite, il faut créer une session Linky avec la gestion du refresh token :
1 2 3 4 5 6 7 8 9 10 11 12 13 14
const session = new linky.Session({ accessToken: conf.accessToken, refreshToken: conf.refreshToken, usagePointId: conf.usagePointId, onTokenRefresh: (accessToken, refreshToken) => { fs.writeFileSync('conf.json-bak-' + dayjs().valueOf(), JSON.stringify(conf), 'utf-8') conf.accessToken = accessToken; conf.refreshToken = refreshToken; // Cette fonction sera appelée si les tokens sont renouvelés // Les tokens précédents ne seront plus valides // Il faudra utiliser ces nouveaux tokens à la prochaine création de session fs.writeFileSync('conf.json', JSON.stringify(conf), 'utf-8') }, });
Ensuite on va se coder la fonction de récupération de l’historique :
// à partir d'une date dans le passé et de step en step functionupdateHistory(startDate, step) { const now = dayjs().subtract(1, 'day'); returngetBetween2Dates(startDate, now.format('YYYY-MM-DD'), step) }
// entre 2 dates et de step en step functiongetBetween2Dates(startDate, endDate, step = 1) { let start = dayjs(startDate, 'YYYY-MM-DD'); let end = dayjs(endDate, 'YYYY-MM-DD'); returnnewPromise(async (resolve, reject) => { let sum = 0; // le nombre de données que l'on va récupérer // Zou! on boucle de step en step pour aller du début à la fin de la période temporelle while (end.isAfter(start) && !!session) { const next = end.subtract(step, 'day'); try { // On récupère notre courbe de charge qui offre un point toutes les 30 minutes const data = await session.getLoadCurve(next.format('YYYY-MM-DD'), end.format('YYYY-MM-DD')); let inputFormat = []; data.data.forEach(d => { // On converti au format Warp 10 const ts = dayjs(d.date, 'YYYY-MM-DD HH:mm:ss').valueOf() * 1000; if (d.value) { // Pensez à modifier subscribed avec votre puissance souscrite inputFormat.push(`${ts}// enedis.linky{unit=W,subscribed=9,pdl=${conf.usagePointId}} ${d.value}`); } }); // on pousse notre buffer dans Warp 10 await w10.update(conf.warp10.wt, inputFormat); sum += data.data.length; } catch (e) { console.error(start.format('YYYY-MM-DD'), e); } end = next; } resolve(sum); }); }
Parfait, maintenant on peut récupérer notre historique complet (ou presque) entre le premier Janvier 2015 et maintenant.
Il arrive que certains jours, Enedis n’ait pas collecté de données, (il y aura des 404 et pas des Peugeots) et si vous tapez trop loin dans le passé, vous aurez un 500 (de mémoire) pour vous dire qu’Enedis n’a pas collecté de données avant que vous ayez activé votre collecte de votre conso horaire, donc soyez raisonnable quant au choix dans la date (arf).
Une fois fini, vous avez un historique quasi complet. Mais il faut le faire tourner tous les jours :
1 2 3 4
cron.schedule('0 1 * * *', () => { // 1 h du mat tous les jours const start = dayjs().subtract(5, 'day'); // les 5 derniers jours updateHistory(start.format('YYYY-MM-DD'), 1).then(r =>console.log(r)); });
Y’a plus qu’à déployé ce script sur un Raspberry (ou ce que vous voulez), le lancer et il collectera quotidiennement vos données.
Afficher mes données
Pour ce faire, allez sur http://studio.senx.io/ et sélectionnez la Sandbox dans la liste des endpoints.
WarpStudio
Codons notre premier scripte pour afficher 30 jours d’historique. (remplacez bien les textes par vos infos ;) )
1 2 3
'votre token de lecture''token' STORE 'n° de PDL''pdl' STORE [ $token 'enedis.linky' { 'pdl' $pdl } NOW 30 d ] FETCH
Cliquez sur l’onglet ‘Dataviz’ et admirez en bavant votre courbe de charge exprimée en W :
Ma conso à moi
Déjà, c’est cool, mais j’en veux plus, je veux un vrai dashboard qui claque!
On va utiliser Discovery (décrit ici). D’abord, le squelette. Créez un fichier index.html :
1 2 3 4 5 6 7 8 9 10 11 12 13
<html> <head> <title>Linky dashboard</title> </head> <body> <discovery-dashboardurl="https://sandbox.senx.io/api/v0/exec"cols="12"cell-height="160"> // Le WarpScript va ici </discovery-dashboard> <!-- Les imports --> <scriptnomodulesrc="https://unpkg.com/@senx/discovery-widgets/dist/discovery/discovery.js"></script> <scripttype="module"src="https://unpkg.com/@senx/discovery-widgets/dist/discovery/discovery.esm.js"></script> </body> </html>
Et maintenant le code WarpScript :
Pour les tuiles (à ajouter dans tiles), on va récupérer souvent un an, un mois et 24 heures d’historique, la dernière conso et la dernière date de synchro. Plutôt que de faire un accès à la BDD à chaque tuile, on va mutualiser cette récupération et on va présenter l’info différemment par tuile.
1 2 3 4 5 6 7 8 9 10 11 12
'n° de PDL''pdl' STORE 'votre read token''token' STORE // on récupère 1 an qu'on met dans '1y' [ $token 'enedis.linky' { 'pdl' $pdl } NOW 365 d ] FETCH '1y' STORE // on coupe pour ne garder que 30 jours, stoké dans '30d' $1y NOW 30 d TIMECLIP '30d' STORE // on récupère la dernière date connue $30d 0 GET LASTTICK 'lasttick' STORE // la dernière valeur connue $30d $lasttick ATTICK 0 GET REVERSE 0 GET 'lastvalue' STORE // et les dernières 24 heures connues que l'on place dans '24h' $30d $lasttick 24 h TIMECLIP '24h' STORE
Ensuite la coquille du dashboard :
1 2 3 4 5 6 7 8
{ 'title''Linky' 'description''Personal electric consumption' 'options' { 'scheme''CHARTANA' } 'tiles' [ // ... les tuiles ici ] }
Enfin, les tuiles.
La moyenne annuelle
1 2 3 4 5 6 7 8
{ 'title''1 year average' 'x'2'y'0'w'2'h'1 'type''display''unit''kW' 'data' [ $1y bucketizer.mean 001 ] BUCKETIZE 0 GET 'gts' STORE // calcul de la moyenne // récupération de la valeur et conversion/arrondi en kW $gts VALUES 0 GET 1000.0 / 1000.0 * ROUND 1000.0 / }
La dernière conso connue
1 2 3 4 5 6
{ 'title''Last known consumption' 'x'4'y'0'w'2'h'1 'type''display''unit''kW' 'data' $lastvalue 1000.0 / 1000.0 * ROUND 1000.0 / // conversion/arrondi en kW }
{ 'title''Last 24h of data ' 'x'6'y'0'w'4'h'1 'type''bar' // On ne garde que le max horaire 'data' [ $24h bucketizer.max 01 h 0 ] BUCKETIZE }
Le Max sur les derniers 30 jours
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
{ 'title''Max 1 month' 'x'10'y'2'w'2'h'1 'type''display''unit''kW' 'data' [ $30d bucketizer.max 001 ] BUCKETIZE 0 GET 'gts' STORE // recherche du max // récupération de la valeur et conversion/arrondi en kW $gts VALUES 0 GET 1000.0 / 1000.0 * ROUND 1000.0 / 'data' STORE // récupération de la puissance souscrite (en kW) $gts LABELS 'subscribed' GET TODOUBLE 'subscribed' STORE { 'data' $data 'globalParams' { // Si votre conso est > à la puissance souscrite alors la tuile aura un fond rouge. // elle sera verte sinon 'bgColor' <% $data $subscribed <= %> <% '#32cb0099' %> <% '#ff616f99' %> IFTE 'fontColor''#ffffff' } } }
La max sur un an
1 2 3 4 5 6 7 8 9 10 11 12 13 14
{ 'title''Max in 1 year' 'x'10'y'1'w'2'h'1 'type''display''unit''kW' 'data' [ $1y bucketizer.max 001 ] BUCKETIZE 0 GET 'gts' STORE // récupération de la valeur et conversion/arrondi en kW $gts VALUES 0 GET 1000.0 / 1000.0 * ROUND 1000.0 / 'data' STORE $gts LABELS 'subscribed' GET 'subscribed' STORE { 'data' $data 'globalParams' { 'bgColor' <% $data $subscribed TODOUBLE <= %> <% '#32cb0099' %> <% '#ff616f99' %> IFTE 'fontColor''#ffffff' } } }
Le max sur 24 heures
1 2 3 4 5 6 7 8 9 10 11 12 13 14
{ 'title''Max in 24 h' 'x'10'y'0'w'2'h'1 'type''display''unit''kW' 'data' [ $24h bucketizer.max 001 ] BUCKETIZE 0 GET 'gts' STORE // récupération de la valeur et conversion/arrondi en kW $gts VALUES 0 GET 1000.0 / 1000.0 * ROUND 1000.0 / 'data' STORE $gts LABELS 'subscribed' GET 'subscribed' STORE { 'data' $data 'globalParams' { 'bgColor' <% $data $subscribed TODOUBLE <= %> <% '#32cb0099' %> <% '#ff616f99' %> IFTE 'fontColor''#ffffff' } } }
{ 'title''Last month consumption' 'x'0'y'1'w'5'h'2 'type''line' // on calcule le max par tranche de 3 heures 'data' [ $30d bucketizer.max NOW 3 h 0 ] BUCKETIZE 0 GET 'conso' STORE // on récupère la puissance souscrite $conso LABELS 'subscribed' GET TOLONG 1000 * 'subscribed' STORE // On récupère les bornes temporelles min et max $conso TICKLIST REVERSE 0 GET 'first' STORE $conso LASTTICK 'last' STORE // on se crée une courbe pour afficher une ligne correspondant à la puissance souscrite NEWGTS 'subscribed' RENAME 'psGTS' STORE // min $psGTS $first NaN NaN NaN $subscribed ADDVALUE DROP // max $psGTS $last NaN NaN NaN $subscribed ADDVALUE DROP // On met en forme avec des couleurs { 'data' [ $conso $psGTS ] 'params' [ { 'type''area' } { 'datasetColor''#ef5350' } ] } }
La consommation annuelle
Pareil que précédemment.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
{ 'title''Last year consumption' 'x'5'y'1'w'5'h'2 'type''line' 'data' [ $1y bucketizer.max NOW 1 d 0 ] BUCKETIZE 0 GET 'conso' STORE $conso $conso LABELS 'subscribed' GET TOLONG 1000 * 'subscribed' STORE $conso TICKLIST REVERSE 0 GET 'first' STORE $conso LASTTICK 'last' STORE NEWGTS 'subscribed' RENAME 'psGTS' STORE $psGTS $first NaN NaN NaN $subscribed ADDVALUE DROP $psGTS $last NaN NaN NaN $subscribed ADDVALUE 2 ->LIST 'data' STORE { 'data' [ $conso $psGTS ] 'params' [ { 'type''area' } { 'datasetColor''#ef5350' } ] } }
Le résultat
Et donc, en ajoutant un peu de CSS à notre page Web :