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 :
- Reconnaître le début et la fin d’une trame.
- Extraire chaque groupe d’information (étiquette + valeur).
- 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 :
- 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.
- Masquage des 6 bits de poids faible :
S & 0x3F. - Ajout de
0x20pour obtenir un caractère ASCII imprimable.
Le résultat est toujours dans la plage 0x20–0x5F (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 :
- Synchronisation : lire octet par octet jusqu’à recevoir
STX(0x02). Avant ça, jeter tout. - 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).
- Attendre
- 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.
Commentaires
Aucun commentaire pour l'instant. Soyez le premier !
Laisser un commentaire