Remonter les valeurs téléinfo par IP vers jeedom avec un Arduino

Bonjour,

Comme beaucoup je cherchais un moyen de remonter les informations de mon compteur Linky vers Jeedom.

Alors j’ai bien vu le plug-in TeleInfo mais sauf si j’ai loupé quelque chose, il n’y as pas moyen de connecter le compteur autrement que par un port série. Mon problème c’est que le câble de téléinfo arrive dans le tableau électrique de la maison et le NAS qui qui fait tourner Jeedom ne sont pas du tout à côté.
Je suppose qu’il y a la possibilité de trouver un port série « virtuel » qui passe par ethernet mais je n’ai pas ce genre de matériel et mon niveau en administration linux me laisse entrevoir des heures de galère et de recherche sur le net pour réussir à configurer ça.

De mes essais avec le plug-in Jeedouino, je me suis souvenu qu’il offrait la possibilité de faire remonter les trames téléinfo. ça semblait donc la solution idéale pour ma situation.
Le problème c’est que je n’ai pas réussit à le faire fonctionner :

Bref, face à cet échec et après qu’on m’ait donné la solution pour un autre problème qui m’a permit de découvrir certaines possibilités de programmation dans Jeedom

J’ai décidé de développer ma propre solution. Je vous en livre ici la première version. Ce n’est pas aussi performant que certains plug-in que l’on peut trouver à ce sujet, mais ça assure déjà le minimum : remonter les indexs du compteur, la puissance soutirée et l’intensité.
ça fonctionne avec la TIC d’un compteur électronique ou d’un Linky en mode historique.

C’est basé sur un Arduino UNO accompagné d’un shield Ethernet a base de W5100. Pour la partie interface entre la ligne de téléinfo et l’Arduino je vous laisse chercher sur le net, c’est pas les schémas qui manquent et ça reste simple (un optocoupleur, 2 résistances et éventuellement quelques diodes)

Dans le principe au niveau logiciel le gros est fait par l’Arduino. Une partie du code se charge de recevoir les trames et de les décoder pour mettre les valeurs à disposition « en clair ». Je ne suis pas du tout l’auteur de cette partie du code, c’est basé totalement sur le travail de MicroQuettas du forum Arduino

Je l’ai à peine customisé pour récupérer la valeur de l’abonnement souscrit (« ISOUSC » de la trame)

Le reste du code met à disposition les valeurs via un petit serveur web qui fournit une page HTML, un fichier XML et un fichier JSON avec les valeurs traduite de la trame téléinfo.

Coté Jeedom, un scénario appel le fichier JSON généré par l’Arduino et met à jour les infos d’un virtuel.

Pour le fichier .ino voir post suivant

Ensuite le fichier LinkyHistTIC.h de la librairie qui sert à decoder les trames et à placer dans le même répertoire que le .ino

/***********************************************************************
               Objet decodeur de teleinformation client (TIC)
               format Linky "historique" ou anciens compteurs
               electroniques.

Lit les trames et decode les groupes :   |<--  Switches d'option   -->|
                                         | Base | HPHC | IMono | ITri |
 PAPP   : puissance apparente en VA......|   X  |   X  |   X   |   X  |
 BASE   : index general compteur en Wh...|   X  |      |       |      |
 HCHC   : index heures creuses en Wh.....|      |   X  |       |      |
 HCHP   : index heures pleines en Wh.....|      |   X  |       |      |
 PTEC   : periode tarifaire en cours.....|      |   X  |       |      |
 IINST  : intensite instantanee en A.....|      |      |   X   |      |
 IINST1 : intensite instantanee en A.....|      |      |       |   X  |
 IINST2 : intensite instantanee en A.....|      |      |       |   X  |
 IINST3 : intensite instantanee en A.....|      |      |       |   X  |
 ISOUSC : intensite souscrite en A.......|   X  |   X  |   X   |   X  |
 
Reference : ERDF-NOI-CPT_54E V3

V06 : MicroQuettas mars 2018

V10a : initial version, parametric. Compiled OK on 22/10/19.
V10b : fixed bug in ptecIsNew().
V10c : added LKYSIMINPUT mode
V10d : adapted to Arduino Uno and Mega.

***********************************************************************/
#ifndef _LinkyHistTIC
#define _LinkyHistTIC true

/********************** Processor selection ***************************/
//#define ARDUINOMEGA Serial1    /* Define the serial input used   */
                               /* when running on a Mega,        */
                               /* comment out on an AVR328 (Uno) */
                               /* On a Mega, call the constructor */
                               /* without parameters. If parameters */
                               /* (pin numbers) are given, they will */
                               /* be ignored.                        */

/********************** Configuration switches ************************/
//#define LINKYDEBUG true     /* Verbose debugging mode */
//#define LKYSIMINPUT true    /* Simulated Linky input on Serial */
                              /* AVR328 (Uno) processor only */

/************* tariffs and intensities configuration ******************/
//#define LKY_Base true        /* Exclusif avec LKY_HPHC */
#define LKY_HPHC true        /* Exclusif avec LKY_Base */
#define LKY_IMono true       /* Exclusif avec LKY_ITri */
//#define LKY_ITri true        /* Exclusif avec LKY_IMono */

/****************************** Autoconf *****************************/
#if (defined (LKY_Base) && defined (LKY_HPHC))
#undef LKY_Base
#endif

#if (defined (LKY_IMono) && defined (LKY_ITri))
#undef LKY_IMono
#endif

#if (defined (LKYSIMINPUT) && defined (ARDUINOMEGA))
#undef ARDUINOMEGA
#endif

#if !(defined (LKYSIMINPUT) || defined (ARDUINOMEGA))
#define LKYSOFTSERIAL true
#endif

