Filtrage et convertion binaire decimal

Bonjour,

Je souhaite décoder l’information de statuts sur la Télé Information Client du compteur électrique.

https://www.enedis.fr/media/2035/download

Cette information de statuts est codée sur 32 bits.
Je souhaite filtrer sur certains bits puis convertir l’information binaire en décimale ou en information on/off.

Comment réaliser cela avec des templates ?

Merci de votre aide.

Salut,

Tu ne va pas pouvoir directement connecter HA directement sur les bornes TIC du compteur! Je ne te conseille pas tu vas griller un truc, si c’est pas ton PC c’est le compteur qui va y passer :wink:

Il faut un petit montage avec un optocoupleur soit fait maison, soit acheté pour convertir vers un port série et lire pouvoir les données en texte.

En DIY y’a le choix entre ESPHome qui gère ça ou encore plus DIY directement en lisant le le port sérier sur un raspberry ou équivalent…

EspHome:

Montage :

Après il y a aussi les solutions clé en main comme lixee pour recevoir en Zigbee à ~50€
D’autres personnes vendent sur le net des montages tout fait avec une ESP derrière…
T’as aussi des version USB qui existent…

Y’a plein de solutions, à toi de voir ton niveau d’engagement recherché :wink:

Merci pour ton retour, mais question n’est comment récupérer les infos TIC, mais comment faire des traitements du Dword Statuts ?
Filtrage bit à bit (faire un et logique bit à bit entre le status et le masque)
Puis convertir le résultat après filtrage.

Pas certain que des expressions Template soient le meilleur endroit pour faire ça…
Ca doit se faire, faut voir la doc de jinja2 qui est utilisé pour les Templates, mais tu ne vas pas trouver beaucoup d’exemples directement dans HA, qui généralement interagit avec des éléments à un niveau beaucoup plus haut.

J’ai tout de même un peu mal a voir de l’intérèt de la manip :wink:

https://svn.python.org/projects/external/Jinja-2.1.1/docs/_build/html/index.html

Par exemple, dans le registre statuts il y a l’information « Organe de coupure ».
Celle-ci est codée sur 3 bits parmi les 32 du statuts (bit 1 à 3)
Selon la valeur décimale codée par ces 3 bits on obtient:
0 = fermé
1 = ouvert sur surpuissance
2 = ouvert sur surtension
3 = ouvert sur délestage
4 = ouvert sur ordre CPL ou Euridis
5 = ouvert sur une surchauffe avec une valeur du courant supérieure au courant de commutation maximal
6 = ouvert sur une surchauffe avec une valeur de courant inférieure au courant de commutation maxima

Pour décoder le statuts et isoler l’information, il faut faire un et logique bit à bit avec la valeur binaire 00000000000000000000000000001110, puis décaler le résultat d’un bit vers la droite. C’est à dire diviser par 2 le résultat du et logique.

Voila ce que je souhaite faire avec les templates.
Pour d’autres informations on cherche juste à avoir un état on / off.

il y a ceci qui fait le job, mais ce n’est pas des templates HA

Salut,

ok plus clair :slight_smile: Je n’ai pas de Linky, donc je suis plus habitué au mode historique.

Mais bon la réponse restera la même, pas certain qu’il y ait de façon directe de faire ça… Mais c’est surement possible comme c’est basé sur du python, un certain nombre de fonctions python sont accessibles et utilisables directement dans les templates.

L’idéal serait tout de même de faire ce genre de décodage directement à la source en C++ avec EspHome ou en Python sur un pi.

Je suis d’accord :

L’idéal serait tout de même de faire ce genre de décodage directement à la source en C++ avec EspHome.

Il faut que je regarde du côté de la fonction lambda, mais j’avoue que cela fait bien longtemps que je n’ai pas fait de c++.
Si quelqu’un a une piste dans esphome je suis preneur.

Je me suis lancé…

sensor:
  # Statut "Organe de coupure" extrait du Registre de Statuts (STGE)
  - platform: template
    name: "Organe de Coupure"
    lambda: |-
      std::string valeur_hex = id(statut_registre).state;
      uint32_t valeur_entier = std::stoul(valeur_hex, nullptr, 16);
      uint32_t masque = 0b1110;
      uint32_t resultat_masque = valeur_entier & masque;
      uint32_t valeur_organe_coupure = resultat_masque >> 1;
      return (int)valeur_organe_coupure;

text_sensor:
  # Registre de Statuts
  - platform: teleinfo
    tag_name: "STGE"
    name: "Linky Registre de Statuts"
    id: statut_registre
    icon: mdi:information

Quand j’ajoute la partie « Organe de coupure » ça fait planter l’API

