HA-Teleinformation Linky-Mode Standard

Intro

Dans un article précédent https://domo.rem81.com/ha-teleinformation-linky-mode-historique/ , je décrivais la mis en oeuvre d’une communication entre un Linky et Home Assistant en mode Historique.

Après avoir fonctionné un temps en mode « Historique », je suis passé au mode « Standard ». Celui ci offre un plus grand nombre de données.

Raccordement de la télé information de mon compteur Linky. Rien de bien méchant il suffit de se raccorder aux bornes i1/i2 du linky, le plus délicat fut le passage du câble de 60 ml entre le compteur Linky et mon tableau électrique.

Raccordement du Linky

Mises à jour

du 21/03/2025:

Integration du décodage du status LINKY dans le code ESP Home du Tempo. Plus besoin de passer par AppDaemon. PS: n’atant pas la possibilté de les tester, je n’ai pas modifier la version « HP/HC ».

du 23/11/2023:

Ajout d’une version EDF « Tempo »

du 16/08/2022

Ajout du « filter out » dans les HP,HC permettant de rejeter les valeurs nulles si besoin.

du 12/04/2022

Suite à passage Home Assistant 2022.4.0, modification du changement de tarif des utility meter https://www.home-assistant.io/integrations/utility_meter

Pré requis

Mes fichiers « .yaml » sont regroupés dans un dossier « config/packages ». J’utilise la directive « !include_dir_named packages » dans mon « configuration.yaml » très pratique pour organiser son HA.


homeassistant:
  name: Crochon
  latitude: !secret latitude
  longitude: !secret longitude
  unit_system: metric
  packages: !include_dir_named packages
  external_url: !secret external-url
  internal_url: !secret internal-url

Choix du matériel

Les informations transmises par le Linky ne sont pas directement exploitables par un ESP, elles nécessitent un adaptateur, vous trouverez beaucoup de modèles sur le Net, perso j’ai choisi la facilité en achetant un module Wemos Teleinfo. J’utilise la version 1.1, la nouvelle version 1.2 paut présenter des différences, à voir dans la documentation.

WeMos Teleinfo 2

C’est un module pas très cher, correctement étudié et bien fini, compatible avec les esp8266 et ESP32 Mini, qui dispose également d’une Led WS2812 RGB programmable, d’un connecteur I2C disponible pour un afficheur OLED ou autres capteurs compatibles, et d’une led indiquant l’état de la connexion avec le Linky qui se raccorde sur le bornier vert.

Perso j’utilise un L’ESP32 Mini, en effet l’ESP8266 est trop juste en puissance de calcul si on utilise l’intégration ESPHOME plus un afficheur, j’ai galéré avec, je ne le recommande pas, le constructeur du wemos téléinfo non plus d’ailleurs.

L’ESP32 Mini est un ESP32 au format ESP8266 D1, pratique si l’on souhaite utiliser des cartes d’extension, mais avec une puissance de calcul et de traitement bien supérieure.

Descriptif fonctionnel

Les compteurs Linky disposent de 2 modes de téléinformation :
• Le mode Historique, qui correspond à l’ancien mode des compteurs électroniques.
• Le mode Standard, qui est le nouveau format et qui comporte plus d’informations.

Vous trouverez les informations détaillées dans le document Enedis suivant:

Le module WEMOS Teleinfo est compatible, le changement de mode passe obligatoirement par une demande auprès de votre fournisseur d’énergie qui relaye vers Enedis.

Concernant l’intégration dans HA, pas de problème particulier grâce à « teleinfo » disponible sur ESP Home, il est compatible avec les deux modes, les différences étant dans la vitesse de transmission et la validation du mode historique ou pas:

Mode Historique

Mode Standard

Informations collectées

J’utilise le W et non le VA pour l’entité « SINSTS » afin d’exploiter les « Statistiques Longues Durée » qui ne reconnait pas les « Volt Ampéres ». Pour mémoire, en monophasé P en W= P en VA multiplie cos phi, mais come le Linky ne remonte pas le cos phi on se contentera d’un cos phi de 1, les puristes ne m’en voudront pas.

Je divise par 1000 le total de l’énergie active soutirée « EAST » pour obtenir des kWh. C’est cette entité que j’utilise dans les « utility meter » de HA pour calculer mes consommations journalière, semaine, mois, année.

Je calcul en % dans un « template » « Linky P/Pcoup » le ratio entre la puissance instantanée « SINST » et la puissance de coupure « PCOUP », ce qui donne une idée de la réserve de puissance disponible et plus tard, s’en servir pour vérifier si l’abonnement peut être revue à la baisse.

« UMOY1 » nous donne la tension moyenne du secteur.

« IRMS1 » nous donne l’intensité efficace de la phase 1.

« NTARF » nous indique si le tarif est en heure creuse (=1) ou heure pleine (=2), variable utilisée dans l’automatisme de comptabilisation de l’énergie avec les « utility_meter ». J’en profite pour afficher en clair dans un « template », le tarif en cours.

Je remonte la consommation journalière depuis HA vers l’ESP afin de l’afficher en local.

Tableau des informations disponibles

Certaines sont dispo uniquement en tri-phasé et/ou en tant que producteur d’énergie.

Code ESP

Si vous avez des difficultés de flashage avec ESPHome, parcourez mon article https://domo.rem81.com/home-assistant_esp-home/ cela devrait vous aider.

L’afficheur utilise une police de caractère « Arial », vous pouvez télécharger le fichier arial.ttf ici puis le transférer dans un dossier « fonts » de votre « /config/esphome ». Bien entendu vous pouvez utiliser une autre police de caractères, « size » détermine la taille des caractères.

Version « Tempo »

les filtres « filter_out: 0.0 » sont à mettre en action si et seulement si les index sont supérieurs à 0 sinon vous afficherez « indisponible ».

Le filtre « Lambda » permet d’éviter des incohérences dans l’évolution des index, il m’est arrivé de lire des valeurs très faibles mais supérieure à 0 pour des raisons que j’ignore.

L’écart se regle dans la variable « globals » ecart_max.

substitutions:
  device_name: esp124-tic
  adress_ip: "192.168.0.124"
  friendly_name: esp124
  time_timezone: "Europe/Paris"

esphome:
  name: ${device_name}
  project:
    name: "rem81.esp124-TIC"
    version: "1.0.1"
  platformio_options:
    lib_deps: NeoPixelBus@2.6.0
  #platform: ESP32
  #board: mhetesp32minikit    
      
esp32:
  board: mhetesp32minikit
  framework:
    type: arduino
    
wifi:
  networks:
    - ssid: !secret wifi
      password: !secret mdpwifi
  reboot_timeout: 5min

  manual_ip:
    static_ip: ${adress_ip}
    gateway: 192.168.0.254
    subnet: 255.255.255.0


# Enable logging
logger:
#  baud_rate: 0
# Enable Home Assistant API
api:

ota:
  platform: esphome
  
web_server:
  port: 80

font:
  - file: "fonts/arial.ttf"
    id: arial
    size: 15

i2c:
  sda: GPIO21 #D2=pin 19
  scl: GPIO22 #D1=pin 20
  scan: True
  id: bus_a
  
# 
uart:
  id: uart_a
  rx_pin: GPIO23
#  tx_pin: GPIO1
  baud_rate: 9600
  parity: EVEN
  data_bits: 7

teleinfo:
  id: myteleinfo
  uart_id: uart_a
  update_interval: 10s
  historical_mode: false
  
# Led WS2812 RGB  
light:
  - platform: partition
    name: led1
    id: led1
    default_transition_length: 0s
    segments:
      - id: rgb_led
        from: 0
        to: 0

  - platform: neopixelbus
    num_leds: 1
    pin: GPIO18
    name: "RGB strip"
    variant: ws2812
    id: rgb_led
    default_transition_length: 0s  

globals:
  # Ecart maximum admissible entre deux lectures
  - id: ecart_max
    type: float
    restore_value: no
    initial_value: '50'

  - id: hex_to_binary
    type: std::string
    restore_value: no
    initial_value: '""'

  - id: update_binary
    type: bool
    restore_value: no
    initial_value: 'false'


sensor:
# Energie Active soutirée totale
  - platform: teleinfo
    id: hc_hp
    tag_name: "EAST"
    name: "Linky HPHC KWH"
    unit_of_measurement: "kWh"
    icon: mdi:flash
    teleinfo_id: myteleinfo
    device_class: "energy"
    state_class: "total_increasing"  
    filters:
      - filter_out: 0.0
      - lambda: |-
          return x/1000;
      - lambda: |-
          float MAX_DIFFERENCE = id(ecart_max);  // Difference max entre de lecture, à ajuster selon votre cas de figure.
          static float last_value = NAN;
          if (isnan(last_value) || std::abs(x - last_value) < MAX_DIFFERENCE)
            return last_value = x;
          else
            return {};          
          

# Energie Active soutirée Index01 HC Bleu
  - platform: teleinfo
    id: hcbleu
    tag_name: "EASF01"
    name: "Linky HC Bleu kWh"
    unit_of_measurement: "kWh"
    icon: mdi:flash
    teleinfo_id: myteleinfo
    device_class: "energy"
    state_class: "total_increasing"      
    filters:
      - lambda: return x / 1000;
      - filter_out: 0.0
      - lambda: |-
          float MAX_DIFFERENCE = id(ecart_max);  // Difference max entre de lecture, à ajuster selon votre cas de figure.
          static float last_value = NAN;
          if (isnan(last_value) || std::abs(x - last_value) < MAX_DIFFERENCE)
            return last_value = x;
          else
            return {};      

# Energie Active soutirée Index02 HP Bleu
  - platform: teleinfo
    id: hpbleu
    tag_name: "EASF02"
    name: "Linky HP Bleu kWh"
    unit_of_measurement: "kWh"
    icon: mdi:flash
    teleinfo_id: myteleinfo
    device_class: "energy"
    state_class: "total_increasing"      
    filters:
      - lambda: return x / 1000;      
      - filter_out: 0.0
      - lambda: |-
          float MAX_DIFFERENCE = id(ecart_max);  // Difference max entre de lecture, à ajuster selon votre cas de figure.
          static float last_value = NAN;
          if (isnan(last_value) || std::abs(x - last_value) < MAX_DIFFERENCE)
            return last_value = x;
          else
            return {};      

# Energie Active soutirée Index03 HC Blanc
  - platform: teleinfo
    id: hcblanc
    tag_name: "EASF03"
    name: "Linky HC Blanc kWh"
    unit_of_measurement: "kWh"
    icon: mdi:flash
    teleinfo_id: myteleinfo
    device_class: "energy"
    state_class: "total_increasing"      
    filters:
      - lambda: return x / 1000;
      - filter_out: 0.0
      - lambda: |-
          float MAX_DIFFERENCE = id(ecart_max);  // Difference max entre de lecture, à ajuster selon votre cas de figure.
          static float last_value = NAN;
          if (isnan(last_value) || std::abs(x - last_value) < MAX_DIFFERENCE)
            return last_value = x;
          else
            return {};      

# Energie Active soutirée Index04 HP Blanc
  - platform: teleinfo
    id: hpblanc
    tag_name: "EASF04"
    name: "Linky HP Blanc kWh"
    unit_of_measurement: "kWh"
    icon: mdi:flash
    teleinfo_id: myteleinfo
    device_class: "energy"
    state_class: "total_increasing"      
    filters:
      - lambda: return x / 1000;
      - filter_out: 0.0   
      - lambda: |-
          float MAX_DIFFERENCE = id(ecart_max);  // Difference max entre de lecture, à ajuster selon votre cas de figure.
          static float last_value = NAN;
          if (isnan(last_value) || std::abs(x - last_value) < MAX_DIFFERENCE)
            return last_value = x;
          else
            return {};       