/*************************** Includes ********************************/
#ifdef LKYSOFTSERIAL
#include <SoftwareSerial.h>
#endif

/********************** Defines and consts ***************************/
#define CLy_BfSz 24            /* Maximum size of the Rx buffers */

const uint16_t CLy_Bds = 1200; /* Transmission speed in bds */

const uint8_t CpinRx_def = 8;
const uint8_t CpinTx_def = 9;

/******************************** Class *******************************
      LinkyHistTIC : Linky historique TIC (teleinformation client)
***********************************************************************/

class LinkyHistTIC
  {
  public:
    #ifdef LKYSOFTSERIAL
    LinkyHistTIC(uint8_t pin_Rx, uint8_t pin_Tx);    /* Constructor */
    #else
    LinkyHistTIC(uint8_t pin_Rx = CpinRx_def, \
                 uint8_t pin_Tx = CpinTx_def);       /* Constructor */
    #endif    

    void Init(uint16_t BdR = CLy_Bds);  
                        /* Initialisation, call from setup() */
    void Update();      /* Update, call from loop() */

    bool pappIsNew();   /* Returns true if papp has changed */
    uint16_t papp();    /* Returns papp in VA */

    bool isouscIsNew();  /* Returns true if isousc has changed*/
    uint8_t isousc();   /* Returns isousc in A*/

    #ifdef LKY_Base
    bool baseIsNew();   /* Returns true if base has changed */
    uint32_t base();    /* Returns base index in Wh */
    #endif

    #ifdef LKY_HPHC
    enum Tarifs:uint8_t {C_HPleines, C_HCreuses};
    bool hchcIsNew();   /* Returns true if hchc has changed */
    uint32_t hchc();    /* Index heures creuses en Wh */
    bool hchpIsNew();   /* Returns true if hchp has changed */
    uint32_t hchp();    /* Index heures pleines en Wh */
    bool ptecIsNew();   /* Returns true if ptec has changed */
    uint8_t ptec();     /* Periode tarifaire en cours (0 = HP, 1 = HC) */
    #endif

    #ifdef LKY_IMono
    bool iinstIsNew();  /* Returns true if iinst has changed */
    uint8_t iinst();    /* Returns iinst in A */
    #endif

    #ifdef LKY_ITri
    enum Phases:uint8_t {C_Phase_1, C_Phase_2, C_Phase_3};
    bool iinstIsNew(uint8_t Ph);  /* Returns true if iinst(Ph)
                                   * has changed */
    uint8_t iinst(uint8_t Ph);    /* Returns iinst(Ph) in A */
    #endif

  private:
    char _BfA[CLy_BfSz];        /* Buffer A */
    char _BfB[CLy_BfSz];        /* Buffer B */

    uint8_t _FR;                /* Flag register */
    uint8_t _DNFR;              /* Data new flag register */

    uint16_t _papp;
    uint8_t _isousc;

    #ifdef LKY_Base
    uint32_t _base;      /* Index base */
    #endif

    #ifdef LKY_HPHC
    uint32_t _hchc;      /* Index heures creuses en Wh */
    uint32_t _hchp;      /* Index heures pleines en Wh */
    uint8_t  _ptec;      /* Periode tarifaire en cours :
                          * 0 = HP ; 1 = HC */
    #endif

    #ifdef LKY_IMono
    uint8_t _iinst;     /* Intensite instantanee */
    #endif

    #ifdef LKY_ITri
    uint8_t _iinst[3];  /* Intensite instantanee pour chaque phase */
    #endif

    #ifdef LKYSOFTSERIAL
    SoftwareSerial _LRx;   /* Needs to be constructed at the same time
                            * as LinkyHistTIC. Cf. Special syntax
                            * (initialisation list) in LinkyHistTIC
                            * constructor */
    uint8_t _pin_Rx;
    uint8_t _pin_Tx;
    #endif

    char *_pRec;     /* Reception pointer in the buffer */
    char *_pDec;     /* Decode pointer in the buffer */
    uint8_t _iRec;   /* Received char index */
    uint8_t _iCks;   /* Index of Cks in the received message */
    uint8_t _GId;    /* Group identification */

  };
  
#endif /* _LinkyHistTIC */
/*************************** End of code ******************************/

Enfin le fichier LinkyHistTIC.cpp de la librairie qui sert à decoder les trames et à placer lui aussi dans le même répertoire que le .ino

/***********************************************************************
               Objet decodeur de teleinformation client (TIC)
               format Linky "historique" ou anciens compteurs
               electroniques.

Lit les trames et decode les groupes :
  BASE  : (base) index general compteur en Wh,
  IINST : (iinst) intensite instantanee en A,
  PAPP  : (papp) puissance apparente en VA.

Reference : ERDF-NOI-CPT_54E V3

V06 : MicroQuettas mars 2018

V10a : initial version, parametric.
V10b : fixed bug in ptecIsNew().
V10c : added LKYSIMINPUT mode
V10d : adapted to Arduino Uno and Mega.

***********************************************************************/

/***************************** Includes *******************************/
#include <string.h>
#include <Streaming.h>
#include "LinkyHistTIC.h"