INFO ESPHome 2024.3.1
INFO Reading configuration /config/esphome/esp-tic.yaml...
INFO Generating C++ source...
INFO Compiling app...
Processing teleinfo (board: esp32dev; framework: arduino; platform: platformio/espressif32@5.4.0)
--------------------------------------------------------------------------------
HARDWARE: ESP32 240MHz, 320KB RAM, 4MB Flash
 - toolchain-xtensa-esp32 @ 8.4.0+2021r2-patch5
Dependency Graph
|-- AsyncTCP-esphome @ 2.1.3
|-- WiFi @ 2.0.0
|-- FS @ 2.0.0
|-- Update @ 2.0.0
|-- ESPAsyncWebServer-esphome @ 3.1.0
|-- DNSServer @ 2.0.0
|-- ESPmDNS @ 2.0.0
|-- ArduinoJson @ 6.18.5
Compiling .pioenvs/teleinfo/src/main.cpp.o
Linking .pioenvs/teleinfo/firmware.elf
RAM:   [=         ]  12.6% (used 41212 bytes from 327680 bytes)
Flash: [=====     ]  49.7% (used 912205 bytes from 1835008 bytes)
Building .pioenvs/teleinfo/firmware.bin
Creating esp32 image...
Successfully created esp32 image.
esp32_create_combined_bin([".pioenvs/teleinfo/firmware.bin"], [".pioenvs/teleinfo/firmware.elf"])
Wrote 0xf01e0 bytes to file /data/build/teleinfo/.pioenvs/teleinfo/firmware-factory.bin, ready to flash to offset 0x0
========================= [SUCCESS] Took 28.55 seconds =========================
INFO Successfully compiled program.
INFO Connecting to 192.168.0.39
INFO Uploading /data/build/teleinfo/.pioenvs/teleinfo/firmware.bin (917984 bytes)
Uploading: [============================================================] 100% Done...

INFO Upload took 14.08 seconds, waiting for result...
INFO OTA successful
INFO Successfully uploaded program.
INFO Starting log output from 192.168.0.39 using esphome API
WARNING Can't connect to ESPHome API for teleinfo @ 192.168.0.39: Error connecting to [AddrInfo(family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, proto=6, sockaddr=IPv4Sockaddr(address='192.168.0.39', port=6053))]: [Errno 111] Connect call failed ('192.168.0.39', 6053) (SocketAPIError)
INFO Trying to connect to teleinfo @ 192.168.0.39 in the background

Si quelqu’un peut me dire où est le problème, si suis preneur.
Merci d’avance pour votre aide

Je suis parti de ton code, j’ai fais du débogage à l’ancienne (tester les lignes une par une jusqu’a trouver celle qui bloque) et je pense être parvenu à une méthode qui semble fonctionner.
c’est sans doute pas très « propre » (je suis développeur amateur, et en C++ je suis encore largement débutant)
voilà ce que donne mon code :
(ici c’est pour décoder les bits 26 & 27 pour lire l’annonce de la couleur du lendemain, mais le principe doit fonctionner pour n’importe lesquels)

sensor:
  - platform: template
    name: "Linky couleur demain"
    id: linky_couleur_lendemain
    accuracy_decimals: 0
    lambda: |-
     std::string str_registre = id(linky_registre_status).state;
     if (str_registre.length() > 1){
      std::string str_demain = str_registre.substr(1,1); //extraction du 2eme caractère de la chaine (bits 27..24)
      unsigned int int_demain = std::stoul(str_demain, nullptr, 16);
      int_demain &= 12; //masque 0b1100 pour garder bits 26 & 27
      int_demain = int_demain >> 2; //décalage de 2 vers la droite (division par 4)
      return {(int)int_demain};
     } else {
      return {0};
     }

J’ai juste un warning parce que ça ne lui plait pas que je lui retourne un int alors qu’il attend un float, je ne sais pas comment on definit le type d’un sensor pour le forcer en int

En relisant mon code et le tient, je me suis dis que ta méthode devrait fonctionner aussi.
J’ai donc écrit ça, ici c’est pour lire la couleur du jour, mais c’est le même principe

  - platform: template
    name: "Linky couleur jour"
    id: linky_couleur_jour
    accuracy_decimals: 0
    lambda: |-
     std::string valeur_hex = id(linky_registre_status).state;
     if (valeur_hex.length() > 1){
      uint32_t valeur_entiere = std::stoul(valeur_hex, nullptr,16);
      valeur_entiere &= 0x3000000;
      valeur_entiere = valeur_entiere >> 24;
      return {(int)valeur_entiere};
     } else {
     return{0};
     }

Maintenant, en écrivant ça, je me suis souvenu que id().state semble permetre non seulement de lire la valeur d’un sensor, mais aussi de l’écrire.
Donc il doit y avoir moyen d’écrire un petit script dans le sensor de la lecture de STGE et que celui-ci vienne mettre à jour la valeur des différents sensor template que l’on veut.
ça fait un seul script qui décode toute les valeurs (utile) plutôt que d’en avoir un à chaque sensor.
Là il est tard, je verrais demain, mais je suis quasi certain qu’il y a moyen…