# Energie Active soutirée Index05 HC Rouge
  - platform: teleinfo
    id: hcrouge
    tag_name: "EASF05"
    name: "Linky HC Rouge kWh"
    unit_of_measurement: "kWh"
    icon: mdi:flash
    teleinfo_id: myteleinfo
    device_class: "energy"
    state_class: "total_increasing"      
    filters:
      - multiply: 0.001
      - filter_out: 0.0
      - lambda: |-
          float MAX_DIFFERENCE = id(ecart_max);  // Difference max entre de lecture, à ajuster selon votre cas de figure.
          static float last_value = NAN;
          if (isnan(last_value) || std::abs(x - last_value) < MAX_DIFFERENCE)
            return last_value = x;
          else
            return {};
# Energie Active soutirée Index06 HP Rouge
  - platform: teleinfo
    id: hprouge
    tag_name: "EASF06"
    name: "Linky HP Rouge kWh"
    unit_of_measurement: "kWh"
    icon: mdi:flash
    teleinfo_id: myteleinfo
    device_class: "energy"
    state_class: "total_increasing"  
    filters:
      - lambda: return x / 1000;
      - filter_out: 0.0   
      - lambda: |-
          float MAX_DIFFERENCE = id(ecart_max);  // Difference max entre de lecture, à ajuster selon votre cas de figure.
          static float last_value = NAN;
          if (isnan(last_value) || std::abs(x - last_value) < MAX_DIFFERENCE)
            return last_value = x;
          else
            return {};       

#Puissance apparente de coupure
  - platform: teleinfo
    id: pcoup
    tag_name: "PCOUP"
    name: "Linky PCOUP"
    unit_of_measurement: "kVA"
    device_class: "power"
    state_class: "measurement"      
    icon: mdi:flash
    teleinfo_id: myteleinfo

#Puissance apparente instantanée ph1 

  - platform: teleinfo
    id: papp
    tag_name: "SINSTS"
    name: "Linky PAPP"
    unit_of_measurement: "VA"
    device_class: "power"
    state_class: "measurement"  
    icon: mdi:flash
    teleinfo_id: myteleinfo


#Tension moyenne ph1
  - platform: teleinfo
    id: umoy1
    tag_name: "UMOY1"
    name: "Linky Umoy"
    unit_of_measurement: "V"
    device_class: "voltage"
    state_class: "measurement"      
    icon: mdi:flash
    teleinfo_id: myteleinfo

#Courant efficace ph1
  - platform: teleinfo
    id: iinst
    tag_name: "IRMS1"
    name: "Linky I Inst"
    unit_of_measurement: "A"
    device_class: "current"
    state_class: "measurement"       
    icon: mdi:flash
    teleinfo_id: myteleinfo

# Numero du Tarif en cours
  - platform: teleinfo
    id: ntarif
    tag_name: "NTARF"
    name: "linky N Tarif"
    unit_of_measurement: ""
    icon: mdi:flash
    teleinfo_id: myteleinfo

############### TEMPLATE  ######################"
# Calcul du ratio de la puissance apparente utilisée en % par rapport au contrat
  - platform: template
    name: "Linky P/PCoup"
    id: p100
    icon: mdi:flash
    unit_of_measurement: "%"
    accuracy_decimals: 0
    lambda: |-
      return ((id(papp).state/1000)/id(pcoup).state*100);

# Lecture dans HA de la conso du jour
  - platform: homeassistant
    name: "HCHP J"
    unit_of_measurement: "kWh"
    entity_id: sensor.compteur_energie_total_jour_tous_tarifs
    id: hphcj

    
#######################################
text_sensor:
# Registre de statuts
  - platform: teleinfo
    id: stge
    tag_name: "STGE"
    name: "linky Statuts"
    icon: mdi:flash
    teleinfo_id: myteleinfo
    on_value:
      then:
        - lambda: |-
            std::string hex = x;
            if (hex.length() != 8) {
              ESP_LOGD("linky", "Erreur format registre STGE: %s", hex.c_str());
              return;
            }
            std::string binary = "";
            for (char c : hex) {
              int val = std::stoi(std::string(1, c), nullptr, 16);
              for (int i = 3; i >= 0; i--) {
                binary += (val & (1 << i)) ? "1" : "0";
              }
            }
            id(hex_to_binary) = binary;
            id(update_binary) = true;
            ESP_LOGD("linky", "STGE=%s, Binaire=%s", hex.c_str(), binary.c_str());
# nom du calendrier tarifaire
  - platform: teleinfo
    id: ngtf
    tag_name: "NGTF"
    name: "linky Nom calendrier tarifaire"
    icon: mdi:flash
    teleinfo_id: myteleinfo

# Libellé tarif fournisseur en cours
  - platform: teleinfo
    id: ltarf
    tag_name: "LTARF"
    name: "linky Libelle tarif fournisseur en cours"
    icon: mdi:flash
    teleinfo_id: myteleinfo

# Convertion du tarif en cours
  - platform: template
    id: tarif
    name: "Linky PTEC"
    lambda: |-
      if ( id(ntarif).state == 1 || id(ntarif).state == 3 || id(ntarif).state == 5){
        return { "HC.." };
      } else {
        return { "HP.." };
      }

  # Bits 1-3: Organe de coupure
  - platform: template
    name: "Linky Organe de Coupure"
    id: linky_organe_de_coupure
    icon: "mdi:flash"
    lambda: |-
      if (!id(update_binary) || id(hex_to_binary).length() != 32) return {};
      std::string bits_1_3 = id(hex_to_binary).substr(28, 3);
      if (bits_1_3 == "000") return {"Fermé"};
      else if (bits_1_3 == "001") return {"Ouvert sur Surpuissance"};
      else if (bits_1_3 == "010") return {"Ouvert sur Surtension"};
      else if (bits_1_3 == "011") return {"Ouvert sur Délestage"};
      else if (bits_1_3 == "100") return {"Ouvert sur Ordre CPL ou Euridis"};
      else if (bits_1_3 == "101") return {"Ouvert sur Surchauffe avec I>Imax"};
      else if (bits_1_3 == "110") return {"Ouvert sur Surchauffe avec I<Imax"};
      else return {"??"};

  # Bit 4: Cache-borne
  - platform: template
    name: "Linky Cache Borne"
    id: linky_cache_borne
    icon: "mdi:flash"
    lambda: |-
      if (!id(update_binary) || id(hex_to_binary).length() != 32) return {};
      return (id(hex_to_binary)[27] == '0') ? std::string("Ferme") : std::string("Ouvert");

  # Bit 6: Surtension
  - platform: template
    name: "Linky Surtension"
    id: linky_surtension
    icon: "mdi:flash"
    lambda: |-
      if (!id(update_binary) || id(hex_to_binary).length() != 32) return {};
      return (id(hex_to_binary)[25] == '0') ? std::string("Pas de Surtension") : std::string("Surtension");

  # Bit 7: Dépassement Puissance de Référence
  - platform: template
    name: "Linky Depassement Pref"
    id: linky_dep_pref
    icon: "mdi:flash"
    lambda: |-
      if (!id(update_binary) || id(hex_to_binary).length() != 32) return {};
      return (id(hex_to_binary)[24] == '0') ? std::string("Pas de Depassement") : std::string("Depassement en Cours");

  # Bit 8: Producteur/Consommateur
  - platform: template
    name: "Linky Fonctionnement"
    id: linky_fonct_prod_conso
    icon: "mdi:flash"
    lambda: |-
      if (!id(update_binary) || id(hex_to_binary).length() != 32) return {};
      return (id(hex_to_binary)[23] == '0') ? std::string("Consommateur") : std::string("Producteur");

  # Bits 10-13: Tarif en cours (fourniture)
  - platform: template
    name: "Linky Tarif Fourniture"
    id: linky_tarif_fourniture
    icon: "mdi:flash"
    lambda: |-
      if (!id(update_binary) || id(hex_to_binary).length() != 32) return {};
      std::string bits_10_13 = id(hex_to_binary).substr(18, 4);
      if (bits_10_13 == "0000") return {"HC BLEU"};
      else if (bits_10_13 == "0001") return {"HP BLEU"};
      else if (bits_10_13 == "0010") return {"HC BLANC"};
      else if (bits_10_13 == "0011") return {"HP BLANC"};
      else if (bits_10_13 == "0100") return {"HP ROUGE"};
      else if (bits_10_13 == "0101") return {"HC ROUGE"};
      else if (bits_10_13 == "0110") return {"Energie ventilée sur index 7"};
      else if (bits_10_13 == "0111") return {"Energie ventilée sur index 8"};
      else if (bits_10_13 == "1000") return {"Energie ventilée sur index 9"};
      else if (bits_10_13 == "1001") return {"Energie ventilée sur index 10"};
      else return {"??"};

  # Bits 14-15: Tarif distributeur
  - platform: template
    name: "Linky Tarif Distributeur"
    id: linky_tarif_distributeur
    icon: "mdi:flash"
    lambda: |-
      if (!id(update_binary) || id(hex_to_binary).length() != 32) return {};
      std::string bits_14_15 = id(hex_to_binary).substr(16, 2);
      if (bits_14_15 == "00") return {"Energie ventilée sur index 1"};
      else if (bits_14_15 == "01") return {"Energie ventilée sur index 2"};
      else if (bits_14_15 == "10") return {"Energie ventilée sur index 3"};
      else if (bits_14_15 == "11") return {"Energie ventilée sur index 4"};
      else return {"??"};

  # Bit 16: Mode dégradé horloge
  - platform: template
    name: "Linky Mode Horloge"
    id: linky_mode_horloge
    icon: "mdi:flash"
    lambda: |-
      if (!id(update_binary) || id(hex_to_binary).length() != 32) return {};
      return (id(hex_to_binary)[15] == '0') ? std::string("Horloge correcte") : std::string("Horloge mode degrade");

  # Bit 17: Etat TIC
  - platform: template
    name: "Linky Etat TIC"
    id: linky_etat_tic
    icon: "mdi:flash"
    lambda: |-
      if (!id(update_binary) || id(hex_to_binary).length() != 32) return {};
      return (id(hex_to_binary)[14] == '0') ? std::string("Mode Historique") : std::string("Mode Standard");

  # Bits 19-20: Etat communication Euridis
  - platform: template
    name: "Linky Com Euridis"
    id: linky_com_euridis
    icon: "mdi:flash"
    lambda: |-
      if (!id(update_binary) || id(hex_to_binary).length() != 32) return {};
      std::string bits_19_20 = id(hex_to_binary).substr(11, 2);
      if (bits_19_20 == "00") return {"Com désactivée"};
      else if (bits_19_20 == "01") return {"Com active sans sécurité"};
      else if (bits_19_20 == "11") return {"Com active avec sécurité"};
      else return {"Com ??"};

  # Bits 21-22: Statut CPL
  - platform: template
    name: "Linky Statut CPL"
    id: linky_statut_cpl
    icon: "mdi:flash"
    lambda: |-
      if (!id(update_binary) || id(hex_to_binary).length() != 32) return {};
      std::string bits_21_22 = id(hex_to_binary).substr(9, 2);
      if (bits_21_22 == "00") return {"New/unlock"};
      else if (bits_21_22 == "01") return {"New/Lock"};
      else if (bits_21_22 == "11") return {"Registered"};
      else return {"Com ??"};

  # Bit 23: Synchro CPL
  - platform: template
    name: "Linky Synchro CPL"
    id: linky_synchro_cpl
    icon: "mdi:flash"
    lambda: |-
      if (!id(update_binary) || id(hex_to_binary).length() != 32) return {};
      return (id(hex_to_binary)[8] == '0') ? std::string("Compteur non synchronise") : std::string("Compteur synchronise");

  # Bits 24-25: Couleur du jour (Tempo)
  - platform: template
    name: "Linky Couleur Jour Tempo"
    id: linky_couleur_j_tempo
    icon: "mdi:flash"
    lambda: |-
      if (!id(update_binary) || id(hex_to_binary).length() != 32) return {};
      std::string bits_24_25 = id(hex_to_binary).substr(6, 2);
      if (bits_24_25 == "00") return {"Contrat non Tempo"};
      else if (bits_24_25 == "01") return {"Bleu"};
      else if (bits_24_25 == "10") return {"Blanc"};
      else if (bits_24_25 == "11") return {"Rouge"};
      else return {"Couleur J tempo indéfinie"};

  # Bits 26-27: Couleur J+1 (Tempo)
  - platform: template
    name: "Linky Couleur J+1 Tempo"
    id: linky_couleur_j1_tempo
    icon: "mdi:flash"
    lambda: |-
      if (!id(update_binary) || id(hex_to_binary).length() != 32) return {};
      std::string bits_26_27 = id(hex_to_binary).substr(4, 2);
      if (bits_26_27 == "00") return {"Pas d'information"};
      else if (bits_26_27 == "01") return {"Bleu"};
      else if (bits_26_27 == "10") return {"Blanc"};
      else if (bits_26_27 == "11") return {"Rouge"};
      else return {"Couleur J+1 tempo indéfinie"};

  # Bits 28-29: Préavis Pointes Mobiles
  - platform: template
    name: "Linky Préavis Pointes Mobiles"
    id: linky_preavis_p_mobiles
    icon: "mdi:flash"
    lambda: |-
      if (!id(update_binary) || id(hex_to_binary).length() != 32) return {};
      std::string bits_28_29 = id(hex_to_binary).substr(2, 2);
      if (bits_28_29 == "00") return {"Pas de préavis en cours"};
      else if (bits_28_29 == "01") return {"Préavis PM1 en cours"};
      else if (bits_28_29 == "10") return {"Préavis PM2 en cours"};
      else if (bits_28_29 == "11") return {"Préavis PM3 en cours"};
      else return {"Préavis en cours indéfini"};

  # Bits 30-31: Pointes Mobiles
  - platform: template
    name: "Linky Pointe Mobile"
    id: linky_pointe_mobile
    icon: "mdi:flash"
    lambda: |-
      if (!id(update_binary) || id(hex_to_binary).length() != 32) return {};
      std::string bits_30_31 = id(hex_to_binary).substr(0, 2);  // Correction ici : bits 30-31 sont les 2 premiers
      if (bits_30_31 == "00") return {"Pas de pointe mobile"};
      else if (bits_30_31 == "01") return {"PM1 en cours"};
      else if (bits_30_31 == "10") return {"PM2 en cours"};
      else if (bits_30_31 == "11") return {"PM3 en cours"};
      else return {"Pointe mobile indéfinie"};