/***********************************************************************
                  Objet recepteur TIC Linky historique

 Trame historique :
  - delimiteurs de trame :     <STX> trame <ETX>
    <STX> = 0x02
    <ETX> = 0x03

  - groupes dans une trame : <LF>lmnopqrs<SP>123456789012<SP>C<CR>
    <LR> = 0x0A
    lmnopqrs = label 8 char max
    <SP> = 0x20 ou 0x09 (<HT>)
    123456789012 = data 12 char max
    C = checksum 1 char
    <CR> = 0x0d
       Longueur max : label + data = 7 + 9 = 16

  _FR : flag register

    |  7  |  6  |   5  |   4  |   3  |  2  |   1  |   0  |
    |     |     | _Dec | _GId | _Cks |     | _RxB | _Rec |

     _Rec : receiving
     _RxB : receive in buffer B, decode in buffer A
     _GId : Group identification
     _Dec : decode data

  _DNFR : data available flags

    |  7    |    6    |    5    |    4    |   3   |   2   |   1   |   0   |
    |_isousc| _iinst3 | _iinst2 | _iinst1 | _ptec | _hchc | _hchp | _papp |
    |       |         |         |  _iinst |       |       | _base |       |

                              ********************

  Exemple of group :
       <LF>lmnopqr<SP>123456789<SP>C<CR>
           0123456  7 890123456  7 8  9
     Cks:  xxxxxxx  x xxxxxxxxx    ^
                                   |  iCks
    Minimum length group  :
           <LF>lmno<SP>12<SP>C<CR>
               0123  4 56  7 8  9
     Cks:      xxxx  x xx    ^
                             |  iCks

    The storing stops at CRC (included), ie a max of 19 chars

***********************************************************************/



/****************************** Macros ********************************/
#ifdef LKYSOFTSERIAL
#define _LKY _LRx           /*_LRx = software serial instance */
#else
#ifdef ARDUINOMEGA
#define _LKY ARDUINOMEGA    /* Arduino Mega serial port */
#else
#define _LKY Serial         /* Simulated input trough Serial */
#endif
#endif

#ifndef SetBits
#define SetBits(Data, Mask) \
Data |= Mask
#endif

#ifndef ResetBits
#define ResetBits(Data, Mask) \
Data &= ~Mask
#endif

#ifndef InsertBits
#define InsertBits(Data, Mask, Value) \
Data &= ~Mask;\
Data |= (Value & Mask)
#endif

#ifndef P1
#define P1(Name) const char Name[] PROGMEM
#endif

/************************* Defines and const  **************************/

const uint8_t bLy_Rec = 0x01;  /* Receiving */
const uint8_t bLy_RxB = 0x02;  /* Receive in buffer B */
const uint8_t bLy_Cks = 0x08;  /* Check Cks */
const uint8_t bLy_GId = 0x10;  /* Group identification */
const uint8_t bLy_Dec = 0x20;  /* Decode */

const char Car_SP = 0x20;     /* Char space */
const char Car_HT = 0x09;     /* Horizontal tabulation */

const uint8_t CLy_MinLg = 8;  /* Minimum useful message length */

const char CLy_Sep[] = {Car_SP, Car_HT, '\0'};  /* Separators */

/***  const below are used for _GId and for flag rank in _DNFR ***/
const uint8_t  CLy_papp = 0,  \
  CLy_base = 1, CLy_hchp = 1, CLy_hchc = 2, CLy_ptec = 3,  \
  CLy_iinst = 4, CLy_iinst1 = 4, CLy_iinst2 = 5, CLy_iinst3 = 6, \
  CLy_isousc = 7;

#ifdef LKY_IMono
enum Phases:uint8_t {C_Phase_1, C_Phase_2, C_Phase_3};
#endif

/************************* Donnees en progmem *************************/
P1(PLy_papp)  = "PAPP";
P1(PLy_isousc)  = "ISOUSC";
#ifdef LKY_Base
P1(PLy_base)  = "BASE";
#endif

#ifdef LKY_HPHC
P1(PLy_hchp)  = "HCHP";
P1(PLy_hchc)  = "HCHC";
P1(PLy_ptec)  = "PTEC";
P1(PLy_HC)    = "HC";         /* Tarif HC (default = HP) */
#endif

#if (defined (LKY_IMono) || defined(LKY_ITri))
P1(PLy_iinst) = "IINST";
#endif

/*************** Constructor, methods and properties ******************/
#ifdef LKYSOFTSERIAL
LinkyHistTIC::LinkyHistTIC(uint8_t pin_Rx, uint8_t pin_Tx) \
      : _LRx (pin_Rx, pin_Tx)  /* Software serial constructor
                                * Achtung : special syntax */
#else
LinkyHistTIC::LinkyHistTIC(uint8_t pin_Rx, uint8_t pin_Tx)
#endif

  {
  _FR = 0;
  _DNFR = 0;
  _pRec = _BfA;    /* Receive in A */
  _pDec = _BfB;    /* Decode in B */
  _iRec = 0;
  _iCks = 0;
  _GId = CLy_papp;
  
  #ifdef LKYSOFTSERIAL
  _pin_Rx = pin_Rx;
  _pin_Tx = pin_Tx;
  #endif
  };

void LinkyHistTIC::Init(uint16_t BdR)
  {
  uint8_t i;

  #ifdef LKYSOFTSERIAL
  /* Initialise the SoftwareSerial */
  pinMode (_pin_Rx, INPUT_PULLUP);
  pinMode (_pin_Tx, OUTPUT);
  #endif

  _LKY.begin(BdR);  /* When LKYSIMINPUT is activated, will adjust */
                    /* the Serial Baud rate to that of the Linky */

  /* Clear all data buffers */
  _papp = 0;
  _isousc = 0;

  #ifdef LKY_Base
  _base = 0;
  #endif

  #ifdef LKY_HPHC
  _hchc = 0;
  _hchp = 0;
  _ptec = 255;   /* 1st input, whatever, will trigger ptecIsNew() */
  #endif

  #ifdef LKY_IMono
  _iinst = 0;
  #endif

  #ifdef LKY_ITri
  for (i = 0; i < 3; i++)
    {
    _iinst[i] = 0;
    }
  #endif
  }

