HA-Routeur Solaire

Intro.

Dans un article précédent, j’abordais le sujet des routeurs solaires, ces systèmes qui redirige le surplus de production de votre installation photovoltaïque vers généralement un ballon eau chaude sanitaire.

Depuis plusieurs années, j’utilise le modèle P’TIWATT qui remplit parfaitement son role mais qui malheureusement n’offre pas la possibilité d’envoyer des informations de monitoring vers HA par exemple.

Après quelques recherches, j’ai découvert, il y a quelques mois, un routeur à monter soit même chez https://pvrouteur.apper-solaire.org/ , la particularité étant que le routeur, l’élément qui mesure la puissance est séparé du triac, l’élément qui fait varier la puissance aux bornes des résistances du ballon d’ECS, ce que j’avais trouvé sur le coup intéressant. Après quelques essais j’ai été convaincu par la solution mais je suis resté avec mon « P’Titwatt » le temps de la réflexion.

Présentation du PVROUTEUR de Cyril.

La mise en oeuvre est très bien expliquée dans la documentation. Le flashage initiale des ESP peut se faire depuis une interface WEB ou pour les plus aguerris, vous pouvez personnaliser et télécharger le code avec Visual Studio Code.

Cyril a fait un énorme boulot, il propose les circuits imprimés du routeur et du dimmer à petit prix, par le biais de son association APPER, donnant par la même occasion la possibilité d’en déduire une partie de vos impots.

Vous pouvez « chainer » plusieurs dimmers, soit en parallèle, soit en cascade, quand le premier ballon est chaud, vous basculer vers le second.

La configuration du dimmer se fait depuis une page WEB.

Enfin, la dernière version de son dimmer communique avec HA via MQTT nativement (aucune déclaration .yaml) à écrire dans HA.

Ma solution.

Dernièrement, toujours aussi frustré d’avoir un routeur seul dans son coin sans remontée d’information, j’ai ré-ouvert le dossier du « pvrouter » et opté pour une solution mixte.

Mon installation PV est équipée de panneaux PV et de micro onduleur ainsi qu’un onduleur chargeur Victron Multiplus 2, je récupère donc très facilement la puissance injectée ou soutirée sur le réseau en interrogeant cycliquement l’onduleur via le protocole Modbus. Il ne me restait plus qu’a piloter le dimmer en fonction de cette puissance en lui envoyant trame URL « htpp://IP_dimmer/?POWER=xx » avec XX de 0 à 100%.

La partie matériel:

Pour le dimmer j’utilise un module ESP12F précablé avec alimentation en 220 VCA et un module relais. J’ai décrit ce produit dans un article précédent.

C’est compact et facile à raccorder, cependant il est indispensable de réaffecter les broches définies par défaut dans le code d’origine en les adaptant à la carte. Pour cela il faut modifier le fichier « config.h » avec Visual Studio Code.

Vous trouverez ci après les lignes concernées: les lignes 32 à 40 sont à passer en commentaire et 44 à 49 sont à ajouter.

Ce mapping vous permet de définir facilement le câblage utilisé pour le triac.

Avec cette solution vous avez à câbler 4 fils entre la carte et triac, alimenter la carte (220 Vca, 5V, 12V), intercaler votre triac sur la ligne d’alimentation du ballon. Dans cet article, j’explique mon approche sur le sujet mais rien n’est figé et chacun l’adapte à sa configuration.

Pour le triac j’ai pris celui-ci,

Il existe d’autres modèles plus ou moins performants, Cyril les présente dans sa documentation. Concernant les risques de surchauffe à haute intensité, il est prévu une sortie de commande d’un ventilateur de refroidissement du triac, c’est très bien, mais personnellement je préfère surdimensionner le radiateur et rester avec un refroidissement statique sans risque de panne.

Provisoirement le raccordement entre le triac et la carte ESP est réalisé avec des fils Dupont. Vous remarquerez l’ajout du gros radiateur à ailettes sur le triac et la sonde DS18B20 qui mesure la température du radiateur. A terme, je dois encapsuler le tout dans un boitier.

La mise en oeuvre de la sonde est décrite dans cet article.

La partie logiciel:

Je souhaitais, pour des raisons pratiques implanté l’algorithme dans HA, je me suis tourné vers Node red, ce module complémentaire HA est très pratique, puissant et facile à installer, il existe de nombreux tuto sur le net concernant la programmation Node Red. Je ne suis pas fan, mais le pour le coup, il m’a bien rendu service, de plus, NR est universel et s’installe sur beaucoup de systèmes.

Code Node red.

