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",
"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":

View File

@ -1,5 +1,5 @@
name=LibTeleinfo
version=1.1.2
version=1.1.3
author=Charles-Henri Hallard <hallard.me>
maintainer=Charles-Henri Hallard <community.hallard.me>
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
// 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);

View File

@ -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 <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 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);