void LinkyHistTIC::Update()
  {   /* Called from the main loop */
  char c;
  uint8_t cks, i, j;
  uint32_t ba;
  uint16_t pa;
  bool Run = true;

  /* Achtung : actions are in the reverse order to prevent
   *           execution of all actions in the same turn of
   *           the loop */

  /* 1st part, last action : decode information */
  if (_FR & bLy_Dec)
    {
    ResetBits(_FR, bLy_Dec);     /* Clear requesting flag */
    _pDec = strtok(NULL, CLy_Sep);

    switch (_GId)
      {
      case CLy_papp:
        pa = atoi(_pDec);
        if (_papp != pa)
          {  /* New value for papp */
          _papp = pa;
          SetBits(_DNFR, (1<<CLy_papp));
          }
        break;

      #ifdef LKY_Base
      case CLy_base:
        ba = atol(_pDec);
        if (_base != ba)
          {  /* New value for _base */
          _base = ba;
          SetBits(_DNFR, (1<<CLy_base));
          }
        break;
      #endif

      #ifdef LKY_HPHC
      case CLy_hchp:
        ba = atol(_pDec);
        if (_hchp != ba)
          {  /* New value for _hchp */
          _hchp = ba;
          SetBits(_DNFR, (1<<CLy_hchp));
          }
        break;

      case CLy_hchc:
        ba = atol(_pDec);
        if (_hchc != ba)
          {  /* New value for _hchc */
          _hchc = ba;
          SetBits(_DNFR, (1<<CLy_hchc));
          }
        break;

      case CLy_ptec:
        /*  Format PTEC :
         *    HP..    HC..
         *    0123    0123
         *  Just compare the 2 first chars */
        i = C_HPleines;      /* By default HP */
        if (strncmp_P(_pDec, PLy_HC, 2) == 0)
          { /* Tarif HC */
          i = C_HCreuses;
          }
        if (_ptec != i)
          {  /* PTEC has changed */
          _ptec = i;
          SetBits(_DNFR, (1<<CLy_ptec));  /* New value for _ptec */
          }
        break;
      #endif  /* LKY_HPHC */

       case CLy_isousc:
        i = (uint8_t) atoi(_pDec);
        if (_isousc != i)
          {  /* New value for _iinst */
          _isousc = i;
          SetBits(_DNFR, (1<<CLy_isousc));
          }
        break;

      
      #ifdef LKY_IMono
      case CLy_iinst:
        i = (uint8_t) atoi(_pDec);
        if (_iinst != i)
          {  /* New value for _iinst */
          _iinst = i;
          SetBits(_DNFR, (1<<CLy_iinst));
          }
        break;
      #endif

      #ifdef LKY_ITri
      case CLy_iinst1:
      case CLy_iinst2:
      case CLy_iinst3:
        i = (uint8_t) atoi(_pDec);
        j = _GId - CLy_iinst1;
        if (_iinst[j] != i)
          {  /* New value for _iinst[] */
          _iinst[j] = i;
          SetBits(_DNFR, (1<<_GId));
          }
      #endif

      default:
        break;
      }
    }

  /* 2nd part, second action : group identification */
  if (_FR & bLy_GId)
    {
    ResetBits(_FR, bLy_GId);   /* Clear requesting flag */
    _pDec = strtok(_pDec, CLy_Sep);

    if (strcmp_P(_pDec, PLy_papp) == 0)
      {
      Run = false;
      _GId = CLy_papp;
      }

    #ifdef LKY_Base
    if (Run && (strcmp_P(_pDec, PLy_base) == 0))
      {
      Run = false;
      _GId = CLy_base;
      }
    #endif

    #ifdef LKY_HPHC
    if (Run && (strcmp_P(_pDec, PLy_hchp) == 0))
      {
      Run = false;
      _GId = CLy_hchp;
      }
    if (Run && (strcmp_P(_pDec, PLy_hchc) == 0))
      {
      Run = false;
      _GId = CLy_hchc;
      }
    if (Run && (strcmp_P(_pDec, PLy_ptec) == 0))
      {
      Run = false;
      _GId = CLy_ptec;
      }
    #endif

    #if (defined(LKY_IMono) || defined (LKY_ITri))
    if (Run && (strncmp_P(_pDec, PLy_iinst, 4) == 0))
      {   /*  Format :
           *    IINSTx  x = 1, 2, 3 or '\0' for single phase
           *    012345
           */
      switch (*(_pDec+5))
        {
        case '2':   /* Phase 2 */
          _GId = C_Phase_2 + CLy_iinst;
          break;

        case '3':   /* Phase 3 */
          _GId = C_Phase_3 + CLy_iinst;
          break;

        default:   /* Phase 1 or single phase */
          _GId = C_Phase_1 + CLy_iinst;
          break;
        }    /* End switch */

        Run = false;
        }
    #endif

    if (Run && (strcmp_P(_pDec, PLy_isousc) == 0))
      {
      Run = false;
      _GId = CLy_isousc;
      }

    if (!Run)
      {
      SetBits(_FR, bLy_Dec);   /* Next = decode */
      }
    }

  /* 3rd part, first action : check cks */
  if (_FR & bLy_Cks)
    {
    ResetBits(_FR, bLy_Cks);   /* Clear requesting flag */
    cks = 0;
    if (_iCks >= CLy_MinLg)
      {   /* Message is long enough */
      for (i = 0; i < _iCks - 1; i++)
        {
        cks += *(_pDec + i);
        }
      cks = (cks & 0x3f) + Car_SP;

      #ifdef LINKYDEBUG
      Serial << _pDec << endl;
      #endif

      if (cks == *(_pDec + _iCks))
        {  /* Cks is correct */
        *(_pDec + _iCks-1) = '\0';
                       /* Terminate the string just before the Cks */
        SetBits(_FR, bLy_GId);  /* Next step, group identification */

        #ifdef LINKYDEBUG
        }
        else
        {
        i = *(_pDec + _iCks);
        Serial << F("Error Cks ") << cks << F(" - ") << i << endl;
        #endif
        }   /* Else, Cks error, do nothing */

      }     /* Message too short, do nothing */
    }

  /* 4th part, receiver processing */
  while (_LKY.available())
    {  /* At least 1 char has been received */
    c = _LKY.read() & 0x7f;   /* Read char, exclude parity */

    if (_FR & bLy_Rec)
      {  /* On going reception */
      if (c == '\r')
        {   /* Received end of group char */
        ResetBits(_FR, bLy_Rec);   /* Receiving complete */
        SetBits(_FR, bLy_Cks);     /* Next check Cks */
        _iCks = _iRec-1;           /* Index of Cks in the message */
        *(_pRec + _iRec) = '\0';   /* Terminate the string */

        /* Swap reception and decode buffers */
        if (_FR & bLy_RxB)
          {  /* Receiving in B, Decode in A, swap */
          ResetBits(_FR, bLy_RxB);
          _pRec = _BfA;       /* --> Receive in A */
          _pDec = _BfB;       /* --> Decode in B */
          }
          else
          {  /* Receiving in A, Decode in B, swap */
          SetBits(_FR, bLy_RxB);
          _pRec = _BfB;     /* --> Receive in B */
          _pDec = _BfA;     /* --> Decode in A */
          }

        }  /* End reception complete */
        else
        {  /* Other character */
        *(_pRec+_iRec) = c;   /* Store received character */
        _iRec += 1;
        if (_iRec >= CLy_BfSz-1)
          {  /* Buffer overrun */
          ResetBits(_FR, bLy_Rec); /* Stop reception and do nothing */
          }
        }  /* End other character than '\r' */
      }    /* End on-going reception */
      else
      {    /* Reception not yet started */
      if (c == '\n')
        {   /* Received start of group char */
        _iRec = 0;
        SetBits(_FR, bLy_Rec);   /* Start reception */
        }
      }
    }  /* End while */
  }

