📖 Chapitre 23/27 — La domotique chez soi Voir le sommaire →
← Retour
domotique

Protocole de la téléinformation client (TIC, mode historique)

Cédrix · 01/01/2021
Modifié le 23 mai 2026 à 04h58

1/ 7 j  ·  1/ 14 j  ·  1/ 30 j  lecteurs

Contexte

Les articles précédents ont décrit comment accéder physiquement à la sortie TIC du compteur (« Le bornier téléinfo ») et comment démoduler son signal pour obtenir un flux série TTL exploitable (« Démodulation ASK »).

L’UART reçoit maintenant quelque chose — mais quoi ? Ce flux, c’est un protocole texte normalisé par Enedis. Cet article décrit sa structure côté logiciel pour pouvoir :

  1. Reconnaître le début et la fin d’une trame.
  2. Extraire chaque groupe d’information (étiquette + valeur).
  3. Vérifier l’intégrité d’un groupe via son checksum — étape souvent ignorée des tutoriels mais essentielle en environnement bruyant.

Cet article couvre uniquement le mode historique (1200 bauds), qui est la configuration par défaut des compteurs Linky et le seul mode disponible sur les anciens compteurs électroniques. Le mode standard Linky (9600 bauds) a une structure différente — il fera l’objet d’un article séparé.


Configuration de la liaison série

Les paramètres généraux (1200 7E1) ont été présentés dans l’article bornier. Côté Linux, la configuration concrète du port série donne :

stty -F /dev/ttyAMA0 1200 cs7 parenb -parodd -cstopb -ixon -icrnl raw

Les options -ixon -icrnl raw désactivent l’interprétation logicielle des caractères de contrôle — important parce que le protocole TIC utilise CR (0x0D) et LF (0x0A) comme délimiteurs, pas comme fins de ligne.


Structure d’une trame

Le compteur émet des trames en continu, environ une par seconde. Une trame est un bloc encadré par deux caractères de contrôle :

<STX> ...contenu de la trame... <ETX>
Caractère Code hex Rôle
STX 0x02 Début de trame (Start of Text)
ETX 0x03 Fin de trame (End of Text)

Entre ces deux bornes, la trame contient une succession de groupes d’information. Chaque groupe représente une grandeur du compteur (par exemple l’intensité instantanée ou l’index heures creuses) et a toujours le même format :

<LF> ÉTIQUETTE <SP> DONNÉE <SP> CHECKSUM <CR>
Élément Code hex Taille Description
LF 0x0A 1 octet Début de groupe (Line Feed)
Étiquette 4 à 8 caractères Identifie la grandeur (ex. IINST, HCHP)
SP 0x20 1 octet Séparateur (Space)
Donnée 1 à 12 caractères Valeur ASCII
SP 0x20 1 octet Séparateur
Checksum 1 octet Caractère de contrôle imprimable (0x20–0x5F)
CR 0x0D 1 octet Fin de groupe (Carriage Return)

Exemple de trame réelle (option BASE)

<STX>
<LF>ADCO 123456789012 P<CR>
<LF>OPTARIF BASE 0<CR>
<LF>ISOUSC 30 9<CR>
<LF>BASE 002565285 ,<CR>
<LF>PTEC TH..  $<CR>
<LF>IINST 002 Y<CR>
<LF>IMAX 030 B<CR>
<LF>PAPP 00420 '<CR>
<LF>MOTDETAT 000000 B<CR>
<ETX>

Lue en clair : compteur n° 123456789012, abonnement BASE 30 A, index 2 565 285 Wh, intensité instantanée 2 A, intensité max atteinte 30 A, puissance apparente 420 VA.


Le checksum : ce qu’il vérifie et comment le calculer

Le checksum est un caractère unique qui permet à un récepteur de détecter une trame corrompue (parasite secteur, mauvais contact, erreur de démodulation). C’est un filet de sécurité indispensable : sans vérification, un parasite peut transformer IINST 002 en IINST 022 sans qu’on s’en aperçoive, et fausser durablement les statistiques.

Algorithme

D’après la spécification Enedis :

La checksum est calculée sur l’ensemble des caractères allant du début du champ étiquette à la fin du champ donnée, caractère SP inclus. On fait tout d’abord la somme des codes ASCII de tous ces caractères. Pour éviter d’introduire des fonctions ASCII (00 à 1F en hexadécimal), on ne conserve que les six bits de poids faible du résultat obtenu (cette opération se traduit par un ET logique entre la somme précédemment calculée et 03Fh). Enfin, on ajoute 20 en hexadécimal.

En trois étapes :

  1. Somme des codes ASCII de tous les caractères allant du premier caractère de l’étiquette jusqu’au séparateur SP qui suit la donnée, ce séparateur inclus.
  2. Masquage des 6 bits de poids faible : S & 0x3F.
  3. Ajout de 0x20 pour obtenir un caractère ASCII imprimable.

Le résultat est toujours dans la plage 0x200x5F (espace, chiffres, lettres majuscules, ponctuation), donc lisible à l’œil dans une capture de trame.

⚠️ Attention au piège du SP. La spec dit « jusqu’au SP entre Donnée et Checksum, inclus ». Le SP entre Étiquette et Donnée est forcément dans la somme (il est à l’intérieur de la plage). Mais le SP entre Donnée et Checksum est lui aussi compté. Une implémentation qui oublie ce second SP donne un résultat décalé de 0x20 et rejette toutes les trames.

Exemple chiffré : PMAX 06030