J’ai un peu bossé sur mon idée et ça fonctionne parfaitement.
Voilà un bout de code qui permet de traiter tous les états STGE dans le même script.
Il suffit simplement de créer des sensor template du bon type avec les id correspondant.
Là ça remonte du binaire / float pour tester le concept, mais c’est pas compliqué d’adapter pour un affichage en texte.

# Exemple Sensor "Organe de Coupure" 
# les autres sont à créer suivant le même modèle
sensor:
  - platform: template
    name: "Linky Organe Coupure"
    id: linky_organe_coupure
    accuracy_decimals: 0

text_sensor:
  - platform: teleinfo
    tag_name: "STGE"
    name: "Registre de Status"
    id: linky_registre_status
    icon: mdi:key-variant
    teleinfo_id: myteleinfo
    on_value: 
      then:
        - lambda: |-
           std::string str_registre = id(linky_registre_status).state;
           if (str_registre.length() > 1){
            uint32_t int_registre = std::stoul(str_registre, nullptr,16);
            unsigned int int_fonction = int_registre & 0b1; //masque bit 0 = Contact sec
            id(linky_contact_sec).publish_state(int_fonction !=0);
            int_registre = int_registre >> 1;
            int_fonction = int_registre & 0b111; //masque bit 1..3 = Organe de coupure
            id(linky_organe_coupure).publish_state((float)int_fonction);
            int_registre = int_registre >> 3;
            int_fonction = int_registre & 0b1; //masque bit 4 = Etat cache-bornes
            id(linky_cachebornes).publish_state(int_fonction !=0);
            int_registre = int_registre >> 2;
            int_fonction = int_registre & 0b1; //masque bit 6 = Surtension
            id(linky_surtension_phase).publish_state(int_fonction !=0);
            int_registre = int_registre >> 1;
            int_fonction = int_registre & 0b1; //masque bit 7 = Dépassement puissance
            id(linky_depassement_puissance).publish_state(int_fonction !=0);
            int_registre = int_registre >> 1;
            int_fonction = int_registre & 0b1; //masque bit 8 = Producteur
            id(linky_producteur).publish_state(int_fonction !=0);
            int_registre = int_registre >> 1;
            int_fonction = int_registre & 0b1; //masque bit 9 = Sens énergie active
            id(linky_sens_energie).publish_state(int_fonction !=0);
            int_registre = int_registre >> 1;
            int_fonction = int_registre & 0b1111; //masque bit 10..13 = Numéro index fournisseur
            id(linky_index_fournisseur).publish_state((float)int_fonction);
            int_registre = int_registre >> 4;
            int_fonction = int_registre & 0b11; //masque bits 14 & 15 = Numéro index distributeur
            id(linky_index_distributeur).publish_state((float)int_fonction);
            int_registre = int_registre >> 2;
            int_fonction = int_registre & 0b1; //masque bit 16 = Mode dégradé horloge
            id(linky_horloge_degrade).publish_state(int_fonction !=0);
            int_registre = int_registre >> 1;
            int_fonction = int_registre & 0b1; //masque bit 17 = Mode Téléinformation
            id(linky_mode_tic).publish_state(int_fonction !=0);
            int_registre = int_registre >> 2;
            int_fonction = int_registre & 0b11; //masque bits 19 & 20 = Etat communication Euridis
            id(linky_com_euridis).publish_state((float)int_fonction);
            int_registre = int_registre >> 2;
            int_fonction = int_registre & 0b11; //masque bits 21 & 22 = Status CPL
            id(linky_status_cpl).publish_state((float)int_fonction);  
            int_registre = int_registre >> 2;
            int_fonction = int_registre & 0b1; //masque bit 23 = Synchronisation CPL
            id(linky_sync_cpl).publish_state(int_fonction !=0);  
            int_registre = int_registre >> 1;
            int_fonction = int_registre & 0b11; //masque bits 24 & 25 = Couleur du jour Tempo
            id(linky_couleur_jour).publish_state((float)int_fonction);            
            int_registre = int_registre >> 2;
            int_fonction = int_registre & 0b11; //masque bits 26 & 27 = Couleur du lendemain Tempo
            id(linky_couleur_lendemain).publish_state((float)int_fonction); 
            int_registre = int_registre >> 2;
            int_fonction = int_registre & 0b11; //masque bits 28 & 29 = Préavis pointes mobiles
            id(linky_preavis_pointe).publish_state((float)int_fonction);
            int_registre = int_registre >> 2;
            int_fonction = int_registre & 0b11; //masque bits 30 & 31 = Pointes mobiles
            id(linky_pointe_mobile).publish_state((float)int_fonction);
           }