bool LinkyHistTIC::pappIsNew()
  {
  bool Res = false;

  if(_DNFR & (1<<CLy_papp))
    {
    Res = true;
    ResetBits(_DNFR, (1<<CLy_papp));
    }
  return Res;
  }

uint16_t LinkyHistTIC::papp()
  {
  return _papp;
  }

#ifdef LKY_Base
bool LinkyHistTIC::baseIsNew()
  {
  bool Res = false;

  if(_DNFR & (1<<CLy_base))
    {
    Res = true;
    ResetBits(_DNFR, (1<<CLy_base));
    }
  return Res;
  }

uint32_t LinkyHistTIC::base()
  {
  return _base;
  }
#endif  /* LKY_Base */

#ifdef LKY_HPHC
bool LinkyHistTIC::hchpIsNew()
  {
  bool Res = false;

  if(_DNFR & (1<<CLy_hchp))
    {
    Res = true;
    ResetBits(_DNFR, (1<<CLy_hchp));
    }
  return Res;
  }
uint32_t LinkyHistTIC::hchp()
  {
  return _hchp;
  }

bool LinkyHistTIC::hchcIsNew()
  {
  bool Res = false;

  if(_DNFR & (1<<CLy_hchc))
    {
    Res = true;
    ResetBits(_DNFR, (1<<CLy_hchc));
    }
  return Res;
  }
uint32_t LinkyHistTIC::hchc()
  {
  return _hchc;
  }

bool LinkyHistTIC::ptecIsNew()
  {
  bool Res = false;

  if(_DNFR & (1<<CLy_ptec))
    {
    Res = true;
    ResetBits(_DNFR, (1<<CLy_ptec));
    }
  return Res;
  }
uint8_t LinkyHistTIC::ptec()
  {
  return _ptec;
  }
#endif  /* LKY_HPHC */

#ifdef LKY_IMono
bool LinkyHistTIC::iinstIsNew()
  {
  bool Res = false;

  if(_DNFR & (1<<CLy_iinst))
    {
    Res = true;
    ResetBits(_DNFR, (1<<CLy_iinst));
    }
  return Res;
  }

uint8_t LinkyHistTIC::iinst()
  {
  return _iinst;
  }
#endif  /* LKY_IMono */

#ifdef LKY_ITri
bool LinkyHistTIC::iinstIsNew(uint8_t Ph)
  {
  bool Res = false;

  if(_DNFR & (1<<(CLy_iinst1 + Ph)))
    {
    Res = true;
    ResetBits(_DNFR, (1<<(CLy_iinst1 + Ph)));
    }
  return Res;
  }

uint8_t LinkyHistTIC::iinst(uint8_t Ph)
  {
  return _iinst[Ph];
  }
#endif  /* LKY_ITri */



bool LinkyHistTIC::isouscIsNew()
  {
  bool Res = false;

  if(_DNFR & (1<<CLy_isousc))
    {
    Res = true;
    ResetBits(_DNFR, (1<<CLy_isousc));
    }
  return Res;
  }

uint8_t LinkyHistTIC::isousc()
  {
  return _isousc;
  }




/***********************************************************************
               Fin d'objet recepteur TIC Linky historique
***********************************************************************/

Pour compiler tout ça il vous faudra au moins la version 1.8.13 de l’IDE arduino (je sais que sous la 1.8.8 ça plante…) et certaines librairies additionnelles (toutes dispo dans le getionnaire de librairies integré)

  • Ethernet
  • EasyWebServer de Kalle Lundberg
  • Streaming de Mikal Hart

