Arduino et TeleInfo
par , le dimanche 19 mars 2017 à 23:55

Catégorie : Général
Mots clés : Arduino EDF TeleInfo

Les compteurs électriques blancs d'EDF intègrent une interface série, dite TeleInfo, permettant de recevoir, en continue, les informations de consommation.


Principe

L'interface TeleInfo est une interface série modulée sur du 50Hz alternatif. Elle se situe en bas à du compteur (marques I1 et I2) que ce soit pour le modèle '1991' (photographié ici) ou le modèle Linky (je viens juste d'y passer) :

Compteur EDF

Un bon nombre de sites fournit le schéma du montage électronique qui sert d'interface avec une carte Arduino. Il s'agit d'un simple opto-coupleur associé à une diode qui isole et redresse le signal du compteur :

http://www.magdiblog.fr/gpio/teleinfo-edf-suivi-conso-de-votre-compteur-electrique/

Il existe des adaptateurs tout près mais c'est plus drôle de faire le sien.

Montage

Prototype 

Après quelques tests à l'oscilloscope, j'ai constaté que la LED signalétique présente sur le montage provoque une baisse de tension trop importante et rend le signal non lisible par Arduino, je l'ai donc retirée ainsi que la résistance associée (Arduino comportant déjà une résistance de pull-up) :

Compteur EDF

Compteur EDF Compteur EDF

Signal sortant du compteur :

Compteur EDF

Signal après redressement à la sortie de l'opto-coupleur :

Compteur EDF

Montage soudé

Pour réduire l'ensemble du matériel, j'ai utilisé une carte Arduino Nano et réalisé un montage soudé sur circuit imprimé pastillé :

Montage Teleinfo sur CI pastillé Montage Teleinfo sur CI pastillé - dessous Nano Arduino + montage soudé

et j'ai réalisé un boîtier en impression 3D pour protéger le montage :

Plan boîtier Modélisation boîtier Boîtier Boîtier en fonctionnement

Croquis Arduino

Le 'croquis' Arduino pour l'interface, permettant de récupérer les informations de TeleInfo par interface série sur port USB coté PC, est :

#include <SoftwareSerial.h>

SoftwareSerial cptSerial(2, 3);

#define startFrame 0x02
#define endFrame 0x03
#define startLine 0x0A
#define endLine 0x0D

void setup()
{
Serial.begin(9600);
cptSerial.begin(1200);
pinMode(13, OUTPUT);
}

void loop()
{
String TeleInfo = "";S
char charIn = 0;

while (charIn != startLine)
{
  if (cptSerial.available() > 0)
      charIn = cptSerial.read() & 0x7F;
}

while (charIn != endLine)
{
    if (cptSerial.available() > 0)
    {
        charIn = cptSerial.read() & 0x7F;
        TeleInfo += charIn;
    }
}

digitalWrite(13, HIGH);  
Serial.println(TeleInfo);
digitalWrite(13, LOW);  
}

Les mesures reçues sont de la forme :

Compteur EDF

Affichage des mesures avec RRDTOOL

J'ai écrit quelques scripts shell très basiques pour récupérer les mesures intéressantes : consommation heures creuses/heures pleines, intensité instantanée, puissance apparente.

Et avec l'outil rrdtool j'en ai fait une représentation graphique :

Compteur EDF

Scripts shell

Pour afficher le résultat j'ai développé 3 scripts sous Linux :

script de création des bases, à ne lancer qu'une seule fois :

#!/bin/sh

rrdtool create /root/consommation/HCHC.rrd --step 1 --no-overwrite \
"DS:HCHC:COUNTER:300:0:U" \
"RRA:AVERAGE:0:60:1440" 

rrdtool create /root/consommation/HCHP.rrd --step 1 --no-overwrite \
"DS:HCHP:COUNTER:300:0:U" \
"RRA:AVERAGE:0:60:1440" 

rrdtool create /root/consommation/IINST.rrd --step 1 --no-overwrite \
"DS:IINST:GAUGE:300:0:U" \
"RRA:MIN:0:60:1440"     \
"RRA:AVERAGE:0:60:1440" \
"RRA:MAX:0:60:1440" 

rrdtool create /root/consommation/PAPP.rrd --step 1 --no-overwrite \
"DS:PAPP:GAUGE:300:0:U" \
"RRA:MIN:0:60:1440"     \
"RRA:AVERAGE:0:60:1440" \
"RRA:MAX:0:60:1440" 

script de récupération des valeurs et mise en base, à lancer dans une session screen :

#!/bin/sh