#Etat de la connection
binary_sensor:
  - platform: status
    name: "${friendly_name} Status"

# Capteurs pour les bits du registre STGE
  # Bit 0: Contact Sec
  - platform: template
    name: "Linky Contact Sec"
    id: linky_contact_sec
    device_class: connectivity
    icon: "mdi:flash"
    lambda: |-
      if (!id(update_binary) || id(hex_to_binary).length() != 32) return {};
      return (id(hex_to_binary)[31] == '0');  // Fermé = true (on), Ouvert = false (off)

  # Bit 9: Sens de l'énergie active
  - platform: template
    name: "Linky Sens Energie Active"
    id: linky_sens_energie_active
    device_class: power
    icon: "mdi:flash"
    lambda: |-
      if (!id(update_binary) || id(hex_to_binary).length() != 32) return {};
      return (id(hex_to_binary)[22] == '0');  // Positive = true (on), Négative = false (off)


switch:
  - platform: restart
    name: "${friendly_name} Restart"

# Affichage
display:
  - platform: ssd1306_i2c
    model: "SSD1306 128x64"
    address: 0x3C
    brightness: 100%
    lambda: |-
      it.printf(0,0,id(arial),"P=%.0f VA",id(papp).state);
      it.printf(75,0,id(arial),"-%.0f %%",id(p100).state);
      it.printf(0,15,id(arial),"I Inst=%.0f A",id(iinst).state);
      it.printf(0,30,id(arial),"ConsoJ=%.0f kWh",id(hphcj).state);
      std::string tarif_ = id(ltarf).state;
      it.printf(0,45,id(arial), tarif_.c_str());      

interval:
  - interval: 1h
    then:
      - script.execute: calcul_led_jour

# ------------------------  Scripts
script:
# 
# Couleur du Jour en cours
  - id: calcul_led_jour
    mode: single
    then:
      - logger.log:
          format: "Ltarf= %f"
          args: [ 'id(ntarif).state']

      - if: 
          condition:
            - lambda: |-
                return ( id(ntarif).state == 1 || id(ntarif).state == 2);

          then:
            - light.control:
                id: led1
                brightness: 75%
                state: on
                red: 0% 
                green: 0%
                blue: 100%    

      - if: 
          condition:
            - lambda: |-
                return ( id(ntarif).state == 3 || id(ntarif).state == 4);
          then:
            - light.control:
                id: led1
                brightness: 75%
                state: on
                red: 100% 
                green: 100%
                blue: 100%    
          
      - if: 
          condition:
            - lambda: |-
                return ( id(ntarif).state == 5 || id(ntarif).state == 6);
          then:
            - light.control:
                id: led1
                brightness: 75%
                state: on
                red: 100% 
                green: 0%
                blue: 0%    

Version HC/HP

les filtres « filter_out: 0.0 » sont à mettre en action si et seulement si les index sont supérieurs à 0 sinon vous afficherez « indisponible ».

Le filtre « Lambda » permet d’éviter des incohérences dans l’évolution des index, il m’est arrivé de lire des valeurs très faibles mais supérieure à 0 pour des raisons que j’ignore.

L’écart se regle dans la variable « globals » ecart_max

substitutions:
  device_name: esp124-tic
  adress_ip: "192.168.0.124"
  friendly_name: esp124
  time_timezone: "Europe/Paris"
  
esphome:
  name: ${device_name}
  platform: ESP32
  board: mhetesp32minikit
  project:
    name: "rem81.esp124-TIC"
    version: "1.0.1"
  platformio_options:
    lib_deps: NeoPixelBus@2.6.0
  on_boot:
    then:
      - light.control:
          id: led1
          brightness: 0.25
          state: on
wifi:
  networks:
    - ssid: !secret wifi_mi4
      password: !secret mdpwifi_mi4
      priority: 2  
    - ssid: !secret wifi_esp
      password: !secret mdpwifi_esp
      priority: 1
    - ssid: !secret wifi
      password: !secret mdpwifi
      priority: 0
  reboot_timeout: 5min

  manual_ip:
    static_ip: ${adress_ip}
    gateway: 192.168.0.254
    subnet: 255.255.255.0


# Enable logging
logger:
#  baud_rate: 0
# Enable Home Assistant API
api:

ota:

web_server:
  port: 80

font:
  - file: "fonts/arial.ttf"
    id: arial
    size: 15

i2c:
  sda: GPIO21 #D2=pin 19
  scl: GPIO22 #D1=pin 20
  scan: True
  id: bus_a
  
# 
uart:
  id: uart_a
  rx_pin: GPIO23
#  tx_pin: GPIO1
  baud_rate: 9600
  parity: EVEN
  data_bits: 7

teleinfo:
  id: myteleinfo
  uart_id: uart_a
  update_interval: 10s
  historical_mode: false
  
# Led WS2812 RGB  
light:
  - platform: partition
    name: led1
    id: led1
    default_transition_length: 0s
    segments:
      - id: rgb_led
        from: 0
        to: 0

  - platform: neopixelbus
    num_leds: 1
    pin: GPIO18
    name: "RGB strip"
    variant: ws2812
    id: rgb_led
    default_transition_length: 0s  

globals:
  # Ecart maximum admissible entre deux lectures
   - id: ecart_max
     type: float
     restore_value: no
     initial_value: '50'

sensor:
# Energie Active soutirée totale
  - platform: teleinfo
    id: hc_hp
    tag_name: "EAST"
    name: "Linky HPHC KWH"
    unit_of_measurement: "kWh"
    icon: mdi:flash
    teleinfo_id: myteleinfo
    device_class: "energy"
    state_class: "total_increasing"  
    filters:
      - filter_out: 0.0
      - lambda: |-
          return x/1000;


# Energie Active soutirée Index01
  - platform: teleinfo
    id: hchc
    tag_name: "EASF01"
    name: "Linky HC Wh"
    unit_of_measurement: "Wh"
    icon: mdi:flash
    teleinfo_id: myteleinfo
    filters:
      - filter_out: 0.0
      - lambda: |-
          float MAX_DIFFERENCE = id(ecart_max);  // Difference max entre de lecture, à ajuster selon votre cas de figure.
          static float last_value = NAN;
          if (isnan(last_value) || std::abs(x - last_value) < MAX_DIFFERENCE)
            return last_value = x;
          else
            return {};    
    
# Energie Active soutirée Index02    
  - platform: teleinfo
    id: hchp
    tag_name: "EASF02"
    name: "Linky HP Wh"
    unit_of_measurement: "Wh"
    icon: mdi:flash
    teleinfo_id: myteleinfo
    filters:
      - filter_out: 0.0    
      - lambda: |-
          float MAX_DIFFERENCE = id(ecart_max);  // Difference max entre de lecture, à ajuster selon votre cas de figure.
          static float last_value = NAN;
          if (isnan(last_value) || std::abs(x - last_value) < MAX_DIFFERENCE)
            return last_value = x;
          else
            return {};    

#Puissance apparente de coupure
  - platform: teleinfo
    id: pcoup
    tag_name: "PCOUP"
    name: "Linky PCOUP"
    unit_of_measurement: "kVA"
    icon: mdi:flash
    teleinfo_id: myteleinfo

#Puissance apparente instantanée ph1 

  - platform: teleinfo
    id: papp
    tag_name: "SINSTS"
    name: "Linky PAPP"
    unit_of_measurement: "VA"
    device_class: "power"
    state_class: "measurement"  
    icon: mdi:flash
    teleinfo_id: myteleinfo
    on_value:
      - if:
          condition:
            sensor.in_range:
              id: papp
              below: 1000
          then: 
            - light.control:
                id: led1
                red: 0%
                green: 100% # vert
                blue: 0%
      - if:
          condition:
            sensor.in_range:
              id: papp
              above: 1000
              below: 3000
          then: 
            - light.control:
                id: led1
                red: 0%
                green: 0% # bleu
                blue: 100%
      - if:
          condition:
            sensor.in_range:
              id: papp
              above: 3000
          then: 
            - light.control:
                id: led1
                red: 100% #rouge
                green: 0%
                blue: 0%    

#Tension moyenne ph1
  - platform: teleinfo
    id: umoy1
    tag_name: "UMOY1"
    name: "Linky Umoy"
    unit_of_measurement: "V"
    icon: mdi:flash
    teleinfo_id: myteleinfo