Avant de complier, il faudra adapter certaines portion du sketch et des fichiers liés a votre configuration :

  • Configuration réseau (adresse MAC, IP, Masque, passerelle) ligne 60 du .ino
  • Tarif souscrit (Base ou Heures creuse) et type de raccordement (Mono ou Tri) ligne 47 de LinkyHistTIC.h
  • Eventuellement numéro de PIN arduino utilisé pour le RX téléinfo (8 par défaut) ligne 48 du .ino

Une fois le code complié et téléchargé dans l’Arduino et celui-ci raccordé à la téléinfo et au réseau ethernet il suffit de se rentre sur http://IP_ARDUINO pour lire les valeurs disponible. Cette page affiche tous les groupes qu’est capable de décoder le programmet quelque soit la configuration, il est donc normal que certaines valeurs soient à 0
Le fichier XML est dispo à l’adresse suivante http://IP_ARDUINO/xml (c’est pas un vrai XML mais si besoin Jeedom arrive à l’exploiter)
Le fichier JSON est dispo à l’adresse suivante http://IP_ARDUINO/json c’est lui que l’on va utiliser dans Jeedom
A note que le fichier xml et le fichier json ne font apparaitre que les groupes correspondants à votre configuration (mon/tri/base/hc)

Côté Jeedom, il faut créer un virtuel avec les infos que l’on veut afficher et un scenario qui se présente ainsi

Après un peu de customisation du virtuel ça donne ça :
VirtuelLinky

Voilà, peut être que ça sera utile à quelqu’un un jour ou pas…

J’ai des idées pour améliorer le principe et ce sera peut être pour une prochaine version :
Comme la librairie pour decoder les trames fournit des « drapeau » sur le changement de valeur, il serait plus performant de faire des push directement vers jeedom lorsque qu’une valeur change plutôt que ce soit Jeedom qui interoge l’arduino à intervalle regulier (ici j’ai mis 1mn)
Mais pour ça il faudrait une page de configuration qui permet de renseigner l’URL, la clé API et les ID des infos virtuel pour faire les push
Il faudrait aussi utiliser le mode DHCP à la première mise en route et donc avoir aussi une page pour configurer l’adresse IP de l’Arduino.
Tout ça demande pas mal de place, donc un Arduino sera trop petit, il faudrait sans doute passer par un ESP, ce qui permetrait au passage de faire la même chose en wifi.
Au final faire un plug-in pour rendre l’utilisation de tout ça plus pratique a ceux qui ne sont pas trop à l’aise avec ce genre de bidouilles.
Bref, pas mal d’idées d’amelioration mais aussi beaucoup de boulot… pas sur que ce soit fait un jour.

Voisi le .ino du sketch arduino (c’était trop long pour tenir dans un seul post)

//Sources :
//
//EasyWebServer : https://github.com/llelundberg/EasyWebServer
//SPI: Librairie SPI integrée IDE Arduino (pour communication avec module Ethernet) 
//Ethernet : Libairie pour chipset ethernet W5100
//
// Téléinfo (TIC) par MicroQuettas
// https://forum.arduino.cc/index.php?topic=533891.0
//

#define DEBUGtoSERIAL 0  // 0, ou 1 pour debug dans la console serie (consomme environ 5% de ROM et 13% de RAM)

//Affectation des E/S physiques (numero de PIN Arduino)
// ATTENTION ! Certaines pins reservé pour le système
// Pour UNO/NANO :
//  4 = W5100
//  8 = RX téléinfo
//  9 = TX téléinfo (non utilisé)
// 10 = SS (W5100)
// 11 = MOSI (W5100)
// 12 = MISO (W5100)
// 13 = SCK (W5100) / BUILTIN_LED


/***************************** Includes *******************************/
#include <string.h>
#include "LinkyHistTIC.h"
 
#include <SPI.h>
#include <Ethernet.h>
#include <EasyWebServer.h>

// Déclaration des variables
unsigned long previousMillis = 0;
unsigned long indexBase = 0;
unsigned long indexHC = 0;
unsigned long indexHP = 0;
bool flagHP = false;
unsigned int courantIinst = 0;
unsigned int intensitesousc = 0;
unsigned int PhaseIinst[3] = {0, 0, 0};
long puissancePapp = 0;


//Déclaration des constantes

const uint8_t pin_LkyRx = 8;   //Numero de PIN pour RX téléinfo
const uint8_t pin_LkyTx = 9;   /* !!! Not used but reserved !!! 
                                  * Do not use for anything else */

/************************* Object instanciation ***********************/
LinkyHistTIC Linky(pin_LkyRx, pin_LkyTx);
                                  



// Enter a MAC address and IP address for your controller below.
// The IP address will be dependent on your local network:
byte mac[] = {0xDE, 0xAD, 0xBE, 0xE1, 0xFE, 0xE4};
IPAddress ip(192, 168, 0, 151);
IPAddress gateway(192, 168, 0, 1);
IPAddress subnet(255, 255, 255, 0);

// Initialize the Ethernet server library
// with the IP address and port you want to use
// (port 80 is default for HTTP):
EthernetServer server(80);

void setup() {
#if (DEBUGtoSERIAL == 1)  
Serial.begin(57600);
 int timeout = 0;
 while(!Serial){
  delay(1);
  if (timeout++ > 25) break;
 }
#endif
  // Start the Ethernet connection and the server:

  Ethernet.begin(mac, ip, gateway, gateway, subnet);
  server.begin();
#if (DEBUGtoSERIAL == 1)
  Serial.print("Server is at ");
  Serial.println(Ethernet.localIP());
  Serial.print("the subnet mask is ");
  Serial.println(Ethernet.subnetMask());
  Serial.print("with gateway at ");
  Serial.println(Ethernet.gatewayIP());
#endif
 
 /* Initialise the Linky receiver */
  Linky.Init();



#if (DEBUGtoSERIAL == 1)
  Serial.println("Hello, World !");
#endif


}
// FIN SETUP





