Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ Récupérer l'instance d'EventEmitter du module en appelant la fonction teleinfo
var trameEvents = teleinfo('/dev/ttyAMA0');
```

```javascript
// Configuration TIC Standard
var trameEvents = teleinfo('/dev/ttyAMA0', teleinfo.version.STANDARD);
```

Les trames téléinfo sont envoyées sous forme d'évènements :
* trame : trames brutes non vérifiées (utile uniquement à des fins de debug)
* tramedecodee : trames sous forme d'un objet (checksums validés pour chaque propriété)
Expand Down
136 changes: 136 additions & 0 deletions configs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
module.exports = {
HISTORIQUE: {
baudRate: 1200,
separator: ' ',
computeChecksum: (line) => {
// Spec chk : somme des codes ASCII + ET logique 03Fh + ajout 20 en hexadécimal
// Résultat toujours un caractère ASCII imprimable allant de 20 à 5F en hexadécimal
// Checksum calculé sur etiquette+space+données => retirer les 2 derniers caractères
var sum = 0;
for (var j=0; j < line.length-2; j++) {
sum += line.charCodeAt(j);
}
return (sum & 63) + 32;
},
labels: {
'ADCO': { numeric: false },
'OPTARIF': { numeric: false },
'PTEC': { numeric: false },
'DEMAIN': { numeric: false },
'HHPHC': { numeric: false },
'MOTDETAT': { numeric: false },
'PPOT': { numeric: false },
'ISOUSC': { numeric: true },
'BASE': { numeric: true },
'HCHC': { numeric: true },
'HCHP': { numeric: true },
'EJPHN': { numeric: true },
'EJPHPM': { numeric: true },
'BBRHCJB': { numeric: true },
'BBRHPJB': { numeric: true },
'BBRHCJW': { numeric: true },
'BBRHPJW': { numeric: true },
'BBRHCJR': { numeric: true },
'BBRHPJR': { numeric: true },
'PEJP': { numeric: true },
'IINST': { numeric: true },
'IINST1': { numeric: true },
'IINST2': { numeric: true },
'IINST3': { numeric: true },
'ADPS': { numeric: true },
'IMAX': { numeric: true },
'IMAX1': { numeric: true },
'IMAX2': { numeric: true },
'IMAX3': { numeric: true },
'PMAX': { numeric: true },
'PAPP': { numeric: true },
}
},
STANDARD: {
baudRate: 9600,
separator: '\t',
computeChecksum: (line) => {
// Spec chk : somme des codes ASCII + ET logique 03Fh + ajout 20 en hexadécimal
// Résultat toujours un caractère ASCII imprimable allant de 20 à 5F en hexadécimal
// Checksum calculé sur etiquette+tab+données(+tab+données)+tab => retirer le dernier caractères
var sum = 0;
for (var j=0; j < line.length-1; j++) {
sum += line.charCodeAt(j);
}
return (sum & 63) + 32;
},
labels: {
'ADSC': { numeric: false, date: false },
'VTIC': { numeric: true, date: false },
'DATE': { numeric: false, date: true },
'NGTF': { numeric: false, date: false },
'LTARF': { numeric: false, date: false },
'EAST': { numeric: true, date: false },
'EASF01': { numeric: true, date: false },
'EASF02': { numeric: true, date: false },
'EASF03': { numeric: true, date: false },
'EASF04': { numeric: true, date: false },
'EASF05': { numeric: true, date: false },
'EASF06': { numeric: true, date: false },
'EASF07': { numeric: true, date: false },
'EASF08': { numeric: true, date: false },
'EASF09': { numeric: true, date: false },
'EASF10': { numeric: true, date: false },
'EASD01': { numeric: true, date: false },
'EASD02': { numeric: true, date: false },
'EASD03': { numeric: true, date: false },
'EASD04': { numeric: true, date: false },
'EAIT': { numeric: true, date: false },
'ERQ1': { numeric: true, date: false },
'ERQ2': { numeric: true, date: false },
'ERQ3': { numeric: true, date: false },
'ERQ4': { numeric: true, date: false },
'IRMS1': { numeric: true, date: false },
'IRMS2': { numeric: true, date: false },
'IRMS3': { numeric: true, date: false },
'URMS1': { numeric: true, date: false },
'URMS2': { numeric: true, date: false },
'URMS3': { numeric: true, date: false },
'PREF': { numeric: true, date: false },
'PCOUP': { numeric: true, date: false },
'SINSTS': { numeric: true, date: false },
'SINSTS1': { numeric: true, date: false },
'SINSTS2': { numeric: true, date: false },
'SINSTS3': { numeric: true, date: false },
'SMAXSN': { numeric: true, date: true },
'SMAXSN1': { numeric: true, date: true },
'SMAXSN2': { numeric: true, date: true },
'SMAXSN3': { numeric: true, date: true },
'SMAXSN-1': { numeric: true, date: true },
'SMAXSN1-1': { numeric: true, date: true },
'SMAXSN2-1': { numeric: true, date: true },
'SMAXSN3-1': { numeric: true, date: true },
'SINSTI': { numeric: true, date: false },
'SMAXIN': { numeric: true, date: true },
'SMAXIN-1': { numeric: true, date: true },
'CCASN': { numeric: true, date: true },
'CCASN-1': { numeric: true, date: true },
'CCAIN': { numeric: true, date: true },
'CCAIN-1': { numeric: true, date: true },
'UMOY1': { numeric: true, date: true },
'UMOY2': { numeric: true, date: true },
'UMOY3': { numeric: true, date: true },
'STGE': { numeric: false, date: false },
'DPM1': { numeric: false, date: true },
'FPM1': { numeric: false, date: true },
'DPM2': { numeric: false, date: true },
'FPM2': { numeric: false, date: true },
'DPM3': { numeric: false, date: true },
'FPM3': { numeric: false, date: true },
'MSG1': { numeric: false, date: false },
'MSG2': { numeric: false, date: false },
'PRM': { numeric: false, date: false },
'RELAIS': { numeric: false, date: false },
'NTARF': { numeric: false, date: false },
'NJOURF': { numeric: false, date: false },
'NJOURF+1': { numeric: false, date: false },
'PJOURF+1': { numeric: false, date: false },
'PPOINTE': { numeric: false, date: false },
}
}
};
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "teleinfo",
"version": "0.2.0",
"version": "0.3.0",
"description": "Decode teleinfo data (from french electricity meter) from serial port",
"main": "teleinfo.js",
"keywords": [
Expand All @@ -13,7 +13,7 @@
"url": "https://github.com/lhuet/teleinfo-node.git"
},
"dependencies": {
"serialport": "1.2.5"
"serialport": "^7.1.4"
},
"author": {
"name": "Laurent HUET",
Expand Down
119 changes: 71 additions & 48 deletions teleinfo.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,38 @@
var serialport = require('serialport');
var SerialPort = require('serialport');
var events = require('events');
var util = require('util');
var configs = require('./configs');
const { REPL_MODE_STRICT } = require('repl');

function teleinfo(port) {
function teleinfo(portName, config = configs.HISTORIQUE) {
// Evénements 'trame' et 'tramedecodee'
var trameEvents = new events.EventEmitter();

var serialPort = new serialport.SerialPort(port, {
baudrate: 1200,
const Readline = require('@serialport/parser-readline');
const port = new SerialPort(portName, {
baudRate: config.baudRate,
dataBits: 7,
parity: 'even',
stopBits: 1,
// Caractères séparateurs = fin de trame + début de trame
parser: serialport.parsers.readline(String.fromCharCode(13,3,2,10))
stopBits: 1
});
const parser = port.pipe(new Readline({ delimiter: String.fromCharCode(13,3,2,10) })); // Caractères séparateurs = fin de trame + début de trame

serialPort.on('data', function(data) {
parser.on('data', function(data) {
trameEvents.emit('trame', data);
});

serialPort.on('error', function(err) {
port.on('error', function(err) {
trameEvents.emit('error', err);
});

trameEvents.on('trame', function(data) {
// Decode trame '9 lignes en tarif bleu base'
var trame = {};
// Decode trame '9 lignes en tarif bleu base'
var arrayOfData = data.split('\r\n');
for (var i=0; i < arrayOfData.length; i++) {
decodeLigne(arrayOfData[i], trame, trameEvents);
decodeLigne(arrayOfData[i], trame, trameEvents, config);
}
// trame incomplete s'il manque la première ligne ADCO
if (!(trame.ADCO===undefined)) {
if (trame.ADCO || trame.ADSC) {
trameEvents.emit('tramedecodee', trame);
}
else {
Expand All @@ -44,46 +45,68 @@ function teleinfo(port) {
}


function decodeLigne(ligneBrute, trame, trameEvents) {
// Ligne du type "PAPP 00290 ," (Etiquette / Donnée / Checksum)
var elementsLigne = ligneBrute.split(' ');
if (elementsLigne.length === 3) {
// Spec chk : somme des codes ASCII + ET logique 03Fh + ajout 20 en hexadécimal
// Résultat toujours un caractère ASCII imprimable allant de 20 à 5F en hexadécimal
// Checksum calculé sur etiquette+space+données => retirer les 2 derniers caractères
var sum = 0;
for (var j=0; j < ligneBrute.length-2; j++) {
sum += ligneBrute.charCodeAt(j);
}
sum = (sum & 63) + 32;
if (sum === ligneBrute.charCodeAt(j+1)) {
// Checksum ok -> on ajoute la propriété à la trame
// Conversion en valeur numérique pour certains cas
switch (elementsLigne[0].substr(0,4)) {
case 'BASE': // Index Tarif bleu
case 'HCHC': // Index Heures creuses
case 'HCHP': // Index Heures pleines
case 'EJPH': // Index EJP (HN et HPM)
case 'BBRH': // Index Tempo (HC/HP en jours Blanc, Bleu et Rouge)
case 'ISOU': // Intensité souscrite
case 'IINS': // Intensité instantannée (1/2/3 pour triphasé)
case 'ADPS': // Avertissement de dépassement
case 'IMAX': // Intensité max appelée (1/2/3 pour triphasé)
case 'PAPP': // Puissance apparente
case 'PMAX': // Puissance max triphasée atteinte
trame[elementsLigne[0]]= Number(elementsLigne[1]);
break;
default:
trame[elementsLigne[0]]= elementsLigne[1];
function decodeLigne(ligneBrute, trame, trameEvents, config) {
var sum = config.computeChecksum(ligneBrute);
var checksum = ligneBrute.charCodeAt(ligneBrute.length-1);
if (sum === checksum) {
// Checksum ok -> on ajoute la propriété à la trame
var elementsLigne = ligneBrute.split(config.separator);
if (elementsLigne.length >= 3) {
const label = elementsLigne[0];
const props = config.labels[label];
if(!props) {
trameEvents.emit('error', new Error('Label inconnu: ' + label));
return false;
}
if(props.date) {
if(elementsLigne.length >= 4) {
const date = convertDate(elementsLigne[1]);
const value = props.numeric ? Number(elementsLigne[2]) : elementsLigne[2];
if(Number.isNaN(date)) {
trameEvents.emit('error', new Error('Date invalide: ' + elementsLigne[1]));
return false;
}
if(Number.isNaN(value)) {
trameEvents.emit('error', new Error('Valeur invalide: ' + elementsLigne[2]));
return false;
}
if(label == 'DATE') {
trame[label] = date.toISOString();
} else {
trame[label] = {
date: date.toISOString(),
value: props.numeric ? Number(elementsLigne[2]) : elementsLigne[2]
};
}
return true;
}
} else {
const value = props.numeric ? Number(elementsLigne[1]) : elementsLigne[1];
if(Number.isNaN(value)) {
trameEvents.emit('error', new Error('Valeur invalide: ' + elementsLigne[1]));
return false;
}
trame[label] = props.numeric ? Number(elementsLigne[1]) : elementsLigne[1];
return true;
}
return true;
} else {
var err = new Error('Erreur de checksum : \n' + ligneBrute + '\n Checksum calculé/reçu : ' + sum + ' / ' + ligneBrute.charCodeAt(j+1));
trameEvents.emit('error', err);
}
} else {
var err = new Error('Erreur de checksum : \n' + ligneBrute + '\n Checksum calculé/reçu : ' + sum + ' / ' + checksum);
trameEvents.emit('error', err);
};
return false;
};

function convertDate(str) {
const dateStr = new String((new Date()).getFullYear()).substring(0,2) + str.substring(1,3) + '-' + str.substring(3,5) + '-' + str.substring(5,7) + 'T' + str.substring(7,9) + ':' + str.substring(9,11) + ':' + str.substring(11,13) + 'Z';
const timestamp = Date.parse(dateStr);
if(Number.isNaN(timestamp)) {
return timestamp;
} else {
return new Date(timestamp - (str.substring(0,1) === 'E' ? (2*60*60*1000) : (60*60*1000)));
}
};

module.exports = teleinfo;
module.exports.version = configs;