#Courant efficace ph1
  - platform: teleinfo
    id: iinst
    tag_name: "IRMS1"
    name: "Linky I Inst"
    unit_of_measurement: "A"
    icon: mdi:flash
    teleinfo_id: myteleinfo

# Numero du Tarif en cours
  - platform: teleinfo
    id: ntarif
    tag_name: "NTARF"
    name: "linky N Tarif"
    unit_of_measurement: ""
    icon: mdi:flash
    teleinfo_id: myteleinfo

############### TEMPLATE  ######################"
# Calcul du ratio de la puissance apparente utilisée en % par rapport au contrat
  - platform: template
    name: "Linky P/PCoup"
    id: p100
    icon: mdi:flash
    unit_of_measurement: "%"
    accuracy_decimals: 0
    lambda: |-
      return ((id(papp).state/1000)/id(pcoup).state*100);

# Lecture dans HA de la conso du jour
  - platform: homeassistant
    name: "HCHP J"
    unit_of_measurement: "kWh"
    entity_id: sensor.energie_totale_jour
    id: hphcj

# Puissance du signal WIFI
  - platform: wifi_signal
    id: wif
    name: "${friendly_name} WiFi Signal Sensor"
    update_interval: 60s
# Temps de fonctionnement de l'ESP    
  - platform: uptime
    id: uptime_seconds
    name: "${friendly_name} Uptime"
    update_interval: 60s
    unit_of_measurement: s
    accuracy_decimals: 0
    force_update: false
    icon: mdi:timer
    
#######################################
text_sensor:
# Registre de statuts
  - platform: teleinfo
    id: stge
    tag_name: "STGE"
    name: "linky Statuts"
    icon: mdi:flash
    teleinfo_id: myteleinfo
    
# Convertion du tarif en cours
  - platform: template
    id: tarif
    name: "Linky PTEC"
    lambda: |-
      if ( id(ntarif).state == 1 ) {
        return { "HC.." };
      } else {
        return { "HP.." };
      }
# Affichage du temps de fonctionnement
  - platform: template
    name: "${friendly_name} Uptime"
    update_interval: 60s
    icon: mdi:clock-start
    lambda: |-
      int seconds = (id(uptime_seconds).state);
      int days = seconds / (24 * 3600);
      seconds = seconds % (24 * 3600);
      int hours = seconds / 3600;
      seconds = seconds % 3600;
      int minutes = seconds /  60;
      seconds = seconds % 60;
      if ( days ) {
        return { (String(days) +"d " + String(hours) +"h " + String(minutes) +"m "+ String(seconds) +"s").c_str() };
      } else if ( hours ) {
        return { (String(hours) +"h " + String(minutes) +"m "+ String(seconds) +"s").c_str() };
      } else if ( minutes ) {
        return { (String(minutes) +"m "+ String(seconds) +"s").c_str() };
      } else {
        return { (String(seconds) +"s").c_str() };
      }

#Etat de la connection
binary_sensor:
  - platform: status
    name: "${friendly_name} Status"
# Restart de l'ESP
switch:   
  - platform: restart
    name: "${friendly_name} Restart"

# Affichage
display:
  - platform: ssd1306_i2c
    model: "SSD1306 128x64"
    address: 0x3C
    brightness: 100%
    lambda: |-
      it.printf(0,0,id(arial),"P=%.0f VA",id(papp).state);
      it.printf(75,0,id(arial),"-%.0f %%",id(p100).state);
      it.printf(0,15,id(arial),"I Inst=%.0f A",id(iinst).state);
      it.printf(0,30,id(arial),"ConsoJ=%.0f kWh",id(hphcj).state);
      std::string tarif_ = id(tarif).state;
      it.printf(0,45,id(arial), "Tarif: %s", tarif_.c_str());      


      
   

Registre de Statuts

Le registre de statuts « STGE » (uniquement dispo en mode standard) nous donne un état du Linky en général. Il remonte dans un format de huit caractères ascii. Chaque caractères est codés sur 4 bits qui correspondent à une ou plusieurs informations. Le détail est donné au chapitre 6.2.3.14 du document Enedis ci dessous:

Le décodage est intégré au code ESPHOME

Pour les utilisateurs de APP DAEMON, j’ai écrit une routine qui décode les caractères du registre de statuts permettent de l’exploiter dans HA via l’Addon AppDaemon.

Celle ci convertit les 8 caractères du registre Statuts de l’ASCII vers du Binaire.

Addon Appdaemon

Cet Addon, pour ceux qui ne connaissent pas, permet d’écrire des programmes en Python très élaborés.

Je décrit son installation dans cet article cet article.

Vous trouverez ci après les fichiers « .yaml » et « .py » à installer dans « votre AppDaemon. »

Code de « linky_status.yaml »


Linky:
  class: LinkyStatuts
  module: linky_statuts
  registre: input_text.linky_test_status
#  registre: sensor.linky_statuts

Vous pouvez faire des tests en de-commentant le « registre: input_text.linky_test_status » et en commentant le « registre: input_text.linky_test_status » (il faut un seul « registre » actif à la fois).

Code de « linky_status.py »

import hassapi as hass

# Conversion ASCII vers binaire codé décimal
def repr_bin2(str):
    caract = 0
    decod=""        
    for caract in str:
        decod += bin(int(caract,16))[2::].zfill(4)
    return decod

class LinkyStatuts(hass.Hass):
    def initialize(self):
        self.listen_state(self.statuts_change, self.args["registre"])
        self.log("Initialisation Linky..", log="linky_log")

    def statuts_change(self, entity, attribute, old, new, kwargs):
        statuts = new
        if len(statuts)!=8:
            self.log(f"Erreur format registre de statuts= {statuts}", log="linky_log")
        else:
            self.convertion_status(kwargs)

#  Lecture et conversion du registre de statuts du Linky 
    def convertion_status(self, kwargs):
        statuts = self.get_state(self.args["registre"])
        statuts_binaire=(repr_bin2(statuts))

        # Exploitation des 32 bits du statuts poids forts en tête #
        # Bit 0:Contact Sec#
        bit_0= statuts_binaire[31]
        if bit_0 == "0":
            contact_sec = "Ferme"
        else:
            contact_sec = "Ouvert"

        # Bits 1 à 3: Organe de coupure #
        bit_1_3= statuts_binaire[28:31]
        if bit_1_3 == "000":
            organe_de_coupure = "Ferme"
        elif bit_1_3 == "001":
            organe_de_coupure = "Ouvert sur Surpuissance"
        elif bit_1_3 == "010":
            organe_de_coupure = "Ouvert sur Surtension"
        elif bit_1_3 == "011":
            organe_de_coupure = "Ouvert sur Delestage"
        elif bit_1_3 == "100":
            organe_de_coupure = "Ouvert sur Ordre CPL ou Euridis"
        elif bit_1_3 == "101":
            organe_de_coupure = "Ouvert sur Surchauffe avec I>Imax"
        elif bit_1_3 == "110":
            organe_de_coupure = "Ouvert sur Surchauffe avec I<Imax"
        else:
            organe_de_coupure = "??"

        # Bit 4: Etat du cache borne #
        bit_4= statuts_binaire[27]
        if bit_4 == "0":
            cache_borne = "Ferme"
        else:
            cache_borne = "Ouvert"

        # Bit 5: Non utilisé toujours à 0 #

        # Bit 6: Surtension sur une des phases #
        bit_6= statuts_binaire[25]
        if bit_6 == "0":
            surtension = "Pas de Surtension"
        else:
            surtension = "Surtension"

        # Bit 7: Dépassement Puissance de Référence #
        bit_7= statuts_binaire[24]
        if bit_7 == "0":
            dep_pref = "Pas de Depassement"
        else:
            dep_pref = "Depassement en Cours"

        # Bit 8: Producteur ou Consommateur #
        bit_8= statuts_binaire[23]
        if bit_8 == "0":
            fonct_prod_conso = "Consommateur"
        else:
            fonct_prod_conso = "Producteur"

        # Bit 9: Sens de L'énergie active #
        bit_9= statuts_binaire[22]
        if bit_9 == "0":
            sens_energie_act = "Energie active positive"
        else:
            sens_energie_act = "Energie active negative"

        # Bits 10 à 13: Tarif en cours contrat fourniture #
        bit_10_13= statuts_binaire[18:22]
        if bit_10_13 == "0000":
            tarif_fourniture = "Energie ventilee sur index 1"
        elif bit_10_13 == "0001":
            tarif_fourniture = "Energie ventilee sur index 2"
        elif bit_10_13 == "0010":
            tarif_fourniture = "Energie ventilee sur index 3"
        elif bit_10_13 == "0011":
            tarif_fourniture = "Energie ventilee sur index 4"
        elif bit_10_13 == "0100":
            tarif_fourniture = "Energie ventilee sur index 5"
        elif bit_10_13 == "0101":
            tarif_fourniture = "Energie ventilee sur index 6"
        elif bit_10_13 == "0110":
            tarif_fourniture = "Energie ventilee sur index 7"
        elif bit_10_13 == "0111":
            tarif_fourniture = "Energie ventilee sur index 8"
        elif bit_10_13 == "1000":
            tarif_fourniture = "Energie ventilee sur index 9"
        elif bit_10_13 == "1001":
            tarif_fourniture = "Energie ventilee sur index 10"
        else:
            tarif_fourniture = "??"

        # Bits 14 à 15: Tarif en cours contrat fourniture #
        bit_14_15= statuts_binaire[16:18]
        if bit_14_15 == "00":
            tarif_distributeur = "Energie ventilee sur index 1"
        elif bit_14_15 == "01":
            tarif_distributeur = "Energie ventilee sur index 2"
        elif bit_14_15 == "10":
            tarif_distributeur = "Energie ventilee sur index 3"
        elif bit_14_15 == "11":
            tarif_distributeur = "Energie ventilee sur index 4"

        # Bit 16: Mode dégradé horloge #
        bit_16= statuts_binaire[15]
        if bit_16 == "0":
            mode_horloge = "Horloge correcte"
        else:
            mode_horloge = "Horloge mode degrade"

        # Bit 17: Etat TIC #
        bit_17= statuts_binaire[14]
        if bit_17 == "0":
            etat_tic = "Mode Historique"
        else:
            etat_tic = "Mode Standard"

        # Bit 18: Non utilisé #

        # Bits 19 à 20: Etat de la com Euridis #
        bit_19_20= statuts_binaire[11:13]
        if bit_19_20 == "00":
            com_euridis = "Com desactivee"
        elif bit_19_20 == "01":
            com_euridis = "Com Active sans securite"
        elif bit_19_20 == "11":
            com_euridis = "Com Active avec securite"
        else:
            com_euridis = "Com ??"

        # Bits 21 à 22: Statut du CPL #
        bit_21_22= statuts_binaire[9:11]
        if bit_21_22 == "00":
            statut_cpl = "New/unlock"
        elif bit_21_22 == "01":
            statut_cpl = "New/Lock"
        elif bit_21_22 == "11":
            statut_cpl = "Registered"
        else:
            statut_cpl = "Com ??"

        # Bit 23: Synchro CPL #
        bit_23= statuts_binaire[8]
        if bit_23 == "0":
            synchro_cpl = "Compteur non synchronise"
        else:
            synchro_cpl = "Compteur synchronise"

        # Bits 24_25: Couleur du jour contrat historique Tempo #
        bit_24_25= statuts_binaire[6:8]
        if bit_24_25 == "00":
            couleur_j_tempo = "Contrat non Tempo"
        elif bit_24_25 == "01":
            couleur_j_tempo = "Bleu"
        elif bit_24_25 == "10":
            couleur_j_tempo = "Blanc"
        elif bit_24_25 == "11":
            couleur_j_tempo = "Rouge"
        else:
            couleur_j_tempo = "Couleur J tempo indefinie"

        # Bits 26_27: Couleur du J+1 contrat historique Tempo #
        bit_26_27= statuts_binaire[4:6]
        if bit_26_27 == "00":
            couleur_j1_tempo = "Contrat non Tempo"
        elif bit_26_27 == "01":
            couleur_j1_tempo = "Bleu"
        elif bit_26_27 == "10":
            couleur_j1_tempo = "Blanc"
        elif bit_26_27 == "11":
            couleur_j1_tempo = "Rouge"
        else:
            couleur_j1_tempo = "Couleur J+1 tempo indefinie"

        # Bits 28_29: Préavis Pointes mobiles #
        bit_28_29= statuts_binaire[2:4]
        if bit_28_29 == "00":
            préavis_p_mobiles = "pas de preavis en cours"
        elif bit_28_29 == "01":
            préavis_p_mobiles = "Preavis PM1 en cours"
        elif bit_28_29 == "10":
            préavis_p_mobiles = "Preavis PM2 en cours"
        elif bit_28_29 == "11":
            préavis_p_mobiles = "Preavis PM3 en cours"
        else:
            préavis_p_mobiles = "Preavis en cours indéfini"
            
        # Bits 30_31: Préavis Pointes mobiles #
        bit_30_31= statuts_binaire[2:4]
        if bit_30_31 == "00":
            pointe_mobile = "Pas de pointe mobile"
        elif bit_30_31 == "01":
            pointe_mobile = "PM1 en cours"
        elif bit_30_31 == "10":
            pointe_mobile = "PM2 en cours"
        elif bit_30_31 == "11":
            pointe_mobile = "PM3 en cours"
        else:
            pointe_mobile = "Pointe mobile indefinie"

        #### Creation et Mise à jour des entités HA  ####

        # Etat du contact Sec (= 1 = fermé en Heure Creuse) #
        if bit_0 == "0":
            self.set_state("binary_sensor.linky_contact_sec", state="on", replace=True, attributes= {"icon": "mdi:flash"})  
        else:
            self.set_state("binary_sensor.linky_contact_sec", state="off", replace=True, attributes= {"icon": "mdi:flash"})  
        
        # Etat de l'organe de coupure #
        self.set_state("sensor.linky_organe_de_coupure", state=organe_de_coupure, replace=True, attributes= {"icon": "mdi:flash"})

        #  Sens de l'énergie #
        if bit_9 == "0":
            self.set_state("binary_sensor.linky_sens_energie_active", state="on", replace=True, attributes= {"icon": "mdi:flash"})   
        else:
            self.set_state("binary_sensor.linky_sens_energie_active", state="off", replace=True, attributes= {"icon": "mdi:flash"})   
        self.set_state("sensor.linky_sens_energie_active", state=sens_energie_act, replace=True, attributes= {"icon": "mdi:flash"})           


        ####   Log de Déboggage   
        #    A commenter ou supprimer si inutile #
        self.log(f"Statuts={statuts}", log="linky_log")
        self.log(f"Statuts Binaire={statuts_binaire}", log="linky_log")
        self.log(f"bit_0={bit_0} / Contact Sec={contact_sec}", log="linky_log")
        self.log(f"bit_1_3={bit_1_3} / Organe de coupure={organe_de_coupure}", log="linky_log")
        self.log(f"bit_4={bit_4} / Cache_borne={cache_borne}", log="linky_log")
        self.log(f"bit_6={bit_6} / Surtension={surtension}", log="linky_log")
        self.log(f"bit_7={bit_7} / Depassement Pref={dep_pref}", log="linky_log")
        self.log(f"bit_8={bit_8} / Fonctionnement={fonct_prod_conso}", log="linky_log")
        self.log(f"bit_9={bit_9} / Sens Energie Active={sens_energie_act}", log="linky_log")
        self.log(f"bit_10_13={bit_10_13} / Tarif fourniture={tarif_fourniture}", log="linky_log")
        self.log(f"bit_14_15={bit_14_15} / Tarif distributeur={tarif_distributeur}", log="linky_log")
        self.log(f"bit_16={bit_16} / Mode degrade horloge={mode_horloge}", log="linky_log")
        self.log(f"bit_17={bit_17} / Etat TIC={etat_tic}", log="linky_log")
        self.log(f"bit_19_20={bit_19_20} / Com Euridis={com_euridis}", log="linky_log")
        self.log(f"bit_21_22={bit_21_22} / Statut CPL={statut_cpl}", log="linky_log")
        self.log(f"bit_23={bit_23} / Synchro CPL={synchro_cpl}", log="linky_log")
        self.log(f"bit_24_25={bit_24_25} / Couleur Jour tempo={couleur_j_tempo}", log="linky_log")
        self.log(f"bit_26_27={bit_26_27} / Couleur J+1 tempo={couleur_j1_tempo}", log="linky_log")
        self.log(f"bit_28_29={bit_28_29} / Preavis Pointes Mobiles={préavis_p_mobiles}", log="linky_log")
        self.log(f"bit_30_31={bit_30_31} / Pointe Mobile={pointe_mobile}", log="linky_log")        
        self.log(f"Statuts Linky:{statuts}", log="linky_log")
        self.log(f"Statuts Binaire: {statuts_binaire}", log="linky_log")
        self.log(f"Sens Energie Active: {sens_energie_act}", log="linky_log")
    