On veut vérifier le checksum du groupe PMAX<SP>06030<SP>?<CR> (le ? est le caractère à calculer).

Étape 1 — Somme des codes ASCII des caractères de PMAX 06030 (le SP final inclus) :

Caractère P M A X SP 0 6 0 3 0 SP
Code hex 50 4D 41 58 20 30 36 30 33 30 20

Somme : 50 + 4D + 41 + 58 + 20 + 30 + 36 + 30 + 33 + 30 + 20 = 0x26F

Étape 2 — Masquage sur 6 bits :

  0x26F  =  10 0110 1111
& 0x03F  =  00 0011 1111
─────────────────────────
  0x02F  =  00 0010 1111

Étape 3 — Ajout de 0x20 :

0x2F + 0x20 = 0x4F

0x4F est le code ASCII de O (lettre majuscule). C’est le checksum attendu pour ce groupe.

Implémentation en C

char tic_checksum(const char *etiquette, const char *donnee) {
    unsigned int s = 0;
    while (*etiquette) s += (unsigned char)*etiquette++;
    s += ' ';                                  // SP entre étiquette et donnée
    while (*donnee)    s += (unsigned char)*donnee++;
    s += ' ';                                  // SP entre donnée et checksum
    return (char)((s & 0x3F) + 0x20);
}

Implémentation en Python

def tic_checksum(etiquette: str, donnee: str) -> str:
    s = sum(ord(c) for c in etiquette) + ord(' ')
    s += sum(ord(c) for c in donnee)    + ord(' ')
    return chr((s & 0x3F) + 0x20)

assert tic_checksum("PMAX", "06030") == "O"

Sur certains très anciens compteurs, on rencontre une variante de l’algorithme où seul le premier SP (entre étiquette et donnée) est inclus dans la somme. Si la vérification échoue systématiquement sur du matériel ancien, essayer cette variante en retirant le + ord(' ') final.


Liste des étiquettes (option par option)

Les groupes effectivement présents dans une trame dépendent de l’option tarifaire souscrite. Inutile de chercher un groupe BBRHCJB sur un compteur configuré en BASE — il n’existera jamais.

Identification et configuration (toutes options)

Étiquette Nb car. Unité Description
ADCO 12 N° d’identification du compteur
OPTARIF 4 Option tarifaire (BASE, HC.., EJP., BBRx)
ISOUSC 2 A Intensité souscrite
PTEC 4 Période tarifaire en cours
MOTDETAT 6 Mot d’état (autocontrôle du compteur)

Mesures instantanées (toutes options)

Étiquette Nb car. Unité Description
IINST 3 A Intensité instantanée
IMAX 3 A Intensité maximale atteinte
PAPP 5 VA Puissance apparente
ADPS 3 A Avertissement dépassement de puissance souscrite (émis uniquement en cas de dépassement effectif)

Index — option BASE

Étiquette Nb car. Unité Description
BASE 9 Wh Index général

Index — option Heures Creuses

Étiquette Nb car. Unité Description
HCHC 9 Wh Index heures creuses
HCHP 9 Wh Index heures pleines
HHPHC 1 Groupe horaire (identifie le planning HC/HP du compteur)

Index — option EJP

Étiquette Nb car. Unité Description
EJPHN 9 Wh Index heures normales
EJPHPM 9 Wh Index heures de pointe mobile
PEJP 2 Préavis EJP (envoyé 30 min avant le début d’une période EJP)

Index — option Tempo

Étiquette Nb car. Unité Description
BBRHCJB 9 Wh Index heures creuses, jours bleus
BBRHPJB 9 Wh Index heures pleines, jours bleus
BBRHCJW 9 Wh Index heures creuses, jours blancs
BBRHPJW 9 Wh Index heures pleines, jours blancs
BBRHCJR 9 Wh Index heures creuses, jours rouges
BBRHPJR 9 Wh Index heures pleines, jours rouges
DEMAIN 4 Couleur du lendemain (BLEU, BLAN, ROUG, ---- si inconnue avant 14h)
HHPHC 1 Groupe horaire

Stratégie de parsing

Un parseur TIC robuste suit ce schéma :

  1. Synchronisation : lire octet par octet jusqu’à recevoir STX (0x02). Avant ça, jeter tout.
  2. Boucle sur les groupes : tant qu’on n’a pas reçu ETX (0x03) :
    • Attendre LF (0x0A).
    • Lire jusqu’au prochain CR (0x0D) — c’est un groupe complet.
    • Découper sur les SP : trois champs (étiquette, donnée, checksum).
    • Vérifier le checksum. Si invalide → ignorer le groupe (ne pas casser la trame entière, juste sauter ce groupe).
    • Si valide → stocker la paire (étiquette → donnée).
  3. Trame complète : sur ETX, livrer la trame à l’application.

Ne jamais faire confiance à une trame dont un seul checksum a échoué. Une trame TIC est émise toutes les secondes : si la précédente est mauvaise, la suivante arrive tout de suite. Mieux vaut sauter une trame que d’enregistrer une mesure fantaisiste.


Suite

Avec la structure et le checksum maîtrisés, on peut écrire un programme qui lit /dev/ttyAMA0, valide chaque groupe et émet une trame JSON par cycle. C’est ce que fait raspjson, présenté dans l’article suivant sur la préparation du Raspberry Pi.

Partager : ✉ Mail X in 🐘
Commentaires

Aucun commentaire pour l'instant. Soyez le premier !

Laisser un commentaire
Un code de vérification sera envoyé à votre adresse email.