Added support for Linky Standard Mode frames

This commit is contained in:
Charles 2020-08-11 19:56:50 +02:00
parent cc37c48fd1
commit e673724eb4
4 changed files with 175 additions and 30 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "LibTeleinfo", "name": "LibTeleinfo",
"version": "1.1.2", "version": "1.1.3",
"keywords": "teleinfo, french, meter, power, erdf, linky, tic", "keywords": "teleinfo, french, meter, power, erdf, linky, tic",
"description": "Decoder for Teleinfo (aka TIC) from French smart power meters", "description": "Decoder for Teleinfo (aka TIC) from French smart power meters",
"repository": "repository":

View File

@ -1,5 +1,5 @@
name=LibTeleinfo name=LibTeleinfo
version=1.1.2 version=1.1.3
author=Charles-Henri Hallard <hallard.me> author=Charles-Henri Hallard <hallard.me>
maintainer=Charles-Henri Hallard <community.hallard.me> maintainer=Charles-Henri Hallard <community.hallard.me>
sentence=Decoder for Teleinfo (aka TIC) from French smart power meters sentence=Decoder for Teleinfo (aka TIC) from French smart power meters

View File

@ -15,6 +15,8 @@
// //
// History : V1.00 2015-06-14 - First release // History : V1.00 2015-06-14 - First release
// V2.00 2020-06-11 - Integration into Tasmota // V2.00 2020-06-11 - Integration into Tasmota
// V2.01 2020-08-11 - Merged LibTeleinfo official and Tasmota version
// Added support for new standard mode of linky smart meter
// //
// All text above must be included in any redistribution. // All text above must be included in any redistribution.
// //
@ -40,6 +42,8 @@ TInfo::TInfo()
_valueslist.checksum = '\0'; _valueslist.checksum = '\0';
_valueslist.flags = TINFO_FLAGS_NONE; _valueslist.flags = TINFO_FLAGS_NONE;
_separator = ' ';
// callback // callback
_fn_ADPS = NULL; _fn_ADPS = NULL;
_fn_data = NULL; _fn_data = NULL;
@ -50,11 +54,11 @@ TInfo::TInfo()
/* ====================================================================== /* ======================================================================
Function: init Function: init
Purpose : try to guess Purpose : try to guess
Input : - Input : Mode, historique ou standard
Output : - Output : -
Comments: - Comments: -
====================================================================== */ ====================================================================== */
void TInfo::init() void TInfo::init(_Mode_e mode)
{ {
// free up linked list (in case on recall init()) // free up linked list (in case on recall init())
listDelete(); listDelete();
@ -64,6 +68,13 @@ void TInfo::init()
// We're in INIT in term of receive data // We're in INIT in term of receive data
_state = TINFO_INIT; _state = TINFO_INIT;
if ( mode == TINFO_MODE_STANDARD ) {
_separator = TINFO_HT;
} else {
_separator = ' ';
}
} }
/* ====================================================================== /* ======================================================================
@ -174,23 +185,31 @@ Input : Pointer to the label name
pointer to the value pointer to the value
checksum value checksum value
flag state of the label (modified by function) flag state of the label (modified by function)
string date (teleinfo format)
Output : pointer to the new node (or founded one) Output : pointer to the new node (or founded one)
Comments: - state of the label changed by the function Comments: - state of the label changed by the function
====================================================================== */ ====================================================================== */
ValueList * TInfo::valueAdd(char * name, char * value, uint8_t checksum, uint8_t * flags) ValueList * TInfo::valueAdd(char * name, char * value, uint8_t checksum, uint8_t * flags, char *horodate)
{ {
// Get our linked list // Get our linked list
ValueList * me = &_valueslist; ValueList * me = &_valueslist;
uint8_t lgname = strlen(name); uint8_t lgname = strlen(name);
uint8_t lgvalue = strlen(value); uint8_t lgvalue = strlen(value);
uint8_t thischeck = calcChecksum(name,value); uint8_t thischeck = calcChecksum(name,value,horodate);
// just some paranoia // just some paranoia
if (thischeck != checksum ) { if (thischeck != checksum ) {
TI_Debug(name); TI_Debug(name);
TI_Debug('='); TI_Debug('=');
TI_Debug(value); TI_Debug(value);
if (horodate && *horodate) {
TI_Debug(F(" Date="));
TI_Debug(horodate);
TI_Debug(F(" "));
}
TI_Debug(F(" '")); TI_Debug(F(" '"));
TI_Debug((char) checksum); TI_Debug((char) checksum);
TI_Debug(F("' Not added bad checksum calculated '")); TI_Debug(F("' Not added bad checksum calculated '"));
@ -202,6 +221,11 @@ ValueList * TInfo::valueAdd(char * name, char * value, uint8_t checksum, uint8_t
// Create pointer on the new node // Create pointer on the new node
ValueList *newNode = NULL; ValueList *newNode = NULL;
ValueList *parNode = NULL ; ValueList *parNode = NULL ;
uint32_t ts = 0;
if (horodate && *horodate) {
ts = horodate2Timestamp(horodate);
}
// Loop thru the node // Loop thru the node
while (me->next) { while (me->next) {
@ -213,6 +237,9 @@ ValueList * TInfo::valueAdd(char * name, char * value, uint8_t checksum, uint8_t
// Check if we already have this LABEL (same name AND same size) // Check if we already have this LABEL (same name AND same size)
if (lgname==strlen(me->name) && strcmp(me->name, name)==0) { if (lgname==strlen(me->name) && strcmp(me->name, name)==0) {
if (ts) {
me->ts = ts;
}
// Already got also this value return US // Already got also this value return US
if (lgvalue==strlen(me->value) && strcmp(me->value, value) == 0) { if (lgvalue==strlen(me->value) && strcmp(me->value, value) == 0) {
*flags |= TINFO_FLAGS_EXIST; *flags |= TINFO_FLAGS_EXIST;
@ -254,7 +281,7 @@ ValueList * TInfo::valueAdd(char * name, char * value, uint8_t checksum, uint8_t
lgname = ESP_allocAlign(lgname+1); // Align name buffer lgname = ESP_allocAlign(lgname+1); // Align name buffer
lgvalue = ESP_allocAlign(lgvalue+1); // Align value buffer lgvalue = ESP_allocAlign(lgvalue+1); // Align value buffer
// Align the whole structure // Align the whole structure
size = ESP_allocAlign( sizeof(ValueList) + lgname + lgvalue ) ; size = ESP_allocAlign( sizeof(ValueList) + lgname + lgvalue ) ;
#else #else
size = sizeof(ValueList) + lgname + 1 + lgvalue + 1 ; size = sizeof(ValueList) + lgname + 1 + lgvalue + 1 ;
#endif #endif
@ -278,6 +305,8 @@ ValueList * TInfo::valueAdd(char * name, char * value, uint8_t checksum, uint8_t
// Copy the string data // Copy the string data
memcpy(newNode->name , name , lgname ); memcpy(newNode->name , name , lgname );
memcpy(newNode->value, value , lgvalue ); memcpy(newNode->value, value , lgvalue );
// Add timestamp
newNode->ts = ts;
// So we just created this node but was it new // So we just created this node but was it new
// or was matter of text size ? // or was matter of text size ?
@ -421,7 +450,7 @@ char * TInfo::valueGet(char * name, char * value)
me = me->next; me = me->next;
// Check if we match this LABEL // Check if we match this LABEL
if (lgname==strlen(me->name) && strncmp(me->name, name, lgname)==0) { if (lgname==strlen(me->name) && strcmp(me->name, name)==0) {
// this one has a value ? // this one has a value ?
if (me->value) { if (me->value) {
// copy to dest buffer // copy to dest buffer
@ -459,7 +488,7 @@ char * TInfo::valueGet_P(const char * name, char * value)
me = me->next; me = me->next;
// Check if we match this LABEL // Check if we match this LABEL
if (lgname==strlen(me->name) && strncmp_P(me->name, name, lgname)==0) { if (lgname==strlen(me->name) && strcmp_P(me->name, name)==0) {
// this one has a value ? // this one has a value ?
if (me->value) { if (me->value) {
// copy to dest buffer // copy to dest buffer
@ -608,12 +637,13 @@ Function: checksum
Purpose : calculate the checksum based on data/value fields Purpose : calculate the checksum based on data/value fields
Input : label name Input : label name
label value label value
label timestamp
Output : checksum Output : checksum
Comments: return '\0' in case of error Comments: return '\0' in case of error
====================================================================== */ ====================================================================== */
unsigned char TInfo::calcChecksum(char *etiquette, char *valeur) unsigned char TInfo::calcChecksum(char *etiquette, char *valeur, char * horodate)
{ {
uint8_t sum = ' '; // Somme des codes ASCII du message + un espace uint8_t sum = _separator ; // Somme des codes ASCII du message + un separateur
// avoid dead loop, always check all is fine // avoid dead loop, always check all is fine
if (etiquette && valeur) { if (etiquette && valeur) {
@ -624,13 +654,58 @@ unsigned char TInfo::calcChecksum(char *etiquette, char *valeur)
while(*valeur) while(*valeur)
sum += *valeur++ ; sum += *valeur++ ;
return ( (sum & 63) + ' ' ) ; if (horodate) {
sum += _separator;
while (*horodate)
sum += *horodate++ ;
}
return ( (sum & 0x3f) + ' ' ) ;
} }
} }
return 0; return 0;
} }
/* ======================================================================
Function: horodate2Timestamp
Purpose : convert string date from frame to timestamp
Input : pdate : pointer to string containing the date SAAMMJJhhmmss
season, year, month, day, hour, minute, second
Output : unix format timestamp
Comments:
====================================================================== */
uint32_t TInfo::horodate2Timestamp( char * pdate)
{
struct tm tm;
time_t ts;
char * p ;
if (pdate==NULL || *pdate=='\0' || strlen(pdate)!=13) {
return 0;
}
p = pdate + strlen(pdate) -2;
tm.tm_sec = atoi(p); *p='\0'; p-=2;
tm.tm_min = atoi(p); *p='\0'; p-=2;
tm.tm_hour = atoi(p); *p='\0'; p-=2;
tm.tm_mday = atoi(p); *p='\0'; p-=2;
tm.tm_mon = atoi(p); *p='\0'; p-=2;
tm.tm_year = atoi(p) + 2000;
tm.tm_year -= 1900;
tm.tm_mon -= 1;
tm.tm_isdst = 0;
ts = mktime(&tm);
if (ts == (time_t)-1) {
TI_Debug(F("Failed to convert time "));
TI_Debugln(pdate);
return 0;
}
return (uint32_t) ts;
}
/* ====================================================================== /* ======================================================================
Function: customLabel Function: customLabel
Purpose : do action when received a correct label / value + checksum line Purpose : do action when received a correct label / value + checksum line
@ -682,11 +757,15 @@ ValueList * TInfo::checkLine(char * pline)
char * ptok; char * ptok;
char * pend; char * pend;
char * pvalue; char * pvalue;
char * pts;
char checksum; char checksum;
char buff[TINFO_BUFSIZE]; char buff[TINFO_BUFSIZE];
uint8_t flags = TINFO_FLAGS_NONE; uint8_t flags = TINFO_FLAGS_NONE;
//boolean err = true ; // Assume error //boolean err = true ; // Assume error
int len ; // Group len int len ; // Group len
int i;
int sep =0;
bool hasts = false ; // Assume timestamp on line
if (pline==NULL) if (pline==NULL)
return NULL; return NULL;
@ -695,11 +774,27 @@ ValueList * TInfo::checkLine(char * pline)
// a line should be at least 7 Char // a line should be at least 7 Char
// 2 Label + Space + 1 etiquette + space + checksum + \r // 2 Label + Space + 1 etiquette + space + checksum + \r
if ( len < 7 ) if ( len < 7 || len >= TINFO_BUFSIZE)
return NULL; return NULL;
// Get our own working copy p = &buff[0];
strlcpy( buff, pline, len+1); i = len + 1 ;
sep = 0;
// Get our own working copy and in the
// meantime, calculate separator count for
// standard mode (to know if timestamped data)
while (i--) {
// count separator
if (*pline == _separator) {
// Label + sep + Date + sep + Etiquette + sep + Checksum
if (++sep >=3){
hasts = true;
}
}
// Copy
*p++ = *pline++;
}
*p = '\0';
p = &buff[0]; p = &buff[0];
ptok = p; // for sure we start with token name ptok = p; // for sure we start with token name
@ -707,29 +802,49 @@ ValueList * TInfo::checkLine(char * pline)
// Init values // Init values
pvalue = NULL; pvalue = NULL;
pts = NULL;
checksum = 0; checksum = 0;
//TI_Debug("Got ["); //TI_Debug("Got [");
//TI_Debug(len); //TI_Debug(len);
//TI_Debug("] "); //TI_Debug("] ");
// Loop in buffer // Loop in buffer
while ( p < pend ) { while ( p < pend ) {
// start of token value // start of token value
if ( *p==' ' && ptok) { if ( *p==_separator && ptok) {
// Isolate token name // Isolate token name
*p++ = '\0'; *p++ = '\0';
// 1st space, it's the label value // We have a timestamp
if (!pvalue) // Label + sep + Date + sep + Etiquette + sep + Checksum
pvalue = p; if (hasts) {
else if (!pts) {
// 2nd space, so it's the checksum pts = p;
checksum = *p; } else {
// 2nd separator, it's the label value
if (!pvalue) {
pvalue = p;
} else {
// 3rd separator so it's the checksum
checksum = *p;
}
}
// No timestamp
// Label + sep + Etiquette + sep + Checksum
} else {
// 1st separator, it's the label value
if (!pvalue) {
pvalue = p;
} else {
// 2nd separator so it's the checksum
checksum = *p;
}
}
} }
// new line ? ok we got all we need ? // new line ? ok we got all we need ?
if ( *p=='\r' ) { if ( *p=='\r' ) {
*p='\0'; *p='\0';
@ -738,7 +853,7 @@ ValueList * TInfo::checkLine(char * pline)
// Always check to avoid bad behavior // Always check to avoid bad behavior
if(strlen(ptok) && strlen(pvalue)) { if(strlen(ptok) && strlen(pvalue)) {
// Is checksum is OK // Is checksum is OK
if ( calcChecksum(ptok,pvalue) == checksum) { if ( calcChecksum(ptok,pvalue,pts) == checksum) {
// In case we need to do things on specific labels // In case we need to do things on specific labels
customLabel(ptok, pvalue, &flags); customLabel(ptok, pvalue, &flags);

View File

@ -15,6 +15,8 @@
// //
// History : V1.00 2015-06-14 - First release // History : V1.00 2015-06-14 - First release
// V2.00 2020-06-11 - Integration into Tasmota // V2.00 2020-06-11 - Integration into Tasmota
// V2.01 2020-08-11 - Merged LibTeleinfo official and Tasmota version
// Added support for new standard mode of linky smart meter
// //
// All text above must be included in any redistribution. // All text above must be included in any redistribution.
// //
@ -25,7 +27,17 @@
#ifndef LibTeleinfo_h #ifndef LibTeleinfo_h
#define LibTeleinfo_h #define LibTeleinfo_h
#include "Arduino.h" #ifdef __arm__
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define boolean bool
#endif
#ifdef ARDUINO
#include <Arduino.h>
#endif
// Define this if you want library to be verbose // Define this if you want library to be verbose
//#define TI_DEBUG //#define TI_DEBUG
@ -53,7 +65,9 @@
#endif #endif
// For 4 bytes Aligment boundaries // For 4 bytes Aligment boundaries
#if defined (ESP8266) || defined (ESP32)
#define ESP_allocAlign(size) ((size + 3) & ~((size_t) 3)) #define ESP_allocAlign(size) ((size + 3) & ~((size_t) 3))
#endif
#pragma pack(push) // push current alignment to stack #pragma pack(push) // push current alignment to stack
#pragma pack(1) // set alignment to 1 byte boundary #pragma pack(1) // set alignment to 1 byte boundary
@ -63,6 +77,9 @@ typedef struct _ValueList ValueList;
struct _ValueList struct _ValueList
{ {
ValueList *next; // next element ValueList *next; // next element
//#ifdef USE_TELEINFO_STANDARD
time_t ts; // TimeStamp of data if any
//#endif
uint8_t checksum;// checksum uint8_t checksum;// checksum
uint8_t flags; // specific flags uint8_t flags; // specific flags
char * name; // LABEL of value name char * name; // LABEL of value name
@ -71,6 +88,11 @@ struct _ValueList
#pragma pack(pop) #pragma pack(pop)
// Library state machine
enum _Mode_e {
TINFO_MODE_HISTORIQUE, // Legacy mode (1200)
TINFO_MODE_STANDARD // Standard mode (9600)
};
// Library state machine // Library state machine
enum _State_e { enum _State_e {
@ -90,11 +112,17 @@ enum _State_e {
// Local buffer for one line of teleinfo // Local buffer for one line of teleinfo
// maximum size, I think it should be enought // maximum size, I think it should be enought
#ifdef USE_TELEINFO_STANDARD
// Linky and standard mode may have longer lines
#define TINFO_BUFSIZE 128
#else
#define TINFO_BUFSIZE 64 #define TINFO_BUFSIZE 64
#endif
// Teleinfo start and end of frame characters // Teleinfo start and end of frame characters
#define TINFO_STX 0x02 #define TINFO_STX 0x02
#define TINFO_ETX 0x03 #define TINFO_ETX 0x03
#define TINFO_HT 0x09
#define TINFO_SGR '\n' // start of group #define TINFO_SGR '\n' // start of group
#define TINFO_EGR '\r' // End of group #define TINFO_EGR '\r' // End of group
@ -107,7 +135,7 @@ class TInfo
{ {
public: public:
TInfo(); TInfo();
void init(); void init(_Mode_e mode = TINFO_MODE_HISTORIQUE);
_State_e process (char c); _State_e process (char c);
void attachADPS(void (*_fn_ADPS)(uint8_t phase)); void attachADPS(void (*_fn_ADPS)(uint8_t phase));
void attachData(void (*_fn_data)(ValueList * valueslist, uint8_t state)); void attachData(void (*_fn_data)(ValueList * valueslist, uint8_t state));
@ -119,20 +147,22 @@ class TInfo
char * valueGet(char * name, char * value); char * valueGet(char * name, char * value);
char * valueGet_P(const char * name, char * value); char * valueGet_P(const char * name, char * value);
boolean listDelete(); boolean listDelete();
unsigned char calcChecksum(char *etiquette, char *valeur) ; unsigned char calcChecksum(char *etiquette, char *valeur, char *horodate=NULL) ;
private: private:
void clearBuffer(); void clearBuffer();
ValueList * valueAdd (char * name, char * value, uint8_t checksum, uint8_t * flags); ValueList * valueAdd (char * name, char * value, uint8_t checksum, uint8_t * flags, char * horodate=NULL);
boolean valueRemove (char * name); boolean valueRemove (char * name);
boolean valueRemoveFlagged(uint8_t flags); boolean valueRemoveFlagged(uint8_t flags);
int labelCount(); int labelCount();
uint32_t horodate2Timestamp( char * pdate) ;
void customLabel( char * plabel, char * pvalue, uint8_t * pflags) ; void customLabel( char * plabel, char * pvalue, uint8_t * pflags) ;
ValueList * checkLine(char * pline) ; ValueList * checkLine(char * pline) ;
_State_e _state; // Teleinfo machine state _State_e _state; // Teleinfo machine state
ValueList _valueslist; // Linked list of teleinfo values ValueList _valueslist; // Linked list of teleinfo values
char _recv_buff[TINFO_BUFSIZE]; // line receive buffer char _recv_buff[TINFO_BUFSIZE]; // line receive buffer
char _separator;
uint8_t _recv_idx; // index in receive buffer uint8_t _recv_idx; // index in receive buffer
boolean _frame_updated; // Data on the frame has been updated boolean _frame_updated; // Data on the frame has been updated
void (*_fn_ADPS)(uint8_t phase); void (*_fn_ADPS)(uint8_t phase);