← Retour
domotique

Téléinfo EDF sur Raspberry Pi — partie logicielle

Cédrix · 01/01/2021
Modifié le 22 mai 2026 à 15h38

5/ 7 j  ·  14/ 14 j  ·  14/ 30 j  lecteurs

Contexte

Dans la partie précédente, j’ai câblé l’interface optoélectronique entre le compteur EDF et le port série du Raspberry Pi. Les trames de téléinformation client (TIC) arrivent désormais sur /dev/ttyAMA0 en mode historique : 1200 bauds, 7 bits, parité paire, 1 bit de stop. Chaque trame contient un jeu d’étiquettes (ADCO, IINST, HCHC, HCHP, PTEC, PAPP…) que le compteur envoie toutes les secondes.

Reste à exploiter ce flux. La partie logicielle a trois rôles :

  1. lire les trames sur le port série ;
  2. stocker les données dans une base relationnelle pour les requêter ensuite ;
  3. archiver les trames brutes en CSV, en filet de sécurité hors-SGBD.

Tout tourne sur le Raspberry Pi lui-même : pas de serveur distant à ce stade. Le langage retenu est PHP (syntaxe proche du C, manipulation de chaînes confortable, écosystème PostgreSQL mûr) et le SGBD PostgreSQL (types riches, contraintes solides, intervalles de date natifs — utiles pour les agrégats horaires).

Cette page sert de référence technique : commandes exactes, fichiers de configuration touchés, et squelette de l’application.


Prérequis

  • Raspberry Pi sous Raspberry Pi OS (Bookworm ou ultérieur).
  • Port série activé et libéré de la console noyau (raspi-configInterfaceSerial Port : login shell non, hardware oui).
  • Accès sudo depuis le compte pi.

Versions visées dans ce document : PHP 8.2, PostgreSQL 16. Les chemins de configuration sont indiqués pour ces versions ; adapter les numéros si besoin.


1. Installation de PHP

sudo apt update
sudo apt install php php-cli php-pgsql

Les paquets installés couvrent :

  • l’interpréteur CLI (php) — c’est celui qui exécutera le démon de lecture ;
  • l’extension PostgreSQL (php-pgsql) ;
  • Apache et libapache2-mod-php sont tirés en dépendance ; ils ne sont pas nécessaires au démon de lecture mais serviront plus tard pour une interface web de visualisation.

Fichiers de configuration créés (PHP 8.2) :

Chemin Rôle
/etc/php/8.2/cli/php.ini Configuration de l’interpréteur en ligne de commande
/etc/php/8.2/apache2/php.ini Configuration du module Apache
/etc/php/8.2/mods-available/ Activation/désactivation des extensions

Vérification :

php -v
php -m | grep -i pgsql   # doit afficher "pgsql" et "pdo_pgsql"

2. Installation de PostgreSQL

sudo apt install postgresql

Le paquet installe le serveur, le client psql, et crée :

  • l’utilisateur système postgres ;
  • le cluster par défaut (main) dans /var/lib/postgresql/16/main ;
  • les fichiers de configuration dans /etc/postgresql/16/main/.

Le service démarre automatiquement. Vérification :

sudo systemctl status postgresql

3. Création de la base et du rôle

PostgreSQL ne distingue pas « utilisateur » et « groupe » : tout est un rôle. Un rôle qui peut se connecter au SGBD a l’attribut LOGIN.

Connexion en super-utilisateur via le compte système postgres :

sudo -u postgres psql

L’invite postgres=# apparaît : le moteur est prêt. Création du rôle de connexion puis de la base :

CREATE ROLE r_ampere
  WITH LOGIN
  PASSWORD 'This1sN0tAnPwd'
  VALID UNTIL 'infinity';

CREATE DATABASE ampere
  WITH OWNER     = r_ampere
       ENCODING  = 'UTF8'
       LC_COLLATE = 'fr_FR.UTF-8'
       LC_CTYPE   = 'fr_FR.UTF-8'
       TEMPLATE   = template0;

Le TEMPLATE template0 est nécessaire dès que les locales LC_COLLATE/LC_CTYPE diffèrent de celles de template1 (qui hérite des locales du cluster).

Quitter psql : \q.


4. Autoriser les connexions locales

Le fichier /etc/postgresql/16/main/pg_hba.conf contrôle qui peut se connecter, depuis où, et avec quelle méthode d’authentification. Format général :

type    database    user    address    method

Ajouter avant la ligne local all all peer :

local   ampere      r_ampere               scram-sha-256

Recharger la configuration :

sudo systemctl reload postgresql

Test de la connexion depuis le compte pi :

psql -U r_ampere -W -d ampere -h localhost

À propos de la méthode trust : certains tutoriels la suggèrent (« connexion locale sans mot de passe »). C’est pratique sur une machine personnelle, mais cela signifie n’importe quel utilisateur local peut se connecter à la base sans s’authentifier. Préférer scram-sha-256 (méthode par mot de passe par défaut depuis PostgreSQL 14) même en local.


5. Architecture du programme de lecture