/*****************************************************
 *         BOUCLE PRINCIPALE DU PROGRAMME            *
 *****************************************************/  

void loop() {
  
  Linky.Update(); 
  uint8_t i; //Variable de boucle pour compteur triphasé (fonctionnalité non testée)

  // Lecture de la puissance apparente
   if (Linky.pappIsNew())
    {
      #if (DEBUGtoSERIAL == 1)
        Serial.println(F("New PAPP"));
      #endif
      puissancePapp = Linky.papp();  
    }

  // Lecture de l'intensité souscrite
   if (Linky.isouscIsNew())
    {
      #if (DEBUGtoSERIAL == 1)
        Serial.println(F("New ISOUC"));
      #endif
      intensitesousc = Linky.isousc();  
    } 

 //Lecture des index de consommation
 //cas abonnement de base   
 #ifdef LKY_Base
  if (Linky.baseIsNew())
    {
      #if (DEBUGtoSERIAL == 1)
        Serial.println(F("New BASE"));
      #endif
      indexBase = Linky.base();    
    }
 #endif   

//Cas abonnement HC/HP
#ifdef LKY_HPHC
  if (Linky.hchpIsNew())
    {
    #if (DEBUGtoSERIAL == 1)
        Serial.println(F("New HCHP"));
    #endif
    indexHP = Linky.hchp();
    }
  if (Linky.hchcIsNew())
    {
     #if (DEBUGtoSERIAL == 1)
        Serial.println(F("New HCHC"));
     #endif 
    indexHC = Linky.hchc();
    }
  if (Linky.ptecIsNew())
    {
    #if (DEBUGtoSERIAL == 1)
        Serial.println(F("New PTEC"));
    #endif  
    if (Linky.ptec() == Linky.C_HPleines)
      {
      flagHP = true;
      }
      else
      {
      flagHP = false;
      }
    }
  #endif

// Lecture valeur Intensité phase(s)
// Cas Monophasé
 #ifdef LKY_IMono
  if (Linky.iinstIsNew())
    {
    #if (DEBUGtoSERIAL == 1)
        Serial.println(F("New IINST"));
    #endif  
    courantIinst = Linky.iinst();
    }
  #endif
  
//Cas Triphasé
  #ifdef LKY_ITri
  for (i = Linky.C_Phase_1; i <= Linky.C_Phase_3; i++)
    {
    if (Linky.iinstIsNew(i))
      #if (DEBUGtoSERIAL == 1)
        Serial.print(F("New IINST"));
        Serial.println(i);
      #endif
      {
      PhaseIinst[i] = Linky.iinst(i);
      }
    }
  #endif
 

  // Partie serveur web, attente d'une connexion client
  EthernetClient client = server.available();
  if (client) {
    
    #if (DEBUGtoSERIAL == 1)
      Serial.println("New client!");
    #endif
    EasyWebServer w(client);                    // Read and parse the HTTP Request
    #if (DEBUGtoSERIAL == 1)
     Serial.println(w.url);
     Serial.println(w.verb);
     Serial.println(w.querystring);
     
    #endif
    
    w.serveUrl("/",rootPage);                   // Root page
    w.serveUrl("/xml",xmlFile);                 // Fichier pseudo XML (HTML contenant du XML...)
    w.serveUrl("/json",jsonFile,EWS_TYPE_JSON); // Fichier JSON
    
  }  

}

//***********************************************************
//  GENERATION DES PAGES INTERNET
//***********************************************************



//Rafraichissement de l'Etat Sortie affichée sur la page web
void FormatTexte(EasyWebServer &w){
  w.client.println(F("Sortie au format"));
  w.client.println(F("texte"));
}

    
// PAGE RACINE
void rootPage(EasyWebServer &w){

 

  //Génération du code HTML
  
  w.client.println(F("<!DOCTYPE HTML>"));
  w.client.println(F("<html><head><title>Serveur Teleinfo Web</title></head><body>"));
  w.client.println(F("<p>Bienvenue sur mont petit serveur web téléinfo Linky</p>")); 
  w.client.print(F("<P>le type de raccordement configuré est : "));
  #ifdef LKY_IMono
  w.client.print(F("MONOPHASE"));
  #endif
  #ifdef LKY_ITri
  w.client.print(F("TRIPHASE"));
  #endif
  w.client.println(F("</P>"));

  w.client.print(F("<P>l'option tarifaire configurée est : "));
  #ifdef LKY_Base
  w.client.print(F("BASE"));
  #endif
  #ifdef LKY_HPHC
  w.client.print(F("HEURES CREUSES"));
  #endif
  w.client.println(F("</P>"));
  w.client.print(F("<P>l'index Base vaut : "));
  w.client.print(indexBase);
  w.client.println(F("Wh</P>"));
   w.client.print(F("<P>l'index HP vaut : "));
  w.client.print(indexHP);
  w.client.println(F("Wh</P>"));
   w.client.print(F("<P>l'index HC vaut : "));
  w.client.print(indexHC);
  w.client.println(F("Wh</P>"));
   w.client.print(F("<P>la periode heures pleines est : "));
  if (flagHP == true){
    w.client.print(F("PLEINE"));
  } else{
    w.client.print(F("CREUSE"));
  }
  w.client.println(F("</P>"));
  w.client.print(F("<P>L'intensité instantannée est de : "));
  w.client.print(courantIinst);
  w.client.println(F(" A</P>"));
  w.client.print(F("<P>L'intensité instantannée tri est de : "));
  for (int j = 0; j<3; j++){
    w.client.print(PhaseIinst[j]);
    w.client.print(F(" A; "));
  }
  w.client.println(F("</P>"));
  w.client.print(F("<P>L'intensité souscrite est de : "));
  w.client.print(intensitesousc);
  w.client.println(F(" A</P>"));
  w.client.println(F("<P>La puissance apparente est de : "));
  w.client.println(puissancePapp);
  w.client.println(F(" VA</P>"));
  w.client.println(F("</body></html>"));
}


