Merge pull request #9095 from hallard/teleinfo

Teleinfo Added setOption108
This commit is contained in:
Theo Arends 2020-08-17 15:24:52 +02:00 committed by GitHub
commit bfc027a638
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 233 additions and 174 deletions

View File

@ -778,14 +778,13 @@ ValueList * TInfo::checkLine(char * pline)
return NULL;
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) {
for (i=0 ; i<len ; i++) {
// count separator, take care, checksum last one can be space separator
if (*pline==_separator && *(pline+1)!='\r') {
// Label + sep + Date + sep + Etiquette + sep + Checksum
if (++sep >=3){
hasts = true;

View File

@ -46,16 +46,24 @@
// debugging, this should not interfere with main sketch or other
// libraries
#ifdef TI_DEBUG
#ifdef ESP8266
#define TI_Debug(x) Serial1.print(x)
#define TI_Debugln(x) Serial1.println(x)
#define TI_Debugf(...) Serial1.printf(__VA_ARGS__)
#define TI_Debugflush Serial1.flush
// Tasmota build
#ifdef CODE_IMAGE_STR
#define TI_Debug(x) AddLog_P2(LOG_LEVEL_DEBUG, x);
#define TI_Debugln(x) AddLog_P2(LOG_LEVEL_DEBUG, x);
#define TI_Debugf(...) AddLog_P2(LOG_LEVEL_DEBUG, __VA_ARGS__);
#define TI_Debugflush {}
#else
#define TI_Debug(x) Serial.print(x)
#define TI_Debugln(x) Serial.println(x)
#define TI_Debugf(...) Serial.printf(__VA_ARGS__)
#define TI_Debugflush Serial.flush
#ifdef ESP8266
#define TI_Debug(x) Serial1.print(x)
#define TI_Debugln(x) Serial1.println(x)
#define TI_Debugf(...) Serial1.printf(__VA_ARGS__)
#define TI_Debugflush Serial1.flush
#else
#define TI_Debug(x) Serial.print(x)
#define TI_Debugln(x) Serial.println(x)
#define TI_Debugf(...) Serial.printf(__VA_ARGS__)
#define TI_Debugflush Serial.flush
#endif
#endif
#else
#define TI_Debug(x) {}

View File

@ -4,6 +4,7 @@
- Add better config corruption recovery (#9046)
- Remove support for 1-step upgrade from versions before 6.6.0.11 to versions after 8.4.0.1
- Add command ``SetOption108 0/1`` to enable Teleinfo telemetry into Tasmota Energy MQTT (0) or Teleinfo only (1) in this case MQTT will send RAW Teleinfo telemetry on each frame received and not into Tasmota energy calculation telemetry.
- Change White blend mode moved to using ``SetOption 105`` instead of ``RGBWWTable``
- Add Virtual CT for 4 channels lights, emulating a 5th channel

View File

@ -127,8 +127,8 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu
uint32_t white_blend_mode : 1; // bit 23 (v8.4.0.1) - SetOption105 - White Blend Mode - used to be `RGBWWTable` last value `0`, now deprecated in favor of this option
uint32_t virtual_ct : 1; // bit 24 (v8.4.0.1) - SetOption106 - Virtual CT - Creates a virtual White ColorTemp for RGBW lights
uint32_t virtual_ct_cw : 1; // bit 25 (v8.4.0.1) - SetOption107 - Virtual CT Channel - signals whether the hardware white is cold CW (true) or warm WW (false)
uint32_t spare26 : 1; // bit 26
uint32_t spare27 : 1; // bit 27
uint32_t teleinfo_rawdata : 1; // bit 21 (v8.4.0.2) - SetOption108 - enable Teleinfo + Tasmota Energy device (0) or Teleinfo raw data only (1)
uint32_t spare27 : 1;
uint32_t spare28 : 1; // bit 28
uint32_t spare29 : 1; // bit 29
uint32_t spare30 : 1; // bit 30

View File

@ -27,6 +27,7 @@
* {"NAME":"Denky (Teleinfo)","GPIO":[1,1,1,1,5664,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,0,1376,1,1,0,0,0,0,1,5632,1,1,1,0,0,1],"FLAG":0,"BASE":1}
*
* Denky (aka WifInfo) ESP8266 Teleinfo Template
* {"NAME":"WifInfo v1.0a","GPIO":[17,255,255,255,6,5,255,255,7,210,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}
*
\*********************************************************************************************/
@ -107,6 +108,7 @@ const char kLabel[] PROGMEM =
TInfo tinfo; // Teleinfo object
TasmotaSerial *TInfoSerial = nullptr;
_Mode_e tinfo_mode = TINFO_MODE_HISTORIQUE;
char serialNumber[13] = ""; // Serial number is 12 char long
bool tinfo_found = false;
int contrat;
int tarif;
@ -144,19 +146,17 @@ Comments: should have been initialised with a
====================================================================== */
void ADPSCallback(uint8_t phase)
{
char adco[13];
// n = phase number 1 to 3
if (phase == 0){
phase = 1;
}
if (tinfo_mode == TINFO_MODE_HISTORIQUE) {
if (getValueFromLabelIndex(LABEL_ADCO, adco) ) {
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"%s\":{\"ADPS\":%i}}"), adco, phase );
MqttPublishPrefixTopic_P(RESULT_OR_TELE, mqtt_data);
}
}
Response_P(PSTR("{"));
ResponseAppend_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"%s\":{\"ADPS\":%i}}"), serialNumber, phase );
ResponseJsonEnd();
// Publish adding ADCO serial number into the topic
MqttPublishPrefixTopic_P(RESULT_OR_TELE, serialNumber, false);
AddLog_P2(LOG_LEVEL_INFO, PSTR("ADPS on phase %d"), phase);
}
@ -185,132 +185,191 @@ void DataCallback(struct _ValueList * me, uint8_t flags)
}
}
// Current tariff (legacy)
if (ilabel == LABEL_PTEC)
{
char tarif_value[] = " "; // 4 spaces
// Find the tariff index
for (tarif = TARIF_TH ; tarif < TARIF_END ; tarif++) {
GetTextIndexed(tarif_value, sizeof(tarif_value), tarif-1, kTarifValue);
if (!strcmp(tarif_value, me->value)) {
break;
if (flags & TINFO_FLAGS_ADDED) { c = '#'; }
if (flags & TINFO_FLAGS_UPDATED) { c = '*'; }
if (flags & TINFO_FLAGS_STRING) { c = '$'; }
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: [%d]%c %s=%s"), ilabel, c , me->name, me->value);
if (ilabel<LABEL_END) {
// Current tariff (legacy)
if (ilabel == LABEL_PTEC)
{
char tarif_value[] = " "; // 4 spaces
// Find the tariff index
for (tarif = TARIF_TH ; tarif < TARIF_END ; tarif++) {
GetTextIndexed(tarif_value, sizeof(tarif_value), tarif-1, kTarifValue);
if (!strcmp(tarif_value, me->value)) {
break;
}
}
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)
else if ( ilabel == LABEL_TENSION || ilabel == LABEL_URMS1)
{
Energy.voltage_available = true;
Energy.voltage[0] = (float) atoi(me->value);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: Voltage %s, now %d"), me->value, (int) Energy.voltage[0]);
}
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)
else if ( ilabel == LABEL_TENSION || ilabel == LABEL_URMS1)
{
Energy.voltage_available = true;
Energy.voltage[0] = (float) atoi(me->value);
// Update current
if (Energy.voltage_available && Energy.voltage[0]) {
Energy.current[0] = Energy.active_power[0] / Energy.voltage[0] ;
}
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: Voltage %s, now %d"), me->value, (int) Energy.voltage[0]);
}
// Current I
else if (ilabel == LABEL_IINST || ilabel == LABEL_IRMS1)
{
if (!Energy.voltage_available) {
// Current I
else if (ilabel == LABEL_IINST || ilabel == LABEL_IRMS1)
{
Energy.current[0] = (float) atoi(me->value);
} else if (Energy.voltage[0]) {
Energy.current[0] = Energy.active_power[0] / Energy.voltage[0] ;
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: Current %s, now %d"), me->value, (int) Energy.current[0]);
}
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: Current %s, now %d"), me->value, (int) Energy.current[0]);
}
// Power P
else if (ilabel == LABEL_PAPP || ilabel == LABEL_SINSTS)
{
int papp = atoi(me->value);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: Power %s, now %d"), me->value, papp);
Energy.active_power[0] = (float) atoi(me->value);
// Update current
if (Energy.voltage_available && Energy.voltage[0]) {
Energy.current[0] = Energy.active_power[0] / Energy.voltage[0] ;
// Power P
else if (ilabel == LABEL_PAPP || ilabel == LABEL_SINSTS)
{
Energy.active_power[0] = (float) atoi(me->value);;
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: Power %s, now %d"), me->value, (int) Energy.active_power[0]);
}
}
// Wh indexes (legacy)
else if ( ilabel == LABEL_HCHC || ilabel == LABEL_HCHP)
{
char value[32];
uint32_t hc = 0;
uint32_t hp = 0;
uint32_t total = 0;
// Wh indexes (legacy)
else if ( ilabel == LABEL_HCHC || ilabel == LABEL_HCHP)
{
char value[32];
uint32_t hc = 0;
uint32_t hp = 0;
uint32_t total = 0;
if ( getValueFromLabelIndex(LABEL_HCHC, value) ) { hc = atoi(value);}
if ( getValueFromLabelIndex(LABEL_HCHP, value) ) { hp = atoi(value);}
total = hc + hp;
EnergyUpdateTotal(total/1000.0f, true);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: HC:%u HP:%u Total:%u"), hc, hp, total);
}
if ( getValueFromLabelIndex(LABEL_HCHC, value) ) { hc = atoi(value);}
if ( getValueFromLabelIndex(LABEL_HCHP, value) ) { hp = atoi(value);}
total = hc + hp;
// 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)
{
char contrat_value[] = " "; // 4 spaces
// Find the contract index
for (contrat = CONTRAT_BAS ; contrat < CONTRAT_END ; contrat++) {
GetTextIndexed(contrat_value, sizeof(contrat_value), contrat, kContratValue);
if (!strcmp(contrat_value, me->value)) {
break;
if (!Settings.flag4.teleinfo_rawdata) {
EnergyUpdateTotal(total/1000.0f, true);
}
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: HC:%u HP:%u Total:%u"), hc, hp, total);
}
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)
else if (ilabel == LABEL_ISOUSC || ilabel == LABEL_PREF)
{
isousc = atoi( me->value);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: ISousc set to %d"), isousc);
}
// Wh total index (standard)
else if ( ilabel == LABEL_EAST)
{
uint32_t total = atoi(me->value);
if (!Settings.flag4.teleinfo_rawdata) {
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)
{
char contrat_value[] = " "; // 4 spaces
// Find the contract index
for (contrat = CONTRAT_BAS ; contrat < CONTRAT_END ; contrat++) {
GetTextIndexed(contrat_value, sizeof(contrat_value), contrat, kContratValue);
if (!strcmp(contrat_value, me->value)) {
break;
}
}
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)
else if (ilabel == LABEL_ISOUSC || ilabel == LABEL_PREF)
{
isousc = atoi( me->value);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: ISousc set to %d"), isousc);
}
// Serial Number of device
else if (ilabel == LABEL_ADCO || ilabel == LABEL_ADSC)
{
strcpy(serialNumber, me->value);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: %s set to %s"), me->name, serialNumber);
}
}
}
if (flags & TINFO_FLAGS_ADDED) { c = '#'; }
if (flags & TINFO_FLAGS_UPDATED) { c = '*'; }
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("TIC: %c %s=%s"),c , me->name, me->value);
}
/* ======================================================================
Function: responseDumpTInfo
Purpose : add teleinfo values into JSON response
Input : -
Output : -
Comments: -
====================================================================== */
void ResponseAppendTInfo()
{
struct _ValueList * me = tinfo.getList();
char sep = ' '; // First JSON value separator
char * p ;
boolean isNumber ;
// Loop thru all the teleinfo frame but
// always check we don't buffer overflow of MQTT data
while (me->next) {
// go to next node
me = me->next;
if (me->name && me->value && *me->name && *me->value) {
isNumber = true;
p = me->value;
// Specific treatment serial number don't convert to number later
if (strcmp(me->name, "ADCO")==0 || strcmp(me->name, "ADSC")==0) {
isNumber = false;
} else {
// check if value is number
while (*p && isNumber) {
if ( *p < '0' || *p > '9' ) {
isNumber = false;
}
p++;
}
}
ResponseAppend_P( PSTR("%c\"%s\":"), sep, me->name );
if (!isNumber || (me->flags & TINFO_FLAGS_STRING) ) {
ResponseAppend_P( PSTR("\"%s\""), me->value );
} else {
ResponseAppend_P( PSTR("%d"), atoi(me->value));
}
// Now JSON separator is needed
sep =',';
}
}
}
/* ======================================================================
@ -324,6 +383,17 @@ void NewFrameCallback(struct _ValueList * me)
{
// Reset Energy Watchdog
Energy.data_valid[0] = 0;
// send teleinfo full frame only if setup like that
// see setOption108
if (Settings.flag4.teleinfo_rawdata) {
Response_P(PSTR("{"));
ResponseAppendTInfo();
ResponseJsonEnd();
// Publish adding ADCO serial number into the topic
// Need setOption4 to be enabled
MqttPublishPrefixTopic_P(RESULT_OR_TELE, serialNumber, false);
}
}
/* ======================================================================
@ -335,10 +405,8 @@ Comments: -
====================================================================== */
void TInfoDrvInit(void) {
if (PinUsed(GPIO_TELEINFO_RX)) {
energy_flg = XNRG_15;
Energy.voltage_available = false;
//Energy.current_available = false;
Energy.type_dc = true;
energy_flg = XNRG_15;
Energy.voltage_available = false;
}
}
@ -433,14 +501,16 @@ Comments: -
====================================================================== */
void TInfoEvery250ms(void)
{
char c;
if (!tinfo_found)
if (!tinfo_found) {
return;
}
if (TInfoSerial->available()) {
//AddLog_P2(LOG_LEVEL_INFO, PSTR("TIC: received %d chars"), TInfoSerial->available());
// We received some data?
while (TInfoSerial->available()>8) {
unsigned long start = millis();
char c;
// We received some data, process but never more than 100ms ?
while (TInfoSerial->available()>8 && millis()-start < 100) {
// get char
c = TInfoSerial->read();
// data processing
@ -457,6 +527,7 @@ Output : -
Comments: -
====================================================================== */
#ifdef USE_WEBSERVER
const char HTTP_ENERGY_ID_TELEINFO[] PROGMEM = "{s}ID{m}%s{e}" ;
const char HTTP_ENERGY_INDEX_TELEINFO[] PROGMEM = "{s}%s{m}%s " D_UNIT_WATTHOUR "{e}" ;
const char HTTP_ENERGY_PAPP_TELEINFO[] PROGMEM = "{s}" D_POWERUSAGE "{m}%d " D_UNIT_WATT "{e}" ;
const char HTTP_ENERGY_IINST_TELEINFO[] PROGMEM = "{s}" D_CURRENT "{m}%d " D_UNIT_AMPERE "{e}" ;
@ -469,46 +540,19 @@ const char HTTP_ENERGY_PMAX_TELEINFO[] PROGMEM = "{s}" D_MAX_POWER "{m}%d" D_UN
void TInfoShow(bool json)
{
char name[32];
char value[32];
// Since it's an Energy device , current, voltage and power are
// already present on the telemetry frame. No need to add here
// Just add the raw label/values of the teleinfo frame
if (json)
{
struct _ValueList * me = tinfo.getList();
// Calculated values
if (isousc) {
ResponseAppend_P(PSTR(",\"Load\":%d"),(int) ((Energy.current[0]*100.0f) / isousc));
}
// Loop thru all the teleinfo frame
while (me->next) {
// go to next node
me = me->next;
if (me->name && me->value && *me->name && *me->value) {
boolean isNumber = true;
char * p = me->value;
// check if value is number
while (*p && isNumber) {
if ( *p < '0' || *p > '9' ) {
isNumber = false;
}
p++;
}
// this will add "" on not number values
ResponseAppend_P(PSTR(",\"%s\":"), me->name);
if (!isNumber) {
ResponseAppend_P(PSTR("\"%s\""), me->value);
} else {
ResponseAppend_P(PSTR("%u"), atol(me->value));
}
}
// add teleinfo full frame only if no teleinfo raw data setup
if (!Settings.flag4.teleinfo_rawdata) {
ResponseAppendTInfo();
}
@ -516,6 +560,9 @@ void TInfoShow(bool json)
}
else
{
char name[32];
char value[32];
if (getValueFromLabelIndex(LABEL_HCHC, value) ) {
GetTextIndexed(name, sizeof(name), LABEL_HCHC, kLabel);
WSContentSend_PD(HTTP_ENERGY_INDEX_TELEINFO, name, value);
@ -531,7 +578,7 @@ void TInfoShow(bool json)
WSContentSend_PD(HTTP_ENERGY_PMAX_TELEINFO, atoi(value));
}
if (tinfo_mode==TINFO_MODE_STANDARD ) {
if (tinfo_mode==TINFO_MODE_HISTORIQUE ) {
if (tarif) {
GetTextIndexed(name, sizeof(name), tarif-1, kTarifName);
WSContentSend_PD(HTTP_ENERGY_TARIF_TELEINFO, name);
@ -542,7 +589,7 @@ void TInfoShow(bool json)
WSContentSend_PD(HTTP_ENERGY_CONTRAT_TELEINFO, name, isousc);
WSContentSend_PD(HTTP_ENERGY_LOAD_TELEINFO, percent);
}
} else {
} else if (tinfo_mode==TINFO_MODE_STANDARD ) {
if (getValueFromLabelIndex(LABEL_LTARF, name) ) {
WSContentSend_PD(HTTP_ENERGY_TARIF_TELEINFO, name);
}
@ -555,6 +602,10 @@ void TInfoShow(bool json)
}
}
}
// Serial number ADCO or ADSC
WSContentSend_PD(HTTP_ENERGY_ID_TELEINFO, serialNumber);
#endif // USE_WEBSERVER
}
}