Le démon principal est une boucle infinie qui, à chaque cycle, traite une trame :

boucle:
    trame  ← readTrame(/dev/ttyAMA0)
    trame.timestamp ← maintenant()
    saveTrameTampon(trame)   # mémoire vive, fenêtre glissante
    saveTrameBdd(trame)      # PostgreSQL
    saveTrameCsv(trame)      # fichier journalier

Justification des trois destinations :

  • Tampon mémoire : pour exposer la dernière trame à une éventuelle interface temps réel sans interroger le SGBD à chaque rafraîchissement.
  • Base de données : source de vérité pour les agrégats et l’analyse.
  • CSV : sauvegarde brute, indépendante du SGBD, qui permet de rejouer l’historique en cas de corruption de la base.

6. Chargement d’un CSV dans PostgreSQL

L’équivalent PostgreSQL de LOAD DATA LOCAL INFILE (MySQL) est la commande COPY. Deux variantes :

  • COPY ... FROM '/chemin' — le fichier est lu côté serveur (chemin sur la machine PostgreSQL) ;
  • \copy ... FROM '/chemin' — le fichier est lu côté client par psql (équivalent du LOCAL INFILE).

Depuis PHP, on passe par pg_copy_from() ou par une instruction COPY ... FROM STDIN envoyée via PDO. Exemple avec PDO :

<?php
$dsn  = 'pgsql:host=localhost;dbname=ampere';
$user = 'r_ampere';
$pass = 'This1sN0tAnPwd';

$csvfile   = 'releves/teleinfo_20181219.csv';
$tablename = 'tr_journalier';
$separator = ',';

if (!file_exists($csvfile)) {
    fwrite(STDERR, "Fichier introuvable : $csvfile\n");
    exit(1);
}

try {
    $pdo = new PDO($dsn, $user, $pass, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    ]);

    $pdo->exec("BEGIN");
    $pdo->pgsqlCopyFromFile(
        $tablename,
        $csvfile,
        $separator,
        '\\\\N'      // chaîne représentant NULL
    );
    $pdo->exec("COMMIT");

    echo "Import terminé.\n";
} catch (PDOException $e) {
    fwrite(STDERR, 'Erreur : ' . $e->getMessage() . "\n");
    exit(2);
}

pgsqlCopyFromFile() est une méthode spécifique au driver PostgreSQL de PDO. Elle est plus rapide qu’une boucle d’INSERT (un seul aller-retour réseau, une seule transaction).


7. Conversion d’un timestamp Unix en date lisible

L’article initial utilisait FROM_UNIXTIME() — c’est une fonction MySQL. Sous PostgreSQL :

SELECT to_timestamp(ts) AS date_lisible
FROM   tr_journalier;

to_timestamp() retourne un timestamp with time zone. Pour un affichage formaté :

SELECT to_char(to_timestamp(ts), 'YYYY-MM-DD HH24:MI:SS') AS date_lisible
FROM   tr_journalier;

8. API PHP de la bibliothèque

Le code expose des méthodes regroupées en trois familles. Les noms reprennent les étiquettes TIC pour rester lisibles depuis le code applicatif.

Mesures instantanées (dernière trame reçue)

Méthode Étiquette TIC Description
getIINST() IINST Intensité instantanée (A)
getPAPP() PAPP Puissance apparente (VA)
getPTEC() PTEC Période tarifaire en cours (HC.. / HP..)
getHCHC() HCHC Index heures creuses (Wh)
getHCHP() HCHP Index heures pleines (Wh)

Informations d’abonnement (stables)

Méthode Étiquette TIC Description
getADCO() ADCO Numéro d’identification du compteur
getOPTARIF() OPTARIF Option tarifaire (HC.., BASE…)
getISOUSC() ISOUSC Intensité souscrite (A)

Analyse de consommation

Toutes les méthodes ci-dessous prennent un argument $tarif ('HC' ou 'HP') et retournent une consommation en Wh pour la période demandée.

Méthode Fenêtre
getConsoPeriodeNow($tarif) Période tarifaire en cours
getConsoPeriodePrev($tarif) Période tarifaire précédente
getConsoPeriode24($tarif) Dernières 24 heures glissantes
getConsoPeriode7jours($tarif, $debut) 7 jours glissants à partir de $debut
getConsoMinute($tarif, $dateHeure) Minute donnée
getConsoHeure($tarif, $dateHeure) Heure donnée
getConsoJour($tarif, $date) Jour donné
getConsoSemaine($tarif, $date) Semaine ISO contenant $date
getConsoMois($tarif, $date) Mois calendaire de $date
getConsoAnnee($tarif, $date) Année de $date

Le calcul s’appuie sur la différence entre deux index HCHC/HCHP : on cherche la première et la dernière trame de la fenêtre, et on soustrait. C’est plus robuste que d’intégrer PAPP au cours du temps, car les index sont déjà la grandeur cumulée publiée par le compteur.


Suite

La partie suivante traitera du démarrage automatique du démon au boot (unité systemd), de la rotation des fichiers CSV, et des premières requêtes d’agrégation côté SQL.

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.