From e673724eb4979d4b7876bfe0de5c1d52b2c34b66 Mon Sep 17 00:00:00 2001 From: Charles Date: Tue, 11 Aug 2020 19:56:50 +0200 Subject: [PATCH] Added support for Linky Standard Mode frames --- lib/LibTeleinfo/library.json | 2 +- lib/LibTeleinfo/library.properties | 2 +- lib/LibTeleinfo/src/LibTeleinfo.cpp | 163 ++++++++++++++++++++++++---- lib/LibTeleinfo/src/LibTeleinfo.h | 38 ++++++- 4 files changed, 175 insertions(+), 30 deletions(-) diff --git a/lib/LibTeleinfo/library.json b/lib/LibTeleinfo/library.json index a93105e61..721266bef 100755 --- a/lib/LibTeleinfo/library.json +++ b/lib/LibTeleinfo/library.json @@ -1,6 +1,6 @@ { "name": "LibTeleinfo", - "version": "1.1.2", + "version": "1.1.3", "keywords": "teleinfo, french, meter, power, erdf, linky, tic", "description": "Decoder for Teleinfo (aka TIC) from French smart power meters", "repository": diff --git a/lib/LibTeleinfo/library.properties b/lib/LibTeleinfo/library.properties index 7cfc9a635..89fce0969 100755 --- a/lib/LibTeleinfo/library.properties +++ b/lib/LibTeleinfo/library.properties @@ -1,5 +1,5 @@ name=LibTeleinfo -version=1.1.2 +version=1.1.3 author=Charles-Henri Hallard maintainer=Charles-Henri Hallard sentence=Decoder for Teleinfo (aka TIC) from French smart power meters diff --git a/lib/LibTeleinfo/src/LibTeleinfo.cpp b/lib/LibTeleinfo/src/LibTeleinfo.cpp index 588555831..3d2f9f084 100644 --- a/lib/LibTeleinfo/src/LibTeleinfo.cpp +++ b/lib/LibTeleinfo/src/LibTeleinfo.cpp @@ -15,6 +15,8 @@ // // History : V1.00 2015-06-14 - First release // 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. // @@ -40,6 +42,8 @@ TInfo::TInfo() _valueslist.checksum = '\0'; _valueslist.flags = TINFO_FLAGS_NONE; + _separator = ' '; + // callback _fn_ADPS = NULL; _fn_data = NULL; @@ -50,11 +54,11 @@ TInfo::TInfo() /* ====================================================================== Function: init Purpose : try to guess -Input : - +Input : Mode, historique ou standard Output : - Comments: - ====================================================================== */ -void TInfo::init() +void TInfo::init(_Mode_e mode) { // free up linked list (in case on recall init()) listDelete(); @@ -64,6 +68,13 @@ void TInfo::init() // We're in INIT in term of receive data _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 checksum value flag state of the label (modified by function) + string date (teleinfo format) Output : pointer to the new node (or founded one) 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 ValueList * me = &_valueslist; uint8_t lgname = strlen(name); uint8_t lgvalue = strlen(value); - uint8_t thischeck = calcChecksum(name,value); + uint8_t thischeck = calcChecksum(name,value,horodate); // just some paranoia if (thischeck != checksum ) { TI_Debug(name); TI_Debug('='); TI_Debug(value); + + if (horodate && *horodate) { + TI_Debug(F(" Date=")); + TI_Debug(horodate); + TI_Debug(F(" ")); + } + TI_Debug(F(" '")); TI_Debug((char) checksum); 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 ValueList *newNode = NULL; ValueList *parNode = NULL ; + uint32_t ts = 0; + + if (horodate && *horodate) { + ts = horodate2Timestamp(horodate); + } // Loop thru the node 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) if (lgname==strlen(me->name) && strcmp(me->name, name)==0) { + if (ts) { + me->ts = ts; + } // Already got also this value return US if (lgvalue==strlen(me->value) && strcmp(me->value, value) == 0) { *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 lgvalue = ESP_allocAlign(lgvalue+1); // Align value buffer // Align the whole structure - size = ESP_allocAlign( sizeof(ValueList) + lgname + lgvalue ) ; + size = ESP_allocAlign( sizeof(ValueList) + lgname + lgvalue ) ; #else size = sizeof(ValueList) + lgname + 1 + lgvalue + 1 ; #endif @@ -278,6 +305,8 @@ ValueList * TInfo::valueAdd(char * name, char * value, uint8_t checksum, uint8_t // Copy the string data memcpy(newNode->name , name , lgname ); memcpy(newNode->value, value , lgvalue ); + // Add timestamp + newNode->ts = ts; // So we just created this node but was it new // or was matter of text size ? @@ -421,7 +450,7 @@ char * TInfo::valueGet(char * name, char * value) me = me->next; // 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 ? if (me->value) { // copy to dest buffer @@ -459,7 +488,7 @@ char * TInfo::valueGet_P(const char * name, char * value) me = me->next; // 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 ? if (me->value) { // copy to dest buffer @@ -608,12 +637,13 @@ Function: checksum Purpose : calculate the checksum based on data/value fields Input : label name label value + label timestamp Output : checksum 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 if (etiquette && valeur) { @@ -624,13 +654,58 @@ unsigned char TInfo::calcChecksum(char *etiquette, char *valeur) while(*valeur) sum += *valeur++ ; - - return ( (sum & 63) + ' ' ) ; + + if (horodate) { + sum += _separator; + while (*horodate) + sum += *horodate++ ; + } + + return ( (sum & 0x3f) + ' ' ) ; } } 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 Purpose : do action when received a correct label / value + checksum line @@ -682,11 +757,15 @@ ValueList * TInfo::checkLine(char * pline) char * ptok; char * pend; char * pvalue; + char * pts; char checksum; char buff[TINFO_BUFSIZE]; uint8_t flags = TINFO_FLAGS_NONE; //boolean err = true ; // Assume error int len ; // Group len + int i; + int sep =0; + bool hasts = false ; // Assume timestamp on line if (pline==NULL) return NULL; @@ -695,11 +774,27 @@ ValueList * TInfo::checkLine(char * pline) // a line should be at least 7 Char // 2 Label + Space + 1 etiquette + space + checksum + \r - if ( len < 7 ) + if ( len < 7 || len >= TINFO_BUFSIZE) return NULL; - // Get our own working copy - strlcpy( buff, pline, len+1); + p = &buff[0]; + 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]; ptok = p; // for sure we start with token name @@ -707,29 +802,49 @@ ValueList * TInfo::checkLine(char * pline) // Init values pvalue = NULL; + pts = NULL; checksum = 0; //TI_Debug("Got ["); //TI_Debug(len); //TI_Debug("] "); - // Loop in buffer while ( p < pend ) { // start of token value - if ( *p==' ' && ptok) { + if ( *p==_separator && ptok) { // Isolate token name *p++ = '\0'; - // 1st space, it's the label value - if (!pvalue) - pvalue = p; - else - // 2nd space, so it's the checksum - checksum = *p; + // We have a timestamp + // Label + sep + Date + sep + Etiquette + sep + Checksum + if (hasts) { + if (!pts) { + pts = 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 ? - if ( *p=='\r' ) { *p='\0'; @@ -738,7 +853,7 @@ ValueList * TInfo::checkLine(char * pline) // Always check to avoid bad behavior if(strlen(ptok) && strlen(pvalue)) { // 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 customLabel(ptok, pvalue, &flags); diff --git a/lib/LibTeleinfo/src/LibTeleinfo.h b/lib/LibTeleinfo/src/LibTeleinfo.h index 5b43523c7..45a467320 100755 --- a/lib/LibTeleinfo/src/LibTeleinfo.h +++ b/lib/LibTeleinfo/src/LibTeleinfo.h @@ -15,6 +15,8 @@ // // History : V1.00 2015-06-14 - First release // 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. // @@ -25,7 +27,17 @@ #ifndef LibTeleinfo_h #define LibTeleinfo_h -#include "Arduino.h" +#ifdef __arm__ +#include +#include +#include +#include +#define boolean bool +#endif + +#ifdef ARDUINO +#include +#endif // Define this if you want library to be verbose //#define TI_DEBUG @@ -53,7 +65,9 @@ #endif // For 4 bytes Aligment boundaries +#if defined (ESP8266) || defined (ESP32) #define ESP_allocAlign(size) ((size + 3) & ~((size_t) 3)) +#endif #pragma pack(push) // push current alignment to stack #pragma pack(1) // set alignment to 1 byte boundary @@ -63,6 +77,9 @@ typedef struct _ValueList ValueList; struct _ValueList { ValueList *next; // next element +//#ifdef USE_TELEINFO_STANDARD + time_t ts; // TimeStamp of data if any +//#endif uint8_t checksum;// checksum uint8_t flags; // specific flags char * name; // LABEL of value name @@ -71,6 +88,11 @@ struct _ValueList #pragma pack(pop) +// Library state machine +enum _Mode_e { + TINFO_MODE_HISTORIQUE, // Legacy mode (1200) + TINFO_MODE_STANDARD // Standard mode (9600) +}; // Library state machine enum _State_e { @@ -90,11 +112,17 @@ enum _State_e { // Local buffer for one line of teleinfo // 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 +#endif // Teleinfo start and end of frame characters #define TINFO_STX 0x02 #define TINFO_ETX 0x03 +#define TINFO_HT 0x09 #define TINFO_SGR '\n' // start of group #define TINFO_EGR '\r' // End of group @@ -107,7 +135,7 @@ class TInfo { public: TInfo(); - void init(); + void init(_Mode_e mode = TINFO_MODE_HISTORIQUE); _State_e process (char c); void attachADPS(void (*_fn_ADPS)(uint8_t phase)); void attachData(void (*_fn_data)(ValueList * valueslist, uint8_t state)); @@ -119,20 +147,22 @@ class TInfo char * valueGet(char * name, char * value); char * valueGet_P(const char * name, char * value); boolean listDelete(); - unsigned char calcChecksum(char *etiquette, char *valeur) ; + unsigned char calcChecksum(char *etiquette, char *valeur, char *horodate=NULL) ; private: 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 valueRemoveFlagged(uint8_t flags); int labelCount(); + uint32_t horodate2Timestamp( char * pdate) ; void customLabel( char * plabel, char * pvalue, uint8_t * pflags) ; ValueList * checkLine(char * pline) ; _State_e _state; // Teleinfo machine state ValueList _valueslist; // Linked list of teleinfo values char _recv_buff[TINFO_BUFSIZE]; // line receive buffer + char _separator; uint8_t _recv_idx; // index in receive buffer boolean _frame_updated; // Data on the frame has been updated void (*_fn_ADPS)(uint8_t phase);