MAJ août 2016 : Depuis la parution de cet article en septembre 2014 (bientôt 2 ans Oo), de nombreuses évolutions ont été faites sur les Raspberry Pi et Raspbian. Grâce aux retours d’expériences de plusieurs lecteurs et à leur participation, cet article est aujourd’hui mis à jour Un gros merci à Arnaud (qui se reconnaîtra) qui m’a notamment envoyé de nombreuses informations sur les modifcations relatives à Raspbian Jessie et au RPi3
Vous pouvez notamment télécharger les versions revues et corrigées des scripts PHP par Arnaud, ici : teleinfo.zip
A l’ère de la domotique et des économies d’énergie, le suivi de la consommation d’électricité dans nos maison devient un point incontournable. Détecter les appareils électriques gourmands, optimiser sa consommation en jouant sur les tarifs heures creuses/pleines, opter pour des ampoules à économie d’énergie et couper ses équipements multimédia lorsqu’on ne s’en sert pas : autant de gestes qui prennent sens à partir du moment où l’on peut en mesurer l’effet sur sa facture EDF.
Dans cet article, nous allons explorer une méthode très simple pour collecter et analyser les informations de notre compteur EDF et les représenter sur un graphique. Comme toujours sur magdiblog.fr, nous utiliserons un Raspberry Pi pour construire ce petit système
Téléinfo EDF, qu’est ce que c’est ?
Depuis quelques années, EDF a ajouté à ses compteurs électriques domestiques la possibilité de lire à distance les informations enregistrées (consommation heures creuses/pleines, consommation instantanée, intensité max, etc…). Ceci permet, entre autre, de renvoyer les informations vers votre coffret EDF extérieur afin que les agents EDF puissent effectuer le relevé de consommation (et donc établir une facture) sans avoir à accéder à votre compteur à l’intérieur de votre maison. Les compteurs compatibles disposent de deux bornes I1 et I2, permettant de récupérer directement les informations sur un bus UART-TTL
Pour plus d’informations sur la téléinfo EDF et son mode de fonctionnement, vous pouvez consulter le document « Sorties de télé-information client des appareils de comptage électroniques utilisés par ERDF« , publié par EDF et disponible ici : ERDF-NOI-CPT_02E.pdf
GPIO et optocoupleur
La première étape consiste à créer le petit circuit électronique qui fera l’interface entre le GPIO du Raspberry Pi et le compteur EDF. Il s’agit simplement de « démoduler » le signal sortant des bornes I1 et I2 du compteur, à l’aide d’un optocoupleur et d’une paire de résistances. Le circuit est ensuite relié a la broche UART du GPIO du Raspberry Pi ce qui nous permettra de récupérer les trames de données du compteur EDF.
Pour cette partie, je me suis largement inspiré de cet article très synthétique : http://lhuet.github.io/blog/2014/01/montage-teleinfo.html
Matériel nécessaire
- optocoupleur SFH620A
- résistance 1.2 kΩ
- résistance 3.3 kΩ
- une carte d’expérimentation ou « breadbord »
- des câbles « wire jumper »
- une plaque epoxy
Montage et branchement
Voici un schéma électronique du circuit qui est très répandu sur la toile :
Ce qui nous donne un montage très simple à réaliser :
Voici ce que ça donne avec un petit bout d’epoxy :
Le branchement sur le port GPIO du Pi est également très simple. On utilise l’alimentation 3,3V du GPIO (fil rouge), la masse du GPIO (fil noir) et la broche 15 RXD (fil jaune).
Pour plus de détails sur le port GPIO du Pi, je vous invite à consulter cet article : http://www.magdiblog.fr/gpio/gpio-entree-en-matiere/
Il ne reste qu’à connecter les deux fils bleus (ou blanc et bleu, que vous pouvez utiliser dans n’importe quel sens) aux bornes I1 et I2 du compteur EDF (généralement en bas à droite) :
Et voilà A ce stade, votre Raspberry Pi est bombardé en continu par les infos envoyées par votre compteur EDF
Récupération des données
MAJ août 2016 : Depuis la parution de Raspbian Jessie :
Dans le fichier /boot/cmdline.txt :
- supprimer la ligne :
console=serial0,115200
- ajouter la ligne :
enable_uart=1
Inutile de modifier le fichier /etc/inittab.
Sur Raspberry Pi3, l’UART PL011 (full UART) du BCM2837 a été ré-alloué au WLAN/BT combo. Et le mini UART est mis à disposition des utilisateurs (sous le nom de /dev/ttyS0).
Il faut donc configurer /dev/ttyS0 (à la place de /dev/ttyAMA0) et remplacer /dev/ttyAMA0 par /dev/ttyS0 à la ligne 10 de teleinfo_func.php. (voir plus bas)
Pour plus d’information sur ces changements : http://spellfoundry.com/2016/05/29/configuring-gpio-serial-port-raspbian-jessie-including-pi-3/
Pour les anciennes versions de Raspbian :
Dans un premier temps il convient d’activer le port série du Raspberry Pi :
Dans le fichier /boot/cmdline.txt :
- supprimer les paramètres suivants :
console=ttyAMA0,115200 kgdboc=ttyAMA0,115200
Puis dans le fichier /etc/inittab :
- commentez la ligne suivante (tout en bas du fichier) en ajoutant un # devant :
#T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100
Redémarrer votre Raspberry Pi :
shutdown -r now
Enfin, pour créer un périphérique idoine et configurer le port série de manière à récupérer correctement les informations :
stty -F /dev/ttyAMA0 1200 sane evenp parenb cs7 -crtscts
A ce stade, vous devriez avoir un périphérique /dev/ttyAMA0 qui renvoi l’ensemble des données émises par votre compteur EDF.
En faisant un cat sur ce fichier, vous devriez voir défiler vos données de consommation EDF
cat /dev/ttyAMA0
Note : Si vous n’avez que des lignes du type ADCO 012345678945 ?, il se peut que la téléinfo ne soit pas activée sur votre compteur. Il vous suffit normalement de faire une demande à EDF pour l’activer.
Données de la téléinfo EDF
Comme indiqué dans le document « Sorties de télé-information client des appareils de comptage électroniques utilisés par ERDF« , publié par EDF et disponible ici : ERDF-NOI-CPT_02E.pdf, et selon votre type d’abonnement EDF, vous pouvez récupérer les informations suivantes :
- ADCO : Identifiant du compteur
- OPTARIF : Option tarifaire (type d’abonnement)
- ISOUSC : Intensité souscrite
- BASE : Index si option = base (en Wh)
- HCHC : Index heures creuses si option = heures creuses (en Wh)
- HCHP : Index heures pleines si option = heures creuses (en Wh)
- EJP HN : Index heures normales si option = EJP (en Wh)
- EJP HPM : Index heures de pointe mobile si option = EJP (en Wh)
- BBR HC JB : Index heures creuses jours bleus si option = tempo (en Wh)
- BBR HP JB : Index heures pleines jours bleus si option = tempo (en Wh)
- BBR HC JW : Index heures creuses jours blancs si option = tempo (en Wh)
- BBR HC JW : Index heures pleines jours blancs si option = tempo (en Wh)
- BBR HC JR : Index heures creuses jours rouges si option = tempo (en Wh)
- BBR HP JR : Index heures pleines jours rouges si option = tempo (en Wh)
- PEJP : Préavis EJP si option = EJP 30mn avant période EJP
- PTEC : Période tarifaire en cours
- DEMAIN : Couleur du lendemain si option = tempo
- IINST : Intensité instantanée (en ampères)
- ADPS : Avertissement de dépassement de puissance souscrite (en ampères)
- IMAX : Intensité maximale (en ampères)
- PAPP : Puissance apparente (en Volt.ampères)
- HHPHC : Groupe horaire si option = heures creuses ou tempo
- MOTDETAT : Mot d’état (autocontrôle)
Une trame commence toujours par l’étiquette ADCO et se termine par le MOTDETAT.
Chaque message, ou ligne, d’une trame est formé de la manière suivante :
ETIQUETTE espace VALEUR espace CHECKSUM
Seules l’ETIQUETTE et la VALEUR nous seront utiles. La CHEKSUM, ou somme de contrôle sert uniquement à vérifier l’intégrité que la trame.
Données utiles
Avant d’aller plus loin, il convient de faire le point sur les données récupérées. AMHA, seules deux informations sont réellement intéressantes à observer :
- la puissance instantanée en Watts (permet de voir sa consommation de « courant » au cours de la journée)
- la consommation d’électricité en Wh servant d’élément de facturation à EDF (permet de voir sa consommation global par jour/mois/année en fonction des périodes tarifaires)
Petit détail technique, ces informations ne sont pas directement disponibles ; il va falloir les déduire des données remontées par la téléinfo. Et c’est là que j’en profite pour remercier mon cher cousin Vincent pour m’avoir éclairé sur la subtilité de la chose. Merci Vincent
En effet, la téléinfo ne vous montre que la puissance apparente en Volt.Ampère (étiquette PAPP dans la trame). Rien à voir avec la puissance active en Watts ! Non monsieur ! Enfin presque ! Nous connaissons tous cette formule de puissance :
P = U x I (puissance en watt = tension en Volt X intensité en Ampère)
Seulement voilà, cette formule n’est valable qu’en courant continu… En courant alternatif, tel que délivré par EDF/ERDF, il faut multiplier ce résultat par un facteur de puissance appelé cos phi… Le problème, c’est que ce facteur de puissance est variable en fonction des appareils électriques qui consomment du courant dans votre maison… Rapport à un problème de phase d’après les experts… ^^ Pour les plus courageux d’entre vous, je vous conseille cet article qui explique très bien le pourquoi du comment de la chose : http://www.energieplus-lesite.be/index.php?id=11495
M’enfin bref, revenons à notre téléinfo. Nous n’avons donc pas directement la puissance active en Watt, mais, nous avons l’intensité instantanée en Ampère ! Il nous suffira donc de multiplier ce nombre par 230V (ou 220V, ou 240V, selon que vous soyez proche ou non du transformateur) pour avoir une puissance instantanée en Watt Nous aurons ainsi une idée approximative de la puissance instantanée consommée par tous les appareils de notre maison, ce qui nous permettra par exemple de constater que le chauffe eau fonctionne de telle heure à telle heure, que télé éteinte, on consomme 250W de moins, et que « Oh mon Dieu !!! » une plaque de cuisson ça consomme à MOOOOOOOOORRRRRTTT, 6000 W !!!
En ce qui concerne la consommation quotidienne de courant en Wh, nous devrons également ruser car la téléinfo ne donne que le nombre total de Wh consommés depuis l’installation de votre compteur. Pour avoir la consommation sur une journée, il va donc falloir faire la différence entre le nombre total de Wh consommées ce jour et le nombre total de Wh consommés la veille. Rien de bien méchant donc
Voyons maintenant comment procéder pour récupérer et traiter ces données à l’aide de scripts PHP et d’une base de données SQLite. Nous utiliserons ensuite la librairie graphique Google Chart pour tracer de jolies courbes de notre consommation d’électricité
Traitement des données et création d’un graphique de suivi
Le principe général est simple : un script PHP est exécuté à intervalles réguliers (toutes les minutes pour la puissance instantanée, et une fois par jour pour la consommation quotidienne en Wh), récupère les données de la téléinfo puis les stocks dans une base de données SQLite. Dans un second temps, une page web PHP/HTML lit les infos dans la base de données SQLite et affiche les graphiques.
- teleinfo_func.php : script PHP qui contient les fonctions nécessaires aux différents scripts.
<?php
$sqlite = 'teleinfo.sqlite';
//
// renvoie une trame teleinfo complete sous forme d'array
//
function getTeleinfo () {
$handle = fopen ('/dev/ttyACM0', "r"); // ouverture du flux
while (fread($handle, 1) != chr(2)); // on attend la fin d'une trame pour commencer a avec la trame suivante
$char = '';
$trame = '';
$datas = '';
while ($char != chr(2)) { // on lit tous les caracteres jusqu'a la fin de la trame
$char = fread($handle, 1);
if ($char != chr(2)){
$trame .= $char;
}
}
fclose ($handle); // on ferme le flux
$trame = chop(substr($trame,1,-1)); // on supprime les caracteres de debut et fin de trame
$messages = explode(chr(10), $trame); // on separe les messages de la trame
foreach ($messages as $key => $message) {
$message = explode (' ', $message, 3); // on separe l'etiquette, la valeur et la somme de controle de chaque message
if(!empty($message[0]) && !empty($message[1])) {
$etiquette = $message[0];
$valeur = $message[1];
$datas[$etiquette] = $valeur; // on stock les etiquettes et les valeurs de l'array datas
}
}
return $datas;
}
//
// enregistre la puissance instantanée en V.A et en W
//
function handlePuissance () {
global $sqlite;
$db = new SQLite3($sqlite);
$db->exec('CREATE TABLE IF NOT EXISTS puissance (timestamp INTEGER, hchp TEXT, va REAL, iinst REAL, watt REAL);'); // cree la table puissance si elle n'existe pas
$trame = getTeleinfo (); // recupere une trame teleinfo
$datas = array();
$datas['timestamp'] = time();
$datas['hchp'] = substr($trame['PTEC'],0,2); // indicateur heure pleine/creuse, on garde seulement les carateres HP (heure pleine) et HC (heure creuse)
$datas['va'] = preg_replace('`^[0]*`','',$trame['PAPP']); // puissance en V.A, on supprime les 0 en debut de chaine
$datas['iinst'] = preg_replace('`^[0]*`','',$trame['IINST']); // intensité instantanée en A, on supprime les 0 en debut de chaine
$datas['watt'] = $datas['iinst']*220; // intensite en A X 220 V
if($db->busyTimeout(5000)){ // stock les donnees
$db->exec("INSERT INTO puissance (timestamp, hchp, va, iinst, watt) VALUES (".$datas['timestamp'].", '".$datas['hchp']."', ".$datas['va'].", ".$datas['iinst'].", ".$datas['watt'].");");
}
return 1;
}
//
// enregistre la consommation en Wh
//
function handleConso () {
global $sqlite;
$db = new SQLite3($sqlite);
$db->exec('CREATE TABLE IF NOT EXISTS conso (timestamp INTEGER, total_hc INTEGER, total_hp INTEGER, daily_hc REAL, daily_hp REAL);'); // cree la table conso si elle n'existe pas
$trame = getTeleinfo (); // recupere une trame teleinfo
$today = strtotime('today 00:00:00');
$yesterday = strtotime("-1 day 00:00:00");
// recupere la conso totale enregistree la veille pour pouvoir calculer la difference et obtenir la conso du jour
if($db->busyTimeout(5000)){
$previous = $db->query("SELECT * FROM conso WHERE timestamp = '".$yesterday."';")->fetchArray(SQLITE3_ASSOC);
}
if(empty($previous)){
$previous = array();
$previous['timestamp'] = $yesterday;
$previous['total_hc'] = 0;
$previous['total_hp'] = 0;
$previous['daily_hc'] = 0;
$previous['daily_hp'] = 0;
}
$datas = array();
$datas['query'] = 'hchp';
$datas['timestamp'] = $today;
$datas['total_hc'] = preg_replace('`^[0]*`','',$trame['HCHC']); // conso total en Wh heure creuse, on supprime les 0 en debut de chaine
$datas['total_hp'] = preg_replace('`^[0]*`','',$trame['HCHP']); // conso total en Wh heure pleine, on supprime les 0 en debut de chaine
if($previous['total_hc'] == 0){
$datas['daily_hc'] = 0;
}
else{
$datas['daily_hc'] = ($datas['total_hc']-$previous['total_hc'])/1000; // conso du jour heure creuse = total aujourd'hui - total hier, on divise par 1000 pour avec un resultat en kWh
}
if($previous['total_hp'] == 0){
$datas['daily_hp'] = 0;
}
else{
$datas['daily_hp'] = ($datas['total_hp']-$previous['total_hp'])/1000; // conso du jour heure pleine = total aujourd'hui - total hier, on divise par 1000 pour avec un resultat en kWh
}
if($db->busyTimeout(5000)){ // stock les donnees
$db->exec("INSERT INTO conso (timestamp, total_hc, total_hp, daily_hc, daily_hp) VALUES (".$datas['timestamp'].", ".$datas['total_hc'].", ".$datas['total_hp'].", ".$datas['daily_hc'].", ".$datas['daily_hp'].");");
}
}
//
// recupere les donnees de puissance des $nb_days derniers jours et les met en forme pour les affichers sur le graphique
//
function getDatasPuissance ($nb_days) {
global $sqlite;
$months = array('01' => 'janv', '02' => 'fev', '03' => 'mars', '04' => 'avril', '05' => 'mai', '06' => 'juin', '07' => 'juil', '08' => 'aout', '09' => 'sept', '10' => 'oct', '11' => 'nov', '12' => 'dec');
$now = time();
$past = strtotime("-$nb_days day", $now);
$db = new SQLite3($sqlite);
$results = $db->query("SELECT * FROM puissance WHERE timestamp > $past ORDER BY timestamp ASC;");
$sums = array();
$days = array();
$datas = array();
while($row = $results->fetchArray(SQLITE3_ASSOC)){
$year = date("Y", $row['timestamp']);
$month = date("n", $row['timestamp']-1);
$day = date("j", $row['timestamp']);
$hour = date("G", $row['timestamp']);
$minute = date("i", $row['timestamp']);
$second = date("s", $row['timestamp']);
$datas[] = "[{v:new Date($year, $month, $day, $hour, $minute, $second), f:'".date("j", $row['timestamp'])." ".$months[date("m", $row['timestamp'])]." ".date("H\hi", $row['timestamp'])."'}, {v:".$row['va'].", f:'".$row['va']." V.A'}, {v:".$row['watt'].", f:'".$row['watt']." kW'}]";
}
return implode(', ', $datas);
}
//
// recupere les donnees de consommation des $nb_days derniers jours et les met en forme pour les affichers sur le graphique
//
function getDatasConso ($nb_days) {
global $sqlite;
$months = array('01' => 'janv', '02' => 'fev', '03' => 'mars', '04' => 'avril', '05' => 'mai', '06' => 'juin', '07' => 'juil', '08' => 'aout', '09' => 'sept', '10' => 'oct', '11' => 'nov', '12' => 'dec');
$now = time();
$past = strtotime("-$nb_days day", $now);
$db = new SQLite3($sqlite);
$results = $db->query("SELECT * FROM conso WHERE timestamp > $past ORDER BY timestamp ASC;");
$datas = array();
while($row = $results->fetchArray(SQLITE3_ASSOC)){
$day = date("j", $row['timestamp'])." ".$months[date("m", $row['timestamp'])];
$datas[] = "['".$day."', {v:".$row['daily_hp'].", f:'".$row['daily_hp']." kWh'}, {v:".$row['daily_hc'].", f:'".$row['daily_hc']." kWh'}]";
}
return implode(', ', $datas);
}
?>
-
- teleinfo_puissance.php : script PHP exécuté toutes les minutes pour enregistrer la puissance instantanée en V.A et en W.
#!/usr/bin/php5
<?php
header('Content-type: text/html; charset=utf-8');
require_once('/root/teleinfo/teleinfo_func.php');
handlePuissance();
?>
- teleinfo_conso.php : script PHP exécuté tous les jours juste avant minuit pour enregistrer la consommation en Wh.
#!/usr/bin/php5
<?php
header('Content-type: text/html; charset=utf-8');
require_once('/root/teleinfo/teleinfo_func.php');
handleConso();
?>
- teleinfo_graph.php : page PHP/HTML qui permet d’afficher les deux graphiques
<?php require_once('teleinfo_func.php'); ?>
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<div id="puissance">
<div id="chart_div"></div>
<div id="filter_div"></div>
</div>
<div id="conso"></div>
<script type="text/javascript">
google.load('visualization', '1.0', {'packages':['controls']});
google.setOnLoadCallback(drawDashboard);
function drawDashboard() {
var data = new google.visualization.DataTable();
data.addColumn('date', 'Date');
data.addColumn('number', 'V.A');
data.addColumn('number', 'kW');
data.addRows([<?php echo getDatasPuissance (5); ?>]);
var dashboard = new google.visualization.Dashboard(
document.getElementById('puissance'));
var rangeSlider = new google.visualization.ControlWrapper({
'controlType': 'ChartRangeFilter',
'containerId': 'filter_div',
'options': {
filterColumnLabel : 'Date',
ui : {chartType: 'LineChart', chartOptions: {
height : 80,
backgroundColor: '#FFF',
colors : ['#375D81', '#ABC8E2'],
curveType : 'function',
focusTarget : 'category',
lineWidth : '1',
'legend': {'position': 'none'},
'hAxis': {'textPosition': 'in'},
'vAxis': {
'textPosition': 'none',
'gridlines': {'color': 'none'}
}
}}
}
});
var lineChart = new google.visualization.ChartWrapper({
'chartType': 'LineChart',
'containerId': 'chart_div',
'options': {
title: '',
height : 400,
backgroundColor: '#FFF',
colors : ['#375D81', '#ABC8E2'],
curveType : 'function',
focusTarget : 'category',
lineWidth : '1',
legend : {position: 'bottom', alignment: 'center', textStyle: {color: '#333', fontSize: 16}},
vAxis : {textStyle : {color : '#555', fontSize : '16'}, gridlines : {color: '#CCC', count: 'auto'}, baselineColor : '#AAA', minValue : 0},
hAxis : {textStyle : {color : '#555'}, gridlines : {color: '#DDD'}}
}
});
dashboard.bind(rangeSlider, lineChart);
dashboard.draw(data);
}
google.load("visualization", "1", {packages:["corechart"]});
google.setOnLoadCallback(drawChart);
function drawChart() {
var data = new google.visualization.DataTable();
data.addColumn('string', 'Date');
data.addColumn('number', 'Heures pleines');
data.addColumn('number', 'Heures creuses');
data.addRows([<?php echo getDatasConso (365); ?>]);
var options = {
title: '',
height : 200,
backgroundColor: '#FFF',
colors : ['#375D81', '#ABC8E2'],
curveType : 'function',
focusTarget : 'category',
lineWidth : '1',
isStacked: true,
legend : {position: 'bottom', alignment: 'center', textStyle: {color: '#333', fontSize: 16}},
vAxis : {textStyle : {color : '#555', fontSize : '16'}, gridlines : {color: '#CCC', count: 'auto'}, baselineColor : '#AAA', minValue : 0},
hAxis : {textStyle : {color : '#555'}, gridlines : {color: '#DDD'}}
};
var chart = new google.visualization.ColumnChart(document.getElementById("conso"));
chart.draw(data, options);
}
</script>
Pour installer et configurer un serveur web Apache et PHP5. Je vous propose de suivre la procédure décrite dans cette article : PiHomeDashScreen – Installation et configuration. Vous pourrez ensuite afficher cette page PHP/HTML dans votre navigateur.
Résultat
Et voici le résultat pour la puissance instantanée en V.A et W :
Et le résultat pour la consommation quotidienne heures pleines/creuses en kWh :
Comme toujours, les idées d’amélioration, les remarques et toutes les questions sont les bienvenues en commentaire de cet article
Dans mon prochain article « RaspberryPi + Razberry + Domoticz : Le combo parfait ! » je vous montrerai comment créer un système domotique complet et à moindre frais pour gérer efficacement votre maison
MàJ : Un micro module USB prêt à l’emploi est disponible ici : http://hallard.me/teleinfo/. Je ne l’ai pas encore tester, mais cela semble être une solution idéale, quoi que beaucoup plus cher qu’un petit optocoupleur
L’article Téléinfo EDF – Suivi conso de votre compteur électrique (màj 08/2016) est apparu en premier sur MagdiBlog.