#        self.call_service('notify/telegram', message="Statuts Linky:" + format(statuts))
#        self.call_service('notify/telegram', message="Sens Energie Active: " + format(sens_energie_act))
#        self.call_service('notify/telegram', message="Tarif Fourniture: " +format(tarif_fourniture))

Dans la rubrique « Mise à jour des entités HA » du code ci-dessus, vous personnalisez les entités que vous souhaitez exploiter. Elles sont crées et mise à jour dans HA. J’en ai quatre dans mon cas.

Les intitulés parlent d’eux-même. Si vous souhaitez en ajouter d’autre, il suffit de les ajouter dans le fichier ci-dessus.

Vous pouvez visualiser vos entités dans HA:

Dans un premier temps, utilisez le mode test et amusez vous, puis basculer sur la lecture du statuts et exploitez les infos via des automatismes ou autres.

Affichage local

J’affiche quelques valeurs en local sur un oled SSD1306 128×64, pratique pour une visu rapide à la puissance et l’intensité instantanée, du ratio P/ Pref , la conso du jour.

Je joue également avec la Led WS2812 RGB connectée sur le GPIO18. En fonction de la puissance la led change de couleur:

  • Verte de 0 à 1000 VA
  • Bleu entre 1000 et 3000 VA
  • Rouge si supérieure à 3000 VA
  • Les combinaisons sont infinies et facilement programmables.

Codes du fichier linky.yaml Version HC/HP

On y retrouve:

  • les « utility meter » qui comptabilisent les energies consommées HC/HP par jour, semaine, mois, année
  • Un « input_boolean com_linky » indiquant l’état de la communication avec le Linky = 1 si com ok
  • deux « input_number » cout d’un kWh aux HC et HP
  • un ‘binary_sensor » HC utilisé dans l’affichage des graphiques de puissance permettant de matérialiser la zone HC
  • les « templates » qui cumulent les HC/HP des utility meter et calculent en € les coûts de consommations
  • Les entités du registre de statuts
####################################################
#                                                  #
#                       LINKY                      #
#                                                  #
####################################################


utility_meter:
# usage jour  
  energy_total_usage_daily:
    source: sensor.linky_hphc_kwh
    cycle: daily
    tariffs:
      - hp
      - hc
# usage semaine
  energy_total_usage_weekly:
    source: sensor.linky_hphc_kwh
    cycle: weekly
    tariffs:
      - hp
      - hc
# usage mois
  energy_total_usage_monthly:
    source: sensor.linky_hphc_kwh
    cycle: monthly
    tariffs:
      - hp
      - hc
#usage an
  energy_total_usage_yearly:
    source: sensor.linky_hphc_kwh
    cycle: yearly
    tariffs:
      - hp
      - hc
## 
input_boolean:
  com_linky:
    name: Comm Linky OK=On

input_number:
  # Calcul des coûts journaliers
  cout_kwh_hp:
    name: Cout du Kwh HP
    min: 0
    max: 10
    unit_of_measurement: €
    icon: mdi:currency-eur
    step: 0.00001
    mode: box

  cout_kwh_hc:
    name: Cout du Kwh HC
    min: 0
    max: 10
    unit_of_measurement: €
    icon: mdi:currency-eur
    step: 0.00001
    mode: box

input_text:
  # Permet de simuler des Statuts pour les tests unitaires
  linky_test_status:   
    name: Test statuts Linky

template:
  - binary_sensor:
    # Utilisé dans l'affichage des graphiques de puissance permettant de matérialiser la zone HC
    # = true si HC
    - name: "display_hp_hc"
      unique_id: "display_hp_hc"
      state: >-
        {{ (states.sensor.linky_n_tarif.state == '1') }}" # =1 si HC =2 si H

# Consommation journalière HP + HC - addition des utility meter
  - sensor:
    - name: "Energie Totale Jour"
      unique_id: "energy_total_daily"
      state: >-
        {% set p = states('sensor.energy_total_usage_daily_hp') | float(default=0) | round(2) %}
        {% set o = states('sensor.energy_total_usage_daily_hc') | float(default=0) | round(2) %}
        {{ (o + p) | round(2) }}
      unit_of_measurement: "kWh"
      device_class: "energy"
      state_class: "total"

# Consommation semaine HP + HC
    - name: "energy_total_weekly"
      state: >-
        {% set p = states('sensor.energy_total_usage_weekly_hp') | float(default=0) | round(2) %}
        {% set o = states('sensor.energy_total_usage_weekly_hc') | float(default=0) | round(2) %}
        {{ (o + p) | round(2) }}
      unit_of_measurement: "kWh"
      device_class: "energy"
      state_class: "total"  

# Consommation mensuelle HP + HC
    - name: "energy_total_monthly"
      state: >-
        {% set p = states('sensor.energy_total_usage_monthly_hp') | float(default=0) | round(2) %}
        {% set o = states('sensor.energy_total_usage_monthly_hc') | float(default=0) | round(2) %}
        {{ (o + p) | round(2) }}
      unit_of_measurement: "kWh"
      device_class: "energy"
      state_class: "total"  

# Consommation annuelle HP + HC
    - name: "energy_total_yearly"
      state: >-
        {% set p = states('sensor.energy_total_usage_yearly_hp') | float(default=0) | round(2) %}
        {% set o = states('sensor.energy_total_usage_yearly_hc') | float(default=0) | round(2) %}
        {{ (o + p) | round(2) }}
      unit_of_measurement: "kWh"
      device_class: "energy"
      state_class: "total"  