stty -F /dev/ttyUSB0 ispeed 9600  cs8 -cstopb -parenb

cat /dev/ttyUSB0 | grep --line-buffered -E "(HCHC|HCHP|IINST|PAPP)" | unbuffer -p cut -s -d ' ' -f 1,2,3 | while read -r code value dummy
do
echo $code $value
rrdtool update /root/consommation/$code.rrd N:$value
done

script de génération des graphiques, à mettre en crontab :

#!/bin/sh

rrdtool graph /home/public/www/www.kozodo.com/htdocs/consommation/general.png -w 1440 -h 300 \
"DEF:HCHC=/root/consommation/HCHC.rrd:HCHC:AVERAGE" \
"DEF:HCHP=/root/consommation/HCHP.rrd:HCHP:AVERAGE" \
"LINE1:HCHC#0000FF:Heures creuses" \
"LINE1:HCHP#00FF00:Heures pleines"

rrdtool graph /home/public/www/www.kozodo.com/htdocs/consommation/general_ampere.png -w 1440 -h 300 \
"DEF:IINST=/root/consommation/IINST.rrd:IINST:AVERAGE" \
"DEF:IINSTMIN=/root/consommation/IINST.rrd:IINST:MIN"  \
"DEF:IINSTMAX=/root/consommation/IINST.rrd:IINST:MAX"  \
"LINE1:IINST#FF0000:Intensité moyenne" \
"LINE1:IINSTMIN#00FF00:Intensité min"  \
"LINE1:IINSTMAX#0000FF:Intensité max"

rrdtool graph /home/public/www/www.kozodo.com/htdocs/consommation/general_power.png -w 1440 -h 300 \
"DEF:PAPP=/root/consommation/PAPP.rrd:PAPP:AVERAGE" \
"DEF:PAPPMIN=/root/consommation/PAPP.rrd:PAPP:MIN"  \
"DEF:PAPPMAX=/root/consommation/PAPP.rrd:PAPP:MAX"  \
"LINE1:PAPP#FF0000:Puissance apparente moyenne" \
"LINE1:PAPPMIN#00FF00:Puissance apparente min"  \
"LINE1:PAPPMAX#0000FF:Puissance apparente max"

Affichage des mesures avec InfluDB+Grafana

Dans un second temps, j'ai passé les mesures sur base InfluxDB et affichage sous Grafana.

InfluxDB + Grafana

Scripts

Il faut créer une base teleinfo dans influxdb, et mettre lancer le script suivant pour la nourrir :

root@server:/usr/local/bin/conso_mesure.sh

#!/bin/bash

tarifhc=0.12700
tarifhp=0.15600

while true; do

  stty -F /dev/ttyUSB0 ispeed 9600 cs8 -cstopb -parenb

  cat /dev/ttyUSB0 | while read -r values
  do
     if [[ $values =~ HCHC=([0-9]+),HCHP=([0-9]+) ]]; then

        prixhc=$( echo "scale=4; ${BASH_REMATCH[1]} * $tarifhc" | bc )
        prixhp=$( echo "scale=4; ${BASH_REMATCH[2]} * $tarifhp" | bc )

        values="$values,COSTHC=$prixhc,COSTHP=$prixhp"
        echo $values

        influx -database teleinfo -execute "insert conso $values"
     fi
  done

  sleep 5

done

Les 2 variables tarifhc et tarifhp doivent être mise-à-jour avec le prix du KWh selon votre forfait électrique; elles permettent de calculer vos coût de consommation au fil de l'eau.

Pour lancer le script précédent en tâche de fond :

root@server:/etc/init.d/conso.sh

#!/bin/sh

### BEGIN INIT INFO
# Provides:          conso
# Required-Start:    $local_fs $remote_fs $network
# Required-Stop:     $local_fs $remote_fs $network
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# X-Interactive:     true
# Short-Description: Consommation
# Description:       Start the power consuption daemon 
### END INIT INFO

if [ "$1" = "start" ]; then
    screen -d -m -S conso /usr/local/bin/conso_mesure.sh
else
    pkill -f conso_mesure.sh
fi

Dashboard

La définition du dashboard Grafana est :

