Merge pull request #9075 from hallard/teleinfo

Teleinfo added support for Linky standard mode
This commit is contained in:
Theo Arends 2020-08-11 20:50:51 +02:00 committed by GitHub
commit 21f7056fce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 290 additions and 77 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;
@ -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) {
@ -625,12 +655,57 @@ 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
if (hasts) {
if (!pts) {
pts = p;
} else {
// 2nd separator, it's the label value
if (!pvalue) {
pvalue = p; pvalue = p;
else } else {
// 2nd space, so it's the checksum // 3rd separator so it's the checksum
checksum = *p; checksum = *p;
} }
// new line ? ok we got all we need ? }
// 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' ) { 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);

View File

@ -7,6 +7,7 @@
- Add ESP32 Analog input support for GPIO32 to GPIO39 - Add ESP32 Analog input support for GPIO32 to GPIO39
- Add Zigbee options to ``ZbSend`` ``Config`` and ``ReadCondig`` - Add Zigbee options to ``ZbSend`` ``Config`` and ``ReadCondig``
- Add command ``Restart 2`` to halt system. Needs hardware reset or power cycle to restart (#9046) - Add command ``Restart 2`` to halt system. Needs hardware reset or power cycle to restart (#9046)
- Add command ``SetOption102 0/1`` to switch between Teleinfo French Metering mode, legacy 1200 bps (0) or Linky standard 9600 bps (1)
### 8.4.0 20200730 ### 8.4.0 20200730

View File

@ -121,7 +121,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu
uint32_t zerocross_dimmer : 1; // bit 17 (v8.3.1.4) - SetOption99 - Enable zerocross dimmer on PWM DIMMER uint32_t zerocross_dimmer : 1; // bit 17 (v8.3.1.4) - SetOption99 - Enable zerocross dimmer on PWM DIMMER
uint32_t remove_zbreceived : 1; // bit 18 (v8.3.1.7) - SetOption100 - Remove ZbReceived form JSON message uint32_t remove_zbreceived : 1; // bit 18 (v8.3.1.7) - SetOption100 - Remove ZbReceived form JSON message
uint32_t zb_index_ep : 1; // bit 19 (v8.3.1.7) - SetOption101 - Add the source endpoint as suffix to attributes, ex `Power3` instead of `Power` if sent from endpoint 3 uint32_t zb_index_ep : 1; // bit 19 (v8.3.1.7) - SetOption101 - Add the source endpoint as suffix to attributes, ex `Power3` instead of `Power` if sent from endpoint 3
uint32_t spare20 : 1; uint32_t teleinfo_baudrate : 1; // bit 20 (v8.4.0.1) - SetOption102 - Set Baud rate for Teleinfo communication (0 = 1200 or 1 = 9600)
uint32_t spare21 : 1; uint32_t spare21 : 1;
uint32_t spare22 : 1; uint32_t spare22 : 1;
uint32_t spare23 : 1; uint32_t spare23 : 1;

View File

@ -904,6 +904,7 @@ void CmndSetoption(void)
case 3: // SetOption85 - Enable Device Groups case 3: // SetOption85 - Enable Device Groups
case 6: // SetOption88 - PWM Dimmer Buttons control remote devices case 6: // SetOption88 - PWM Dimmer Buttons control remote devices
case 15: // SetOption97 - Set Baud rate for TuyaMCU serial communication (0 = 9600 or 1 = 115200) case 15: // SetOption97 - Set Baud rate for TuyaMCU serial communication (0 = 9600 or 1 = 115200)
case 20: // SetOption102 - Set Baud rate for Teleinfo serial communication (0 = 1200 or 1 = 9600)
restart_flag = 2; restart_flag = 2;
break; break;
} }

View File

@ -30,7 +30,6 @@
* {"NAME":"WifInfo","GPIO":[7,255,255,210,6,5,255,255,255,255,255,255,255],"FLAG":15,"BASE":18} * {"NAME":"WifInfo","GPIO":[7,255,255,210,6,5,255,255,255,255,255,255,255],"FLAG":15,"BASE":18}
* *
\*********************************************************************************************/ \*********************************************************************************************/
#define XNRG_15 15 #define XNRG_15 15
#include "LibTeleinfo.h" #include "LibTeleinfo.h"
@ -38,7 +37,7 @@
#define TINFO_READ_TIMEOUT 400 #define TINFO_READ_TIMEOUT 400
// All contract type // All contract type for legacy, standard mode has in clear text
enum TInfoContrat{ enum TInfoContrat{
CONTRAT_BAS = 1, // BASE => Option Base. CONTRAT_BAS = 1, // BASE => Option Base.
CONTRAT_HC, // HC.. => Option Heures Creuses. CONTRAT_HC, // HC.. => Option Heures Creuses.
@ -47,17 +46,17 @@ enum TInfoContrat{
CONTRAT_END CONTRAT_END
}; };
// contract displayed name // contract displayed name for legacy, standard mode has in clear text
const char kContratName[] PROGMEM = const char kContratName[] PROGMEM =
"|Base|Heures Creuses|EJP|Bleu Blanc Rouge" "|Base|Heures Creuses|EJP|Bleu Blanc Rouge"
; ;
// Received current contract value // Received current contract value for legacy, standard mode has in clear text
const char kContratValue[] PROGMEM = const char kContratValue[] PROGMEM =
"|BASE|HC..|EJP.|BBR" "|BASE|HC..|EJP.|BBR"
; ;
// all tariff type // all tariff type for legacy, standard mode has in clear text
enum TInfoTarif{ enum TInfoTarif{
TARIF_TH = 1, TARIF_TH = 1,
TARIF_HC, TARIF_HP, TARIF_HC, TARIF_HP,
@ -68,6 +67,7 @@ enum TInfoTarif{
}; };
// Received current tariff values // Received current tariff values
// for legacy, standard mode has in clear text
const char kTarifValue[] PROGMEM = const char kTarifValue[] PROGMEM =
"|TH..|HC..|HP.." "|TH..|HC..|HP.."
"|HN..|PM.." "|HN..|PM.."
@ -75,7 +75,7 @@ const char kTarifValue[] PROGMEM =
"|HPJB|HPJW|HPJR" "|HPJB|HPJW|HPJR"
; ;
// tariff displayed name // tariff displayed name (for legacy, standard mode has in clear text)
const char kTarifName[] PROGMEM = const char kTarifName[] PROGMEM =
"|Toutes|Creuses|Pleines" "|Toutes|Creuses|Pleines"
"|Normales|Pointe Mobile" "|Normales|Pointe Mobile"
@ -83,27 +83,30 @@ const char kTarifName[] PROGMEM =
"|Pleines Bleu|Pleines Blanc|Pleines Rouges" "|Pleines Bleu|Pleines Blanc|Pleines Rouges"
; ;
// Label used to do some post processing and/or calculation
enum TInfoLabel{ enum TInfoLabel{
LABEL_BASE = 1, LABEL_BASE = 1,
LABEL_ADCO, LABEL_ADCO, LABEL_ADSC,
LABEL_HCHC, LABEL_HCHP, LABEL_HCHC, LABEL_HCHP, LABEL_EAST, LABEL_EASF01, LABEL_EASF02,
LABEL_OPTARIF, LABEL_ISOUSC, LABEL_PTEC, LABEL_OPTARIF, LABEL_NGTF, LABEL_ISOUSC, LABEL_PREF, LABEL_PTEC, LABEL_LTARF, LABEL_NTARF,
LABEL_PAPP, LABEL_IINST, LABEL_TENSION, LABEL_PAPP, LABEL_SINSTS, LABEL_IINST, LABEL_IRMS1, LABEL_TENSION, LABEL_URMS1,
LABEL_IMAX, LABEL_PMAX, LABEL_IMAX, LABEL_PMAX, LABEL_SMAXSN,
LABEL_DEMAIN, LABEL_DEMAIN,
LABEL_END LABEL_END
}; };
const char kLabel[] PROGMEM = const char kLabel[] PROGMEM =
"|BASE|ADCO|HCHC|HCHP" "|BASE|ADCO|ADSC"
"|OPTARIF|ISOUSC|PTEC" "|HCHC|HCHP|EAST|EASF01|EASF02"
"|PAPP|IINST|TENSION" "|OPTARIF|NGTF|ISOUSC|PREF|PTEC|LTARF|NTARF"
"|IMAX|PMAX" "|PAPP|SINSTS|IINST|IRMS1|TENSION|URMS1"
"|IMAX|PMAX|SMAXSN"
"|DEMAIN" "|DEMAIN"
; ;
TInfo tinfo; // Teleinfo object TInfo tinfo; // Teleinfo object
TasmotaSerial *TInfoSerial = nullptr; TasmotaSerial *TInfoSerial = nullptr;
_Mode_e tinfo_mode = TINFO_MODE_HISTORIQUE;
bool tinfo_found = false; bool tinfo_found = false;
int contrat; int contrat;
int tarif; int tarif;
@ -148,10 +151,12 @@ void ADPSCallback(uint8_t phase)
phase = 1; phase = 1;
} }
if (getValueFromLabelIndex(LABEL_ADCO, adco)) { if (tinfo_mode == TINFO_MODE_HISTORIQUE) {
if (getValueFromLabelIndex(LABEL_ADCO, adco) ) {
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"%s\":{\"ADPS\":%i}}"), adco, phase ); snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"%s\":{\"ADPS\":%i}}"), adco, phase );
MqttPublishPrefixTopic_P(RESULT_OR_TELE, mqtt_data); MqttPublishPrefixTopic_P(RESULT_OR_TELE, mqtt_data);
} }
}
AddLog_P2(LOG_LEVEL_INFO, PSTR("ADPS on phase %d"), phase); AddLog_P2(LOG_LEVEL_INFO, PSTR("ADPS on phase %d"), phase);
} }
@ -180,7 +185,7 @@ void DataCallback(struct _ValueList * me, uint8_t flags)
} }
} }
// Current tariff // Current tariff (legacy)
if (ilabel == LABEL_PTEC) if (ilabel == LABEL_PTEC)
{ {
char tarif_value[] = " "; // 4 spaces char tarif_value[] = " "; // 4 spaces
@ -194,8 +199,21 @@ void DataCallback(struct _ValueList * me, uint8_t flags)
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: Tarif changed, now '%s' (%d)"), me->value, tarif); AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: Tarif changed, now '%s' (%d)"), me->value, tarif);
} }
// Current tariff (standard is in clear text in value)
else if (ilabel == LABEL_LTARF)
{
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: Tarif name changed, now '%s'"), me->value);
}
// Current tariff (standard index is is in clear text in value)
else if (ilabel == LABEL_NTARF)
{
tarif = atoi(me->value);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: Tarif index changed, now '%d'"), tarif);
}
// Voltage V (not present on all Smart Meter) // Voltage V (not present on all Smart Meter)
else if ( ilabel == LABEL_TENSION) else if ( ilabel == LABEL_TENSION || ilabel == LABEL_URMS1)
{ {
Energy.voltage_available = true; Energy.voltage_available = true;
Energy.voltage[0] = (float) atoi(me->value); Energy.voltage[0] = (float) atoi(me->value);
@ -207,7 +225,7 @@ void DataCallback(struct _ValueList * me, uint8_t flags)
} }
// Current I // Current I
else if (ilabel == LABEL_IINST) else if (ilabel == LABEL_IINST || ilabel == LABEL_IRMS1)
{ {
if (!Energy.voltage_available) { if (!Energy.voltage_available) {
Energy.current[0] = (float) atoi(me->value); Energy.current[0] = (float) atoi(me->value);
@ -218,7 +236,7 @@ void DataCallback(struct _ValueList * me, uint8_t flags)
} }
// Power P // Power P
else if (ilabel == LABEL_PAPP) else if (ilabel == LABEL_PAPP || ilabel == LABEL_SINSTS)
{ {
int papp = atoi(me->value); int papp = atoi(me->value);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: Power %s, now %d"), me->value, papp); AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: Power %s, now %d"), me->value, papp);
@ -229,7 +247,7 @@ void DataCallback(struct _ValueList * me, uint8_t flags)
} }
} }
// Wh indexes // Wh indexes (legacy)
else if ( ilabel == LABEL_HCHC || ilabel == LABEL_HCHP) else if ( ilabel == LABEL_HCHC || ilabel == LABEL_HCHP)
{ {
char value[32]; char value[32];
@ -244,7 +262,25 @@ void DataCallback(struct _ValueList * me, uint8_t flags)
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: HC:%u HP:%u Total:%u"), hc, hp, total); AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: HC:%u HP:%u Total:%u"), hc, hp, total);
} }
// Contract subscribed // Wh total index (standard)
else if ( ilabel == LABEL_EAST)
{
uint32_t total = atoi(me->value);
EnergyUpdateTotal(total/1000.0f, true);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: Total:%uWh"), total);
}
// Wh indexes (standard)
else if ( ilabel == LABEL_EASF01)
{
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: HC:%u"), atoi(me->value));
}
else if ( ilabel == LABEL_EASF02)
{
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: HP:%u"), atoi(me->value));
}
// Contract subscribed (legacy)
else if (ilabel == LABEL_OPTARIF) else if (ilabel == LABEL_OPTARIF)
{ {
char contrat_value[] = " "; // 4 spaces char contrat_value[] = " "; // 4 spaces
@ -257,9 +293,14 @@ void DataCallback(struct _ValueList * me, uint8_t flags)
} }
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: Contract changed, now '%s' (%d)"), me->value, contrat); AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: Contract changed, now '%s' (%d)"), me->value, contrat);
} }
// Contract subscribed (standard is in clear text in value)
else if (ilabel == LABEL_NGTF)
{
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: Contract changed, now '%s'"), me->value);
}
// Contract subscribed (Power) // Contract subscribed (Power)
else if (ilabel == LABEL_ISOUSC) else if (ilabel == LABEL_ISOUSC || ilabel == LABEL_PREF)
{ {
isousc = atoi( me->value); isousc = atoi( me->value);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: ISousc set to %d"), isousc); AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: ISousc set to %d"), isousc);
@ -285,7 +326,6 @@ void NewFrameCallback(struct _ValueList * me)
Energy.data_valid[0] = 0; Energy.data_valid[0] = 0;
} }
/* ====================================================================== /* ======================================================================
Function: TInfoDrvInit Function: TInfoDrvInit
Purpose : Tasmota core driver init Purpose : Tasmota core driver init
@ -311,13 +351,20 @@ Comments: -
====================================================================== */ ====================================================================== */
void TInfoInit(void) void TInfoInit(void)
{ {
#ifdef USE_TELEINFO_STANDARD int baudrate;
#define TINFO_SPEED 9600
#else
#define TINFO_SPEED 1200
#endif
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: inferface speed %d bps"),TINFO_SPEED); AddLog_P2(LOG_LEVEL_INFO, PSTR("TIC: inferface saved settings %d bps"), Settings.flag4.teleinfo_baudrate );
// SetOption102 - Set Baud rate for Teleinfo serial communication (0 = 1200 or 1 = 9600)
if (Settings.flag4.teleinfo_baudrate) {
baudrate = 9600;
tinfo_mode = TINFO_MODE_STANDARD;
} else {
baudrate = 1200;
tinfo_mode = TINFO_MODE_HISTORIQUE;
}
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: inferface speed %d bps"),baudrate);
if (PinUsed(GPIO_TELEINFO_RX)) { if (PinUsed(GPIO_TELEINFO_RX)) {
uint8_t rx_pin = Pin(GPIO_TELEINFO_RX); uint8_t rx_pin = Pin(GPIO_TELEINFO_RX);
@ -333,12 +380,17 @@ void TInfoInit(void)
AddLog_P2(LOG_LEVEL_INFO, PSTR("TIC: always enabled")); AddLog_P2(LOG_LEVEL_INFO, PSTR("TIC: always enabled"));
} }
TInfoSerial = new TasmotaSerial(rx_pin, -1, 1); #if defined (ESP8266)
// Allow GPIO3 AND GPIO13 with hardware fallback to 2
TInfoSerial = new TasmotaSerial(rx_pin, -1, 2);
//pinMode(rx_pin, INPUT_PULLUP); //pinMode(rx_pin, INPUT_PULLUP);
#else
TInfoSerial = new TasmotaSerial(rx_pin, -1, 1);
#endif
// Trick here even using SERIAL_7E1 or TS_SERIAL_7E1 // Trick here even using SERIAL_7E1 or TS_SERIAL_7E1
// this is not working, need to call SetSerialConfig after // this is not working, need to call SetSerialConfig after
if (TInfoSerial->begin(TINFO_SPEED)) { if (TInfoSerial->begin(baudrate)) {
#if defined (ESP8266) #if defined (ESP8266)
@ -360,7 +412,7 @@ void TInfoInit(void)
AddLog_P2(LOG_LEVEL_INFO, PSTR("TIC: using ESP32 hardware serial")); AddLog_P2(LOG_LEVEL_INFO, PSTR("TIC: using ESP32 hardware serial"));
#endif #endif
// Init teleinfo // Init teleinfo
tinfo.init(); tinfo.init(tinfo_mode);
// Attach needed callbacks // Attach needed callbacks
tinfo.attachADPS(ADPSCallback); tinfo.attachADPS(ADPSCallback);
tinfo.attachData(DataCallback); tinfo.attachData(DataCallback);
@ -479,6 +531,7 @@ void TInfoShow(bool json)
WSContentSend_PD(HTTP_ENERGY_PMAX_TELEINFO, atoi(value)); WSContentSend_PD(HTTP_ENERGY_PMAX_TELEINFO, atoi(value));
} }
if (tinfo_mode==TINFO_MODE_STANDARD ) {
if (tarif) { if (tarif) {
GetTextIndexed(name, sizeof(name), tarif-1, kTarifName); GetTextIndexed(name, sizeof(name), tarif-1, kTarifName);
WSContentSend_PD(HTTP_ENERGY_TARIF_TELEINFO, name); WSContentSend_PD(HTTP_ENERGY_TARIF_TELEINFO, name);
@ -489,6 +542,19 @@ void TInfoShow(bool json)
WSContentSend_PD(HTTP_ENERGY_CONTRAT_TELEINFO, name, isousc); WSContentSend_PD(HTTP_ENERGY_CONTRAT_TELEINFO, name, isousc);
WSContentSend_PD(HTTP_ENERGY_LOAD_TELEINFO, percent); WSContentSend_PD(HTTP_ENERGY_LOAD_TELEINFO, percent);
} }
} else {
if (getValueFromLabelIndex(LABEL_LTARF, name) ) {
WSContentSend_PD(HTTP_ENERGY_TARIF_TELEINFO, name);
}
if (getValueFromLabelIndex(LABEL_NGTF, name) ) {
if (isousc) {
int percent = (int) ((Energy.current[0]*100.0f) / isousc) ;
WSContentSend_PD(HTTP_ENERGY_CONTRAT_TELEINFO, name, isousc);
WSContentSend_PD(HTTP_ENERGY_LOAD_TELEINFO, percent);
}
}
}
#endif // USE_WEBSERVER #endif // USE_WEBSERVER
} }
} }