# Cout de l'Energie
# les couts du kWh HP et HC sont calculés dans excel en fonction des factures recues
# et saisis manuellement dans HA

    - name: "Cout Energy Total Jour HPHC"
      state: >-
        {% set hp = states('sensor.energy_total_usage_daily_hp') | float(default=0) | round(2) %}
        {% set hc = states('sensor.energy_total_usage_daily_hc') | float(default=0) | round(2) %}
        {% set chp = states('input_number.cout_kwh_hp') | float(default=0) | round(5) %}
        {% set chc = states('input_number.cout_kwh_hc') | float(default=0) | round(5) %}
        {{((hc*chc) + (hp*chp)) | round(2) }}
      unit_of_measurement: "€"
      device_class: "monetary"
      state_class: "total"  
      unique_id: "cout_energy_total_jour_hphc"

    - name: "Cout Energy Total Jour HP"
      state: >-
        {% set hp = states('sensor.energy_total_usage_daily_hp') | float(default=0) | round(2) %}
        {% set chp = states('input_number.cout_kwh_hp') | float(default=0) | round(5) %}
        {{(hp*chp) | round(2) }}
      unit_of_measurement: "€"
      device_class: "monetary"
      state_class: "total"  
      unique_id: "cout_energy_total_jour_hp"
      
    - name: "Cout Energy Total Jour HC"
      state: >-
        {% set hc = states('sensor.energy_total_usage_daily_hc') | float(default=0) | round(2) %}
        {% set chc = states('input_number.cout_kwh_hc') | float(default=0) | round(5) %}
        {{(hc*chc) | round(2) }}
      unit_of_measurement: "€"
      device_class: "monetary"
      state_class: "total"  
      unique_id: "cout_energy_total_jour_hc"
      

Codes du fichier linky.yaml Version « Tempo »

####################################################
#                                                  #
#                       LINKY                      #
#                                                  #
####################################################

# Sensor à conserver l'historique dans le tableau Energy
# le sensor sensor.linky_hphc_kwh est mis à zero dans tic Linky 
# depuis le 3/12/2023
# Date à laquelle le comptage des tarifs tempo a été mis en place
#utility_meter:
# usage jour  
#  energy_total_usage_daily:
#    source: sensor.linky_hphc_kwh
#    cycle: daily
#    tariffs:
#      - hp
#      - hc

## 
input_boolean:
  com_linky:
    name: Comm Linky OK=On



input_text:
  # Permet de simuler des Statuts pour les tests unitaires
  linky_test_status:   
    name: Test statuts Linky

template:
  - binary_sensor:
    # Utilisé dans l'affichage des graphiques de puissance permettant de materialiser la zone HC
    # = true si HC
    - name: "display_hp_hc"
      unique_id: "display_hp_hc"
      state: >-
        {{ states('sensor.linky_ptec')|int(default=0) == "HC.." }} # =on si HC =off si HP

  - sensor:

# Template utilisés dans le tableau Energy, ils ont pris le relais des Utility Meter supprimés le 3/11/2023
# Donc NE PAS SUPPRIMER sinon perte de l'historique des consommations

    - name: "energy_total_usage_daily_hc"
      unique_id: "energy_total_usage_daily_hc"
      state: >-
        {{ 0 }}
      unit_of_measurement: "kWh"
      device_class: "energy"
      state_class: "total_increasing"    

    - name: "energy_total_usage_daily_hp"
      unique_id: "energy_total_usage_daily_hp"
      state: >-
        {{ 0 }}
      unit_of_measurement: "kWh"
      device_class: "energy"
      state_class: "total_increasing"

# Calcul du Cout Journalier de l'Energie en fonction du tarif en cours
# Apres passage en Tempo le 3/11/2023
    - name: "Cout Energie HC Bleu Jour"
      state: >-
        {% set en = states('sensor.compteur_energie_hc_bleu_jour') | float(default=0) | round(2) %}
        {% set ct = states('input_number.tarif_edf_tempo_bleu_hc') | float(default=0) | round(2) %}
        {{(en*ct) | round(2) }}
      unit_of_measurement: "€"
      device_class: "monetary"
      state_class: "total"  
      unique_id: "cout_energie_hc_bleu_jour"

    - name: "Cout Energie HP Bleu Jour"
      state: >-
        {% set en = states('sensor.compteur_energie_hp_bleu_jour') | float(default=0) | round(2) %}
        {% set ct = states('input_number.tarif_edf_tempo_bleu_hp') | float(default=0) | round(2) %}
        {{(en*ct) | round(2) }}
      unit_of_measurement: "€"
      device_class: "monetary"
      state_class: "total"  
      unique_id: "cout_energie_hp_bleu_jour"

    - name: "Cout Energie HC Blanc Jour"
      state: >-
        {% set en = states('sensor.compteur_energie_hc_blanc_jour') | float(default=0) | round(2) %}
        {% set ct = states('input_number.tarif_edf_tempo_blanc_hc') | float(default=0) | round(2) %}
        {{(en*ct) | round(2) }}
      unit_of_measurement: "€"
      device_class: "monetary"
      state_class: "total"  
      unique_id: "cout_energie_hc_blanc_jour"

    - name: "Cout Energie HP Blanc Jour"
      state: >-
        {% set en = states('sensor.compteur_energie_hp_blanc_jour') | float(default=0) | round(2) %}
        {% set ct = states('input_number.tarif_edf_tempo_blanc_hp') | float(default=0) | round(2) %}
        {{(en*ct) | round(2) }}
      unit_of_measurement: "€"
      device_class: "monetary"
      state_class: "total"  
      unique_id: "cout_energie_hp_blanc_jour"

    - name: "Cout Energie HC Rouge Jour"
      state: >-
        {% set en = states('sensor.compteur_energie_hc_rouge_jour') | float(default=0) | round(2) %}
        {% set ct = states('input_number.tarif_edf_tempo_rouge_hc') | float(default=0) | round(2) %}
        {{(en*ct) | round(2) }}
      unit_of_measurement: "€"
      device_class: "monetary"
      state_class: "total"  
      unique_id: "cout_energie_hc_rouge_jour"

    - name: "Cout Energie HP Rouge Jour"
      state: >-
        {% set en = states('sensor.compteur_energie_hp_rouge_jour') | float(default=0) | round(2) %}
        {% set ct = states('input_number.tarif_edf_tempo_rouge_hp') | float(default=0) | round(2) %}
        {{(en*ct) | round(2) }}
      unit_of_measurement: "€"
      device_class: "monetary"
      state_class: "total"  
      unique_id: "cout_energie_hp_rouge_jour"

Automatismes

Commutation de HP vers HC et Inversement

Il permet de permuter la tarification des utiliy meter en fonction du tarif récupéré dans le Linky (PTEC)

Code de l’automatisme Simplifié

N’oubliez pas de supprimer les « utility_meter » qui ne vous concerne pas.

alias: 6_1_0 Energie Changement HP<->HC
description: ''
trigger:
  - platform: state
    entity_id: sensor.linky_n_tarif
    id: tarif_hc
    from: '2'
    to: '1'
  - platform: state
    entity_id: sensor.linky_n_tarif
    from: '1'
    id: tarif_hp
    to: '2'
condition: []
action:
  - choose:
      - conditions:
          - condition: trigger
            id: tarif_hc
        sequence:
          - service: select.select_option
            data:
              option: hc
            target:
              entity_id:
                - select.energy_total_usage_daily
                - select.energy_total_usage_weekly
                - select.energy_total_usage_monthly
                - select.energy_total_usage_yearly
                - select.energy_pisc_usage_daily
                - select.energy_pisc_usage_weekly
                - select.energy_pisc_usage_monthly
                - select.energy_pisc_usage_yearly
                - select.energy_sdb_usage_daily
                - select.energy_sdb_usage_weekly
                - select.energy_sdb_usage_monthly
                - select.energy_sdb_usage_yearly
                - select.energy_ecs1_usage_daily
                - select.energy_ecs1_usage_weekly
                - select.energy_ecs1_usage_monthly
                - select.energy_ecs1_usage_yearly
                - select.energy_pac1_usage_daily
                - select.energy_pac1_usage_weekly
                - select.energy_pac1_usage_monthly
                - select.energy_pac1_usage_yearly
                - select.energy_mal_usage_daily
                - select.energy_mal_usage_monthly
                - select.energy_mal_usage_weekly
                - select.energy_mal_usage_yearly
                - select.energy_autres_usage_daily
                - select.energy_autres_usage_monthly
                - select.energy_autres_usage_weekly
                - select.energy_autres_usage_yearly
                - select.energy_vmc_usage_daily
      - conditions:
          - condition: trigger
            id: tarif_hp
        sequence:
          - service: select.select_option
            data:
              option: hp
            target:
              entity_id:
                - select.energy_total_usage_daily
                - select.energy_total_usage_weekly
                - select.energy_total_usage_monthly
                - select.energy_total_usage_yearly
                - select.energy_pisc_usage_daily
                - select.energy_pisc_usage_weekly
                - select.energy_pisc_usage_monthly
                - select.energy_pisc_usage_yearly
                - select.energy_sdb_usage_daily
                - select.energy_sdb_usage_weekly
                - select.energy_sdb_usage_monthly
                - select.energy_sdb_usage_yearly
                - select.energy_ecs1_usage_daily
                - select.energy_ecs1_usage_weekly
                - select.energy_ecs1_usage_monthly
                - select.energy_ecs1_usage_yearly
                - select.energy_pac1_usage_daily
                - select.energy_pac1_usage_weekly
                - select.energy_pac1_usage_monthly
                - select.energy_pac1_usage_yearly
                - select.energy_mal_usage_daily
                - select.energy_mal_usage_monthly
                - select.energy_mal_usage_weekly
                - select.energy_mal_usage_yearly
                - select.energy_autres_usage_daily
                - select.energy_autres_usage_monthly
                - select.energy_autres_usage_weekly
                - select.energy_autres_usage_yearly
                - select.energy_vmc_usage_daily
    default: []
mode: single

Automatisme de contrôle de la communication du Linky avec « Automation »

Descriptif fonctionnel

Le but est de surveiller si la communication entre le Linky et HA est établie.

Je surveille la tension moyenne car elle varie très souvent contrairement à la puissance ou l’intensité qui peuvent restés stables plus d’une heure la nuit notamment.

Il faut déclarer une entité dans « config/helpers »

L’entité est « ON » si la communication est OK. Il faut peut etre ajuster la durée (<1000) en ms en fonction de votre cofiguration.

Blueprint Notification défaut communication:

J’exploite le défaut avec un blueprint « Nagging Alerting Notification Automation » plus pratique que l’intégration « alert » qui nécessite un redémarrage de HA à chaque modification.

J’utilise également ce blueprint dans d’autres cas de surveillance, portes, température, communication, etc..

Les services de notification sont à adapter à votre configuration (Télégram dans mon cas).

alias: BluePrint Notification Def Com Linky
description: Blue Print
use_blueprint:
  path: pavax/nagging_alert_notification.yaml
  input:
    condition_entity_state: "on"
    sensor_entity: binary_sensor.status_communication_linky
    alert_state: "off"
    alert_action:
      - data:
          message: Com Linky off Line{{-"\n"-}}{{states("sensor.date_time") }}
          title: Etat Reseau !!!
        action: notify.telegram
    resolved_action:
      - data:
          message: Com Linky On Line{{-"\n"-}} {{states("sensor.date_time") }}
          title: Etat RESEAU!!!
        action: notify.telegram
    notify_device: 367dba31447009458e03b70e3a29b6b2
    repeat_delay: 300
    initial_delay: 60
    max_alerts: 5
    resolved_message: " "
    notify_message: " "

Lovelace

Exemple d’affichage des données

Avec « auto-entities », vous affichez toutes les entités contenant « linlky » dans son id.

type: custom:auto-entities
card:
  type: entities
  title: Linky
  show_header_toggle: false
filter:
  include:
    - entity_id: "*linky*"

