Récupération et publication MQTT des données de la téléinformation avec Raspberry Pi et Docker
-
Hello,
Ca fait un petit bout de temps que j'ai mis en place cette solution chez moi, et ça tourne parfaitement. Le mode historique comme standard est géré.
Je pense que la solution a suffisamment fait ses preuves pour la partager avec vous !
Toute la marche à suivre est disponible ici : https://github.com/tms0/teleinfo-publisher
N'hésitez pas à tester et me dire ce que vous en pensez !
Valentin
-
Alors ça, c'est top, merci à toi pour le partage
Par ailleurs, sais que maintenant le firmware tasmota gère parfaitement la téléinfo est il envoi les données dans MQTT, ça c'est encore une autre très bonne nouvelle, toutes les cartes à base d'ESP8266 ou ESP32 peuvent être flashée avec. Il suffit d'ajouter un bon vieil opto ou prendre un pitinfo aussi.
-
Oui j'ai vu ça j'ai laissé une petite note dans le README, mais c'est un domaine que j'ai très peu touché jusqu'ici !
Pour l'instant, je suis pleinement satisfait du couple rpi zero / pitinfo, j'ai tout installé dans mon tableau électrique avec une petite alim rail din 5V et un support rail din pour le rpi aussi, c'est tout propre et ça tourne 24h24 sans problème.
-
Canon, en plus t'as un broker MQTT du coup
Par ailleurs je ne sais pas si tou connais balena ca te permet de générer des images RPI avec balenaos (systeme protégé avec peu d'usure de la SD card).
Comme tu maitrises docker ça peut avoir un sens d'avoir une image toute faite pour les users. Il choisi l'image, sa cible (rasp ou autre) et hop ca récup une image SD dans laquelle tout est pret y compris la config docker qui s'installe au 1er boot et se met à jour qd besoin toute seule
C'est super souple et tu peux prendre la main à distance sur ton rasp. C'est gratuit jusque 10 devices c'est vraiment bien, Regarde si tu peux tu vas adorer -
Je fais tourner le broker sur un autre RPI, depuis lequel j'exploite les données avec la stack InfluxDB, tout dockerisé là aussi. C'est disponible sur un autre repo, mais il mériterait un README un peu plus fourni : https://github.com/tms0/energy-monitoring
Je connaissais pas du tout balena, en plus je vois qu'ils ont une version open source en beta, je testerais ça à l'occasion, je commence à avoir une petite flotte de RPI mine de rien !
-
Bonjour @tms ,
J'utilise ton flux Node-RED depuis quelques temps mais il me manquait des infos
J'ai donc modifié la fonction et ajouter une variable jeedom pour que le JSON soit interpréter par l'extension téléinfo de JeedomPour le contexte j'ai un dongle Micro Teleinfo V3.0 sur la BananaPi dans mon garage sur lequel tourne une instance Node-RED et plutôt que de partager le port USB à ma VM Jeedom j'ai préféré mettre un place un flux qui publie le TIC en MQTT.
function computeChecksum(chars) { // Calculer le checksum en additionnant les codes ASCII des caractères et en appliquant le masque var sum = Array.from(chars).reduce((acc, curr) => acc + curr.charCodeAt(), 0); return String.fromCharCode((sum & 0x3F) + 0x20); // Conversion en caractère ASCII } // Définir si le formatage pour Jeedom doit être appliqué var jeedom = true; // Passe à false si le formatage n'est pas nécessaire // Initialiser le payload var payload = { timestamp: new Date().getTime() // Ajouter un horodatage au payload }; // Supprimer les caractères de début/fin de trame var lines = msg.payload.toString().replace("\u0002\n", "").replace("\r\u0003", ""); // Traiter chaque ligne séparément lines.split("\r\n").forEach(line => { node.debug(`Ligne à décoder : ${line}`); // Découper la ligne selon le format attendu : étiquette, horodatage, valeur, checksum var groups = line.match(/^(.+?)\t(?:([\w\d]+)\t)?(.+)\t(.)$/i); if (groups === null) { node.warn(`Impossible de décoder la ligne '${line}'`); } else { var etiquette = groups[1].trim(); // Nom du champ var valeur = groups[3].trim(); // Valeur associée var checksum = groups[4].trim(); // Checksum reçu // Calculer le checksum attendu var computedChecksum = computeChecksum(line.slice(0, -1)); // Exclure le checksum reçu if (computedChecksum !== checksum) { node.warn(`Checksum invalide pour '${line}'. Attendu : '${computedChecksum}', reçu : '${checksum}'`); return; // Ignorer cette ligne si le checksum est invalide } // Si l'étiquette est STGE, traiter la valeur comme un champ binaire if (etiquette === 'STGE') { let stgeValue = parseInt(valeur, 16).toString(2).padStart(32, '0'); // Compléter à 32 bits // Décoder chaque groupe de bits avec les fonctions équivalentes payload['STGE01'] = stgeValue[31] === '1' ? 'Ouvert' : 'Ferme'; payload['STGE02'] = switchMot02(parseInt(stgeValue.slice(28, 31), 2)); payload['STGE03'] = stgeValue[27] === '1' ? 'Ouvert' : 'Ferme'; payload['STGE04'] = switchMot04(parseInt(stgeValue[26], 2)); payload['STGE05'] = switchMot05(parseInt(stgeValue[25], 2)); payload['STGE06'] = switchMot06(parseInt(stgeValue[24], 2)); payload['STGE07'] = switchMot07(parseInt(stgeValue[23], 2)); payload['STGE08'] = switchMot08(parseInt(stgeValue[22], 2)); payload['STGE09'] = switchMot09(parseInt(stgeValue.slice(20, 22), 2)); payload['STGE10'] = switchMot10(parseInt(stgeValue.slice(18, 20), 2)); payload['STGE11'] = switchMot11(parseInt(stgeValue[17], 2)); payload['STGE12'] = switchMot12(parseInt(stgeValue[16], 2)); payload['STGE13'] = switchMot13(parseInt(stgeValue[15], 2)); payload['STGE14'] = switchMot14(parseInt(stgeValue.slice(13, 15), 2)); payload['STGE15'] = switchMot15(parseInt(stgeValue.slice(11, 13), 2)); payload['STGE16'] = switchMot16(parseInt(stgeValue[10], 2)); payload['STGE17'] = switchMot17(parseInt(stgeValue.slice(8, 10), 2)); payload['STGE18'] = switchMot18(parseInt(stgeValue.slice(6, 8), 2)); payload['STGE19'] = switchMot19(parseInt(stgeValue.slice(4, 6), 2)); payload['STGE20'] = switchMot20(parseInt(stgeValue.slice(2, 4), 2)); } else { // Traitement normal pour les autres étiquettes if (valeur.match(/^\d+$/)) { valeur = parseInt(valeur); } payload[etiquette] = valeur; } } }); // Fonctions de traduction (équivalents des switch_motXX en Python) function switchMot01(argument) { const map = { 0: "Ferme", 1: "Ouvert", }; return map[argument] || "Invalide"; } function switchMot02(argument) { const map = { 0: "Ferme", 1: "Ouvert sur surpuissance", 2: "Ouvert sur surtension", 3: "Ouvert sur delestage", 4: "Ouvert sur ordre CPL ou Euridis", 5: "Ouvert sur surchauffe avec I > Imax", 6: "Ouvert sur surchauffe avec I < Imax", }; return map[argument] || "Invalide"; } function switchMot03(argument) { const map = { 0: "Ferme", 1: "Ouvert", }; return map[argument] || "Invalide"; } function switchMot04(argument) { const map = { 0: "Toujours à 0", 1: "Anormal", }; return map[argument] || "Invalide"; } function switchMot05(argument) { const map = { 0: "Pas de surtension", 1: "Surtension", }; return map[argument] || "Invalide"; } function switchMot06(argument) { const map = { 0: "Pas de dépassement", 1: "Dépassement en cours", }; return map[argument] || "Invalide"; } function switchMot07(argument) { const map = { 0: "Consommateur", 1: "Producteur", }; return map[argument] || "Invalide"; } function switchMot08(argument) { const map = { 0: "Énergie active positive", 1: "Énergie active négative", }; return map[argument] || "Invalide"; } function switchMot09(argument) { const map = { 0: "Énergie ventilée sur Index 1", 1: "Énergie ventilée sur Index 2", 2: "Énergie ventilée sur Index 3", 3: "Énergie ventilée sur Index 4", 4: "Énergie ventilée sur Index 5", 5: "Énergie ventilée sur Index 6", 6: "Énergie ventilée sur Index 7", 7: "Énergie ventilée sur Index 8", 8: "Énergie ventilée sur Index 9", 9: "Énergie ventilée sur Index 10", }; return map[argument] || "Invalide"; } function switchMot10(argument) { const map = { 0: "Énergie ventilée sur Index 1", 1: "Énergie ventilée sur Index 2", 2: "Énergie ventilée sur Index 3", 3: "Énergie ventilée sur Index 4", }; return map[argument] || "Invalide"; } function switchMot11(argument) { const map = { 0: "Horloge correcte", 1: "Horloge en mode dégradé", }; return map[argument] || "Invalide"; } function switchMot12(argument) { const map = { 0: "Mode Historique", 1: "Mode Standard", }; return map[argument] || "Invalide"; } function switchMot13(argument) { const map = { 0: "Non utilisé", 1: "Non utilisé", }; return map[argument] || "Invalide"; } function switchMot14(argument) { const map = { 0: "Désactivée", 1: "Activée sans sécurité", 2: "Invalide", 3: "Activée avec sécurité", }; return map[argument] || "Invalide"; } function switchMot15(argument) { const map = { 0: "New/Unlock", 1: "New/Lock", 2: "Registered", 3: "Invalide", }; return map[argument] || "Invalide"; } function switchMot16(argument) { const map = { 0: "Compteur non synchronisé", 1: "Compteur synchronisé", }; return map[argument] || "Invalide"; } function switchMot17(argument) { const map = { 0: "Pas d'annonce", 1: "Bleu", 2: "Blanc", 3: "Rouge", }; return map[argument] || "Invalide"; } function switchMot18(argument) { const map = { 0: "Pas d'annonce", 1: "Bleu", 2: "Blanc", 3: "Rouge", }; return map[argument] || "Invalide"; } function switchMot19(argument) { const map = { 0: "Pas de préavis en cours", 1: "Préavis PM1 en cours", 2: "Préavis PM2 en cours", 3: "Préavis PM3 en cours", }; return map[argument] || "Invalide"; } function switchMot20(argument) { const map = { 0: "Pas de pointe mobile", 1: "PM1 en cours", 2: "PM2 en cours", 3: "PM3 en cours", }; return map[argument] || "Invalide"; } // Ajouter le formatage pour Jeedom si nécessaire if (jeedom) { msg.payload = '{"TIC":' + JSON.stringify(payload) + '}'; msg.topic = 'teleinfo/TIC'; } else { msg.payload = payload; } return msg;
C'est certainement perfectible en ajoutant par exemple un noeud réglages qui regrouperai les variables
Cordialement
Alexis -
@Nalexdouze Merci pour le partage, c'est effectivement la meilleure solution, en plus tu peux modifier ou dispatcher tes flux vers plusieurs solutions si besoin. J'aurais fait pareil