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 :
- lire les trames sur le port série ;
- stocker les données dans une base relationnelle pour les requêter ensuite ;
- 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-config→ Interface → Serial Port : login shell non, hardware oui). - Accès
sudodepuis le comptepi.
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-phpsont 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 template0est nécessaire dès que les localesLC_COLLATE/LC_CTYPEdiffèrent de celles detemplate1(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érerscram-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 parpsql(équivalent duLOCAL 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.
Commentaires
Aucun commentaire pour l'instant. Soyez le premier !
Laisser un commentaire