{
  "__inputs": [
    {
      "name": "DS_TELEINFO",
      "label": "teleinfo",
      "description": "",
      "type": "datasource",
      "pluginId": "influxdb",
      "pluginName": "InfluxDB"
    }
  ],
  "__requires": [
    {
      "type": "grafana",
      "id": "grafana",
      "name": "Grafana",
      "version": "4.4.1"
    },
    {
      "type": "panel",
      "id": "graph",
      "name": "Graph",
      "version": ""
    },
    {
      "type": "datasource",
      "id": "influxdb",
      "name": "InfluxDB",
      "version": "1.0.0"
    },
    {
      "type": "panel",
      "id": "singlestat",
      "name": "Singlestat",
      "version": ""
    }
  ],
  "annotations": {
    "list": []
  },
  "editable": true,
  "gnetId": null,
  "graphTooltip": 0,
  "hideControls": false,
  "id": null,
  "links": [],
  "refresh": false,
  "rows": [
    {
      "collapse": false,
      "height": 256,
      "panels": [
        {
          "aliasColors": {},
          "bars": false,
          "dashLength": 10,
          "dashes": false,
          "datasource": "${DS_TELEINFO}",
          "fill": 1,
          "id": 1,
          "interval": "> 60s",
          "legend": {
            "avg": false,
            "current": false,
            "max": false,
            "min": false,
            "rightSide": false,
            "show": true,
            "total": false,
            "values": false
          },
          "lines": true,
          "linewidth": 1,
          "links": [],
          "nullPointMode": "null",
          "percentage": false,
          "pointradius": 5,
          "points": false,
          "renderer": "flot",
          "seriesOverrides": [],
          "spaceLength": 10,
          "span": 12,
          "stack": false,
          "steppedLine": true,
          "targets": [
            {
              "dsType": "influxdb",
              "groupBy": [
                {
                  "params": [
                    "1s"
                  ],
                  "type": "time"
                }
              ],
              "hide": false,
              "measurement": "conso",
              "orderByTime": "ASC",
              "policy": "default",
              "query": "SELECT derivative(mean(HCHP),3600s) as heures_pleines, derivative(mean(HCHC),3600s) as heures_creuses FROM conso WHERE $timeFilter group by time($interval)",
              "rawQuery": true,
              "refId": "A",
              "resultFormat": "time_series",
              "select": [
                [
                  {
                    "params": [
                      "value"
                    ],
                    "type": "field"
                  },
                  {
                    "params": [],
                    "type": "max"
                  }
                ]
              ],
              "tags": [
                {
                  "key": "code",
                  "operator": "=",
                  "value": "HCHC"
                }
              ]
            }
          ],
          "thresholds": [],
          "timeFrom": null,
          "timeShift": null,
          "title": "Consommation",
          "tooltip": {
            "shared": true,
            "sort": 0,
            "value_type": "individual"
          },
          "transparent": false,
          "type": "graph",
          "xaxis": {
            "buckets": null,
            "mode": "time",
            "name": null,
            "show": true,
            "values": []
          },
          "yaxes": [
            {
              "format": "watth",
              "label": null,
              "logBase": 1,
              "max": null,
              "min": "0",
              "show": true
            },
            {
              "format": "short",
              "label": null,
              "logBase": 1,
              "max": null,
              "min": null,
              "show": true
            }
          ]
        }
      ],
      "repeat": null,
      "repeatIteration": null,
      "repeatRowId": null,
      "showTitle": false,
      "title": "Dashboard Row",
      "titleSize": "h6"
    },
    {
      "collapse": false,
      "height": 250,
      "panels": [
        {
          "aliasColors": {},
          "bars": false,
          "dashLength": 10,
          "dashes": false,
          "datasource": "${DS_TELEINFO}",
          "fill": 0,
          "hideTimeOverride": false,
          "id": 5,
          "interval": ">60s",
          "legend": {
            "avg": false,
            "current": false,
            "max": false,
            "min": false,
            "show": true,
            "total": false,
            "values": false
          },
          "lines": true,
          "linewidth": 1,
          "links": [],
          "nullPointMode": "null",
          "percentage": false,
          "pointradius": 5,
          "points": false,
          "renderer": "flot",
          "seriesOverrides": [],
          "spaceLength": 10,
          "span": 12,
          "stack": false,
          "steppedLine": false,
          "targets": [
            {
              "dsType": "influxdb",
              "groupBy": [
                {
                  "params": [
                    "$__interval"
                  ],
                  "type": "time"
                },
                {
                  "params": [
                    "null"
                  ],
                  "type": "fill"
                }
              ],
              "hide": false,
              "orderByTime": "ASC",
              "policy": "default",
              "query": "SELECT (derivative(mean(COSTHP),3600s)  + derivative(mean(COSTHC),3600s)) / 100  FROM conso WHERE $timeFilter group by time($interval)",
              "rawQuery": true,
              "refId": "A",
              "resultFormat": "time_series",
              "select": [
                [
                  {
                    "params": [
                      "value"
                    ],
                    "type": "field"
                  },
                  {
                    "params": [],
                    "type": "mean"
                  }
                ]
              ],
              "tags": []
            }
          ],
          "thresholds": [],
          "timeFrom": null,
          "timeShift": null,
          "title": "Coût",
          "tooltip": {
            "shared": true,
            "sort": 0,
            "value_type": "individual"
          },
          "type": "graph",
          "xaxis": {
            "buckets": null,
            "mode": "time",
            "name": null,
            "show": true,
            "values": []
          },
          "yaxes": [
            {
              "format": "currencyEUR",
              "label": "¤/h",
              "logBase": 1,
              "max": null,
              "min": null,
              "show": true
            },
            {
              "format": "short",
              "label": null,
              "logBase": 1,
              "max": null,
              "min": null,
              "show": true
            }
          ]
        }
      ],
      "repeat": null,
      "repeatIteration": null,
      "repeatRowId": null,
      "showTitle": false,
      "title": "Dashboard Row",
      "titleSize": "h6"
    },
    {
      "collapse": false,
      "height": 265,
      "panels": [
        {
          "aliasColors": {},
          "bars": true,
          "dashLength": 10,
          "dashes": false,
          "datasource": "${DS_TELEINFO}",
          "fill": 1,
          "id": 3,
          "interval": "> 10s",
          "legend": {
            "avg": false,
            "current": false,
            "max": false,
            "min": false,
            "show": true,
            "total": false,
            "values": false
          },
          "lines": false,
          "linewidth": 1,
          "links": [],
          "nullPointMode": "null",
          "percentage": false,
          "pointradius": 5,
          "points": false,
          "renderer": "flot",
          "seriesOverrides": [],
          "spaceLength": 10,
          "span": 5,
          "stack": false,
          "steppedLine": false,
          "targets": [
            {
              "dsType": "influxdb",
              "groupBy": [
                {
                  "params": [
                    "$__interval"
                  ],
                  "type": "time"
                },
                {
                  "params": [
                    "null"
                  ],
                  "type": "fill"
                }
              ],
              "hide": false,
              "orderByTime": "ASC",
              "policy": "default",
              "query": "SELECT max(PAPP),mean(PAPP),min(PAPP) FROM conso WHERE $timeFilter group by time($interval) fill(null)",
              "rawQuery": true,
              "refId": "A",
              "resultFormat": "time_series",
              "select": [
                [
                  {
                    "params": [
                      "value"
                    ],
                    "type": "field"
                  },
                  {
                    "params": [],
                    "type": "mean"
                  }
                ]
              ],
              "tags": []
            }
          ],
          "thresholds": [],
          "timeFrom": null,
          "timeShift": null,
          "title": "Puissance apparente",
          "tooltip": {
            "shared": true,
            "sort": 0,
            "value_type": "individual"
          },
          "type": "graph",
          "xaxis": {
            "buckets": null,
            "mode": "time",
            "name": null,
            "show": true,
            "values": []
          },
          "yaxes": [
            {
              "format": "watt",
              "label": null,
              "logBase": 1,
              "max": null,
              "min": "0",
              "show": true
            },
            {
              "format": "short",
              "label": null,
              "logBase": 1,
              "max": null,
              "min": null,
              "show": true
            }
          ]
        },
        {
          "aliasColors": {},
          "bars": true,
          "dashLength": 10,
          "dashes": false,
          "datasource": "${DS_TELEINFO}",
          "fill": 1,
          "id": 2,
          "interval": "> 10s",
          "legend": {
            "avg": false,
            "current": false,
            "max": false,
            "min": false,
            "rightSide": false,
            "show": true,
            "total": false,
            "values": false
          },
          "lines": false,
          "linewidth": 1,
          "links": [],
          "nullPointMode": "null",
          "percentage": false,
          "pointradius": 5,
          "points": false,
          "renderer": "flot",
          "seriesOverrides": [],
          "spaceLength": 10,
          "span": 4,
          "stack": false,
          "steppedLine": false,
          "targets": [
            {
              "dsType": "influxdb",
              "groupBy": [
                {
                  "params": [
                    "$__interval"
                  ],
                  "type": "time"
                },
                {
                  "params": [
                    "null"
                  ],
                  "type": "fill"
                }
              ],
              "hide": false,
              "orderByTime": "ASC",
              "policy": "default",
              "query": "SELECT max(IINST),mean(IINST),min(IINST) FROM conso WHERE $timeFilter group by time($interval) fill(null)\n",
              "rawQuery": true,
              "refId": "A",
              "resultFormat": "time_series",
              "select": [
                [
                  {
                    "params": [
                      "value"
                    ],
                    "type": "field"
                  },
                  {
                    "params": [],
                    "type": "mean"
                  }
                ]
              ],
              "tags": []
            }
          ],
          "thresholds": [],
          "timeFrom": null,
          "timeShift": null,
          "title": "Intensité instantanée",
          "tooltip": {
            "shared": true,
            "sort": 0,
            "value_type": "individual"
          },
          "transparent": false,
          "type": "graph",
          "xaxis": {
            "buckets": null,
            "mode": "time",
            "name": null,
            "show": true,
            "values": []
          },
          "yaxes": [
            {
              "format": "amp",
              "label": "",
              "logBase": 1,
              "max": null,
              "min": "0",
              "show": true
            },
            {
              "format": "short",
              "label": null,
              "logBase": 1,
              "max": null,
              "min": null,
              "show": true
            }
          ]
        },
        {
          "cacheTimeout": null,
          "colorBackground": false,
          "colorValue": false,
          "colors": [
            "rgba(245, 54, 54, 0.9)",
            "rgba(237, 129, 40, 0.89)",
            "rgba(50, 172, 45, 0.97)"
          ],
          "datasource": "${DS_TELEINFO}",
          "decimals": null,
          "format": "watt",
          "gauge": {
            "maxValue": 8000,
            "minValue": 0,
            "show": true,
            "thresholdLabels": false,
            "thresholdMarkers": true
          },
          "id": 4,
          "interval": null,
          "links": [],
          "mappingType": 1,
          "mappingTypes": [
            {
              "name": "value to text",
              "value": 1
            },
            {
              "name": "range to text",
              "value": 2
            }
          ],
          "maxDataPoints": 100,
          "nullPointMode": "connected",
          "nullText": null,
          "postfix": "",
          "postfixFontSize": "50%",
          "prefix": "",
          "prefixFontSize": "50%",
          "rangeMaps": [
            {
              "from": "null",
              "text": "N/A",
              "to": "null"
            }
          ],
          "span": 3,
          "sparkline": {
            "fillColor": "rgba(31, 118, 189, 0.18)",
            "full": false,
            "lineColor": "rgb(31, 120, 193)",
            "show": false
          },
          "tableColumn": "",
          "targets": [
            {
              "dsType": "influxdb",
              "groupBy": [
                {
                  "params": [
                    "$__interval"
                  ],
                  "type": "time"
                },
                {
                  "params": [
                    "null"
                  ],
                  "type": "fill"
                }
              ],
              "hide": false,
              "orderByTime": "ASC",
              "policy": "default",
              "query": "SELECT last(PAPP) FROM conso WHERE time > now() - 1h",
              "rawQuery": true,
              "refId": "A",
              "resultFormat": "time_series",
              "select": [
                [
                  {
                    "params": [
                      "value"
                    ],
                    "type": "field"
                  },
                  {
                    "params": [],
                    "type": "mean"
                  }
                ]
              ],
              "tags": []
            }
          ],
          "thresholds": "",
          "title": "Puissante apparente courante",
          "type": "singlestat",
          "valueFontSize": "80%",
          "valueMaps": [
            {
              "op": "=",
              "text": "N/A",
              "value": "null"
            }
          ],
          "valueName": "current"
        }
      ],
      "repeat": null,
      "repeatIteration": null,
      "repeatRowId": null,
      "showTitle": false,
      "title": "Dashboard Row",
      "titleSize": "h6"
    }
  ],
  "schemaVersion": 14,
  "style": "dark",
  "tags": [],
  "templating": {
    "list": []
  },
  "time": {
    "from": "now/y",
    "to": "now/y"
  },
  "timepicker": {
    "refresh_intervals": [
      "5s",
      "10s",
      "30s",
      "1m",
      "5m",
      "15m",
      "30m",
      "1h",
      "2h",
      "1d"
    ],
    "time_options": [
      "5m",
      "15m",
      "1h",
      "6h",
      "12h",
      "24h",
      "2d",
      "7d",
      "30d"
    ]
  },
  "timezone": "browser",
  "title": "Consommation électrique",
  "version": 17
}

Améliorations possibles

  • Corriger les unités sur Grafana : Consommation Wh -> W et Puissance Apparente W -> VA
  • Ecrire un installeur/paquetage : création de la base teleinfo dans influxdb...
  • Développer un vrai service : recherche de l'interface Arduino, teste de la base de données...
  • Passer à systemd ?
  • Ajouter le compactage des valeurs dans influxdb

A bientôt

Antoine

Ecrire à l'auteur