Pour visualiser votre max de puissance sur l’année:

square: true
type: grid
cards:
  - type: statistic
    entity: sensor.linky_papp
    period:
      calendar:
        period: month
    stat_type: min
    name: Min
  - type: statistic
    entity: sensor.linky_papp
    period:
      calendar:
        period: month
    stat_type: mean
    name: Moyenne
  - type: statistic
    entity: sensor.linky_papp
    period:
      calendar:
        period: year
    stat_type: max
    name: Max
title: Puissance sur l'Année

Un autre exemple d’affichage utilisant « custom:mini-graph-card » de HACS

type: vertical-stack
cards:
  - type: custom:mini-graph-card
    entities:
      - entity: sensor.energie_totale_jour
    name: Conso Linky 7j
    hours_to_show: 168
    aggregate_func: max
    group_by: date
    show:
      graph: bar
    icon: mdi:flash
  - type: custom:mini-graph-card
    color_thresholds:
      - color: '#00FF00'
        value: 0
      - color: '#FF9900'
        value: 4000
      - color: '#EA9999'
        value: 6000
      - color: '#CC0000'
        value: 10000
    color_thresholds_transition: hard
    line_width: 2
    icon: mdi:flash
    show:
      extrema: true
      fill: true
      icon: true
      labels: false
      name: true
      state: true
    hour24: true
    points_per_hour: 4
    hours_to_show: 24
    group: false
    state_map:
      - label: hp
        value: 'off'
      - label: hc
        value: 'on'
    style: |
      ha-card {
        border: solid 2px var(--primary-color);
      }
    entities:
      - entity: sensor.linky_papp
        name: Totale
      - color: '#CCC0cCC'
        entity: binary_sensor.display_hp_hc
        name: HC
        show_line: false
        y_axis: secondary
    name: Puissance Linky 24h

Un autre exemple d’affichage utilisant « custom:apexcharts-card » de HACS

type: custom:apexcharts-card
chart_type: donut
header:
  show: true
  title: Conso du Jour
  show_states: false
  colorize_states: true
series:
  - entity: sensor.compteur_energie_hc_bleu_jour
    name: HC Bleu
    color: "#2119A8"
  - entity: sensor.compteur_energie_hp_bleu_jour
    name: HP Bleu
    color: "#7975CA"
  - entity: sensor.compteur_energie_hc_blanc_jour
    name: HC Blanc
    color: "#ffffff"
  - entity: sensor.compteur_energie_hp_blanc_jour
    name: HP Blanc
    color: "#9E9E9E"
  - entity: sensor.compteur_energie_hc_rouge_jour
    name: HC Rouge
    color: "#F80E1A"
  - entity: sensor.compteur_energie_hp_rouge_jour
    name: HP Rouge
    color: "#7C070D"
type: custom:apexcharts-card
chart_type: donut
header:
  show: true
  title: Conso du Jour
  show_states: false
  colorize_states: true
series:
  - entity: sensor.energy_total_usage_daily_hp
    name: HP
    color: green
  - entity: sensor.energy_total_usage_daily_hc
    name: HC
    color: blue

Affichage du tableau Energie

Affichage dans Grafana

Vous trouverez ci-après le fichier Graphana correspondant.

Conclusion

Pas grand chose d’autre à ajouter, c’est simple à mettre en oeuvre (si le Linky est accessible), fiable et peu onéreux, il ne reste plus qu’a exploiter les informations.

Publication en lien avec cet article:

https://domo.rem81.com/ha-teleinformation-linky-mode-historique

44 Comments on “HA-Teleinformation Linky-Mode Standard”

  1. Article vraiment top
    Complet et bien écrit

    Un lien pour le boîtier avec le couvercle transparent ? Comment fais tu tenir le montage à l’intérieur

  2. bonjour, apres avoir testé pleins de solutions dont bcp non fonctionnelles :p , celle ci me semble idéale …
    je vais donc tester .

    par contre, j ai pas un BASE ni un HP/HC , mais un HP/HC/WE …. je vais essayer de modifier tes scripts et si cela fonctionne, je partagerais 🙂 (mais pas de suite, je fais les choses lentement )

    1. Ok Jérôme, si besoin n’hésites pas à me solliciter, et merci d’avance pour ton retour d’expérience.

      1. Bonjour, Alors, l ESP est en place et me remonte des infos ( il sert aussi pour le gaz et l eau , le gaz ça marche et l eau j ai pas encore recu le capteur inductif )

        coté linky, tout ne remonte pas, deja , j ai les entités suivantes vides :
        HCHP J = Inconnu
        Index Linky kWh = Inconnu
        le reste est OK ( j ai un index HP, HC , le pourcentage d utilisation, ect, meme l affichage OLED marche ( saut la conso jour et veille qui affiche NaN )

        nous sommes le WE , et donc ej suis pile dans un cas spécial , le ‘linky N tarif = 3  »

        mes connaissance en programmation sont limitées, comment modifier ceci pour prendre en compte le « 3 » ? ( il me faudrait juste un truc genre 1=HC, 3=WE sinon HP

        # Convertion du tarif en cours
        – platform: template
        id: tarif
        name: « Linky PTEC »
        lambda: |-
        if ( id(ntarif).state == 1 ) {
        return { « HC.. » };
        } else {
        return { « HP.. » };
        }

        ( en fait je bloque sur la syntaxe )

        la page energy ne me permet pas de choisir les entités Linky HC et linky HP , j ai raté une definition quelque part ?

        ps : une fois que tout fonctionnera je t enverrais la liste des modifs pour publication et que d autres en profitent 🙂

        1. Bonjour
          pour récuperer le troisième index il te faut ajouter dans l’esp apres EAFS02 par ex:
          # Energie Active soutirée Index03
          – platform: teleinfo
          id: we <- Mets ici le nom de l'index souhaitée tag_name: "EASF03" name: "Linky WE Wh" <- Mets ici le nom de l'entité souhaitée unit_of_measurement: "Wh" icon: mdi:flash teleinfo_id: myteleinfo Concernant "Index Linky kWh" c'est fourni par le linky label EAST donc cela doit fonctionné, regarde ta syntax: # Energie Active soutirée totale - platform: teleinfo id: hc_hp tag_name: "EAST" name: "Linky HPHC KWH" unit_of_measurement: "kWh" icon: mdi:flash teleinfo_id: myteleinfo device_class: "energy" state_class: "total_increasing" filters: lambda: |- return x/1000; Concernant HCHP J c'est calculé dans HA dans le linky.yaml Je te réponds dans le commentaire suivant.

        1. J’ai ajouté un chapitre « prè requis » expliquant que mes fichiers de configuration « .yaml » sont contenus dans un dossier spécifique « config/packages » et qu’il faut ajouter une ligne « packages: !include_dir_named packages » dans « configuration.yaml » qui indique à HA ou trouver les fichiers de configuration (apres reboot) et donc c’est dans ce dossier qu’il faut déposer le « linky.yaml ». avec cette organisation tu peux mélanger dans un même fichier des « sensors, binary_sensor, template, utility-meter, etc.. »
          Regarde dans mon github https://github.com/remycrochon/home-assistant tu comprendra mon organisation.
          Une fois fait, tu retrouvera notamment le template « sensor.energie_totale_jour » et appelé dans l’ESP « HPHC J »
          Bon courage.

          1. bonjour, alors j ai bien avancé, je recupere bien bcp de choses, mais je rencontre les soucis suivants :

            1) pour le graph en donut , je n ai pas sensor.energy_total_linky_usage_daily_hp ( ni hc ) , il est créé a quel moment ?

            2 )pour l autre graph , je n’ai pas le sensor.energie_totale_linky_jour pourtant j ai bien dans l ESP :
            – platform: homeassistant
            name: « HCHP J »
            unit_of_measurement: « kWh »
            entity_id: sensor.energie_totale_linky_jour
            id: hphcj
            il y a une raison pour que cela ne remonte pas ?

            3) la led verte ne s allume pas , elle est basée sur sensor.linky_index , il ne me semble pas avoir ce sensor …

            4) j ai un soucis de sytaxe sur le passage HP/HC pour rajouter le WE
            comment je dois ré-écrire ce paragraphe ? ( 3=WE )

            # Convertion du tarif en cours
            – platform: template
            id: tarif
            name: « Linky PTEC »
            lambda: |-
            if ( id(ntarif).state == 1 ) {
            return { « HC.. » };
            } else {
            return { « HP.. » };
            }

            voila déjà les questions du jour 🙂 merci pour ton aide

          2. Salut
            J’ai mis à jour le tuto, notamment les utility-meter du fichier linky.yaml, le donut, le code ESP, veille à mettre en concordance les utility-meter déclarés dans le linky.yaml et utilisés dans l’ESP, l’affichage, l’automatisme, etc.. Une fois fait ça doit fonctionner. C’est peut être pas facile à suivre mais je le fais évolué de mon coté également, pour suivre les modifications tu peux regarder regarde dans les fichiers github qui sont mis à jour quasi en temps réels (une fois le fichier ouvert cliquer sur hytory en haut à droite..)
            Pour la Led dans mon code elle est calculée dans:
            #Puissance apparente instantanée ph1
            – platform: teleinfo
            id: papp
            tag_name: « SINSTS »
            name: « Linky PAPP »

            Concernant ta syntaxe essaye ceci:

            lambda: |-
            if ( id(ntarif).state == 1 ) {
            return { « HC.. » };
            } else if ( id(ntarif).state == 2 ) {
            return { « HP.. » };
            else {
            return { « WE.. » };
            }

            Tu peux inspirer du template:
            # Affichage du temps de fonctionnement
            – platform: template
            name: « ${friendly_name} Uptime »

            @+

    2. Bonjour Jérome, suite à ta conversation avec Rem81, je n’arrive pas à trouver l’étiquette qui remonte les heures Week-end. Si tu peux m’aider je t’en serai gré.
      Merci d’avance et @+

  3. Bonjour ( et merci pour tes réponses )

    (il n y a pas le bouton répondre sur ta dernière réponse )

    alors pour la led verte, c est le nom du sensor qui n était pas le bon
    maintenant que les infos remontent bien,( il me manque les valeurs dans les graphs, peut etre attendre 24h vu que c est des totaux jour ) j ai re modifié les fichier pour prendre en compte le WE , verdict … ce WE pour tester 🙂
    si tout est OK je t enverais la liste des modifs, cela pourra toujours aider ceux qui sont en contrat non classique ( ni BASE ni HPHC )

    je bloque toujours sur la syntaxe pour le HP/HC/WE ( quand on débute il n y a pas a dire mais le yaml c est chiant :p ) , mais a priori il ne sert que pour l affichage OLED , j ai donc modifié 2=plein tarif sinon tarif réduit … a voir si ca pose des soucis sur d autre scripts.

    Jerome

  4. pfui, je cherchais un article de ce type et la quelle claque, super descriptions et explication.

    j’ai un module qui ce connecte directement en USB mais je ne trouve rien de bien concluant pour faire l’intégration complete.
    je vais surement réfléchir a la solution proposer et voir pour la mettre en place, et je vais aussi regarder les autre article et le github pour avoir de idée 😉

  5. Rémi,

    Vous êtes à un niveau « pro » avec Home Assistant (et pas seulement) , je vous admire : c’est juste impréssionant tout ce que vous faites.

    Je suis parti sur une autre solution: une clé Lixee (bien plus chère que la solution que vous avez utilisée), et qui plus est, en utilisant ZHA au lieu de zigbee2mqtt qui marche sans bidouille.

    J’ai fait toutes les adaptations pour que le panneau Énergie montre mes infos… et rien.
    J’ai compris que la cause est justement mon Linky qui est en mode TIC historique au lieu de standard.

    Donc, comment avez-vous réussi à activer le fichu mode Standard du Linky ? Pour moi, jusque-là, c’est une aventure :

    1) J’ai appelé Enedis : ils m’ont dit que ça se fait directement sur leur espace client ;

    2) Je me suis connecté sur mon espace Enedis, j’ai ajouté mon compteur et j’ai autorisé la collecte de données historiques (vu qu’il n’y a aucune option TIC standard/historique), mais quelques semaines après, mon compteur reste toujours sur mode historique ;

    3) Je me connecte sur le site Enedis à nouveau, et il me dit qu’ils ont un ‘problème technique’, pour connecter plus tard ;

    4) J’ai appelé EDF : la personne au bout du fil ne savait pas ce qui était mode TIC … après insister, elle me dit que la manip se fait par l’espace-client …

    5) Je rentre sur l’espace client, et il n’y a rien de spécifique « TIC Historique/Standard ». J’ai essayé à nouveau – cette fois au site EDF – d’activer les options de suivi à chaque 30 min dans l’espoir que ça m’active le mode standard…

    … bref, si vous avez une méthode « coup-sûr » pour changer le TIC en mode standard, je suis preneur ! 🙂

    Merci encore une fois.

    PS: l’app de contrôle de la piscine marche toujours nickel, inébranlable !

    1. Bonjour, pour le passage en mode standard, il faut faire la dde auprès de ton fournisseur d’énergie. Perso je suis chez Engie, la dde se fait sur son compte internet, assez réactif, ça a dû mettre une semaine. Pour mon fils, chez EDF, ça été plus laborieux, dde par tel, mais plus long à réagir, après de multiples relances par tel, il est passé au mode standard plus d’1 mois après. Bon courage et merci pour tes retours d’expériences. Slts

  6. Bonjour
    je fonctionnais en mode historique lorsque je passe en standard j’ai le message suivant:

    [11:10:20][W][teleinfo:060]: Internal buffer full

    ou faut il modifier la valeur du buffer
    Cdlt

    1. Oups désolé cela fonctionne j’avais omis de modifier le UART en mettant 9600 pour le Baud rate en lieu et place de 1200

  7. Bravo Rémi,
    J’ai appliqué tes recommandations concernant le mode HISTORIQUE mais étant chez Engie avec un contrat HP-HC-WE, la remontée des HC et HP et WE ne se fait pas. D’où ma décision de passer en mode standard. Demande en cours et en attente de validation…
    J’ai parcouru tonexplication qui me paraît très complète et je te tiendrai au courant des résultats.
    En core merci.
    @ suivre
    Paul

  8. Bonjour rémi, l’article est vraiment …. super.
    Je démare avec HA, j’ai ajouté mon linky réseau, et un sonoff pow comme appareil individuel.
    mais il n’apparait pas comme sur ton graphique, je n’ai que deux ronds « réseau » et « maison ».

    Sur ton graphique tu as deux equipements aprés ta maison (58W et 1W)

  9. Bonjour,
    Ce système est intégré avec succès à mon HA depuis fin 2022. Mais en regardant le panel « Energie », je me rends compte que j’ai parfois des valeurs aberrantes qui plombent mes statistiques (du genre : une valeur de milliers de GigaWatts ! Non, je n’ai pas de centrale nucléaire à la maison ;-)). Il arrive que ces valeurs erronées remontent plusieurs fois dans la même journée. Elle ne concernent que les Heures Creuses ( sensor.electricite_hc), et ce n’est pas lié au changement de tarif (HPHC).

    J’ai des difficultés à identifier la cause (bad CRC ? comm réseau ? MAJ ? etc…).
    Une idée ?

    1. Bonjour,
      Est ce le « sensor » des HC lu dans le Linky (tag_name: « EASF01 ») qui contient ces valeurs incohérentes ou bien c’est le « compteur de service public » journalier qui deconne?
      Dans le tableau « energy » tu utilise la valeur lue dans le linky ou bien « le compteur de service public »? Dans le deuxieme cas le passage d’un jour à l’autre dans le module energy et les « compteurs de service public » peux être désynchronisés. Dans le module Energy, Il est préférable de configurer directement les sensor du TIC (en kWh).
      Perso j’utilise les deux « compteur de service public » mais je n’ai pas de problème. J’ai eu par le passé des valeurs qui passaient à 0 de temps en temps, j’ai filtré au niveau de l’ESP avec « filter_ou:0.0 ».
      Voici quelques pistes de réflexion. Bon courage

      1. Bonjour,
        Merci pour ta réponse. J’utilise bien les valeurs lues dans le Linky dans le panneau Energy (sensor.electricite_hc).
        J’aurais bien posté une copie d’écran de l’historique sur une journée avec des valeurs incohérentes pour illustrer mais ce n’est pas possible ici.
        Il semblerait que ces valeurs soient négatives (un débordement, pb de représentation interne numérique ?). Exemple pris dans l’historique du sensor sur la journée du 30/08 :
        30 août 2023 à 00:45:51
        Electricité HC:
        -2 954 495 Wh

        En réalité, les valeurs enregistrées dans l’historique sont de cet ordre de grandeur entre 00:00:00 et 00:45:51, puis passent à des valeurs correctes (+900Wh).
        Plus tard, à 09:56:32, une seule valeur incohérente énorme et négative, puis d’autres encore plus tard dans la journée.

        C’est vraiment curieux, et seul le sensor HC est concerné. Autre point, j’ai un linky triphasé, mais je ne vois pas ce que cela change…

  10. Bonjour à tous,
    Je me permet de « m’incruster » juste pour une question sur un problème que je n’arrive pas à résoudre malgré mes recherches sur le Net ??
    A quoi sont dues ces différentes erreur qui me pourissent mes données ?
    Si vous avez un conseil et des idées, je suis preneur.
    Merci pour vos réponses.

    Paul

    16:18:04 [E] [teleinfo:038] bad crc: got 52 except 92
    16:18:04 [E] [teleinfo:038] bad crc: got 52 except 92
    16:18:04 [E] [teleinfo:038] bad crc: got 52 except 38
    16:18:04 [E] [teleinfo:038] bad crc: got 52 except 44
    16:18:04 [E] [teleinfo:038] bad crc: got 42 except 55
    16:18:04 [E] [teleinfo:038] bad crc: got 42 except 89
    16:18:04 [E] [teleinfo:038] bad crc: got 34 except 94
    16:18:04 [E] [teleinfo:133] No group found
    16:18:04 [W] [component:204] Component teleinfo took a long time for an operation (0.12 s).
    16:18:04 [W] [component:205] Components should block for at most 20-30ms.

    1. Bonjour, le crc permet de valider la trame reçue, en général les perturbations sont dues à des parasites sur la ligne, quelle longueur fait votre ligne et quelle section de cable utilisez vous?
      Est ce que ces erreurs sont systématiques? malgré cela arrivez vous à recueillir des données exploitables?
      Slts

      1. Bonjour,
        Désolé pour le retard à l’allumage, mais occupé ces derniers temps.
        Concernant mon équipement, c’est exactement celui décrit dans cet excellent post.
        Le cable utilisé est un JA28 (https://fr.rs-online.com/web/p/cables-audio/3729596)
        de 0.22 mm² sur unje longueur de 10-15 mètres environ dans la gaine de la commande J/N qui enclenche le relais des heures creuses. J’ai cherché partout, en tirant un câble différent en « volant » sur la pelouse et le résulat est absolument idem ?????
        Après mures réflexions, j’ai pensé que le signal était trop fort et j’ai mis en série avant d’attaquer la carte une résistance de 1.2 Kohms, ce qui m’a permis de résoudre ce problème et ce aujourd’hui (Alléluia…!) Il me reste encore un bad CRC très régulier celui-là qui est certainement dû à une mauvaise requête dans mon code. On progresse.
        Pour résumé, avant la remontée des données était d’une trentaine de minutes et les mises à jour étaient très aléatoires. Maintenant c’est INSTANTANE…
        J’en profite pour vous remercier de votre excellent article et vous en félicite. Maintent il faut que j’attaque le stockage de ces données ainsi que de beaux graphiques.
        Encore bravo et je ne manquerait pas de vous tenir au courant.
        Amitiés

  11. Bonjour Rem81, super Tuto qui m’a motivé ,
    je me lance pour son intégration dans mon Home Assistant pour un Linky Triphasé mode Tempo.
    j’ai déjà le module Wemos Teleinfo avec ESP32C3 mini qui tourne en mode « Standard » donc cela devrait être réalisable 🙂
    Eric

    1. Bonjour, oui bien sur sans soucis, il faut lire les variables spécifiques au triphasé, voir le document enedis à ce sujet. piur info j’ai ajouter des ficltres sur les sensors. Bon courage

      1. Merci Rem81, cela tourne maintenant depuis une dizaine de jours avec les infos du triphasé, j’ai un peu transpiré avec vos 2 petits modules appdaemon mais ça tourne maintenant et c’est bien efficace. Je vais décortiquer les utility-meter et le « panneau Energie » pour adapter cela au triphasé et retrouver les variables que vous utilisez. Merci encore pour le tuto.
        Ps: les prix de l’Energie en fonction des périodes sont « hard coded », n’est ce pas ? Ces infos ne seraient t’elles pas dispo quelque part dans le cloud?

  12. Bonjour, Novice en domotique je me lance avec HA, et étant producteur avec panneaux solaire et vente du surplus, je souhaite router le surplus sur mon ballon ESC. Je suis en attente du module optocoupleur pour raccorder un ESP32 sur le linky en mode standard. Le but est de récupérer notament l’énergie ou la puissance injectée par les panneaux sur le réseau pour la transferer vers un deuxieme ESP32 et un dimmer 16/24A auprés du chauffe-eau. J’ai bien regardé vos différnt code Yaml mais j’avous je suis perdu dans tout ça. Est-il possible d’avoir de l’aide dans la programmation des ESP32. ?
    Merci d’avance pour la réponse
    Cordialement

  13. bonjour
    bravo pour votre réalisation.
    j’ai une question concernant la led ws2812: je ne n’arrive pas a faire allumer en fonction du tarif.
    y’a t-il une astuce?
    merci pour votre retour

      1. Bonjour
        Oui le script existe bien en bas du programme la vérification de celui-ci ne donne aucune erreur.
        Je vois bien au niveau des logs s’afficher HC bleu, ou autre mais la led (ws2811) reste éteinte
        Merci pour votre retour

        1. Bonjour, j’utilise le GPIO18 pour la led, est ce que tu as pu vérifier que ton câblage ou platine correspond à ce GPIO?.

          1. Bonjour, oui c’estle GPIO18 que j’utilise avec un esp32 mini et une planche V1.1, il faut regarder sur la DOC quel GPIO en fonction de l’ESP utilisé https://github.com/hallard/WeMos-TIC chapitre »description détailllée » . Essayer avec le GPIO02 la doc n’est pas trés claire à ce sujet. @+

  14. Merci pour cet article très complet.
    Serait il possible d’intégrer le décodage du registre directement dans l’ESP via lambda ?

    1. Salut et Merci.
      Je viens d’intégrer le décodage du status linky dans le code ESP HOME uniquement dans TEMPO (version du 21/06/2025)
      J’en ai profiter pour mettre à jour le tuto en fonction des évolutions de HA

Laisser un commentaire

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