// PAGE XML
void xmlFile(EasyWebServer &w){
  
w.client.println(F("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"));
w.client.println(F("<ROOT>"));
w.client.println(F("<INDEX>"));
#ifdef LKY_Base
w.client.print(F("<INDEXBASE>"));
w.client.print(indexBase);
w.client.println(F("</INDEXBASE>"));
#endif
#ifdef LKY_HPHC
w.client.print(F("<INDEXHP>"));
w.client.print(indexHP);
w.client.println(F("</INDEXHP>"));
w.client.print(F("<INDEXHC>"));
w.client.print(indexHC);
w.client.println(F("</INDEXHC>"));
#endif
w.client.println(F("</INDEX>"));
w.client.println(F("<VALEURS>"));
w.client.print(F("<PINST>"));
w.client.print(puissancePapp);
w.client.println(F("</PINST>"));
#ifdef LKY_IMono
w.client.print(F("<IINST>"));
w.client.print(courantIinst);
w.client.println(F("</IINST>"));
#endif
#ifdef LKY_ITri
w.client.print(F("<IINST1>"));
w.client.print(PhaseIinst[0]);
w.client.println(F("</IINST1>"));
w.client.print(F("<IINST2>"));
w.client.print(PhaseIinst[1]);
w.client.println(F("</IINST2>"));
w.client.print(F("<IINST3>"));
w.client.print(PhaseIinst[2]);
w.client.println(F("</IINST3>"));
#endif
w.client.println(F("</VALEURS>"));
w.client.println(F("<INDICATEURS>"));
#ifdef LKY_HPHC
w.client.print(F("<PTEC>"));
if (flagHP == true){
  w.client.print(F("HP"));
}else{
  w.client.print(F("HC"));
}
w.client.println(F("</PTEC>"));
#endif
w.client.print(F("<ISOUSC>"));
w.client.print(intensitesousc);
w.client.println(F("</ISOUSC>"));
w.client.print(F("<OPTARIF>"));
#ifdef LKY_Base
w.client.print(F("BASE"));
#endif
#ifdef LKY_HPHC
w.client.print(F("HC"));
#endif
w.client.println(F("</OPTARIF>"));
w.client.println(F("</INDICATEURS>"));
w.client.println(F("</ROOT>"));
}

//PAGE JSON
void jsonFile(EasyWebServer &w){

  #ifdef LKY_Base
  w.client.println((String)F("{\"BASE\":\"") + indexBase + F("\","));
  #endif
  #ifdef LKY_HPHC
  w.client.println((String)F("{\"HCHC\":\"") + indexHC +  F("\","));
  w.client.println((String)F("\"HCHP\":\"") + indexHP + F("\","));
  w.client.print(F("\"PTEC\":\""));
  if (flagHP == true){
     w.client.print(F("HP"));
  }else{
    w.client.print(F("HC"));
  }
  w.client.println(F("\","));
  #endif
  #ifdef LKY_IMono
  w.client.println((String)"\"IINST\":\"" + courantIinst + "\",");
  #endif
  #ifdef LKY_ITri
  w.client.println((String)"\"IINST1\":\"" + PhaseIinst[0] + "\",");
  w.client.println((String)"\"IINST2\":\"" + PhaseIinst[1] + "\",");
  w.client.println((String)"\"IINST3\":\"" + PhaseIinst[2] + "\",");
  #endif
  w.client.println((String)F("\"ISOUSC\":\"") + intensitesousc +  F("\","));

  w.client.println((String)F("\"PAPP\":\"") + puissancePapp + F("\","));
  #ifdef LKY_Base
   w.client.println((String)F("\"OPTARIF\":\"BASE\"}"));
  #endif
  #ifdef LKY_HPHC
   w.client.println((String)F("\"OPTARIF\":\"HC\"}"));
  #endif
  //w.client.println(F("\"\":\"\","));
  //w.client.println(F(""));  
}

Et ici le script du scenario (à adapter en fonction de votre situation : mono/tri/base/hc)
la division par 1000 des index est pour un affichage en kWh

$tags = $scenario->getTags(); // récuperation des tag

$url = 'http://'.$tags['#TeleinfoIP#'].'/json';
	$curl = curl_init();
	curl_setopt($curl, CURLOPT_URL, $url);
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

	$datas = curl_exec($curl); //appel du fichier JSON de TeleinfoIP
	$err="";
	$tags["#enligne#"] = true;
	if(curl_error($curl)) {
      	$tags["#enligne#"] = false;
    	$scenario->setLog('DEBUG 0 ------------------> Erreur sur CURL N° : '.curl_errno($curl).' , '.curl_error($curl));
  		$err = 'Erreur sur CURL N° : '.curl_errno($curl).' , '.curl_error($curl);
	}
	curl_close($curl);// fermeture de la requette
	
	$jsonarray = json_decode($datas, true); //parser JSON pour récuperation des valeurs

	$tags['#PAPP#'] = $jsonarray["PAPP"];
	$tags['#indexHC#'] = intval($jsonarray["HCHC"])/1000;
	$tags['#indexHP#'] = intval($jsonarray["HCHP"])/1000;
    $tags['#Ptec#'] = $jsonarray["PTEC"];
	$tags['#Iinst#'] = $jsonarray["IINST"];
	$tags['#Isousc#'] = $jsonarray["ISOUSC"];

$scenario->setTags($tags); // affectation des tags