Il est décomposé en 6 Nodes (ou noeuds) principaux:

  • Un Node « Modbus read » permet d’acquérir la puissance du réseau, négative si injection, positive si soutirage. Je lis l’information dans mon cerbo GX (le systéme central de mon installation Victron) toutes les 2 secondes.
  • Un Node « convertion » qui convertit un entier non signé en un entier signé.
  • Un Node « Etat Dimmer » qui me remonte, depuis HA, si le dimmer est en fonctionnement ou pas, dans ce cas la sortie, le compteur interne, l’incrément sont forcés à zéro.
  • un Node « Calcul Sortie % ». C’est ici que je calcule la sortie vers mon dimmer entre 0 et 100%. Il contient également la mise en forme de l’URL.
  • Un Node « transfert de l’URL »
  • Un Node « envoi de la requète HTTP » au dimmer.
  • Quelques Nodes de mise au point et d’affichage sur le dasboard.

Et c’est tout, j’ai quand même ajouté un Dahsborad Node Red, histoire de visualiser le résultat et de se donner la possibilité de simuler la sortie ou la puissance réseau.

Pour utiliser le code que je vous propose, il faut installer ces deux palettes complémentaires dans NR:

  • node-red-contrib-modbus
  • node-red-dashboard

Vous trouver en annexe le code NR à copier/coller dans « import nodes ».

Algorithme de régulation.

J’ai fait simple, le principe retenu est le suivant:

  • Je compare la valeur absolue de la mesure de puissance à 7 plages de valeurs et selon la plage concernée, je donne une valeur à un incrément. Avec cette valeur, j’incrémente ou je décrémente ma sortie % du triac au rythme des lectures de puissance, cadencées à 2 secondes.
  • Plus la puissance est proche de zéro, plus l’incrément est petit, plus la puissance s’éloigne du zéro, plus l’incrément est grand.
  • Les plages de puissance et les valeurs des incréments, définies arbitrairement, sont à ajuster, j’attends plus de production solaire pour affiner les réglages.
  • Je n’exclue pas, si les résultats sont non satisfaisants de revoir complètement cet algorithme.
PalierPu (W)Incrément
Palier 000
Palier 1101
Palier 2501
Palier 31002
Palier 42003
Palier 53004
Palier 66006
Palier 79007
Tableau des paliers programmés dans NR

Un des avantages de Node Red est qu’il vous permet de réaliser des tests en temps réels et revenir très facilement en arrière.

J’ai fait quelques tentatives de régulation avec un PID, sans résultat, mais c’est une piste que je compte explorer plus en avant.

Courbes de résultat:

A compléter quand j’aurai d’autres courbes significatives à montrer.

Solution alternative.

Vous l’avez compris, ma solution est simplifiée par le fait que la puissance réseau est disponible en Modbus dans mon onduleur, ce qui n’est pas le cas le plus répandu, j’en convient, mais vous pouvez très bien exemple vous équiper d’un compteur de puissance communiquant en Modbus ou autre protocole, l’important étant d’avoir un temps de rafraîchissement de la mesure de puissance de quelques secondes (2 dans mon cas).

J’anticipe la question, non on ne peut pas utiliser le Linky qui ne délivre qu’une puissance apparente en VA, à une cadence non maîtrisée et aux valeurs incertaines.

La solution avec un module PZEM ne convient pas non plus, la mesure est toujours positive.

Liens vers des routeurs à monter soi-même:

Bien entendu cette liste est non exhaustive, si vous avez d’autres liens, n’hésiter pas à me les transmettre, je les ajouterai.

Conclusion:

Cette solution est destinée à ceux qui aiment bricoler, mais finalement pas tant que ça. Il existe beaucoup de routeurs prêt à l’emploi, certainement plus performant vu leur prix. Celui ci vous coûtera quelques euros, surtout si vous avez un compteur communiquant.

Annexe:

Code .JSON du Flow Node Red:

[{"id":"99c8e0165e0d303e","type":"tab","label":"Router","disabled":false,"info":"","env":[]},{"id":"d5bb5a6fbe8a8900","type":"http request","z":"99c8e0165e0d303e","name":"","method":"GET","ret":"txt","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[{"keyType":"msg","keyValue":"payload","valueType":"other","valueValue":""}],"x":710,"y":100,"wires":[[]]},{"id":"f3351be463b175be","type":"function","z":"99c8e0165e0d303e","name":"function 1","func":"var value=msg.payload;\n\nif (value < 0) {\n    value = 0;\n} else if (value > 100) {\n    value = 100;\n}\n\n\nmsg.payload = \"http://192.168.0.77/?POWER=\"+value;\nreturn msg;\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":300,"y":80,"wires":[["5317965d8acbf016"]]},{"id":"5317965d8acbf016","type":"change","z":"99c8e0165e0d303e","name":"","rules":[{"t":"set","p":"url","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":490,"y":220,"wires":[["d5bb5a6fbe8a8900","efd318f8.965d28","e7032485600e2013"]]},{"id":"1ae1ff1136b1ce65","type":"debug","z":"99c8e0165e0d303e","name":"Increment","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":700,"y":380,"wires":[]},{"id":"90a44f2c9d4a8c4d","type":"debug","z":"99c8e0165e0d303e","name":"PU grid","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":420,"y":360,"wires":[]},{"id":"efd318f8.965d28","type":"debug","z":"99c8e0165e0d303e","name":"URL","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":730,"y":160,"wires":[]},{"id":"3215c48483a96014","type":"function","z":"99c8e0165e0d303e","name":"Calcul Sortie %","func":"var pu = Math.abs(msg.pu);\nvar pu_net = msg.pu;\nvar pourcentage;\nvar increment;\nvar count;\nvar Palier0 = 0;\nvar Palier1 = 10;\nvar Palier2 = 50;\nvar Palier3 = 100;\nvar Palier4 = 200;\nvar Palier5 = 350;\nvar Palier6 = 600;\nvar Palier7 = 900;\nvar etat = msg.dimmer; // Etat dimmer\n\nif (etat === \"on\") {\n\n    if (context.get('count') === undefined) {\n        context.set('count', 0);\n    }\n\n    if (pu >= Palier1 && pu < Palier2) {\n        increment = 1;\n    }\n    else if (pu >= Palier2 && pu < Palier3) {\n        increment = 1; \n    }\n    else if (pu >= Palier3 && pu < Palier4) {\n        increment = 2; \n    }\n    else if (pu >= Palier4 && pu < Palier5) {\n        increment = 3; \n    }\n    else if (pu >= Palier5 && pu < Palier6) {\n        increment = 5;\n    }\n    else if (pu >= Palier6 && pu < Palier7) {\n        increment = 6;\n    }\n    else if (pu >= Palier7) {\n        increment = 7;\n    }\n    else {\n        increment = 0;\n    }\n\n    if (pu_net < 0) {\n        count = context.get('count') + increment;\n    } else {\n        count = context.get('count') - increment;\n    }\n    context.set('count', count);\n    pourcentage = Math.trunc(Math.min(100, count));\n\n    if (context.get('count') < 0) {\n        context.set('count', 0);\n        pourcentage = 0;\n    }\n    if (context.get('count') > 100) {\n        context.set('count', 100);\n        pourcentage = 100;\n    }\n} else {\n    pourcentage = 0;\n    increment = 0;\n    context.set('count', 0)\n}\n\n\nmsg.payload = \"http://192.168.0.77/?POWER=\" + pourcentage;\n\nvar msg2 = { payload: increment }\nvar msg3 = { payload: pourcentage }\nreturn [msg, msg2, msg3];","outputs":3,"noerr":0,"initialize":"","finalize":"","libs":[],"x":440,"y":420,"wires":[["5317965d8acbf016"],["1ae1ff1136b1ce65","46f7a8fd5e230c2f"],["d94c0ff96671f33a","bf0712cff0298b48"]]},{"id":"46f7a8fd5e230c2f","type":"ui_gauge","z":"99c8e0165e0d303e","name":"","group":"dedd34935ebbcdc1","order":1,"width":"3","height":"3","gtype":"gage","title":"Increment","label":"units","format":"{{value}}","min":0,"max":"20","colors":["#00b500","#e6e600","#ca3838"],"seg1":"","seg2":"","diff":false,"className":"","x":700,"y":440,"wires":[]},{"id":"d94c0ff96671f33a","type":"ui_gauge","z":"99c8e0165e0d303e","name":"","group":"dedd34935ebbcdc1","order":2,"width":"3","height":"3","gtype":"gage","title":"Sortie V1","label":"units","format":"{{value}}","min":0,"max":"100","colors":["#00b500","#e6e600","#ca3838"],"seg1":"","seg2":"","diff":false,"className":"","x":700,"y":480,"wires":[]},{"id":"8a52316c884dd04e","type":"modbus-read","z":"99c8e0165e0d303e","name":"Pu grid","topic":"Pu_grid","showStatusActivities":true,"logIOActivities":false,"showErrors":false,"showWarnings":true,"unitid":"100","dataType":"HoldingRegister","adr":"820","quantity":"1","rate":"2","rateUnit":"s","delayOnStart":false,"startDelayTime":"5","server":"af1b6fc455ed5ad2","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":true,"x":90,"y":200,"wires":[["d4c7e3e3.526538"],[]]},{"id":"d4c7e3e3.526538","type":"function","z":"99c8e0165e0d303e","name":"Convertion","func":"// Entrée : msg.payload (entier non signé)\n// Sortie : msg.payload (entier signé)\n\n// Récupérer la valeur de l'entier non signé\nvar unsignedInt = msg.payload;\n\n// Créer un tableau tampon (Buffer) pour stocker les données\nvar buffer = Buffer.allocUnsafe(2);\n\n// Écrire la valeur de l'entier non signé dans le tampon\nbuffer.writeUInt16BE(unsignedInt, 0);\n\n// Lire la valeur de l'entier signé depuis le tampon\nvar signedInt = buffer.readInt16BE(0);\n\n// Mettre à jour la valeur du message avec l'entier signé\nmsg.payload = signedInt;\n\n// Renvoyer le message modifié\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":130,"y":300,"wires":[["90a44f2c9d4a8c4d","af31ae510b0e868b","fe46843e5a1bd030","62c4bfac1802aab7"]]},{"id":"d485d8cc726be5c8","type":"ui_slider","z":"99c8e0165e0d303e","name":"","label":"Simul Sortie","tooltip":"","group":"378299f3e47d4d20","order":2,"width":0,"height":0,"passthru":true,"outs":"all","topic":"topic","topicType":"msg","min":"0","max":"100","step":1,"className":"","x":110,"y":80,"wires":[["f3351be463b175be"]]},{"id":"af31ae510b0e868b","type":"ui_chart","z":"99c8e0165e0d303e","name":"","group":"13956cf323d720cd","order":2,"width":0,"height":0,"label":"Pu Réseau","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"","ymax":"","removeOlder":"5","removeOlderPoints":"","removeOlderUnit":"60","cutout":0,"useOneColor":false,"useUTC":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"useDifferentColor":false,"className":"","x":710,"y":280,"wires":[[]]},{"id":"e7032485600e2013","type":"ui_text","z":"99c8e0165e0d303e","group":"dedd34935ebbcdc1","order":4,"width":0,"height":0,"name":"","label":"Trame","format":"{{msg.payload}}","layout":"row-spread","className":"","x":690,"y":220,"wires":[]},{"id":"bf0712cff0298b48","type":"ui_chart","z":"99c8e0165e0d303e","name":"","group":"dedd34935ebbcdc1","order":3,"width":0,"height":0,"label":"Sortie GV1","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"","ymax":"","removeOlder":"15","removeOlderPoints":"","removeOlderUnit":"60","cutout":0,"useOneColor":false,"useUTC":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"useDifferentColor":false,"className":"","x":710,"y":520,"wires":[[]]},{"id":"b746980fc02e1b13","type":"ui_slider","z":"99c8e0165e0d303e","name":"","label":"Simul Puissance","tooltip":"","group":"378299f3e47d4d20","order":1,"width":0,"height":0,"passthru":true,"outs":"all","topic":"topic","topicType":"msg","min":"-1000","max":"1000","step":1,"className":"","x":130,"y":140,"wires":[["3215c48483a96014"]]},{"id":"eb37db9b39905dbc","type":"function","z":"99c8e0165e0d303e","name":"essai","func":"var etat = msg.pu;\nvar msg1;\n\nif (etat === \"on\") {\n      msg1 = msg.payload;\n    } \n    else{\n      msg1=0;\n    }\n  \n\nmsg = { payload:etat};\n\nreturn [msg];","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":490,"y":540,"wires":[["621b588ed3d71afe"]]},{"id":"fe46843e5a1bd030","type":"api-current-state","z":"99c8e0165e0d303e","name":"Etat Dimmer","server":"45bc8be7.a4f0a4","version":3,"outputs":1,"halt_if":"","halt_if_type":"str","halt_if_compare":"is","entity_id":"switch.dimmer_on_off_4630","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"dimmer","propertyType":"msg","value":"","valueType":"entityState"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":130,"y":360,"wires":[["be258b9d8eefcd93"]]},{"id":"be258b9d8eefcd93","type":"change","z":"99c8e0165e0d303e","name":"payload->Pu","rules":[{"t":"set","p":"pu","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":210,"y":420,"wires":[["eb37db9b39905dbc","ea6ab9ee1fa0e3f6","3215c48483a96014"]]},{"id":"621b588ed3d71afe","type":"debug","z":"99c8e0165e0d303e","name":"debug 5","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"payload","statusType":"auto","x":700,"y":580,"wires":[]},{"id":"ea6ab9ee1fa0e3f6","type":"debug","z":"99c8e0165e0d303e","name":"Pu","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"payload","statusType":"auto","x":470,"y":480,"wires":[]},{"id":"f531da7a37f7ebd6","type":"comment","z":"99c8e0165e0d303e","name":"Routeur PV","info":"","x":390,"y":40,"wires":[]},{"id":"62c4bfac1802aab7","type":"ui_gauge","z":"99c8e0165e0d303e","name":"","group":"13956cf323d720cd","order":1,"width":"3","height":"3","gtype":"gage","title":"Pu reseau","label":"units","format":"{{value}}","min":"-1000","max":"1000","colors":["#00b500","#e6e600","#ca3838"],"seg1":"","seg2":"","diff":false,"className":"","x":710,"y":320,"wires":[]},{"id":"dedd34935ebbcdc1","type":"ui_group","name":"1","tab":"35ebc542aeecc309","order":1,"disp":true,"width":"6","collapse":false,"className":""},{"id":"af1b6fc455ed5ad2","type":"modbus-client","name":"Victron","clienttype":"tcp","bufferCommands":true,"stateLogEnabled":false,"queueLogEnabled":false,"failureLogEnabled":true,"tcpHost":"192.168.0.86","tcpPort":"502","tcpType":"DEFAULT","serialPort":"/dev/ttyUSB","serialType":"RTU-BUFFERD","serialBaudrate":"9600","serialDatabits":"8","serialStopbits":"1","serialParity":"none","serialConnectionDelay":"100","serialAsciiResponseStartDelimiter":"0x3A","unit_id":"1","commandDelay":"1","clientTimeout":"1000","reconnectOnTimeout":true,"reconnectTimeout":"2000","parallelUnitIdsAllowed":true,"showWarnings":true,"showLogs":true},{"id":"378299f3e47d4d20","type":"ui_group","name":"4","tab":"35ebc542aeecc309","order":4,"disp":true,"width":"6","collapse":false,"className":""},{"id":"13956cf323d720cd","type":"ui_group","name":"2","tab":"35ebc542aeecc309","order":2,"disp":true,"width":"6","collapse":false,"className":""},{"id":"45bc8be7.a4f0a4","type":"server","name":"Home Assistant","version":5,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true,"heartbeat":false,"heartbeatInterval":"30","areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":"at: ","statusYear":"hidden","statusMonth":"short","statusDay":"numeric","statusHourCycle":"h23","statusTimeFormat":"h:m","enableGlobalContextStore":true},{"id":"35ebc542aeecc309","type":"ui_tab","name":"Victron","icon":"dashboard","disabled":false,"hidden":false}]

9 Comments on “HA-Routeur Solaire”

  1. Bonjour,
    Merci pour cet article très inspirant qui m’a permis de découvrir le travail de Cyril.
    J’ai déjà installé la partie pvrouteur qui fonctionne parfaitement.
    J’ai commandé le triac.
    Aurais-tu un lien pour le radiateur sur-dimensionné ? J’ai aussi une préférence pour le côté passif.
    Pas de soucis de surchauffe ? Ton triac est bcp sollicité ?

    1. Bonjour merci pour ton retour ça fait plaisir. Mon dissipateur thermique vient d’une récupération, mais tu devrais trouvé ton bonheur sur amazon ou ebay, sinon le nieux est de supprimer le dissipateur en place et de déporter le triac sur un dissipateur plus grand avec de la pate thermique au contact. Pour info je travail sur une solution ESP32+ ESPhome complètement intégrée dans Home assistant. Bonne continuation

      1. Merci pour ta réponse. Je vais suivre avec assiduité ton site pour voir ces évolutions à base de ESPHome.

  2. Bonjour,
    Merci pour ton partage, j’ai aussi mis en place récemment un routeur solaire, mon index provin de ma passerelle Envoy S. Par contre la partie calcul et commande du Dimmer est effectué par un programme qui tourne sur appdaemon : https://github.com/frtz13/EnergyRouter

    Ca marche très très bien.

    1. Bonjour. Merci pour ton retour et félicitations pour ton travail, cette nouvelle approche est très intéressante, bravo et merci d’avoir partagé.

  3. Bonjour, si nous récupérons directement sur le linky la puissance consommée ou exportée sous ha peut on commander ce routeur et router le surplus ?
    Merci

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *